tests: make unregistered tests impossible, and repair the ones that rotted

PR #134 fixed test_netplay_snap_ring.c's harness. This addresses why it broke:
its only build instructions were a gcc command line in a header comment, and a
comment cannot fail. 16 of the 41 C/C++ tests in runtime/tests/ were in that
state, plus 3 python guards. Four had already rotted against the runtime API.

Worse, two tests that WERE registered did not link on origin/master --
sio_dualshock_rumble_test (psx_read_word / psx_get_cycle_count / i_mask /
g_debug_last_store_pc) and spu_fidelity_test (crc32_update) -- so `ctest` in a
runtime tree did not build at all.

Registering them fixes the instance. check_test_registration.cmake fixes the
class: it reads both CMakeLists.txt files as text and fails configure on any
test file that is neither registered nor listed with a reason. Reading them as
text (rather than adding a top-level CMakeLists.txt) keeps the BIOS-free
recompiler tree configurable, which is the only tree a newcomer can configure;
both projects call the guard, so each catches orphans in the other.
CONFIGURE_DEPENDS matters: without it, ADDING a test would not re-run configure.

Also incorporates TechnicallyComputers' review of #134. -Werror turns a
pre-existing Linux warning into a hard failure: clock_gettime/CLOCK_MONOTONIC
need _POSIX_C_SOURCE under a strict -std=cNN. Both halves are needed -- the
define in netplay_snap_ring.c fixes a direct compile of that TU, but not the
harness, which includes <stdio.h> first and so has already latched glibc's
<features.h>. Verified on Ubuntu 24.04 / GCC 13.3.

Test-file changes:
  * repaired 4 rotted tests (stubs / a changed parameter list), no logic edits
  * build recipes replaced with `ctest -R <name>`, so exactly one description
    of how a test builds exists and the build executes it
  * known-failing tests registered DISABLED, not de-registered -- de-registering
    is the mechanism that hid these in the first place; DISABLED keeps them in
    `ctest -N` and countable without turning the run red
  * sio_card_protocol_test runs in a build-tree scratch dir; it writes
    test_dir/card1.mcd, and that is a TRACKED fixture
  * removed three committed test binaries and widened the .gitignore rule that
    missed them (it only covered *.exe, and these were Linux builds)

Disabled, each with a bead: sio_card_protocol_test (147/309),
sio_dualshock_rumble_test (27), spu_fidelity_test (4 ADSR), aot_overlay_discovery
(cwd), and the 3 pre-existing perf guards.

