123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- /*
- * test.h - Michael Smith <mikesmiffy128@gmail.com>
- * I hereby dedicate the contents of this file to the public domain. In
- * jurisdictions with no public domain, go you your supreme court and get a
- * public domain.
- */
- /*
- * NOTE: This is a hacky black magic Windows port! If you only want Unix
- * support, the less-atrocious original resides in a Git repository:
- * https://gitlab.com/mikesmiffy128/test.h
- */
- #include <stdlib.h>
- #include <stdio.h>
- #include <stdbool.h>
- #include <string.h>
- #ifdef _WIN32
- #include <Windows.h>
- #else
- #include <unistd.h>
- #include <signal.h>
- #include <sys/wait.h>
- #include <sys/select.h>
- #endif
- #ifdef __clang__
- #define _TEST_SILENCE_CLANG \
- _Pragma("clang diagnostic push") \
- _Pragma("clang diagnostic ignored \"-Winitializer-overrides\"")
- #define _TEST_UNSILENCE_CLANG \
- _Pragma("clang diagnostic pop")
- #else
- #define _TEST_SILENCE_CLANG
- #define _TEST_UNSILENCE_CLANG
- #endif
- static struct _test_desc {
- char *desc;
- int default_flags;
- } _test_desc;
- static struct _test {
- char *desc;
- /* Optional attributes that can be set to further customise the test case */
- // Note: the first attribute here has to have a default value of 0 because
- // of __VA_ARGS__ requiring at least one argument
- int expected_exit; /* expected exit from forked child (255 is reserved!) */
- int flags; /* see flags below */
- int timeout; /* milisecond timeout on forked child (if forking) */
- bool (*_f)(void);
- struct _test *_next;
- } *_tests = 0, **_tests_tail = &_tests;
- static int _ntests = 0;
- /* Test flags - currently just NOFORK but you could add your own custom ones! */
- #define NOFORK 1
- #define _TEST_USE_DEFAULT_FLAGS -1 // indicator to use global default_flags
- #define _TEST_DEFAULT_TIMEOUT 1000 // 1s seems reasonable
- #define _TESTCAT1(a, b) a##b
- #define _TESTCAT(a, b) _TESTCAT1(a, b)
- #define _TESTSTR1(x) #x
- #define _TESTSTR(x) _TESTSTR1(x)
- #define TEST(desc_, ...) \
- static bool _TESTCAT(_test_f_, __LINE__)(void); \
- _TEST_SILENCE_CLANG \
- static struct _test _TESTCAT(_test_, __LINE__) = { \
- .flags = _TEST_USE_DEFAULT_FLAGS, \
- .timeout = _TEST_DEFAULT_TIMEOUT, \
- .desc = __FILE__":"_TESTSTR(__LINE__)": "desc_ __VA_OPT__(,) \
- __VA_ARGS__, \
- ._f = &_TESTCAT(_test_f_, __LINE__) \
- }; \
- _TEST_UNSILENCE_CLANG \
- /* constructor adds tests to the list tail to run them in order */ \
- __attribute__((constructor(100 + __LINE__))) \
- static void _TESTCAT(_test_init_, __LINE__)(void) { \
- if (_TESTCAT(_test_, __LINE__).flags == _TEST_USE_DEFAULT_FLAGS) { \
- _TESTCAT(_test_, __LINE__).flags = _test_desc.default_flags; \
- } \
- _TESTCAT(_test_, __LINE__)._next = *_tests_tail; \
- *_tests_tail = &_TESTCAT(_test_, __LINE__); \
- _tests_tail = &_TESTCAT(_test_, __LINE__)._next; \
- ++_ntests; \
- } \
- static bool _TESTCAT(_test_f_, __LINE__)(void)
- #ifdef _WIN32
- // since we can't fork, we CreateProcess ourselves and use WriteProcessMemory
- // to set this function pointer to call the test we want to call
- static volatile bool (*_test_entry_f)(void) = 0;
- unsigned short _test_exepath[MAX_PATH];
- #define _test_perror_win(thing) do { \
- char err[128]; \
- FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), \
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err, sizeof(err), 0); \
- fprintf(stderr, thing ": %s\n", err); \
- return false; \
- } while (0)
- #else
- static sigset_t _test_sigmask = {0};
- #endif
- static bool _run_test(struct _test *t) {
- if (t->flags & NOFORK) return t->_f();
- #ifdef _WIN32
- STARTUPINFOW startinfo = {0};
- PROCESS_INFORMATION info;
- if (!CreateProcessW(_test_exepath, L"", 0, 0, 1, CREATE_SUSPENDED, 0, 0,
- &startinfo, &info)) {
- _test_perror_win("CreateProcess");
- }
- if (!WriteProcessMemory(info.hProcess, (void *)&_test_entry_f, &t->_f,
- sizeof(t->_f), 0)) {
- TerminateProcess(info.hProcess, -1);
- _test_perror_win("WriteProcessMemory");
- }
- ResumeThread(info.hThread);
- bool success;
- if (t->timeout) {
- if (WaitForSingleObject(info.hProcess, t->timeout) == WAIT_TIMEOUT) {
- TerminateProcess(info.hProcess, -1);
- fprintf(stderr, "child process timed out after %d milliseconds\n",
- t->timeout);
- success = false;
- goto r;
- }
- }
- else {
- WaitForSingleObject(info.hProcess, INFINITE);
- }
- unsigned long status;
- GetExitCodeProcess(info.hProcess, &status);
- success = status == t->expected_exit;
- r: CloseHandle(info.hProcess);
- CloseHandle(info.hThread);
- return success;
- #else
- pid_t pid = fork();
- if (pid == -1) {
- perror("fork");
- return false;
- }
- if (!pid) {
- bool ret = t->_f();
- if (!ret) exit(255);
- exit(t->expected_exit);
- }
- if (t->timeout) {
- struct timespec ts = { t->timeout / 1000, (t->timeout % 1000) * 1000000 };
- if (!pselect(0, 0, 0, 0, &ts, &_test_sigmask)) {
- // if pselect returned zero it must've timed out
- fprintf(stderr, "child process timed out after %d milliseconds\n",
- t->timeout);
- kill(pid, SIGKILL); // XXX should this be a less harsh signal?
- waitpid(pid, 0, 0); // still have to reap the zombie process
- return false;
- }
- }
- // either there was no timeout value or pselect errored meaning we should
- // have something to wait() on!
- int status;
- wait(&status);
- if (WIFEXITED(status)) {
- return WEXITSTATUS(status) == t->expected_exit;
- }
- else /* WIFSIGNALED(status) */ {
- fprintf(stderr, "child process killed by signal %d (%s)\n",
- WTERMSIG(status), strsignal(WTERMSIG(status)));
- return false;
- }
- #endif
- }
- #ifndef _WIN32
- static void _test_sigchld(int sig) {}
- #endif
- /*
- * Main test driver, does the important stuff
- */
- int main(void) {
- #ifdef _WIN32
- // if _test_entry_f points at something, we're the """forked""" child
- if (_test_entry_f) return !_test_entry_f();
- GetModuleFileNameW(0, _test_exepath, sizeof(_test_exepath) /
- sizeof(*_test_exepath));
- // make the output look correct
- void *con = GetStdHandle(STD_OUTPUT_HANDLE);
- unsigned long conmode;
- GetConsoleMode(con, &conmode);
- SetConsoleMode(con, conmode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
- #else
- // set up no-op SIGCHLD handling so we can (ab)use pselect() to do race-free
- // timeouts
- struct sigaction sa;
- sigfillset(&sa.sa_mask);
- sa.sa_flags = 0;
- sa.sa_handler = &_test_sigchld;
- sigaction(SIGCHLD, &sa, 0);
- sigaddset(&_test_sigmask, SIGCHLD);
- sigprocmask(SIG_BLOCK, &_test_sigmask, 0);
- sigemptyset(&_test_sigmask);
- #endif
- int thistest = 1;
- bool failed = false;
- for (struct _test *t = _tests; t; t = t->_next, ++thistest) {
- if (!_run_test(t)) {
- if (!failed) {
- fprintf(stderr, "\
- \x1b[1;31m==== TESTS FAILED ====\x1b[0m\n\
- Testing \x1b[36m%s\x1b[0m failed on the following cases:\n\
- ", _test_desc.desc);
- }
- failed = true;
- fprintf(stderr, "[%02d/%02d] %s\n", thistest, _ntests, t->desc);
- }
- }
- #ifdef _WIN32
- SetConsoleMode(con, conmode);
- #endif
- return !!failed;
- }
- // get any normal main()s out the way in the tested code
- #define main _main
- static struct _test_desc _test_desc = // user input follows this header
- // vi: sw=4 ts=4 noet tw=80 cc=80
|