summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-24 01:17:50 +0200
committerDirk Engling <erdgeist@erdgeist.org>2026-10-24 01:17:50 +0200
commitf24ec48b27d4cb8dd0555840206dedbb45f1423c (patch)
tree4cf772a476be6b06f52b80abc05f952a33209711 /tests
parentf33203e15a00449152283cab2b878c0886fcede5 (diff)
Add a unit test harness and a first set of tests
make check builds tests/runner from the modules that link without the io subsystem and runs it.
Diffstat (limited to 'tests')
-rw-r--r--tests/runner.c34
-rw-r--r--tests/stubs.c14
-rw-r--r--tests/t_iovec.c124
-rw-r--r--tests/t_scan_urlencoded_query.c274
-rw-r--r--tests/t_vector.c161
-rw-r--r--tests/test.c58
-rw-r--r--tests/test.h83
7 files changed, 748 insertions, 0 deletions
diff --git a/tests/runner.c b/tests/runner.c
new file mode 100644
index 0000000..d73c516
--- /dev/null
+++ b/tests/runner.c
@@ -0,0 +1,34 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8
9/* Opentracker */
10#include "test.h"
11
12struct module {
13 const char *name;
14 void (*run)(void);
15};
16
17static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urlencoded_query},
18 {"ot_iovec", test_iovec},
19 {"ot_vector", test_vector},
20 {NULL, NULL}};
21
22/* Run every module, or only those whose name contains argv[1] */
23int main(int argc, char **argv) {
24 const struct module *m;
25
26 for (m = modules; m->name; ++m) {
27 if (argc > 1 && !strstr(m->name, argv[1]))
28 continue;
29 test_module(m->name);
30 m->run();
31 }
32
33 return test_summary();
34}
diff --git a/tests/stubs.c b/tests/stubs.c
new file mode 100644
index 0000000..ae40306
--- /dev/null
+++ b/tests/stubs.c
@@ -0,0 +1,14 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* Stubs for symbols the modules under test reference but never reach along
7 the paths the tests exercise. If a test ever does reach one, the missing
8 behaviour should be supplied here rather than the test weakened. */
9
10/* Opentracker */
11#include "trackerlogic.h"
12
13/* ot_vector.c calls this from vector_clean_list, which no test drives yet. */
14void free_peerlist(ot_peerlist *peer_list) { (void)peer_list; }
diff --git a/tests/t_iovec.c b/tests/t_iovec.c
new file mode 100644
index 0000000..89f3fda
--- /dev/null
+++ b/tests/t_iovec.c
@@ -0,0 +1,124 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdlib.h>
8#include <string.h>
9#include <sys/uio.h>
10
11/* Opentracker */
12#include "ot_iovec.h"
13#include "test.h"
14
15static void case_increase_and_length(void) {
16 size_t entries = 0;
17 struct iovec *vector = NULL;
18 void *first, *second;
19
20 test_case("increase appends an entry and length is their sum");
21 first = iovec_increase(&entries, &vector, 100);
22 CHECK(first != NULL);
23 CHECK_INT(entries, 1);
24 CHECK_INT(vector[0].iov_len, 100);
25 CHECK_PTR(vector[0].iov_base, first);
26
27 second = iovec_increase(&entries, &vector, 50);
28 CHECK(second != NULL);
29 CHECK_INT(entries, 2);
30 CHECK_INT(vector[1].iov_len, 50);
31 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 150);
32
33 iovec_free(&entries, &vector);
34}
35
36static void case_free_resets(void) {
37 size_t entries = 0;
38 struct iovec *vector = NULL;
39
40 test_case("free releases the entries and resets the vector");
41 CHECK(iovec_increase(&entries, &vector, 10) != NULL);
42 CHECK(iovec_increase(&entries, &vector, 10) != NULL);
43 iovec_free(&entries, &vector);
44 CHECK_INT(entries, 0);
45 CHECK_PTR(vector, NULL);
46 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 0);
47}
48
49static void case_fixlast_shrinks(void) {
50 size_t entries = 0;
51 struct iovec *vector = NULL;
52 char *block;
53
54 test_case("fixlast trims the last entry to the bytes actually used");
55 CHECK(iovec_increase(&entries, &vector, 64) != NULL);
56 block = iovec_increase(&entries, &vector, 1000);
57 CHECK(block != NULL);
58 memset(block, 'x', 20);
59
60 iovec_fixlast(&entries, &vector, block + 20);
61 CHECK_INT(entries, 2);
62 CHECK_INT(vector[1].iov_len, 20);
63 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 84);
64 CHECK_MEM(vector[1].iov_base, "xxxxxxxxxxxxxxxxxxxx", 20);
65
66 iovec_free(&entries, &vector);
67}
68
69static void case_fixlast_on_empty(void) {
70 size_t entries = 0;
71 struct iovec *vector = NULL;
72
73 test_case("fixlast on an empty vector does nothing");
74 iovec_fixlast(&entries, &vector, NULL);
75 CHECK_INT(entries, 0);
76 CHECK_PTR(vector, NULL);
77}
78
79static void case_append_takes_ownership(void) {
80 size_t entries = 0;
81 struct iovec *vector = NULL;
82 struct iovec source;
83
84 test_case("append moves the buffer and clears the source");
85 source.iov_base = malloc(10);
86 CHECK(source.iov_base != NULL);
87 memcpy(source.iov_base, "0123456789", 10);
88 source.iov_len = 10;
89
90 CHECK(iovec_append(&entries, &vector, &source) != NULL);
91 CHECK_INT(entries, 1);
92 CHECK_INT(vector[0].iov_len, 10);
93 CHECK_MEM(vector[0].iov_base, "0123456789", 10);
94 CHECK_PTR(source.iov_base, NULL);
95 CHECK_INT(source.iov_len, 0);
96
97 iovec_free(&entries, &vector);
98}
99
100static void case_fix_increase(void) {
101 size_t entries = 0;
102 struct iovec *vector = NULL;
103 char *block;
104
105 test_case("fix_increase trims the previous entry and adds a new one");
106 block = iovec_increase(&entries, &vector, 500);
107 CHECK(block != NULL);
108 block = iovec_fix_increase_or_free(&entries, &vector, block + 30, 200);
109 CHECK(block != NULL);
110 CHECK_INT(entries, 2);
111 CHECK_INT(vector[0].iov_len, 30);
112 CHECK_INT(vector[1].iov_len, 200);
113
114 iovec_free(&entries, &vector);
115}
116
117void test_iovec(void) {
118 case_increase_and_length();
119 case_free_resets();
120 case_fixlast_shrinks();
121 case_fixlast_on_empty();
122 case_append_takes_ownership();
123 case_fix_increase();
124}
diff --git a/tests/t_scan_urlencoded_query.c b/tests/t_scan_urlencoded_query.c
new file mode 100644
index 0000000..4c1ef23
--- /dev/null
+++ b/tests/t_scan_urlencoded_query.c
@@ -0,0 +1,274 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8
9/* Opentracker */
10#include "scan_urlencoded_query.h"
11#include "test.h"
12
13/* The scanner returns the number of bytes written to the destination, or
14 -1 for a parse error and -2 for a hard terminator encountered before any
15 output was produced. It advances the source pointer past the separator it
16 stopped on, except for hard terminators and the '&' ending a parameter,
17 which it leaves for the next call to see. */
18
19static void case_path(void) {
20 char query[] = "/announce?info_hash=A";
21 char dest[64];
22 char *p = query;
23 ssize_t n;
24
25 test_case("path stops at the question mark");
26 n = scan_urlencoded_query(&p, dest, SCAN_PATH);
27 CHECK_INT(n, 9);
28 dest[n] = 0;
29 CHECK_STR(dest, "/announce");
30 CHECK_INT(*p, 'i');
31}
32
33static void case_param_and_value(void) {
34 char query[] = "info_hash=abc&port=80";
35 char dest[64];
36 char *p = query;
37 ssize_t n;
38
39 test_case("parameter stops at '=', value stops at '&'");
40 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
41 CHECK_INT(n, 9);
42 dest[n] = 0;
43 CHECK_STR(dest, "info_hash");
44
45 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
46 CHECK_INT(n, 3);
47 dest[n] = 0;
48 CHECK_STR(dest, "abc");
49
50 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
51 CHECK_INT(n, 4);
52 dest[n] = 0;
53 CHECK_STR(dest, "port");
54
55 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
56 CHECK_INT(n, 2);
57 dest[n] = 0;
58 CHECK_STR(dest, "80");
59}
60
61static void case_percent_decoding(void) {
62 char query[] = "%41%42%43&x=1";
63 char dest[64];
64 char *p = query;
65 ssize_t n;
66
67 test_case("percent escapes decode");
68 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
69 CHECK_INT(n, 3);
70 CHECK_MEM(dest, "ABC", 3);
71}
72
73static void case_binary_info_hash(void) {
74 /* The twenty byte info_hash is what the announce path really decodes */
75 char query[] = "%00%01%02%ff%fe%7f%80ABCDEFGHIJKLM&x=1";
76 unsigned char want[] = {0x00, 0x01, 0x02, 0xff, 0xfe, 0x7f, 0x80, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M'};
77 char dest[64];
78 char *p = query;
79 ssize_t n;
80
81 test_case("twenty byte binary value survives decoding");
82 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
83 CHECK_INT(n, 20);
84 CHECK_MEM(dest, want, 20);
85}
86
87static void case_in_place(void) {
88 char query[] = "%41%42&x=1";
89 char *p = query;
90 ssize_t n;
91
92 test_case("decoding in place over the source is safe");
93 n = scan_urlencoded_query(&p, query, SCAN_SEARCHPATH_VALUE);
94 CHECK_INT(n, 2);
95 CHECK_MEM(query, "AB", 2);
96}
97
98static void case_bad_escape(void) {
99 char query[] = "%zz";
100 char dest[64];
101 char *p = query;
102 ssize_t n;
103
104 test_case("a non hexadecimal escape is a parse error");
105 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
106 CHECK_INT(n, -1);
107}
108
109static void case_truncated_escape(void) {
110 char query[] = "%4";
111 char dest[64];
112 char *p = query;
113 ssize_t n;
114
115 test_case("an escape cut short by the terminator is a parse error");
116 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
117 CHECK_INT(n, -1);
118}
119
120static void case_terminator(void) {
121 char empty[] = "";
122 char space[] = " rest";
123 char dest[64];
124 char *p = empty;
125 ssize_t n;
126
127 test_case("a hard terminator before any output reports end of input");
128 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
129 CHECK_INT(n, -2);
130
131 p = space;
132 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM);
133 CHECK_INT(n, -2);
134}
135
136static void case_terminator_after_output(void) {
137 char query[] = "abc";
138 char dest[64];
139 char *p = query;
140 ssize_t n;
141
142 test_case("a hard terminator after output is left for the next call");
143 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
144 CHECK_INT(n, 3);
145 CHECK_INT(*p, 0);
146 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
147 CHECK_INT(n, -2);
148}
149
150/* The character matrix makes '?' terminate a path only, and '=' terminate a
151 path or a parameter only. Both are ordinary characters inside a value, so a
152 value is free to contain a nested query string. */
153
154static void case_separators_are_contextual(void) {
155 char question[] = "a?b&x=1";
156 char equals[] = "a=b&x=1";
157 char dest[64];
158 char *p = question;
159 ssize_t n;
160
161 test_case("a question mark is literal inside a value");
162 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
163 CHECK_INT(n, 3);
164 dest[n] = 0;
165 CHECK_STR(dest, "a?b");
166
167 test_case("an equals sign is literal inside a value");
168 p = equals;
169 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
170 CHECK_INT(n, 3);
171 dest[n] = 0;
172 CHECK_STR(dest, "a=b");
173}
174
175static void case_ampersand_in_path(void) {
176 char query[] = "/path&x";
177 char dest[64];
178 char *p = query;
179 ssize_t n;
180
181 test_case("an ampersand in a path is a parse error");
182 n = scan_urlencoded_query(&p, dest, SCAN_PATH);
183 CHECK_INT(n, -1);
184}
185
186static void case_raw_high_byte(void) {
187 char query[] = {'a', 'b', (char)0x80, 0};
188 char dest[64];
189 char *p = query;
190 ssize_t n;
191
192 test_case("an undecoded byte above ASCII is a parse error");
193 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE);
194 CHECK_INT(n, -1);
195}
196
197static void case_skipvalue(void) {
198 char query[] = "abc&next=1";
199 char bare[] = "abc";
200 char *p = query;
201
202 test_case("skipvalue steps over the value and its separator");
203 scan_urlencoded_skipvalue(&p);
204 CHECK_STR(p, "next=1");
205
206 test_case("skipvalue leaves a hard terminator in place");
207 p = bare;
208 scan_urlencoded_skipvalue(&p);
209 CHECK_INT(*p, 0);
210}
211
212static void case_find_keywords(void) {
213 static const ot_keywords keywords[] = {{"port", 1}, {"left", 2}, {"compact", 3}, {NULL, -3}};
214 char match[] = "left=0";
215 char nomatch[] = "bogus=1";
216 char prefix[] = "por=1";
217 char *p = match;
218
219 test_case("a known keyword returns its value");
220 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), 2);
221
222 test_case("an unknown keyword returns -3");
223 p = nomatch;
224 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
225
226 test_case("a prefix of a keyword does not match it");
227 p = prefix;
228 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
229}
230
231static void case_fixed_int(void) {
232 char positive[] = "123";
233 char negative[] = "-45";
234 char trailing[] = "12a";
235 int value;
236
237 test_case("scan_fixed_int returns the count of unparsed bytes");
238 CHECK_INT(scan_fixed_int(positive, 3, &value), 0);
239 CHECK_INT(value, 123);
240
241 CHECK_INT(scan_fixed_int(negative, 3, &value), 0);
242 CHECK_INT(value, -45);
243
244 CHECK_INT(scan_fixed_int(trailing, 3, &value), 1);
245 CHECK_INT(value, 12);
246}
247
248static void case_fixed_int_bounded(void) {
249 char digits[] = "12345";
250 int value;
251
252 test_case("scan_fixed_int honours the length it is given");
253 CHECK_INT(scan_fixed_int(digits, 3, &value), 0);
254 CHECK_INT(value, 123);
255}
256
257void test_scan_urlencoded_query(void) {
258 case_path();
259 case_param_and_value();
260 case_percent_decoding();
261 case_binary_info_hash();
262 case_in_place();
263 case_bad_escape();
264 case_truncated_escape();
265 case_terminator();
266 case_terminator_after_output();
267 case_separators_are_contextual();
268 case_ampersand_in_path();
269 case_raw_high_byte();
270 case_skipvalue();
271 case_find_keywords();
272 case_fixed_int();
273 case_fixed_int_bounded();
274}
diff --git a/tests/t_vector.c b/tests/t_vector.c
new file mode 100644
index 0000000..fee336b
--- /dev/null
+++ b/tests/t_vector.c
@@ -0,0 +1,161 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdlib.h>
8#include <string.h>
9
10/* Opentracker */
11#include "trackerlogic.h"
12#include "test.h"
13
14/* binary_search returns the matching member when there is one, and otherwise
15 the position at which the key would have to be inserted to keep the vector
16 sorted. exactmatch distinguishes the two. */
17
18static const unsigned char sorted[5][4] = {{0, 0, 0, 1}, {0, 0, 0, 3}, {0, 0, 0, 5}, {0, 0, 0, 7}, {0, 0, 0, 9}};
19
20static void case_search_finds_member(void) {
21 const unsigned char key[4] = {0, 0, 0, 5};
22 int exactmatch;
23 void *found;
24
25 test_case("an existing key is found");
26 found = binary_search(key, sorted, 5, 4, 4, &exactmatch);
27 CHECK(exactmatch != 0);
28 CHECK_PTR(found, sorted[2]);
29}
30
31static void case_search_finds_first_and_last(void) {
32 const unsigned char first[4] = {0, 0, 0, 1};
33 const unsigned char last[4] = {0, 0, 0, 9};
34 int exactmatch;
35
36 test_case("the first and last members are found");
37 CHECK_PTR(binary_search(first, sorted, 5, 4, 4, &exactmatch), sorted[0]);
38 CHECK(exactmatch != 0);
39 CHECK_PTR(binary_search(last, sorted, 5, 4, 4, &exactmatch), sorted[4]);
40 CHECK(exactmatch != 0);
41}
42
43static void case_search_insert_position(void) {
44 const unsigned char middle[4] = {0, 0, 0, 4};
45 const unsigned char before[4] = {0, 0, 0, 0};
46 int exactmatch;
47 void *slot;
48
49 test_case("a missing key yields its insert position");
50 slot = binary_search(middle, sorted, 5, 4, 4, &exactmatch);
51 CHECK_INT(exactmatch, 0);
52 CHECK_PTR(slot, sorted[2]);
53
54 slot = binary_search(before, sorted, 5, 4, 4, &exactmatch);
55 CHECK_INT(exactmatch, 0);
56 CHECK_PTR(slot, sorted[0]);
57}
58
59static void case_search_past_the_end(void) {
60 const unsigned char beyond[4] = {0, 0, 0, 0xff};
61 int exactmatch;
62 void *slot;
63
64 test_case("a key above every member yields the position past the end");
65 slot = binary_search(beyond, sorted, 5, 4, 4, &exactmatch);
66 CHECK_INT(exactmatch, 0);
67 CHECK_PTR(slot, sorted[5]);
68}
69
70static void case_search_empty(void) {
71 int exactmatch;
72 void *base = (void *)sorted;
73
74 test_case("searching an empty vector reports no match at the base");
75 CHECK_PTR(binary_search(sorted[0], base, 0, 4, 4, &exactmatch), base);
76 CHECK_INT(exactmatch, 0);
77}
78
79static void case_insert_keeps_order(void) {
80 ot_vector vector = {NULL, 0, 0};
81 const unsigned char five[4] = {0, 0, 0, 5};
82 const unsigned char one[4] = {0, 0, 0, 1};
83 const unsigned char nine[4] = {0, 0, 0, 9};
84 int exactmatch;
85 void *slot;
86
87 test_case("inserts land in sorted order and grow the vector");
88 slot = vector_find_or_insert(&vector, (void *)five, 4, 4, &exactmatch);
89 CHECK(slot != NULL);
90 CHECK_INT(exactmatch, 0);
91 memcpy(slot, five, 4);
92 CHECK_INT(vector.size, 1);
93 CHECK_INT(vector.space, OT_VECTOR_MIN_MEMBERS);
94
95 slot = vector_find_or_insert(&vector, (void *)one, 4, 4, &exactmatch);
96 CHECK(slot != NULL);
97 CHECK_INT(exactmatch, 0);
98 memcpy(slot, one, 4);
99 CHECK_INT(vector.size, 2);
100
101 slot = vector_find_or_insert(&vector, (void *)nine, 4, 4, &exactmatch);
102 CHECK(slot != NULL);
103 CHECK_INT(exactmatch, 0);
104 memcpy(slot, nine, 4);
105 CHECK_INT(vector.size, 3);
106 CHECK_INT(vector.space, OT_VECTOR_MIN_MEMBERS * OT_VECTOR_GROW_RATIO);
107
108 CHECK_MEM(vector.data, "\0\0\0\1\0\0\0\5\0\0\0\11", 12);
109
110 free(vector.data);
111}
112
113static void case_insert_finds_existing(void) {
114 ot_vector vector = {NULL, 0, 0};
115 const unsigned char five[4] = {0, 0, 0, 5};
116 int exactmatch;
117 void *slot, *again;
118
119 test_case("a second insert of the same key returns the existing member");
120 slot = vector_find_or_insert(&vector, (void *)five, 4, 4, &exactmatch);
121 CHECK(slot != NULL);
122 memcpy(slot, five, 4);
123
124 again = vector_find_or_insert(&vector, (void *)five, 4, 4, &exactmatch);
125 CHECK(exactmatch != 0);
126 CHECK_PTR(again, vector.data);
127 CHECK_INT(vector.size, 1);
128
129 free(vector.data);
130}
131
132static void case_insert_compare_prefix(void) {
133 ot_vector vector = {NULL, 0, 0};
134 unsigned char first[6] = {1, 2, 3, 0xaa, 0xaa, 0xaa};
135 unsigned char second[6] = {1, 2, 3, 0xbb, 0xbb, 0xbb};
136 int exactmatch;
137 void *slot;
138
139 test_case("only the compare prefix decides identity");
140 slot = vector_find_or_insert(&vector, first, 6, 3, &exactmatch);
141 CHECK(slot != NULL);
142 memcpy(slot, first, 6);
143
144 slot = vector_find_or_insert(&vector, second, 6, 3, &exactmatch);
145 CHECK(exactmatch != 0);
146 CHECK_INT(vector.size, 1);
147 CHECK_MEM(slot, first, 6);
148
149 free(vector.data);
150}
151
152void test_vector(void) {
153 case_search_finds_member();
154 case_search_finds_first_and_last();
155 case_search_insert_position();
156 case_search_past_the_end();
157 case_search_empty();
158 case_insert_keeps_order();
159 case_insert_finds_existing();
160 case_insert_compare_prefix();
161}
diff --git a/tests/test.c b/tests/test.c
new file mode 100644
index 0000000..4686369
--- /dev/null
+++ b/tests/test.c
@@ -0,0 +1,58 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdarg.h>
8#include <stdio.h>
9
10/* Opentracker */
11#include "test.h"
12
13unsigned long test_checks;
14unsigned long test_failures;
15
16static const char *current_module = "";
17static const char *current_case = "";
18
19void test_module(const char *name) {
20 current_module = name;
21 current_case = "";
22 printf("%s\n", name);
23}
24
25void test_case(const char *name) { current_case = name; }
26
27static void test_where(const char *file, int line) {
28 ++test_failures;
29 printf(" FAIL %s/%s (%s:%d)\n ", current_module, current_case, file, line);
30}
31
32void test_failf(const char *file, int line, const char *fmt, ...) {
33 va_list ap;
34 test_where(file, line);
35 va_start(ap, fmt);
36 vprintf(fmt, ap);
37 va_end(ap);
38 printf("\n");
39}
40
41void test_failmem(const char *file, int line, const char *what, const void *got, const void *want, size_t len) {
42 const unsigned char *g = got, *w = want;
43 size_t i;
44
45 test_where(file, line);
46 printf("%s: %zu bytes differ\n got ", what, len);
47 for (i = 0; i < len; ++i)
48 printf("%02x", g[i]);
49 printf("\n want ");
50 for (i = 0; i < len; ++i)
51 printf("%02x", w[i]);
52 printf("\n");
53}
54
55int test_summary(void) {
56 printf("\n%lu checks, %lu failures\n", test_checks, test_failures);
57 return test_failures != 0;
58}
diff --git a/tests/test.h b/tests/test.h
new file mode 100644
index 0000000..941e058
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,83 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_TEST_H__
7#define OT_TEST_H__
8
9#include <stddef.h>
10#include <string.h>
11
12/* Minimal scaffolding for opentracker's unit tests. No external dependencies.
13
14 Tests are grouped into modules, one per source file under test. A module is
15 a list of small case functions, each pinning one behaviour. A failing CHECK
16 reports file, line and the offending values, then returns from the case it
17 is in, so a single run reports as many independent problems as it can.
18 Because the macros return, they may only be used in functions returning
19 void. */
20
21void test_module(const char *name);
22void test_case(const char *name);
23void test_failf(const char *file, int line, const char *fmt, ...);
24void test_failmem(const char *file, int line, const char *what, const void *got, const void *want, size_t len);
25int test_summary(void);
26
27extern unsigned long test_checks;
28extern unsigned long test_failures;
29
30#define CHECK(cond) \
31 do { \
32 ++test_checks; \
33 if (!(cond)) { \
34 test_failf(__FILE__, __LINE__, "expected %s", #cond); \
35 return; \
36 } \
37 } while (0)
38
39#define CHECK_INT(got, want) \
40 do { \
41 long long got_ = (long long)(got), want_ = (long long)(want); \
42 ++test_checks; \
43 if (got_ != want_) { \
44 test_failf(__FILE__, __LINE__, "%s: got %lld, want %lld", #got, got_, want_); \
45 return; \
46 } \
47 } while (0)
48
49#define CHECK_PTR(got, want) \
50 do { \
51 const void *got_ = (const void *)(got), *want_ = (const void *)(want); \
52 ++test_checks; \
53 if (got_ != want_) { \
54 test_failf(__FILE__, __LINE__, "%s: got %p, want %p", #got, got_, want_); \
55 return; \
56 } \
57 } while (0)
58
59#define CHECK_MEM(got, want, len) \
60 do { \
61 ++test_checks; \
62 if (memcmp((got), (want), (len))) { \
63 test_failmem(__FILE__, __LINE__, #got, (got), (want), (len)); \
64 return; \
65 } \
66 } while (0)
67
68#define CHECK_STR(got, want) \
69 do { \
70 const char *got_ = (got), *want_ = (want); \
71 ++test_checks; \
72 if (strcmp(got_, want_)) { \
73 test_failf(__FILE__, __LINE__, "%s: got \"%s\", want \"%s\"", #got, got_, want_); \
74 return; \
75 } \
76 } while (0)
77
78/* One entry point per module under test */
79void test_scan_urlencoded_query(void);
80void test_iovec(void);
81void test_vector(void);
82
83#endif