mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
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:
+12
-1
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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,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,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.
@@ -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.
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user