dbUnitTest.h
Helpers for unittests of process database.
- Author
Michael Davidsaver, Ralph Lange
Unit testing of record processing
See also
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
Discussion:
Some tests require the context of an IOC to be run. This conflicts with the idea of running multiple tests within a test harness, as iocInit() is only allowed to be called once, and some parts of the full IOC (e.g. the rsrv CA server) can not be shut down cleanly. The function iocBuildIsolated() allows to start an IOC without its Channel Access parts, so that it can be shutdown quite cleanly using iocShutdown(). This feature is only intended to be used from test programs, do not use it on production IOCs. After building the IOC using iocBuildIsolated() or iocBuild(), it has to be started by calling iocRun().
The part from iocBuildIsolated() to iocShutdown() can be repeated to execute multiple tests within one executable or harness.
To make it easier to create a single test program that can be built for both the embedded and workstation operating system harnesses, the header file testMain.h provides a convenience macro MAIN() that adjusts the name of the test program according to the platform it is running on: main() on workstations and a regular function name on embedded systems.
Actions
Several helper functions are provided to interact with a running database.
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
testdbPutFieldOk("pvname", DBF_ULONG, (unsigned int)5);
testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1);
testdbPutFieldOk("pvname", DBF_STRING, "hello world");
See also
enum dbfType in dbFldTypes.h
Monitoring for changes
When Put and Get aren’t sufficient, testMonitor may help to setup and monitor for changes.
Synchronizing
Helpers to synchronize with some database worker threads
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.
Typedefs
-
typedef struct testMonitor testMonitor
Functions
-
void testdbPrepare(void)
First step in test database setup
See also
-
void testdbReadDatabase(const char *file, const char *path, const char *substitutions)
Read .dbd or .db file
See also
-
void testIocShutdownOk(void)
Shutdown test database processing.
eg. Stops scan threads
See also
-
void testdbCleanup(void)
Final step in test database cleanup
See also
-
void testdbPutFieldOk(const char *pv, int dbrType, ...)
Assert that a dbPutField() scalar operation will complete successfully.
testdbPutFieldOk("some.TPRO", DBF_LONG, 1);
See also
-
void testdbPutFieldFail(long status, const char *pv, int dbrType, ...)
Assert that a dbPutField() operation will fail with a certain S_* code
See also
-
long testdbVPutField(const char *pv, short dbrType, va_list ap)
Assert that a dbPutField() scalar operation will complete successfully.
See also
-
void testdbGetFieldEqual(const char *pv, int dbrType, ...)
Assert that a dbGetField() scalar operation will complete successfully, with the provided value.
testdbGetFieldEqual("some.TPRO", DBF_LONG, 0);
See also
-
void testdbVGetFieldEqual(const char *pv, short dbrType, va_list ap)
Assert that a dbGetField() scalar operation will complete successfully, with the provided value.
See also
-
void testdbPutArrFieldOk(const char *pv, short dbrType, unsigned long count, const void *pbuf)
Assert that a dbPutField() array operation will complete successfully.
static const epicsUInt32 putval[] = {1,2,3}; testdbPutArrFieldOk("some:wf", DBF_ULONG, NELEMENTS(putval), putval);
See also
- Parameters:
pv – a PV name, possibly including filter expression
dbrType – a DBF_* type code (cf. dbfType in dbFldTypes.h)
count – Number of elements in pbuf array
pbuf – Array of values to write
-
void testdbGetArrFieldEqual(const char *pv, short dbfType, long nRequest, unsigned long pbufcnt, const void *pbuf)
Execute dbGet() of nRequest elements and compare the result with pbuf (pbufcnt is an element count). Element size is derived from dbfType.
nRequest > pbufcnt will detect truncation. nRequest < pbufcnt always fails. nRequest ==pbufcnt checks prefix (actual may be longer than expected)
- Parameters:
pv – PV name string
dbfType – One of the DBF_* macros from dbAccess.h
nRequest – Number of elements to request from pv
pbufcnt – Number of elements pointed to be pbuf
pbuf – Expected value buffer
-
dbCommon *testdbRecordPtr(const char *pv)
Obtain pointer to record.
Calls testAbort() on failure. Will never return NULL.
Note
Remember to dbScanLock() when accessing mutable fields.
-
testMonitor *testMonitorCreate(const char *pvname, unsigned dbe_mask, unsigned opt)
Setup monitoring the named PV for changes
Calls testAbort() on failure. Will never return NULL.
- Since
3.16.0.1
- Parameters:
pvname – [in] Requested PV name. Must be valid for dbChannelCreate().
dbe_mask – [in] A bitwise or of DBE_VALUE and friends.
opt – [in] Currently unused. Set to zero.
- Returns:
Newly allocated testMonitor object, which caller must testMonitorDestroy()
-
void testMonitorDestroy(testMonitor*)
Stop monitoring
- Since
3.16.0.1
-
void testMonitorWait(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.
- Since
3.16.0.1
-
void testMonitorSync(testMonitor*)
Synchronize with dbEvent working for subscription.
On return, any updates previously posted for this subscriptions have been delivered.
- Since
7.0.10
-
unsigned testMonitorCount(testMonitor*, unsigned reset)
Return the number of monitor events which have occurred since create, or a previous 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.
- Since
3.16.0.1
-
void testSyncCallback(void)
Synchronize the shared callback queues.
Block until all callback queue jobs which were queued, or running, have completed.
-
void testGlobalLock(void)
Lock Global convenience mutex for use by test code.
See also
-
void testGlobalUnlock(void)
Unlock Global convenience mutex for use by test code.
See also