Verified:
  * adding an unregistered test file fails the build with an actionable message;
    removing it passes
  * runtime ctest 32/32 pass, 3 disabled
  * recompiler ctest: same 5 failures as a pristine origin/master baseline
  * Tomba, Tomba 2 and MMX6 build against this framework and were each driven
    240s of attract + randomised pad fuzz (~195 injections, 47+ combos):
    59.6 fps sustained, zero stalls, all three reached real gameplay

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-08-11 14:34:12 -07:00
parent 6c641db0ea
commit fe22c12f7b
18 changed files with 510 additions and 79 deletions
+12 -1
View File
@@ -111,8 +111,19 @@ games/*/*
# Session-scratch debug scripts (hardcoded local paths; kept on disk, not tracked)
tools/_options_session/
# Compiled test binaries (build artifacts; the runtime/tests/*.c sources are tracked)
# Compiled test binaries (build artifacts; the runtime/tests/*.c sources are tracked).
# The *.exe rule alone missed extension-less Linux binaries, and three of them
# (test_netplay_snap_ring, test_netplay_hash_confirm, test_netplay_input_hist)
# were committed as a result — the snap-ring one from before boot_state grew its
# VRAM-incremental queries, so it could no longer be rebuilt from the tree it sat
# in. Ignore everything matching test_* here except the tracked sources/fixtures.
runtime/tests/*.exe
runtime/tests/test_*
!runtime/tests/test_*.c
!runtime/tests/test_*.cpp
!runtime/tests/test_*.py
!runtime/tests/test_*.mcd
!runtime/tests/test_dir/
# transient split-gen build dirs
recompiler/build-splitgen/
+95 -47
View File
@@ -309,29 +309,29 @@ if(BUILD_TESTING)
lib/ELFIO
)
target_link_libraries(recompiler_patch_test PRIVATE fmt rabbitizer)
add_test(NAME recompiler_patch_test COMMAND recompiler_patch_test)
add_executable(full_function_emitter_test
tests/full_function_emitter_test.cpp
src/ps1_exe_parser.cpp
src/mips_decoder.cpp
src/strict_translator.cpp
src/bios_address_model.cpp
src/function_discovery.cpp
src/full_function_emitter.cpp
src/config_loader.cpp
src/recompiler_patch.cpp
)
target_include_directories(full_function_emitter_test PRIVATE
include
src
lib/rabbitizer/include
lib/rabbitizer/cplusplus/include
lib/fmt/include
lib/toml11
)
target_link_libraries(full_function_emitter_test PRIVATE fmt rabbitizer)
add_test(NAME full_function_emitter_test COMMAND full_function_emitter_test)
add_test(NAME recompiler_patch_test COMMAND recompiler_patch_test)
add_executable(full_function_emitter_test
tests/full_function_emitter_test.cpp
src/ps1_exe_parser.cpp
src/mips_decoder.cpp
src/strict_translator.cpp
src/bios_address_model.cpp
src/function_discovery.cpp
src/full_function_emitter.cpp
src/config_loader.cpp
src/recompiler_patch.cpp
)
target_include_directories(full_function_emitter_test PRIVATE
include
src
lib/rabbitizer/include
lib/rabbitizer/cplusplus/include
lib/fmt/include
lib/toml11
)
target_link_libraries(full_function_emitter_test PRIVATE fmt rabbitizer)
add_test(NAME full_function_emitter_test COMMAND full_function_emitter_test)
add_executable(recompiler_patch_cxx17_header_test
tests/recompiler_patch_cxx17_header_test.cpp
@@ -398,18 +398,18 @@ if(BUILD_TESTING)
NAME overlay_shim_compile_contract
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_overlay_shim_compile.py)
add_test(
NAME dirty_text_continuation_guards
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_dirty_text_continuation_guards.py)
add_test(
NAME bios_backend_constant_codegen
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_backend_constant_codegen.py
--recompiler $<TARGET_FILE:psxrecomp-bios>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
# ---- codegen tests that were written but never registered -----------
add_test(
NAME dirty_text_continuation_guards
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_dirty_text_continuation_guards.py)
add_test(
NAME bios_backend_constant_codegen
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_backend_constant_codegen.py
--recompiler $<TARGET_FILE:psxrecomp-bios>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
# ---- codegen tests that were written but never registered -----------
# These existed in recompiler/tests/ with no add_test() entry, so they
# had no way to run and no way to fail. Each was executed manually
# before being wired here; all pass. They drive psxrecomp-game, so they
@@ -430,6 +430,24 @@ if(BUILD_TESTING)
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endforeach()
# test_aot_overlay_discovery.py had no add_test() entry at all. It runs
# far enough to exercise real discovery, then fails in
# check_real_batched_fragment_publication: that check reaches
# MOD.compile_fragment_batch, which spawns psxrecomp-game with cwd set
# to its own TemporaryDirectory, so BIOS-profile resolution (which
# probes bios/SCPH1001.toml relative to the working directory) finds
# nothing and returns "recompiler-error: no BIOS profile found". Every
# other subprocess in the same file passes cwd=str(ROOT) explicitly, so
# this looks like a missed cwd rather than a product defect — but it
# needs an owner, and DISABLED keeps it counted until then.
add_test(
NAME aot_overlay_discovery
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_aot_overlay_discovery.py
--recompiler $<TARGET_FILE:psxrecomp-game>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
set_tests_properties(aot_overlay_discovery PROPERTIES DISABLED TRUE)
# ---- runtime source-invariant guards --------------------------------
# These live in runtime/tests/ but only READ runtime source, so they
# need neither a generated BIOS nor a built runtime. Registering them
@@ -438,21 +456,11 @@ if(BUILD_TESTING)
# configure before regenerating a BIOS. Each was run manually first;
# all pass.
#
# Deliberately NOT registered, because they currently FAIL — see
# docs/TESTING.md for the detail. Wiring a known-failing test would
# recreate the exact problem that took CI off PRs (a red check nobody
# trusts), so these need an owner's decision first:
# test_interpreter_perf_guards.py - psx_devices_mmio_sync's
# recompute-deadline branch never clears g_psx_cycle_fast_limit,
# and the guard also still names the pre-rename
# s_next_service_cycle
# test_runtime_perf_diag_guards.py - asserts a substring no longer
# present in the runtime source
# test_overlay_pair_dedup_runtime.py - needs its companion harness
# built; not a source-only guard
foreach(_t IN ITEMS
bios_selection_guards
capture_history
default_renderer_guards
mod_owned_display_controls
fmv_quiet_guards
fps_telemetry_opt_in
frame_interpolation_display_rate
@@ -479,6 +487,38 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_${_t}.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endforeach()
# ---- known-failing guards: DISABLED, not de-registered ---------------
# These three were previously left out of the list above with a comment
# explaining that they FAIL and that wiring a red check would recreate
# the problem that took CI off PRs. That reasoning is right; the remedy
# was not. De-registering is precisely how a test stops being counted,
# and it is the mechanism that let 16 C/C++ tests rot in runtime/tests/.
#
# ctest's DISABLED property is the intended tool: the test stays
# registered, stays visible in `ctest -N`, and is reported as Disabled
# rather than silently absent — without turning the run red. Delete the
# matching set_tests_properties line to re-enable one once it is fixed.
#
# interpreter_perf_guards psx_devices_mmio_sync's recompute-deadline
# branch never clears g_psx_cycle_fast_limit,
# and the guard still names the pre-rename
# s_next_service_cycle
# runtime_perf_diag_guards asserts a substring no longer present in
# the runtime source
# overlay_pair_dedup_runtime needs its companion harness built; not a
# source-only guard
foreach(_t IN ITEMS
interpreter_perf_guards
runtime_perf_diag_guards
overlay_pair_dedup_runtime)
add_test(
NAME ${_t}
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_${_t}.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
set_tests_properties(${_t} PROPERTIES DISABLED TRUE)
endforeach()
endif()
endif()
@@ -495,6 +535,14 @@ target_include_directories(reachable_discovery_test PRIVATE
)
target_link_libraries(reachable_discovery_test PRIVATE fmt)
# ---- test-registration completeness guard -----------------------------------
# Reads both CMakeLists.txt files as text, so it catches an orphan in either
# project from whichever one is being configured. Placed before the collision
# guard, which must stay last because it needs every target declared; this one
# inspects files on disk and has no such ordering requirement.
include(${CMAKE_CURRENT_SOURCE_DIR}/../runtime/check_test_registration.cmake)
psxrecomp_check_all_tests_registered()
# ---- output-name collision guard (must stay LAST) ---------------------------
# Fails configure if any two targets here would write the same file on a
# case-insensitive filesystem. See the module header for the shipped-bug story.
+187 -1
View File
@@ -52,11 +52,22 @@ if(BUILD_TESTING)
PSX_OVERLAY_DLL_BUILD=1)
add_test(NAME psx_icache_fastpath_test COMMAND psx_icache_fastpath_test)
# Registered since before this change, but it did NOT LINK on origin/master:
# sio.c had grown psx_read_word / psx_get_cycle_count / i_mask /
# g_debug_last_store_pc references that this test's stub block never gained,
# so `ctest` in a runtime tree could not even build. The stubs are added now
# (a registered test that cannot compile is worse than an unregistered one —
# it breaks the whole suite's build), which exposes a real disagreement:
# 27 checks fail, starting with enter.id expected=0x73 (DualShock analog ID)
# actual=0xFF (no device). Constant-clock and monotonic-clock stubs give
# byte-identical results, so the cycle stub is not the discriminator — this
# is the SIO/peripheral axis, not the harness. DISABLED until owned.
add_executable(sio_dualshock_rumble_test
tests/test_sio_dualshock_rumble.c
src/sio.c)
target_include_directories(sio_dualshock_rumble_test PRIVATE include)
add_test(NAME sio_dualshock_rumble_test COMMAND sio_dualshock_rumble_test)
set_tests_properties(sio_dualshock_rumble_test PROPERTIES DISABLED TRUE)
add_executable(gte_register_access_test
tests/test_gte_register_access.cpp
@@ -99,9 +110,20 @@ if(BUILD_TESTING)
# generator, volume sweeps / direct decode, capture buffers, and
# savestate coverage of all the new state. Includes src/spu.c directly
# (white-box) and stubs its externs.
add_executable(spu_fidelity_test tests/test_spu_fidelity.c)
# src/crc32.c is linked rather than stubbed: spu.c's capture/shadow paths
# feed real CRCs that this test checks, so a zero-returning stub would make
# those assertions vacuous. (This target did not link at all before —
# crc32_update was undefined on origin/master too.)
add_executable(spu_fidelity_test tests/test_spu_fidelity.c src/crc32.c)
target_include_directories(spu_fidelity_test PRIVATE include)
add_test(NAME spu_fidelity_test COMMAND spu_fidelity_test)
# Same story as sio_dualshock_rumble_test: registered but unlinkable on
# origin/master (crc32_update undefined), so it never ran. With crc32 linked
# it now runs 137 checks and fails 4, all in one ADSR sweep region
# (mid > 0, late > mid, fall_mid < 0x7FFE, fall_late < fall_mid). Rebuilding
# it against a zero-returning crc32 stub reproduces exactly those 4, so the
# crc32 linkage is not the cause — the envelope sweep genuinely disagrees.
set_tests_properties(spu_fidelity_test PROPERTIES DISABLED TRUE)
add_executable(overlay_capture_retry_test
tests/test_overlay_capture_retry.cpp
@@ -201,8 +223,172 @@ if(BUILD_TESTING)
add_test(NAME autocompile_degraded_test
COMMAND autocompile_degraded_test)
endif()
# ---- previously-unregistered standalone tests -------------------------
# These 16 existed in runtime/tests/ with no add_test() entry. Their only
# build instructions were a gcc command line in each file's header comment,
# which nothing executes — so when the runtime API moved under them they
# broke silently and stayed broken. Four had already rotted when they were
# collected here (2026-08-11):
#
# test_netplay_snap_ring.c duplicate TU in the documented command;
# no stubs for the VRAM-incremental
# boot_state queries (PR #134)
# test_psx_cycle_event_boundaries.c
# psx_cycles_resync_after_restore() grew a
# CPUState* parameter; psx_cycles.c grew
# three externs the stub block never did
# test_spu_end_without_repeat.c spu.c grew psx_irq_raise / crc32_update
# test_sio_card_protocol.c builds, but 147 of 309 checks fail —
# registered DISABLED below, see beads
#
# Registering them is only half the fix; check_test_registration.cmake at
# the end of this file is what stops the next one from going unnoticed.
# Each test's build recipe now lives HERE and nowhere else — the header
# comments were reduced to a `ctest -R <name>` pointer so there is exactly
# one description of how a test is built, and it is one the build executes.
add_executable(cdrom_irq_mask_test tests/test_cdrom_irq_mask.c)
target_include_directories(cdrom_irq_mask_test PRIVATE src)
add_test(NAME cdrom_irq_mask_test COMMAND cdrom_irq_mask_test)
# Mirrors func_00005A08 (BIOS card READ chain handler) against a mock CPU,
# so it pins the disassembly reading without linking the recompiled BIOS.
add_executable(chain_handler_check_test tests/test_chain_handler_check.c)
target_include_directories(chain_handler_check_test PRIVATE include src)
add_test(NAME chain_handler_check_test COMMAND chain_handler_check_test)
# Bug B: the pacing decision must take ONE counter sample, so the unsigned
# subtraction cannot underflow into a ~24.7-day SDL_Delay.
add_executable(frame_pacing_test tests/test_frame_pacing.c)
target_include_directories(frame_pacing_test PRIVATE include)
add_test(NAME frame_pacing_test COMMAND frame_pacing_test)
add_executable(gpu_primitive_reject_test tests/test_gpu_primitive_reject.c)
target_include_directories(gpu_primitive_reject_test PRIVATE include)
add_test(NAME gpu_primitive_reject_test COMMAND gpu_primitive_reject_test)
add_executable(gpu_sw_edges_test tests/test_gpu_sw_edges.c)
target_include_directories(gpu_sw_edges_test PRIVATE src)
add_test(NAME gpu_sw_edges_test COMMAND gpu_sw_edges_test)
add_executable(gpu_vk_upload_alignment_test
tests/test_gpu_vk_upload_alignment.c)
target_include_directories(gpu_vk_upload_alignment_test PRIVATE src)
add_test(NAME gpu_vk_upload_alignment_test
COMMAND gpu_vk_upload_alignment_test)
add_executable(netplay_hash_confirm_test
tests/test_netplay_hash_confirm.c
src/netplay_hash_confirm.c)
target_include_directories(netplay_hash_confirm_test PRIVATE include)
add_test(NAME netplay_hash_confirm_test COMMAND netplay_hash_confirm_test)
# Single TU: the test #includes src/netplay_snap_ring.c and stubs
# boot_state, so the .c must NOT also be listed here (that was PR #134's
# duplicate-symbol bug, in its documented command).
add_executable(netplay_snap_ring_test tests/test_netplay_snap_ring.c)
target_include_directories(netplay_snap_ring_test PRIVATE include)
target_compile_definitions(netplay_snap_ring_test PRIVATE
NETPLAY_SNAP_RING_TEST_STUB_BOOT_STATE)
add_test(NAME netplay_snap_ring_test COMMAND netplay_snap_ring_test)
add_executable(overlay_store_cycle_barrier_test
tests/test_overlay_store_cycle_barrier.c)
target_include_directories(overlay_store_cycle_barrier_test PRIVATE include)
add_test(NAME overlay_store_cycle_barrier_test
COMMAND overlay_store_cycle_barrier_test)
add_executable(psx_cycle_event_boundaries_test
tests/test_psx_cycle_event_boundaries.c
src/psx_cycles.c)
target_include_directories(psx_cycle_event_boundaries_test PRIVATE include)
add_test(NAME psx_cycle_event_boundaries_test
COMMAND psx_cycle_event_boundaries_test)
add_executable(rb_post_tip_filter_test tests/test_rb_post_tip_filter.c)
target_include_directories(rb_post_tip_filter_test PRIVATE include)
add_test(NAME rb_post_tip_filter_test COMMAND rb_post_tip_filter_test)
add_executable(spu_end_without_repeat_test
tests/test_spu_end_without_repeat.c)
target_include_directories(spu_end_without_repeat_test PRIVATE include)
add_test(NAME spu_end_without_repeat_test
COMMAND spu_end_without_repeat_test)
add_executable(spu_gaussian_test tests/test_spu_gaussian.c)
target_include_directories(spu_gaussian_test PRIVATE include)
add_test(NAME spu_gaussian_test COMMAND spu_gaussian_test)
# DISABLED, not de-registered. It links and runs, but 147 of its 309 checks
# fail: the no-card path is clean (141/141) while the card-present READ
# reply stream comes back shifted by one and then stuck (idx[1] 0xFF for an
# expected 0x08 FLAG, then 0x08 for every subsequent byte). That is either a
# real sio.c protocol regression or a stale expectation in the test, and it
# needs an owner either way.
#
# De-registering a failing test is exactly the mechanism that let these 16
# rot in the first place, and wiring it RED would recreate the red-check-
# nobody-trusts problem that took CI off PRs. DISABLED keeps it built, keeps
# it listed in `ctest -N`, and keeps it countable, without failing the run.
# Re-enable by deleting the set_tests_properties line once triaged.
add_executable(sio_card_protocol_test
tests/test_sio_card_protocol.c
src/sio.c
src/memcard.c)
target_include_directories(sio_card_protocol_test PRIVATE include src)
# Runs in a build-tree scratch dir, NOT in tests/: the test writes
# test_dir/card1.mcd relative to the working directory (self-provisioning it
# via mkdir if absent), and tests/test_dir/card1.mcd is a TRACKED fixture —
# running it from tests/ overwrites that file and leaves the repo dirty.
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sio_card_protocol_scratch)
add_test(NAME sio_card_protocol_test COMMAND sio_card_protocol_test
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}/sio_card_protocol_scratch)
set_tests_properties(sio_card_protocol_test PROPERTIES DISABLED TRUE)
# POSIX-only: dlopen()s a fixture .so out of a synthetic cache tree, so it
# cannot link under MinGW/MSVC (-ldl). run_overlay_posix_test.sh already
# stages the cache tree, builds the fixture and the test, and runs it — so
# register THAT rather than reimplementing its fixture staging here.
if(UNIX)
find_program(BASH_EXECUTABLE bash)
if(BASH_EXECUTABLE)
add_test(NAME overlay_posix_test
COMMAND ${BASH_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tests/run_overlay_posix_test.sh)
else()
message(STATUS "overlay_posix_test: bash not found - skipped")
endif()
endif()
# recomp-net is an optional submodule; the netplay input-history contract
# cannot be compiled without it. Conditional, not skipped silently: the
# registration guard is told about it via the same EXISTS check.
if(EXISTS "${PSXRECOMP_ROOT}/lib/recomp-net/include/recomp_net/rollback.h")
add_executable(netplay_input_hist_test
tests/test_netplay_input_hist.c
src/netplay_input_hist.c
${PSXRECOMP_ROOT}/lib/recomp-net/src/input/rnet_input_contract.c)
target_include_directories(netplay_input_hist_test PRIVATE
include
${PSXRECOMP_ROOT}/lib/recomp-net/include)
target_compile_definitions(netplay_input_hist_test PRIVATE
PSX_HAS_RECOMP_NET=1)
add_test(NAME netplay_input_hist_test COMMAND netplay_input_hist_test)
else()
message(STATUS
"netplay_input_hist_test: lib/recomp-net submodule absent - skipped")
endif()
endif()
# ---- test-registration completeness guard (must stay LAST) ------------------
# Fails configure if a file in runtime/tests/ or recompiler/tests/ is neither
# registered as a test nor listed as a deliberate exception. See the module
# header for the rot story this exists to prevent.
include(${CMAKE_CURRENT_SOURCE_DIR}/check_test_registration.cmake)
psxrecomp_check_all_tests_registered()
if(NOT MSVC)
psxrecomp_add_runtime_target(psx-oracle
ORACLE
+147
View File
@@ -0,0 +1,147 @@
# Guard against test files that no build system knows about.
#
# WHY THIS EXISTS
# ---------------
# Until 2026-08-11, 16 of the 41 C/C++ tests in runtime/tests/ had no add_test()
# entry anywhere. Their only build instructions were a gcc command line written
# into each file's header comment — and a comment cannot fail. So when the
# runtime API moved under them, they broke and nobody found out:
#
# * test_netplay_snap_ring.c stopped linking when boot_state grew its
# VRAM-incremental queries. Its documented recipe ALSO listed a .c file the
# test already #includes, so the recipe produced duplicate symbols. Both
# defects sat there until someone tried to run it by hand (PR #134).
# * test_psx_cycle_event_boundaries.c called psx_cycles_resync_after_restore()
# after that function grew a CPUState* parameter.
# * test_spu_end_without_repeat.c never grew stubs for two externs spu.c
# picked up.
# * test_sio_card_protocol.c drifted until 147 of its 309 checks failed.
#
# Registering those 16 fixes the instance. It does not fix the class: the class
# is that adding an unregistered test is silent, and silence is the default.
# This check inverts that default. A new file under tests/ must either be
# registered or be listed below with a reason — there is no third option that
# configures successfully.
#
# HOW IT WORKS
# ------------
# The two projects here (recompiler/ and runtime/) are separate project()s with
# separate BUILD_TESTING gates, and runtime/ additionally needs a generated BIOS
# to configure — so there is no single tree in which CMake can see every
# registered target. Rather than force them together (which would make the
# BIOS-free recompiler tree unconfigurable for newcomers), this reads both
# CMakeLists.txt files as TEXT and compares the test files they mention against
# the test files on disk. That works identically from either project, so both
# call it and each independently catches an orphan in the other.
#
# Usage: call at the END of a CMakeLists.txt.
# include(${CMAKE_CURRENT_SOURCE_DIR}/check_test_registration.cmake)
# psxrecomp_check_all_tests_registered()
set(_PSXRECOMP_TESTREG_DIR "${CMAKE_CURRENT_LIST_DIR}")
# Files under tests/ that are deliberately not registered as their own test.
# Every entry needs a reason. An entry with no reason is a silent orphan with
# extra steps, which is the thing this file exists to prevent.
set(PSXRECOMP_TESTS_NOT_REGISTERED
"test_overlay_posix.c|built and run by tests/run_overlay_posix_test.sh, which stages the dlopen fixture tree; that script is registered as overlay_posix_test on UNIX"
)
function(psxrecomp_check_all_tests_registered)
set(_root "${_PSXRECOMP_TESTREG_DIR}/..")
# ---- what the build systems say -------------------------------------
set(_declared "")
foreach(_cml "${_root}/runtime/CMakeLists.txt"
"${_root}/recompiler/CMakeLists.txt")
if(NOT EXISTS "${_cml}")
continue()
endif()
file(READ "${_cml}" _text)
# Direct references: tests/test_foo.c, ../runtime/tests/test_foo.py,
# tests/foo_test.cpp ... Matched narrowly (test_ prefix / _test suffix)
# so that ordinary sources on the same add_executable line cannot be
# mistaken for a registration.
string(REGEX MATCHALL "test_[A-Za-z0-9_]+\\.(c|cpp|py)" _hits "${_text}")
string(REGEX MATCHALL "[A-Za-z0-9_]+_test\\.(c|cpp)" _hits2 "${_text}")
foreach(_h IN LISTS _hits _hits2)
string(REGEX REPLACE "\\.(c|cpp|py)$" "" _stem "${_h}")
list(APPEND _declared "${_stem}")
endforeach()
# foreach(_t IN ITEMS a b c) blocks register test_<item>.py. Match only
# up to the first ')' so the list items are captured but the add_test()
# body inside the loop is not.
string(REGEX MATCHALL "foreach\\(_t IN ITEMS[^)]*\\)" _loops "${_text}")
foreach(_loop IN LISTS _loops)
string(REGEX REPLACE "foreach\\(_t IN ITEMS" "" _loop "${_loop}")
string(REGEX MATCHALL "[A-Za-z0-9_]+" _items "${_loop}")
foreach(_i IN LISTS _items)
list(APPEND _declared "test_${_i}")
endforeach()
endforeach()
endforeach()
list(REMOVE_DUPLICATES _declared)
# ---- what is actually on disk ---------------------------------------
# CONFIGURE_DEPENDS matters more than usual here: without it, ADDING a test
# file would not re-run configure, so this check would sleep through the
# exact event it exists to catch.
file(GLOB _found CONFIGURE_DEPENDS
"${_root}/runtime/tests/test_*.c"
"${_root}/runtime/tests/test_*.cpp"
"${_root}/runtime/tests/test_*.py"
"${_root}/recompiler/tests/*_test.cpp"
"${_root}/recompiler/tests/test_*.py")
# ---- diff -------------------------------------------------------------
set(_orphans "")
foreach(_path IN LISTS _found)
get_filename_component(_file "${_path}" NAME)
get_filename_component(_stem "${_path}" NAME_WE)
set(_excused FALSE)
foreach(_ex IN LISTS PSXRECOMP_TESTS_NOT_REGISTERED)
string(REGEX REPLACE "\\|.*$" "" _ex_file "${_ex}")
if(_ex_file STREQUAL _file)
set(_excused TRUE)
break()
endif()
endforeach()
if(_excused)
continue()
endif()
list(FIND _declared "${_stem}" _idx)
if(_idx EQUAL -1)
file(RELATIVE_PATH _rel "${_root}" "${_path}")
list(APPEND _orphans "${_rel}")
endif()
endforeach()
if(_orphans)
list(JOIN _orphans "\n " _pretty)
message(FATAL_ERROR
"Test file(s) present on disk but registered nowhere:\n"
" ${_pretty}\n\n"
"An unregistered test cannot run, cannot fail, and rots silently — "
"four of them had already broken against the runtime API before "
"anyone noticed (see the header of "
"runtime/check_test_registration.cmake).\n\n"
"Fix by EITHER:\n"
" * adding an add_test() entry in runtime/CMakeLists.txt or "
"recompiler/CMakeLists.txt — register it DISABLED via "
"set_tests_properties(<name> PROPERTIES DISABLED TRUE) if it is "
"known-failing, so it stays visible in `ctest -N` instead of "
"vanishing; or\n"
" * adding '<filename>|<reason>' to "
"PSXRECOMP_TESTS_NOT_REGISTERED in "
"runtime/check_test_registration.cmake, if it is a fixture or is "
"driven by another test.")
endif()
list(LENGTH _found _n_found)
message(STATUS
"test-registration guard: ${_n_found} test file(s), all registered")
endfunction()
+10
View File
@@ -1,3 +1,13 @@
/* clock_gettime/CLOCK_MONOTONIC are POSIX, not ISO C. Under a strict -std=cNN
* (no GNU extensions) glibc hides them unless _POSIX_C_SOURCE is set, so this
* TU fails to compile — loudly under -Werror, silently as an implicit
* declaration without it. The runtime build happens to use -std=gnu99, which
* is why this never surfaced there. Must precede the first libc header:
* glibc's <features.h> latches the feature-test macros on first inclusion. */
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 200809L
#endif
#include "netplay_snap_ring.h"
#include "boot_state.h"
+1 -3
View File
@@ -15,9 +15,7 @@
* not after three failed spawns - the point is to know before any compile is
* attempted.
*
* Build (standalone):
* gcc -I ../include -o test_autocompile_degraded \
* test_autocompile_degraded.c ../src/autocompile.c
* Build/run: ctest -R autocompile_degraded_test
*/
#include "autocompile.h"
#include "overlay_loader.h"
+1 -3
View File
@@ -1,9 +1,7 @@
/*
* Validate PS1 CD-ROM interrupt mask matching.
*
* Build:
* cc -std=c99 -Wall -Wextra -Werror -I../src \
* -o test_cdrom_irq_mask test_cdrom_irq_mask.c
* Build/run: ctest -R cdrom_irq_mask_test
*/
#include "cdrom_irq.h"
Binary file not shown.
+1 -3
View File
@@ -1,9 +1,7 @@
/*
* test_netplay_hash_confirm.c — watermark advance / mismatch hold.
*
* gcc -std=c11 -Wall -Wextra -Werror -I../include \
* -o test_netplay_hash_confirm test_netplay_hash_confirm.c \
* ../src/netplay_hash_confirm.c && ./test_netplay_hash_confirm
* Build/run: ctest -R netplay_hash_confirm_test
*/
#include "netplay_hash_confirm.h"
Binary file not shown.
+2 -6
View File
@@ -1,12 +1,8 @@
/*
* test_netplay_input_hist.c — pad map, invent hold-last, promote.
*
* gcc -std=c11 -Wall -Wextra -Werror -DPSX_HAS_RECOMP_NET=1 \
* -I../include -I../../lib/recomp-net/include \
* -o test_netplay_input_hist test_netplay_input_hist.c \
* ../src/netplay_input_hist.c \
* ../../lib/recomp-net/src/input/rnet_input_contract.c && \
* ./test_netplay_input_hist
* Build/run: ctest -R netplay_input_hist_test
* (needs the lib/recomp-net submodule; registration is conditional.)
*/
#define PSX_HAS_RECOMP_NET 1
#include "netplay_input_hist.h"
Binary file not shown.
+16 -4
View File
@@ -4,11 +4,23 @@
* Stores synthetic blobs via netplay_snap_ring_store and checks has/peek,
* same-tick overwrite, and oldest-tick eviction at capacity.
*
* Build (from runtime/tests):
* gcc -std=c11 -Wall -Wextra -Werror -I../include \
* -o test_netplay_snap_ring test_netplay_snap_ring.c \
* -DNETPLAY_SNAP_RING_TEST_STUB_BOOT_STATE
* Build/run: ctest -R netplay_snap_ring_test
* (registered in runtime/CMakeLists.txt, which owns the source list and the
* NETPLAY_SNAP_RING_TEST_STUB_BOOT_STATE define. This file deliberately does
* not repeat a gcc recipe: an unexecuted recipe in a comment is what let this
* harness rot — it kept listing ../src/netplay_snap_ring.c, which the file
* already #includes, long after that became a duplicate-symbol error.)
*/
/* This TU pulls in ../src/netplay_snap_ring.c at the bottom, and that file
* calls clock_gettime/CLOCK_MONOTONIC on non-Windows. The equivalent define in
* netplay_snap_ring.c itself is too late here: <stdio.h> below already latched
* glibc's <features.h>, so under a strict -std=cNN the POSIX declarations stay
* hidden and the build dies on an implicit declaration. Must stay first. */
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 200809L
#endif
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
@@ -1,10 +1,7 @@
/*
* Pin cross-device causality at a scheduler deadline.
*
* Build from runtime/tests:
* gcc -std=c99 -Wall -Wextra -ffunction-sections -fdata-sections \
* -Wl,--gc-sections -I../include -o test_psx_cycle_event_boundaries \
* test_psx_cycle_event_boundaries.c ../src/psx_cycles.c
* Build/run: ctest -R psx_cycle_event_boundaries_test
*/
#include "psx_cycles.h"
@@ -61,8 +58,14 @@ int psx_get_in_exception(void) { return 0; }
void starvation_watchdog_check(void) {}
void starvation_ring_pc_sample(void) {}
int psx_netplay_active(void) { return 0; }
int psx_selfcheck_enabled(void) { return 0; }
void dirty_ram_ld_delay_discard(void) {}
int main(void) {
psx_cycles_resync_after_restore();
/* NULL cpu: this test pins the scheduler boundary, not the CPU-state
* rewind. psx_cycles_resync_after_restore guards that block on `if (cpu)`. */
psx_cycles_resync_after_restore(NULL);
psx_advance_cycles(5);
if (psx_get_cycle_count() != 5) {
+1 -3
View File
@@ -4,9 +4,7 @@
* Soak class (rb-diag 2026-07-31): both peers POST@1007 (matched), tip-extend
* to 1008, both POST@1008 (matched); initiator aborted local@1008 vs peer@1007.
*
* gcc -std=c11 -Wall -Wextra -Werror -I../include \
* -o test_rb_post_tip_filter test_rb_post_tip_filter.c && \
* ./test_rb_post_tip_filter
* Build/run: ctest -R rb_post_tip_filter_test
*/
#include "netplay_rb_post.h"
+10
View File
@@ -38,9 +38,19 @@
/* ---- Stubs for sio.c's external dependencies ---- */
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
/* sio.c gates card-transfer deferral on `psx_get_cycle_count() < deadline`
* (s_card_ct_defer_until_cyc). A constant clock would defer forever and stall
* the state machine, so the stub advances monotonically. */
uint64_t psx_get_cycle_count(void) {
static uint64_t t;
return t += 64;
}
uint32_t memory_get_sr(void) { return 0; }
void debug_server_poll(void) {}
void debug_server_log_sio_write(uint32_t addr, uint32_t value, uint8_t width) {
+10
View File
@@ -12,9 +12,19 @@ void sio_snapshot_write(uint8_t *p);
int sio_snapshot_read(const uint8_t *p, uint32_t len);
uint32_t i_stat = 0;
uint32_t i_mask = 0;
uint32_t g_debug_current_func_addr = 0;
uint32_t g_debug_last_store_pc = 0;
int psx_get_in_exception(void) { return 0; }
uint8_t psx_read_byte(uint32_t addr) { (void)addr; return 0; }
uint32_t psx_read_word(uint32_t addr) { (void)addr; return 0; }
/* sio.c gates card-transfer deferral on `psx_get_cycle_count() < deadline`, so
* a constant clock would defer forever. Advance monotonically. */
uint64_t psx_get_cycle_count(void) {
static uint64_t t;
return t += 64;
}
uint32_t memory_get_sr(void) { return 0; }
void debug_server_poll(void) {}
void debug_server_log_sio_write(uint32_t a, uint32_t v, uint8_t w) {
+9 -3
View File
@@ -4,9 +4,7 @@
* The test includes spu.c so it can stage a voice exactly at the block
* boundary, without exposing emulator internals in the runtime API.
*
* Build from runtime/tests:
* gcc -std=c99 -I../include -o test_spu_end_without_repeat.exe \
* test_spu_end_without_repeat.c
* Build/run: ctest -R spu_end_without_repeat_test
*/
#include <stdbool.h>
@@ -32,6 +30,14 @@ void audio_trace_event(uint16_t kind, uint32_t a, uint32_t b) {
(void)b;
}
void psx_irq_raise(uint32_t bit, uint32_t detail) { (void)bit; (void)detail; }
uint32_t crc32_update(uint32_t crc, const uint8_t *data, size_t len) {
(void)data;
(void)len;
return crc;
}
bool spu_shadow_enabled(void) { return false; }
void spu_shadow_reset(void) {}
void spu_shadow_process(int16_t *canon, int frames) {