Unit testing of record processing
- page dbunittest
See also
epicsUnitTest.h
Test skeleton
For the impatient, the skeleton of a test:
#include <dbUnitTest.h> #include <testMain.h> int mytest_registerRecordDeviceDriver(DBBASE *pbase); void testCase(void) { testdbPrepare(); testdbReadDatabase("mytest.dbd", 0, 0); mytest_registerRecordDeviceDriver(pdbbase); testdbReadDatabase("some.db", 0, "VAR=value"); testIocInitOk(); // database running ... testIocShutdownOk(); testdbCleanup(); } MAIN(mytestmain) { testPlan(0); // adjust number of tests testCase(); testCase(); // may be repeated if desirable. return testDone(); }
TOP = .. include $(TOP)/configure/CONFIG TARGETS += $(COMMON_DIR)/mytest.dbd DBDDEPENDS_FILES += mytest.dbd$(DEP) TESTFILES += $(COMMON_DIR)/mytest.dbd mytest_DBD += base.dbd mytest_DBD += someother.dbd TESTPROD_HOST += mytest mytest_SRCS += mytestmain.c # see above mytest_SRCS += mytest_registerRecordDeviceDriver.cpp TESTFILES += some.db include $(TOP)/configure/RULES
Actions
Several helper functions are provided to interact with a running database.
testdbPutFieldOk()
testdbPutFieldFail()
testdbPutArrFieldOk()
testdbVPutField()
testdbGetFieldEqual()
testdbVGetFieldEqual()
int for DBR_UCHAR, DBR_CHAR, DBR_USHORT, DBR_SHORT, DBR_LONG
unsigned int for DBR_ULONG
long long for DBF_INT64
unsigned long long for DBF_UINT64
double for DBR_FLOAT and DBR_DOUBLE
const char* for DBR_STRING
See also
enum dbfType in dbFldTypes.h
testdbPutFieldOk("pvname", DBF_ULONG, (unsigned int)5); testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1); testdbPutFieldOk("pvname", DBF_STRING, "hello world");
Monitoring for changes
When Put and Get aren’t sufficient, testMonitor may help to setup and monitor for changes.
testMonitorCreate()
testMonitorDestroy()
testMonitorWait()
testMonitorCount()
Synchronizing
Helpers to synchronize with some database worker threads
testSyncCallback()
Global mutex for use by test code.
This utility mutex is intended to be used to avoid races in situations where some other synchronization primitive is being destroyed (epicsEvent, epicsMutex, …) and use of epicsThreadMustJoin() is impractical.
For example. The following has a subtle race where the event may be destroyed (free()’d) before the call to epicsEventMustSignal() has returned. On some targets this leads to a use after free() error.
epicsEventId evt; void thread1() { evt = epicsEventMustCreate(...); // spawn thread2() epicsEventMustWait(evt); epicsEventDestroy(evt); // <- Racer } // ... void thread2() { epicsEventMustSignal(evt); // <- Racer }
When possible, the best way to avoid this race would be to join the worker before destroying the event.
epicsEventId evt; void thread1() { epicsThreadOpts opts = EPICS_THREAD_OPTS_INIT; epicsThreadId t2; opts.joinable = 1; evt = epicsEventMustCreate(...); t2 = epicsThreadCreateOpt("thread2", &thread2, NULL, &opts); assert(t2); epicsEventMustWait(evt); epicsThreadMustJoin(t2); epicsEventDestroy(evt); } void thread2() { epicsEventMustSignal(evt); }
Another way to avoid this race is to use a global mutex to ensure that epicsEventMustSignal() has returned before destroying the event. testGlobalLock() and testGlobalUnlock() provide access to such a mutex.
epicsEventId evt; void thread1() { evt = epicsEventMustCreate(...); // spawn thread2() epicsEventMustWait(evt); testGlobalLock(); // <-- added epicsEventDestroy(evt); testGlobalUnlock(); // <-- added } // ... void thread2() { testGlobalLock(); // <-- added epicsEventMustSignal(evt); testGlobalUnlock(); // <-- added }
This must be a global mutex to avoid simply shifting the race from the event to a locally allocated mutex.