Files
epics-base/modules/libcom/src/misc/epicsUnitTest.h
2023-05-01 09:03:45 -07:00

286 lines
11 KiB
C

/*************************************************************************\
* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/**
* \file epicsUnitTest.h
* \brief Unit test routines
* \author Andrew Johnson
*
* The unit test routines make it easy for a test program to generate output
* that is compatible with the Test Anything Protocol and can thus be used with
* Perl's automated Test::Harness as well as generating human-readable output.
* The routines detect whether they are being run automatically and print a
* summary of the results at the end if not.
* A test program starts with a call to testPlan(), announcing how many tests
* are to be conducted. If this number is not known a value of zero can be
* used during development, but it is recommended that the correct value be
* substituted after the test program has been completed.
*
* Individual test results are reported using any of testOk(), testOk1(),
* testOkV(), testPass() or testFail(). The testOk() call takes and also
* returns a logical pass/fail result (zero means failure, any other value
* is success) and a printf-like format string and arguments which describe
* the test. The convenience macro testOk1() is provided which stringifies its
* single condition argument, reducing the effort needed when writing test
* programs. The individual testPass() and testFail() routines can be used when
* the test program takes a different path on success than on failure, but one
* or other must always be called for any particular test. The testOkV() routine
* is a varargs form of testOk() included for internal purposes which may prove
* useful in some cases.
*
* If some program condition or failure makes it impossible to run some tests,
* the testSkip() routine can be used to indicate how many tests are being omitted
* from the run, thus keeping the test counts correct; the constant string why is
* displayed as an explanation to the user (this string is not printf-like).
*
* If some tests are expected to fail because functionality in the module under
* test has not yet been fully implemented, these tests may still be executed,
* wrapped between calls to testTodoBegin() and testTodoEnd(). testTodoBegin()
* takes a constant string indicating why these tests are not expected to
* succeed. This modifies the counting of the results so the wrapped tests will not
* be recorded as failures.
*
* Additional information can be supplied using the testDiag() routine, which
* displays the relevant information as a comment in the result output. None of
* the printable strings passed to any testXxx() routine should contain a newline
* '\\n' character, newlines will be added by the test routines as part of the
* Test Anything Protocol. For multiple lines of diagnostic output, call
* testDiag() as many times as necessary.
*
* If at any time the test program is unable to continue for some catastrophic
* reason, calling testAbort() with an appropriate message will ensure that the
* test harness understands this. testAbort() does not return, but calls the ANSI
* C routine abort() to cause the program to stop immediately.
*
* After all of the tests have been completed, the return value from
* testDone() can be used as the return status code from the program's main()
* routine.
*
* On vxWorks and RTEMS, an alternative test harness can be used to run a
* series of tests in order and summarize the results from them all at the end
* just like the Perl harness does. The routine testHarness() is called once at
* the beginning of the test harness program. Each test program is run by
* passing its main routine name to the runTest() macro which expands into a call
* to the runTestFunc() routine. The last test program or the harness program
* itself must finish by calling testHarnessDone() which triggers the summary
* mechanism to generate its result outputs (from an epicsAtExit() callback
* routine).
*
* ### IOC Testing
*
* 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 suggested call sequence in a test program that needs to run the
* IOC without Channel Access is:
\code
#include "iocInit.h"
MAIN(iocTest)
{
testPlan(0);
testdbPrepare();
testdbReadDatabase("<dbdname>.dbd", 0, 0);
<dbdname>_registerRecordDeviceDriver(pdbbase);
testdbReadDatabase("some.db", 0, 0);
... test code before iocInit(). eg. dbGetString() ...
testIocInitOk();
... test code with IOC running. eg. dbGet()
testIocShutdownOk();
testdbCleanup();
return testDone();
}
\endcode
* 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.
*
* ### Example
*
* The following is a simple example of a test program using the epicsUnitTest
* routines:
\code
#include <math.h>
#include "epicsUnitTest.h"
#include "testMain.h"
MAIN(mathTest)
{
testPlan(3);
testOk(sin(0.0) == 0.0, "Sine starts");
testOk(cos(0.0) == 1.0, "Cosine continues");
if (!testOk1(M_PI == 4.0*atan(1.0)))
testDiag("4 * atan(1) = %g", 4.0 * atan(1.0));
return testDone();
}
\endcode
* The output from running the above program looks like this:
\code
1..3
ok 1 - Sine starts
ok 2 - Cosine continues
ok 3 - M_PI == 4.0*atan(1.0)
Results
=======
Tests: 3
Passed: 3 = 100%
\endcode
*/
#ifndef INC_epicsUnitTest_H
#define INC_epicsUnitTest_H
#include <stdarg.h>
#include "compilerDependencies.h"
#include "libComAPI.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \brief Declare the test plan, required.
* \param tests Number of tests to be run. May be zero if not known but the
* test harness then can't tell if the program dies prematurely.
*/
LIBCOM_API void testPlan(int tests);
/** \name Announcing Test Results
* Routines that declare individual test results.
*/
/** @{ */
/** \brief Test result with printf-style description.
* \param pass True/False value indicating result.
* \param fmt A printf-style format string describing the test.
* \param ... Any parameters required for the format string.
* \return The value of \p pass.
*/
LIBCOM_API int testOk(int pass, EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(2, 3);
/** \brief Test result using expression as description
* \param cond Expression to be evaluated and displayed.
* \return The value of \p cond.
*/
#define testOk1(cond) testOk(cond, "%s", #cond)
/** \brief Test result with var-args description.
* \param pass True/False value indicating result.
* \param fmt A printf-style format string describing the test.
* \param pvar A var-args pointer to any parameters for the format string.
* \return The value of \p pass.
*/
LIBCOM_API int testOkV(int pass, const char *fmt, va_list pvar);
/** \brief Passing test result with printf-style description.
* \param fmt A printf-style format string describing the test.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API void testPass(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** \brief Failing test result with printf-style description.
* \param fmt A printf-style format string describing the test.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API void testFail(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** @} */
/** \name Missing or Failing Tests
* Routines for handling special situations.
*/
/** @{ */
/** \brief Place-holders for tests that can't be run.
* \param skip How many tests are being skipped.
* \param why Reason for skipping these tests.
*/
LIBCOM_API void testSkip(int skip, const char *why);
/** \brief Mark the start of a group of tests that are expected to fail
* \param why Reason for expected failures.
*/
LIBCOM_API void testTodoBegin(const char *why);
/** \brief Mark the end of a failing test group.
*/
LIBCOM_API void testTodoEnd(void);
/** \brief Stop testing, program cannot continue.
* \param fmt A printf-style format string giving the reason for stopping.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API void testAbort(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** @} */
/** \brief Output additional diagnostics
* \param fmt A printf-style format string containing diagnostic information.
* \param ... Any parameters required for the format string.
*/
LIBCOM_API int testDiag(EPICS_PRINTF_FMT(const char *fmt), ...)
EPICS_PRINTF_STYLE(1, 2);
/** \brief Mark the end of testing.
*/
LIBCOM_API int testDone(void);
/** \brief Return non-zero in shared/oversubscribed testing environments
*
* May be used to testSkip(), or select longer timeouts, for some cases
* when the test process may be preempted for arbitrarily long times.
* This is common in shared CI environments.
*
* The environment variable $EPICS_TEST_IMPRECISE_TIMING=YES should be
* set in by such testing environments.
*/
LIBCOM_API
int testImpreciseTiming(void);
/** \name Test Harness for Embedded OSs
* These routines are used to create a test-harness that can run
* multiple test programs, collect their names and results, then
* display a summary at the end of testing.
*/
/** @{ */
typedef int (*TESTFUNC)(void);
/** \brief Initialize test harness
*/
LIBCOM_API void testHarness(void);
/** \brief End of testing
*/
LIBCOM_API void testHarnessExit(void *dummy);
/** \brief Run a single test program
* \param name Program name
* \param func Function implementing test program
*/
LIBCOM_API void runTestFunc(const char *name, TESTFUNC func);
/** \brief Run a test program
* \param func Name of the test program.
*/
#define runTest(func) runTestFunc(#func, func)
/** \brief Declare all test programs finished
*/
#define testHarnessDone() testHarnessExit(0)
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* INC_epicsUnitTest_H */