EPICS Base  7.0.8.1
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Unit testing of record processing
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) {
testdbReadDatabase("mytest.dbd", 0, 0);
mytest_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("some.db", 0, "VAR=value");
// database running ...
}
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.

Correct argument types must be used with var-arg functions.

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.

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.

void thread1() {
// spawn thread2()
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.

void thread1() {
epicsThreadId t2;
opts.joinable = 1;
t2 = epicsThreadCreateOpt("thread2", &thread2, NULL, &opts);
assert(t2);
}
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.

void thread1() {
// spawn thread2()
testGlobalLock(); // <-- added
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.