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

105 lines
4.1 KiB
Python

"""Splitting the work into known results and tests still to run."""
from apworld_tester.core.model.blacklist import Blacklist
from apworld_tester.core.model.name import Name
from apworld_tester.core.state.keys import StateKeys
from apworld_tester.pipeline.batch.cache_check import CacheCheck
from apworld_tester.pipeline.batch.record import TestRecord
class Planner:
"""What is already answered, and what has to be generated.
The blacklist is checked before the cache and is unaffected by a
force flag: "do not retry" is the entire reason a world is listed,
so a flag that retried it would defeat the point.
"""
def __init__(self, config, settings, tests, tag, roms_fingerprint,
force, repeats, random_repeats, companion_range,
releases=None):
self.config = config
self.settings = settings
self.tests = tests
self.tag = tag
self.roms_fingerprint = roms_fingerprint
self.force = force
self.repeats = repeats
self.random_repeats = random_repeats
self.companion_range = companion_range
self.blacklist = Blacklist(config)
# Keyed on the folded name: the work list names a row by
# its download folder, while game_releases keys on the
# sheet's own wording, and the two differ wherever the
# sheet uses a colon or its own capitalisation.
self.releases = {
Name(name).directory: (release or {}).get("published_at")
for name, release in (releases or {}).items()
}
self.wrote_state = False
def record_for(self, item):
game_name, apworld_path, key, _ = item
return TestRecord(game_name, key,
is_core=apworld_path is None)
def decline(self, item):
"""Write and return the result for a declined world."""
_, _, key, current_hash = item
record = self.record_for(item)
self.tests[key] = record.blacklisted(
blacklist=self.blacklist,
current_hash=current_hash,
tag=self.tag,
roms_fingerprint=self.roms_fingerprint,
)
self.wrote_state = True
return record.as_result(self.tests[key])
def confirm(self, previous):
"""Note that this answer was looked at and still holds.
A cached result is not re-run, so its tested_at stays at the
run that reached it - which on a page refreshed today reads as
a world nobody has looked at in weeks rather than as a result
nothing has been able to change. This is the date that says
which of the two it is, and only a download failure was
recording it, so it was missing from every row that passed.
"""
previous[StateKeys.LAST_CHECKED_AT] = TestRecord.now()
self.wrote_state = True
def known(self, item):
"""This world's answer already, or None if it must be tested."""
game_name, _, key, current_hash = item
if self.blacklist.listed(game_name):
return self.decline(item)
previous = self.tests.get(key)
cache = CacheCheck(self.settings, previous)
served = not self.force and cache.serves(
current_hash=current_hash,
tag=self.tag,
roms_fingerprint=self.roms_fingerprint,
repeats=self.repeats,
random_repeats=self.random_repeats,
companion_range=self.companion_range,
# A core world has no release of its own here, so this
# is None for one and the rule cannot fire: what dates
# it is the Archipelago build, already compared above.
released_at=self.releases.get(game_name),
)
if not served:
return None
self.confirm(previous)
return self.record_for(item).as_result(previous)
def plan(self, work):
results, to_test = [], []
for item in work:
answer = self.known(item)
if answer is None:
to_test.append(item)
else:
results.append(answer)
return results, to_test