run.py raw
1 #!/usr/bin/env python3
2 """Run a Moxie wasm module in a real browser and report its stdout.
3
4 Serves the repository plus the module over HTTP (fetch cannot read file://),
5 loads tests/wasm/host.html, and returns the module's output and any JS error.
6 Exits non-zero if the module failed to load or produced an error.
7 """
8 import functools
9 import http.server
10 import json
11 import pathlib
12 import socketserver
13 import sys
14 import threading
15
16 try:
17 from playwright.sync_api import sync_playwright
18 except ImportError:
19 print("playwright is required: pip install playwright && playwright install chromium")
20 sys.exit(2)
21
22 ROOT = pathlib.Path(__file__).resolve().parent
23 REPO = ROOT.parent.parent
24 args = [a for a in sys.argv[1:] if a != "-q"]
25 QUIET = "-q" in sys.argv[1:]
26 WASM = args[0] if args else str(ROOT / "app.wasm")
27 TIMEOUT = 20000
28
29
30 class Handler(http.server.SimpleHTTPRequestHandler):
31 def translate_path(self, path):
32 path = path.split("?")[0].split("#")[0]
33 if path.startswith("/jsruntime/"):
34 return str(REPO) + path
35 if path == "/":
36 return str(ROOT / "host.html")
37 return str(ROOT) + path
38
39 def end_headers(self):
40 # SharedArrayBuffer - the backing store for every channel that crosses
41 # a spawn boundary - only exists in a cross-origin isolated context.
42 # Without these two headers the browser leaves the constructor
43 # undefined and channel_create fails before a domain can start.
44 self.send_header("Cross-Origin-Opener-Policy", "same-origin")
45 self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
46 super().end_headers()
47
48 def log_message(self, *args):
49 pass
50
51
52 def main():
53 (ROOT / "app.wasm").write_bytes(pathlib.Path(WASM).read_bytes())
54
55 srv = socketserver.TCPServer(("127.0.0.1", 0), functools.partial(Handler, directory=str(ROOT)))
56 srv.allow_reuse_address = True
57 port = srv.server_address[1]
58 threading.Thread(target=srv.serve_forever, daemon=True).start()
59
60 with sync_playwright() as pw:
61 browser = pw.chromium.launch()
62 page = browser.new_page()
63 page.goto(f"http://127.0.0.1:{port}/", wait_until="load")
64 try:
65 page.wait_for_function("window.__done === true", timeout=TIMEOUT)
66 except Exception:
67 print("TIMEOUT: module did not finish loading")
68 browser.close()
69 srv.shutdown()
70 return 1
71 page.wait_for_timeout(200)
72 out = page.evaluate("() => ({logs: window.__logs, error: window.__error, errs: window.__consoleErrors})")
73 browser.close()
74 srv.shutdown()
75
76 if not QUIET:
77 print("stdout: " + json.dumps(out["logs"]))
78 if out["errs"]:
79 print("console errors: " + json.dumps(out["errs"]))
80 if out["error"]:
81 print("JS error: " + out["error"])
82 return 1
83 # -q prints only the module's stdout, so callers can diff it against a
84 # native run.
85 for line in out["logs"]:
86 print(line, end="")
87 return 0
88
89
90 if __name__ == "__main__":
91 sys.exit(main())
92