diff --git a/src/ioc/db/dbUnitTest.c b/src/ioc/db/dbUnitTest.c index 48d92a7fd..8e23ab691 100644 --- a/src/ioc/db/dbUnitTest.c +++ b/src/ioc/db/dbUnitTest.c @@ -16,18 +16,33 @@ #include "epicsUnitTest.h" #include "osiFileName.h" #include "registry.h" +#include "epicsEvent.h" #define epicsExportSharedSymbols #include "dbAccess.h" #include "dbBase.h" +#include "dbChannel.h" +#include "dbEvent.h" #include "dbStaticLib.h" #include "dbUnitTest.h" #include "initHooks.h" #include "iocInit.h" +static dbEventCtx testEvtCtx; +static epicsMutexId testEvtLock; +static ELLLIST testEvtList; /* holds testMonitor::node */ + +struct testMonitor { + ELLNODE node; + dbEventSubscription sub; + epicsEventId event; + unsigned count; +}; + void testdbPrepare(void) { - /* No-op at the moment */ + if(!testEvtLock) + testEvtLock = epicsMutexMustCreate(); } void testdbReadDatabase(const char* file, @@ -46,10 +61,21 @@ void testIocInitOk(void) { if(iocBuildIsolated() || iocRun()) testAbort("Failed to start up test database"); + if(!(testEvtCtx=db_init_events())) + testAbort("Failed to initialize test dbEvent context"); + if(DB_EVENT_OK!=db_start_events(testEvtCtx, "CAS-test", NULL, NULL, epicsThreadPriorityCAServerLow)) + testAbort("Failed to start test dbEvent context"); } void testIocShutdownOk(void) { + epicsMutexMustLock(testEvtLock); + if(ellCount(&testEvtList)) + testDiag("Warning, testing monitors still active at testIocShutdownOk()"); + epicsMutexUnlock(testEvtLock); + + db_close_events(testEvtCtx); + testEvtCtx = NULL; if(iocShutdown()) testAbort("Failed to shutdown test database"); } @@ -199,3 +225,88 @@ dbCommon* testdbRecordPtr(const char* pv) return addr.precord; } + +static +void testmonupdate(void *user_arg, struct dbChannel *chan, + int eventsRemaining, struct db_field_log *pfl) +{ + testMonitor *mon = user_arg; + + epicsMutexMustLock(testEvtLock); + mon->count++; + epicsMutexUnlock(testEvtLock); + epicsEventMustTrigger(mon->event); +} + +testMonitor* testMonitorCreate(const char* pvname, unsigned mask, unsigned opt) +{ + long status; + testMonitor *mon; + dbChannel *chan; + assert(testEvtCtx); + + mon = callocMustSucceed(1, sizeof(*mon), "testMonitorCreate"); + + mon->event = epicsEventMustCreate(epicsEventEmpty); + + chan = dbChannelCreate(pvname); + if(!chan) + testAbort("testMonitorCreate - dbChannelCreate(\"%s\") fails", pvname); + if(!!(status=dbChannelOpen(chan))) + testAbort("testMonitorCreate - dbChannelOpen(\"%s\") fails w/ %ld", pvname, status); + + mon->sub = db_add_event(testEvtCtx, chan, &testmonupdate, mon, mask); + if(!mon->sub) + testAbort("testMonitorCreate - db_add_event(\"%s\") fails", pvname); + + db_event_enable(mon->sub); + + epicsMutexMustLock(testEvtLock); + ellAdd(&testEvtList, &mon->node); + epicsMutexUnlock(testEvtLock); + + return mon; +} + +void testMonitorDestroy(testMonitor *mon) +{ + if(!mon) return; + + db_event_disable(mon->sub); + + epicsMutexMustLock(testEvtLock); + ellDelete(&testEvtList, &mon->node); + epicsMutexUnlock(testEvtLock); + + db_cancel_event(mon->sub); + + epicsEventDestroy(mon->event); + + free(mon); +} + +void testMonitorWait(testMonitor *mon) +{ + switch(epicsEventWaitWithTimeout(mon->event, 10.0)) + { + case epicsEventOK: + return; + case epicsEventWaitTimeout: + default: + testAbort("testMonitorWait() exceeds timeout"); + } +} + +unsigned testMonitorCount(testMonitor *mon, unsigned reset) +{ + unsigned count; + epicsMutexMustLock(testEvtLock); + count = mon->count; + if(reset) { + mon->count = 0; + epicsEventWaitWithTimeout(mon->event, 0); /* clear the event */ + } + epicsMutexUnlock(testEvtLock); + return count; +} + diff --git a/src/ioc/db/dbUnitTest.h b/src/ioc/db/dbUnitTest.h index 80867dc9d..f6145d505 100644 --- a/src/ioc/db/dbUnitTest.h +++ b/src/ioc/db/dbUnitTest.h @@ -57,6 +57,26 @@ epicsShareFunc void testdbVGetFieldEqual(const char* pv, short dbrType, va_list epicsShareFunc dbCommon* testdbRecordPtr(const char* pv); +typedef struct testMonitor testMonitor; + +/* Begin monitoring the named PV for changes */ +epicsShareFunc testMonitor* testMonitorCreate(const char* pvname, unsigned dbe_mask, unsigned opt); +/* End monitoring */ +epicsShareFunc void testMonitorDestroy(testMonitor*); +/* Return immediately if it has been updated since create, last wait, + * or reset (count w/ reset=1). + * Otherwise, block until the value of the target PV is updated. + */ +epicsShareFunc void testMonitorWait(testMonitor*); +/* Return the number of monitor events which have occured since create, + * or a pervious reset (called reset=1). + * Calling w/ reset=0 only returns the count. + * Calling w/ reset=1 resets the count to zero and ensures that the next + * wait will block unless subsequent events occur. Returns the previous + * count. + */ +epicsShareFunc unsigned testMonitorCount(testMonitor*, unsigned reset); + #ifdef __cplusplus } #endif diff --git a/src/std/rec/test/Makefile b/src/std/rec/test/Makefile index ad204a580..5e7a8fb29 100644 --- a/src/std/rec/test/Makefile +++ b/src/std/rec/test/Makefile @@ -31,6 +31,13 @@ testHarness_SRCS += arrayOpTest.c TESTFILES += ../arrayOpTest.db TESTS += arrayOpTest +TESTPROD_HOST += linkRetargetLinkTest +linkRetargetLinkTest_SRCS += linkRetargetLinkTest.c +linkRetargetLinkTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp +testHarness_SRCS += linkRetargetLinkTest.c +TESTFILES += ../linkRetargetLink.db +TESTS += linkRetargetLinkTest + TARGETS += $(COMMON_DIR)/asTestIoc.dbd asTestIoc_DBD += base.dbd asTestIoc_DBD += asTest.dbd diff --git a/src/std/rec/test/epicsRunRecordTests.c b/src/std/rec/test/epicsRunRecordTests.c index 1a5858d76..3476d2138 100644 --- a/src/std/rec/test/epicsRunRecordTests.c +++ b/src/std/rec/test/epicsRunRecordTests.c @@ -15,6 +15,7 @@ int analogMonitorTest(void); int arrayOpTest(void); int asTest(void); +int linkRetargetLinkTest(void); void epicsRunRecordTests(void) { @@ -26,5 +27,7 @@ void epicsRunRecordTests(void) runTest(asTest); + runTest(linkRetargetLinkTest); + epicsExit(0); /* Trigger test harness */ } diff --git a/src/std/rec/test/linkRetargetLink.db b/src/std/rec/test/linkRetargetLink.db new file mode 100644 index 000000000..3306e43ea --- /dev/null +++ b/src/std/rec/test/linkRetargetLink.db @@ -0,0 +1,17 @@ +record(ai, "rec:ai") { + field(INP, "0") +} +record(ai, "rec:src1") { + field(VAL, "1") +} +record(ai, "rec:src2") { + field(VAL, "2") +} +record(stringout, "rec:link1") { + field(VAL, "rec:src1") + field(OUT, "rec:ai.INP CA") +} +record(stringout, "rec:link2") { + field(VAL, "rec:src2 CP") + field(OUT, "rec:ai.INP CA") +} diff --git a/src/std/rec/test/linkRetargetLinkTest.c b/src/std/rec/test/linkRetargetLinkTest.c new file mode 100644 index 000000000..e829fa6e0 --- /dev/null +++ b/src/std/rec/test/linkRetargetLinkTest.c @@ -0,0 +1,86 @@ +/*************************************************************************\ +* Copyright (c) 2015 Michael Davidsaver +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. + \*************************************************************************/ + +/* + * Author: Michael Davidsaver + * + * Test using several stringout records to retarget the link of another record + */ + +#include + +#include "dbAccess.h" + +#include "dbUnitTest.h" +#include "errlog.h" +#include "epicsThread.h" + +#include "testMain.h" + +void recTestIoc_registerRecordDeviceDriver(struct dbBase *); + +static void testRetarget(void) +{ + testMonitor *lnkmon, *valmon; + + testdbPrepare(); + + testdbReadDatabase("recTestIoc.dbd", NULL, NULL); + + recTestIoc_registerRecordDeviceDriver(pdbbase); + + testdbReadDatabase("linkRetargetLink.db", NULL, NULL); + + eltc(0); + testIocInitOk(); + eltc(1); + + lnkmon = testMonitorCreate("rec:ai.INP", DBE_VALUE, 0); + valmon = testMonitorCreate("rec:ai", DBE_VALUE, 0); + + /* initially rec:ai.INP is CONSTANT */ + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "0"); + + /* rec:ai.INP becomes DB_LINK, but no processing is triggered */ + testdbPutFieldOk("rec:link1.PROC", DBF_LONG, 0); + + testMonitorWait(lnkmon); + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 0.0); + testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "rec:src1 NPP NMS"); + + /* trigger a read from rec:ai.INP */ + testdbPutFieldOk("rec:ai.PROC", DBF_LONG, 0); + + testMonitorWait(valmon); + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 1.0); + + /* rec:ai.INP becomes CA_LINK w/ CP, processing is triggered */ + testdbPutFieldOk("rec:link2.PROC", DBF_LONG, 0); + + testMonitorWait(lnkmon); + testMonitorWait(valmon); + + testdbGetFieldEqual("rec:ai", DBR_DOUBLE, 2.0); + testdbGetFieldEqual("rec:ai.INP", DBR_STRING, "rec:src2 CP NMS"); + + testMonitorDestroy(lnkmon); + testMonitorDestroy(valmon); + + testIocShutdownOk(); + + testdbCleanup(); +} + +MAIN(linkRetargetLinkTest) +{ + testPlan(10); + testRetarget(); + return testDone(); +}