#!/usr/bin/env python3 """Fast check after a change: one server boot, a sampled world, no full template. 1. Prepare the test server (mods, dependency check) and boot a fresh world "quick-check" with the recipe's seed, world type and gamerules. 2. Run the smoke checks (tools/smoke_checks.json) and the companion mod's in-game tests (`/cobblesurvival selftest`, e.g. the permadeath matrix; they play on a platform high above spawn). 3. `/locate` every structure in world/world.json → quick_check.absent_structures and fail if one is found (villages, ocean ruins, ... must not generate). 4. `/locate` the sample structures (quick_check.structures) and pregenerate a square of structure_radius around each, plus spawn_radius around spawn. 5. Force-load the chunks around the sampled village for a few seconds, as a nearby player would, so Lootr converts its loot chests. 6. Stop, scan the server log, and run tools/world_report.py --sample on the sampled world: no ore, no vanilla mobs or mob sources, only listed biomes, gamerules as in the recipe, and no vanilla loot chest left near the village. The border, strongholds-inside-border and the map need the whole island: tools/make_world.py checks those when the template is rebuilt. Usage: tools/quick_check.py [--accept-eula] (run tools/build.py first) """ import argparse import json import re import shutil import subprocess import sys import time from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from mcserver import ROOT, SELFTEST_JVM_ARGS, SERVER, Server, chunky, eula_accepted, prepare, scan_log # noqa: E402 LEVEL = "quick-check" FOUND = re.compile(r"is at \[(-?\d+), [^,\]]+, (-?\d+)\]") def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--accept-eula", action="store_true", help="write eula=true for the test server") args = parser.parse_args() if args.accept_eula: (SERVER / "eula.txt").parent.mkdir(parents=True, exist_ok=True) (SERVER / "eula.txt").write_text("eula=true\n") elif not eula_accepted(): sys.exit("build/server/eula.txt does not accept the Minecraft EULA yet; pass --accept-eula if you accept it.") started = time.time() recipe = json.loads((ROOT / "world/world.json").read_text()) sample = recipe["quick_check"] args_file = prepare() shutil.rmtree(SERVER / LEVEL, ignore_errors=True) props = {"level-name": LEVEL, "level-seed": recipe["seed"], "level-type": recipe["level_type"]} failures, village = [], None with chunky(recipe), Server(args_file, props, heap="8G", stdout_name="quick-check-stdout.log", jvm_args=SELFTEST_JVM_ARGS) as server: try: print(f"server started in {server.wait_ready():.0f}s") for rule, value in recipe.get("gamerules", {}).items(): server.command(f"gamerule {rule} {str(value).lower()}") failures += server.run_checks() failures += server.run_selftest() for structure in sample["absent_structures"]: reply = server.command(f"locate structure {structure}").strip() found = FOUND.search(reply) print(f" [{'FAIL' if found else 'ok'}] {structure} absent: {reply[:120]!r}") if found: failures.append(f"{structure} generates but should not: {reply}") areas = [("spawn", 0, 0, sample["spawn_radius"])] for structure in sample["structures"]: reply = server.command(f"locate structure {structure}").strip() found = FOUND.search(reply) if not found: print(f" note: {structure} not found near spawn, not sampled ({reply[:80]!r})") continue areas.append((structure, int(found[1]), int(found[2]), sample["structure_radius"])) for name, x, z, radius in areas: seconds = server.pregenerate(x, z, radius, report_every=0) print(f" sampled {name} at {x},{z} (radius {radius}) in {seconds:.0f}s") village = next(((x, z, r) for name, x, z, r in areas if name == "#minecraft:village"), None) if village: x, z, r = village server.command(f"forceload add {x - r} {z - r} {x + r} {z + r}") time.sleep(5) server.command(f"forceload remove {x - r} {z - r} {x + r} {z + r}") server.command("save-all flush") server.stop() except Exception as e: # report; the context manager still kills the process failures.append(f"{type(e).__name__}: {e}") failures += [f"log: {line}" for line in scan_log(server.log)] report = subprocess.run([sys.executable, "-I", str(ROOT / "tools/world_report.py"), str(SERVER / LEVEL), "--sample", "--json", str(ROOT / "build/quick-check-report.json")]) if report.returncode: failures.append("world report failed (see above)") if village: # Lootr only converts a chest once the 5x5 chunks around it are loaded, so check the inner part. x, z, r = village inner = r - 32 loot = json.loads((ROOT / "build/quick-check-report.json").read_text())["loot_containers"] left = [p for p in loot["unconverted_at"] if abs(p[0] - x) <= inner and abs(p[2] - z) <= inner] print(f" loot containers: {loot['lootr']} Lootr, {len(left)} vanilla left near the village") if left: failures.append(f"Lootr did not convert {len(left)} loot chests near the village: {left[:5]}") print(f"\nquick check took {time.time() - started:.0f}s") if failures: print("QUICK CHECK FAILED") print("\n".join(f" - {f}" for f in failures)) sys.exit(1) print("QUICK CHECK PASSED") if __name__ == "__main__": main()