From 0fcf290ba2db320287163f142a83da0f9fe48b77 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Tue, 3 Nov 2026 02:40:38 +0100 Subject: Add integration tests for live sync --- tests/integration/harness.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'tests/integration/harness.py') diff --git a/tests/integration/harness.py b/tests/integration/harness.py index b98ce83..a9d38ca 100644 --- a/tests/integration/harness.py +++ b/tests/integration/harness.py @@ -20,6 +20,9 @@ import urllib.error import urllib.parse import urllib.request +# groupip_1 in src/ot_livesync.c +LIVESYNC_GROUP = "224.0.23.5" + TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug")) @@ -76,6 +79,9 @@ class Tracker: def __init__(self, extra_config="", files=None): self.port = free_port() + # Only used by a config that asks for live sync, but allocated either + # way so {livesync_port} always resolves. + self.livesync_port = free_port() self.dir = tempfile.mkdtemp(prefix="ot-integration-") self.log = None self.process = None @@ -87,7 +93,7 @@ class Tracker: with open(self.config, "w") as handle: handle.write("listen.tcp 127.0.0.1:%d\n" % self.port) handle.write("listen.udp 127.0.0.1:%d\n" % self.port) - handle.write(extra_config.replace("{dir}", self.dir)) + handle.write(extra_config.replace("{dir}", self.dir).replace("{livesync_port}", str(self.livesync_port))) self.log = open(os.path.join(self.dir, "tracker.log"), "w+b") # Leak detection is left at the sanitizer's own default, which is on # where LeakSanitizer exists and off where it does not. Asking for it @@ -186,6 +192,32 @@ class Tracker: def torrent_count(self): return int(self.stats("torr").split()[0]) + def livesync_send(self, payload): + """Put a packet on the live sync group. + + It has to go to the group rather than straight at the port: the + tracker binds two sockets there, the reader on the wildcard address + and the writer on the configured one, and the kernel gives a unicast + datagram to the more specific of the two, which nobody reads.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton("127.0.0.1")) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1) + sock.sendto(payload, (LIVESYNC_GROUP, self.livesync_port)) + finally: + sock.close() + + def wait_until(self, predicate, timeout=5): + """A live sync packet is handled by another thread, so an effect of + one arrives some time after the send.""" + deadline = time.time() + timeout + while time.time() < deadline: + if predicate(): + return True + time.sleep(0.02) + return predicate() + # ------------------------------------------------------------ over udp def udp_connect(self): -- cgit v1.3