summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/integration/harness.py5
-rw-r--r--tests/integration/test_fullscrape.py12
-rw-r--r--tests/integration/test_statefile.py53
3 files changed, 63 insertions, 7 deletions
diff --git a/tests/integration/harness.py b/tests/integration/harness.py
index 10aa6de..e5ec621 100644
--- a/tests/integration/harness.py
+++ b/tests/integration/harness.py
@@ -77,7 +77,7 @@ def free_port(attempts=50):
77class Tracker: 77class Tracker:
78 """One tracker process, listening on loopback.""" 78 """One tracker process, listening on loopback."""
79 79
80 def __init__(self, extra_config="", files=None): 80 def __init__(self, extra_config="", files=None, extra_args=()):
81 self.port = free_port() 81 self.port = free_port()
82 # Only used by a config that asks for live sync, but allocated either 82 # Only used by a config that asks for live sync, but allocated either
83 # way so {livesync_port} always resolves. 83 # way so {livesync_port} always resolves.
@@ -99,7 +99,8 @@ class Tracker:
99 # where LeakSanitizer exists and off where it does not. Asking for it 99 # where LeakSanitizer exists and off where it does not. Asking for it
100 # by name makes the tracker refuse to start on macOS and FreeBSD. 100 # by name makes the tracker refuse to start on macOS and FreeBSD.
101 environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0") 101 environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0")
102 self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment) 102 argv = [TRACKER, "-f", self.config] + [str(a) for a in extra_args]
103 self.process = subprocess.Popen(argv, stdout=self.log, stderr=self.log, env=environment)
103 self._wait_for_port() 104 self._wait_for_port()
104 except BaseException: 105 except BaseException:
105 self._cleanup() 106 self._cleanup()
diff --git a/tests/integration/test_fullscrape.py b/tests/integration/test_fullscrape.py
index be7b502..3150612 100644
--- a/tests/integration/test_fullscrape.py
+++ b/tests/integration/test_fullscrape.py
@@ -78,13 +78,15 @@ class FullScrape(TrackerTestCase):
78 escaped = line.rsplit(b":", 2)[0] 78 escaped = line.rsplit(b":", 2)[0]
79 self.assertIn(b"%", escaped, "these hashes are mostly unprintable bytes") 79 self.assertIn(b"%", escaped, "these hashes are mostly unprintable bytes")
80 80
81 def test_the_state_dump_carries_a_base_time_rather_than_a_peer_count(self): 81 def test_the_state_dump_carries_an_age_rather_than_a_peer_count(self):
82 """What a restart reads back, so its second column is the torrent's 82 """What a restart reads back. The second column is how many minutes
83 own clock and not a count of anybody.""" 83 ago the torrent was last touched, not a stamp and not a count of
84 anybody: the clock counts from boot, so an absolute time would mean
85 nothing to the process that reads it."""
84 entries = self.entries_from_lines(self.scrape_all("/stats?mode=statedump")) 86 entries = self.entries_from_lines(self.scrape_all("/stats?mode=statedump"))
85 self.assertIn(self.seeding.hex().upper().encode(), entries) 87 self.assertIn(self.seeding.hex().upper().encode(), entries)
86 base, completed = entries[self.seeding.hex().upper().encode()] 88 age, completed = entries[self.seeding.hex().upper().encode()]
87 self.assertGreater(int(base), 0) 89 self.assertEqual(int(age), 0, "announced a moment ago")
88 self.assertEqual(int(completed), 0) 90 self.assertEqual(int(completed), 0)
89 91
90 # ------------------------------------------------------------ shape 92 # ------------------------------------------------------------ shape
diff --git a/tests/integration/test_statefile.py b/tests/integration/test_statefile.py
new file mode 100644
index 0000000..2a2d147
--- /dev/null
+++ b/tests/integration/test_statefile.py
@@ -0,0 +1,53 @@
1"""The state dump and the -l switch that reads it back.
2
3Nothing exercised the load path before, and the dump's second column is now
4how long ago a torrent was last touched rather than when, because the clock
5counts from boot and an absolute stamp would mean nothing to the process
6that reads the file.
7"""
8
9import os
10import unittest
11
12from harness import Tracker, TrackerTestCase
13
14
15class StateFileRoundTrip(TrackerTestCase):
16 def dump(self):
17 return self.tracker.get("/stats?mode=statedump")
18
19 def test_a_dumped_torrent_comes_back_with_its_completed_count(self):
20 info_hash = self.hash_for("state-rt")
21 self.tracker.announce(info_hash, port=6001, left=0, event="completed")
22 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"downloaded"], 1)
23
24 path = os.path.join(self.tracker.dir, "state")
25 with open(path, "wb") as handle:
26 handle.write(self.dump())
27
28 restarted = Tracker(extra_args=["-l", path])
29 try:
30 files = restarted.scrape(info_hash)[b"files"]
31 self.assertIn(info_hash, files, "the torrent did not survive the restart")
32 entry = files[info_hash]
33 self.assertEqual(entry[b"complete"], 0, "no peers are carried over, only the torrent")
34 self.assertEqual(entry[b"incomplete"], 0)
35 complaints = restarted.stop()
36 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
37 finally:
38 restarted._cleanup()
39
40 def test_a_torrent_older_than_the_tracker_would_keep_is_not_loaded(self):
41 """An age past the torrent timeout would be cleaned away on the first
42 sweep, so it is never built in the first place."""
43 info_hash = self.hash_for("state-stale")
44 path = os.path.join(self.tracker.dir, "stale")
45 with open(path, "w") as handle:
46 handle.write("%s:%d:7\n" % (info_hash.hex(), 24 * 60 + 1))
47
48 restarted = Tracker(extra_args=["-l", path])
49 try:
50 self.assertNotIn(info_hash, restarted.scrape(info_hash)[b"files"])
51 restarted.stop()
52 finally:
53 restarted._cleanup()