Files
APWorldTester/src/apworld_tester/pipeline/generation/companion_check.py
2026-09-06 15:12:38 +02:00

84 lines
3.1 KiB
Python

"""Which core worlds can actually generate in this container."""
import os
import subprocess
from apworld_tester.core.config.run_settings import RunSettings
from apworld_tester.pipeline.generation.command import ContainerCommand
from apworld_tester.pipeline.generation.report import DriverReport
class CompanionCheck:
"""Tries every core world once, so the rest can trust the pool.
Several core worlds need a base ROM this container has no copy of,
and drawing one fails the seed - with the failure landing on the
apworld under test rather than on the companion that caused it.
"""
#: The whole verification's ceiling, and each world's share of it.
#: Not configuration: the per-world limit is deliberately shorter
#: than a generation timeout, because a companion slow to generate
#: would multiply that cost across every seed that draws it.
TIMEOUT = 1800
PER_WORLD_TIMEOUT = 120
def __init__(self, config, paths, image):
self.config = config
self.paths = paths
self.image = image
self.command = ContainerCommand(config, paths, image)
def build(self, output_dir, per_world_timeout, spoiler):
verify_in = ContainerCommand.VERIFY_DRIVER_PATH
return [
*self.command.prefix,
"-v", f"{self.paths.verify_driver}:{verify_in}:ro",
"-v", (f"{self.paths.driver}:"
f"{ContainerCommand.DRIVER_PATH}:ro"),
"-v", (f"{output_dir}:"
f"{ContainerCommand.OUTPUT_DIRECTORY}"),
*self.command.rom_mounts,
*self.command.common_client_mount,
self.image,
verify_in,
"--output-dir", ContainerCommand.OUTPUT_DIRECTORY,
"--timeout", str(per_world_timeout),
"--spoiler", str(spoiler),
]
@staticmethod
def not_verified(detail):
return {"ran": False, "verified": [], "rejected": {},
"detail": detail}
def parse(self, result):
data = DriverReport(result).data
if data is not None:
data.setdefault("ran", True)
return data
return self.not_verified(
f"no JSON output (exit {result.returncode}): "
f"{DriverReport.tail(result.stderr)}")
def run(self, output_dir, timeout=None, per_world_timeout=None,
spoiler=None):
"""The pool of usable companions, or why there is none."""
timeout = timeout or self.TIMEOUT
per_world_timeout = per_world_timeout or self.PER_WORLD_TIMEOUT
if spoiler is None:
spoiler = RunSettings(self.config).spoiler
output_dir = os.path.abspath(output_dir)
os.makedirs(output_dir, exist_ok=True)
try:
result = subprocess.run(
self.build(output_dir, per_world_timeout, spoiler),
capture_output=True,
text=True,
timeout=timeout,
)
except subprocess.TimeoutExpired:
return self.not_verified("companion verification did not "
f"finish within {timeout}s")
return self.parse(result)