#!/usr/bin/env python3 """Verify deterministic web generation and the checked-in generated assets.""" from __future__ import annotations import gzip import hashlib import tempfile from pathlib import Path from generate_web_assets import generate_assets PROJECT_DIRECTORY = Path(__file__).resolve().parent.parent COMPRESSED_PAYLOAD_LIMIT = 1572864 def tree_digest(directory: Path) -> str: digest = hashlib.sha256() for path in sorted(item for item in directory.rglob("*") if item.is_file()): digest.update(path.relative_to(directory).as_posix().encode("utf-8")) digest.update(b"\0") digest.update(path.read_bytes()) return digest.hexdigest() def main() -> int: source_directory = PROJECT_DIRECTORY / "web" checked_in_directory = PROJECT_DIRECTORY / "data" with tempfile.TemporaryDirectory(prefix="maze32-assets-") as temporary: temporary_directory = Path(temporary) first = temporary_directory / "first" second = temporary_directory / "second" generate_assets(source_directory, first) generate_assets(source_directory, second) first_digest = tree_digest(first) second_digest = tree_digest(second) checked_in_digest = tree_digest(checked_in_directory) if first_digest != second_digest: raise AssertionError("Repeated web-asset generation produced different output") if first_digest != checked_in_digest: raise AssertionError("Generated web assets are stale; regenerate the data directory") for compressed_path in sorted(path for path in first.rglob("*.gz") if path.is_file()): relative_path = compressed_path.relative_to(first) source_relative = Path(str(relative_path)[:-3]) if gzip.decompress(compressed_path.read_bytes()) != ( source_directory / source_relative ).read_bytes(): raise AssertionError(f"Compressed asset does not reproduce {source_relative}") compressed_size = sum(path.stat().st_size for path in first.rglob("*.gz")) if compressed_size > COMPRESSED_PAYLOAD_LIMIT: raise AssertionError( f"Compressed web payload is {compressed_size} bytes and exceeds " f"the {COMPRESSED_PAYLOAD_LIMIT}-byte limit" ) print( f"web asset reproducibility: PASS ({first_digest}, " f"{compressed_size}/{COMPRESSED_PAYLOAD_LIMIT} bytes)" ) return 0 if __name__ == "__main__": raise SystemExit(main())