initial commit
This commit is contained in:
83
src/apworld_tester/pipeline/generation/companion_check.py
Normal file
83
src/apworld_tester/pipeline/generation/companion_check.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user