84 lines
3.1 KiB
Python
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)
|