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