summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile17
-rw-r--r--tests/integration/harness.py60
-rw-r--r--tests/runner.c72
3 files changed, 131 insertions, 18 deletions
diff --git a/Makefile b/Makefile
index ae01373..b172af6 100644
--- a/Makefile
+++ b/Makefile
@@ -134,6 +134,19 @@ $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
134check: $(TESTBIN) $(BINARY) 134check: $(TESTBIN) $(BINARY)
135 ./$(TESTBIN) 135 ./$(TESTBIN)
136 136
137# The same unit tests under the sanitizers. Use clang over $(CC) because the
138# two toolchains do not check the same things
139# Override CC_sanitize to use another compiler.
140CC_sanitize?=clang
141CFLAGS_sanitize=$(CFLAGS_test) -fsanitize=address,undefined -fno-omit-frame-pointer
142LDFLAGS_sanitize=$(LDFLAGS) -fsanitize=address,undefined
143
144$(TESTBIN).sanitize: $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
145 $(CC_sanitize) -o $@ $(CFLAGS_sanitize) $(TEST_SOURCES) $(TEST_UNITS) $(LDFLAGS_sanitize)
146
147check-sanitize: $(TESTBIN).sanitize
148 ./$(TESTBIN).sanitize
149
137# Black box tests: these drive the real binary over the wire, so they cover 150# Black box tests: these drive the real binary over the wire, so they cover
138# the event loop and the parsers working together, which the unit tests 151# the event loop and the parsers working together, which the unit tests
139# cannot reach. Built with the sanitizers on, so the tracker reports its own 152# cannot reach. Built with the sanitizers on, so the tracker reports its own
@@ -149,10 +162,10 @@ opentracker.integration: $(SOURCES) $(HEADERS)
149check-integration: opentracker.integration 162check-integration: opentracker.integration
150 OPENTRACKER=./opentracker.integration $(PYTHON) -m unittest discover -s tests/integration 163 OPENTRACKER=./opentracker.integration $(PYTHON) -m unittest discover -s tests/integration
151 164
152.PHONY: all check check-integration clean install 165.PHONY: all check check-sanitize check-integration clean install
153 166
154clean: 167clean:
155 rm -rf opentracker opentracker.debug opentracker.integration proxy proxy.debug $(TESTBIN) *.o *~ 168 rm -rf opentracker opentracker.debug opentracker.integration proxy proxy.debug $(TESTBIN) $(TESTBIN).sanitize *.o *~
156 169
157install: 170install:
158 install -m 755 opentracker $(DESTDIR)$(BINDIR) 171 install -m 755 opentracker $(DESTDIR)$(BINDIR)
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();