Files
psxrecomp/runtime/tests/test_netplay_hash_confirm.c
T
Matthew Stanley fe22c12f7b 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>
2026-08-11 14:34:12 -07:00

106 lines
4.5 KiB
C

/*
* test_netplay_hash_confirm.c — watermark advance / mismatch hold.
*
* Build/run: ctest -R netplay_hash_confirm_test
*/
#include "netplay_hash_confirm.h"
#include <stdio.h>
static int failures;
#define CHECK(cond, msg) do { \
if (!(cond)) { printf("FAIL: %s\n", msg); failures++; } \
else { printf("ok: %s\n", msg); } \
} while (0)
int main(void) {
NetplayHashConfirm hc;
netplay_hc_reset(&hc);
CHECK(!netplay_hc_confirm_through(&hc, 0), "empty not confirmed");
CHECK(netplay_hc_resolved_through(&hc) == 0u, "resolved starts 0");
netplay_hc_note_local(&hc, 0, 0x1111u);
netplay_hc_note_local(&hc, 1, 0x2222u);
netplay_hc_note_local(&hc, 2, 0x3333u);
CHECK(!netplay_hc_confirm_through(&hc, 0), "local-only not enough");
netplay_hc_note_peer(&hc, 0, 0x1111u);
CHECK(netplay_hc_confirm_through(&hc, 0), "tick 0 matched");
CHECK(netplay_hc_resolved_through(&hc) == 0u, "resolved at 0");
CHECK(!netplay_hc_confirm_through(&hc, 1), "tick 1 not yet");
{
uint32_t pd = 0;
CHECK(netplay_hc_peer_digest(&hc, 0, &pd) && pd == 0x1111u,
"peer_digest peeks FRAME_COMMIT hash");
CHECK(!netplay_hc_peer_digest(&hc, 1, &pd), "peer_digest missing tick");
}
netplay_hc_note_peer(&hc, 1, 0x2222u);
CHECK(netplay_hc_confirm_through(&hc, 1), "tick 1 matched");
CHECK(netplay_hc_resolved_through(&hc) == 1u, "resolved at 1");
/* Mismatch must not advance past the gap. */
netplay_hc_note_peer(&hc, 2, 0xDEADu);
CHECK(netplay_hc_resolved_through(&hc) == 1u, "mismatch holds watermark");
CHECK(!netplay_hc_confirm_through(&hc, 2), "tick 2 not confirmed");
{
uint32_t mt = 0, mld = 0, mpd = 0;
CHECK(netplay_hc_peek_mismatch(&hc, &mt, &mld, &mpd), "peek mismatch");
CHECK(mt == 2u && mld == 0x3333u && mpd == 0xDEADu, "peek mismatch vals");
}
/* Late local after peer (reverse order) still advances. */
netplay_hc_reset(&hc);
netplay_hc_note_peer(&hc, 0, 0xAAAAu);
netplay_hc_note_peer(&hc, 1, 0xBBBBu);
CHECK(!netplay_hc_confirm_through(&hc, 0), "peer-only not enough");
netplay_hc_note_local(&hc, 0, 0xAAAAu);
CHECK(netplay_hc_resolved_through(&hc) == 0u, "advance on late local 0");
netplay_hc_note_local(&hc, 1, 0xBBBBu);
CHECK(netplay_hc_resolved_through(&hc) == 1u, "advance on late local 1");
/* prime_after drops stale live mismatches (Replay entry). */
netplay_hc_prime_after(&hc, 819u);
netplay_hc_note_local(&hc, 820, 0x1111u);
netplay_hc_note_peer(&hc, 820, 0x2222u);
CHECK(netplay_hc_peek_mismatch(&hc, NULL, NULL, NULL), "stale mismatch present");
netplay_hc_prime_after(&hc, 819u);
CHECK(!netplay_hc_peek_mismatch(&hc, NULL, NULL, NULL), "prime clears stale");
netplay_hc_note_local(&hc, 820, 0xAAAAu);
netplay_hc_note_peer(&hc, 820, 0xAAAAu);
CHECK(netplay_hc_resolved_through(&hc) == 820u, "prime then match load tick");
/* Stale FIRST-CORE gap: mismatch tick ages out of the ring; later tip
* digests match — heal must advance past the stuck watermark. */
netplay_hc_reset(&hc);
netplay_hc_note_local(&hc, 0, 0x1u);
netplay_hc_note_peer(&hc, 0, 0x1u);
CHECK(netplay_hc_resolved_through(&hc) == 0u, "heal setup resolved 0");
netplay_hc_note_local(&hc, 1, 0xAAAAu);
netplay_hc_note_peer(&hc, 1, 0xBBBBu); /* FIRST CORE */
CHECK(netplay_hc_resolved_through(&hc) == 0u, "mismatch sticks at 0");
CHECK(netplay_hc_peek_mismatch(&hc, NULL, NULL, NULL), "mismatch visible");
/* Overwrite slot for tick 1 (1 % 128 == 1) with a far tip match. */
netplay_hc_note_local(&hc, 1u + NETPLAY_HC_RING, 0xCCCCu);
netplay_hc_note_peer(&hc, 1u + NETPLAY_HC_RING, 0xCCCCu);
netplay_hc_note_local(&hc, 50u + NETPLAY_HC_RING, 0xDDDDu);
netplay_hc_note_peer(&hc, 50u + NETPLAY_HC_RING, 0xDDDDu);
CHECK(!netplay_hc_peek_mismatch(&hc, NULL, NULL, NULL),
"aged mismatch no longer peekable");
CHECK(netplay_hc_heal_stale_gap(&hc), "heal advances over stale gap");
CHECK(netplay_hc_resolved_through(&hc) == 50u + NETPLAY_HC_RING,
"heal to highest matched tip");
/* Do not heal over a still-visible fork. */
netplay_hc_note_local(&hc, 60u + NETPLAY_HC_RING, 0x1111u);
netplay_hc_note_peer(&hc, 60u + NETPLAY_HC_RING, 0x2222u);
CHECK(!netplay_hc_heal_stale_gap(&hc), "heal refuses live fork in ring");
if (failures) {
printf("%d failure(s)\n", failures);
return 1;
}
printf("ALL PASS\n");
return 0;
}