aboutsummaryrefslogtreecommitdiffstats
path: root/packaging/win/smoke-connection-process.py
diff options
context:
space:
mode:
Diffstat (limited to 'packaging/win/smoke-connection-process.py')
-rw-r--r--packaging/win/smoke-connection-process.py86
1 files changed, 86 insertions, 0 deletions
diff --git a/packaging/win/smoke-connection-process.py b/packaging/win/smoke-connection-process.py
new file mode 100644
index 0000000..a4a61b1
--- /dev/null
+++ b/packaging/win/smoke-connection-process.py
@@ -0,0 +1,86 @@
+"""Start a frozen meshbay-node as a connection process and carry bytes through it.
+
+With `connection_processes` above 1 the node starts copies of itself as
+`meshbay-node.exe webrtc-worker` (transport/webrtc/workers.py). The daemon's own
+smoke test never gets there: the processes start once the node has signed in to
+a hub, and that test has none. This is the node's side of it, unchanged, with a
+frozen executable as the process and an aiortc peer here as the browser.
+
+Run by build-node-runtime.ps1 with the build venv's interpreter, which has the
+node's own modules. It takes any meshbay-node.exe, an installed one included
+(`resources/node-runtime` under the application's directory).
+"""
+
+import asyncio
+import sys
+import time
+
+from aiortc import RTCPeerConnection, RTCSessionDescription
+from meshbay_node.transport.webrtc import workers
+
+CHUNK = bytes(48_000)
+CHUNKS = 200
+
+
+async def until(test, what: str, seconds: float = 30.0) -> None:
+ deadline = time.monotonic() + seconds
+ while not test():
+ if time.monotonic() > deadline:
+ raise SystemExit(f"FAILED {what}: not within {seconds:.0f}s")
+ await asyncio.sleep(0.02)
+
+
+async def carry(exe: str) -> str:
+ workers._argv = lambda: [exe, "webrtc-worker"]
+ pool = workers.WorkerPool(2)
+ browser = RTCPeerConnection()
+ try:
+ await pool.ensure()
+ if pool.processes() != 2:
+ raise SystemExit(f"FAILED {pool.processes()} of 2 connection processes started")
+
+ channel = browser.createDataChannel("mnp")
+ got: list[bytes] = []
+ channel.on("message", got.append)
+ await browser.setLocalDescription(await browser.createOffer())
+ peer = await pool.open(browser.localDescription.sdp)
+ ours: list = []
+ peer.on("datachannel", ours.append)
+ await browser.setRemoteDescription(
+ RTCSessionDescription(sdp=await peer.negotiate(), type="answer"))
+ await until(lambda: channel.readyState == "open" and ours, "the data channel opens")
+
+ heard: list[bytes] = []
+ ours[0].on("message", heard.append)
+ channel.send(b"from the browser")
+ await until(lambda: heard == [b"from the browser"], "a message reaches the node")
+
+ for _ in range(CHUNKS):
+ ours[0].send(CHUNK)
+ while ours[0].bufferedAmount > 2_000_000:
+ await asyncio.sleep(0.005)
+ await until(lambda: sum(map(len, got)) == CHUNKS * len(CHUNK),
+ "what the node sent arrives whole")
+ await until(lambda: ours[0].bufferedAmount == 0,
+ "the node's count of what is buffered comes back to zero")
+ finally:
+ started = [w.process for w in pool._workers]
+ await browser.close()
+ await pool.close()
+ # A process still there would be one an upgrade finds holding
+ # meshbay-node.exe. Its exit code says nothing: the node ends a process
+ # whose socket has closed, and on Windows that reads 1 however it left.
+ if any(p.returncode is None for p in started):
+ raise SystemExit("FAILED a connection process outlived the node's stop")
+ return f"{CHUNKS * len(CHUNK) // 1_000_000} MB through a connection process"
+
+
+def main() -> None:
+ if len(sys.argv) < 2:
+ raise SystemExit(__doc__)
+ for exe in sys.argv[1:]:
+ print(f"OK {exe}: {asyncio.run(carry(exe))}")
+
+
+if __name__ == "__main__":
+ main()