aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-09-16 08:25:54 -0700
committer3gg <3gg@shellblade.net>2024-09-16 08:25:54 -0700
commit1321c90186706309ed0fda78858a2a15b7a521a9 (patch)
tree00fe0d3eb2eaf94b241ee8cc1d91ffe273654069 /test
parentf59ede03d8f1d9934823481bcea10c9cfd238c91 (diff)
Make test its own library.
Diffstat (limited to 'test')
-rw-r--r--test/CMakeLists.txt13
-rw-r--r--test/test.h248
2 files changed, 261 insertions, 0 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..102568c
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,13 @@
1cmake_minimum_required(VERSION 3.0)
2
3project(test)
4
5set(CMAKE_CXX_STANDARD 20)
6set(CMAKE_CXX_STANDARD_REQUIRED On)
7set(CMAKE_CXX_EXTENSIONS Off)
8
9add_library(test INTERFACE
10 test.h)
11
12target_include_directories(test INTERFACE
13 .)
diff --git a/test/test.h b/test/test.h
new file mode 100644
index 0000000..cdd2f05
--- /dev/null
+++ b/test/test.h
@@ -0,0 +1,248 @@
1// SPDX-License-Identifier: MIT
2#pragma once
3
4#ifdef UNIT_TEST
5
6#include <stdbool.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
12 defined(__NetBSD__) || defined(__OpenBSD__)
13#define USE_SYSCTL_FOR_ARGS 1
14// clang-format off
15#include <sys/types.h>
16#include <sys/sysctl.h>
17// clang-format on
18#include <unistd.h> // getpid
19#endif
20
21struct test_file_metadata;
22
23struct test_failure {
24 bool present;
25 const char *message;
26 const char *file;
27 int line;
28 bool owned;
29};
30
31struct test_case_metadata {
32 void (*fn)(struct test_case_metadata *, struct test_file_metadata *);
33 struct test_failure failure;
34 const char *name;
35 struct test_case_metadata *next;
36};
37
38struct test_file_metadata {
39 bool registered;
40 const char *name;
41 struct test_file_metadata *next;
42 struct test_case_metadata *tests;
43};
44
45struct test_file_metadata __attribute__((weak)) * test_file_head;
46
47#define SET_FAILURE(_message, _owned) \
48 metadata->failure = (struct test_failure) { \
49 .present = true, \
50 .message = _message, \
51 .file = __FILE__, \
52 .line = __LINE__, \
53 .owned = _owned, \
54 }
55
56#define TEST_EQUAL(a, b) \
57 do { \
58 if ((a) != (b)) { \
59 SET_FAILURE(#a " != " #b, false); \
60 return; \
61 } \
62 } while (0)
63
64#define TEST_NOTEQUAL(a, b) \
65 do { \
66 if ((a) == (b)) { \
67 SET_FAILURE(#a " == " #b, false); \
68 return; \
69 } \
70 } while (0)
71
72#define TEST_TRUE(a) \
73 do { \
74 if (!(a)) { \
75 SET_FAILURE(#a " is not true", false); \
76 return; \
77 } \
78 } while (0)
79
80#define TEST_STREQUAL(a, b) \
81 do { \
82 if (strcmp(a, b) != 0) { \
83 const char *test_strequal__part2 = " != " #b; \
84 size_t test_strequal__len = \
85 strlen(a) + strlen(test_strequal__part2) + 3; \
86 char *test_strequal__buf = malloc(test_strequal__len); \
87 snprintf(test_strequal__buf, test_strequal__len, "\"%s\"%s", a, \
88 test_strequal__part2); \
89 SET_FAILURE(test_strequal__buf, true); \
90 return; \
91 } \
92 } while (0)
93
94#define TEST_STRNEQUAL(a, b, len) \
95 do { \
96 if (strncmp(a, b, len) != 0) { \
97 const char *test_strnequal__part2 = " != " #b; \
98 size_t test_strnequal__len2 = \
99 len + strlen(test_strnequal__part2) + 3; \
100 char *test_strnequal__buf = malloc(test_strnequal__len2); \
101 snprintf(test_strnequal__buf, test_strnequal__len2, \
102 "\"%.*s\"%s", (int)len, a, test_strnequal__part2); \
103 SET_FAILURE(test_strnequal__buf, true); \
104 return; \
105 } \
106 } while (0)
107
108#define TEST_STREQUAL3(str, expected, len) \
109 do { \
110 if (len != strlen(expected) || strncmp(str, expected, len) != 0) { \
111 const char *test_strequal3__part2 = " != " #expected; \
112 size_t test_strequal3__len2 = \
113 len + strlen(test_strequal3__part2) + 3; \
114 char *test_strequal3__buf = malloc(test_strequal3__len2); \
115 snprintf(test_strequal3__buf, test_strequal3__len2, \
116 "\"%.*s\"%s", (int)len, str, test_strequal3__part2); \
117 SET_FAILURE(test_strequal3__buf, true); \
118 return; \
119 } \
120 } while (0)
121
122#define TEST_CASE(_name) \
123 static void __test_h_##_name(struct test_case_metadata *, \
124 struct test_file_metadata *); \
125 static struct test_file_metadata __test_h_file##_name; \
126 static struct test_case_metadata __test_h_meta_##_name = { \
127 .fn = __test_h_##_name, \
128 .failure = {}, \
129 .name = #_name, \
130 .next = 0, \
131 }; \
132 static void __attribute__((constructor(101))) __test_h_##_name##_register(void) { \
133 __test_h_meta_##_name.next = __test_h_file##_name.tests; \
134 __test_h_file##_name.tests = &__test_h_meta_##_name; \
135 if (!__test_h_file##_name.registered) { \
136 __test_h_file##_name.name = __FILE__; \
137 __test_h_file##_name.next = test_file_head; \
138 test_file_head = &__test_h_file##_name; \
139 __test_h_file##_name.registered = true; \
140 } \
141 } \
142 static void __test_h_##_name( \
143 struct test_case_metadata *metadata __attribute__((unused)), \
144 struct test_file_metadata *file_metadata __attribute__((unused)))
145
146extern void __attribute__((weak)) (*test_h_unittest_setup)(void);
147/// Run defined tests, return true if all tests succeeds
148/// @param[out] tests_run if not NULL, set to whether tests were run
149static inline void __attribute__((constructor(102))) run_tests(void) {
150 bool should_run = false;
151#ifdef USE_SYSCTL_FOR_ARGS
152 int mib[] = {
153 CTL_KERN,
154#if defined(__NetBSD__) || defined(__OpenBSD__)
155 KERN_PROC_ARGS,
156 getpid(),
157 KERN_PROC_ARGV,
158#else
159 KERN_PROC,
160 KERN_PROC_ARGS,
161 getpid(),
162#endif
163 };
164 char *arg = NULL;
165 size_t arglen;
166 sysctl(mib, sizeof(mib) / sizeof(mib[0]), NULL, &arglen, NULL, 0);
167 arg = malloc(arglen);
168 sysctl(mib, sizeof(mib) / sizeof(mib[0]), arg, &arglen, NULL, 0);
169#else
170 FILE *cmdlinef = fopen("/proc/self/cmdline", "r");
171 char *arg = NULL;
172 int arglen;
173 fscanf(cmdlinef, "%ms%n", &arg, &arglen);
174 fclose(cmdlinef);
175#endif
176 for (char *pos = arg; pos < arg + arglen; pos += strlen(pos) + 1) {
177 if (strcmp(pos, "--unittest") == 0) {
178 should_run = true;
179 break;
180 }
181 }
182 free(arg);
183
184 if (!should_run) {
185 return;
186 }
187
188 if (&test_h_unittest_setup) {
189 test_h_unittest_setup();
190 }
191
192 struct test_file_metadata *i = test_file_head;
193 int failed = 0, success = 0;
194 while (i) {
195 fprintf(stderr, "Running tests from %s:\n", i->name);
196 struct test_case_metadata *j = i->tests;
197 while (j) {
198 fprintf(stderr, "\t%s ... ", j->name);
199 j->failure.present = false;
200 j->fn(j, i);
201 if (j->failure.present) {
202 fprintf(stderr, "failed (%s at %s:%d)\n", j->failure.message,
203 j->failure.file, j->failure.line);
204 if (j->failure.owned) {
205 free((char *)j->failure.message);
206 j->failure.message = NULL;
207 }
208 failed++;
209 } else {
210 fprintf(stderr, "passed\n");
211 success++;
212 }
213 j = j->next;
214 }
215 fprintf(stderr, "\n");
216 i = i->next;
217 }
218 int total = failed + success;
219 fprintf(stderr, "Test results: passed %d/%d, failed %d/%d\n", success, total,
220 failed, total);
221 exit(failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
222}
223
224#else
225
226#include <stdbool.h>
227
228#define TEST_CASE(name) static void __attribute__((unused)) __test_h_##name(void)
229
230#define TEST_EQUAL(a, b) \
231 (void)(a); \
232 (void)(b)
233#define TEST_NOTEQUAL(a, b) \
234 (void)(a); \
235 (void)(b)
236#define TEST_TRUE(a) (void)(a)
237#define TEST_STREQUAL(a, b) \
238 (void)(a); \
239 (void)(b)
240#define TEST_STRNEQUAL(a, b, len) \
241 (void)(a); \
242 (void)(b); \
243 (void)(len)
244#define TEST_STREQUAL3(str, expected, len) \
245 (void)(str); \
246 (void)(expected); \
247 (void)(len)
248#endif