From eff8d43479e7704df65fae2a80eefa787213f502 Mon Sep 17 00:00:00 2001 From: skal Date: Mon, 9 Feb 2026 20:27:04 +0100 Subject: refactor: Reorganize tests into subsystem subdirectories Restructured test suite for better organization and targeted testing: **Structure:** - src/tests/audio/ - 15 audio system tests - src/tests/gpu/ - 12 GPU/shader tests - src/tests/3d/ - 6 3D rendering tests - src/tests/assets/ - 2 asset system tests - src/tests/util/ - 3 utility tests - src/tests/common/ - 3 shared test helpers - src/tests/scripts/ - 2 bash test scripts (moved conceptually, not physically) **CMake changes:** - Updated add_demo_test macro to accept LABEL parameter - Applied CTest labels to all 36 tests for subsystem filtering - Updated all test file paths in CMakeLists.txt - Fixed common helper paths (webgpu_test_fixture, etc.) - Added custom targets for subsystem testing: - run_audio_tests, run_gpu_tests, run_3d_tests - run_assets_tests, run_util_tests, run_all_tests **Include path updates:** - Fixed relative includes in GPU tests to reference ../common/ **Documentation:** - Updated doc/HOWTO.md with subsystem test commands - Updated doc/CONTRIBUTING.md with new test organization - Updated scripts/check_all.sh to reflect new structure **Verification:** - All 36 tests passing (100%) - ctest -L filters work correctly - make run__tests targets functional - scripts/check_all.sh passes Backward compatible: make test and ctest continue to work unchanged. handoff(Gemini): Test reorganization complete. 36/36 tests passing. --- src/tests/audio/test_tracker_timing.cc | 309 +++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 src/tests/audio/test_tracker_timing.cc (limited to 'src/tests/audio/test_tracker_timing.cc') diff --git a/src/tests/audio/test_tracker_timing.cc b/src/tests/audio/test_tracker_timing.cc new file mode 100644 index 0000000..9f15197 --- /dev/null +++ b/src/tests/audio/test_tracker_timing.cc @@ -0,0 +1,309 @@ +// This file is part of the 64k demo project. +// It tests tracker timing and synchronization using MockAudioBackend. +// Verifies pattern triggers occur at correct times with proper BPM scaling. + +#include "audio/audio.h" +#include "audio/audio_engine.h" +#include "audio/backend/mock_audio_backend.h" +#include "audio/synth.h" +#include "audio/tracker.h" +#include +#include +#include + +#if !defined(STRIP_ALL) + +// Helper: Setup audio engine for testing +static void setup_audio_test(MockAudioBackend& backend, AudioEngine& engine) { + audio_set_backend(&backend); + engine.init(); +} + +// Helper: Check if a timestamp exists in events within tolerance +static bool has_event_at_time(const std::vector& events, + float expected_time, float tolerance = 0.001f) { + for (const auto& evt : events) { + if (std::abs(evt.timestamp_sec - expected_time) < tolerance) { + return true; + } + } + return false; +} + +// Helper: Count events at a specific time +static int count_events_at_time(const std::vector& events, + float expected_time, float tolerance = 0.001f) { + int count = 0; + for (const auto& evt : events) { + if (std::abs(evt.timestamp_sec - expected_time) < tolerance) { + count++; + } + } + return count; +} + +// Helper: Get all unique timestamps in events +static std::vector +get_unique_timestamps(const std::vector& events, + float tolerance = 0.001f) { + std::vector timestamps; + for (const auto& evt : events) { + bool found = false; + for (float ts : timestamps) { + if (std::abs(evt.timestamp_sec - ts) < tolerance) { + found = true; + break; + } + } + if (!found) { + timestamps.push_back(evt.timestamp_sec); + } + } + return timestamps; +} + +void test_basic_event_recording() { + printf("Test: Basic event recording with mock backend...\n"); + + MockAudioBackend backend; + AudioEngine engine; + setup_audio_test(backend, engine); + + engine.update(0.0f, 0.0f); + const auto& events = backend.get_events(); + printf(" Events triggered at t=0.0: %zu\n", events.size()); + + assert(events.size() > 0); + for (const auto& evt : events) { + assert(evt.timestamp_sec < 0.1f); + } + + engine.shutdown(); + printf(" ✓ Basic event recording works\n"); +} + +void test_progressive_triggering() { + printf("Test: Progressive pattern triggering...\n"); + + MockAudioBackend backend; + AudioEngine engine; + setup_audio_test(backend, engine); + + engine.update(0.0f, 0.0f); + const size_t events_at_0 = backend.get_events().size(); + printf(" Events at t=0.0: %zu\n", events_at_0); + + engine.update(1.0f, 0.0f); + const size_t events_at_1 = backend.get_events().size(); + printf(" Events at t=1.0: %zu\n", events_at_1); + + engine.update(2.0f, 0.0f); + const size_t events_at_2 = backend.get_events().size(); + printf(" Events at t=2.0: %zu\n", events_at_2); + + assert(events_at_1 >= events_at_0); + assert(events_at_2 >= events_at_1); + + engine.shutdown(); + printf(" ✓ Events accumulate over time\n"); +} + +void test_simultaneous_triggers() { + printf("Test: SIMULTANEOUS pattern triggers at same time...\n"); + + MockAudioBackend backend; + AudioEngine engine; + setup_audio_test(backend, engine); + + backend.clear_events(); + engine.update(0.0f, 0.0f); + + const auto& events = backend.get_events(); + if (events.size() == 0) { + printf(" No events at t=0.0, skipping test\n"); + return; + } + + // Check if we have multiple events at t=0 + const int simultaneous_count = count_events_at_time(events, 0.0f, 0.001f); + printf(" Simultaneous events at t=0.0: %d out of %zu total\n", + simultaneous_count, events.size()); + + if (simultaneous_count > 1) { + // Verify all simultaneous events have EXACTLY the same timestamp + const float first_timestamp = events[0].timestamp_sec; + float max_delta = 0.0f; + + for (size_t i = 1; i < events.size(); ++i) { + const float delta = std::abs(events[i].timestamp_sec - first_timestamp); + max_delta = std::fmaxf(max_delta, delta); + } + + printf(" Maximum timestamp delta: %.6f seconds (%.3f ms)\n", max_delta, + max_delta * 1000.0f); + + // Simultaneous events should have sub-millisecond timing + assert(max_delta < 0.001f); // Less than 1ms difference + + printf(" ✓ All simultaneous events within 1ms of each other\n"); + } else { + printf(" ℹ Only one event at t=0.0, cannot verify simultaneity\n"); + } + + engine.shutdown(); +} + +void test_timing_monotonicity() { + printf("Test: Event timestamps are monotonically increasing...\n"); + + MockAudioBackend backend; + AudioEngine engine; + setup_audio_test(backend, engine); + + for (float t = 0.0f; t <= 5.0f; t += 0.5f) { + engine.update(t, 0.5f); + } + + const auto& events = backend.get_events(); + printf(" Total events recorded: %zu\n", events.size()); + + // Verify timestamps are monotonically increasing (non-decreasing) + for (size_t i = 1; i < events.size(); ++i) { + assert(events[i].timestamp_sec >= events[i - 1].timestamp_sec); + } + + engine.shutdown(); + printf(" ✓ All timestamps monotonically increasing\n"); +} + +void test_seek_simulation() { + printf("Test: Seek/fast-forward simulation...\n"); + + MockAudioBackend backend; + audio_set_backend(&backend); + + audio_init(); + AudioEngine engine; + engine.init(); + + // Simulate seeking to t=3.0s by rendering silent audio + // This should trigger all patterns in range [0, 3.0] + const float seek_target = 3.0f; + + // Update tracker progressively (simulating real playback) + float t = 0.0f; + const float step = 0.1f; + while (t <= seek_target) { + engine.update(t, step); + // Simulate audio rendering + float dummy_buffer[512 * 2]; + engine.render(dummy_buffer, 512); + t += step; + } + + const auto& events = backend.get_events(); + printf(" Events triggered during seek to %.1fs: %zu\n", seek_target, + events.size()); + + // Should have triggered multiple patterns + assert(events.size() > 0); + + // All events should be before seek target time + for (const auto& evt : events) { + // Events can be slightly after due to synth processing + assert(evt.timestamp_sec <= seek_target + 0.5f); + } + + engine.shutdown(); + audio_shutdown(); + + printf(" ✓ Seek simulation works correctly\n"); +} + +void test_timestamp_clustering() { + printf("Test: Analyzing timestamp clustering...\n"); + + MockAudioBackend backend; + audio_set_backend(&backend); + + AudioEngine engine; + engine.init(); + + // Update through the first 4 seconds + for (float t = 0.0f; t <= 4.0f; t += 0.1f) { + engine.update(t, 0.1f); + } + + const auto& events = backend.get_events(); + printf(" Total events: %zu\n", events.size()); + + // Get unique timestamps + auto unique_timestamps = get_unique_timestamps(events, 0.001f); + printf(" Unique trigger times: %zu\n", unique_timestamps.size()); + + // For each unique timestamp, count how many events occurred + for (float ts : unique_timestamps) { + const int count = count_events_at_time(events, ts, 0.001f); + if (count > 1) { + printf(" %.3fs: %d simultaneous events\n", ts, count); + } + } + + engine.shutdown(); + printf(" ✓ Timestamp clustering analyzed\n"); +} + +void test_render_integration() { + printf("Test: Integration with audio_render_silent...\n"); + + MockAudioBackend backend; + audio_set_backend(&backend); + + audio_init(); + AudioEngine engine; + engine.init(); + + // Trigger some patterns + engine.update(0.0f, 0.0f); + const size_t events_before = backend.get_events().size(); + + // Render 1 second of silent audio + audio_render_silent(1.0f); + + // Check that backend time advanced + const float backend_time = backend.get_current_time(); + printf(" Backend time after 1s render: %.3fs\n", backend_time); + assert(backend_time >= 0.9f && backend_time <= 1.1f); + + // Trigger more patterns after time advance + engine.update(1.0f, 0.0f); + const size_t events_after = backend.get_events().size(); + + printf(" Events before: %zu, after: %zu\n", events_before, events_after); + assert(events_after >= events_before); + + engine.shutdown(); + audio_shutdown(); + + printf(" ✓ audio_render_silent integration works\n"); +} + +#endif /* !defined(STRIP_ALL) */ + +int main() { +#if !defined(STRIP_ALL) + printf("Running Tracker Timing tests...\n\n"); + test_basic_event_recording(); + test_progressive_triggering(); + test_simultaneous_triggers(); + test_timing_monotonicity(); + test_seek_simulation(); + test_timestamp_clustering(); + test_render_integration(); + printf("\n✅ All Tracker Timing tests PASSED\n"); + return 0; +#else + printf("Tracker Timing tests skipped (STRIP_ALL enabled)\n"); + return 0; +#endif /* !defined(STRIP_ALL) */ +} -- cgit v1.2.3