doc dbUnitTest.h

This commit is contained in:
Michael Davidsaver
2021-04-01 10:18:49 -07:00
parent 0d2228b536
commit 6ed6dc11bb

View File

@@ -6,9 +6,11 @@
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
* Author: Michael Davidsaver <mdavidsaver@bnl.gov>
* Ralph Lange <Ralph.Lange@gmx.de>
/** @file dbUnitTest.h
* @brief Helpers for unitests of process database
* @author Michael Davidsaver, Ralph Lange
*
* @see @ref dbunittest
*/
#ifndef EPICSUNITTESTDB_H
@@ -26,38 +28,88 @@
extern "C" {
#endif
/** First step in test database setup
*
* @see @ref dbtestskel
*/
epicsShareFunc void testdbPrepare(void);
/** Read .dbd or .db file
*
* @see @ref dbtestskel
*/
epicsShareFunc void testdbReadDatabase(const char* file,
const char* path,
const char* substitutions);
/** Assert success of iocInit()
*
* @see @ref dbtestskel
*/
epicsShareFunc void testIocInitOk(void);
/** Shutdown test database processing.
*
* eg. Stops scan threads
*
* @see @ref dbtestskel
*/
epicsShareFunc void testIocShutdownOk(void);
/** Final step in test database cleanup
*
* @see @ref dbtestskel
*/
epicsShareFunc void testdbCleanup(void);
/* Correct argument types must be used with this var-arg function!
* Doing otherwise will result in corruption of argument values!
/** Assert that a dbPutField() scalar operation will complete successfully.
*
* 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
* @code
* testdbPutFieldOk("some.TPRO", DBF_LONG, 1);
* @endcode
*
* eg.
* testdbPutFieldOk("pvname", DBF_ULONG, (unsigned int)5);
* testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1);
* testdbPutFieldOk("pvname", DBF_STRING, "hello world");
* @see @ref dbtestactions
*/
epicsShareFunc void testdbPutFieldOk(const char* pv, int dbrType, ...);
/* Tests for put failure */
/** Assert that a dbPutField() operation will fail with a certain S_\* code
*
* @see @ref dbtestactions
*/
epicsShareFunc void testdbPutFieldFail(long status, const char* pv, int dbrType, ...);
/** Assert that a dbPutField() scalar operation will complete successfully.
*
* @see @ref dbtestactions
*/
epicsShareFunc long testdbVPutField(const char* pv, short dbrType, va_list ap);
/** Assert that a dbGetField() scalar operation will complete successfully, with the provided value.
*
* @code
* testdbGetFieldEqual("some.TPRO", DBF_LONG, 0);
* @endcode
*
* @see @ref dbtestactions
*/
epicsShareFunc void testdbGetFieldEqual(const char* pv, int dbrType, ...);
/** Assert that a dbGetField() scalar operation will complete successfully, with the provided value.
*
* @see @ref dbtestactions
*/
epicsShareFunc void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap);
/** Assert that a dbPutField() array operation will complete successfully.
*
* @param pv a PV name, possibly including filter expression
* @param a DBF_\* type code (cf. dbfType in dbFldTypes.h)
* @param count Number of elements in pbuf array
* @param pbuf Array of values to write
*
* @code
* static const epicsUInt32 putval[] = {1,2,3};
* testdbVGetFieldEqual("some:wf", DBF_ULONG, NELEMENTS(putval), putval);
* @endcode
*
* @see @ref dbtestactions
*/
epicsShareFunc void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf);
/**
@@ -77,20 +129,26 @@ epicsShareFunc void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned
*/
epicsShareFunc void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsigned long pbufcnt, const void *pbuf);
/** Obtain pointer to record.
*
* Calls testAbort() on failure. Will never return NULL.
*
* @note Remember to dbScanLock() when accessing mutable fields.
*/
epicsShareFunc dbCommon* testdbRecordPtr(const char* pv);
typedef struct testMonitor testMonitor;
/* Begin monitoring the named PV for changes */
/** Setup monitoring the named PV for changes */
epicsShareFunc testMonitor* testMonitorCreate(const char* pvname, unsigned dbe_mask, unsigned opt);
/* End monitoring */
/** Stop monitoring */
epicsShareFunc void testMonitorDestroy(testMonitor*);
/* Return immediately if it has been updated since create, last wait,
/** 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,
/** 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
@@ -106,60 +164,178 @@ epicsShareFunc unsigned testMonitorCount(testMonitor*, unsigned reset);
*/
epicsShareFunc void testSyncCallback(void);
/** Global mutex for use by test code.
/** Lock Global convenience mutex for use by test code.
*
* This utility mutex is intended to be used to avoid races in situations
* where some other syncronization primitive is being destroyed (epicsEvent,
* epicsMutex, ...).
*
* 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.
*
@code
epicsEventId evt;
void thread1() {
evt = epicsEventMustCreate(...);
// spawn thread2()
epicsEventMustWait(evt);
epicsEventDestroy(evt);
}
// ...
void thread2() {
epicsEventMustSignal(evt);
}
@endcode
*
* One way to avoid this race is to use a global mutex to ensure
* that epicsEventMustSignal() has returned before destroying
* the event.
*
@code
epicsEventId evt;
void thread1() {
evt = epicsEventMustCreate(...);
// spawn thread2()
epicsEventMustWait(evt);
testGlobalLock(); // <-- added
epicsEventDestroy(evt);
testGlobalUnlock(); // <-- added
}
// ...
void thread2() {
testGlobalLock(); // <-- added
epicsEventMustSignal(evt);
testGlobalUnlock(); // <-- added
}
@endcode
*
* This must be a global mutex to avoid simply shifting the race
* from the event to a locally allocated mutex.
* @see @ref dbtestmutex
*/
epicsShareFunc void testGlobalLock(void);
/** Unlock Global convenience mutex for use by test code.
*
* @see @ref dbtestmutex
*/
epicsShareFunc void testGlobalUnlock(void);
#ifdef __cplusplus
}
#endif
/** @page dbunittest Unit testing of record processing
*
* @see @ref epicsUnitTest.h
*
* @section dbtestskel Test skeleton
*
* For the impatient, the skeleton of a test:
*
* @code
* #include <dbUnitTest.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);
* testCase();
* testCase(); // may be repeated if desirable.
* return testDone();
* }
* @endcode
*
* @code
* 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
* @endcode
*
* @section dbtestactions Actions
*
* Several helper functions are provided to interact with a running database.
*
* @li testdbPutFieldOk()
* @li testdbPutFieldFail()
* @li testdbPutArrFieldOk()
* @li testdbVPutField()
* @li testdbGetFieldEqual()
* @li testdbVGetFieldEqual()
*
* Correct argument types must be used with var-arg functions.
*
* @li int for DBR_UCHAR, DBR_CHAR, DBR_USHORT, DBR_SHORT, DBR_LONG
* @li unsigned int for DBR_ULONG
* @li long long for DBF_INT64
* @li unsigned long long for DBF_UINT64
* @li double for DBR_FLOAT and DBR_DOUBLE
* @li const char* for DBR_STRING
*
* @see enum dbfType in dbFldTypes.h
*
* @code
* testdbPutFieldOk("pvname", DBF_ULONG, (unsigned int)5);
* testdbPutFieldOk("pvname", DBF_FLOAT, (double)4.1);
* testdbPutFieldOk("pvname", DBF_STRING, "hello world");
* @endcode
*
* @section dbtestmonitor Monitoring for changes
*
* When Put and Get aren't sufficient, testMonitor may help to setup and monitor for changes.
*
* @li testMonitorCreate()
* @li testMonitorDestroy()
* @li testMonitorWait()
* @li testMonitorCount()
*
* @section dbtestsync Synchronizing
*
* Helpers to synchronize with some database worker threads
*
* @li testSyncCallback()
*
* @section dbtestmutex 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.
*
* @code
* epicsEventId evt;
* void thread1() {
* evt = epicsEventMustCreate(...);
* // spawn thread2()
* epicsEventMustWait(evt);
* epicsEventDestroy(evt); // <- Racer
* }
* // ...
* void thread2() {
* epicsEventMustSignal(evt); // <- Racer
* }
* @endcode
*
* When possible, the best way to avoid this race would be to join the worker
* before destroying the event.
*
* @code
* 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);
* }
* @endcode
*
* 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.
*
* @code
* epicsEventId evt;
* void thread1() {
* evt = epicsEventMustCreate(...);
* // spawn thread2()
* epicsEventMustWait(evt);
* testGlobalLock(); // <-- added
* epicsEventDestroy(evt);
* testGlobalUnlock(); // <-- added
* }
* // ...
* void thread2() {
* testGlobalLock(); // <-- added
* epicsEventMustSignal(evt);
* testGlobalUnlock(); // <-- added
* }
* @endcode
*
* This must be a global mutex to avoid simply shifting the race
* from the event to a locally allocated mutex.
*/
#endif // EPICSUNITTESTDB_H