From fe22c12f7b87d4a8872acdb77ce39d9c427eaafe Mon Sep 17 00:00:00 2001 From: Matthew Stanley Date: Tue, 11 Aug 2026 14:34:12 -0700 Subject: [PATCH] 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 first and so has already latched glibc's . 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 `, 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) --- .gitignore | 13 +- recompiler/CMakeLists.txt | 142 ++++++++----- runtime/CMakeLists.txt | 188 +++++++++++++++++- runtime/check_test_registration.cmake | 147 ++++++++++++++ runtime/src/netplay_snap_ring.c | 10 + runtime/tests/test_autocompile_degraded.c | 4 +- runtime/tests/test_cdrom_irq_mask.c | 4 +- runtime/tests/test_netplay_hash_confirm | Bin 20864 -> 0 bytes runtime/tests/test_netplay_hash_confirm.c | 4 +- runtime/tests/test_netplay_input_hist | Bin 26096 -> 0 bytes runtime/tests/test_netplay_input_hist.c | 8 +- runtime/tests/test_netplay_snap_ring | Bin 21360 -> 0 bytes runtime/tests/test_netplay_snap_ring.c | 20 +- .../tests/test_psx_cycle_event_boundaries.c | 13 +- runtime/tests/test_rb_post_tip_filter.c | 4 +- runtime/tests/test_sio_card_protocol.c | 10 + runtime/tests/test_sio_dualshock_rumble.c | 10 + runtime/tests/test_spu_end_without_repeat.c | 12 +- 18 files changed, 510 insertions(+), 79 deletions(-) create mode 100644 runtime/check_test_registration.cmake delete mode 100755 runtime/tests/test_netplay_hash_confirm delete mode 100755 runtime/tests/test_netplay_input_hist delete mode 100755 runtime/tests/test_netplay_snap_ring diff --git a/.gitignore b/.gitignore index 32eb0523..f0a98020 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/recompiler/CMakeLists.txt b/recompiler/CMakeLists.txt index 8c53e25a..f8c4ab2b 100644 --- a/recompiler/CMakeLists.txt +++ b/recompiler/CMakeLists.txt @@ -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 $ - 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 $ + 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 $ + 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. diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 4c03f095..23246357 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -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 ` 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 diff --git a/runtime/check_test_registration.cmake b/runtime/check_test_registration.cmake new file mode 100644 index 00000000..a26d87f3 --- /dev/null +++ b/runtime/check_test_registration.cmake @@ -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_.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( PROPERTIES DISABLED TRUE) if it is " + "known-failing, so it stays visible in `ctest -N` instead of " + "vanishing; or\n" + " * adding '|' 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() diff --git a/runtime/src/netplay_snap_ring.c b/runtime/src/netplay_snap_ring.c index a69fc39f..c057bef6 100644 --- a/runtime/src/netplay_snap_ring.c +++ b/runtime/src/netplay_snap_ring.c @@ -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 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" diff --git a/runtime/tests/test_autocompile_degraded.c b/runtime/tests/test_autocompile_degraded.c index 259a55b0..9cdbe256 100644 --- a/runtime/tests/test_autocompile_degraded.c +++ b/runtime/tests/test_autocompile_degraded.c @@ -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" diff --git a/runtime/tests/test_cdrom_irq_mask.c b/runtime/tests/test_cdrom_irq_mask.c index 645702eb..518928e0 100644 --- a/runtime/tests/test_cdrom_irq_mask.c +++ b/runtime/tests/test_cdrom_irq_mask.c @@ -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" diff --git a/runtime/tests/test_netplay_hash_confirm b/runtime/tests/test_netplay_hash_confirm deleted file mode 100755 index 07e60f433a13ba57291b93bb1b2e1583416b6824..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20864 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 D;9o%p diff --git a/runtime/tests/test_netplay_hash_confirm.c b/runtime/tests/test_netplay_hash_confirm.c index 82d6bdbb..fbb9e902 100644 --- a/runtime/tests/test_netplay_hash_confirm.c +++ b/runtime/tests/test_netplay_hash_confirm.c @@ -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" diff --git a/runtime/tests/test_netplay_input_hist b/runtime/tests/test_netplay_input_hist deleted file mode 100755 index d8c935bc34a4604a1ae846ded85dff97fb0716bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26096 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 S-O4BbgR3ORxfTISQT!LjJW(P5 diff --git a/runtime/tests/test_netplay_snap_ring.c b/runtime/tests/test_netplay_snap_ring.c index 2b912637..f081ed4a 100644 --- a/runtime/tests/test_netplay_snap_ring.c +++ b/runtime/tests/test_netplay_snap_ring.c @@ -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: below already latched + * glibc's , 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 #include #include diff --git a/runtime/tests/test_psx_cycle_event_boundaries.c b/runtime/tests/test_psx_cycle_event_boundaries.c index ce29ec05..1d5a6ad1 100644 --- a/runtime/tests/test_psx_cycle_event_boundaries.c +++ b/runtime/tests/test_psx_cycle_event_boundaries.c @@ -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) { diff --git a/runtime/tests/test_rb_post_tip_filter.c b/runtime/tests/test_rb_post_tip_filter.c index 0afa3d04..3ff90a8c 100644 --- a/runtime/tests/test_rb_post_tip_filter.c +++ b/runtime/tests/test_rb_post_tip_filter.c @@ -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" diff --git a/runtime/tests/test_sio_card_protocol.c b/runtime/tests/test_sio_card_protocol.c index 607286dd..30ee00dd 100644 --- a/runtime/tests/test_sio_card_protocol.c +++ b/runtime/tests/test_sio_card_protocol.c @@ -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) { diff --git a/runtime/tests/test_sio_dualshock_rumble.c b/runtime/tests/test_sio_dualshock_rumble.c index d6b0ca59..6f913034 100644 --- a/runtime/tests/test_sio_dualshock_rumble.c +++ b/runtime/tests/test_sio_dualshock_rumble.c @@ -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) { diff --git a/runtime/tests/test_spu_end_without_repeat.c b/runtime/tests/test_spu_end_without_repeat.c index e883276b..65d12a42 100644 --- a/runtime/tests/test_spu_end_without_repeat.c +++ b/runtime/tests/test_spu_end_without_repeat.c @@ -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 @@ -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) {