#!/usr/bin/env python3
"""The daemon reaches the PUBLIC DHT with no --bootstrap, and two peers mesh.
python3 -B test_daemon_public_dht.py
⛔ THIS IS THE GATE THAT WAS MISSING, AND ITS ABSENCE COST A LIVE SESSION.
Every other test of this stack — test_session_channel.cpp (8/8),
test_three_peer_swarm, test_daemon_e2e.py — supplies an explicit `--bootstrap`
pointing at a local testnet, because that is what makes a test fast and
hermetic. So all of them passed while the one configuration the ADDON actually
uses, `--bootstrap` omitted, was dead: an empty list left _bootstrap_count at 0,
bootstrap() sent zero FIND_NODEs, and on_bootstrapped never fired. Two Blenders
on two machines joined the same topic, both reported success, and neither had
ever sent a packet.
A hermetic suite cannot catch that, because the bug IS "the non-hermetic path
was never exercised". Hence this file, and hence it talks to the real network.
⚠ REQUIRES INTERNET. A failure here means the daemon is unreachable OR this
machine is — check `dht=` in the captured output before believing the verdict.
"""
import os
import re
import subprocess
import sys
import tempfile
import time
HERE = os.path.dirname(os.path.realpath(__file__))
DAEMON = os.path.join(HERE, "session-daemon")
BOOTSTRAP_TIMEOUT = 45.0
MESH_TIMEOUT = 90.0
passed = failed = 0
def ck(name, cond, detail=""):
global passed, failed
if cond:
passed += 1
print(" PASS: %-52s %s" % (name, detail))
else:
failed += 1
print(" FAIL: %-52s %s" % (name, detail))
class Daemon:
"""One session-daemon, started EXACTLY as blender_addon does."""
def __init__(self, topic, tag):
self.log_path = os.path.join(tempfile.gettempdir(),
"dht-gate-%s-%d.log" % (tag, os.getpid()))
self.sock = os.path.join(tempfile.gettempdir(),
"dht-gate-%s-%d.sock" % (tag, os.getpid()))
self._log = open(self.log_path, "wb")
# ⛔ NO --bootstrap. That omission is the whole point of this gate; adding
# one here would turn it back into the test that already passed.
self.proc = subprocess.Popen(
[DAEMON, "--topic", topic, "--socket", self.sock],
stdout=self._log, stderr=subprocess.STDOUT)
def output(self):
try:
with open(self.log_path, "r", errors="replace") as fh:
return fh.read()
except OSError:
return ""
def state(self):
"""(dht, peers) from the most recent status line, or (None, None)."""
lines = re.findall(r"dht=(\w+) peers=(\d+)", self.output())
if not lines:
return None, None
return lines[-1][0], int(lines[-1][1])
def stop(self):
if self.proc.poll() is None:
self.proc.terminate()
try:
self.proc.wait(timeout=5)
except subprocess.TimeoutExpired:
self.proc.kill()
self._log.close()
def wait_for(fn, timeout, step=1.0):
deadline = time.time() + timeout
while time.time() < deadline:
if fn():
return True
time.sleep(step)
return False
def main():
print("=" * 78)
print("session-daemon on the PUBLIC DHT (no --bootstrap)")
print("=" * 78)
if not os.path.isfile(DAEMON):
print("SKIP: no session-daemon binary — run ./build.sh")
return 0
topic = "gate-%d-%d" % (os.getpid(), int(time.time()))
a = Daemon(topic, "a")
b = Daemon(topic, "b")
try:
# 1. It must actually enter the DHT. This is the assertion that was RED:
# before the fix it sat on dht=connecting for ever.
up = wait_for(lambda: a.state()[0] == "up" and b.state()[0] == "up",
BOOTSTRAP_TIMEOUT)
ck("both daemons bootstrapped the public DHT", up,
"A=%s B=%s" % (a.state()[0], b.state()[0]))
if not up:
print("\n--- A ---\n%s\n--- B ---\n%s" % (a.output(), b.output()))
return 1
# 2. And find each other on the topic. Bootstrapping alone is not the
# claim — "joined" was already true when nothing worked.
met = wait_for(lambda: a.state()[1] >= 1 and b.state()[1] >= 1,
MESH_TIMEOUT)
ck("they found each other on the topic", met,
"A sees %d, B sees %d" % (a.state()[1], b.state()[1]))
# 3. Both directions. A channel that opens one way is a half-session,
# and the panel would show a peer that never receives a grab.
ck("the channel is open on BOTH peers", a.state()[1] >= 1 and b.state()[1] >= 1,
"one-sided means grabs travel one way only")
# 4. The status line itself, because it is now the only way anyone can
# tell a live session from a dead one.
ck("the daemon reports its DHT state", "dht=" in a.output(),
"an unobservable daemon is why this took ten minutes to notice")
finally:
a.stop()
b.stop()
print()
print("%d/%d passed" % (passed, passed + failed))
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())