Public Access
122 lines
5.2 KiB
Python
122 lines
5.2 KiB
Python
#!/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()
|