From d5e52a9525d8bd2124c1e3ba63e486e402e5e522 Mon Sep 17 00:00:00 2001 From: Alexbeav <7005125+Alexbeav@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:39:53 +0200 Subject: [PATCH] fix: require fresh reports from each kit startup --- README.md | 3 +- src/pin_builder/workbench_package.py | 2 + tests/test_kit_start_reports.py | 60 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/test_kit_start_reports.py diff --git a/README.md b/README.md index 0d903c2..00d3929 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,5 @@ built with it, or their updates, features, or game content, including early acce Matthew Stanley has authority from the relevant copyright holders to grant these permissions for the project's PolyForm-covered code. Third-party -components and code explicitly licensed under other terms retain those terms. +components and code explicitly licensed under other terms retain those terms. +Optional startup checks remove previous run reports before starting the kit, so a recent old report cannot substitute for fresh frames. diff --git a/src/pin_builder/workbench_package.py b/src/pin_builder/workbench_package.py index b198383..c6fb66f 100644 --- a/src/pin_builder/workbench_package.py +++ b/src/pin_builder/workbench_package.py @@ -168,6 +168,8 @@ def kit_exe_start(kit, exe_name, disc, bios, env, output, receipt, name='b4-kit- receipt.setdefault('commands', []).append(command) own = output / (name + '-host') start_env = pb.host_start_environment({key: value for key, value in env.items() if not key.startswith('PSX')}, own) | forced + for folder in (kit, product.parent): + (folder / RUN_REPORT).unlink(missing_ok=True) began = time.time() code, text = start_setup_program(executable, kit, start_env, log, KIT_START_SECONDS, arguments=argv[1:]) command['exit_code'] = code diff --git a/tests/test_kit_start_reports.py b/tests/test_kit_start_reports.py new file mode 100644 index 0000000..595a0dd --- /dev/null +++ b/tests/test_kit_start_reports.py @@ -0,0 +1,60 @@ +"""Kit startup must consume this start's report, never a recent previous one.""" +import json +from pathlib import Path +import tempfile +import unittest +from unittest import mock +from pin_builder import pin_build as builder, workbench_package as package + + +class KitStartReportTests(unittest.TestCase): + def run_start(self, root, in_product, fresh): + kit = root / "kit" + product = kit / "build-release" / "game.exe" + product.parent.mkdir(parents=True) + output = root / "evidence" + output.mkdir() + folder = product.parent if in_product else kit + old = folder / package.RUN_REPORT + old.write_text(json.dumps({"frame": 999, "exit_origin": "exit_after_ms"}), encoding="utf-8") + calls = 0 + + def processes(_executable): + nonlocal calls + calls += 1 + if calls == 1: + return [] + if calls == 2: + return [{"pid": 42, "window": 0, "command_line": f'"{product}" --bios "bios.bin" --disc "disc.iso"'}] + if fresh: + old.write_text(json.dumps({"frame": 1234, "exit_origin": "exit_after_ms"}), encoding="utf-8") + return [] + + def start(_executable, _cwd, _env, log, *_args, **_kwargs): + text = package.HANDOVER_LINE + "\n" + str(product) + "\n" + Path(log).write_text(text, encoding="utf-8") + return 0, text + + with mock.patch.object(package, "start_setup_program", side_effect=start), \ + mock.patch.object(package, "product_processes", side_effect=processes), \ + mock.patch.object(package, "stop_process") as stop: + if fresh: + result = package.kit_exe_start(kit, "game", "disc.iso", "bios.bin", {}, output, + {"commands": []}, stage=2, wait=0.1, poll=0, frames_wait=1) + self.assertEqual(result["frames"], 1234) + else: + with self.assertRaisesRegex(builder.BuildStop, "left no fresh"): + package.kit_exe_start(kit, "game", "disc.iso", "bios.bin", {}, output, + {"commands": []}, stage=2, wait=0.1, poll=0, frames_wait=1) + self.assertFalse(old.exists()) + stop.assert_not_called() + + def test_recent_previous_reports_are_removed_at_both_locations(self): + for in_product in (False, True): + with self.subTest(in_product=in_product), tempfile.TemporaryDirectory() as temp: + self.run_start(Path(temp), in_product, False) + + def test_fresh_report_is_accepted_at_both_locations(self): + for in_product in (False, True): + with self.subTest(in_product=in_product), tempfile.TemporaryDirectory() as temp: + self.run_start(Path(temp), in_product, True)