#ifndef SIMPLE_TEST_H #define SIMPLE_TEST_H #include #include #include #include #include #include #include /************************************************** * INTERNAL FUNCTIONALITY. DO NOT CALL DIRECTLY * **************************************************/ enum simple_test_type { SIMPLE_TEST_BOOL, SIMPLE_TEST_INT, SIMPLE_TEST_UNSIGNED, SIMPLE_TEST_CHAR, SIMPLE_TEST_STRING, SIMPLE_TEST_POINTER, SIMPLE_TEST_UNKNOWN, SIMPLE_TEST_MISMATCH, }; enum simple_test_cond { SIMPLE_TEST_TRUE, SIMPLE_TEST_FALSE, SIMPLE_TEST_EQ, SIMPLE_TEST_NEQ, SIMPLE_TEST_GT, SIMPLE_TEST_LT, SIMPLE_TEST_GEQ, SIMPLE_TEST_LEQ }; static intmax_t simple_test_il, simple_test_ir; static uintmax_t simple_test_ul, simple_test_ur; static char *simple_test_sl, *simple_test_sr; static void *simple_test_pl, *simple_test_pr; static int simple_test_assert(enum simple_test_type t, enum simple_test_cond c, ...) { va_list ap; bool two = !(c == SIMPLE_TEST_TRUE || c == SIMPLE_TEST_FALSE); int r; va_start(ap, c); switch (t) { case SIMPLE_TEST_MISMATCH: return -2; case SIMPLE_TEST_BOOL: case SIMPLE_TEST_INT: case SIMPLE_TEST_CHAR: simple_test_il = va_arg(ap, intmax_t); if (two) simple_test_ir = va_arg(ap, intmax_t); switch (c) { case SIMPLE_TEST_TRUE: r = simple_test_il; goto end; case SIMPLE_TEST_FALSE: r = !simple_test_il; goto end; case SIMPLE_TEST_EQ: r = simple_test_il == simple_test_ir; goto end; case SIMPLE_TEST_NEQ: r = simple_test_il != simple_test_ir; goto end; case SIMPLE_TEST_GT: r = simple_test_il > simple_test_ir; goto end; case SIMPLE_TEST_LT: r = simple_test_il < simple_test_ir; goto end; case SIMPLE_TEST_GEQ: r = simple_test_il >= simple_test_ir; goto end; case SIMPLE_TEST_LEQ: r = simple_test_il <= simple_test_ir; goto end; } case SIMPLE_TEST_UNSIGNED: simple_test_ul = va_arg(ap, uintmax_t); if (two) simple_test_ur = va_arg(ap, uintmax_t); switch (c) { case SIMPLE_TEST_TRUE: r = simple_test_ul; goto end; case SIMPLE_TEST_FALSE: r = !simple_test_ul; goto end; case SIMPLE_TEST_EQ: r = simple_test_ul == simple_test_ur; goto end; case SIMPLE_TEST_NEQ: r = simple_test_ul != simple_test_ur; goto end; case SIMPLE_TEST_GT: r = simple_test_ul > simple_test_ur; goto end; case SIMPLE_TEST_LT: r = simple_test_ul < simple_test_ur; goto end; case SIMPLE_TEST_GEQ: r = simple_test_ul >= simple_test_ur; goto end; case SIMPLE_TEST_LEQ: r = simple_test_ul <= simple_test_ur; goto end; } case SIMPLE_TEST_STRING: simple_test_sl = va_arg(ap, char *); if (two) simple_test_sr = va_arg(ap, char *); switch (c) { case SIMPLE_TEST_TRUE: r = simple_test_sl != NULL; goto end; case SIMPLE_TEST_FALSE: r = simple_test_sl == NULL; goto end; case SIMPLE_TEST_EQ: if (simple_test_sl == NULL || simple_test_sr == NULL) r = simple_test_sl == simple_test_sr; else r = strcmp(simple_test_sl, simple_test_sr) ? 0 : 1; goto end; case SIMPLE_TEST_NEQ: if (simple_test_sl == NULL || simple_test_sr == NULL) r = simple_test_sl != simple_test_sr; else r = strcmp(simple_test_sl, simple_test_sr) ? 1 : 0; goto end; case SIMPLE_TEST_GT: if (simple_test_sl == NULL || simple_test_sr == NULL) r = false; else r = strcmp(simple_test_sl, simple_test_sr) > 0 ? 1 : 0; goto end; case SIMPLE_TEST_LT: if (simple_test_sl == NULL || simple_test_sr == NULL) r = false; else r = strcmp(simple_test_sl, simple_test_sr) < 0 ? 1 : 0; goto end; case SIMPLE_TEST_GEQ: if (simple_test_sl == NULL || simple_test_sr == NULL) r = false; else r = strcmp(simple_test_sl, simple_test_sr) >= 0 ? 1 : 0; goto end; case SIMPLE_TEST_LEQ: if (simple_test_sl == NULL || simple_test_sr == NULL) r = false; else r = strcmp(simple_test_sl, simple_test_sr) <= 0 ? 1 : 0; goto end; } case SIMPLE_TEST_POINTER: simple_test_pl = va_arg(ap, void *); if (two) simple_test_pr = va_arg(ap, void *); switch (c) { case SIMPLE_TEST_TRUE: r = simple_test_pl != NULL; goto end; case SIMPLE_TEST_FALSE: r = simple_test_pl == NULL; goto end; case SIMPLE_TEST_EQ: r = simple_test_pl == simple_test_pr; goto end; case SIMPLE_TEST_NEQ: r = simple_test_pl != simple_test_pr; goto end; case SIMPLE_TEST_GT: r = simple_test_pl > simple_test_pr; goto end; case SIMPLE_TEST_LT: r = simple_test_pl < simple_test_pr; goto end; case SIMPLE_TEST_GEQ: r = simple_test_pl >= simple_test_pr; goto end; case SIMPLE_TEST_LEQ: r = simple_test_pl <= simple_test_pr; goto end; } default: r = -1; } end: va_end(ap); return r; } static enum simple_test_type simple_test_type_resolve(enum simple_test_type t1, enum simple_test_type t2) { if (t1 != t2) { if ((t1 == SIMPLE_TEST_INT && t2 == SIMPLE_TEST_UNSIGNED) || (t1 == SIMPLE_TEST_UNSIGNED && t2 == SIMPLE_TEST_INT) || (t1 == SIMPLE_TEST_BOOL && t2 == SIMPLE_TEST_UNSIGNED) || (t1 == SIMPLE_TEST_UNSIGNED && t2 == SIMPLE_TEST_BOOL)) { return SIMPLE_TEST_UNSIGNED; } else if ((t1 == SIMPLE_TEST_BOOL && t2 == SIMPLE_TEST_INT) || (t1 == SIMPLE_TEST_INT && t2 == SIMPLE_TEST_BOOL)) { return SIMPLE_TEST_INT; } else if ((t1 == SIMPLE_TEST_INT && t2 == SIMPLE_TEST_CHAR) || (t1 == SIMPLE_TEST_CHAR && t2 == SIMPLE_TEST_INT)) { return SIMPLE_TEST_CHAR; } else if ((t1 == SIMPLE_TEST_CHAR && t2 == SIMPLE_TEST_UNSIGNED) || (t1 == SIMPLE_TEST_UNSIGNED && t2 == SIMPLE_TEST_CHAR)) { return SIMPLE_TEST_UNSIGNED; } else if ((t1 == SIMPLE_TEST_POINTER && t2 == SIMPLE_TEST_STRING) || (t1 == SIMPLE_TEST_STRING && t2 == SIMPLE_TEST_POINTER)) { return SIMPLE_TEST_POINTER; } else { return SIMPLE_TEST_MISMATCH; } } return t1; } #define SIMPLE_TEST_TYPE(a) \ _Generic((a), \ bool: SIMPLE_TEST_BOOL, \ char: SIMPLE_TEST_CHAR, \ int8_t: SIMPLE_TEST_INT, \ int16_t: SIMPLE_TEST_INT, \ int32_t: SIMPLE_TEST_INT, \ int64_t: SIMPLE_TEST_INT, \ uint8_t: SIMPLE_TEST_UNSIGNED, \ uint16_t: SIMPLE_TEST_UNSIGNED, \ uint32_t: SIMPLE_TEST_UNSIGNED, \ uint64_t: SIMPLE_TEST_UNSIGNED, \ char *: SIMPLE_TEST_STRING, \ void *: SIMPLE_TEST_POINTER, \ default: SIMPLE_TEST_UNKNOWN) #define SIMPLE_TEST_FAIL1(...) \ do { \ printf("\e[1m%*c :: at line %d, \e[m\e[1;31mfail: \e[m", \ simple_test_pad_width, ' ', __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ } while (0) #define SIMPLE_TEST_PRINT_VAL(w, s, t) \ do { \ printf("\e[1m%*c :: ", simple_test_pad_width, ' '); \ printf("`%s` \e[m== \e[1m", s); \ switch (t) { \ case SIMPLE_TEST_BOOL: \ printf("%s", (w ? simple_test_il : simple_test_ir) \ ? "true" : "false"); \ break; \ case SIMPLE_TEST_CHAR: \ printf("%c", w ? (char)simple_test_il : (char)simple_test_ir); \ break; \ case SIMPLE_TEST_INT: \ printf("%" PRIdMAX, w ? simple_test_il : simple_test_ir); \ break; \ case SIMPLE_TEST_UNSIGNED: \ printf("%" PRIuMAX, w ? simple_test_ul : simple_test_ur); \ break; \ case SIMPLE_TEST_STRING: \ printf(((w ? simple_test_sl : simple_test_sr) \ ? "\"%s\"" : "%s"), w \ ? simple_test_sl : simple_test_sr); \ break; \ case SIMPLE_TEST_POINTER: \ printf("%p", w ? simple_test_pl : simple_test_pr); \ break; \ default: \ break; \ } \ printf("\e[m\n"); \ } while (0) #define SIMPLE_TEST_FAIL2 \ do { \ simple_test_fail_count++; \ goto simple_test_loop_end; \ } while (0) #define SIMPLE_TEST_ERR(...) \ do { \ printf("\e[1m%*c :: at line %d, \e[m\e[1;31merr: \e[m", \ simple_test_pad_width, ' ', __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ printf("\e[1;31mtesting aborted\e[m\n"); \ exit(1); \ } while (0) #define SIMPLE_TEST_PRINT_BUF_WIDTH 512 #define SIMPLE_TEST_ASSERT1(t, c, s, a) \ do { \ switch ( simple_test_assert(t, c, (a)) ) { \ case 1: break; \ case 0: \ SIMPLE_TEST_FAIL1(s " failed"); \ SIMPLE_TEST_PRINT_VAL(0, #a, t); \ SIMPLE_TEST_FAIL2; \ break; \ default: \ SIMPLE_TEST_ERR("unrecognised type in assertion"); \ } \ } while (0) #define SIMPLE_TEST_ASSERT2(t1, t2, c, s, a, b) \ do { \ switch ( simple_test_assert(simple_test_type_resolve(t1, t2), \ c, (a), (b)) ) { \ case 1: break; \ case 0: \ SIMPLE_TEST_FAIL1(s " failed"); \ SIMPLE_TEST_PRINT_VAL(0, #a, simple_test_type_resolve(t1, t2)); \ SIMPLE_TEST_PRINT_VAL(1, #b, simple_test_type_resolve(t1, t2)); \ SIMPLE_TEST_FAIL2; \ break; \ case -2: \ SIMPLE_TEST_ERR("type mismatch in assertion"); \ default: \ SIMPLE_TEST_ERR("unrecognised type in assertion"); \ } \ } while (0) /****************** * BASIC MACROS * ******************/ #define REGISTER_TEARDOWN(f) \ do { \ if (simple_test_teardown != NULL) \ SIMPLE_TEST_ERR("teardown function already defined"); \ simple_test_teardown = _Generic((f), \ void(*)(void): (f), default: NULL); \ if (simple_test_teardown == NULL) { \ SIMPLE_TEST_ERR( \ "wrongly-typed function passed to REGISTER_TEARDOWN"); \ } \ } while (0) #define USE_TEARDOWN \ do { \ if (simple_test_teardown == NULL) \ SIMPLE_TEST_ERR("teardown function undefined"); \ simple_test_do_teardown = true; \ } while (0) /* must appear before all tests */ #define BEGIN_TEST \ int main(int argc, char **argv) \ { \ int simple_test_iterator; \ int simple_test_pad_width = 0; \ int simple_test_fail_count = 0; \ char simple_test_print_buf[SIMPLE_TEST_PRINT_BUF_WIDTH]; \ int simple_test_test_count = 0; \ int simple_test_test_current = 1; \ int simple_test_test_current_at; \ int simple_test_pass_number = 0; \ bool simple_test_do_teardown = false; \ void (*simple_test_teardown)(void) = NULL; \ do { \ simple_test_test_current_at = 0; \ if (simple_test_pass_number == 0) { \ printf("\e[1mstarting tests in " __FILE__ "...\n"); \ } else { \ simple_test_pad_width = sprintf(simple_test_print_buf, \ "%d", simple_test_test_count) + 1; \ } \ for (simple_test_iterator = 0; simple_test_pass_number == 0 && \ simple_test_iterator < 1; simple_test_iterator++) { \ (void)0; \ #define TEST(description) \ } \ if (simple_test_do_teardown) { \ simple_test_do_teardown = false; \ simple_test_teardown(); \ } \ simple_test_test_current_at++; \ if (simple_test_pass_number == 0) { \ simple_test_test_count++; \ } else if (simple_test_pass_number \ == simple_test_test_current_at) { \ printf("\e[m%*d \e[1m:: \e[m\e[33m%s\e[m\n", simple_test_pad_width, \ simple_test_test_current++, description); \ /* must appear after all tests */ #define END_TEST \ } \ if (simple_test_do_teardown) { \ simple_test_do_teardown = false; \ simple_test_teardown(); \ } \ if (simple_test_test_count == 0) { \ SIMPLE_TEST_ERR("no tests defined"); \ } \ simple_test_loop_end: \ (void)0; \ } while (simple_test_pass_number++ < simple_test_test_count); \ if (simple_test_fail_count) { \ printf("\e[1;31m%d of %d tests failed\e[m\n", \ simple_test_fail_count, simple_test_test_count); \ return 1; \ } else { \ printf("\e[1;32mall tests passed!\e[m\n"); \ return 0; \ } \ } #define ECHO(...) \ do { \ printf("\e[1m%*c :: \e[m\e[34m", simple_test_pad_width, ' '); \ printf(__VA_ARGS__); \ printf("...\e[m\n"); \ } while (0) /********************** * ASSERTION MACROS * **********************/ #define ASSERT(a) \ SIMPLE_TEST_ASSERT1(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TRUE, "ASSERT", a) #define ASSERT_NOT(a) \ SIMPLE_TEST_ASSERT1(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_FALSE, \ "ASSERT_NOT", a) #define ASSERT_EQ(a, b) \ SIMPLE_TEST_ASSERT2(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TYPE((b)), \ SIMPLE_TEST_EQ, "ASSERT_EQ", a, b) #define ASSERT_NEQ(a, b) \ SIMPLE_TEST_ASSERT2(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TYPE((b)), \ SIMPLE_TEST_NEQ, "ASSERT_NEQ", a, b) #define ASSERT_GT(a, b) \ SIMPLE_TEST_ASSERT2(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TYPE((b)), \ SIMPLE_TEST_GT, "ASSERT_GT", a, b) #define ASSERT_LT(a, b) \ SIMPLE_TEST_ASSERT2(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TYPE((b)), \ SIMPLE_TEST_LT, "ASSERT_LT", a, b) #define ASSERT_GEQ(a, b) \ SIMPLE_TEST_ASSERT2(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TYPE((b)), \ SIMPLE_TEST_GEQ, "ASSERT_GEQ", a, b) #define ASSERT_LEQ(a, b) \ SIMPLE_TEST_ASSERT2(SIMPLE_TEST_TYPE((a)), SIMPLE_TEST_TYPE((b)), \ SIMPLE_TEST_LEQ, "ASSERT_LEQ", a, b) #endif