summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/test_tcpworkers.py67
-rw-r--r--tests/runner.c2
-rw-r--r--tests/t_workqueue.c (renamed from tests/t_mutex.c)50
-rw-r--r--tests/test.h2
4 files changed, 94 insertions, 27 deletions
diff --git a/tests/integration/test_tcpworkers.py b/tests/integration/test_tcpworkers.py
new file mode 100644
index 0000000..e43612a
--- /dev/null
+++ b/tests/integration/test_tcpworkers.py
@@ -0,0 +1,67 @@
1"""The tracker with its tcp side handed to worker threads.
2
3With workers configured, a connection is accepted and answered on a worker's
4own stack. Anything it cannot finish there is copied to the heap and handed
5to the event loop, which is the one place where a worker and the main thread
6touch the same structures, so these drive all three outcomes: answered
7inline, held open, and parked waiting for another thread entirely.
8"""
9
10import threading
11import unittest
12import urllib.parse
13
14from harness import TrackerTestCase, bdecode
15
16WORKERS = 4
17
18
19class TcpWorkers(TrackerTestCase):
20 config = "listen.tcp.workers %d\n" % WORKERS
21
22 def announce_path(self, name, port=6881):
23 return "/announce?info_hash=%s&peer_id=%s&port=%d&left=0" % (
24 urllib.parse.quote_from_bytes(self.hash_for(name), safe=""),
25 "x" * 20,
26 port,
27 )
28
29 def test_many_at_once_are_all_answered(self):
30 """Every worker gets something to do, and the swarm adds up."""
31 info_hash = self.hash_for("workers-swarm")
32 errors = []
33
34 def announce(index):
35 try:
36 self.tracker.announce(info_hash, peer_id=b"-OT0001-%012d" % index, port=7000 + index, left=0)
37 except Exception as problem: # noqa: BLE001 - reported, not swallowed
38 errors.append("%d: %r" % (index, problem))
39
40 threads = [threading.Thread(target=announce, args=(i,)) for i in range(32)]
41 for thread in threads:
42 thread.start()
43 for thread in threads:
44 thread.join()
45
46 self.assertEqual(errors, [], "\n".join(errors))
47 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"complete"], 32)
48
49 def test_a_kept_connection_outlives_the_worker_that_took_it(self):
50 """Keep-alive is the plain case of a connection a worker hands on."""
51 connection = self.tracker.connect()
52 try:
53 for i in range(4):
54 connection.send(self.announce_path("workers-kept"))
55 status, _, body = connection.reply()
56 self.assertIn(b"200", status)
57 self.assertIn(b"complete", bdecode(body))
58 finally:
59 connection.close()
60
61 def test_a_full_scrape_asked_for_by_a_worker_still_arrives(self):
62 """The reply is built by another thread, so the worker parks the
63 connection waiting for nothing at all before handing it over."""
64 for i in range(20):
65 self.tracker.announce(bytes([i]) + b"\0" * 19, port=7100 + i, left=0)
66 body = self.tracker.get("/scrape")
67 self.assertEqual(len(bdecode(body)[b"files"]), 20)
diff --git a/tests/runner.c b/tests/runner.c
index 71a5e2a..38bfb86 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -27,7 +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_batch", test_batch}, 31 {"ot_batch", test_batch},
32 {"ot_fdstate", test_fdstate}, 32 {"ot_fdstate", test_fdstate},
33 {"ot_event", test_event}, 33 {"ot_event", test_event},
diff --git a/tests/t_mutex.c b/tests/t_workqueue.c
index 3307d70..13e0b2e 100644
--- a/tests/t_mutex.c
+++ b/tests/t_workqueue.c
@@ -8,7 +8,7 @@
8#include <string.h> 8#include <string.h>
9 9
10/* Opentracker */ 10/* Opentracker */
11#include "ot_mutex.h" 11#include "ot_workqueue.h"
12#include "test.h" 12#include "test.h"
13 13
14/* A worker asks for a class of task and blocks until one turns up, so a task 14/* A worker asks for a class of task and blocks until one turns up, so a task
@@ -20,25 +20,25 @@ static void case_task_goes_to_its_own_class(void) {
20 ot_taskid taskid; 20 ot_taskid taskid;
21 21
22 test_case("a worker is handed the task of the class it asked for"); 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); 23 CHECK_INT(workqueue_pushtask(41, TASK_FULLSCRAPE_TPB_ASCII), 0);
24 asked = TASK_FULLSCRAPE; 24 asked = TASK_FULLSCRAPE;
25 taskid = mutex_workqueue_poptask(&asked); 25 taskid = workqueue_poptask(&asked);
26 CHECK(taskid != 0); 26 CHECK(taskid != 0);
27 27
28 test_case("and the full task type, not just its class"); 28 test_case("and the full task type, not just its class");
29 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII); 29 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_ASCII);
30 mutex_workqueue_canceltask(41); 30 workqueue_canceltask(41);
31} 31}
32 32
33static void case_flags_survive_the_queue(void) { 33static void case_flags_survive_the_queue(void) {
34 ot_tasktype asked; 34 ot_tasktype asked;
35 35
36 test_case("flags outside the class mask are carried through"); 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); 37 CHECK_INT(workqueue_pushtask(42, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP), 0);
38 asked = TASK_FULLSCRAPE; 38 asked = TASK_FULLSCRAPE;
39 CHECK(mutex_workqueue_poptask(&asked) != 0); 39 CHECK(workqueue_poptask(&asked) != 0);
40 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP); 40 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_URLENCODED | TASK_FLAG_GZIP);
41 mutex_workqueue_canceltask(42); 41 workqueue_canceltask(42);
42} 42}
43 43
44static void case_classes_do_not_cross(void) { 44static void case_classes_do_not_cross(void) {
@@ -46,22 +46,22 @@ static void case_classes_do_not_cross(void) {
46 ot_taskid taskid; 46 ot_taskid taskid;
47 47
48 test_case("a stats worker passes over a fullscrape task"); 48 test_case("a stats worker passes over a fullscrape task");
49 CHECK_INT(mutex_workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0); 49 CHECK_INT(workqueue_pushtask(43, TASK_FULLSCRAPE_TPB_BINARY), 0);
50 CHECK_INT(mutex_workqueue_pushtask(44, TASK_STATS_PEERS), 0); 50 CHECK_INT(workqueue_pushtask(44, TASK_STATS_PEERS), 0);
51 51
52 asked = TASK_STATS; 52 asked = TASK_STATS;
53 taskid = mutex_workqueue_poptask(&asked); 53 taskid = workqueue_poptask(&asked);
54 CHECK(taskid != 0); 54 CHECK(taskid != 0);
55 CHECK_INT(asked, TASK_STATS_PEERS); 55 CHECK_INT(asked, TASK_STATS_PEERS);
56 56
57 test_case("and leaves it for the one that wants it"); 57 test_case("and leaves it for the one that wants it");
58 asked = TASK_FULLSCRAPE; 58 asked = TASK_FULLSCRAPE;
59 taskid = mutex_workqueue_poptask(&asked); 59 taskid = workqueue_poptask(&asked);
60 CHECK(taskid != 0); 60 CHECK(taskid != 0);
61 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY); 61 CHECK_INT(asked, TASK_FULLSCRAPE_TPB_BINARY);
62 62
63 mutex_workqueue_canceltask(43); 63 workqueue_canceltask(43);
64 mutex_workqueue_canceltask(44); 64 workqueue_canceltask(44);
65} 65}
66 66
67static void case_every_task_gets_its_own_id(void) { 67static void case_every_task_gets_its_own_id(void) {
@@ -69,36 +69,36 @@ static void case_every_task_gets_its_own_id(void) {
69 ot_taskid first, second; 69 ot_taskid first, second;
70 70
71 test_case("two queued tasks are handed out under different ids"); 71 test_case("two queued tasks are handed out under different ids");
72 CHECK_INT(mutex_workqueue_pushtask(45, TASK_FULLSCRAPE), 0); 72 CHECK_INT(workqueue_pushtask(45, TASK_FULLSCRAPE), 0);
73 CHECK_INT(mutex_workqueue_pushtask(46, TASK_FULLSCRAPE), 0); 73 CHECK_INT(workqueue_pushtask(46, TASK_FULLSCRAPE), 0);
74 74
75 asked = TASK_FULLSCRAPE; 75 asked = TASK_FULLSCRAPE;
76 first = mutex_workqueue_poptask(&asked); 76 first = workqueue_poptask(&asked);
77 asked = TASK_FULLSCRAPE; 77 asked = TASK_FULLSCRAPE;
78 second = mutex_workqueue_poptask(&asked); 78 second = workqueue_poptask(&asked);
79 CHECK(first != 0); 79 CHECK(first != 0);
80 CHECK(second != 0); 80 CHECK(second != 0);
81 CHECK(first != second); 81 CHECK(first != second);
82 82
83 mutex_workqueue_canceltask(45); 83 workqueue_canceltask(45);
84 mutex_workqueue_canceltask(46); 84 workqueue_canceltask(46);
85} 85}
86 86
87static void case_cancelled_task_is_not_handed_out(void) { 87static void case_cancelled_task_is_not_handed_out(void) {
88 ot_tasktype asked; 88 ot_tasktype asked;
89 89
90 test_case("a cancelled task is gone before any worker sees it"); 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); 91 CHECK_INT(workqueue_pushtask(47, TASK_FULLSCRAPE_TPB_ASCII_PLUS), 0);
92 CHECK_INT(mutex_workqueue_pushtask(48, TASK_FULLSCRAPE), 0); 92 CHECK_INT(workqueue_pushtask(48, TASK_FULLSCRAPE), 0);
93 mutex_workqueue_canceltask(47); 93 workqueue_canceltask(47);
94 94
95 asked = TASK_FULLSCRAPE; 95 asked = TASK_FULLSCRAPE;
96 CHECK(mutex_workqueue_poptask(&asked) != 0); 96 CHECK(workqueue_poptask(&asked) != 0);
97 CHECK_INT(asked, TASK_FULLSCRAPE); 97 CHECK_INT(asked, TASK_FULLSCRAPE);
98 mutex_workqueue_canceltask(48); 98 workqueue_canceltask(48);
99} 99}
100 100
101void test_mutex(void) { 101void test_workqueue(void) {
102 case_task_goes_to_its_own_class(); 102 case_task_goes_to_its_own_class();
103 case_flags_survive_the_queue(); 103 case_flags_survive_the_queue();
104 case_classes_do_not_cross(); 104 case_classes_do_not_cross();
diff --git a/tests/test.h b/tests/test.h
index 0eddc82..d58742c 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -86,7 +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_batch(void); 90void test_batch(void);
91void test_fdstate(void); 91void test_fdstate(void);
92void test_event(void); 92void test_event(void);