summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/runner.c2
-rw-r--r--tests/stubs.c9
-rw-r--r--tests/t_batch.c348
-rw-r--r--tests/t_mutex.c107
-rw-r--r--tests/t_socket.c34
-rw-r--r--tests/t_vector.c104
-rw-r--r--tests/test.h2
7 files changed, 587 insertions, 19 deletions
diff --git a/tests/runner.c b/tests/runner.c
index db62246..148b928 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -23,6 +23,8 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
23 {"ot_buf", test_buf}, 23 {"ot_buf", test_buf},
24 {"ot_ip", test_ip}, 24 {"ot_ip", test_ip},
25 {"ot_socket", test_socket}, 25 {"ot_socket", test_socket},
26 {"ot_mutex", test_mutex},
27 {"ot_batch", test_batch},
26 {"ot_accesslist", test_accesslist}, 28 {"ot_accesslist", test_accesslist},
27 {NULL, NULL}}; 29 {NULL, NULL}};
28 30
diff --git a/tests/stubs.c b/tests/stubs.c
index ce93071..09a7312 100644
--- a/tests/stubs.c
+++ b/tests/stubs.c
@@ -18,3 +18,12 @@ void free_peerlist(ot_peerlist *peer_list) { (void)peer_list; }
18 neither of which any test drives. */ 18 neither of which any test drives. */
19time_t g_now_seconds; 19time_t g_now_seconds;
20volatile int g_opentracker_running; 20volatile int g_opentracker_running;
21
22/* ot_mutex.c wakes the main loop through the self pipe when a result is
23 ready. The queue tests never get that far. */
24int g_self_pipe[2] = {-1, -1};
25long io_trywrite(long d, const char *buf, long len) {
26 (void)d;
27 (void)buf;
28 return len;
29}
diff --git a/tests/t_batch.c b/tests/t_batch.c
new file mode 100644
index 0000000..02743f1
--- /dev/null
+++ b/tests/t_batch.c
@@ -0,0 +1,348 @@
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 <fcntl.h>
8#include <poll.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14/* Opentracker */
15#include "ot_batch.h"
16#include "test.h"
17
18/* The batch writes to a real socket pair, because what is worth pinning down
19 is what it does when the kernel takes only part of what it was offered. */
20
21static char *dup_of(const char *text) {
22 size_t len = strlen(text);
23 char *copy = malloc(len);
24
25 memcpy(copy, text, len);
26 return copy;
27}
28
29static void drain(int s, char *into, size_t len) {
30 size_t got = 0;
31
32 while (got < len) {
33 ssize_t r = read(s, into + got, len - got);
34
35 if (r <= 0)
36 break;
37 got += (size_t)r;
38 }
39 into[got] = 0;
40}
41
42static void case_queue_accounting(void) {
43 ot_batch batch;
44
45 memset(&batch, 0, sizeof(batch));
46
47 test_case("a zeroed batch is empty and sends nothing");
48 CHECK_INT(batch_pending(&batch), 0);
49 CHECK_INT(batch_send(-1, &batch), BATCH_DONE);
50
51 test_case("anything queued makes it pending");
52 CHECK_INT(batch_add(&batch, dup_of("hello "), 6), 0);
53 CHECK_INT(batch_add_copy(&batch, "world", 5), 0);
54 CHECK_INT(batch_pending(&batch), 1);
55 CHECK_INT(batch.count, 2);
56
57 batch_reset(&batch);
58 test_case("and a reset empties it");
59 CHECK_INT(batch_pending(&batch), 0);
60 CHECK_PTR(batch.entries, NULL);
61}
62
63static void case_send_in_one_go(void) {
64 ot_batch batch;
65 int sv[2];
66 char got[32];
67
68 memset(&batch, 0, sizeof(batch));
69 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
70
71 test_case("several buffers arrive as one stream, in order");
72 CHECK_INT(batch_add(&batch, dup_of("opentracker"), 11), 0);
73 CHECK_INT(batch_add_copy(&batch, " says ", 6), 0);
74 CHECK_INT(batch_add(&batch, dup_of("hello"), 5), 0);
75 CHECK_INT(batch_send(sv[0], &batch), 22);
76 CHECK_INT(batch_pending(&batch), 0);
77
78 drain(sv[1], got, 22);
79 CHECK_STR(got, "opentracker says hello");
80
81 test_case("and an empty batch reports it has nothing left");
82 CHECK_INT(batch_send(sv[0], &batch), BATCH_DONE);
83
84 batch_reset(&batch);
85 close(sv[0]);
86 close(sv[1]);
87}
88
89/* Send everything the batch holds, reading the other end as we go so the
90 socket never stays full, and return what arrived. */
91static size_t pump(int writer, int reader, ot_batch *batch, char *into, size_t room) {
92 size_t got = 0;
93
94 while (batch_pending(batch) || got < room) {
95 ssize_t r;
96
97 if (batch_pending(batch)) {
98 ssize_t res = batch_send(writer, batch);
99
100 if (res == BATCH_FAILED)
101 break;
102 if (res > 0)
103 continue;
104 }
105
106 r = read(reader, into + got, room - got);
107 if (r <= 0)
108 break;
109 got += (size_t)r;
110 }
111 return got;
112}
113
114static void case_partial_write_resumes(void) {
115 const size_t size = 200000;
116 ot_batch batch;
117 int sv[2], sndbuf = 4096;
118 char *sent, *got;
119 size_t i, arrived;
120
121 memset(&batch, 0, sizeof(batch));
122 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
123 setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
124 fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK);
125
126 sent = malloc(size);
127 got = malloc(size);
128 for (i = 0; i < size; ++i)
129 sent[i] = (char)(i % 251);
130
131 test_case("a buffer the kernel takes in pieces arrives whole and in order");
132 CHECK_INT(batch_add(&batch, memcpy(malloc(size), sent, size), size), 0);
133 arrived = pump(sv[0], sv[1], &batch, got, size);
134 CHECK_INT(arrived, size);
135 CHECK_INT(batch_pending(&batch), 0);
136 CHECK_MEM(got, sent, size);
137
138 free(sent);
139 free(got);
140 batch_reset(&batch);
141 close(sv[0]);
142 close(sv[1]);
143}
144
145/* writev refuses more than IOV_MAX entries at once, so a long batch has to be
146 sent across several calls. */
147static void case_more_entries_than_one_writev(void) {
148 const size_t entries = 3000, each = 4;
149 ot_batch batch;
150 int sv[2], sndbuf = 4096;
151 char *sent, *got;
152 size_t i, arrived, refused = 0;
153
154 memset(&batch, 0, sizeof(batch));
155 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
156 setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
157 fcntl(sv[0], F_SETFL, fcntl(sv[0], F_GETFL, 0) | O_NONBLOCK);
158
159 sent = malloc(entries * each);
160 got = malloc(entries * each);
161
162 test_case("a batch of more entries than writev accepts still arrives whole");
163 for (i = 0; i < entries; ++i) {
164 char piece[4];
165
166 piece[0] = (char)(i), piece[1] = (char)(i >> 8), piece[2] = 'x', piece[3] = 'y';
167 memcpy(sent + i * each, piece, each);
168 if (batch_add(&batch, memcpy(malloc(each), piece, each), each))
169 ++refused;
170 }
171 CHECK_INT(refused, 0);
172 CHECK_INT(batch.count, entries);
173
174 arrived = pump(sv[0], sv[1], &batch, got, entries * each);
175 CHECK_INT(arrived, entries * each);
176 CHECK_MEM(got, sent, entries * each);
177
178 free(sent);
179 free(got);
180 batch_reset(&batch);
181 close(sv[0]);
182 close(sv[1]);
183}
184
185static void case_reset_frees_the_unsent(void) {
186 ot_batch batch;
187 int sv[2];
188
189 memset(&batch, 0, sizeof(batch));
190 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
191
192 test_case("a batch torn down mid flight keeps no bytes outstanding");
193 CHECK_INT(batch_add(&batch, dup_of("first"), 5), 0);
194 CHECK_INT(batch_add(&batch, dup_of("second"), 6), 0);
195 CHECK_INT(batch_send(sv[0], &batch), 11);
196 CHECK_INT(batch_add(&batch, dup_of("never sent"), 10), 0);
197 CHECK_INT(batch_pending(&batch), 1);
198
199 /* Whether the unsent buffer is freed is for the sanitizer to say */
200 batch_reset(&batch);
201 CHECK_INT(batch_pending(&batch), 0);
202 CHECK_INT(batch.count, 0);
203
204 close(sv[0]);
205 close(sv[1]);
206}
207
208static void case_closed_socket_is_fatal(void) {
209 ot_batch batch;
210 int sv[2];
211
212 memset(&batch, 0, sizeof(batch));
213 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
214 close(sv[1]);
215
216 test_case("writing to a socket whose peer is gone reports a dead connection");
217 CHECK_INT(batch_add(&batch, dup_of("nobody is listening"), 19), 0);
218 CHECK_INT(batch_send(sv[0], &batch), BATCH_FAILED);
219
220 batch_reset(&batch);
221 close(sv[0]);
222}
223
224static void case_vector_is_taken_whole(void) {
225 ot_batch batch;
226 struct iovec *vector = malloc(3 * sizeof(struct iovec));
227 int sv[2];
228 char got[32];
229
230 memset(&batch, 0, sizeof(batch));
231 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
232
233 test_case("a whole vector is queued in one go, in order");
234 vector[0].iov_base = dup_of("one "), vector[0].iov_len = 4;
235 vector[1].iov_base = dup_of("two "), vector[1].iov_len = 4;
236 vector[2].iov_base = dup_of("three"), vector[2].iov_len = 5;
237 CHECK_INT(batch_add_vector(&batch, vector, 3), 0);
238 free(vector);
239 CHECK_INT(batch.count, 3);
240 CHECK_INT(batch_send(sv[0], &batch), 13);
241
242 drain(sv[1], got, 13);
243 CHECK_STR(got, "one two three");
244
245 batch_reset(&batch);
246 close(sv[0]);
247 close(sv[1]);
248}
249
250/* The entry array starts small and doubles, so crossing that boundary one
251 buffer at a time is worth its own case. */
252static void case_growing_one_entry_at_a_time(void) {
253 const size_t entries = 100;
254 ot_batch batch;
255 int sv[2];
256 char sent[400], got[401];
257 size_t i, refused = 0;
258
259 memset(&batch, 0, sizeof(batch));
260 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
261
262 test_case("a batch grown one buffer at a time keeps them all, in order");
263 for (i = 0; i < entries; ++i) {
264 char piece[4];
265
266 piece[0] = (char)('a' + i % 26), piece[1] = (char)('0' + i % 10);
267 piece[2] = (char)('A' + i % 26), piece[3] = '.';
268 memcpy(sent + i * 4, piece, 4);
269 if (batch_add(&batch, memcpy(malloc(4), piece, 4), 4))
270 ++refused;
271 }
272 CHECK_INT(refused, 0);
273 CHECK_INT(batch.count, entries);
274 CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(entries * 4));
275
276 drain(sv[1], got, entries * 4);
277 CHECK_MEM(got, sent, entries * 4);
278
279 batch_reset(&batch);
280 close(sv[0]);
281 close(sv[1]);
282}
283
284/* Appending while earlier entries are still on their way is what a chunked
285 transfer does. Rather than depend on when a kernel decides to block, this
286 takes entries off the front the way a completed send would and checks that
287 the array reuses the space instead of growing. */
288static void case_append_while_sending(void) {
289 const size_t per_round = 12, each = 8;
290 ot_batch batch;
291 int sv[2];
292 char expected[16], got[17];
293 size_t i, round, refused = 0, space_after_first = 0;
294
295 memset(&batch, 0, sizeof(batch));
296 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
297
298 test_case("an array whose front has gone out is reused, not grown");
299 for (round = 0; round < 8; ++round) {
300 for (i = 0; i < per_round; ++i) {
301 /* Distinct per entry, so a move from the wrong index shows up */
302 char *piece = malloc(each);
303
304 memset(piece, (int)('a' + (round * per_round + i) % 26), each);
305 piece[0] = (char)('0' + i);
306 piece[1] = (char)('0' + round);
307 if (batch_add(&batch, piece, each))
308 ++refused;
309 }
310
311 /* All but the last entry have left and been freed */
312 while (batch.count - batch.first > 1)
313 free(batch.entries[batch.first++].iov_base);
314
315 if (!round)
316 space_after_first = batch.space;
317 }
318 CHECK_INT(refused, 0);
319 CHECK_INT(batch.space, space_after_first);
320
321 test_case("and what is still queued survives the move, in order");
322 CHECK_INT(batch.count - batch.first, 1);
323 CHECK_INT(batch_add(&batch, memset(malloc(each), 'Z', each), each), 0);
324 CHECK_INT(batch_send(sv[0], &batch), (ssize_t)(2 * each));
325
326 memset(expected, (int)('a' + (7 * per_round + per_round - 1) % 26), each);
327 expected[0] = (char)('0' + per_round - 1);
328 expected[1] = '7';
329 memset(expected + each, 'Z', each);
330 drain(sv[1], got, 2 * each);
331 CHECK_MEM(got, expected, 2 * each);
332
333 batch_reset(&batch);
334 close(sv[0]);
335 close(sv[1]);
336}
337
338void test_batch(void) {
339 case_queue_accounting();
340 case_send_in_one_go();
341 case_vector_is_taken_whole();
342 case_growing_one_entry_at_a_time();
343 case_append_while_sending();
344 case_partial_write_resumes();
345 case_more_entries_than_one_writev();
346 case_reset_frees_the_unsent();
347 case_closed_socket_is_fatal();
348}
diff --git a/tests/t_mutex.c b/tests/t_mutex.c
new file mode 100644
index 0000000..3307d70
--- /dev/null
+++ b/tests/t_mutex.c
@@ -0,0 +1,107 @@
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_mutex.h"
12#include "test.h"
13
14/* A worker asks for a class of task and blocks until one turns up, so a task
15 whose class matches no worker is never claimed and whoever is waiting for
16 its answer waits forever. These cases pin the matching. */
17
18static void case_task_goes_to_its_own_class(void) {
19 ot_tasktype asked;
20 ot_taskid taskid;
21
22 test_case("a worker is handed the task of the class it asked for");
23 CHECK_INT(mutex_workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0);
24 asked = TASK_FULLSCRAPE;
25 taskid = mutex_workqueue_poptask(&asked);
26 CHECK(taskid != 0);
27
28 test_case("and the full task type, not just its class");
29 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII);
30 mutex_workqueue_canceltask(41);
31}
32
33static void case_flags_survive_the_queue(void) {
34 ot_tasktype asked;
35
36 test_case("flags outside the class mask are carried through");
37 CHECK_INT(mutex_workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0);
38 asked = TASK_FULLSCRAPE;
39 CHECK(mutex_workqueue_poptask(&asked) != 0);
40 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP);
41 mutex_workqueue_canceltask(42);
42}
43
44static void case_classes_do_not_cross(void) {
45 ot_tasktype asked;
46 ot_taskid taskid;
47
48 test_case("a stats worker passes over a fullscrape task");
49 CHECK_INT(mutex_workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0);
50 CHECK_INT(mutex_workqueue_pushtask(44, TASK_STATS_PEERS), 0);
51
52 asked = TASK_STATS;
53 taskid = mutex_workqueue_poptask(&asked);
54 CHECK(taskid != 0);
55 CHECK_INT(asked, TASK_STATS_PEERS);
56
57 test_case("and leaves it for the one that wants it");
58 asked = TASK_FULLSCRAPE;
59 taskid = mutex_workqueue_poptask(&asked);
60 CHECK(taskid != 0);
61 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY);
62
63 mutex_workqueue_canceltask(43);
64 mutex_workqueue_canceltask(44);
65}
66
67static void case_every_task_gets_its_own_id(void) {
68 ot_tasktype asked;
69 ot_taskid first, second;
70
71 test_case("two queued tasks are handed out under different ids");
72 CHECK_INT(mutex_workqueue_pushtask(45, TASK_FULLSCRAPE), 0);
73 CHECK_INT(mutex_workqueue_pushtask(46, TASK_FULLSCRAPE), 0);
74
75 asked = TASK_FULLSCRAPE;
76 first = mutex_workqueue_poptask(&asked);
77 asked = TASK_FULLSCRAPE;
78 second = mutex_workqueue_poptask(&asked);
79 CHECK(first != 0);
80 CHECK(second != 0);
81 CHECK(first != second);
82
83 mutex_workqueue_canceltask(45);
84 mutex_workqueue_canceltask(46);
85}
86
87static void case_cancelled_task_is_not_handed_out(void) {
88 ot_tasktype asked;
89
90 test_case("a cancelled task is gone before any worker sees it");
91 CHECK_INT(mutex_workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0);
92 CHECK_INT(mutex_workqueue_pushtask(48, TASK_FULLSCRAPE), 0);
93 mutex_workqueue_canceltask(47);
94
95 asked = TASK_FULLSCRAPE;
96 CHECK(mutex_workqueue_poptask(&asked) != 0);
97 CHECK_INT(asked, TASK_FULLSCRAPE);
98 mutex_workqueue_canceltask(48);
99}
100
101void test_mutex(void) {
102 case_task_goes_to_its_own_class();
103 case_flags_survive_the_queue();
104 case_classes_do_not_cross();
105 case_every_task_gets_its_own_id();
106 case_cancelled_task_is_not_handed_out();
107}
diff --git a/tests/t_socket.c b/tests/t_socket.c
index bbc9e5e..8eef7fd 100644
--- a/tests/t_socket.c
+++ b/tests/t_socket.c
@@ -91,14 +91,14 @@ static void case_socket_options(void) {
91 int udp, tcp; 91 int udp, tcp;
92 92
93 test_case("a bound udp socket is non blocking and serves both families"); 93 test_case("a bound udp socket is non blocking and serves both families");
94 udp = socket_udp_bind(ip_any, 0); 94 udp = socket_udp_bind(ip_any, 0, SOCKET_EXCLUSIVE);
95 CHECK(udp != -1); 95 CHECK(udp != -1);
96 CHECK_INT(is_nonblocking(udp), 1); 96 CHECK_INT(is_nonblocking(udp), 1);
97 CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0); 97 CHECK_INT(sock_flag(udp, IPPROTO_IPV6, IPV6_V6ONLY), 0);
98 close(udp); 98 close(udp);
99 99
100 test_case("a listening tcp socket is non blocking and serves both families"); 100 test_case("a listening tcp socket is non blocking and serves both families");
101 tcp = socket_tcp_listen(ip_any, 0); 101 tcp = socket_tcp_listen(ip_any, 0, SOCKET_EXCLUSIVE);
102 CHECK(tcp != -1); 102 CHECK(tcp != -1);
103 CHECK_INT(is_nonblocking(tcp), 1); 103 CHECK_INT(is_nonblocking(tcp), 1);
104 CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0); 104 CHECK_INT(sock_flag(tcp, IPPROTO_IPV6, IPV6_V6ONLY), 0);
@@ -106,24 +106,20 @@ static void case_socket_options(void) {
106} 106}
107 107
108#ifdef SO_REUSEPORT 108#ifdef SO_REUSEPORT
109static void case_reuseport_is_opt_in(void) { 109static void case_sharing_is_opt_in(void) {
110 int s; 110 int s;
111 111
112 test_case("reuseport stays off unless the operator asks for it"); 112 test_case("an address is exclusive unless sharing is asked for");
113 g_socket_reuseport = 0; 113 s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE);
114 s = socket_udp_bind(ip_loopback, 0);
115 CHECK(s != -1); 114 CHECK(s != -1);
116 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0); 115 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 0);
117 close(s); 116 close(s);
118 117
119 test_case("reuseport reaches the socket when it is asked for"); 118 test_case("and shared when it is");
120 g_socket_reuseport = 1; 119 s = socket_udp_bind(ip_loopback, 0, SOCKET_SHARED);
121 s = socket_udp_bind(ip_loopback, 0);
122 CHECK(s != -1); 120 CHECK(s != -1);
123 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1); 121 CHECK_INT(sock_flag(s, SOL_SOCKET, SO_REUSEPORT), 1);
124 close(s); 122 close(s);
125
126 g_socket_reuseport = 0;
127} 123}
128#endif 124#endif
129 125
@@ -135,7 +131,7 @@ static void case_udp_roundtrip(void) {
135 int s; 131 int s;
136 132
137 test_case("a datagram comes back with the sender's address and port"); 133 test_case("a datagram comes back with the sender's address and port");
138 s = socket_udp_bind(ip_loopback, 0); 134 s = socket_udp_bind(ip_loopback, 0, SOCKET_EXCLUSIVE);
139 CHECK(s != -1); 135 CHECK(s != -1);
140 bound_address(s, own_ip, &own_port); 136 bound_address(s, own_ip, &own_port);
141 CHECK(own_port != 0); 137 CHECK(own_port != 0);
@@ -158,9 +154,9 @@ static void case_udp_v4mapped(void) {
158 int receiver, sender; 154 int receiver, sender;
159 155
160 test_case("an IPv4 sender is reported in its v4 mapped form"); 156 test_case("an IPv4 sender is reported in its v4 mapped form");
161 receiver = socket_udp_bind(ip_v4_loopback, 0); 157 receiver = socket_udp_bind(ip_v4_loopback, 0, SOCKET_EXCLUSIVE);
162 CHECK(receiver != -1); 158 CHECK(receiver != -1);
163 sender = socket_udp_bind(ip_v4_loopback, 0); 159 sender = socket_udp_bind(ip_v4_loopback, 0, SOCKET_EXCLUSIVE);
164 CHECK(sender != -1); 160 CHECK(sender != -1);
165 bound_address(receiver, receiver_ip, &receiver_port); 161 bound_address(receiver, receiver_ip, &receiver_port);
166 bound_address(sender, sender_ip, &sender_port); 162 bound_address(sender, sender_ip, &sender_port);
@@ -184,7 +180,7 @@ static void case_tcp_roundtrip(void) {
184 int listener, client, accepted; 180 int listener, client, accepted;
185 181
186 test_case("a connection is accepted and carries the caller's address"); 182 test_case("a connection is accepted and carries the caller's address");
187 listener = socket_tcp_listen(ip_loopback, 0); 183 listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE);
188 CHECK(listener != -1); 184 CHECK(listener != -1);
189 bound_address(listener, listener_ip, &listener_port); 185 bound_address(listener, listener_ip, &listener_port);
190 CHECK(listener_port != 0); 186 CHECK(listener_port != 0);
@@ -216,7 +212,7 @@ static void case_connect_refused(void) {
216 212
217 test_case("a refused connection is reported by socket_is_connected"); 213 test_case("a refused connection is reported by socket_is_connected");
218 /* Take a port, then drop it, so nothing is listening on it any more */ 214 /* Take a port, then drop it, so nothing is listening on it any more */
219 listener = socket_tcp_listen(ip_loopback, 0); 215 listener = socket_tcp_listen(ip_loopback, 0, SOCKET_EXCLUSIVE);
220 CHECK(listener != -1); 216 CHECK(listener != -1);
221 bound_address(listener, listener_ip, &port); 217 bound_address(listener, listener_ip, &port);
222 close(listener); 218 close(listener);
@@ -247,8 +243,8 @@ static void case_bind_failure(void) {
247 test_case("binding an address no interface carries fails"); 243 test_case("binding an address no interface carries fails");
248 before = next_descriptor(); 244 before = next_descriptor();
249 CHECK(before != -1); 245 CHECK(before != -1);
250 CHECK_INT(socket_tcp_listen(ip_unassigned, 0), -1); 246 CHECK_INT(socket_tcp_listen(ip_unassigned, 0, SOCKET_EXCLUSIVE), -1);
251 CHECK_INT(socket_udp_bind(ip_unassigned, 0), -1); 247 CHECK_INT(socket_udp_bind(ip_unassigned, 0, SOCKET_EXCLUSIVE), -1);
252 248
253 test_case("a failed bind leaves no socket behind"); 249 test_case("a failed bind leaves no socket behind");
254 CHECK_INT(next_descriptor(), before); 250 CHECK_INT(next_descriptor(), before);
@@ -294,7 +290,7 @@ static void case_mcast_sender(void) {
294void test_socket(void) { 290void test_socket(void) {
295 case_socket_options(); 291 case_socket_options();
296#ifdef SO_REUSEPORT 292#ifdef SO_REUSEPORT
297 case_reuseport_is_opt_in(); 293 case_sharing_is_opt_in();
298#endif 294#endif
299 case_udp_roundtrip(); 295 case_udp_roundtrip();
300 case_udp_v4mapped(); 296 case_udp_v4mapped();
diff --git a/tests/t_vector.c b/tests/t_vector.c
index fee336b..a8186b2 100644
--- a/tests/t_vector.c
+++ b/tests/t_vector.c
@@ -4,10 +4,12 @@
4 $id$ */ 4 $id$ */
5 5
6/* System */ 6/* System */
7#include <stdint.h>
7#include <stdlib.h> 8#include <stdlib.h>
8#include <string.h> 9#include <string.h>
9 10
10/* Opentracker */ 11/* Opentracker */
12#include "ot_vector.h"
11#include "trackerlogic.h" 13#include "trackerlogic.h"
12#include "test.h" 14#include "test.h"
13 15
@@ -149,6 +151,105 @@ static void case_insert_compare_prefix(void) {
149 free(vector.data); 151 free(vector.data);
150} 152}
151 153
154/* ------------------------------------------------- bucket redistribution */
155
156/* A flat peer list of count distinct v4 peers. The low byte of the index goes
157 into the first address byte, so the peers are distinct but arrive in no
158 particular order: a list generated in ascending order would come back
159 sorted whether or not vector_redistribute_buckets sorted it. */
160static void build_flat_peerlist(ot_peerlist *peer_list, size_t count) {
161 size_t i;
162
163 memset(peer_list, 0, sizeof(*peer_list));
164 peer_list->peers.data = malloc(count * OT_PEER_SIZE4);
165 peer_list->peers.size = count;
166 peer_list->peers.space = count;
167 peer_list->peer_count = count;
168
169 for (i = 0; i < count; ++i) {
170 uint8_t *peer = (uint8_t *)peer_list->peers.data + i * OT_PEER_SIZE4;
171
172 memset(peer, 0, OT_PEER_SIZE4);
173 peer[0] = (uint8_t)i;
174 peer[1] = (uint8_t)(i >> 8);
175 peer[2] = (uint8_t)(i >> 16);
176 peer[3] = 1;
177 }
178}
179
180static void case_redistribute_holds_every_peer(void) {
181 const size_t count = 513;
182 ot_peerlist peer_list;
183 size_t bucket, total_size = 0, empty = 0, largest = 0;
184 ot_vector *buckets;
185
186 test_case("a peer list past the threshold is split into buckets");
187 build_flat_peerlist(&peer_list, count);
188 CHECK(peer_list.peers.data != NULL);
189 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4);
190 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1);
191 CHECK_INT(peer_list.peers.size, 16);
192
193 buckets = peer_list.peers.data;
194 for (bucket = 0; bucket < peer_list.peers.size; ++bucket) {
195 total_size += buckets[bucket].size;
196 if (!buckets[bucket].size)
197 ++empty;
198 if (buckets[bucket].size > largest)
199 largest = buckets[bucket].size;
200 }
201
202 test_case("no peer is lost on the way");
203 CHECK_INT(total_size, count);
204
205 /* The point of the exercise is that a lookup only searches one bucket, so
206 a hash that piled everything into one would defeat it. The input and the
207 hash are both fixed, so this is not a matter of luck. */
208 test_case("and they are spread over the buckets rather than piled up");
209 CHECK_INT(empty, 0);
210 CHECK(largest < count / 2);
211
212 vector_clean_list(peer_list.peers.data, peer_list.peers.size);
213}
214
215static void case_redistribute_sorts_each_bucket(void) {
216 const size_t count = 1000;
217 ot_peerlist peer_list;
218 size_t bucket, i, out_of_order = 0;
219 ot_vector *buckets;
220
221 test_case("every bucket comes back sorted, so bsearch can be used on it");
222 build_flat_peerlist(&peer_list, count);
223 CHECK(peer_list.peers.data != NULL);
224 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4);
225 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 1);
226
227 buckets = peer_list.peers.data;
228 for (bucket = 0; bucket < peer_list.peers.size; ++bucket)
229 for (i = 1; i < buckets[bucket].size; ++i) {
230 const uint8_t *prev = (uint8_t *)buckets[bucket].data + (i - 1) * OT_PEER_SIZE4;
231 const uint8_t *next = (uint8_t *)buckets[bucket].data + i * OT_PEER_SIZE4;
232
233 if (memcmp(prev, next, OT_PEER_COMPARE_SIZE4) >= 0)
234 ++out_of_order;
235 }
236 CHECK_INT(out_of_order, 0);
237
238 vector_clean_list(peer_list.peers.data, peer_list.peers.size);
239}
240
241static void case_redistribute_below_threshold(void) {
242 ot_peerlist peer_list;
243
244 test_case("a small peer list is left flat");
245 build_flat_peerlist(&peer_list, 100);
246 CHECK(peer_list.peers.data != NULL);
247 vector_redistribute_buckets(&peer_list, OT_PEER_SIZE4);
248 CHECK_INT(OT_PEERLIST_HASBUCKETS(&peer_list), 0);
249 CHECK_INT(peer_list.peers.size, 100);
250 free(peer_list.peers.data);
251}
252
152void test_vector(void) { 253void test_vector(void) {
153 case_search_finds_member(); 254 case_search_finds_member();
154 case_search_finds_first_and_last(); 255 case_search_finds_first_and_last();
@@ -158,4 +259,7 @@ void test_vector(void) {
158 case_insert_keeps_order(); 259 case_insert_keeps_order();
159 case_insert_finds_existing(); 260 case_insert_finds_existing();
160 case_insert_compare_prefix(); 261 case_insert_compare_prefix();
262 case_redistribute_holds_every_peer();
263 case_redistribute_sorts_each_bucket();
264 case_redistribute_below_threshold();
161} 265}
diff --git a/tests/test.h b/tests/test.h
index d79c4d2..fd574c2 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,5 +86,7 @@ void test_scan(void);
86void test_buf(void); 86void test_buf(void);
87void test_ip(void); 87void test_ip(void);
88void test_socket(void); 88void test_socket(void);
89void test_mutex(void);
90void test_batch(void);
89 91
90#endif 92#endif