summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-01 01:26:05 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-01 01:26:05 +0100
commitb6a4741a15371882a477dbc9a684faf6b39ec000 (patch)
tree3dae962280d7676103af8a6c974e0e10f0234dc7 /tests
parent6038e24d2ba3991dcb3a2295982382a80f193448 (diff)
Add tests for the ot_mutex code, it didn't have any until now
Diffstat (limited to 'tests')
-rw-r--r--tests/runner.c1
-rw-r--r--tests/t_mutex.c138
-rw-r--r--tests/test.h1
3 files changed, 140 insertions, 0 deletions
diff --git a/tests/runner.c b/tests/runner.c
index 38bfb86..dcb009c 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -27,6 +27,7 @@ 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},
30 {"ot_workqueue", test_workqueue}, 31 {"ot_workqueue", test_workqueue},
31 {"ot_batch", test_batch}, 32 {"ot_batch", test_batch},
32 {"ot_fdstate", test_fdstate}, 33 {"ot_fdstate", test_fdstate},
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/test.h b/tests/test.h
index d58742c..7582dab 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,6 +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);
89void test_workqueue(void); 90void test_workqueue(void);
90void test_batch(void); 91void test_batch(void);
91void test_fdstate(void); 92void test_fdstate(void);