Last active
April 1, 2022 14:50
-
-
Save Qix-/11319a07a37d447ee8518e21c1a48a4b to your computer and use it in GitHub Desktop.
Tiny test suite for C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
NOTE: I'm just including this here since it was | |
NOTE: useful for another project. There are some | |
NOTE: project-specific things in here; customize | |
NOTE: INIT_TEST, END_TEST and TEST to suite your | |
NOTE: needs. | |
*/ | |
#ifndef TEST_SUITE_H | |
#define TEST_SUITE_H | |
#pragma once | |
/* | |
Tiny test suite. | |
Josh Junon, 2022. | |
This header file is released | |
into the public domain. | |
Credit is appreciated but | |
not required. | |
USAGE: | |
Define tests with | |
TEST(name) { body } | |
which should perform tests via | |
assert(some != expression); | |
then, in `int main()', list which ones | |
PASS(name); | |
or instead are expected to | |
FAIL(name, "with this assert"); | |
FAIL()'s second parameter must be | |
a string, and can optionally start | |
and/or end with '*' to indicate | |
that the assert message should start | |
or end with a string. | |
'*' in the middle of the string is | |
treated literally (it is not a glob | |
nor a regular expression). They're | |
only honored at the beginning or end. | |
*/ | |
#include <setjmp.h> | |
#include <stdio.h> | |
#include <string.h> | |
static jmp_buf assertbuf; | |
#ifdef assert | |
#undef assert | |
#endif | |
#define STRIZE_(_x)#_x | |
#define STRIZE(_x)STRIZE_(_x) | |
static const char * last_assert_msg = NULL; | |
static const char * last_assert = NULL; | |
#define assert(_expr) \ | |
do { \ | |
if (!(_expr)) { \ | |
last_assert_msg = #_expr; \ | |
last_assert = "assertion error: " STRIZE(__LINE__) ": " #_expr; \ | |
longjmp(assertbuf, 1); \ | |
} \ | |
} while(0) | |
#define INIT_TEST \ | |
test_ht_t Htest; \ | |
ht_t *H = &Htest.ht; \ | |
Htest.resize_calls = 0; \ | |
Htest.last_resize_ht = NULL; \ | |
Htest.last_resize_bytes = 0; \ | |
Htest.ht.base = NULL; \ | |
Htest.ht.length = 0; \ | |
Htest.ht.width = 0; \ | |
Htest.ht.resize = ¬ify_ht_resize | |
#define END_TEST \ | |
do { \ | |
if (H->base) { \ | |
free(H->base); \ | |
H->base = NULL; \ | |
H->length = 0; \ | |
} \ | |
} while (0) | |
#define PASS(_name) \ | |
do { \ | |
INIT_TEST; \ | |
if (setjmp(assertbuf)) { \ | |
status = 1; \ | |
fprintf(stderr, "\x1b[91;1mFAIL\x1b[m\n%s\n\n", last_assert); \ | |
} else { \ | |
last_assert = NULL; \ | |
last_assert_msg = NULL; \ | |
fputs(#_name " ... ", stderr); \ | |
(_name)(H, &Htest); \ | |
fputs("\x1b[92;1mOK\x1b[m\n", stderr); \ | |
} \ | |
END_TEST; \ | |
} while (0) | |
#define FAIL(_name, _with) \ | |
do { \ | |
INIT_TEST; \ | |
if (setjmp(assertbuf)) { \ | |
const char *test_with = (_with); \ | |
size_t test_with_len = strlen(test_with); \ | |
size_t last_assert_msg_len = strlen(last_assert_msg); \ | |
if (last_assert_msg == NULL) { \ | |
fputs("\x1b[91;1mFAIL (asserted with NULL/empty message)\x1b[m\n", stderr); \ | |
status = 1; \ | |
} else { \ | |
int passed = 0; \ | |
if (test_with_len == 0) { \ | |
passed = !*last_assert_msg; \ | |
} else if ( \ | |
(test_with_len == 1 && *test_with == '*') \ | |
|| (test_with_len == 2 && test_with[0] == '*' && test_with[1] == '*') \ | |
) { \ | |
passed = 1; \ | |
} else if (test_with[0] == '*' && test_with[test_with_len - 1] == '*') { \ | |
char *copied = malloc(test_with_len - 1); \ | |
if (copied == NULL) { \ | |
fputs("\x1b[92;1mCRITICAL: FAILED TO MALLOC TEST STRING\x1b[m", stderr); \ | |
fputs("\x1b[92;1mSomething went horribly wrong (are you out of memory?)\x1b[m", stderr); \ | |
} else { \ | |
strncpy(copied, &test_with[1], test_with_len - 2); \ | |
passed = strstr(last_assert_msg, copied) != NULL; \ | |
free(copied); \ | |
} \ | |
} else if (test_with[0] == '*') { \ | |
passed = ( \ | |
(test_with_len - 1) <= last_assert_msg_len \ | |
&& strcmp(&test_with[1], &last_assert_msg[last_assert_msg_len - (test_with_len - 1)]) == 0 \ | |
); \ | |
} else if (test_with[test_with_len - 1] == '*') { \ | |
passed = strncmp(test_with, last_assert_msg, test_with_len - 1) == 0; \ | |
} else { \ | |
passed = strcmp(test_with, last_assert_msg) == 0; \ | |
} \ | |
if (passed) { \ | |
fprintf(stderr, "\x1b[92;1mOK (failed successfully)\x1b[m\n"); \ | |
} else { \ | |
fprintf( \ | |
stderr, \ | |
"\x1b[91;1mFAIL (wrong assert message)\x1b[m\n%s\n\n" \ | |
"\x1b[31mgot: %s\x1b[m\n\x1b[32mexpected: %s\x1b[m" \ | |
"\n\x1b[2mFAIL() on line %d\x1b[m\n\n", \ | |
last_assert, \ | |
last_assert_msg, \ | |
(_with), \ | |
__LINE__ \ | |
); \ | |
status = 1; \ | |
} \ | |
} \ | |
} else { \ | |
last_assert = NULL; \ | |
last_assert_msg = NULL; \ | |
fputs(#_name " ... ", stderr); \ | |
(_name)(H, &Htest); \ | |
fputs("\x1b[91;1mFAIL (expected assert)\x1b[m\n", stderr); \ | |
status = 1; \ | |
} \ | |
END_TEST; \ | |
} while (0) | |
#define TEST(_name) static void _name(ht_t *H, test_ht_t *Htest) | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment