summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2021-11-20 03:10:50 +0000
committerMichael Smith <mikesmiffy128@gmail.com>2021-11-20 03:18:08 +0000
commitda6f343032cb01597dc7866e66f091adf3243a62 (patch)
tree870f8cb8e82bb42202ab92bea03fc6ab35ada7ca /test
Initial public snapshot
With code from Bill. Thanks Bill!
Diffstat (limited to 'test')
-rw-r--r--test/bitbuf.test.c34
-rw-r--r--test/hook.test.c45
-rw-r--r--test/kv.test.c49
-rw-r--r--test/test.h234
4 files changed, 362 insertions, 0 deletions
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 <stdio.h>
+#include <string.h>
+
+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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+// 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 <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_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