Merge branch 'epics-base:7.0' into add-simm-to-ao-records

This commit is contained in:
Evan Daykin
2021-06-09 10:19:01 -04:00
334 changed files with 5848 additions and 2555 deletions
+9
View File
@@ -117,6 +117,13 @@ testHarness_SRCS += dbCACTest.cpp
TESTS += dbCaLinkTest
TESTFILES += ../dbCaLinkTest1.db ../dbCaLinkTest2.db ../dbCaLinkTest3.db
TESTPROD_HOST += dbDbLinkTest
dbDbLinkTest_SRCS += dbDbLinkTest.c
dbDbLinkTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
testHarness_SRCS += dbDbLinkTest.c
TESTS += dbDbLinkTest
TESTFILES += ../dbDbLinkTest.db
TESTPROD_HOST += scanIoTest
scanIoTest_SRCS += scanIoTest.c
scanIoTest_SRCS += dbTestIoc_registerRecordDeviceDriver.cpp
@@ -200,7 +207,9 @@ include $(TOP)/configure/RULES
arrRecord$(DEP): $(COMMON_DIR)/arrRecord.h
dbCaLinkTest$(DEP): $(COMMON_DIR)/xRecord.h $(COMMON_DIR)/arrRecord.h
dbDbLinkTest$(DEP): $(COMMON_DIR)/xRecord.h
dbPutLinkTest$(DEP): $(COMMON_DIR)/xRecord.h
dbPutGetTest$(DEP): $(COMMON_DIR)/xRecord.h
dbStressLock$(DEP): $(COMMON_DIR)/xRecord.h
devx$(DEP): $(COMMON_DIR)/xRecord.h
scanIoTest$(DEP): $(COMMON_DIR)/xRecord.h
@@ -54,7 +54,7 @@ typedef struct myPvt {
int resultFail;
} myPvt;
epicsEventId finished;
static epicsEventId finished;
static void myCallback(epicsCallback *pCallback)
{
+1 -1
View File
@@ -54,7 +54,7 @@ typedef struct myPvt {
int resultFail;
} myPvt;
epicsEventId finished;
static epicsEventId finished;
static void myCallback(epicsCallback *pCallback)
@@ -27,6 +27,7 @@
#include "dbAccess.h"
#include "epicsStdio.h"
#include "dbEvent.h"
#include "shareLib.h"
/* Declarations from cadef.h and db_access.h which we can't include here */
typedef void * chid;
+136
View File
@@ -0,0 +1,136 @@
/*************************************************************************\
* Copyright (c) 2020 Michael Davidsaver
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include <dbUnitTest.h>
#include <testMain.h>
#include <errlog.h>
#include <epicsTime.h>
#include <dbLock.h>
#include <dbAccess.h>
#include <recGbl.h>
#include <alarm.h>
#include "xRecord.h"
void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
static
void checkTime(void)
{
epicsTimeStamp stamp;
epicsUTag tag;
dbCommon* target = testdbRecordPtr("target");
dbCommon* src = testdbRecordPtr("src");
testDiag("checkTime()");
dbScanLock(target);
target->time.secPastEpoch = 0x12345678;
target->time.nsec = 0x9abcdef0;
target->utag = 0xdeadbeef;
dbScanUnlock(target);
dbScanLock(src);
testOk1(0==dbGetTimeStamp(dbGetDevLink(src), &stamp));
dbScanUnlock(src);
testOk1(stamp.secPastEpoch==0x12345678);
testOk1(stamp.nsec==0x9abcdef0);
stamp.secPastEpoch = 0;
stamp.nsec = 0;
dbScanLock(src);
testOk1(0==dbGetTimeStampTag(dbGetDevLink(src), &stamp, &tag));
dbScanUnlock(src);
testOk1(stamp.secPastEpoch==0x12345678);
testOk1(stamp.nsec==0x9abcdef0);
testOk1(tag==0xdeadbeef);
}
static
void alarmProc(xRecord *prec)
{
recGblSetSevrMsg(prec, READ_ALARM, MAJOR_ALARM, "a %s", "message");
prec->val = 0;
}
static
void checkAlarm(void)
{
epicsEnum16 stat, sevr;
xRecord* target = (xRecord*)testdbRecordPtr("target");
dbCommon* src = testdbRecordPtr("src");
char amsg[sizeof(src->amsg)];
testDiag("checkAlarm()");
dbScanLock((dbCommon*)target);
target->clbk = &alarmProc;
dbProcess((dbCommon*)target);
target->clbk = NULL;
dbScanUnlock((dbCommon*)target);
dbScanLock(src);
testOk1(0==dbGetAlarm(dbGetDevLink(src), &stat, &sevr));
dbScanUnlock(src);
testOk1(stat==READ_ALARM);
testOk1(sevr==MAJOR_ALARM);
stat = sevr = 0;
dbScanLock(src);
testOk1(0==dbGetAlarmMsg(dbGetDevLink(src), &stat, &sevr, amsg, sizeof(amsg)));
dbScanUnlock(src);
testOk1(stat==READ_ALARM);
testOk1(sevr==MAJOR_ALARM);
testOk1(strcmp(amsg, "a message")==0);
stat = sevr = 0;
memset(amsg, 0, sizeof(amsg));
dbScanLock(src);
testOk1(0==dbGetAlarmMsg(dbGetDevLink(src), &stat, &sevr, amsg, 5));
dbScanUnlock(src);
testOk1(stat==READ_ALARM);
testOk1(sevr==MAJOR_ALARM);
testOk1(strcmp(amsg, "a me")==0);
}
MAIN(dbDbLinkTest)
{
testPlan(18);
testdbPrepare();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
dbTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("dbDbLinkTest.db", NULL, NULL);
eltc(0);
testIocInitOk();
eltc(1);
checkTime();
checkAlarm();
testIocShutdownOk();
testdbCleanup();
return testDone();
}
@@ -0,0 +1,6 @@
record(x, "target") {
}
record(x, "src") {
field(INP, "target.VAL")
}
+180 -1
View File
@@ -7,12 +7,186 @@
#include <string.h>
#include <errlog.h>
#include <alarm.h>
#include <dbAccess.h>
#include <dbStaticLib.h>
#include <dbStaticPvt.h>
#include <dbChannel.h>
#include <dbEvent.h>
#include <dbUnitTest.h>
#include <testMain.h>
#include "xRecord.h"
typedef struct {
DBRstatus
DBRamsg
DBRunits
DBRprecision
DBRtime
DBRutag
DBRgrDouble
DBRctrlDouble
DBRalDouble
} dbMetaDouble;
enum {dbMetaDoubleMask = DBR_STATUS | DBR_AMSG | DBR_UNITS | DBR_PRECISION | DBR_TIME | DBR_UTAG | DBR_GR_DOUBLE | DBR_CTRL_DOUBLE | DBR_AL_DOUBLE};
static
void testdbMetaDoubleSizes(void)
{
dbMetaDouble meta;
size_t pos=0;
testDiag("dbMetaDouble may not have padding");
#define testOffset(FLD) do {\
testOk(offsetof(dbMetaDouble, FLD)==pos, "offset(meta, " #FLD "), %u == %u", (unsigned)offsetof(dbMetaDouble, FLD), (unsigned)pos); \
pos += sizeof(meta.FLD); \
}while(0)
testOffset(status);
testOffset(severity);
testOffset(acks);
testOffset(ackt);
testOffset(amsg);
testOffset(units);
testOffset(precision);
testOffset(time);
testOffset(utag);
testOffset(upper_disp_limit);
testOffset(lower_disp_limit);
testOffset(upper_ctrl_limit);
testOffset(lower_ctrl_limit);
testOffset(upper_alarm_limit);
testOffset(upper_warning_limit);
testOffset(lower_warning_limit);
testOffset(lower_alarm_limit);
#undef testOffset
testOk(sizeof(dbMetaDouble)==pos, "sizeof(dbMetaDouble), %u == %u", (unsigned)sizeof(dbMetaDouble), (unsigned)pos);
}
static
void checkDoubleGet(dbChannel *chan, db_field_log* pfl)
{
dbMetaDouble meta;
long options = (long)dbMetaDoubleMask;
long nReq = 0;
long status;
status=dbChannelGet(chan, DBF_DOUBLE, &meta, &options, &nReq, pfl);
testOk(status==0, "dbGet OTST : %ld", status);
testOk1(meta.severity==INVALID_ALARM);
testOk1(meta.status==UDF_ALARM);
testOk1(meta.acks==MAJOR_ALARM);
testOk1(meta.ackt==1);
testOk1(strncmp(meta.amsg, "oops", DB_UNITS_SIZE)==0);
testOk1(meta.time.secPastEpoch==0x12345678);
testOk1(meta.time.nsec==0x90abcdef);
testOk1(meta.utag==0x10203040);
testOk1(meta.precision.dp==0x12345678);
testOk1(strncmp(meta.units, "arbitrary", DB_UNITS_SIZE)==0);
#define limitEq(UL, FL, VAL) testOk(meta.UL ## _ ## FL ## _limit == (VAL), #UL "_" #FL "_limit (%f) == %f", meta.UL ## _ ## FL ## _limit, VAL)
limitEq(lower, disp, 10000000.0-1.0);
limitEq(upper, disp, 10000000.0+1.0);
limitEq(lower, ctrl, 10000000.0-2.0);
limitEq(upper, ctrl, 10000000.0+2.0);
limitEq(lower, alarm, 10000000.0-3.0);
limitEq(lower, warning, 10000000.0-4.0);
limitEq(upper, warning, 10000000.0+4.0);
limitEq(upper, alarm, 10000000.0+3.0);
#undef limitEq
}
static
void testdbMetaDoubleGet(void)
{
xRecord* prec = (xRecord*)testdbRecordPtr("recmeta");
dbChannel *chan = dbChannelCreate("recmeta.OTST");
db_field_log *pfl;
evSubscrip evsub;
long status;
dbMetaDouble meta;
STATIC_ASSERT(sizeof(meta.amsg)==sizeof(prec->amsg));
STATIC_ASSERT(sizeof(meta.amsg)==sizeof(pfl->amsg));
if(!chan)
testAbort("Missing recmeta OTST");
if((status=dbChannelOpen(chan))!=0)
testAbort("can't open recmeta OTST : %ld", status);
dbScanLock((dbCommon*)prec);
/* ensure that all meta-data has different non-zero values */
prec->otst = 10000000.0;
prec->sevr = INVALID_ALARM;
prec->stat = UDF_ALARM;
strcpy(prec->amsg, "oops");
prec->acks = MAJOR_ALARM;
prec->time.secPastEpoch = 0x12345678;
prec->time.nsec = 0x90abcdef;
prec->utag = 0x10203040;
testDiag("dbGet directly from record");
checkDoubleGet(chan, NULL);
testDiag("dbGet from field log");
/* bare minimum init for db_create_event_log() */
memset(&evsub, 0, sizeof(evsub));
evsub.chan = chan;
evsub.useValque = 1;
pfl = db_create_event_log(&evsub);
/* spoil things which should now come from field log */
prec->sevr = 0;
prec->stat = 0;
strcpy(prec->amsg, "invalid");
prec->time.secPastEpoch = 0xdeadbeef;
prec->time.nsec = 0xdeadbeef;
prec->utag = 0xdeadbeef;
/* never any filters, so skip pre/post */
checkDoubleGet(chan, pfl);
db_delete_field_log(pfl);
dbScanUnlock((dbCommon*)prec);
}
typedef struct {
DBRstatus
DBRamsg
DBRtime
DBRutag
DBRenumStrs
} dbMetaEnum;
static
void testdbMetaEnumSizes(void)
{
dbMetaEnum meta;
size_t pos=0;
testDiag("dbMetaEnum may not have implicit padding");
#define testOffset(FLD) do {\
testOk(offsetof(dbMetaEnum, FLD)==pos, "offset(meta, " #FLD "), %u == %u", (unsigned)offsetof(dbMetaEnum, FLD), (unsigned)pos); \
pos += sizeof(meta.FLD); \
}while(0)
testOffset(status);
testOffset(severity);
testOffset(acks);
testOffset(ackt);
testOffset(amsg);
testOffset(time);
testOffset(utag);
testOffset(no_str);
testOffset(padenumStrs);
testOffset(strs);
#undef testOffset
testOk(sizeof(dbMetaEnum)==pos, "sizeof(dbMetaEnum), %u == %u", (unsigned)sizeof(dbMetaEnum), (unsigned)pos);
}
static
void testdbGetStringEqual(const char *pv, const char *expected)
{
@@ -143,9 +317,12 @@ void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
MAIN(dbPutGet)
{
testPlan(44);
testPlan(111);
testdbPrepare();
testdbMetaDoubleSizes();
testdbMetaEnumSizes();
testdbReadDatabase("dbTestIoc.dbd", NULL, NULL);
dbTestIoc_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("dbPutGetTest.db", NULL, NULL);
@@ -158,6 +335,8 @@ MAIN(dbPutGet)
testIocInitOk();
eltc(1);
testdbMetaDoubleGet();
testLongLink();
testLongAttr();
testLongField();
@@ -44,3 +44,5 @@ record(arr, "arr") {
field(FTVL, "ULONG")
field(NELM, "10")
}
record(x, "recmeta") {}
@@ -30,6 +30,7 @@ int dbLockTest(void);
int dbPutLinkTest(void);
int dbStaticTest(void);
int dbCaLinkTest(void);
int dbDbLinkTest(void);
int testDbChannel(void);
int chfPluginTest(void);
int arrShorthandTest(void);
@@ -52,6 +53,7 @@ void epicsRunDbTests(void)
runTest(dbPutLinkTest);
runTest(dbStaticTest);
runTest(dbCaLinkTest);
runTest(dbDbLinkTest);
runTest(testDbChannel);
runTest(arrShorthandTest);
runTest(recGblCheckDeadbandTest);
+76 -1
View File
@@ -76,7 +76,82 @@ static long process(struct dbCommon *pcommon)
return ret;
}
long get_units(struct dbAddr *paddr, char *units)
{
if(dbGetFieldIndex(paddr)==xRecordOTST) {
strncpy(units, "arbitrary", DB_UNITS_SIZE);
}
return 0;
}
long get_precision(const struct dbAddr *paddr, long *precision)
{
if(dbGetFieldIndex(paddr)==xRecordOTST) {
*precision = 0x12345678;
}
return 0;
}
long get_graphic_double(struct dbAddr *paddr, struct dbr_grDouble *p)
{
xRecord *prec = (xRecord *)paddr->precord;
if(dbGetFieldIndex(paddr)==xRecordOTST) {
p->lower_disp_limit = prec->otst-1.0;
p->upper_disp_limit = prec->otst+1.0;
}
return 0;
}
long get_control_double(struct dbAddr *paddr, struct dbr_ctrlDouble *p)
{
xRecord *prec = (xRecord *)paddr->precord;
if(dbGetFieldIndex(paddr)==xRecordOTST) {
p->lower_ctrl_limit = prec->otst-2.0;
p->upper_ctrl_limit = prec->otst+2.0;
}
return 0;
}
long get_alarm_double(struct dbAddr *paddr, struct dbr_alDouble *p)
{
xRecord *prec = (xRecord *)paddr->precord;
if(dbGetFieldIndex(paddr)==xRecordOTST) {
p->lower_alarm_limit = prec->otst-3.0;
p->lower_warning_limit = prec->otst-4.0;
p->upper_warning_limit = prec->otst+4.0;
p->upper_alarm_limit = prec->otst+3.0;
}
return 0;
}
#define report NULL
#define initialize NULL
#define special NULL
#define get_value NULL
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
static rset xRSET = {
RSETNUMBER, NULL, NULL, init_record, process
RSETNUMBER,
report,
initialize,
init_record,
process,
special,
get_value,
cvt_dbaddr,
get_array_info,
put_array_info,
get_units,
get_precision,
get_enum_str,
get_enum_strs,
put_enum_str,
get_graphic_double,
get_control_double,
get_alarm_double
};
epicsExportAddress(rset,xRSET);
+4
View File
@@ -46,4 +46,8 @@ recordtype(x) {
special(SPC_NOMOD)
extra("void (*clbk)(struct xRecord*)")
}
field(OTST, DBF_DOUBLE) {
prompt("dbGet() options test")
special(SPC_NOMOD)
}
}
+4
View File
@@ -175,8 +175,12 @@ dbHeaderTestxx_SRCS += dbHeaderTestxx.cpp
ifeq ($(T_A),$(EPICS_HOST_ARCH))
# Host-only tests of softIoc/softIocPVA, caget and pvget (if present)
# Unfortunately hangs too often on CI systems:
ifndef CI
TESTS += netget
endif
endif
# epicsRunRecordTests runs all the test programs in a known working order.
testHarness_SRCS += epicsRunRecordTests.c
+2
View File
@@ -37,6 +37,8 @@
#include "testMain.h"
#include "shareLib.h"
epicsShareFunc void testRestore(void);
#include "epicsExport.h"
+88 -42
View File
@@ -3,13 +3,16 @@
use strict;
use warnings;
use if $^O eq 'MSWin32', "Win32::Process";
use if $^O eq 'MSWin32', "Win32";
use lib '@TOP@/lib/perl';
use Test::More tests => 3;
use EPICS::IOC;
# Set to 1 to echo all IOC and client communications
my $debug = 1;
my $debug = 0;
$ENV{HARNESS_ACTIVE} = 1 if scalar @ARGV && shift eq '-tap';
@@ -35,7 +38,7 @@ $ioc->debug($debug);
$SIG{__DIE__} = $SIG{INT} = $SIG{QUIT} = sub {
$ioc->exit;
BAIL_OUT('Caught signal');
BAIL_OUT("Caught signal: $_[0]");
};
@@ -50,11 +53,21 @@ sub kill_bail {
}
sub watchdog (&$$) {
my ($do, $timeout, $abort) = @_;
$SIG{ALRM} = $abort;
alarm $timeout;
&$do;
alarm 0;
my ($code, $timeout, $fail) = @_;
my $bark = "Woof $$\n";
my $result;
eval {
local $SIG{__DIE__};
local $SIG{ALRM} = sub { die $bark };
alarm $timeout;
$result = &$code;
alarm 0;
};
if ($@) {
die if $@ ne $bark;
$result = &$fail;
}
return $result;
}
@@ -90,19 +103,6 @@ like($version, qr/^ \d+ \. \d+ \. \d+ /x,
"Got BaseVersion '$version' from iocsh");
# Client Tests
my $client = EPICS::IOC->new;
$client->debug($debug);
sub close_client {
my $doing = shift;
return sub {
diag("Timeout $doing");
$client->close;
}
}
# Channel Access
SKIP: {
@@ -119,17 +119,9 @@ SKIP: {
# CA Client test
watchdog {
$client->start($caget, '-w5', $pv);
my $caVersion = $client->_getline;
like($caVersion, qr/^ $pv \s+ \Q$version\E $/x,
'Got same BaseVersion from caget');
my @errors = $client->_geterrors;
note("Errors from caget:\n",
map(" $_\n", @errors))
if scalar @errors;
$client->close;
} 15, close_client('doing caget');
my $caVersion = qx_timeout(15, "$caget -w5 $pv");
like($caVersion, qr/^ $pv \s+ \Q$version\E $/x,
'Got same BaseVersion from caget');
}
@@ -152,17 +144,71 @@ SKIP: {
# PVA Client test
watchdog {
$client->start($pvget, '-w5', $pv);
my $pvaVersion = $client->_getline;
like($pvaVersion, qr/^ $pv \s .* \Q$version\E \s* $/x,
'Got same BaseVersion from pvget');
my @errors = $client->_geterrors;
note("Errors from pvget:\n",
map(" $_\n", @errors))
if scalar @errors;
$client->close;
} 10, close_client('doing pvget');
my $pvaVersion = qx_timeout(15, "$pvget -w5 $pv");
like($pvaVersion, qr/^ $pv \s .* \Q$version\E \s* $/x,
'Got same BaseVersion from pvget');
}
$ioc->exit;
# Process timeout utilities
sub system_timeout {
my ($timeout, $cmdline) = @_;
my $status;
if ($^O eq 'MSWin32') {
my $proc;
(my $app) = split ' ', $cmdline;
if (! Win32::Process::Create($proc, $app, $cmdline,
1, &Win32::Process::NORMAL_PRIORITY_CLASS, '.')) {
my $err = Win32::FormatMessage(Win32::GetLastError());
die "Can't create Process for '$cmdline': $err\n";
}
if (! $proc->Wait(1000 * $timeout)) {
$proc->Kill(1);
note("Timed out '$cmdline' after $timeout seconds\n");
}
my $status;
$proc->GetExitCode($status);
return $status;
}
else {
my $pid;
$status = watchdog {
$pid = fork();
die "Can't fork: $!\n"
unless defined $pid;
exec $cmdline
or die "Can't exec: $!\n"
unless $pid;
waitpid $pid, 0;
return $? >> 8;
} $timeout, sub {
kill 9, $pid if $pid;
note("Timed out '$cmdline' after $timeout seconds\n");
return -2;
};
}
return $status;
}
sub qx_timeout {
my ($timeout, $cmdline) = @_;
open(my $stdout, '>&STDOUT')
or die "Can't save STDOUT: $!\n";
my $outfile = "stdout-$$.txt";
unlink $outfile;
open STDOUT, '>', $outfile;
my $text;
if (system_timeout($timeout, $cmdline) == 0 && -r $outfile) {
open(my $file, '<', $outfile)
or die "Can't open $outfile: $!\n";
$text = join '', <$file>;
close $file;
}
open(STDOUT, '>&', $stdout)
or die "Can't restore STDOUT: $!\n";
unlink $outfile;
return $text;
}
+21 -5
View File
@@ -12,9 +12,11 @@
#include <testMain.h>
#include <dbAccess.h>
#include <epicsTime.h>
#include <epicsEvent.h>
#include <epicsThread.h>
#include <errlog.h>
#include <alarm.h>
#include <callback.h>
#include "recSup.h"
#include "aiRecord.h"
@@ -428,6 +430,15 @@ void testSiolWrite(const char *name,
* Asynchronous processing using simm:DELAY
*/
static void
ping(CALLBACK *pcb)
{
epicsEventId ev;
callbackGetUser(ev, pcb);
epicsEventMustTrigger(ev);
}
static
void testSimmDelay(const char *name,
epicsFloat64 *psdly,
@@ -436,6 +447,14 @@ void testSimmDelay(const char *name,
epicsTimeStamp now;
const double delay = 0.01; /* 10 ms */
double diff;
epicsEventId poked;
CALLBACK cb;
memset(&cb, 0, sizeof(CALLBACK));
poked = epicsEventMustCreate(epicsEventEmpty);
callbackSetCallback(ping, &cb);
callbackSetPriority(priorityLow, &cb);
callbackSetUser(poked, &cb);
testDiag("## Asynchronous processing with simm:DELAY ##");
@@ -457,14 +476,11 @@ void testSimmDelay(const char *name,
testdbPutFieldOk(namePROC, DBR_LONG, 0);
testdbGetFieldEqual(namePACT, DBR_USHORT, 1);
epicsTimeGetCurrent(&now);
epicsThreadSleep(1.75*delay);
if(testImpreciseTiming())
testTodoBegin("imprecise");
callbackRequestDelayed(&cb, 1.5 * delay);
epicsEventWait(poked);
testdbGetFieldEqual(namePACT, DBR_USHORT, 0);
diff = epicsTimeDiffInSeconds(mytime, &now);
testOk(diff >= 0.0, "time stamp is recent (%.9f sec)", diff);
if(testImpreciseTiming())
testTodoEnd();
/* Reset delay */
*psdly = -1.;
+4 -2
View File
@@ -30,10 +30,12 @@ is_deeply $menu->choice(2), undef, 'Third choice undefined';
like $menu->toDeclaration, qr/ ^
\s* \# \s* ifndef \s+ test_NUM_CHOICES \s* \n
\s* \/\*\* [^*]* \*\/ \s* \n
\s* typedef \s+ enum \s+ \{ \s* \n
\s* ch1 \s+ \/\* [^*]* \*\/, \s* \n
\s* ch2 \s+ \/\* [^*]* \*\/ \s* \n
\s* ch1 \s+ \/\*\* [^*]* \*\/, \s* \n
\s* ch2 \s+ \/\*\* [^*]* \*\/ \s* \n
\s* \} \s* test \s* ; \s* \n
\s* \/\*\* [^*]* \*\/ \s* \n
\s* \# \s* define \s+ test_NUM_CHOICES \s+ 2 \s* \n
\s* \# \s* endif \s* \n
\s* $ /x, 'C declaration';