diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/runner.c | 39 | ||||
| -rw-r--r-- | tests/stubs.c | 20 | ||||
| -rw-r--r-- | tests/t_accesslist.c | 122 | ||||
| -rw-r--r-- | tests/t_buf.c | 205 | ||||
| -rw-r--r-- | tests/t_hex.c | 128 | ||||
| -rw-r--r-- | tests/t_iovec.c | 124 | ||||
| -rw-r--r-- | tests/t_random.c | 114 | ||||
| -rw-r--r-- | tests/t_scan.c | 162 | ||||
| -rw-r--r-- | tests/t_scan_urlencoded_query.c | 381 | ||||
| -rw-r--r-- | tests/t_vector.c | 161 | ||||
| -rw-r--r-- | tests/test.c | 58 | ||||
| -rw-r--r-- | tests/test.h | 88 |
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 | |||
| 12 | struct module { | ||
| 13 | const char *name; | ||
| 14 | void (*run)(void); | ||
| 15 | }; | ||
| 16 | |||
| 17 | static 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] */ | ||
| 28 | int 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. */ | ||
| 14 | void 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. */ | ||
| 19 | time_t g_now_seconds; | ||
| 20 | volatile 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 | |||
| 18 | static 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 | |||
| 25 | static void case_empty(void) { | ||
| 26 | test_case("an empty buffer yields nothing"); | ||
| 27 | CHECK_INT(accesslist_parse_hashes(dest, 8, "", 0), 0); | ||
| 28 | } | ||
| 29 | |||
| 30 | static 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 | |||
| 37 | static 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 | |||
| 44 | static 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 | |||
| 51 | static 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 | |||
| 58 | static 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 | |||
| 65 | static 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 | |||
| 72 | static 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 | |||
| 79 | static 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 | |||
| 86 | static 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 | |||
| 95 | static 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 | |||
| 110 | void 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 | |||
| 14 | static 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 | |||
| 22 | static 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 | |||
| 35 | static 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 | |||
| 47 | static 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 | |||
| 72 | static 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 | |||
| 99 | static 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 | |||
| 118 | static 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 | |||
| 130 | static 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 | |||
| 140 | static 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 | |||
| 150 | static 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 | |||
| 172 | static 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 | |||
| 193 | void 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 | |||
| 14 | static 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 | |||
| 16 | static 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 | |||
| 27 | static 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 | |||
| 35 | static 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 | |||
| 53 | static 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 | |||
| 62 | static 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 | |||
| 71 | static 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 | |||
| 80 | static 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 | |||
| 89 | static 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 | |||
| 102 | static 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 | |||
| 118 | void 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 | |||
| 15 | static 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 | |||
| 36 | static 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 | |||
| 49 | static 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 | |||
| 69 | static 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 | |||
| 79 | static 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 | |||
| 100 | static 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 | |||
| 117 | void 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 | |||
| 24 | static 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 | |||
| 50 | static 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 | |||
| 65 | static 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 | |||
| 74 | static 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 | |||
| 83 | static 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 | |||
| 95 | static 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 | |||
| 107 | void 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 | |||
| 14 | static 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 | |||
| 28 | static 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 | |||
| 40 | static 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 | |||
| 50 | static 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 | |||
| 60 | static 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 | |||
| 70 | static 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 | |||
| 80 | static 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 | |||
| 94 | static 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 | |||
| 106 | static 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 | |||
| 116 | static 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 | |||
| 126 | static 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 | |||
| 138 | static 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 | |||
| 149 | void 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 | |||
| 19 | static 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 | |||
| 33 | static 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 | |||
| 61 | static 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 | |||
| 73 | static 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 | |||
| 87 | static 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 | |||
| 98 | static 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 | |||
| 109 | static 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 | |||
| 120 | static 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 | |||
| 136 | static 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 | |||
| 154 | static 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 | |||
| 175 | static 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 | |||
| 186 | static 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 | |||
| 197 | static 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 | |||
| 212 | static 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 | |||
| 231 | static 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 | |||
| 248 | static 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 | |||
| 262 | static 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 | |||
| 274 | static 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 | |||
| 286 | static 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 | |||
| 298 | static 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 | |||
| 306 | static 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 | |||
| 315 | static 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 | |||
| 334 | static 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 | |||
| 357 | void 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 | |||
| 18 | static 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 | |||
| 20 | static 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 | |||
| 31 | static 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 | |||
| 43 | static 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 | |||
| 59 | static 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 | |||
| 70 | static 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 | |||
| 79 | static 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 | |||
| 113 | static 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 | |||
| 132 | static 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 | |||
| 152 | void 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 | |||
| 13 | unsigned long test_checks; | ||
| 14 | unsigned long test_failures; | ||
| 15 | |||
| 16 | static const char *current_module = ""; | ||
| 17 | static const char *current_case = ""; | ||
| 18 | |||
| 19 | void test_module(const char *name) { | ||
| 20 | current_module = name; | ||
| 21 | current_case = ""; | ||
| 22 | printf("%s\n", name); | ||
| 23 | } | ||
| 24 | |||
| 25 | void test_case(const char *name) { current_case = name; } | ||
| 26 | |||
| 27 | static 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 | |||
| 32 | void 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 | |||
| 41 | void 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 | |||
| 55 | int 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 | |||
| 21 | void test_module(const char *name); | ||
| 22 | void test_case(const char *name); | ||
| 23 | void test_failf(const char *file, int line, const char *fmt, ...); | ||
| 24 | void test_failmem(const char *file, int line, const char *what, const void *got, const void *want, size_t len); | ||
| 25 | int test_summary(void); | ||
| 26 | |||
| 27 | extern unsigned long test_checks; | ||
| 28 | extern 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 */ | ||
| 79 | void test_scan_urlencoded_query(void); | ||
| 80 | void test_iovec(void); | ||
| 81 | void test_vector(void); | ||
| 82 | void test_random(void); | ||
| 83 | void test_hex(void); | ||
| 84 | void test_accesslist(void); | ||
| 85 | void test_scan(void); | ||
| 86 | void test_buf(void); | ||
| 87 | |||
| 88 | #endif | ||
