diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/bitbuf.test.c | 34 | ||||
| -rw-r--r-- | test/hook.test.c | 45 | ||||
| -rw-r--r-- | test/kv.test.c | 49 | ||||
| -rw-r--r-- | test/test.h | 234 | 
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  | 
