Files
horizon/tools/test_world_parity.py
Ludovic MarieandClaude Opus 5.5 5034e4557d parity: clear rust-<name>-parity outputs after a pass, reuse oracles on CI
Disk: clear_large_outputs only looked at .local directories named after the
harness, so the 174 harnesses writing to rust-<name>-parity kept every dump
(86 GB in one runner checkout, 13 GB for state_provider alone). It now
clears those too, keeps vanilla outputs only up to 256 MB (larger ones cost
more disk than rerunning their oracle), and handles shard names.
collision, world and geometry dropped one mkdtemp run directory per run
(11 GB): each run now removes the earlier ones.

Time: harnesses inherited the runner's stdin, a pipe on GitHub's runner, and
parity_shim never reuses an oracle run whose stdin is a pipe, so oracle reuse
never worked on CI. Harnesses now get /dev/null.

CI: parity.yml falls back to the head's own selection when the names main's
`--list` printed don't match its summary count.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 00:44:22 +01:00

116 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Exercise Rust NBT, palette, registry and region I/O with pinned vanilla bytecode."""
import argparse
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess
import tempfile
try:
from . import lab, test_rust_parity
except ImportError:
import lab
import test_rust_parity
def run(command, log=None):
result = subprocess.run(list(map(str, command)), cwd=lab.ROOT, capture_output=True, text=True, timeout=300)
if log:
log.write_text(result.stdout + result.stderr, encoding="utf-8")
if result.returncode:
raise RuntimeError(f"Command failed ({result.returncode}): {command[0]}\n{result.stdout[-3000:]}\n{result.stderr[-3000:]}")
return result.stdout
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--prepare", action="store_true")
args = parser.parse_args()
directory = lab.LOCAL / "rust-world-parity"
directory.mkdir(parents=True, exist_ok=True)
report_path = directory / "report.json"
report_path.unlink(missing_ok=True)
if args.prepare:
lab.prepare()
lock, jars, libraries = test_rust_parity.verify_artifacts()
for earlier in directory.glob("run-*"): # one run per run used to stay behind: 5 GB here after a few weeks
shutil.rmtree(earlier, ignore_errors=True)
base = Path(tempfile.mkdtemp(prefix="run-", dir=directory))
classes = base / "classes"
classes.mkdir()
oracle = lab.ROOT / "mods/horizon/src/test/java/dev/horizonlab/parity/VanillaWorldOracle.java"
sources = [oracle, Path(__file__).resolve(), lab.ROOT / "tools/test_rust_parity.py", lab.ROOT / "Cargo.toml", lab.ROOT / "Cargo.lock", lab.ROOT / "crates/horizon-world/Cargo.toml",
*sorted((lab.ROOT / "crates/horizon-world").rglob("*.rs")), *sorted((lab.ROOT / "crates/horizon-core/src").rglob("*.rs"))]
tested_sources = test_rust_parity.source_hashes(sources)
dependency_path = os.pathsep.join(map(str, libraries))
run(["javac", "--release", "25", "-proc:none", "-cp", os.pathsep.join([str(jars["server"]), dependency_path]), "-d", classes, oracle], base / "compile-java.log")
def java(side, mode):
return run(["java", "-Xmx1G", "-cp", os.pathsep.join([str(classes), str(jars[side]), dependency_path]),
"dev.horizonlab.parity.VanillaWorldOracle", mode, base], base / f"{side}-{mode}.log")
print("Generating vanilla world-data fixtures...", flush=True)
print(java("server", "generate").split("HORIZON_WORLD_GENERATED")[-1].strip(), flush=True)
run(["cargo", "build", "--release", "-p", "horizon-world", "--bin", "horizon-world", "--example", "world_probe"], base / "compile-rust.log")
metadata = json.loads(run(["cargo", "metadata", "--no-deps", "--format-version", "1"]))
target = Path(metadata["target_directory"]) / "release"
suffix = ".exe" if os.name == "nt" else ""
binary = target / ("horizon-world" + suffix)
run([target / "examples" / ("world_probe" + suffix), "world_fixture", base], base / "rust-fixture.log")
for network in [False, True]:
kind = "network" if network else "nbt"
output = base / "rust" / kind
output.mkdir(parents=True)
for source in range(1, 5):
for destination in range(1, 5):
run([binary, "nbt-network-copy" if network else "nbt-copy", base / kind / f"{source}.bin",
output / f"{source}-{destination}.bin", source, destination])
for source in range(1, 5):
original = base / "regions" / str(source) / "r.-1.0.mca"
for variant in ["copy", "1", "2", "3", "4"]:
output = base / "rust/regions" / f"{source}-{variant}" / original.name
command = [binary, "copy-region", original, output]
if variant != "copy":
command.append(variant)
run(command)
output = base / "rust/regions" / f"{source}-edited" / original.name
run([binary, "edit-block-data", original, output, -512, -64, 0, "minecraft:stone"])
actual = run([binary, "get-block", output, -512, -64, 0]).strip()
if actual != "minecraft:stone":
raise AssertionError(f"CLI returned unexpected edited block: {actual}")
# A second export must fail without changing the existing output.
before = lab.digest(output, "sha256")
attempt = subprocess.run([str(binary), "copy-region", str(original), str(output)], capture_output=True, timeout=30)
if attempt.returncode == 0 or lab.digest(output, "sha256") != before:
raise AssertionError("Existing region output was not protected")
references = []
for side in ["server", "client"]:
print(f"Checking Rust-written world data in vanilla {side}...", flush=True)
output = java(side, "verify")
marker = "HORIZON_WORLD_RESULT "
results = [json.loads(line.split(marker, 1)[1]) for line in output.splitlines() if marker in line]
expected = {"nbt_pairs": 32, "palettes": 44, "palette_values": 123776,
"region_chunks": 48, "registry_states": 35723, "resident_values": 8192}
if len(results) != 1 or any(results[0].get(key) != value for key, value in expected.items()):
raise AssertionError(f"Incomplete world fixture: {results}")
references.append({"side": side, "jar_sha256": lab.digest(jars[side], "sha256"), **results[0]})
print(json.dumps(references[-1]), flush=True)
test_rust_parity.ensure_sources_unchanged(sources, tested_sources)
report = {"schema": 1, "minecraft": lock["minecraft"]["version"], "data_version": lab.version_info()["world_version"],
"platform": platform.platform(), "java": run(["java", "--version"]).splitlines()[0],
"rustc": run(["rustc", "--version"]).strip(), "references": references,
"sources_sha256": tested_sources,
"notes": ["Synthetic NBT and region fixtures serialized and verified by pinned vanilla classes.",
"Includes all four region codecs, external oversized chunks, UTF-16 edge cases, mixed lists, palette mutation, and the full exported block-state ID registry.",
"Storage-only edits; no world generation, simulation, lighting, live multiplayer, renderer, FPS or whole-game parity claim."]}
lab.write_json(report_path, report)
lab.write_json(base / "report.json", report)
print(f"World-data interoperability passed: {report_path}")
if __name__ == "__main__":
main()