diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-11-01 01:26:05 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-11-01 01:26:05 +0100 |
| commit | b6a4741a15371882a477dbc9a684faf6b39ec000 (patch) | |
| tree | 3dae962280d7676103af8a6c974e0e10f0234dc7 /tests | |
| parent | 6038e24d2ba3991dcb3a2295982382a80f193448 (diff) | |
Add tests for the ot_mutex code, it didn't have any until now
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/runner.c | 1 | ||||
| -rw-r--r-- | tests/t_mutex.c | 138 | ||||
| -rw-r--r-- | tests/test.h | 1 |
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 */ | ||
| 22 | static 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 | |||
| 28 | static 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 | |||
| 48 | static 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 | |||
| 64 | static 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 | |||
| 81 | static _Atomic int g_other_got_in; | ||
| 82 | |||
| 83 | static 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 | |||
| 92 | static 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 | |||
| 100 | static 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 | |||
| 115 | static 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 | |||
| 128 | void 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); | |||
| 86 | void test_buf(void); | 86 | void test_buf(void); |
| 87 | void test_ip(void); | 87 | void test_ip(void); |
| 88 | void test_socket(void); | 88 | void test_socket(void); |
| 89 | void test_mutex(void); | ||
| 89 | void test_workqueue(void); | 90 | void test_workqueue(void); |
| 90 | void test_batch(void); | 91 | void test_batch(void); |
| 91 | void test_fdstate(void); | 92 | void test_fdstate(void); |
