From da6f343032cb01597dc7866e66f091adf3243a62 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 20 Nov 2021 03:10:50 +0000 Subject: Initial public snapshot With code from Bill. Thanks Bill! --- test/bitbuf.test.c | 34 ++++++++ test/hook.test.c | 45 +++++++++++ test/kv.test.c | 49 +++++++++++ test/test.h | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 362 insertions(+) create mode 100644 test/bitbuf.test.c create mode 100644 test/hook.test.c create mode 100644 test/kv.test.c create mode 100644 test/test.h (limited to 'test') diff --git a/test/bitbuf.test.c b/test/bitbuf.test.c new file mode 100644 index 0000000..58d1c4d --- /dev/null +++ b/test/bitbuf.test.c @@ -0,0 +1,34 @@ +/* This file is dedicated to the public domain. */ + +{.desc = "the bit buffer implementation"}; + +#include "../src/bitbuf.h" +#include "../src/intdefs.h" + +#include +#include + +static union { + char buf[512]; + bitbuf_cell buf_align[512 / sizeof(bitbuf_cell)]; +} bb_buf; +static struct bitbuf bb = {bb_buf.buf, 512, 512 * 8, 0, false, false, "test"}; + +TEST("The possible UB in bitbuf_appendbuf shouldn't trigger horrible bugs", 0) { + char unalign[3] = {'X', 'X', 'X'}; + char _buf[32 + sizeof(bitbuf_cell)]; + char *buf = _buf; + if (bitbuf_align <= 1) { + // *shouldn't* happen + fputs("what's going on with the alignment???\n", stderr); + return false; + } + // make sure the pointer is definitely misaligned + while (!((usize)buf % bitbuf_align)) ++buf; + + memcpy(buf, "Misaligned test buffer contents!", 32); + bitbuf_appendbuf(&bb, buf, 32); + return !memcmp(bb.buf, buf, 32); +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/test/hook.test.c b/test/hook.test.c new file mode 100644 index 0000000..a918e22 --- /dev/null +++ b/test/hook.test.c @@ -0,0 +1,45 @@ +/* This file is dedicated to the public domain. */ + +{.desc = "inline function hooking"}; + +#ifdef _WIN32 + +#include "../src/udis86.c" +#include "../src/os.c" +#include "../src/hook.c" + +#include +#include +#include + +// stubs +void con_warn(const char *msg, ...) { + va_list l; + va_start(l, msg); + vfprintf(stderr, msg, l); + va_end(l); +} + +__attribute__((noinline)) +static int some_function(int a, int b) { return a + b; } +static int (*orig_some_function)(int, int); +static int some_hook(int a, int b) { + return orig_some_function(a, b) + 5; +} + +TEST("Inline hooks should be able to wrap the original function", 0) { + orig_some_function = hook_inline(&some_function, &some_hook); + if (!orig_some_function) return false; + return some_function(5, 5) == 15; +} + +TEST("Inline hooks should be removable again", 0) { + orig_some_function = hook_inline(&some_function, &some_hook); + if (!orig_some_function) return false; + unhook_inline(orig_some_function); + return some_function(5, 5) == 10; +} + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/test/kv.test.c b/test/kv.test.c new file mode 100644 index 0000000..cd08d16 --- /dev/null +++ b/test/kv.test.c @@ -0,0 +1,49 @@ +/* This file is dedicated to the public domain. */ + +{.desc = "the KeyValues parser"}; + +// undef conflicting macros +#undef ERROR // windows.h +#undef OUT // " +#undef EOF // stdio.h +#include "../src/kv.c" + +#include "../src/intdefs.h" +#include "../src/noreturn.h" + +static noreturn die(const struct kv_parser *kvp) { + fprintf(stderr, "parse error: %d:%d: %s\n", kvp->line, kvp->col, + kvp->errmsg); + exit(1); +} + +static void tokcb(enum kv_token type, const char *p, uint len, + void *ctxt) { + // nop - we're just testing the tokeniser +} + +static const char data[] = +"KeyValues {\n\tKey/1\tVal1! \tKey2\nVal2// comment\n\"String Key\"// also comment\nVal3 Key4{ Key5 \"Value Five\" } // one more\n\t\n}" +; +static const int sz = sizeof(data) - 1; + +TEST("parsing should work with any buffer size", 0) { + for (int chunksz = 3; chunksz <= sz; ++chunksz) { + struct kv_parser kvp = {0}; + // sending data in chunks to test reentrancy + for (int chunk = 0; chunk * chunksz < sz; ++chunk) { + int thischunk = chunksz; + if (chunk * chunksz + thischunk > sz) { + thischunk = sz - chunk * chunksz; + } + kv_parser_feed(&kvp, data + chunk * chunksz, thischunk, + tokcb, 0); + if (kvp.state == KV_PARSER_ERROR) die(&kvp); + } + kv_parser_done(&kvp); + if (kvp.state == KV_PARSER_ERROR) die(&kvp); + } + return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..3893359 --- /dev/null +++ b/test/test.h @@ -0,0 +1,234 @@ +/* + * test.h - Michael Smith + * 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 +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#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_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 -- cgit v1.2.3