summaryrefslogtreecommitdiff
path: root/tests/integration/test_stats.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration/test_stats.py')
-rw-r--r--tests/integration/test_stats.py49
1 files changed, 36 insertions, 13 deletions
diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py
index 920d199..820b917 100644
--- a/tests/integration/test_stats.py
+++ b/tests/integration/test_stats.py
@@ -5,7 +5,9 @@ every one of them and, where the number means something, that it moves when
5the thing it counts happens. 5the thing it counts happens.
6""" 6"""
7 7
8import ipaddress
8import re 9import re
10import socket
9import unittest 11import unittest
10import urllib.error 12import urllib.error
11import xml.etree.ElementTree 13import xml.etree.ElementTree
@@ -14,8 +16,7 @@ from harness import TrackerTestCase
14 16
15# The modes a default build answers. "tpbs" and "statedump" are full scrapes 17# The modes a default build answers. "tpbs" and "statedump" are full scrapes
16# and belong to test_fullscrape; "fulllog" and "woodpeckers" sit behind 18# and belong to test_fullscrape; "fulllog" and "woodpeckers" sit behind
17# feature flags this build does not set; "busy" is advertised but unbuilt, 19# feature flags this build does not set.
18# which the next case pins.
19MRTG_MODES = ["peer", "conn", "scrp", "udp4", "tcp4", "torr", "fscr", "completed", "syncs"] 20MRTG_MODES = ["peer", "conn", "scrp", "udp4", "tcp4", "torr", "fscr", "completed", "syncs"]
20TEXT_MODES = ["herr", "s24s", "top10", "top100", "renew", "version"] 21TEXT_MODES = ["herr", "s24s", "top10", "top100", "renew", "version"]
21 22
@@ -34,14 +35,14 @@ class StatsShape(TrackerTestCase):
34 for line in lines[:2]: 35 for line in lines[:2]:
35 self.assertRegex(line, rb"^\d+$", "mode %s line %r" % (mode, line)) 36 self.assertRegex(line, rb"^\d+$", "mode %s line %r" % (mode, line))
36 37
37 def test_the_busy_mode_is_advertised_but_not_built(self): 38 def test_the_busy_mode_is_gone(self):
38 """TASK_STATS_BUSY_NETWORKS is in the enum and in the mode table, and 39 """It was listed in the mode table and implemented nowhere, so it only
39 no switch anywhere handles it, so the worker frees the empty answer 40 ever answered 500. Removed rather than written: picking poisoning
40 and the request ends as a server error. Pinned rather than blessed: 41 networks out stopped being meaningful once consumer lines became bot
41 either the report gets written or the mode leaves the table.""" 42 hosts and v6 made the address space too large to watch."""
42 with self.assertRaises(urllib.error.HTTPError) as caught: 43 with self.assertRaises(urllib.error.HTTPError) as caught:
43 self.tracker.stats("busy") 44 self.tracker.stats("busy")
44 self.assertEqual(caught.exception.code, 500) 45 self.assertEqual(caught.exception.code, 400, "an unknown mode, like any other")
45 46
46 def test_an_unknown_mode_is_refused(self): 47 def test_an_unknown_mode_is_refused(self):
47 with self.assertRaises(urllib.error.HTTPError) as caught: 48 with self.assertRaises(urllib.error.HTTPError) as caught:
@@ -131,15 +132,37 @@ class StatsTop(TrackerTestCase):
131 132
132 133
133class StatsNetworks(TrackerTestCase): 134class StatsNetworks(TrackerTestCase):
134 def test_the_slash24_report_names_the_loopback_network(self): 135 def source_address(self):
136 """Which address the tracker sees us from is not ours to choose: a
137 FreeBSD jail relocates a loopback connection to the jail's own
138 address, so ask the socket rather than assume 127.0.0.1."""
139 probe = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5)
140 try:
141 return probe.getsockname()[0]
142 finally:
143 probe.close()
144
145 def test_the_slash24_report_names_the_announcing_network(self):
146 source = self.source_address()
135 self.tracker.announce(self.hash_for("s24"), port=6001, left=0) 147 self.tracker.announce(self.hash_for("s24"), port=6001, left=0)
136 body = self.tracker.stats("s24s") 148 body = self.tracker.stats("s24s")
137 self.assertIn(b"Networks, limit /28:", body) 149 self.assertIn(b"Networks, limit /28:", body)
138 self.assertIn(b"Networks, limit /24:", body) 150 self.assertIn(b"Networks, limit /24:", body)
139 # The prefix is masked out of a v4 peer's four address bytes and then 151
140 # printed through the v6 formatter, so 127.0.0.0/24 reads as "7f00::" 152 # The prefix is the first three address bytes of a v4 peer, left
141 # rather than as an address anyone would recognise. Pinned as it is. 153 # aligned in a sixteen byte buffer and then printed by the v6
142 self.assertIn(b"7f00::", body) 154 # formatter, so 127.0.0.0/24 reads as "7f00::" rather than as the
155 # network an operator asked about. Pinned as it is, not blessed.
156 prefix = socket.inet_aton(source)[:3] + b"\0" * 13
157 self.assertIn(str(ipaddress.IPv6Address(prefix)).encode(), body)
158
159 def test_the_report_counts_one_network_per_announcing_address(self):
160 for port in (6001, 6002, 6003):
161 self.tracker.announce(self.hash_for("s24count"), port=port, left=0)
162 lines = [line for line in self.tracker.stats("s24s").splitlines() if b":" in line and b"limit" not in line]
163 self.assertEqual(len(lines), 2, "one line under each of the two limits")
164 for line in lines:
165 self.assertEqual(int(line.split(b":")[0]), 3, "three peers from the one network")
143 166
144 167
145if __name__ == "__main__": 168if __name__ == "__main__":