#!/usr/bin/env python3 """Small dual-target dedicated smoke. Worlds are disposable; stalled checks hand off.""" import argparse from server_test_lock import server_test_lock import json import os from pathlib import Path import shutil import signal import subprocess import tempfile import time ROOT = Path(__file__).resolve().parents[1] class StalledTest(RuntimeError): pass def wait_for(process, log, marker, timeout=60): started = time.monotonic() while time.monotonic() - started < timeout: text = log.read_text(errors="replace") if log.exists() else "" if marker in text: return text if process.poll() is not None: raise RuntimeError(f"Server exited before {marker!r}; inspect {log}") time.sleep(0.25) raise StalledTest(f"No result for {marker!r} within {timeout}s; stop and hand off, do not retry") def stop(process): if process is None or process.poll() is not None: return try: process.stdin.write("stop\n") process.stdin.flush() process.wait(timeout=25) except (BrokenPipeError, subprocess.TimeoutExpired): os.killpg(process.pid, signal.SIGTERM) try: process.wait(timeout=10) except subprocess.TimeoutExpired: os.killpg(process.pid, signal.SIGKILL) process.wait() def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--target", choices=("mc1211", "mc263"), required=True) args = parser.parse_args() logs = ROOT / "build/verification" / args.target logs.mkdir(parents=True, exist_ok=True) work = Path(tempfile.mkdtemp(prefix=f"rivencraft-{args.target}-smoke-")) settings = {"layers": [{"height": 1, "block": "minecraft:bedrock"}, {"height": 2, "block": "minecraft:dirt"}, {"height": 1, "block": "minecraft:grass_block"}], "biome": "minecraft:plains"} (work / "eula.txt").write_text("eula=true\n") (work / "server.properties").write_text( "server-ip=127.0.0.1\nserver-port=0\nonline-mode=false\nwhite-list=false\n" "view-distance=2\nsimulation-distance=2\ngenerate-structures=false\n" "level-type=minecraft:flat\nlevel-seed=263100\ngenerator-settings=" + json.dumps(settings) + "\n") command = [str(ROOT / "gradlew")] if args.target == "mc263": command += ["-p", str(ROOT / "ports/mc263")] command += ["--no-daemon", "runServer", f"-PserverRunDir={work}", "--console=plain"] process = None try: for phase in ("first", "restart"): log = logs / f"{phase}.log" with log.open("w") as output: process = subprocess.Popen(command, cwd=ROOT, stdin=subprocess.PIPE, stdout=output, stderr=subprocess.STDOUT, text=True, start_new_session=True) wait_for(process, log, "Done (") if args.target == "mc263": if phase == "first": for name, seed in (("smoke_a", 123456789), ("smoke_b", 987654321)): process.stdin.write(f"rivencraft_port age create {name} {seed} flat\n") process.stdin.flush() wait_for(process, log, f"Created rivencraft:age/{name} with seed {seed}") else: text = log.read_text() for name, seed in (("smoke_a", 123456789), ("smoke_b", 987654321)): if f"Loaded 26.3 prototype Age rivencraft:age/{name} with seed {seed}" not in text: raise RuntimeError("Age identity/seed failed full restart") process.stdin.write("rivencraft_port age list\n") process.stdin.flush() process.stdin.write("save-all flush\n") process.stdin.flush() wait_for(process, log, "Saved the game") else: # 1.21.1's Gradle launcher does not forward this pipe. Its # unchanged gameplay uses the recorded/manual regression; # this smoke checks startup and graceful process shutdown. os.killpg(process.pid, signal.SIGINT) if args.target == "mc1211": try: process.wait(timeout=25) except subprocess.TimeoutExpired: stop(process) else: stop(process) if process.returncode not in (0, 130, -signal.SIGINT): raise RuntimeError(f"Server exited with {process.returncode}; inspect {log}") process = None print(f"PASS {args.target} {phase}", flush=True) except StalledTest as error: print(f"HANDOFF: {error}. Manual check: start {args.target}, create two Ages, travel, " "restart and revisit both; confirm names/seeds and return travel.", flush=True) raise SystemExit(2) finally: stop(process) shutil.rmtree(work) print(f"Removed temporary world; logs: {logs}", flush=True) if __name__ == "__main__": with server_test_lock(): main()