Files
psxrecomp/runtime/tests/test_codegen_host_bios_stems.py
T
Alexandros Mandravillis c2f9c3d59a setup: detect any recompiled BIOS stem and stop the first-run setup loop
Focused port of the fork's fix/setup-host-bios-deadlock feature (10 commits) onto current upstream master, per the 2026-09-10 decision to port rather than rebase (the fork line and upstream share no merge base).

Content: the codegen host accepts any recompiled BIOS stem instead of only OpenBIOS/SCPH1001 (psx_bios_known_images.h); the runtime checks the setup host before validating a chosen BIOS and the first-run loop breaker reports a cause-specific reason; the CLI decodes subprocess output as UTF-8; --setup-selfcheck verifies a headless setup; the rbengine preflight names the recursive init. Two port conflicts resolved: the new CTest registrations in recompiler/CMakeLists.txt are kept (the names exist only in this change), and runtime.cmake takes the new recomp-ui guidance text. Cubic P3 finding fixed: the sibling PSX_NETPLAY FATAL_ERROR now also names --recursive.

Evidence: python syntax checks pass for psxrecomp_cli.py and the new tests; runtime/tests/test_codegen_host_bios_stems.py runs or skips only on a missing RECOMP_UI_ROOT. No full runtime build or retail route is claimed here. Developed with AI assistance (Cline, 2026-09-10).
2026-09-11 01:26:22 +03:00

128 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""Regression: the codegen host must accept ANY recompiled BIOS stem.
bios_backends_missing() in host/psxrecomp_codegen_host.c used to probe two
hardcoded filenames, psxrecomp/generated/OpenBIOS_dispatch.c and
SCPH1001_dispatch.c. Ports that pin another image via recompiler.bios_config /
PSXRECOMP_BIOS_STEMS emit <stem>_dispatch.c + <stem>_full.c instead, so the
probe could never be satisfied: Generate succeeded, the setup wizard reopened,
and first-run setup looped forever. Every wave-3 kit pins SCPH5552 and every
one of them shipped with that loop.
test_cli_retail_bios_profile.py already covers the psxrecomp_cli.py half of
this feature. This is the C host half, which had no coverage.
"""
from pathlib import Path
import os
import shutil
import subprocess
import sys
import tempfile
ROOT = Path(__file__).resolve().parents[2]
HOST_C = ROOT / "host" / "psxrecomp_codegen_host.c"
PROBE = """
#include <string.h>
#include <stdio.h>
#include "psxrecomp_codegen_host.h"
/* recomp-ui symbol the host references but sources_missing() never reaches. */
int recomp_launcher_relaunch_exe(char* o, size_t c) { (void)o; (void)c; return 0; }
int main(void) {
PsxrecompCodegenHostConfig cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.cmake_target = "psx-runtime";
cfg.exe_basename = "Probe";
cfg.gen_marker_relpath = "generated/SCUS_943.51_dispatch.c";
printf("%d", psxrecomp_codegen_host_sources_missing(&cfg));
return 0;
}
"""
def find_recomp_ui() -> Path | None:
env = os.environ.get("RECOMP_UI_ROOT")
candidates = ([Path(env)] if env else []) + [
ROOT.parent / "recomp-ui",
ROOT / "recomp-ui",
]
for c in candidates:
if (c / "src" / "recomp_launcher.h").is_file():
return c
return None
def make_project(root: Path, bios_files: list[str]) -> None:
"""A project tree the host recognises, with the given BIOS artefacts."""
(root / "generated").mkdir(parents=True, exist_ok=True)
(root / "generated" / "SCUS_943.51_dispatch.c").write_text("", encoding="utf-8")
fw_gen = root / "psxrecomp" / "generated"
fw_gen.mkdir(parents=True, exist_ok=True)
(root / "game.toml").write_text("[game]\n", encoding="utf-8")
(root / "psxrecomp" / "psxrecomp_cli.py").write_text("", encoding="utf-8")
for name in bios_files:
(fw_gen / name).write_text("", encoding="utf-8")
def main() -> int:
ui = find_recomp_ui()
if ui is None:
print("SKIP: recomp-ui not found (set RECOMP_UI_ROOT)")
return 0
cc = shutil.which("cc") or shutil.which("gcc") or shutil.which("clang")
if cc is None:
print("SKIP: no C compiler on PATH")
return 0
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
probe_c = tmp / "probe.c"
probe_c.write_text(PROBE, encoding="utf-8")
exe = tmp / ("probe.exe" if os.name == "nt" else "probe")
build = subprocess.run(
[cc, "-std=c11", "-o", str(exe), str(probe_c), str(HOST_C),
"-I", str(ROOT / "host"),
"-I", str(ROOT / "runtime" / "include"),
"-I", str(ui / "src"), "-I", str(ui / "src" / "common")],
capture_output=True, text=True)
if build.returncode != 0:
print("FAIL: could not build probe\n" + build.stderr[-2000:])
return 1
cases = [
# (BIOS artefacts present, expected sources_missing, why)
(["SCPH5552_dispatch.c", "SCPH5552_full.c"], 0,
"a pinned non-SCPH1001 stem must satisfy the host"),
(["OpenBIOS_dispatch.c", "OpenBIOS_full.c"], 0,
"the bundled stem must still satisfy the host"),
(["SCPH5552_dispatch.c"], 1,
"a dispatch with no _full.c is not a linkable backend"),
([], 1,
"no BIOS backend at all means setup is genuinely incomplete"),
]
failures = 0
for artefacts, expected, why in cases:
project = tmp / ("case%d" % len(artefacts + [why]))
shutil.rmtree(project, ignore_errors=True)
make_project(project, artefacts)
env = dict(os.environ, PSXRECOMP_PROJECT_ROOT=str(project))
run = subprocess.run([str(exe)], capture_output=True, text=True, env=env)
got = run.stdout.strip()
if got != str(expected):
print("FAIL: %s\n artefacts=%s expected=%s got=%r"
% (why, artefacts or "(none)", expected, got))
failures += 1
else:
print("ok: %s" % why)
if failures:
return 1
print("PASS: codegen host accepts any paired BIOS stem")
return 0
if __name__ == "__main__":
sys.exit(main())