Files
capy-bearer/scripts/test_capy_format.py

88 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Check Capy formatter idempotence and Wasm preservation."""
import argparse
import json
import pathlib
import re
import shutil
import subprocess
import sys
import tempfile
def command(capyc, args, input_bytes=None):
result = subprocess.run(
[str(capyc), *map(str, args)],
input=input_bytes,
capture_output=True,
timeout=60,
)
if result.returncode:
detail = result.stderr.decode(errors="replace").strip()
raise RuntimeError(detail or f"capyc exited with {result.returncode}")
return result.stdout
def wasm(capyc, source, root, output, abi):
command(capyc, [source, "-o", output, "--source-map", str(output) + ".source-map",
"--abi-version", abi, "--source-root", root])
return pathlib.Path(output).read_bytes()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("tree", type=pathlib.Path, help="source tree to check")
parser.add_argument("--capyc", type=pathlib.Path, default=pathlib.Path("bin/capyc"))
parser.add_argument("--allow-backtrace", action="store_true",
help="allow the approved site/tests/capy-backtrace.capy exception")
args = parser.parse_args()
tree = args.tree.resolve()
capyc = args.capyc.resolve()
if not tree.is_dir():
parser.error(f"source tree does not exist: {tree}")
abi_text = (capyc.parent.parent / "src/wasm/abi.h").read_text()
abi = re.search(r"#define BEARER_WASM_CORE_ABI_VERSION\s+(\d+)", abi_text)[1]
paths = sorted(tree.rglob("*.capy"))
counts = {"same": 0, "changed": 0, "exception": 0, "failed": 0}
failures = []
with tempfile.TemporaryDirectory(prefix="capy-format-accept-") as scratch_name:
scratch = pathlib.Path(scratch_name)
work_root = scratch / "source"
shutil.copytree(tree, work_root)
for relative in (path.relative_to(tree) for path in paths):
source = work_root / relative
original_bytes = source.read_bytes()
try:
formatted_bytes = command(capyc, ["--format", "-"], input_bytes=original_bytes)
if command(capyc, ["--format", "-"], input_bytes=formatted_bytes) != formatted_bytes:
raise RuntimeError("formatter is not idempotent")
output = scratch / "unit.wasm"
before = wasm(capyc, source, work_root, output, abi)
after = before
if original_bytes != formatted_bytes:
source.write_bytes(formatted_bytes)
after = wasm(capyc, source, work_root, output, abi)
if before != after:
if args.allow_backtrace and relative.as_posix() == "tests/capy-backtrace.capy":
counts["exception"] += 1
print(f"EXCEPTION {relative}: embedded source locations change backtrace bytes")
else:
raise RuntimeError("Wasm differs")
elif original_bytes == formatted_bytes:
counts["same"] += 1
else:
counts["changed"] += 1
except Exception as error:
counts["failed"] += 1
failures.append((relative.as_posix(), str(error)))
print(f"FAIL {relative}: {error}")
finally:
source.write_bytes(original_bytes)
summary = {"count": len(paths), "counts": counts, "failures": failures}
print(json.dumps(summary, indent=2))
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())