From 355c8ae57d582974f4f6a4ba42f635f2253b84a7 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 6 Nov 2026 14:24:04 +0100 Subject: Add a wall clock helper so that statedumps can be written and re-read --- tests/integration/test_accesslist.py | 4 +++- tests/integration/test_fullscrape.py | 14 ++++++------ tests/integration/test_statefile.py | 43 +++++++++++++++++++----------------- 3 files changed, 33 insertions(+), 28 deletions(-) (limited to 'tests') diff --git a/tests/integration/test_accesslist.py b/tests/integration/test_accesslist.py index 7d01264..3ed8ddf 100644 --- a/tests/integration/test_accesslist.py +++ b/tests/integration/test_accesslist.py @@ -7,6 +7,7 @@ produces: an empty file, and a last line with no newline after it. import os import struct +import time import unittest from harness import Tracker, TrackerTestCase @@ -109,9 +110,10 @@ class ListIsInPlaceBeforeTheStateFile(TrackerTestCase): files = {"blacklist": listing(BLOCKED)} def test_a_blacklisted_torrent_in_a_state_file_is_not_restored(self): + touched = int(time.time()) // 60 - 5 path = os.path.join(self.tracker.dir, "state") with open(path, "w") as handle: - handle.write("%s:5:7\n%s:5:7\n" % (BLOCKED.hex(), ALLOWED.hex())) + handle.write("%s:%d:7\n%s:%d:7\n" % (BLOCKED.hex(), touched, ALLOWED.hex(), touched)) restarted = Tracker(self.config, self.files, extra_args=["-l", path]) try: diff --git a/tests/integration/test_fullscrape.py b/tests/integration/test_fullscrape.py index 3150612..45b3ffb 100644 --- a/tests/integration/test_fullscrape.py +++ b/tests/integration/test_fullscrape.py @@ -8,6 +8,7 @@ so these also exercise the chunked transfer the ordinary replies never take. import gzip import struct +import time import unittest from harness import TrackerTestCase, bdecode @@ -78,15 +79,14 @@ class FullScrape(TrackerTestCase): escaped = line.rsplit(b":", 2)[0] self.assertIn(b"%", escaped, "these hashes are mostly unprintable bytes") - def test_the_state_dump_carries_an_age_rather_than_a_peer_count(self): - """What a restart reads back. The second column is how many minutes - ago the torrent was last touched, not a stamp and not a count of - anybody: the clock counts from boot, so an absolute time would mean - nothing to the process that reads it.""" + def test_the_state_dump_carries_a_stamp_rather_than_a_peer_count(self): + """What a restart reads back. The second column is the minute the + torrent was last touched and not a count of anybody, by the wall + clock, which is the only one two runs of the tracker agree on.""" entries = self.entries_from_lines(self.scrape_all("/stats?mode=statedump")) self.assertIn(self.seeding.hex().upper().encode(), entries) - age, completed = entries[self.seeding.hex().upper().encode()] - self.assertEqual(int(age), 0, "announced a moment ago") + stamp, completed = entries[self.seeding.hex().upper().encode()] + self.assertAlmostEqual(int(stamp), int(time.time()) // 60, delta=1, msg="announced a moment ago") self.assertEqual(int(completed), 0) # ------------------------------------------------------------ shape diff --git a/tests/integration/test_statefile.py b/tests/integration/test_statefile.py index 21eefae..a3c8d59 100644 --- a/tests/integration/test_statefile.py +++ b/tests/integration/test_statefile.py @@ -1,12 +1,12 @@ """The state dump and the -l switch that reads it back. -Nothing exercised the load path before, and the dump's second column is now -how long ago a torrent was last touched rather than when, because the clock -counts from boot and an absolute stamp would mean nothing to the process -that reads the file. +The second column is the minute a torrent was last touched, by the wall +clock, which is what the file has recorded for years and the only clock two +runs of the tracker agree on. """ import os +import time import unittest from harness import Tracker, TrackerTestCase @@ -54,23 +54,26 @@ class StateFileRoundTrip(TrackerTestCase): restarted._cleanup() -class StateFileWithNonsenseAges(TrackerTestCase): - """The age column is read as unsigned and handed on as a signed ot_time. - A value at or above 2^63 arrived negative, cleared the timeout check, and +class StateFileWithNonsenseStamps(TrackerTestCase): + """The column is read as unsigned and handed on as a signed ot_time. A + value at or above 2^63 arrived negative, cleared the timeout check, and put the torrent's base in the future, where the cleaner never reaches it. At exactly 2^63 the subtraction itself overflowed.""" - AGES = { - "overflow": 2**63, - "wrapped": 2**63 + 60 * 24, - "minus-one": 2**64 - 1, - "int64-max": 2**63 - 1, - "past-timeout": 60 * 24 + 1, - } - - def test_no_age_outside_the_timeout_builds_a_torrent(self): - lines = ["%s:%d:7" % (self.hash_for(name).hex(), age) for name, age in self.AGES.items()] - lines.append("%s:5:7" % self.hash_for("honest").hex()) + def stamps(self): + now = int(time.time()) // 60 + return { + "overflow": 2**63, + "minus-one": 2**64 - 1, + "int64-max": 2**63 - 1, + "in-the-future": now + 60, + "past-timeout": now - (24 * 60 + 1), + } + + def test_no_stamp_outside_the_timeout_builds_a_torrent(self): + stamps = self.stamps() + lines = ["%s:%d:7" % (self.hash_for(name).hex(), stamp) for name, stamp in stamps.items()] + lines.append("%s:%d:7" % (self.hash_for("honest").hex(), int(time.time()) // 60 - 5)) path = os.path.join(self.tracker.dir, "nonsense") with open(path, "w") as handle: @@ -78,9 +81,9 @@ class StateFileWithNonsenseAges(TrackerTestCase): restarted = Tracker(extra_args=["-l", path]) try: - for name in self.AGES: + for name, stamp in stamps.items(): self.assertNotIn(self.hash_for(name), restarted.scrape(self.hash_for(name))[b"files"], - "the line with age %d was loaded" % self.AGES[name]) + "the line stamped %d was loaded" % stamp) self.assertIn(self.hash_for("honest"), restarted.scrape(self.hash_for("honest"))[b"files"], "the state file was not read at all") self.assertEqual(restarted.torrent_count(), 1) -- cgit v1.3