summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/test_dynamic_accesslist.py50
-rw-r--r--tests/runner.c2
-rw-r--r--tests/stubs.c33
-rw-r--r--tests/t_mutex.c138
-rw-r--r--tests/t_trackerlogic.c243
-rw-r--r--tests/test.h2
6 files changed, 465 insertions, 3 deletions
diff --git a/tests/integration/test_dynamic_accesslist.py b/tests/integration/test_dynamic_accesslist.py
new file mode 100644
index 0000000..7fa5fde
--- /dev/null
+++ b/tests/integration/test_dynamic_accesslist.py
@@ -0,0 +1,50 @@
1"""Hashes added to and removed from the blacklist while the tracker runs.
2
3The two FIFOs are read by threads of their own, which is also what makes
4this the test that drives those threads through a shutdown.
5"""
6
7import os
8import time
9import unittest
10
11from harness import TrackerTestCase
12
13BLOCKED = bytes(range(1, 21))
14
15
16def as_hex(info_hash):
17 return "".join("%02x" % b for b in info_hash)
18
19
20class DynamicAccesslist(TrackerTestCase):
21 config = ("access.blacklist {dir}/blacklist\n"
22 "access.fifo_add {dir}/add.fifo\n"
23 "access.fifo_delete {dir}/delete.fifo\n")
24 files = {"blacklist": b""}
25
26 def feed(self, name, info_hash):
27 """Writing to the FIFO blocks until the reader has it, so this also
28 tells us the thread is alive."""
29 with open(os.path.join(self.tracker.dir, name), "w") as fifo:
30 fifo.write("%s added by a test\n" % as_hex(info_hash))
31
32 def wait_for(self, info_hash, blocked, patience=5.0):
33 """The reader thread picks the hash up on its own schedule, so this
34 waits for the answer to change rather than assuming it already has."""
35 deadline = time.time() + patience
36 while time.time() < deadline:
37 answer = self.tracker.announce(info_hash)
38 if (b"failure reason" in answer) == blocked:
39 return True
40 time.sleep(0.05)
41 return False
42
43 def test_a_hash_can_be_blocked_and_released_while_running(self):
44 self.assertNotIn(b"failure reason", self.tracker.announce(BLOCKED))
45
46 self.feed("add.fifo", BLOCKED)
47 self.assertTrue(self.wait_for(BLOCKED, blocked=True), "the hash was never blocked")
48
49 self.feed("delete.fifo", BLOCKED)
50 self.assertTrue(self.wait_for(BLOCKED, blocked=False), "the hash was never released")
diff --git a/tests/runner.c b/tests/runner.c
index 38bfb86..97c6d51 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -27,6 +27,8 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
27 {"ot_buf", test_buf}, 27 {"ot_buf", test_buf},
28 {"ot_ip", test_ip}, 28 {"ot_ip", test_ip},
29 {"ot_socket", test_socket}, 29 {"ot_socket", test_socket},
30 {"ot_mutex", test_mutex},
31 {"trackerlogic", test_trackerlogic},
30 {"ot_workqueue", test_workqueue}, 32 {"ot_workqueue", test_workqueue},
31 {"ot_batch", test_batch}, 33 {"ot_batch", test_batch},
32 {"ot_fdstate", test_fdstate}, 34 {"ot_fdstate", test_fdstate},
diff --git a/tests/stubs.c b/tests/stubs.c
index a8e6962..5999e2d 100644
--- a/tests/stubs.c
+++ b/tests/stubs.c
@@ -8,11 +8,9 @@
8 behaviour should be supplied here rather than the test weakened. */ 8 behaviour should be supplied here rather than the test weakened. */
9 9
10/* Opentracker */ 10/* Opentracker */
11#include "ot_stats.h"
11#include "trackerlogic.h" 12#include "trackerlogic.h"
12 13
13/* ot_vector.c calls this from vector_clean_list, which no test drives yet. */
14void free_peerlist(ot_peerlist *peer_list) { (void)peer_list; }
15
16/* ot_accesslist.c is built with WANT_ACCESSLIST_BLACK so that the hash parser 14/* 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, 15 is reachable. These two belong to accesslist_readfile and the reload worker,
18 neither of which any test drives. */ 16 neither of which any test drives. */
@@ -26,3 +24,32 @@ long io_trywrite(long d, const char *buf, long len) {
26 (void)buf; 24 (void)buf;
27 return len; 25 return len;
28} 26}
27
28/* trackerlogic.c is under test, and starting it up starts every module it
29 leans on. None of them is driven by these tests, so they are answered here
30 rather than linked in and left to run threads of their own. */
31void clean_init(void) {}
32void clean_deinit(void) {}
33int clean_single_torrent(ot_torrent *torrent) {
34 (void)torrent;
35 return 0;
36}
37void fullscrape_init(void) {}
38void fullscrape_deinit(void) {}
39void livesync_init(void) {}
40void livesync_deinit(void) {}
41void livesync_tell(struct ot_workstruct *ws) { (void)ws; }
42void stats_init(void) {}
43void stats_deinit(void) {}
44void stats_issue_event(struct ot_workstruct *ws, ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) {
45 (void)ws;
46 (void)event;
47 (void)proto;
48 (void)event_data;
49}
50void stats_counters_register(struct ot_workstruct *ws) { (void)ws; }
51void stats_counters_retire(struct ot_workstruct *ws) { (void)ws; }
52
53const char *g_stats_path;
54ssize_t g_stats_path_len;
55uint32_t g_tracker_id;
diff --git a/tests/t_mutex.c b/tests/t_mutex.c
new file mode 100644
index 0000000..7c1463b
--- /dev/null
+++ b/tests/t_mutex.c
@@ -0,0 +1,138 @@
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 <pthread.h>
8#include <stdatomic.h>
9#include <string.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_mutex.h"
14#include "test.h"
15
16/* Every announce passes through here, and what it has to get right is which
17 bucket a hash belongs to and that two threads never hold one bucket at
18 once. Both are checked against real mutexes rather than a stand-in. */
19
20/* A hash that lands in a chosen bucket: the bucket is the top
21 OT_BUCKET_COUNT_BITS of the first four bytes, read in network order */
22static void hash_for_bucket(ot_hash hash, int bucket) {
23 memset(hash, 0, sizeof(ot_hash));
24 hash[0] = (uint8_t)(bucket >> (OT_BUCKET_COUNT_BITS - 8));
25 hash[1] = (uint8_t)((bucket << (16 - OT_BUCKET_COUNT_BITS)) & 0xff);
26}
27
28static void case_a_hash_names_its_bucket(void) {
29 static const int buckets[] = {0, 1, 2, 17, 511, 512, OT_BUCKET_COUNT - 1};
30 size_t i;
31
32 test_case("a hash is served by the bucket its leading bits name");
33 for (i = 0; i < sizeof(buckets) / sizeof(buckets[0]); ++i) {
34 ot_hash hash;
35 ot_vector *by_hash, *by_number;
36
37 hash_for_bucket(hash, buckets[i]);
38 by_hash = mutex_bucket_lock_by_hash(hash);
39 mutex_bucket_unlock_by_hash(hash, 0);
40
41 by_number = mutex_bucket_lock(buckets[i]);
42 mutex_bucket_unlock(buckets[i], 0);
43
44 CHECK(by_hash == by_number);
45 }
46}
47
48static void case_every_bucket_is_its_own(void) {
49 ot_vector *first, *last, *middle;
50
51 test_case("no two buckets are the same one");
52 first = mutex_bucket_lock(0);
53 mutex_bucket_unlock(0, 0);
54 middle = mutex_bucket_lock(OT_BUCKET_COUNT / 2);
55 mutex_bucket_unlock(OT_BUCKET_COUNT / 2, 0);
56 last = mutex_bucket_lock(OT_BUCKET_COUNT - 1);
57 mutex_bucket_unlock(OT_BUCKET_COUNT - 1, 0);
58
59 CHECK(first != middle);
60 CHECK(middle != last);
61 CHECK(first != last);
62}
63
64static void case_the_count_follows_what_unlock_is_told(void) {
65 size_t before = mutex_get_torrent_count();
66
67 test_case("the torrent count moves by what unlocking reports, and not otherwise");
68 mutex_bucket_lock(3);
69 mutex_bucket_unlock(3, 0);
70 CHECK_INT((int)(mutex_get_torrent_count() - before), 0);
71
72 mutex_bucket_lock(3);
73 mutex_bucket_unlock(3, 5);
74 CHECK_INT((int)(mutex_get_torrent_count() - before), 5);
75
76 mutex_bucket_lock(3);
77 mutex_bucket_unlock(3, -2);
78 CHECK_INT((int)(mutex_get_torrent_count() - before), 3);
79}
80
81static _Atomic int g_other_got_in;
82
83static void *take_a_bucket(void *args) {
84 int bucket = (int)(intptr_t)args;
85
86 mutex_bucket_lock(bucket);
87 atomic_store(&g_other_got_in, 1);
88 mutex_bucket_unlock(bucket, 0);
89 return NULL;
90}
91
92static int waited_for_entry(int patience_ms) {
93 int waited;
94
95 for (waited = 0; waited < patience_ms && !atomic_load(&g_other_got_in); ++waited)
96 usleep(1000);
97 return atomic_load(&g_other_got_in);
98}
99
100static void case_a_held_bucket_keeps_others_out(void) {
101 pthread_t other;
102
103 test_case("a second thread waits for the bucket this one holds");
104 atomic_store(&g_other_got_in, 0);
105 mutex_bucket_lock(9);
106 CHECK_INT(pthread_create(&other, NULL, take_a_bucket, (void *)(intptr_t)9), 0);
107
108 CHECK_INT(waited_for_entry(200), 0);
109
110 mutex_bucket_unlock(9, 0);
111 CHECK_INT(waited_for_entry(2000), 1);
112 pthread_join(other, NULL);
113}
114
115static void case_another_bucket_is_free_meanwhile(void) {
116 pthread_t other;
117
118 test_case("and a different bucket is not held up by it");
119 atomic_store(&g_other_got_in, 0);
120 mutex_bucket_lock(9);
121 CHECK_INT(pthread_create(&other, NULL, take_a_bucket, (void *)(intptr_t)10), 0);
122
123 CHECK_INT(waited_for_entry(2000), 1);
124 pthread_join(other, NULL);
125 mutex_bucket_unlock(9, 0);
126}
127
128void test_mutex(void) {
129 mutex_init();
130
131 case_a_hash_names_its_bucket();
132 case_every_bucket_is_its_own();
133 case_the_count_follows_what_unlock_is_told();
134 case_a_held_bucket_keeps_others_out();
135 case_another_bucket_is_free_meanwhile();
136
137 mutex_deinit();
138}
diff --git a/tests/t_trackerlogic.c b/tests/t_trackerlogic.c
new file mode 100644
index 0000000..d3d8755
--- /dev/null
+++ b/tests/t_trackerlogic.c
@@ -0,0 +1,243 @@
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 <arpa/inet.h>
8#include <signal.h>
9#include <pthread.h>
10#include <string.h>
11
12/* Opentracker */
13#include "ot_mutex.h"
14#include "trackerlogic.h"
15#include "test.h"
16
17/* The swarm itself: what an announce does to a torrent, and what a scrape
18 says about it afterwards. The counts are read back through the udp scrape
19 because that is the one reply the tracker builds as plain numbers. */
20
21static struct ot_workstruct ws;
22
23static void hash_named(ot_hash hash, const char *name) {
24 memset(hash, 0, sizeof(ot_hash));
25 memcpy(hash, name, strlen(name) < sizeof(ot_hash) ? strlen(name) : sizeof(ot_hash));
26}
27
28/* An IPv4 peer arrives as the v4 mapped form the tracker stores */
29static void peer_v4(ot_peer6 peer, uint8_t last, uint16_t port) {
30 ot_ip6 ip = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 10, 0, 0, 0};
31 uint16_t net_port = htons(port);
32
33 ip[15] = last;
34 memset(peer, 0, sizeof(ot_peer6));
35 OT_SETIP(peer, ip);
36 OT_SETPORT(peer, &net_port);
37 OT_PEERFLAG(peer) = 0;
38}
39
40static void peer_v6(ot_peer6 peer, uint8_t last, uint16_t port) {
41 ot_ip6 ip = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
42 uint16_t net_port = htons(port);
43
44 ip[15] = last;
45 memset(peer, 0, sizeof(ot_peer6));
46 OT_SETIP(peer, ip);
47 OT_SETPORT(peer, &net_port);
48 OT_PEERFLAG(peer) = 0;
49}
50
51static void announce(ot_hash *hash, ot_peer6 peer) {
52 ws.hash = hash;
53 memcpy(ws.peer, peer, sizeof(ot_peer6));
54 ws.reply = ws.outbuf;
55 add_peer_to_torrent_and_return_peers(FLAG_TCP, &ws, 50);
56}
57
58/* seeders, downloads completed, leechers */
59static void scrape(ot_hash hash, uint32_t counts[3]) {
60 char reply[12];
61
62 return_udp_scrape_for_torrent(hash, reply);
63 counts[0] = ntohl(((uint32_t *)reply)[0]);
64 counts[1] = ntohl(((uint32_t *)reply)[1]);
65 counts[2] = ntohl(((uint32_t *)reply)[2]);
66}
67
68static void case_an_announce_makes_a_torrent(void) {
69 ot_hash hash;
70 ot_peer6 peer;
71 uint32_t counts[3];
72 size_t before = mutex_get_torrent_count();
73
74 test_case("the first announce for a hash creates its torrent");
75 hash_named(hash, "first-announce");
76 peer_v4(peer, 1, 6881);
77 announce(&hash, peer);
78
79 CHECK_INT((int)(mutex_get_torrent_count() - before), 1);
80 scrape(hash, counts);
81 CHECK_INT((int)counts[2], 1); /* one leecher */
82 CHECK_INT((int)counts[0], 0);
83}
84
85static void case_the_same_peer_twice_is_one_peer(void) {
86 ot_hash hash;
87 ot_peer6 peer;
88 uint32_t counts[3];
89
90 test_case("announcing twice renews a peer rather than adding another");
91 hash_named(hash, "renewing");
92 peer_v4(peer, 2, 6881);
93 announce(&hash, peer);
94 announce(&hash, peer);
95
96 scrape(hash, counts);
97 CHECK_INT((int)counts[2], 1);
98}
99
100static void case_a_port_makes_a_different_peer(void) {
101 ot_hash hash;
102 ot_peer6 peer;
103 uint32_t counts[3];
104
105 test_case("the same address on another port is another peer");
106 hash_named(hash, "two-ports");
107 peer_v4(peer, 3, 6881);
108 announce(&hash, peer);
109 peer_v4(peer, 3, 6882);
110 announce(&hash, peer);
111
112 scrape(hash, counts);
113 CHECK_INT((int)counts[2], 2);
114}
115
116static void case_seeding_is_counted_apart(void) {
117 ot_hash hash;
118 ot_peer6 peer;
119 uint32_t counts[3];
120
121 test_case("a seeder counts as complete, a leecher as incomplete");
122 hash_named(hash, "seed-and-leech");
123 peer_v4(peer, 4, 6881);
124 OT_PEERFLAG(peer) |= PEER_FLAG_SEEDING;
125 announce(&hash, peer);
126 peer_v4(peer, 5, 6881);
127 announce(&hash, peer);
128
129 scrape(hash, counts);
130 CHECK_INT((int)counts[0], 1);
131 CHECK_INT((int)counts[2], 1);
132}
133
134static void case_completion_without_seeding_is_not_believed(void) {
135 ot_hash hash;
136 ot_peer6 peer;
137 uint32_t counts[3];
138
139 test_case("a peer that claims to have completed but is still leeching is not counted");
140 hash_named(hash, "claiming");
141 peer_v4(peer, 6, 6881);
142 OT_PEERFLAG(peer) |= PEER_FLAG_COMPLETED;
143 announce(&hash, peer);
144
145 scrape(hash, counts);
146 CHECK_INT((int)counts[1], 0);
147 CHECK_INT((int)counts[2], 1); /* still a leecher */
148}
149
150static void case_completing_is_counted_once(void) {
151 ot_hash hash;
152 ot_peer6 peer;
153 uint32_t counts[3];
154
155 test_case("a seeder reporting completion is counted once, however often it says so");
156 hash_named(hash, "completing");
157 peer_v4(peer, 7, 6881);
158 OT_PEERFLAG(peer) |= PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING;
159 announce(&hash, peer);
160 announce(&hash, peer);
161 announce(&hash, peer);
162
163 scrape(hash, counts);
164 CHECK_INT((int)counts[1], 1);
165 CHECK_INT((int)counts[0], 1);
166}
167
168static void case_a_peer_can_leave(void) {
169 ot_hash hash;
170 ot_peer6 peer;
171 uint32_t counts[3];
172
173 test_case("a peer that stops is gone from the swarm");
174 hash_named(hash, "leaving");
175 peer_v4(peer, 7, 6881);
176 announce(&hash, peer);
177 peer_v4(peer, 8, 6881);
178 announce(&hash, peer);
179 scrape(hash, counts);
180 CHECK_INT((int)counts[2], 2);
181
182 ws.hash = &hash;
183 memcpy(ws.peer, peer, sizeof(ot_peer6));
184 remove_peer_from_torrent(FLAG_TCP, &ws);
185
186 scrape(hash, counts);
187 CHECK_INT((int)counts[2], 1);
188}
189
190static void case_the_families_are_kept_apart(void) {
191 ot_hash hash;
192 ot_peer6 peer;
193 uint32_t counts[3];
194
195 test_case("a v4 and a v6 peer of one swarm are both counted");
196 hash_named(hash, "dual-stack");
197 peer_v4(peer, 9, 6881);
198 announce(&hash, peer);
199 peer_v6(peer, 9, 6881);
200 announce(&hash, peer);
201
202 scrape(hash, counts);
203 CHECK_INT((int)counts[2], 2);
204}
205
206static void case_a_hash_nobody_announced(void) {
207 ot_hash hash;
208 uint32_t counts[3];
209
210 test_case("a hash nobody has announced scrapes as nothing at all");
211 hash_named(hash, "never-seen");
212 scrape(hash, counts);
213 CHECK_INT((int)counts[0], 0);
214 CHECK_INT((int)counts[1], 0);
215 CHECK_INT((int)counts[2], 0);
216}
217
218void test_trackerlogic(void) {
219 sigset_t signal_mask;
220
221 /* accesslist_init starts a thread that waits for SIGHUP and is stopped by
222 being sent one, so the signal has to be blocked here as the tracker
223 blocks it, or stopping the tracker logic ends this process instead */
224 sigemptyset(&signal_mask);
225 sigaddset(&signal_mask, SIGHUP);
226 pthread_sigmask(SIG_BLOCK, &signal_mask, NULL);
227
228 trackerlogic_init();
229 CHECK_INT(workstruct_init(&ws), 0);
230
231 case_an_announce_makes_a_torrent();
232 case_the_same_peer_twice_is_one_peer();
233 case_a_port_makes_a_different_peer();
234 case_seeding_is_counted_apart();
235 case_completion_without_seeding_is_not_believed();
236 case_completing_is_counted_once();
237 case_a_peer_can_leave();
238 case_the_families_are_kept_apart();
239 case_a_hash_nobody_announced();
240
241 workstruct_deinit(&ws);
242 trackerlogic_deinit();
243}
diff --git a/tests/test.h b/tests/test.h
index d58742c..d7296cc 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,6 +86,8 @@ 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_trackerlogic(void);
89void test_workqueue(void); 91void test_workqueue(void);
90void test_batch(void); 92void test_batch(void);
91void test_fdstate(void); 93void test_fdstate(void);