Files
claudeandClaude Opus 5.5 398301f0be Cobblemon Survival Pack: phases 0-2 and Phase 3 zones and hazards
Free NeoForge 1.21.1 modpack on Cobblemon 1.8.4 built with packwiz, plus
the Kotlin companion mod (cobblesurvival) and its built-in data pack.

- Phase 0: packwiz pack with Modrinth/CurseForge cross-links, build and
  export tooling, Prism instance sync, test server with smoke checks.
- Phase 1: Poke Ball shaped island (WorldGen Editor preset, seed isle-26),
  Adrian's biome list, no natural ore, structures without ore or vanilla
  mobs, no Nether portals, world template builder and world report.
- Phase 2: permadeath (CobbleHardcoreMon plus the companion mod's gap
  fixes), Revival Charm, expensive Healing Machine, Lootr refreshing
  loot, in-game self-tests on Minecraft's GameTest framework.
- Phase 3 so far: 14 zones with their own spawn pools and level bands
  (tools/zone_layout.py, tools/zone_spawns.py, "zone" spawn condition),
  Cold Sweat heat and cold as gates with boss-drop insulation recipes,
  spawn survey and hazard self-tests in tools/quick_check.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:01:21 +01:00

102 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Server smoke test: boot a dedicated NeoForge server with the pack, check it
over RCON, scan the log, stop.
Steps
1. Install the NeoForge server named in pack/pack.toml (cached in build/.cache).
2. Install the server-side mods from build/stage/full with packwiz-installer
and check their dependency ranges (tools/check_deps.py).
3. Use the world template from build/world-template when it exists
(tools/make_world.py), otherwise generate a fresh island world.
4. Start the server with RCON on a free local port and wait for "Done (".
5. Run every check in tools/smoke_checks.json over RCON (a check may have
"setup" and "cleanup" command lists around its command).
6. Stop the server, then fail on log lines that match no entry in
tools/smoke_allowlist.txt.
Running a dedicated server needs eula=true in eula.txt. The script only writes
it when called with --accept-eula, which states that you accept the Minecraft
EULA (https://aka.ms/MinecraftEULA) for this test server.
--load-only needs no EULA. NeoForge discovers every mod jar and prints its mod
list before the server checks eula.txt, then stops at the EULA notice; mods
are only constructed after that check. So it proves the loader reads every
jar (with tools/check_deps.py covering dependency ranges), not that the mods
start; construction, world, datapack and RCON checks need the full run.
Usage: tools/smoke_test.py --accept-eula [--keep] [--timeout SECONDS]
tools/smoke_test.py --load-only
"""
import argparse
import json
import re
import shutil
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from mcserver import BUILD, ROOT, SERVER, Server, eula_accepted, prepare, scan_log # noqa: E402
TEMPLATE = BUILD / "world-template"
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")
parser.add_argument("--load-only", action="store_true", help="stop at the EULA check (no EULA needed)")
parser.add_argument("--keep", action="store_true", help="keep the server world between runs")
parser.add_argument("--timeout", type=int, default=900, help="seconds to wait for startup")
args = parser.parse_args()
if not args.accept_eula and not args.load_only:
sys.exit("Refusing to start: pass --accept-eula to accept the Minecraft EULA for this test server, "
"or --load-only to stop at the EULA check.")
args_file = prepare()
world = SERVER / "world"
if not args.keep:
shutil.rmtree(world, ignore_errors=True)
if (TEMPLATE / "level.dat").exists():
shutil.copytree(TEMPLATE, world)
print(f"using world template {TEMPLATE.relative_to(ROOT)}")
if args.load_only:
if eula_accepted():
sys.exit("eula.txt already accepts the EULA here; run the full test with --accept-eula instead.")
else:
(SERVER / "eula.txt").write_text("eula=true\n")
recipe = json.loads((ROOT / "world/world.json").read_text())
failures = []
with Server(args_file, {"level-type": recipe["level_type"], "level-seed": recipe["seed"]},
stdout_name="smoke-stdout.log") as server:
try:
if args.load_only:
server.proc.wait(timeout=args.timeout)
text = server.log.read_text(errors="replace") if server.log.exists() else ""
for pattern, what in ((r"Cobblemon Survival Pack Companion \S+ \(cobblesurvival\)",
"companion mod in the mod list"),
(r"You need to agree to the EULA", "stop at the EULA check")):
if not re.search(pattern, text):
failures.append(f"missing {what} in the log")
print(f"mod discovery finished in {time.time() - server.started:.0f}s")
else:
print(f"server started in {server.wait_ready(args.timeout):.0f}s")
failures += server.run_checks()
server.stop()
except Exception as e: # report; the context manager still kills the process
failures.append(f"{type(e).__name__}: {e}")
if server.log.exists():
failures += [f"log: {line}" for line in scan_log(server.log)]
if failures:
print("\nSMOKE TEST FAILED")
print("\n".join(f" - {f}" for f in failures))
print(f" log: {server.log}")
sys.exit(1)
print("\nLOAD CHECK PASSED (mod discovery only; the full smoke test still needs --accept-eula)"
if args.load_only else "\nSMOKE TEST PASSED")
if __name__ == "__main__":
main()