"""Checks that Vellum and Ariadne saves work in a published browser build, trimmed and optionally AOT compiled. python Scripts/wasmcheck.py Publishes Vellum.WasmProbe trimmed, runs it under node and reports. python Scripts/wasmcheck.py --aot Also publishes it with AOT compilation, which takes several minutes. The trimmer and AOT run only on publish, so a build or a desktop test can't show a save breaking there. The probe loads saves of types nothing constructs in code, which is what a trimmer removes. Node must be on the PATH. """ import argparse import os import shutil import subprocess import sys ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) PROBE = "Vellum.WasmProbe" PROBE_DIRECTORY = os.path.join(ROOT, "Modules", "Vellum", PROBE) PUBLISHED_WWWROOT = os.path.join(ROOT, "Junk", PROBE, "release", "publish", "wwwroot") PASSED_LINE = "ALL PASSED" def failure_lines(output): """Returns the lines of a probe run that say what went wrong, with the detail lines that follow each.""" returnMe = [] reporting = False for line in output.splitlines(): if line.startswith(("FAIL", "RUNTIME ERROR")): reporting = True elif line.startswith(("PASS", "probe:", "converters:")) or line == PASSED_LINE: reporting = False if reporting: returnMe.append(line) return returnMe def run_succeeded(exit_code, output): """Returns whether a probe run passed: it exited cleanly and reported every check.""" return exit_code == 0 and PASSED_LINE in output.splitlines() def publish(aot): """Publishes the probe through build.py, which builds what it references first. Returns the exit code.""" environment = dict(os.environ) environment.pop("ProbeAot", None) if aot: # MSBuild reads an environment variable as a property, and the probe's csproj turns AOT on from this one. environment["ProbeAot"] = "true" command = [sys.executable, "-u", os.path.join(ROOT, "Scripts", "build.py"), "--command", "publish", "--project", PROBE, "--configuration", "release", "--no-interactive", "--quiet-summary"] return subprocess.call(command, env=environment, cwd=ROOT) def run_probe(node, wwwroot): """Runs the published probe under node. Returns (exit code, its output).""" completed = subprocess.run([node, os.path.join(PROBE_DIRECTORY, "run-wasm.mjs"), wwwroot], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) return completed.returncode, completed.stdout def check(node, aot): """Publishes and runs the probe once. Returns whether it passed.""" label = "AOT" if aot else "trimmed" print("[{}] publishing {}".format(label, PROBE)) if publish(aot) != 0: print("[{}] FAILED: the publish failed. Run build.py --command publish --project {} for the log.".format( label, PROBE)) return False exit_code, output = run_probe(node, PUBLISHED_WWWROOT) if run_succeeded(exit_code, output): print("[{}] passed".format(label)) return True print("[{}] FAILED (exit code {})".format(label, exit_code)) for line in failure_lines(output) or output.splitlines()[-10:]: print(" " + line) return False def main(): parser = argparse.ArgumentParser(description="Checks saves in a published browser build.") parser.add_argument("--aot", action="store_true", help="Also check an AOT compiled publish.") args = parser.parse_args() node = shutil.which("node") if node is None: print("node isn't on the PATH, and the check runs the published build under it.") return 2 passed = check(node, aot=False) if args.aot: passed = check(node, aot=True) and passed return 0 if passed else 1 if __name__ == "__main__": sys.exit(main())