summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/harness.py60
-rw-r--r--tests/runner.c72
2 files changed, 116 insertions, 16 deletions
diff --git a/tests/integration/harness.py b/tests/integration/harness.py
index c8da1e2..868075d 100644
--- a/tests/integration/harness.py
+++ b/tests/integration/harness.py
@@ -7,6 +7,8 @@ reports its own memory errors, which the teardown turns into test failures.
7""" 7"""
8 8
9import os 9import os
10import shutil
11import signal
10import socket 12import socket
11import struct 13import struct
12import subprocess 14import subprocess
@@ -61,15 +63,34 @@ class Tracker:
61 def __init__(self, extra_config=""): 63 def __init__(self, extra_config=""):
62 self.port = free_port() 64 self.port = free_port()
63 self.dir = tempfile.mkdtemp(prefix="ot-integration-") 65 self.dir = tempfile.mkdtemp(prefix="ot-integration-")
64 self.config = os.path.join(self.dir, "opentracker.conf") 66 self.log = None
65 with open(self.config, "w") as handle: 67 self.process = None
66 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port) 68 try:
67 handle.write("listen.udp 127.0.0.1:%d\n" % self.port) 69 self.config = os.path.join(self.dir, "opentracker.conf")
68 handle.write(extra_config) 70 with open(self.config, "w") as handle:
69 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b") 71 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port)
70 environment = dict(os.environ, ASAN_OPTIONS="detect_leaks=0:abort_on_error=0") 72 handle.write("listen.udp 127.0.0.1:%d\n" % self.port)
71 self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment) 73 handle.write(extra_config)
72 self._wait_for_port() 74 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b")
75 # Leak detection is left at the sanitizer's own default, which is on
76 # where LeakSanitizer exists and off where it does not. Asking for it
77 # by name makes the tracker refuse to start on macOS and FreeBSD.
78 environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0")
79 self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment)
80 self._wait_for_port()
81 except BaseException:
82 self._cleanup()
83 raise
84
85 def _cleanup(self):
86 """Close what we opened and take the scratch directory with it."""
87 if self.process is not None and self.process.poll() is None:
88 self.process.kill()
89 self.process.wait()
90 if self.log is not None:
91 self.log.close()
92 self.log = None
93 shutil.rmtree(self.dir, ignore_errors=True)
73 94
74 def _wait_for_port(self): 95 def _wait_for_port(self):
75 for _ in range(200): 96 for _ in range(200):
@@ -84,21 +105,32 @@ class Tracker:
84 raise AssertionError("tracker never listened on %d:\n%s" % (self.port, self.output())) 105 raise AssertionError("tracker never listened on %d:\n%s" % (self.port, self.output()))
85 106
86 def output(self): 107 def output(self):
108 if self.log is None:
109 return ""
87 self.log.flush() 110 self.log.flush()
88 self.log.seek(0) 111 self.log.seek(0)
89 return self.log.read().decode("utf-8", "replace") 112 return self.log.read().decode("utf-8", "replace")
90 113
91 def stop(self): 114 def stop(self):
92 """Returns anything the tracker complained about on the way out.""" 115 """Returns anything the tracker complained about on the way out.
93 if self.process.poll() is None: 116
94 self.process.kill() 117 The interrupt rather than a kill is what makes the leak check work at
95 self.process.wait() 118 all: LeakSanitizer reports from an exit handler, so a killed tracker
119 reports nothing and the suite passes over leaks it never looked for.
120 """
121 if self.process is not None and self.process.poll() is None:
122 self.process.send_signal(signal.SIGINT)
123 try:
124 self.process.wait(timeout=5)
125 except subprocess.TimeoutExpired:
126 self.process.kill()
127 self.process.wait()
96 complaints = [ 128 complaints = [
97 line 129 line
98 for line in self.output().splitlines() 130 for line in self.output().splitlines()
99 if "Sanitizer" in line or "runtime error" in line or line.startswith("ERROR") 131 if "Sanitizer" in line or "runtime error" in line or line.startswith("ERROR")
100 ] 132 ]
101 self.log.close() 133 self._cleanup()
102 return complaints 134 return complaints
103 135
104 # ----------------------------------------------------------- over http 136 # ----------------------------------------------------------- over http
diff --git a/tests/runner.c b/tests/runner.c
index a92315e..71a5e2a 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -4,7 +4,11 @@
4 $id$ */ 4 $id$ */
5 5
6/* System */ 6/* System */
7#include <stdio.h>
8#include <stdlib.h>
7#include <string.h> 9#include <string.h>
10#include <sys/wait.h>
11#include <unistd.h>
8 12
9/* Opentracker */ 13/* Opentracker */
10#include "test.h" 14#include "test.h"
@@ -31,15 +35,79 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
31 {"ot_accesslist", test_accesslist}, 35 {"ot_accesslist", test_accesslist},
32 {NULL, NULL}}; 36 {NULL, NULL}};
33 37
34/* Run every module, or only those whose name contains argv[1] */ 38struct tally {
39 unsigned long checks;
40 unsigned long failures;
41};
42
43/* Each module runs in a child of its own, so that a crash or a sanitizer
44 abort costs that module's results rather than every module after it. The
45 child reports its tally through a pipe; one that dies without reporting is
46 counted as a failure under its own name. */
47static void run_forked(const struct module *m) {
48 struct tally tally = {0, 0};
49 int pipefd[2], status = 0;
50 pid_t child;
51 ssize_t got;
52
53 if (pipe(pipefd)) {
54 test_failf(__FILE__, __LINE__, "pipe failed, cannot isolate %s", m->name);
55 return;
56 }
57
58 fflush(stdout); /* or the child inherits a copy and prints it again */
59 child = fork();
60 if (child < 0) {
61 close(pipefd[0]);
62 close(pipefd[1]);
63 test_failf(__FILE__, __LINE__, "fork failed, cannot isolate %s", m->name);
64 return;
65 }
66
67 if (!child) {
68 close(pipefd[0]);
69 test_checks = test_failures = 0;
70 m->run();
71 tally.checks = test_checks;
72 tally.failures = test_failures;
73 if (write(pipefd[1], &tally, sizeof(tally)) != (ssize_t)sizeof(tally))
74 _exit(1);
75 fflush(stdout);
76 _exit(0);
77 }
78
79 close(pipefd[1]);
80 got = read(pipefd[0], &tally, sizeof(tally));
81 close(pipefd[0]);
82 while (waitpid(child, &status, 0) < 0)
83 ;
84
85 if (got == (ssize_t)sizeof(tally)) {
86 test_checks += tally.checks;
87 test_failures += tally.failures;
88 }
89 if (got != (ssize_t)sizeof(tally) || !WIFEXITED(status) || WEXITSTATUS(status))
90 test_failf(__FILE__, __LINE__, "%s did not run to the end", m->name);
91}
92
93/* Run every module, or only those whose name contains argv[1].
94 OT_TEST_FORK=0 keeps everything in one process, which is what a debugger
95 wants. */
35int main(int argc, char **argv) { 96int main(int argc, char **argv) {
97 const char *fork_setting = getenv("OT_TEST_FORK");
98 int isolate = !fork_setting || strcmp(fork_setting, "0");
36 const struct module *m; 99 const struct module *m;
37 100
38 for (m = modules; m->name; ++m) { 101 for (m = modules; m->name; ++m) {
39 if (argc > 1 && !strstr(m->name, argv[1])) 102 if (argc > 1 && !strstr(m->name, argv[1]))
40 continue; 103 continue;
41 test_module(m->name); 104 test_module(m->name);
42 m->run(); 105 if (!isolate) {
106 m->run();
107 continue;
108 }
109 test_case("the module runs to the end");
110 run_forked(m);
43 } 111 }
44 112
45 return test_summary(); 113 return test_summary();