/*************************************************************************\ * Copyright (c) 2006 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. \*************************************************************************/ /* * Author: Andrew Johnson * * Unit test module which generates output in the Test Anything Protocol * format. See perldoc Test::Harness for details of output format. * */ #include #include #include #ifdef _WIN32 # include # include # if !defined(_MSC_VER) || _MSC_VER>=1700 # include /* provided by mingw and MSVC >= 2012 */ # define HAVE_SETERROMODE # endif #endif #ifdef __rtems__ # include # ifndef RTEMS_LEGACY_STACK # include # endif #endif #include "epicsThread.h" #include "epicsMutex.h" #include "epicsUnitTest.h" #include "epicsExit.h" #include "epicsTime.h" #include "ellLib.h" #include "errlog.h" #include "cantProceed.h" #include "epicsStackTrace.h" typedef struct { ELLNODE node; const char *name; int tests; int failures; int skips; } testFailure; static epicsMutexId testLock = 0; static int perlHarness; static int planned; static int tested; static int passed; static int failed; static int skipped; static int bonus; static const char *todo; epicsTimeStamp started; static int Harness; static int Programs; static int Tests; ELLLIST faults; const char *testing = NULL; static epicsThreadOnceId onceFlag = EPICS_THREAD_ONCE_INIT; #ifdef _WIN32 /* * if we return FALSE, _CrtDbgReport is called to print to file etc * if we return TRUE, we are the only function called */ static int testReportHook(int reportType, char *message, int *returnValue) { int nRet = 0; switch (reportType) { case _CRT_ASSERT: case _CRT_ERROR: epicsStackTrace(); break; default: break; } if (returnValue) { *returnValue = 0; } return nRet; } #endif static void testOnce(void *dummy) { testLock = epicsMutexMustCreate(); perlHarness = (getenv("HARNESS_ACTIVE") != NULL); #ifdef __rtems__ // syslog() on RTEMS prints to stdout, which will interfere with test output. // setlogmask() ignores empty mask, so must allow at least one level. (void)setlogmask(LOG_MASK(LOG_CRIT)); printf("# mask syslog() output\n"); #ifndef RTEMS_LEGACY_STACK // with libbsd setlogmask() is a no-op :( // name strings in sys/syslog.h rtems_bsd_setlogpriority("crit"); #endif #endif #ifdef _WIN32 #ifdef HAVE_SETERROMODE /* SEM_FAILCRITICALERRORS - Don't display modal dialog * !SEM_NOALIGNMENTFAULTEXCEPT - auto-fix unaligned access * !SEM_NOGPFAULTERRORBOX - enable Windows Error Reporting (also enables postmortem debugger hooks) * SEM_NOOPENFILEERRORBOX - Don't display modal dialog */ SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX); #endif /* Disable dialog for assertion failures */ _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE |_CRTDBG_MODE_DEBUG ); _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE |_CRTDBG_MODE_DEBUG ); _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR ); _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE |_CRTDBG_MODE_DEBUG ); _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR ); _CrtSetReportHook2( _CRT_RPTHOOK_INSTALL, testReportHook ); #endif } void testPlan(int plan) { epicsThreadOnce(&onceFlag, testOnce, NULL); epicsMutexMustLock(testLock); planned = plan; tested = passed = failed = skipped = bonus = 0; todo = NULL; if (plan) printf("1..%d\n", plan); epicsMutexUnlock(testLock); } int testOkV(int pass, const char *fmt, va_list pvar) { const char *result = "not ok"; epicsMutexMustLock(testLock); tested++; if (pass) { result += 4; /* skip "not " */ passed++; if (todo) bonus++; } else { if (todo) passed++; else failed++; } printf("%s %2d - ", result, tested); vprintf(fmt, pvar); if (todo) printf(" # TODO %s", todo); putchar('\n'); fflush(stdout); epicsMutexUnlock(testLock); return pass; } int testOk(int pass, const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); testOkV(pass, fmt, pvar); va_end(pvar); return pass; } void testPass(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); testOkV(1, fmt, pvar); va_end(pvar); } void testFail(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); testOkV(0, fmt, pvar); va_end(pvar); } void testSkip(int skip, const char *why) { epicsMutexMustLock(testLock); while (skip-- > 0) { tested++; passed++; skipped++; printf("ok %2d # SKIP %s\n", tested, why); } fflush(stdout); epicsMutexUnlock(testLock); } void testTodoBegin(const char *why) { epicsMutexMustLock(testLock); todo = why; epicsMutexUnlock(testLock); } void testTodoEnd(void) { testTodoBegin(NULL); } int testDiag(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); epicsMutexMustLock(testLock); printf("# "); vprintf(fmt, pvar); putchar('\n'); fflush(stdout); epicsMutexUnlock(testLock); va_end(pvar); return 0; } void testAbort(const char *fmt, ...) { va_list pvar; va_start(pvar, fmt); printf("Bail out! "); vprintf(fmt, pvar); putchar('\n'); fflush(stdout); va_end(pvar); abort(); } static void testResult(const char *result, int count) { printf("%12.12s: %3d = %5.2f%%\n", result, count, 100.0 * count / tested); } int testDone(void) { int status = 0; epicsMutexMustLock(testLock); if (perlHarness) { if (!planned) printf("1..%d\n", tested); else if (tested != planned) status = 2; if (failed) status |= 1; } else { if (planned && tested > planned) { printf("\nRan %d tests but only planned for %d!\n", tested, planned); status = 2; } else if (planned && tested < planned) { printf("\nPlanned %d tests but only ran %d\n", planned, tested); status = 2; } printf("\n Results\n =======\n Tests: %-3d\n", tested); if (tested) { testResult("Passed", passed); if (bonus) testResult("Todo Passes", bonus); if (failed) { testResult("Failed", failed); status |= 1; } if (skipped) testResult("Skipped", skipped); } } if (Harness) { if (failed) { testFailure *fault = callocMustSucceed(1, sizeof(testFailure), "testDone calloc"); fault->name = testing; fault->tests = tested; fault->failures = failed; fault->skips = skipped; ellAdd(&faults, &fault->node); } Programs++; Tests += tested; } epicsMutexUnlock(testLock); return (status); } static int impreciseTiming; int testImpreciseTiming(void) { if(impreciseTiming==0) { const char* env = getenv("EPICS_TEST_IMPRECISE_TIMING"); impreciseTiming = (env && strcmp(env, "YES")==0) ? 1 : -1; } return impreciseTiming>0; } /* Our test harness, for RTEMS and vxWorks */ void testHarnessExit(void *dummy) { epicsTimeStamp ended; int Faulty; if (!Harness) return; epicsTimeGetCurrent(&ended); printf("\n\n EPICS Test Harness Results" "\n ==========================\n\n"); Faulty = ellCount(&faults); if (!Faulty) printf("All tests successful.\n"); else { int Failures = 0; testFailure *f; printf("Failing Program Tests Faults\n" "---------------------------------------\n"); while ((f = (testFailure *)ellGet(&faults))) { Failures += f->failures; printf("%-25s %5d %5d\n", f->name, f->tests, f->failures); if (f->skips) printf("%d subtests skipped\n", f->skips); free(f); } printf("\nFailed %d/%d test programs. %d/%d subtests failed.\n", Faulty, Programs, Failures, Tests); } printf("Programs=%d, Tests=%d, %.0f wallclock secs\n\n", Programs, Tests, epicsTimeDiffInSeconds(&ended, &started)); } void testHarness(void) { epicsThreadOnce(&onceFlag, testOnce, NULL); epicsAtExit(testHarnessExit, NULL); Harness = 1; Programs = 0; Tests = 0; ellInit(&faults); epicsTimeGetCurrent(&started); } void runTestFunc(const char *name, TESTFUNC func) { printf("\n***** %s *****\n", name); testing = name; func(); /* May not return */ epicsThreadSleep(1.0); }