diff options
| author | info@mode42.com <info@mode42.com> | 2026-08-07 18:25:13 +0000 |
|---|---|---|
| committer | info@mode42.com <info@mode42.com> | 2026-08-07 18:25:13 +0000 |
| commit | 04d965d67a7264a1c7c211494aebda1953df7603 (patch) | |
| tree | 0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/daemon/test_proto.py | |
Initial push
Diffstat (limited to 'stacks/daemon/test_proto.py')
| -rw-r--r-- | stacks/daemon/test_proto.py | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/stacks/daemon/test_proto.py b/stacks/daemon/test_proto.py new file mode 100644 index 0000000..50ced93 --- /dev/null +++ b/stacks/daemon/test_proto.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Offline smoke test for max25d M25/1 protocol.""" +import socket +import subprocess +import sys +import time +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +DAEMON = ROOT / "stacks/daemon/max25d" +INI = ROOT / "share/max25/max25d.ini.example" + + +class LineReader: + def __init__(self, sock: socket.socket) -> None: + self.sock = sock + self.buf = b"" + + def read(self, timeout: float = 5.0) -> str: + self.sock.settimeout(timeout) + while b"\n" not in self.buf: + chunk = self.sock.recv(4096) + if not chunk: + raise RuntimeError("connection closed") + self.buf += chunk + line, self.buf = self.buf.split(b"\n", 1) + return line.decode("utf-8") + + +def free_port() -> int: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] + sock.close() + return port + + +def wait_for_port(port: int, proc: subprocess.Popen[str], timeout: float = 8.0) -> bool: + deadline = time.time() + timeout + while time.time() < deadline: + if proc.poll() is not None: + return False + try: + probe = socket.create_connection(("127.0.0.1", port), timeout=0.25) + probe.close() + return True + except OSError: + time.sleep(0.1) + return False + + +def main() -> int: + port = free_port() + proc = subprocess.Popen( + [str(DAEMON), "--no-stack", "--no-serial", "-c", str(INI), "--tcp-port", str(port)], + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + text=True, + ) + if not wait_for_port(port, proc): + err = proc.stderr.read() if proc.stderr else "" + proc.terminate() + proc.wait(timeout=3) + print(f"FAIL: max25d not listening on {port}: {err.strip()}", file=sys.stderr) + return 1 + try: + sock = socket.create_connection(("127.0.0.1", port), timeout=3) + except OSError as exc: + proc.terminate() + proc.wait(timeout=3) + print(f"FAIL: connect: {exc}", file=sys.stderr) + return 1 + + reader = LineReader(sock) + try: + assert reader.read() == "OK" + status = reader.read() + assert status.startswith("STATUS ") + + sock.sendall(b"SET CALLERID DG1ABC\n") + assert reader.read() == "OK" + + sock.sendall(b"CONNECT\n") + assert reader.read() == "EVENT connected" + assert reader.read() == "OK" + + sock.sendall(b"SEND 73\n") + rx = reader.read() + assert rx.startswith("RX ") + assert reader.read() == "OK" + + sock.sendall(b"GET STATUS\n") + st = reader.read() + assert "callerid=DG1ABC" in st + assert reader.read() == "OK" + + print("OK: max25d protocol smoke") + return 0 + finally: + sock.close() + proc.terminate() + proc.wait(timeout=5) + + +if __name__ == "__main__": + sys.exit(main()) |
