aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--include/moslib/tester.h84
-rw-r--r--src/tester.c191
-rw-r--r--tests/main.c3
-rw-r--r--tests/mem/alloc_tests.c37
-rw-r--r--tests/mem/alloc_tests.h3
-rw-r--r--tests/tests.c13
7 files changed, 331 insertions, 7 deletions
diff --git a/Makefile b/Makefile
index def9b73..194aa3b 100644
--- a/Makefile
+++ b/Makefile
@@ -26,9 +26,10 @@ structures:
$(CC) $(CFLAGS) -c src/ds/array.c
test:
- $(CC) $(CFLAGS_TEST) tests/main.c -o run_tests
- ./run_tests
- rm run_tests
+ $(CC) $(CFLAGS_TEST) -c tests/mem/alloc_tests.c
+ $(CC) $(CFLAGS_TEST) *.o tests/tests.c -o run_tests
+ ./run_tests 2>/dev/null
+ rm run_tests *.o
.PHONY: clean
clean:
diff --git a/include/moslib/tester.h b/include/moslib/tester.h
index 0165257..9c3ea04 100644
--- a/include/moslib/tester.h
+++ b/include/moslib/tester.h
@@ -25,7 +25,6 @@
// Create tests.
// Tests are of type TestFn (Test (*)()).
// They should use test_assert functions to check correctness.
-//
// Example:
// Test test_fn() {
// int result1 = 1 + 2;
@@ -35,6 +34,18 @@
// test_assert(result2 == 8, "3 + 5 should equal 8");
// }
//
+// You can also test if a function is supposed to call exit.
+// You need to create a helper test and pass it into test_assert_exit in main test.
+// Pass the helper test and expected exit code to it.
+// Example:
+// Test helper_test() {
+// int param1, param2;
+// int result = exit_failure_function(&param1, &param2);
+// }
+// Test main_test() {
+// test_assert_exit(helper_test, EXIT_FAILURE);
+// }
+//
// ----------------------------------------------------------------------------------------------------
//
// Add tests to test group:
@@ -64,4 +75,75 @@ typedef struct Tester Tester;
typedef struct TestGroup TestGroup;
typedef Test (*TestFn)();
+// Initalize new tester
+// You can skip this and just define it as NULL - it will initalize automatically
+//
+// Returns:
+// pointer to the new tester
+extern Tester *new_tester();
+
+// Add new test group to the tester
+//
+// Arguments:
+// tester
+// tester to add the group to
+//
+// group_name
+// name of the group to add
+//
+// Returns:
+// pointer to the added group
+#define add_test_group(tester, group_name) ((!(tester) ? (tester) = new_tester() : 0), add_test_group_fn((tester), (group_name)))
+
+// Assert that an expression is true
+// Use in test functions to test correctness
+//
+// Arguments:
+// expression
+// expression to check - if it's true, test passes
+// if it's false, test fails
+//
+// fail_message
+// message to display when test fails
+extern void test_assert(int expression, const char *fail_message);
+
+// Assert that function exits with given exit code
+//
+// Arguments:
+// function
+// function that is supposed to exit
+//
+// code
+// expected exit code
+#define test_assert_exit(function, code) test_assert_exit_fn((function), (code), #function);
+
+// Add test to a test group
+//
+// Arguments:
+// group
+// test group to add the test to
+//
+// test
+// test to add to the group
+#define add_test(group, test) add_test_fn((group), (test), #test);
+
+// Run defined tests
+//
+// Arguments:
+// tester
+// tester with tests to run
+extern void run_tests(Tester *tester);
+
+// Free the tester
+//
+// Arguments:
+// tester
+// tester to free
+extern void free_tester(Tester *tester);
+
+// Function prototypes for macros
+extern TestGroup *add_test_group_fn(Tester *tester, const char *group_name);
+extern void test_assert_exit_fn(TestFn function, int code, const char *name);
+extern void add_test_fn(TestGroup *group, TestFn test, const char *name);
+
#endif // MOSLIB_TESTER_H
diff --git a/src/tester.c b/src/tester.c
index e69de29..361cfb9 100644
--- a/src/tester.c
+++ b/src/tester.c
@@ -0,0 +1,191 @@
+#include "moslib/tester.h"
+
+#include <setjmp.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "moslib/mem/alloc.h"
+
+#define INIT_GROUP_CAP 8
+#define INIT_TEST_CAP 16
+
+#define RED 91
+#define GREEN 92
+#define WHITE 97
+
+#define RENDER_WIDTH 80
+
+int failed = 0;
+
+#define print_color(template, ...) printf("\033[%dm" template "\033[0m", __VA_ARGS__)
+#define nl printf("\n")
+
+void print_center(const char *text, int color, char padding_char) {
+ size_t post_char_count = (RENDER_WIDTH - strlen(text)) / 2;
+ size_t pre_char_count = RENDER_WIDTH - post_char_count - strlen(text);
+
+ for (size_t i = 1; i < pre_char_count; i++)
+ printf("%c", padding_char);
+
+ print_color(" %s ", color, text);
+
+ for (size_t i = 1; i < post_char_count; i++)
+ printf("%c", padding_char);
+
+ nl;
+}
+
+typedef struct {
+ TestFn test;
+ const char *name;
+} TestName;
+
+struct Tester {
+ TestGroup *groups;
+ size_t n_groups;
+ size_t group_cap;
+};
+
+struct TestGroup {
+ const char *name;
+ TestName *tests;
+ size_t n_tests;
+ size_t test_cap;
+};
+
+Tester *new_tester() {
+ Tester *tester = safe_malloc(sizeof(Tester));
+ *tester = (Tester){
+ .groups = safe_malloc(INIT_GROUP_CAP * sizeof(TestGroup)),
+ .n_groups = 0,
+ .group_cap = INIT_GROUP_CAP,
+ };
+ return tester;
+}
+
+TestGroup *add_test_group_fn(Tester *tester, const char *group_name) {
+ if (tester->n_groups >= tester->group_cap) {
+ tester->group_cap *= 2;
+ tester->groups = safe_realloc(tester->groups, tester->group_cap * sizeof(TestGroup));
+ }
+
+ TestGroup *group = tester->groups + tester->n_groups++;
+ // clang-format off
+ *group = (TestGroup){
+ .name = group_name,
+ .tests = safe_malloc(INIT_TEST_CAP * sizeof(TestName)),
+ .n_tests = 0,
+ .test_cap = INIT_TEST_CAP
+ };
+ // clang-format on
+ return group;
+}
+
+void test_assert(int expression, const char *fail_message) {
+ if (!expression) {
+ failed = 1;
+ print_color("ASSERT FAILED: ", RED);
+ printf("%s\n", fail_message);
+ }
+}
+
+jmp_buf jump_env;
+int expected_code;
+const char *fn_name;
+void exit(int code) {
+ if (code != expected_code) {
+ failed = 1;
+ print_color("ASSERT FAILED: ", RED);
+ printf("exit code was %d, expected %d (in function %s)\n", code, expected_code, fn_name);
+ }
+ longjmp(jump_env, 1);
+}
+
+void test_assert_exit_fn(TestFn function, int code, const char *name) {
+ expected_code = code;
+ fn_name = name;
+ if (setjmp(jump_env) == 0) {
+ function();
+ failed = 1;
+ print_color("ASSERT FAILED: ", RED);
+ printf("exit was not called (in function %s)\n", name);
+ }
+}
+
+void add_test_fn(TestGroup *group, TestFn test, const char *name) {
+ if (group->n_tests >= group->test_cap) {
+ group->test_cap *= 2;
+ group->tests = safe_realloc(group->tests, group->test_cap * sizeof(TestName));
+ }
+
+ group->tests[group->n_tests++] = (TestName){.test = test, .name = name};
+}
+
+void run_tests(Tester *tester) {
+ if (!tester)
+ return;
+
+ size_t total_passed = 0;
+ size_t total_failed = 0;
+ char buf[80];
+
+ print_center("START", WHITE, '=');
+
+ for (size_t i = 0; i < tester->n_groups; i++) {
+ nl;
+ TestGroup *group = tester->groups + i;
+ size_t tests_passed = 0;
+ size_t tests_failed = 0;
+
+ print_center(group->name, WHITE, '-');
+
+ for (size_t j = 0; j < group->n_tests; j++) {
+ failed = 0;
+ printf("%s:\n", group->tests[j].name);
+ group->tests[j].test();
+ if (failed) {
+ tests_failed += 1;
+ print_color("--- FAILED ---\n", RED);
+ }
+ else {
+ tests_passed += 1;
+ print_color("--- PASSED ---\n", GREEN);
+ }
+ }
+
+ print_center("GROUP SUMMARY", WHITE, '-');
+
+ sprintf(buf, "%zu PASSED", tests_passed);
+ print_center(buf, GREEN, ' ');
+ sprintf(buf, "%zu FAILED", tests_failed);
+ print_center(buf, RED, ' ');
+ if (i < tester->n_groups - 1) {
+ nl;
+ print_center("TESTING", WHITE, '=');
+ }
+
+ total_passed += tests_passed;
+ total_failed += tests_failed;
+ }
+
+ nl;
+ print_center("TESTS SUMMARY", WHITE, '=');
+ sprintf(buf, "%zu PASSED", total_passed);
+ print_center(buf, GREEN, ' ');
+ sprintf(buf, "%zu FAILED", total_failed);
+ print_center(buf, RED, ' ');
+ print_center("END", WHITE, '=');
+}
+
+void free_tester(Tester *tester) {
+ if (!tester)
+ return;
+
+ for (size_t i = 0; i < tester->n_groups; i++) {
+ free(tester->groups[i].tests);
+ }
+ free(tester->groups);
+ free(tester);
+}
diff --git a/tests/main.c b/tests/main.c
deleted file mode 100644
index 9b6bdc2..0000000
--- a/tests/main.c
+++ /dev/null
@@ -1,3 +0,0 @@
-int main(void) {
- return 0;
-}
diff --git a/tests/mem/alloc_tests.c b/tests/mem/alloc_tests.c
new file mode 100644
index 0000000..6eb2e9c
--- /dev/null
+++ b/tests/mem/alloc_tests.c
@@ -0,0 +1,37 @@
+#include "alloc_tests.h"
+
+#include <limits.h>
+#include <moslib/mem/alloc.h>
+#include <stdlib.h>
+
+Test malloc_exit() {
+ safe_malloc(ULLONG_MAX);
+}
+
+Test test_safe_malloc() {
+ void *p = safe_malloc(1024);
+ test_assert(p != NULL, "Should be able to malloc 1KiB");
+ test_assert_exit(malloc_exit, EXIT_FAILURE);
+}
+
+Test realloc_exit() {
+ void *p = NULL;
+ safe_realloc(p, ULLONG_MAX);
+}
+
+Test test_safe_realloc() {
+ void *p = NULL;
+ p = safe_realloc(p, 1024);
+ test_assert(p != NULL, "Should be able to realloc NULL to 1KiB");
+
+ p = safe_realloc(p, 4096);
+ test_assert(p != NULL, "Should be able to realloc 1KiB to 4KiB");
+
+ test_assert_exit(realloc_exit, EXIT_FAILURE);
+}
+
+void mem_alloc_tests(Tester *tester) {
+ TestGroup *group = add_test_group(tester, "moslib/mem/alloc.h");
+ add_test(group, test_safe_malloc);
+ add_test(group, test_safe_realloc);
+}
diff --git a/tests/mem/alloc_tests.h b/tests/mem/alloc_tests.h
new file mode 100644
index 0000000..aaf70a4
--- /dev/null
+++ b/tests/mem/alloc_tests.h
@@ -0,0 +1,3 @@
+#include <moslib/tester.h>
+
+extern void mem_alloc_tests(Tester *tester);
diff --git a/tests/tests.c b/tests/tests.c
new file mode 100644
index 0000000..63e84ca
--- /dev/null
+++ b/tests/tests.c
@@ -0,0 +1,13 @@
+#include <moslib/tester.h>
+
+#include "mem/alloc_tests.h"
+
+int main(void) {
+ Tester *tester = new_tester();
+
+ mem_alloc_tests(tester);
+
+ run_tests(tester);
+ free_tester(tester);
+ return 0;
+}