summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/runner.c39
-rw-r--r--tests/stubs.c20
-rw-r--r--tests/t_accesslist.c122
-rw-r--r--tests/t_buf.c205
-rw-r--r--tests/t_hex.c128
-rw-r--r--tests/t_iovec.c124
-rw-r--r--tests/t_random.c114
-rw-r--r--tests/t_scan.c162
-rw-r--r--tests/t_scan_urlencoded_query.c381
-rw-r--r--tests/t_vector.c161
-rw-r--r--tests/test.c58
-rw-r--r--tests/test.h88
12 files changed, 1602 insertions, 0 deletions
diff --git a/tests/runner.c b/tests/runner.c
new file mode 100644
index 0000000..25456bc
--- /dev/null
+++ b/tests/runner.c
@@ -0,0 +1,39 @@
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 {"ot_random", test_random},
21 {"ot_hex", test_hex},
22 {"ot_scan", test_scan},
23 {"ot_buf", test_buf},
24 {"ot_accesslist", test_accesslist},
25 {NULL, NULL}};
26
27/* Run every module, or only those whose name contains argv[1] */
28int main(int argc, char **argv) {
29 const struct module *m;
30
31 for (m = modules; m->name; ++m) {
32 if (argc > 1 && !strstr(m->name, argv[1]))
33 continue;
34 test_module(m->name);
35 m->run();
36 }
37
38 return test_summary();
39}
diff --git a/tests/stubs.c b/tests/stubs.c
new file mode 100644
index 0000000..ce93071
--- /dev/null
+++ b/tests/stubs.c
@@ -0,0 +1,20 @@
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; }
15
16/* ot_accesslist.c is built with WANT_ACCESSLIST_BLACK so that the hash parser
17 is reachable. These two belong to accesslist_readfile and the reload worker,
18 neither of which any test drives. */
19time_t g_now_seconds;
20volatile int g_opentracker_running;
diff --git a/tests/t_accesslist.c b/tests/t_accesslist.c
new file mode 100644
index 0000000..9925acd
--- /dev/null
+++ b/tests/t_accesslist.c
@@ -0,0 +1,122 @@
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 "ot_accesslist.h"
12#include "test.h"
13
14#define A "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
15#define B "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
16#define C "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
17
18static ot_hash dest[8];
19
20/* The parser is handed a mapped file, so it must never look at a byte beyond
21 the length it was given. These cases place a hex digit immediately after
22 that length: a parser that reads one byte too far sees it, decides the hash
23 runs on, and silently drops the last entry. */
24
25static void case_empty(void) {
26 test_case("an empty buffer yields nothing");
27 CHECK_INT(accesslist_parse_hashes(dest, 8, "", 0), 0);
28}
29
30static void case_single_with_newline(void) {
31 const char buf[] = A "\n";
32
33 test_case("one hash followed by a newline");
34 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, 41), 1);
35}
36
37static void case_single_without_newline(void) {
38 const char buf[] = A "A";
39
40 test_case("one hash ending the buffer, with a hex digit just past the end");
41 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, 40), 1);
42}
43
44static void case_several_last_without_newline(void) {
45 const char buf[] = A "\n" B "\n" C "C";
46
47 test_case("a final hash without a trailing newline still counts");
48 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, 3 * 40 + 2), 3);
49}
50
51static void case_run_on_hash_is_rejected(void) {
52 const char buf[] = A "A\n";
53
54 test_case("forty one hex digits are not a hash");
55 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, 42), 0);
56}
57
58static void case_junk_lines_skipped(void) {
59 const char buf[] = "# a comment\n" A "\nnot a hash\n" B "\n";
60
61 test_case("lines that are not hashes are ignored");
62 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, sizeof(buf) - 1), 2);
63}
64
65static void case_trailing_junk_on_line(void) {
66 const char buf[] = A " some comment\n" B "\n";
67
68 test_case("a hash followed by other text on the same line still counts");
69 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, sizeof(buf) - 1), 2);
70}
71
72static void case_capacity_is_respected(void) {
73 const char buf[] = A "\n" B "\n" C "\n";
74
75 test_case("no more than capacity hashes are written");
76 CHECK_INT(accesslist_parse_hashes(dest, 2, buf, sizeof(buf) - 1), 2);
77}
78
79static void case_zero_capacity_never_writes(void) {
80 const char buf[] = A "\n";
81
82 test_case("a zero capacity tolerates a null destination");
83 CHECK_INT(accesslist_parse_hashes(NULL, 0, buf, 41), 0);
84}
85
86static void case_decoded_value(void) {
87 const char buf[] = "000102030405060708090A0B0C0D0E0F10111213\n";
88 const uint8_t want[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
89
90 test_case("the hash is decoded, not merely counted");
91 CHECK_INT(accesslist_parse_hashes(dest, 8, buf, 41), 1);
92 CHECK_MEM(dest[0], want, sizeof(want));
93}
94
95static void case_capacity_bound_holds(void) {
96 /* (len + 1) / 41 is what accesslist_readfile allocates. Pack a buffer as
97 densely as the parser allows and check the bound is not exceeded. */
98 char buf[41 * 4];
99 size_t len, capacity, found;
100
101 test_case("the capacity formula bounds what the parser can produce");
102 memcpy(buf, A "\n" B "\n" C "\n" A, 4 * 40 + 3);
103 len = 4 * 40 + 3;
104 capacity = (len + 1) / 41;
105 found = accesslist_parse_hashes(dest, 8, buf, len);
106 CHECK_INT(found, 4);
107 CHECK(found <= capacity);
108}
109
110void test_accesslist(void) {
111 case_empty();
112 case_single_with_newline();
113 case_single_without_newline();
114 case_several_last_without_newline();
115 case_run_on_hash_is_rejected();
116 case_junk_lines_skipped();
117 case_trailing_junk_on_line();
118 case_capacity_is_respected();
119 case_zero_capacity_never_writes();
120 case_decoded_value();
121 case_capacity_bound_holds();
122}
diff --git a/tests/t_buf.c b/tests/t_buf.c
new file mode 100644
index 0000000..dcfb0bb
--- /dev/null
+++ b/tests/t_buf.c
@@ -0,0 +1,205 @@
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 <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_buf.h"
12#include "test.h"
13
14static void case_starts_empty(void) {
15 ot_buf buf = {NULL, 0};
16
17 test_case("a fresh buffer holds nothing and has no data pointer");
18 CHECK_PTR(buf.data, NULL);
19 CHECK_INT(buf.size, 0);
20}
21
22static void case_cat_appends(void) {
23 ot_buf buf = {NULL, 0};
24
25 test_case("appends concatenate in order and report success");
26 CHECK_INT(buf_cat(&buf, "GET /ann", 8), 0);
27 CHECK(buf.data != NULL);
28 CHECK_INT(buf.size, 8);
29 CHECK_INT(buf_cat(&buf, "ounce", 5), 0);
30 CHECK_INT(buf.size, 13);
31 CHECK_MEM(buf.data, "GET /announce", 13);
32 buf_reset(&buf);
33}
34
35static void case_cat_of_nothing_allocates_nothing(void) {
36 ot_buf buf = {NULL, 0};
37
38 /* handle_read tells an untouched buffer from a partial request by the data
39 pointer, so a zero length append must not quietly allocate one */
40 test_case("appending zero bytes leaves the buffer untouched");
41 CHECK_INT(buf_cat(&buf, "ignored", 0), 0);
42 CHECK_PTR(buf.data, NULL);
43 CHECK_INT(buf.size, 0);
44 buf_reset(&buf);
45}
46
47static void case_grows_across_many_appends(void) {
48 ot_buf buf = {NULL, 0};
49 char chunk[100];
50 size_t i;
51
52 test_case("content survives growing one packet at a time");
53 memset(chunk, 'x', sizeof(chunk));
54 for (i = 0; i < 100; ++i)
55 if (buf_cat(&buf, chunk, sizeof(chunk))) {
56 test_failf(__FILE__, __LINE__, "append %zu was refused", i);
57 buf_reset(&buf);
58 return;
59 }
60 ++test_checks;
61 CHECK_INT(buf.size, 10000);
62 for (i = 0; i < 10000; ++i)
63 if (buf.data[i] != 'x') {
64 test_failf(__FILE__, __LINE__, "byte %zu was not preserved across growth", i);
65 buf_reset(&buf);
66 return;
67 }
68 ++test_checks;
69 buf_reset(&buf);
70}
71
72static void case_grows_one_byte_at_a_time(void) {
73 ot_buf buf = {NULL, 0};
74 size_t i;
75
76 /* The shape a trickling client produces. Nothing here is optimised for it,
77 so the point is only that it stays correct. */
78 test_case("a byte at a time accumulates correctly");
79 for (i = 0; i < 2048; ++i) {
80 char c = (char)('a' + (i % 26));
81 if (buf_cat(&buf, &c, 1)) {
82 test_failf(__FILE__, __LINE__, "append %zu was refused", i);
83 buf_reset(&buf);
84 return;
85 }
86 }
87 ++test_checks;
88 CHECK_INT(buf.size, 2048);
89 for (i = 0; i < 2048; ++i)
90 if (buf.data[i] != (char)('a' + (i % 26))) {
91 test_failf(__FILE__, __LINE__, "byte %zu differs", i);
92 buf_reset(&buf);
93 return;
94 }
95 ++test_checks;
96 buf_reset(&buf);
97}
98
99static void case_truncate_shrinks_only(void) {
100 ot_buf buf = {NULL, 0};
101
102 test_case("truncate drops the tail but never grows");
103 buf_cat(&buf, "0123456789", 10);
104 buf_truncate(&buf, 4);
105 CHECK_INT(buf.size, 4);
106 CHECK_MEM(buf.data, "0123", 4);
107
108 buf_truncate(&buf, 9);
109 CHECK_INT(buf.size, 4);
110 buf_truncate(&buf, 4);
111 CHECK_INT(buf.size, 4);
112
113 buf_truncate(&buf, 0);
114 CHECK_INT(buf.size, 0);
115 buf_reset(&buf);
116}
117
118static void case_append_after_truncate(void) {
119 ot_buf buf = {NULL, 0};
120
121 test_case("appending after a truncate overwrites the dropped tail");
122 buf_cat(&buf, "keepdrop", 8);
123 buf_truncate(&buf, 4);
124 CHECK_INT(buf_cat(&buf, "MORE", 4), 0);
125 CHECK_INT(buf.size, 8);
126 CHECK_MEM(buf.data, "keepMORE", 8);
127 buf_reset(&buf);
128}
129
130static void case_reset_clears_everything(void) {
131 ot_buf buf = {NULL, 0};
132
133 test_case("reset releases the buffer and restores the initial state");
134 buf_cat(&buf, "something", 9);
135 buf_reset(&buf);
136 CHECK_PTR(buf.data, NULL);
137 CHECK_INT(buf.size, 0);
138}
139
140static void case_reset_of_empty_is_safe(void) {
141 ot_buf buf = {NULL, 0};
142
143 test_case("resetting a buffer that was never used is harmless");
144 buf_reset(&buf);
145 buf_reset(&buf);
146 CHECK_PTR(buf.data, NULL);
147 CHECK_INT(buf.size, 0);
148}
149
150static void case_refused_append_is_harmless(void) {
151 ot_buf buf = {NULL, 0};
152
153 /* A length that would wrap the size is rejected before src is read and
154 before anything is allocated, so a short string is safe to pass here.
155 A refused append must not destroy the partial request already collected:
156 the caller answers and closes rather than losing both. */
157 test_case("a length that would wrap the size is refused");
158 CHECK_INT(buf_cat(&buf, "already here", 12), 0);
159 CHECK_INT(buf_cat(&buf, "x", (size_t)-1), -1);
160
161 test_case("and earlier content survives it");
162 CHECK_INT(buf.size, 12);
163 CHECK_MEM(buf.data, "already here", 12);
164
165 test_case("and the buffer still works afterwards");
166 CHECK_INT(buf_cat(&buf, "!", 1), 0);
167 CHECK_INT(buf.size, 13);
168 CHECK_MEM(buf.data, "already here!", 13);
169 buf_reset(&buf);
170}
171
172static void case_accumulates_a_split_request(void) {
173 /* What handle_read actually does: collect a request that arrived in
174 pieces, then hand over the whole of it */
175 ot_buf buf = {NULL, 0};
176 const char *pieces[] = {"GET /announce?info_hash=", "0123456789abcdefghij", " HTTP/1.0\r\n\r\n"};
177 size_t i;
178
179 test_case("a request split over several reads is reassembled");
180 for (i = 0; i < 3; ++i)
181 if (buf_cat(&buf, pieces[i], strlen(pieces[i]))) {
182 test_failf(__FILE__, __LINE__, "piece %zu was refused", i);
183 buf_reset(&buf);
184 return;
185 }
186 ++test_checks;
187
188 CHECK_INT(buf.size, 24 + 20 + 13);
189 CHECK_MEM(buf.data, "GET /announce?info_hash=0123456789abcdefghij HTTP/1.0\r\n\r\n", 57);
190 buf_reset(&buf);
191}
192
193void test_buf(void) {
194 case_starts_empty();
195 case_cat_appends();
196 case_cat_of_nothing_allocates_nothing();
197 case_grows_across_many_appends();
198 case_grows_one_byte_at_a_time();
199 case_truncate_shrinks_only();
200 case_append_after_truncate();
201 case_reset_clears_everything();
202 case_reset_of_empty_is_safe();
203 case_refused_append_is_harmless();
204 case_accumulates_a_split_request();
205}
diff --git a/tests/t_hex.c b/tests/t_hex.c
new file mode 100644
index 0000000..7cce8ac
--- /dev/null
+++ b/tests/t_hex.c
@@ -0,0 +1,128 @@
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 <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_hex.h"
12#include "test.h"
13
14static const uint8_t sample[20] = {0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xff, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0};
15
16static void case_encode(void) {
17 char dest[64];
18
19 test_case("a hash encodes to forty upper case digits and a terminator");
20 memset(dest, 0x7f, sizeof(dest));
21 CHECK_PTR(hex_fromhash(dest, sample), dest);
22 CHECK_STR(dest, "000123456789ABCDEFFF10203040506070809" "0A0");
23 CHECK_INT(strlen(dest), 40);
24 CHECK_INT((unsigned char)dest[41], 0x7f);
25}
26
27static void case_decode(void) {
28 ot_hash hash;
29
30 test_case("forty digits decode back to the hash");
31 CHECK(hex_tohash(hash, "000123456789ABCDEFFF10203040506070809" "0A0"));
32 CHECK_MEM(hash, sample, sizeof(sample));
33}
34
35static void case_round_trip(void) {
36 ot_hash hash;
37 char text[64];
38 size_t i;
39
40 test_case("encode and decode are inverses over every byte value");
41 for (i = 0; i < 256; ++i) {
42 uint8_t probe[20];
43 memset(probe, (uint8_t)i, sizeof(probe));
44 hex_fromhash(text, probe);
45 if (!hex_tohash(hash, text) || memcmp(hash, probe, sizeof(probe))) {
46 test_failf(__FILE__, __LINE__, "round trip failed for byte value %zu", i);
47 return;
48 }
49 }
50 ++test_checks;
51}
52
53static void case_decode_is_case_insensitive(void) {
54 ot_hash upper, lower;
55
56 test_case("lower case digits decode the same as upper case");
57 CHECK(hex_tohash(upper, "ABCDEF0123456789ABCDEF0123456789ABCDEF01"));
58 CHECK(hex_tohash(lower, "abcdef0123456789abcdef0123456789abcdef01"));
59 CHECK_MEM(lower, upper, sizeof(ot_hash));
60}
61
62static void case_rejects_non_digits(void) {
63 ot_hash hash;
64
65 test_case("a non hex character anywhere rejects the whole string");
66 CHECK_INT(hex_tohash(hash, "g00123456789ABCDEFFF102030405060708090A0"), 0);
67 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF102030405060708090Ag"), 0);
68 CHECK_INT(hex_tohash(hash, "000123456789ABCDEF:F102030405060708090A"), 0);
69}
70
71static void case_rejects_short_input(void) {
72 ot_hash hash;
73
74 test_case("a string shorter than forty digits is rejected");
75 CHECK_INT(hex_tohash(hash, ""), 0);
76 CHECK_INT(hex_tohash(hash, "00"), 0);
77 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF102030405060708090A"), 0);
78}
79
80static void case_leaves_hash_untouched_on_failure(void) {
81 ot_hash hash;
82
83 test_case("a rejected string leaves the destination untouched");
84 memset(hash, 0x5a, sizeof(hash));
85 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF10203040506070809XX0"), 0);
86 CHECK_MEM(hash, "\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a", sizeof(hash));
87}
88
89static void case_stops_at_the_terminator(void) {
90 /* The saved state parser hands this whole buffers that may end early. The
91 decoder must not read past the zero that stopped it. */
92 char guarded[8];
93 ot_hash hash;
94
95 test_case("decoding stops at a terminator rather than running past it");
96 memcpy(guarded, "0012", 4);
97 guarded[4] = 0;
98 memset(guarded + 5, 'f', 3);
99 CHECK_INT(hex_tohash(hash, guarded), 0);
100}
101
102static void case_isdigit(void) {
103 test_case("hex_isdigit accepts exactly the hex digits");
104 CHECK(hex_isdigit('0'));
105 CHECK(hex_isdigit('9'));
106 CHECK(hex_isdigit('a'));
107 CHECK(hex_isdigit('f'));
108 CHECK(hex_isdigit('A'));
109 CHECK(hex_isdigit('F'));
110 CHECK_INT(hex_isdigit('g'), 0);
111 CHECK_INT(hex_isdigit('G'), 0);
112 CHECK_INT(hex_isdigit(':'), 0);
113 CHECK_INT(hex_isdigit('\n'), 0);
114 CHECK_INT(hex_isdigit(0), 0);
115 CHECK_INT(hex_isdigit(0x80), 0);
116}
117
118void test_hex(void) {
119 case_encode();
120 case_decode();
121 case_round_trip();
122 case_decode_is_case_insensitive();
123 case_rejects_non_digits();
124 case_rejects_short_input();
125 case_leaves_hash_untouched_on_failure();
126 case_stops_at_the_terminator();
127 case_isdigit();
128}
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_random.c b/tests/t_random.c
new file mode 100644
index 0000000..67dedf6
--- /dev/null
+++ b/tests/t_random.c
@@ -0,0 +1,114 @@
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 <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_random.h"
12#include "test.h"
13
14/* Nothing here can test that the bytes are unpredictable, which is a property
15 of the generator the build selected. What these cases do pin is that the
16 wrapper hands the whole buffer to it, writes nothing beyond the length it
17 was given, and is actually wired to something that varies. The first of
18 those matters: the fallback this module replaced filled only the first
19 sixteen bytes of the sixty-four byte key buffer in ot_udp.c. */
20
21#define DRAWS 32
22#define BUFLEN 64
23
24static void case_every_byte_varies(void) {
25 unsigned char first[BUFLEN], draw[BUFLEN];
26 unsigned char varied[BUFLEN];
27 size_t i, n;
28
29 test_case("every byte of the buffer comes from the generator");
30 memset(varied, 0, sizeof(varied));
31 random_bytes(first, sizeof(first));
32
33 for (n = 0; n < DRAWS; ++n) {
34 random_bytes(draw, sizeof(draw));
35 for (i = 0; i < BUFLEN; ++i)
36 if (draw[i] != first[i])
37 varied[i] = 1;
38 }
39
40 /* A byte position that never moved across 32 draws is not being written.
41 A false alarm needs all 32 draws to agree by chance, around 256^-31. */
42 for (i = 0; i < BUFLEN; ++i)
43 if (!varied[i]) {
44 test_failf(__FILE__, __LINE__, "byte %zu never changed over %d draws", i, DRAWS);
45 return;
46 }
47 ++test_checks;
48}
49
50static void case_respects_length(void) {
51 unsigned char buf[32];
52 size_t i;
53
54 test_case("nothing is written past the requested length");
55 memset(buf, 0xA5, sizeof(buf));
56 random_bytes(buf, 8);
57 for (i = 8; i < sizeof(buf); ++i)
58 if (buf[i] != 0xA5) {
59 test_failf(__FILE__, __LINE__, "byte %zu past the end was overwritten", i);
60 return;
61 }
62 ++test_checks;
63}
64
65static void case_zero_length(void) {
66 unsigned char buf[4];
67
68 test_case("a zero length request writes nothing");
69 memset(buf, 0x5A, sizeof(buf));
70 random_bytes(buf, 0);
71 CHECK_MEM(buf, "\x5a\x5a\x5a\x5a", 4);
72}
73
74static void case_successive_draws_differ(void) {
75 unsigned char a[32], b[32];
76
77 test_case("two draws differ");
78 random_bytes(a, sizeof(a));
79 random_bytes(b, sizeof(b));
80 CHECK(memcmp(a, b, sizeof(a)) != 0);
81}
82
83static void case_uint32_varies(void) {
84 uint32_t first = random_uint32();
85 int differs = 0;
86 size_t n;
87
88 test_case("random_uint32 does not return a constant");
89 for (n = 0; n < DRAWS; ++n)
90 if (random_uint32() != first)
91 differs = 1;
92 CHECK(differs);
93}
94
95static void case_uint32_uses_the_top_bit(void) {
96 uint32_t seen = 0;
97 size_t n;
98
99 test_case("random_uint32 spans the full 32 bits");
100 /* random() never sets bit 31, so a build that returned it unmixed would
101 fail this. Over 64 draws the chance of a false alarm is 2^-64. */
102 for (n = 0; n < 64; ++n)
103 seen |= random_uint32();
104 CHECK_INT(seen & 0x80000000u, 0x80000000u);
105}
106
107void test_random(void) {
108 case_every_byte_varies();
109 case_respects_length();
110 case_zero_length();
111 case_successive_draws_differ();
112 case_uint32_varies();
113 case_uint32_uses_the_top_bit();
114}
diff --git a/tests/t_scan.c b/tests/t_scan.c
new file mode 100644
index 0000000..89f1590
--- /dev/null
+++ b/tests/t_scan.c
@@ -0,0 +1,162 @@
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 <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_scan.h"
12#include "test.h"
13
14static void case_plain_numbers(void) {
15 uint64_t value;
16
17 test_case("a decimal number is read and its length reported");
18 CHECK_INT(scan_udec("0", &value, 65535), 1);
19 CHECK_INT(value, 0);
20 CHECK_INT(scan_udec("7", &value, 65535), 1);
21 CHECK_INT(value, 7);
22 CHECK_INT(scan_udec("6969", &value, 65535), 4);
23 CHECK_INT(value, 6969);
24 CHECK_INT(scan_udec("65535", &value, 65535), 5);
25 CHECK_INT(value, 65535);
26}
27
28static void case_stops_at_non_digit(void) {
29 uint64_t value;
30
31 test_case("scanning stops at the first character that is not a digit");
32 CHECK_INT(scan_udec("6969:rest", &value, 65535), 4);
33 CHECK_INT(value, 6969);
34 CHECK_INT(scan_udec("128/8", &value, 65535), 3);
35 CHECK_INT(value, 128);
36 CHECK_INT(scan_udec("42\n", &value, 65535), 2);
37 CHECK_INT(value, 42);
38}
39
40static void case_rejects_whitespace(void) {
41 uint64_t value = 0xdead;
42
43 test_case("leading whitespace is not skipped");
44 CHECK_INT(scan_udec(" 1", &value, 65535), 0);
45 CHECK_INT(scan_udec("\t1", &value, 65535), 0);
46 CHECK_INT(scan_udec("\n1", &value, 65535), 0);
47 CHECK_INT(value, 0xdead);
48}
49
50static void case_rejects_sign(void) {
51 uint64_t value = 0xdead;
52
53 test_case("a sign is not accepted");
54 CHECK_INT(scan_udec("+1", &value, 65535), 0);
55 CHECK_INT(scan_udec("-1", &value, 65535), 0);
56 CHECK_INT(scan_udec("-0", &value, 65535), 0);
57 CHECK_INT(value, 0xdead);
58}
59
60static void case_rejects_empty_and_junk(void) {
61 uint64_t value = 0xdead;
62
63 test_case("a string with no leading digit yields nothing");
64 CHECK_INT(scan_udec("", &value, 65535), 0);
65 CHECK_INT(scan_udec("x", &value, 65535), 0);
66 CHECK_INT(scan_udec(":80", &value, 65535), 0);
67 CHECK_INT(value, 0xdead);
68}
69
70static void case_no_alternative_bases(void) {
71 uint64_t value;
72
73 test_case("hexadecimal and octal notations are read as plain decimal");
74 CHECK_INT(scan_udec("0x10", &value, 65535), 1);
75 CHECK_INT(value, 0);
76 CHECK_INT(scan_udec("010", &value, 65535), 3);
77 CHECK_INT(value, 10);
78}
79
80static void case_refuses_out_of_range(void) {
81 uint64_t value = 0xdead;
82
83 test_case("a value above max is refused, not truncated");
84 CHECK_INT(scan_udec("65536", &value, 65535), 0);
85 CHECK_INT(scan_udec("99999", &value, 65535), 0);
86 CHECK_INT(scan_udec("129", &value, 128), 0);
87 CHECK_INT(value, 0xdead);
88
89 test_case("the boundary itself is accepted");
90 CHECK_INT(scan_udec("128", &value, 128), 3);
91 CHECK_INT(value, 128);
92}
93
94static void case_sixty_four_bit_range(void) {
95 uint64_t value;
96
97 test_case("the full unsigned 64 bit range is available");
98 CHECK_INT(scan_udec("18446744073709551615", &value, UINT64_MAX), 20);
99 CHECK(value == UINT64_MAX);
100
101 test_case("a number too large for 64 bits is refused");
102 CHECK_INT(scan_udec("18446744073709551616", &value, UINT64_MAX), 0);
103 CHECK_INT(scan_udec("99999999999999999999999999", &value, UINT64_MAX), 0);
104}
105
106static void case_leading_zeros_allowed(void) {
107 uint64_t value;
108
109 test_case("leading zeros are accepted outside bencode");
110 CHECK_INT(scan_udec("007", &value, 65535), 3);
111 CHECK_INT(value, 7);
112 CHECK_INT(scan_udec("0000", &value, 65535), 4);
113 CHECK_INT(value, 0);
114}
115
116static void case_bencode_rejects_leading_zeros(void) {
117 uint64_t value = 0xdead;
118
119 test_case("bencode refuses a leading zero on anything but zero");
120 CHECK_INT(scan_udec_bencode("007", &value, 65535), 0);
121 CHECK_INT(scan_udec_bencode("0000", &value, 65535), 0);
122 CHECK_INT(scan_udec_bencode("01", &value, 65535), 0);
123 CHECK_INT(value, 0xdead);
124}
125
126static void case_bencode_accepts_canonical(void) {
127 uint64_t value;
128
129 test_case("bencode accepts a canonically written number");
130 CHECK_INT(scan_udec_bencode("0", &value, 65535), 1);
131 CHECK_INT(value, 0);
132 CHECK_INT(scan_udec_bencode("0:", &value, 65535), 1);
133 CHECK_INT(value, 0);
134 CHECK_INT(scan_udec_bencode("160:", &value, 65535), 3);
135 CHECK_INT(value, 160);
136}
137
138static void case_bencode_inherits_the_rest(void) {
139 uint64_t value = 0xdead;
140
141 test_case("bencode is no laxer than the plain scanner");
142 CHECK_INT(scan_udec_bencode(" 1", &value, 65535), 0);
143 CHECK_INT(scan_udec_bencode("-1", &value, 65535), 0);
144 CHECK_INT(scan_udec_bencode("+1", &value, 65535), 0);
145 CHECK_INT(scan_udec_bencode("65536", &value, 65535), 0);
146 CHECK_INT(value, 0xdead);
147}
148
149void test_scan(void) {
150 case_plain_numbers();
151 case_stops_at_non_digit();
152 case_rejects_whitespace();
153 case_rejects_sign();
154 case_rejects_empty_and_junk();
155 case_no_alternative_bases();
156 case_refuses_out_of_range();
157 case_sixty_four_bit_range();
158 case_leading_zeros_allowed();
159 case_bencode_rejects_leading_zeros();
160 case_bencode_accepts_canonical();
161 case_bencode_inherits_the_rest();
162}
diff --git a/tests/t_scan_urlencoded_query.c b/tests/t_scan_urlencoded_query.c
new file mode 100644
index 0000000..6fa34e0
--- /dev/null
+++ b/tests/t_scan_urlencoded_query.c
@@ -0,0 +1,381 @@
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
257
258/* The encoding direction. Which bytes survive unescaped is fixed by what
259 existing parsers of the urlencoded full scrape expect, so these cases pin
260 the set rather than describe a preference. */
261
262static void case_fmt_passes_safe_bytes(void) {
263 const uint8_t src[] = "aZ09$-./=_~";
264 char dest[64];
265 size_t n;
266
267 test_case("alphanumerics and the safe punctuation pass through");
268 n = fmt_urlencoded(dest, src, 11);
269 dest[n] = 0;
270 CHECK_INT(n, 11);
271 CHECK_STR(dest, "aZ09$-./=_~");
272}
273
274static void case_fmt_escapes_the_rest(void) {
275 const uint8_t src[] = {' ', '%', '&', '+', ':', '?'};
276 char dest[64];
277 size_t n;
278
279 test_case("everything else becomes a percent escape");
280 n = fmt_urlencoded(dest, src, sizeof(src));
281 dest[n] = 0;
282 CHECK_INT(n, 18);
283 CHECK_STR(dest, "%20%25%26%2b%3a%3f");
284}
285
286static void case_fmt_hex_is_lower_case(void) {
287 const uint8_t src[] = {0x00, 0xab, 0xff};
288 char dest[64];
289 size_t n;
290
291 test_case("escapes use lower case hex digits");
292 n = fmt_urlencoded(dest, src, sizeof(src));
293 dest[n] = 0;
294 CHECK_INT(n, 9);
295 CHECK_STR(dest, "%00%ab%ff");
296}
297
298static void case_fmt_length_only(void) {
299 const uint8_t src[] = {'a', 0xff, 'b'};
300
301 test_case("a null destination reports the length without writing");
302 CHECK_INT(fmt_urlencoded(NULL, src, sizeof(src)), 5);
303 CHECK_INT(fmt_urlencoded(NULL, src, 0), 0);
304}
305
306static void case_fmt_zero_length(void) {
307 char dest[4];
308
309 test_case("a zero length input writes nothing");
310 memset(dest, 0x5a, sizeof(dest));
311 CHECK_INT(fmt_urlencoded(dest, (const uint8_t *)"abc", 0), 0);
312 CHECK_MEM(dest, "\x5a\x5a\x5a\x5a", 4);
313}
314
315static void case_fmt_worst_case_fits(void) {
316 uint8_t src[20];
317 char dest[61];
318 size_t i, n;
319
320 test_case("a hash of entirely unsafe bytes needs three times its length");
321 for (i = 0; i < sizeof(src); ++i)
322 src[i] = 0xff;
323 n = fmt_urlencoded(dest, src, sizeof(src));
324 dest[n] = 0;
325 CHECK_INT(n, 60);
326 for (i = 0; i < 20; ++i)
327 if (memcmp(dest + 3 * i, "%ff", 3)) {
328 test_failf(__FILE__, __LINE__, "escape %zu is wrong", i);
329 return;
330 }
331 ++test_checks;
332}
333
334static void case_fmt_decoder_disagrees_on_dollar(void) {
335 /* Documenting a known asymmetry rather than a wish. Of the bytes the
336 encoder passes through unescaped, '$' is the only one the decoder then
337 refuses, and it refuses the whole string rather than stopping short.
338 Output of the urlencoded full scrape therefore does not round trip
339 through scan_urlencoded_query when a hash contains 0x24. Harmless today
340 because only third party tooling reads that output. */
341 const uint8_t src[] = {'a', '$', 'b'};
342 char dest[16];
343 char scanned[16];
344 char *p = dest;
345 size_t n;
346 ssize_t got;
347
348 test_case("the encoder passes '$' but the decoder rejects it");
349 n = fmt_urlencoded(dest, src, sizeof(src));
350 dest[n] = 0;
351 CHECK_STR(dest, "a$b");
352
353 got = scan_urlencoded_query(&p, scanned, SCAN_SEARCHPATH_VALUE);
354 CHECK_INT(got, -1);
355}
356
357void test_scan_urlencoded_query(void) {
358 case_path();
359 case_param_and_value();
360 case_percent_decoding();
361 case_binary_info_hash();
362 case_in_place();
363 case_bad_escape();
364 case_truncated_escape();
365 case_terminator();
366 case_terminator_after_output();
367 case_separators_are_contextual();
368 case_ampersand_in_path();
369 case_raw_high_byte();
370 case_skipvalue();
371 case_find_keywords();
372 case_fixed_int();
373 case_fixed_int_bounded();
374 case_fmt_passes_safe_bytes();
375 case_fmt_escapes_the_rest();
376 case_fmt_hex_is_lower_case();
377 case_fmt_length_only();
378 case_fmt_zero_length();
379 case_fmt_worst_case_fits();
380 case_fmt_decoder_disagrees_on_dollar();
381}
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..676ca33
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,88 @@
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);
82void test_random(void);
83void test_hex(void);
84void test_accesslist(void);
85void test_scan(void);
86void test_buf(void);
87
88#endif