Merge remote-tracking branch 'acul71/feat/804-add-thin-waist-address' into feat/804-add-thin-waist-address

This commit is contained in:
yashksaini-coder
2025-08-19 19:56:20 +05:30
7 changed files with 311 additions and 82 deletions

View File

@ -1,65 +1,108 @@
import contextlib
import sys
import os
from pathlib import Path
import subprocess
import sys
import time
import pytest
import trio
from multiaddr import Multiaddr
from multiaddr.protocols import P_IP4, P_IP6, P_P2P, P_TCP
# pytestmark = pytest.mark.timeout(20) # Temporarily disabled for debugging
# This test is intentionally lightweight and can be marked as 'integration'.
# It ensures the echo example runs and prints the new Thin Waist lines using Trio primitives.
EXAMPLES_DIR = Path(__file__).parent.parent.parent / "examples" / "echo"
current_file = Path(__file__)
project_root = current_file.parent.parent.parent
EXAMPLES_DIR: Path = project_root / "examples" / "echo"
@pytest.mark.trio
async def test_echo_example_starts_and_prints_thin_waist() -> None:
cmd = [sys.executable, str(EXAMPLES_DIR / "echo.py"), "-p", "0"]
def test_echo_example_starts_and_prints_thin_waist(monkeypatch, tmp_path):
"""Run echo server and validate printed multiaddr and peer id."""
# Run echo example as server
cmd = [sys.executable, "-u", str(EXAMPLES_DIR / "echo.py"), "-p", "0"]
env = {**os.environ, "PYTHONUNBUFFERED": "1"}
proc: subprocess.Popen[str] = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env=env,
)
found_selected = False
found_interfaces = False
if proc.stdout is None:
proc.terminate()
raise RuntimeError("Process stdout is None")
out_stream = proc.stdout
# Use a cancellation scope as timeout (similar to previous 10s loop)
with trio.move_on_after(10) as cancel_scope:
# Start process streaming stdout
proc = await trio.open_process(
cmd,
stdout=trio.SUBPROCESS_PIPE,
stderr=trio.STDOUT,
peer_id: str | None = None
printed_multiaddr: str | None = None
saw_waiting = False
start = time.time()
timeout_s = 8.0
try:
while time.time() - start < timeout_s:
line = out_stream.readline()
if not line:
time.sleep(0.05)
continue
s = line.strip()
if s.startswith("I am "):
peer_id = s.partition("I am ")[2]
if s.startswith("echo-demo -d "):
printed_multiaddr = s.partition("echo-demo -d ")[2]
if "Waiting for incoming connections..." in s:
saw_waiting = True
break
finally:
with contextlib.suppress(ProcessLookupError):
proc.terminate()
with contextlib.suppress(ProcessLookupError):
proc.kill()
assert peer_id, "Did not capture peer ID line"
assert printed_multiaddr, "Did not capture multiaddr line"
assert saw_waiting, "Did not capture waiting-for-connections line"
# Validate multiaddr structure using py-multiaddr protocol methods
ma = Multiaddr(printed_multiaddr) # should parse without error
# Check that the multiaddr contains the p2p protocol
try:
peer_id_from_multiaddr = ma.value_for_protocol("p2p")
assert peer_id_from_multiaddr is not None, (
"Multiaddr missing p2p protocol value"
)
assert peer_id_from_multiaddr == peer_id, (
f"Peer ID mismatch: {peer_id_from_multiaddr} != {peer_id}"
)
except Exception as e:
raise AssertionError(f"Failed to extract p2p protocol value: {e}")
assert proc.stdout is not None # for type checkers
buffer = b""
# Validate the multiaddr structure by checking protocols
protocols = ma.protocols()
try:
while not (found_selected and found_interfaces):
# Read some bytes (non-blocking with timeout scope)
data = await proc.stdout.receive_some(1024)
if not data:
# Process might still be starting; yield control
await trio.sleep(0.05)
continue
buffer += data
# Process complete lines
*lines, buffer = buffer.split(b"\n") if b"\n" in buffer else ([], buffer)
for raw in lines:
line = raw.decode(errors="ignore")
if "Selected binding address:" in line:
found_selected = True
if "Available candidate interfaces:" in line:
found_interfaces = True
if "Waiting for incoming connections..." in line:
# We have reached steady state; can stop reading further
if found_selected and found_interfaces:
break
finally:
# Terminate the long-running echo example
with contextlib.suppress(Exception):
proc.terminate()
with contextlib.suppress(Exception):
await trio.move_on_after(2)(proc.wait) # best-effort wait
if cancel_scope.cancelled_caught:
# Timeout occurred
pass
# Should have at least IP, TCP, and P2P protocols
assert any(p.code == P_IP4 or p.code == P_IP6 for p in protocols), (
"Missing IP protocol"
)
assert any(p.code == P_TCP for p in protocols), "Missing TCP protocol"
assert any(p.code == P_P2P for p in protocols), "Missing P2P protocol"
assert found_selected, "Did not capture Thin Waist binding log line"
assert found_interfaces, "Did not capture Thin Waist interfaces log line"
# Extract the p2p part and validate it matches the captured peer ID
p2p_part = Multiaddr(f"/p2p/{peer_id}")
try:
# Decapsulate the p2p part to get the transport address
transport_addr = ma.decapsulate(p2p_part)
# Verify the decapsulated address doesn't contain p2p
transport_protocols = transport_addr.protocols()
assert not any(p.code == P_P2P for p in transport_protocols), (
"Decapsulation failed - still contains p2p"
)
# Verify the original multiaddr can be reconstructed
reconstructed = transport_addr.encapsulate(p2p_part)
assert str(reconstructed) == str(ma), "Reconstruction failed"
except Exception as e:
raise AssertionError(f"Multiaddr decapsulation failed: {e}")