summaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/3d/test_3d.cc30
-rw-r--r--src/tests/3d/test_physics.cc32
-rw-r--r--src/tests/audio/test_audio_engine.cc71
-rw-r--r--src/tests/audio/test_silent_backend.cc17
-rw-r--r--src/tests/audio/test_tracker.cc25
-rw-r--r--src/tests/audio/test_tracker_timing.cc60
-rw-r--r--src/tests/audio/test_variable_tempo.cc69
-rw-r--r--src/tests/audio/test_wav_dump.cc11
-rw-r--r--src/tests/common/audio_test_fixture.cc19
-rw-r--r--src/tests/common/audio_test_fixture.h26
-rw-r--r--src/tests/common/effect_test_fixture.cc23
-rw-r--r--src/tests/common/effect_test_fixture.h28
-rw-r--r--src/tests/common/test_math_helpers.h18
-rw-r--r--src/tests/util/test_maths.cc83
14 files changed, 286 insertions, 226 deletions
diff --git a/src/tests/3d/test_3d.cc b/src/tests/3d/test_3d.cc
index e0fb2e0..7132b33 100644
--- a/src/tests/3d/test_3d.cc
+++ b/src/tests/3d/test_3d.cc
@@ -4,14 +4,10 @@
#include "3d/camera.h"
#include "3d/object.h"
#include "3d/scene.h"
+#include "../common/test_math_helpers.h"
#include <cassert>
-#include <cmath>
#include <iostream>
-bool near(float a, float b, float e = 0.001f) {
- return std::abs(a - b) < e;
-}
-
void test_camera() {
std::cout << "Testing Camera..." << std::endl;
Camera cam;
@@ -20,7 +16,7 @@ void test_camera() {
mat4 view = cam.get_view_matrix();
// Camera at (0,0,10) looking at (0,0,0). World (0,0,0) -> View (0,0,-10)
- assert(near(view.m[14], -10.0f));
+ assert(test_near(view.m[14], -10.0f, 0.001f));
// Test Camera::set_look_at
cam.set_look_at({5, 0, 0}, {0, 0, 0},
@@ -31,9 +27,9 @@ void test_camera() {
// -dot(s, eye), -dot(u, eye), dot(f, eye) s = (0,0,-1), u = (0,1,0), f =
// (-1,0,0) m[12] = -dot({0,0,-1}, {5,0,0}) = 0 m[13] = -dot({0,1,0}, {5,0,0})
// = 0 m[14] = dot({-1,0,0}, {5,0,0}) = -5
- assert(near(view_shifted.m[12], 0.0f));
- assert(near(view_shifted.m[13], 0.0f));
- assert(near(view_shifted.m[14], -5.0f));
+ assert(test_near(view_shifted.m[12], 0.0f, 0.001f));
+ assert(test_near(view_shifted.m[13], 0.0f, 0.001f));
+ assert(test_near(view_shifted.m[14], -5.0f, 0.001f));
// Test Camera::get_projection_matrix with varied parameters
// Change FOV and aspect ratio
@@ -54,7 +50,7 @@ void test_object_transform() {
// Model matrix should translate by (10,0,0)
mat4 m = obj.get_model_matrix();
- assert(near(m.m[12], 10.0f));
+ assert(test_near(m.m[12], 10.0f, 0.001f));
// Test composed transformations (translate then rotate)
obj.position = vec3(5, 0, 0);
@@ -65,8 +61,8 @@ void test_object_transform() {
// Translation moves it by (5,0,0). Final world pos: (5,0,-1).
vec4 p_comp(1, 0, 0, 1);
vec4 res_comp = m * p_comp;
- assert(near(res_comp.x, 5.0f));
- assert(near(res_comp.z, -1.0f));
+ assert(test_near(res_comp.x, 5.0f, 0.001f));
+ assert(test_near(res_comp.z, -1.0f, 0.001f));
// Test Object3D::inv_model calculation
// Model matrix for translation (5,0,0) is just translation
@@ -80,8 +76,8 @@ void test_object_transform() {
vec4 original_space_t =
inv_model_t *
vec4(translated_point.x, translated_point.y, translated_point.z, 1.0);
- assert(near(original_space_t.x, 0.0f) && near(original_space_t.y, 0.0f) &&
- near(original_space_t.z, 0.0f));
+ assert(test_near(original_space_t.x, 0.0f, 0.001f) && test_near(original_space_t.y, 0.0f, 0.001f) &&
+ test_near(original_space_t.z, 0.0f, 0.001f));
// Model matrix with rotation (90 deg Y) and translation (5,0,0)
obj.position = vec3(5, 0, 0);
@@ -92,11 +88,11 @@ void test_object_transform() {
// Translates to (5,0,-1)
vec4 p_trs(1, 0, 0, 1);
vec4 transformed_p = model_trs * p_trs;
- assert(near(transformed_p.x, 5.0f) && near(transformed_p.z, -1.0f));
+ assert(test_near(transformed_p.x, 5.0f, 0.001f) && test_near(transformed_p.z, -1.0f, 0.001f));
// Apply inverse to transformed point to get back original point
vec4 original_space_trs = inv_model_trs * transformed_p;
- assert(near(original_space_trs.x, 1.0f) && near(original_space_trs.y, 0.0f) &&
- near(original_space_trs.z, 0.0f));
+ assert(test_near(original_space_trs.x, 1.0f, 0.001f) && test_near(original_space_trs.y, 0.0f, 0.001f) &&
+ test_near(original_space_trs.z, 0.0f, 0.001f));
}
void test_scene() {
diff --git a/src/tests/3d/test_physics.cc b/src/tests/3d/test_physics.cc
index df21e70..c1c5c32 100644
--- a/src/tests/3d/test_physics.cc
+++ b/src/tests/3d/test_physics.cc
@@ -4,44 +4,40 @@
#include "3d/bvh.h"
#include "3d/physics.h"
#include "3d/sdf_cpu.h"
+#include "../common/test_math_helpers.h"
#include <cassert>
-#include <cmath>
#include <iostream>
-bool near(float a, float b, float e = 0.001f) {
- return std::abs(a - b) < e;
-}
-
void test_sdf_sphere() {
std::cout << "Testing sdSphere..." << std::endl;
float r = 1.0f;
- assert(near(sdf::sdSphere({0, 0, 0}, r), -1.0f));
- assert(near(sdf::sdSphere({1, 0, 0}, r), 0.0f));
- assert(near(sdf::sdSphere({2, 0, 0}, r), 1.0f));
+ assert(test_near(sdf::sdSphere({0, 0, 0}, r), -1.0f, 0.001f));
+ assert(test_near(sdf::sdSphere({1, 0, 0}, r), 0.0f, 0.001f));
+ assert(test_near(sdf::sdSphere({2, 0, 0}, r), 1.0f, 0.001f));
}
void test_sdf_box() {
std::cout << "Testing sdBox..." << std::endl;
vec3 b(1, 1, 1);
- assert(near(sdf::sdBox({0, 0, 0}, b), -1.0f));
- assert(near(sdf::sdBox({1, 1, 1}, b), 0.0f));
- assert(near(sdf::sdBox({2, 0, 0}, b), 1.0f));
+ assert(test_near(sdf::sdBox({0, 0, 0}, b), -1.0f, 0.001f));
+ assert(test_near(sdf::sdBox({1, 1, 1}, b), 0.0f, 0.001f));
+ assert(test_near(sdf::sdBox({2, 0, 0}, b), 1.0f, 0.001f));
}
void test_sdf_torus() {
std::cout << "Testing sdTorus..." << std::endl;
vec2 t(1.0f, 0.2f);
// Point on the ring: length(p.xz) = 1.0, p.y = 0
- assert(near(sdf::sdTorus({1, 0, 0}, t), -0.2f));
- assert(near(sdf::sdTorus({1.2f, 0, 0}, t), 0.0f));
+ assert(test_near(sdf::sdTorus({1, 0, 0}, t), -0.2f, 0.001f));
+ assert(test_near(sdf::sdTorus({1.2f, 0, 0}, t), 0.0f, 0.001f));
}
void test_sdf_plane() {
std::cout << "Testing sdPlane..." << std::endl;
vec3 n(0, 1, 0);
float h = 1.0f; // Plane is at y = -1 (dot(p,n) + 1 = 0 => y = -1)
- assert(near(sdf::sdPlane({0, 0, 0}, n, h), 1.0f));
- assert(near(sdf::sdPlane({0, -1, 0}, n, h), 0.0f));
+ assert(test_near(sdf::sdPlane({0, 0, 0}, n, h), 1.0f, 0.001f));
+ assert(test_near(sdf::sdPlane({0, -1, 0}, n, h), 0.0f, 0.001f));
}
void test_calc_normal() {
@@ -50,18 +46,18 @@ void test_calc_normal() {
// Sphere normal at (1,0,0) should be (1,0,0)
auto sphere_sdf = [](vec3 p) { return sdf::sdSphere(p, 1.0f); };
vec3 n = sdf::calc_normal({1, 0, 0}, sphere_sdf);
- assert(near(n.x, 1.0f) && near(n.y, 0.0f) && near(n.z, 0.0f));
+ assert(test_near(n.x, 1.0f, 0.001f) && test_near(n.y, 0.0f, 0.001f) && test_near(n.z, 0.0f, 0.001f));
// Box normal at side
auto box_sdf = [](vec3 p) { return sdf::sdBox(p, {1, 1, 1}); };
n = sdf::calc_normal({1, 0, 0}, box_sdf);
- assert(near(n.x, 1.0f) && near(n.y, 0.0f) && near(n.z, 0.0f));
+ assert(test_near(n.x, 1.0f, 0.001f) && test_near(n.y, 0.0f, 0.001f) && test_near(n.z, 0.0f, 0.001f));
// Plane normal should be n
vec3 plane_n(0, 1, 0);
auto plane_sdf = [plane_n](vec3 p) { return sdf::sdPlane(p, plane_n, 1.0f); };
n = sdf::calc_normal({0, 0, 0}, plane_sdf);
- assert(near(n.x, plane_n.x) && near(n.y, plane_n.y) && near(n.z, plane_n.z));
+ assert(test_near(n.x, plane_n.x, 0.001f) && test_near(n.y, plane_n.y, 0.001f) && test_near(n.z, plane_n.z, 0.001f));
}
void test_bvh() {
diff --git a/src/tests/audio/test_audio_engine.cc b/src/tests/audio/test_audio_engine.cc
index 3b29dcd..72c1653 100644
--- a/src/tests/audio/test_audio_engine.cc
+++ b/src/tests/audio/test_audio_engine.cc
@@ -4,6 +4,7 @@
#include "audio/audio_engine.h"
#include "audio/tracker.h"
#include "generated/assets.h"
+#include "../common/audio_test_fixture.h"
#include <assert.h>
#include <stdio.h>
@@ -13,19 +14,13 @@
void test_audio_engine_lifecycle() {
printf("Test: AudioEngine lifecycle...\n");
- AudioEngine engine;
- printf(" Created AudioEngine object...\n");
-
- engine.init();
- printf(" Initialized AudioEngine...\n");
+ AudioTestFixture fixture;
+ printf(" Created and initialized AudioEngine...\n");
// Verify initialization
- assert(engine.get_active_voice_count() == 0);
+ assert(fixture.engine().get_active_voice_count() == 0);
printf(" Verified voice count is 0...\n");
- engine.shutdown();
- printf(" Shutdown AudioEngine...\n");
-
printf(" ✓ AudioEngine lifecycle test passed\n");
}
@@ -33,16 +28,15 @@ void test_audio_engine_lifecycle() {
void test_audio_engine_music_loading() {
printf("Test: AudioEngine music data loading...\n");
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Load global music data
- engine.load_music_data(&g_tracker_score, g_tracker_samples,
- g_tracker_sample_assets, g_tracker_samples_count);
+ fixture.load_music(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
// Verify resource manager was initialized (samples registered but not loaded
// yet)
- SpectrogramResourceManager* res_mgr = engine.get_resource_manager();
+ SpectrogramResourceManager* res_mgr = fixture.engine().get_resource_manager();
assert(res_mgr != nullptr);
// Initially, no samples should be loaded (lazy loading)
@@ -51,8 +45,6 @@ void test_audio_engine_music_loading() {
printf(" ✓ Music data loaded: %u samples registered\n",
g_tracker_samples_count);
- engine.shutdown();
-
printf(" ✓ AudioEngine music loading test passed\n");
}
@@ -60,14 +52,13 @@ void test_audio_engine_music_loading() {
void test_audio_engine_manual_resource_loading() {
printf("Test: AudioEngine manual resource loading...\n");
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Load music data
- engine.load_music_data(&g_tracker_score, g_tracker_samples,
- g_tracker_sample_assets, g_tracker_samples_count);
+ fixture.load_music(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
- SpectrogramResourceManager* res_mgr = engine.get_resource_manager();
+ SpectrogramResourceManager* res_mgr = fixture.engine().get_resource_manager();
const int initial_loaded = res_mgr->get_loaded_count();
assert(initial_loaded == 0); // No samples loaded yet
@@ -89,8 +80,6 @@ void test_audio_engine_manual_resource_loading() {
assert(spec1 != nullptr);
assert(spec2 != nullptr);
- engine.shutdown();
-
printf(" ✓ AudioEngine manual resource loading test passed\n");
}
@@ -98,13 +87,12 @@ void test_audio_engine_manual_resource_loading() {
void test_audio_engine_reset() {
printf("Test: AudioEngine reset...\n");
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
- engine.load_music_data(&g_tracker_score, g_tracker_samples,
- g_tracker_sample_assets, g_tracker_samples_count);
+ fixture.load_music(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
- SpectrogramResourceManager* res_mgr = engine.get_resource_manager();
+ SpectrogramResourceManager* res_mgr = fixture.engine().get_resource_manager();
// Manually load some samples
res_mgr->preload(0);
@@ -115,10 +103,10 @@ void test_audio_engine_reset() {
assert(loaded_before_reset == 3);
// Reset engine
- engine.reset();
+ fixture.engine().reset();
// After reset, state should be cleared
- assert(engine.get_active_voice_count() == 0);
+ assert(fixture.engine().get_active_voice_count() == 0);
// Resources should be marked as unloaded (but memory not freed)
const int loaded_after_reset = res_mgr->get_loaded_count();
@@ -126,8 +114,6 @@ void test_audio_engine_reset() {
loaded_before_reset, loaded_after_reset);
assert(loaded_after_reset == 0);
- engine.shutdown();
-
printf(" ✓ AudioEngine reset test passed\n");
}
@@ -136,25 +122,22 @@ void test_audio_engine_reset() {
void test_audio_engine_seeking() {
printf("Test: AudioEngine seeking...\n");
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
- engine.load_music_data(&g_tracker_score, g_tracker_samples,
- g_tracker_sample_assets, g_tracker_samples_count);
+ fixture.load_music(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
// Seek to t=5.0s
- engine.seek(5.0f);
- assert(engine.get_time() == 5.0f);
+ fixture.engine().seek(5.0f);
+ assert(fixture.engine().get_time() == 5.0f);
// Seek backward to t=2.0s
- engine.seek(2.0f);
- assert(engine.get_time() == 2.0f);
+ fixture.engine().seek(2.0f);
+ assert(fixture.engine().get_time() == 2.0f);
// Seek to beginning
- engine.seek(0.0f);
- assert(engine.get_time() == 0.0f);
-
- engine.shutdown();
+ fixture.engine().seek(0.0f);
+ assert(fixture.engine().get_time() == 0.0f);
printf(" ✓ AudioEngine seeking test passed\n");
}
diff --git a/src/tests/audio/test_silent_backend.cc b/src/tests/audio/test_silent_backend.cc
index 8daacf7..cc98139 100644
--- a/src/tests/audio/test_silent_backend.cc
+++ b/src/tests/audio/test_silent_backend.cc
@@ -6,6 +6,7 @@
#include "audio/audio_engine.h"
#include "audio/backend/silent_backend.h"
#include "audio/synth.h"
+#include "../common/audio_test_fixture.h"
#include <assert.h>
#include <stdio.h>
@@ -80,8 +81,7 @@ void test_silent_backend_tracking() {
SilentBackend backend;
audio_set_backend(&backend);
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Initial state
assert(backend.get_frames_rendered() == 0);
@@ -105,7 +105,6 @@ void test_silent_backend_tracking() {
assert(backend.get_frames_rendered() == 0);
assert(backend.get_voice_trigger_count() == 0);
- engine.shutdown();
audio_shutdown();
printf("SilentBackend tracking test PASSED\n");
@@ -116,8 +115,7 @@ void test_audio_playback_time() {
SilentBackend backend;
audio_set_backend(&backend);
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
audio_start();
// Initial playback time should be 0
@@ -137,7 +135,6 @@ void test_audio_playback_time() {
float t2 = audio_get_playback_time();
assert(t2 >= t1); // Should continue advancing
- engine.shutdown();
audio_shutdown();
printf("Audio playback time test PASSED\n");
@@ -148,8 +145,7 @@ void test_audio_buffer_partial_writes() {
SilentBackend backend;
audio_set_backend(&backend);
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
audio_start();
// Fill buffer multiple times to test wraparound
@@ -164,7 +160,6 @@ void test_audio_buffer_partial_writes() {
// no audio callback to consume from the ring buffer
audio_update(); // Should not crash
- engine.shutdown();
audio_shutdown();
printf("Audio buffer partial writes test PASSED\n");
@@ -175,8 +170,7 @@ void test_audio_update() {
SilentBackend backend;
audio_set_backend(&backend);
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
audio_start();
// audio_update() should be callable without crashing
@@ -184,7 +178,6 @@ void test_audio_update() {
audio_update();
audio_update();
- engine.shutdown();
audio_shutdown();
printf("Audio update test PASSED\n");
diff --git a/src/tests/audio/test_tracker.cc b/src/tests/audio/test_tracker.cc
index 6be2a8d..1112e91 100644
--- a/src/tests/audio/test_tracker.cc
+++ b/src/tests/audio/test_tracker.cc
@@ -5,6 +5,7 @@
#include "audio/gen.h"
#include "audio/synth.h"
#include "audio/tracker.h"
+#include "../common/audio_test_fixture.h"
// #include "generated/music_data.h" // Will be generated by tracker_compiler
#include <assert.h>
#include <stdio.h>
@@ -17,15 +18,12 @@ extern const uint32_t g_tracker_patterns_count;
extern const TrackerScore g_tracker_score;
void test_tracker_init() {
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
printf("Tracker init test PASSED\n");
- engine.shutdown();
}
void test_tracker_pattern_triggering() {
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// At time 0.0f, 3 patterns are triggered:
// - crash (1 event at beat 0.0)
@@ -37,31 +35,30 @@ void test_tracker_pattern_triggering() {
// drums_basic:
// 0.00, ASSET_KICK_1
// 0.00, NOTE_A4
- engine.update(0.0f, 0.0f);
+ fixture.engine().update(0.0f, 0.0f);
// Expect 2 voices: kick + note
- assert(engine.get_active_voice_count() == 2);
+ assert(fixture.engine().get_active_voice_count() == 2);
// Test 2: At music_time = 0.25f (beat 0.5 @ 120 BPM), snare event triggers
// 0.25, ASSET_SNARE_1
- engine.update(0.25f, 0.0f);
+ fixture.engine().update(0.25f, 0.0f);
// Expect at least 2 voices (snare + maybe others)
// Exact count depends on sample duration (kick/note might have finished)
- int voices = engine.get_active_voice_count();
+ int voices = fixture.engine().get_active_voice_count();
assert(voices >= 2);
// Test 3: At music_time = 0.5f (beat 1.0), kick event triggers
// 0.50, ASSET_KICK_1
- engine.update(0.5f, 0.0f);
+ fixture.engine().update(0.5f, 0.0f);
// Expect at least 3 voices (new kick + others)
- assert(engine.get_active_voice_count() >= 3);
+ assert(fixture.engine().get_active_voice_count() >= 3);
// Test 4: Advance to 2.0f - new patterns trigger at time 2.0f
- engine.update(2.0f, 0.0f);
+ fixture.engine().update(2.0f, 0.0f);
// Many events have triggered by now
- assert(engine.get_active_voice_count() > 5);
+ assert(fixture.engine().get_active_voice_count() > 5);
printf("Tracker pattern triggering test PASSED\n");
- engine.shutdown();
}
int main() {
diff --git a/src/tests/audio/test_tracker_timing.cc b/src/tests/audio/test_tracker_timing.cc
index 9f15197..7295de3 100644
--- a/src/tests/audio/test_tracker_timing.cc
+++ b/src/tests/audio/test_tracker_timing.cc
@@ -7,6 +7,7 @@
#include "audio/backend/mock_audio_backend.h"
#include "audio/synth.h"
#include "audio/tracker.h"
+#include "../common/audio_test_fixture.h"
#include <assert.h>
#include <cmath>
#include <stdio.h>
@@ -14,9 +15,10 @@
#if !defined(STRIP_ALL)
// Helper: Setup audio engine for testing
-static void setup_audio_test(MockAudioBackend& backend, AudioEngine& engine) {
+static AudioTestFixture*
+setup_audio_test(MockAudioBackend& backend) {
audio_set_backend(&backend);
- engine.init();
+ return new AudioTestFixture();
}
// Helper: Check if a timestamp exists in events within tolerance
@@ -66,10 +68,9 @@ void test_basic_event_recording() {
printf("Test: Basic event recording with mock backend...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
- engine.update(0.0f, 0.0f);
+ fixture->engine().update(0.0f, 0.0f);
const auto& events = backend.get_events();
printf(" Events triggered at t=0.0: %zu\n", events.size());
@@ -78,7 +79,7 @@ void test_basic_event_recording() {
assert(evt.timestamp_sec < 0.1f);
}
- engine.shutdown();
+ delete fixture;
printf(" ✓ Basic event recording works\n");
}
@@ -86,25 +87,24 @@ void test_progressive_triggering() {
printf("Test: Progressive pattern triggering...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
- engine.update(0.0f, 0.0f);
+ fixture->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);
+ fixture->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);
+ fixture->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();
+ delete fixture;
printf(" ✓ Events accumulate over time\n");
}
@@ -112,11 +112,10 @@ void test_simultaneous_triggers() {
printf("Test: SIMULTANEOUS pattern triggers at same time...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
backend.clear_events();
- engine.update(0.0f, 0.0f);
+ fixture->engine().update(0.0f, 0.0f);
const auto& events = backend.get_events();
if (events.size() == 0) {
@@ -150,18 +149,17 @@ void test_simultaneous_triggers() {
printf(" ℹ Only one event at t=0.0, cannot verify simultaneity\n");
}
- engine.shutdown();
+ delete fixture;
}
void test_timing_monotonicity() {
printf("Test: Event timestamps are monotonically increasing...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
for (float t = 0.0f; t <= 5.0f; t += 0.5f) {
- engine.update(t, 0.5f);
+ fixture->engine().update(t, 0.5f);
}
const auto& events = backend.get_events();
@@ -172,7 +170,7 @@ void test_timing_monotonicity() {
assert(events[i].timestamp_sec >= events[i - 1].timestamp_sec);
}
- engine.shutdown();
+ delete fixture;
printf(" ✓ All timestamps monotonically increasing\n");
}
@@ -183,8 +181,7 @@ void test_seek_simulation() {
audio_set_backend(&backend);
audio_init();
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Simulate seeking to t=3.0s by rendering silent audio
// This should trigger all patterns in range [0, 3.0]
@@ -194,10 +191,10 @@ void test_seek_simulation() {
float t = 0.0f;
const float step = 0.1f;
while (t <= seek_target) {
- engine.update(t, step);
+ fixture.engine().update(t, step);
// Simulate audio rendering
float dummy_buffer[512 * 2];
- engine.render(dummy_buffer, 512);
+ fixture.engine().render(dummy_buffer, 512);
t += step;
}
@@ -214,7 +211,6 @@ void test_seek_simulation() {
assert(evt.timestamp_sec <= seek_target + 0.5f);
}
- engine.shutdown();
audio_shutdown();
printf(" ✓ Seek simulation works correctly\n");
@@ -226,12 +222,11 @@ void test_timestamp_clustering() {
MockAudioBackend backend;
audio_set_backend(&backend);
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Update through the first 4 seconds
for (float t = 0.0f; t <= 4.0f; t += 0.1f) {
- engine.update(t, 0.1f);
+ fixture.engine().update(t, 0.1f);
}
const auto& events = backend.get_events();
@@ -249,7 +244,6 @@ void test_timestamp_clustering() {
}
}
- engine.shutdown();
printf(" ✓ Timestamp clustering analyzed\n");
}
@@ -260,11 +254,10 @@ void test_render_integration() {
audio_set_backend(&backend);
audio_init();
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Trigger some patterns
- engine.update(0.0f, 0.0f);
+ fixture.engine().update(0.0f, 0.0f);
const size_t events_before = backend.get_events().size();
// Render 1 second of silent audio
@@ -276,13 +269,12 @@ void test_render_integration() {
assert(backend_time >= 0.9f && backend_time <= 1.1f);
// Trigger more patterns after time advance
- engine.update(1.0f, 0.0f);
+ fixture.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");
diff --git a/src/tests/audio/test_variable_tempo.cc b/src/tests/audio/test_variable_tempo.cc
index bbc9ebf..da056c5 100644
--- a/src/tests/audio/test_variable_tempo.cc
+++ b/src/tests/audio/test_variable_tempo.cc
@@ -6,6 +6,7 @@
#include "audio/audio_engine.h"
#include "audio/backend/mock_audio_backend.h"
#include "audio/tracker.h"
+#include "../common/audio_test_fixture.h"
#include <assert.h>
#include <cmath>
#include <stdio.h>
@@ -13,11 +14,13 @@
#if !defined(STRIP_ALL)
// Helper: Setup audio engine for testing
-static void setup_audio_test(MockAudioBackend& backend, AudioEngine& engine) {
+static AudioTestFixture*
+setup_audio_test(MockAudioBackend& backend) {
audio_set_backend(&backend);
- engine.init();
- engine.load_music_data(&g_tracker_score, g_tracker_samples,
- g_tracker_sample_assets, g_tracker_samples_count);
+ AudioTestFixture* fixture = new AudioTestFixture();
+ fixture->load_music(&g_tracker_score, g_tracker_samples,
+ g_tracker_sample_assets, g_tracker_samples_count);
+ return fixture;
}
// Helper: Simulate tempo advancement with fixed steps
@@ -47,14 +50,13 @@ void test_basic_tempo_scaling() {
printf("Test: Basic tempo scaling (1.0x, 2.0x, 0.5x)...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
// Test 1: Normal tempo (1.0x)
{
backend.clear_events();
float music_time = 0.0f;
- simulate_tempo(engine, music_time, 1.0f, 1.0f);
+ simulate_tempo(fixture->engine(), music_time, 1.0f, 1.0f);
printf(" 1.0x tempo: music_time = %.3f (expected ~1.0)\n", music_time);
assert(std::abs(music_time - 1.0f) < 0.01f);
}
@@ -62,9 +64,9 @@ void test_basic_tempo_scaling() {
// Test 2: Fast tempo (2.0x)
{
backend.clear_events();
- engine.reset();
+ fixture->engine().reset();
float music_time = 0.0f;
- simulate_tempo(engine, music_time, 1.0f, 2.0f);
+ simulate_tempo(fixture->engine(), music_time, 1.0f, 2.0f);
printf(" 2.0x tempo: music_time = %.3f (expected ~2.0)\n", music_time);
assert(std::abs(music_time - 2.0f) < 0.01f);
}
@@ -72,14 +74,14 @@ void test_basic_tempo_scaling() {
// Test 3: Slow tempo (0.5x)
{
backend.clear_events();
- engine.reset();
+ fixture->engine().reset();
float music_time = 0.0f;
- simulate_tempo(engine, music_time, 1.0f, 0.5f);
+ simulate_tempo(fixture->engine(), music_time, 1.0f, 0.5f);
printf(" 0.5x tempo: music_time = %.3f (expected ~0.5)\n", music_time);
assert(std::abs(music_time - 0.5f) < 0.01f);
}
- engine.shutdown();
+ delete fixture;
printf(" ✓ Basic tempo scaling works correctly\n");
}
@@ -87,8 +89,7 @@ void test_2x_speedup_reset_trick() {
printf("Test: 2x SPEED-UP reset trick...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
float music_time = 0.0f;
float physical_time = 0.0f;
@@ -97,7 +98,7 @@ void test_2x_speedup_reset_trick() {
// Phase 1: Accelerate from 1.0x to 2.0x over 5 seconds
printf(" Phase 1: Accelerating 1.0x → 2.0x\n");
auto accel_fn = [](float t) { return fminf(1.0f + (t / 5.0f), 2.0f); };
- simulate_tempo_fn(engine, music_time, physical_time, 5.0f, dt, accel_fn);
+ simulate_tempo_fn(fixture->engine(), music_time, physical_time, 5.0f, dt, accel_fn);
const float tempo_scale = accel_fn(physical_time);
printf(" After 5s physical: tempo=%.2fx, music_time=%.3f\n", tempo_scale,
@@ -107,14 +108,14 @@ void test_2x_speedup_reset_trick() {
// Phase 2: RESET - back to 1.0x tempo
printf(" Phase 2: RESET to 1.0x tempo\n");
const float music_time_before_reset = music_time;
- simulate_tempo(engine, music_time, 2.0f, 1.0f, dt);
+ simulate_tempo(fixture->engine(), music_time, 2.0f, 1.0f, dt);
printf(" After reset + 2s: tempo=1.0x, music_time=%.3f\n", music_time);
const float music_time_delta = music_time - music_time_before_reset;
printf(" Music time delta: %.3f (expected ~2.0)\n", music_time_delta);
assert(std::abs(music_time_delta - 2.0f) < 0.1f);
- engine.shutdown();
+ delete fixture;
printf(" ✓ 2x speed-up reset trick verified\n");
}
@@ -122,8 +123,7 @@ void test_2x_slowdown_reset_trick() {
printf("Test: 2x SLOW-DOWN reset trick...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
float music_time = 0.0f;
float physical_time = 0.0f;
@@ -132,7 +132,7 @@ void test_2x_slowdown_reset_trick() {
// Phase 1: Decelerate from 1.0x to 0.5x over 5 seconds
printf(" Phase 1: Decelerating 1.0x → 0.5x\n");
auto decel_fn = [](float t) { return fmaxf(1.0f - (t / 10.0f), 0.5f); };
- simulate_tempo_fn(engine, music_time, physical_time, 5.0f, dt, decel_fn);
+ simulate_tempo_fn(fixture->engine(), music_time, physical_time, 5.0f, dt, decel_fn);
const float tempo_scale = decel_fn(physical_time);
printf(" After 5s physical: tempo=%.2fx, music_time=%.3f\n", tempo_scale,
@@ -142,14 +142,14 @@ void test_2x_slowdown_reset_trick() {
// Phase 2: RESET - back to 1.0x tempo
printf(" Phase 2: RESET to 1.0x tempo\n");
const float music_time_before_reset = music_time;
- simulate_tempo(engine, music_time, 2.0f, 1.0f, dt);
+ simulate_tempo(fixture->engine(), music_time, 2.0f, 1.0f, dt);
printf(" After reset + 2s: tempo=1.0x, music_time=%.3f\n", music_time);
const float music_time_delta = music_time - music_time_before_reset;
printf(" Music time delta: %.3f (expected ~2.0)\n", music_time_delta);
assert(std::abs(music_time_delta - 2.0f) < 0.1f);
- engine.shutdown();
+ delete fixture;
printf(" ✓ 2x slow-down reset trick verified\n");
}
@@ -157,34 +157,33 @@ void test_pattern_density_swap() {
printf("Test: Pattern density swap at reset points...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
float music_time = 0.0f;
// Phase 1: Sparse pattern at normal tempo
printf(" Phase 1: Sparse pattern, normal tempo\n");
- simulate_tempo(engine, music_time, 3.0f, 1.0f);
+ simulate_tempo(fixture->engine(), music_time, 3.0f, 1.0f);
const size_t sparse_events = backend.get_events().size();
printf(" Events during sparse phase: %zu\n", sparse_events);
// Phase 2: Accelerate to 2.0x
printf(" Phase 2: Accelerating to 2.0x\n");
- simulate_tempo(engine, music_time, 2.0f, 2.0f);
+ simulate_tempo(fixture->engine(), music_time, 2.0f, 2.0f);
const size_t events_at_2x = backend.get_events().size() - sparse_events;
printf(" Additional events during 2.0x: %zu\n", events_at_2x);
// Phase 3: Reset to 1.0x
printf(" Phase 3: Reset to 1.0x (simulating denser pattern)\n");
const size_t events_before_reset_phase = backend.get_events().size();
- simulate_tempo(engine, music_time, 2.0f, 1.0f);
+ simulate_tempo(fixture->engine(), music_time, 2.0f, 1.0f);
const size_t events_after_reset = backend.get_events().size();
printf(" Events during reset phase: %zu\n",
events_after_reset - events_before_reset_phase);
assert(backend.get_events().size() > 0);
- engine.shutdown();
+ delete fixture;
printf(" ✓ Pattern density swap points verified\n");
}
@@ -192,8 +191,7 @@ void test_continuous_acceleration() {
printf("Test: Continuous acceleration from 0.5x to 2.0x...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
float music_time = 0.0f;
float physical_time = 0.0f;
@@ -215,7 +213,7 @@ void test_continuous_acceleration() {
physical_time += dt;
const float tempo_scale = accel_fn(physical_time);
music_time += dt * tempo_scale;
- engine.update(music_time, dt * tempo_scale);
+ fixture->engine().update(music_time, dt * tempo_scale);
if (i % 50 == 0) {
printf(" t=%.1fs: tempo=%.2fx, music_time=%.3f\n", physical_time,
tempo_scale, music_time);
@@ -232,7 +230,7 @@ void test_continuous_acceleration() {
music_time);
assert(std::abs(music_time - expected_music_time) < 0.5f);
- engine.shutdown();
+ delete fixture;
printf(" ✓ Continuous acceleration verified\n");
}
@@ -240,8 +238,7 @@ void test_oscillating_tempo() {
printf("Test: Oscillating tempo (sine wave)...\n");
MockAudioBackend backend;
- AudioEngine engine;
- setup_audio_test(backend, engine);
+ AudioTestFixture* fixture = setup_audio_test(backend);
float music_time = 0.0f;
float physical_time = 0.0f;
@@ -256,7 +253,7 @@ void test_oscillating_tempo() {
physical_time += dt;
const float tempo_scale = oscil_fn(physical_time);
music_time += dt * tempo_scale;
- engine.update(music_time, dt * tempo_scale);
+ fixture->engine().update(music_time, dt * tempo_scale);
if (i % 25 == 0) {
printf(" t=%.2fs: tempo=%.3fx, music_time=%.3f\n", physical_time,
tempo_scale, music_time);
@@ -267,7 +264,7 @@ void test_oscillating_tempo() {
physical_time, music_time, physical_time);
assert(std::abs(music_time - physical_time) < 0.5f);
- engine.shutdown();
+ delete fixture;
printf(" ✓ Oscillating tempo verified\n");
}
diff --git a/src/tests/audio/test_wav_dump.cc b/src/tests/audio/test_wav_dump.cc
index 85b168d..9175153 100644
--- a/src/tests/audio/test_wav_dump.cc
+++ b/src/tests/audio/test_wav_dump.cc
@@ -5,6 +5,7 @@
#include "audio/audio_engine.h"
#include "audio/backend/wav_dump_backend.h"
#include "audio/ring_buffer.h"
+#include "../common/audio_test_fixture.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
@@ -38,8 +39,7 @@ void test_wav_format_matches_live_audio() {
audio_init();
// Initialize AudioEngine
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
// Create WAV dump backend
WavDumpBackend wav_backend;
@@ -59,7 +59,7 @@ void test_wav_format_matches_live_audio() {
float music_time = 0.0f;
for (float t = 0.0f; t < duration; t += update_dt) {
// Update audio engine (triggers patterns)
- engine.update(music_time, update_dt);
+ fixture.engine().update(music_time, update_dt);
music_time += update_dt;
// Render audio ahead
@@ -76,7 +76,6 @@ void test_wav_format_matches_live_audio() {
// Shutdown
wav_backend.shutdown();
- engine.shutdown();
audio_shutdown();
// Read and verify WAV header
@@ -192,8 +191,7 @@ void test_clipping_detection() {
const char* test_file = "test_clipping.wav";
audio_init();
- AudioEngine engine;
- engine.init();
+ AudioTestFixture fixture;
WavDumpBackend wav_backend;
wav_backend.set_output_file(test_file);
@@ -225,7 +223,6 @@ void test_clipping_detection() {
printf(" Detected %zu clipped samples (expected 200)\n", clipped);
wav_backend.shutdown();
- engine.shutdown();
audio_shutdown();
// Clean up
diff --git a/src/tests/common/audio_test_fixture.cc b/src/tests/common/audio_test_fixture.cc
new file mode 100644
index 0000000..42bf27f
--- /dev/null
+++ b/src/tests/common/audio_test_fixture.cc
@@ -0,0 +1,19 @@
+// audio_test_fixture.cc - RAII wrapper for AudioEngine lifecycle
+// Simplifies audio test setup and teardown
+
+#include "audio_test_fixture.h"
+
+AudioTestFixture::AudioTestFixture() {
+ m_engine.init();
+}
+
+AudioTestFixture::~AudioTestFixture() {
+ m_engine.shutdown();
+}
+
+void AudioTestFixture::load_music(const TrackerScore* score,
+ const NoteParams* samples,
+ const AssetId* assets,
+ uint32_t count) {
+ m_engine.load_music_data(score, samples, assets, count);
+}
diff --git a/src/tests/common/audio_test_fixture.h b/src/tests/common/audio_test_fixture.h
new file mode 100644
index 0000000..328e167
--- /dev/null
+++ b/src/tests/common/audio_test_fixture.h
@@ -0,0 +1,26 @@
+// audio_test_fixture.h - RAII wrapper for AudioEngine lifecycle
+// Simplifies audio test setup and teardown
+
+#pragma once
+#include "audio/audio_engine.h"
+#include "audio/gen.h"
+#include "audio/tracker.h"
+#include "generated/assets.h"
+
+// RAII wrapper for AudioEngine lifecycle
+class AudioTestFixture {
+public:
+ AudioTestFixture(); // Calls engine.init()
+ ~AudioTestFixture(); // Calls engine.shutdown()
+
+ AudioEngine& engine() { return m_engine; }
+
+ // Helper: Load tracker music data
+ void load_music(const TrackerScore* score,
+ const NoteParams* samples,
+ const AssetId* assets,
+ uint32_t count);
+
+private:
+ AudioEngine m_engine;
+};
diff --git a/src/tests/common/effect_test_fixture.cc b/src/tests/common/effect_test_fixture.cc
new file mode 100644
index 0000000..b403ef6
--- /dev/null
+++ b/src/tests/common/effect_test_fixture.cc
@@ -0,0 +1,23 @@
+// effect_test_fixture.cc - Combined WebGPU + AudioEngine + MainSequence fixture
+// Simplifies GPU effect test setup
+
+#include "effect_test_fixture.h"
+#include <stdio.h>
+
+EffectTestFixture::EffectTestFixture() {}
+
+EffectTestFixture::~EffectTestFixture() {
+ if (m_initialized) {
+ m_gpu.shutdown();
+ }
+}
+
+bool EffectTestFixture::init() {
+ if (!m_gpu.init()) {
+ fprintf(stdout, " ⚠ WebGPU unavailable - skipping test\n");
+ return false;
+ }
+ m_sequence.init_test(ctx());
+ m_initialized = true;
+ return true;
+}
diff --git a/src/tests/common/effect_test_fixture.h b/src/tests/common/effect_test_fixture.h
new file mode 100644
index 0000000..399b5ed
--- /dev/null
+++ b/src/tests/common/effect_test_fixture.h
@@ -0,0 +1,28 @@
+// effect_test_fixture.h - Combined WebGPU + AudioEngine + MainSequence fixture
+// Simplifies GPU effect test setup
+
+#pragma once
+#include "webgpu_test_fixture.h"
+#include "audio_test_fixture.h"
+#include "gpu/sequence.h"
+
+// Combined WebGPU + AudioEngine + MainSequence fixture
+class EffectTestFixture {
+public:
+ EffectTestFixture();
+ ~EffectTestFixture();
+
+ // Returns false if GPU unavailable (test should skip)
+ bool init();
+
+ // Accessors
+ GpuContext ctx() const { return m_gpu.ctx(); }
+ MainSequence& sequence() { return m_sequence; }
+ AudioEngine& audio() { return m_audio.engine(); }
+
+private:
+ WebGPUTestFixture m_gpu;
+ AudioTestFixture m_audio;
+ MainSequence m_sequence;
+ bool m_initialized = false;
+};
diff --git a/src/tests/common/test_math_helpers.h b/src/tests/common/test_math_helpers.h
new file mode 100644
index 0000000..99e7f9d
--- /dev/null
+++ b/src/tests/common/test_math_helpers.h
@@ -0,0 +1,18 @@
+// test_math_helpers.h - Math utilities for test code
+// Common floating-point comparison helpers
+
+#pragma once
+#include <cmath>
+#include "util/mini_math.h"
+
+// Floating-point comparison with epsilon tolerance
+inline bool test_near(float a, float b, float epsilon = 1e-6f) {
+ return fabs(a - b) < epsilon;
+}
+
+// Vector comparison
+inline bool test_near_vec3(vec3 a, vec3 b, float epsilon = 1e-6f) {
+ return test_near(a.x, b.x, epsilon) &&
+ test_near(a.y, b.y, epsilon) &&
+ test_near(a.z, b.z, epsilon);
+}
diff --git a/src/tests/util/test_maths.cc b/src/tests/util/test_maths.cc
index 0fed85c..4233adc 100644
--- a/src/tests/util/test_maths.cc
+++ b/src/tests/util/test_maths.cc
@@ -3,16 +3,11 @@
// Verifies vector operations, matrix transformations, and interpolation.
#include "util/mini_math.h"
+#include "../common/test_math_helpers.h"
#include <cassert>
-#include <cmath>
#include <iostream>
#include <vector>
-// Checks if two floats are approximately equal
-bool near(float a, float b, float e = 0.001f) {
- return std::abs(a - b) < e;
-}
-
// Generic test runner for any vector type (vec2, vec3, vec4)
template <typename T> void test_vector_ops(int n) {
T a, b;
@@ -25,37 +20,37 @@ template <typename T> void test_vector_ops(int n) {
// Add
T c = a + b;
for (int i = 0; i < n; ++i)
- assert(near(c[i], (float)(i + 1) + 10.0f));
+ assert(test_near(c[i], (float)(i + 1) + 10.0f, 0.001f));
// Scale
T s = a * 2.0f;
for (int i = 0; i < n; ++i)
- assert(near(s[i], (float)(i + 1) * 2.0f));
+ assert(test_near(s[i], (float)(i + 1) * 2.0f, 0.001f));
// Dot Product
// vec3(1,2,3) . vec3(1,2,3) = 1+4+9 = 14
float expected_dot = 0;
for (int i = 0; i < n; ++i)
expected_dot += a[i] * a[i];
- assert(near(T::dot(a, a), expected_dot));
+ assert(test_near(T::dot(a, a), expected_dot, 0.001f));
// Norm (Length)
- assert(near(a.norm(), std::sqrt(expected_dot)));
+ assert(test_near(a.norm(), std::sqrt(expected_dot), 0.001f));
// Normalize
T n_vec = a.normalize();
- assert(near(n_vec.norm(), 1.0f));
+ assert(test_near(n_vec.norm(), 1.0f, 0.001f));
// Normalize zero vector
T zero_vec = T(); // Default construct to zero
T norm_zero = zero_vec.normalize();
for (int i = 0; i < n; ++i)
- assert(near(norm_zero[i], 0.0f));
+ assert(test_near(norm_zero[i], 0.0f, 0.001f));
// Lerp
T l = lerp(a, b, 0.3f);
for (int i = 0; i < n; ++i)
- assert(near(l[i], .7 * (i + 1) + .3 * 10.0f));
+ assert(test_near(l[i], .7 * (i + 1) + .3 * 10.0f, 0.001f));
}
// Specific test for padding alignment in vec3
@@ -69,7 +64,7 @@ void test_vec3_special() {
// Cross Product
vec3 c = vec3::cross(v, v2);
- assert(near(c.x, 0) && near(c.y, 0) && near(c.z, 1));
+ assert(test_near(c.x, 0, 0.001f) && test_near(c.y, 0, 0.001f) && test_near(c.z, 1, 0.001f));
}
// Tests quaternion rotation, look_at, and slerp
@@ -80,48 +75,48 @@ void test_quat() {
vec3 v(1, 0, 0);
quat q = quat::from_axis({0, 1, 0}, 1.5708f); // 90 deg Y
vec3 r = q.rotate(v);
- assert(near(r.x, 0) && near(r.z, -1));
+ assert(test_near(r.x, 0, 0.001f) && test_near(r.z, -1, 0.001f));
// Rotation edge cases: 0 deg, 180 deg, zero vector
quat zero_rot = quat::from_axis({1, 0, 0}, 0.0f);
vec3 rotated_zero = zero_rot.rotate(v);
- assert(near(rotated_zero.x, 1.0f)); // Original vector
+ assert(test_near(rotated_zero.x, 1.0f, 0.001f)); // Original vector
quat half_pi_rot = quat::from_axis({0, 1, 0}, 3.14159f); // 180 deg Y
vec3 rotated_half_pi = half_pi_rot.rotate(v);
- assert(near(rotated_half_pi.x, -1.0f)); // Rotated 180 deg around Y
+ assert(test_near(rotated_half_pi.x, -1.0f, 0.001f)); // Rotated 180 deg around Y
vec3 zero_vec(0, 0, 0);
vec3 rotated_zero_vec = q.rotate(zero_vec);
- assert(near(rotated_zero_vec.x, 0.0f) && near(rotated_zero_vec.y, 0.0f) &&
- near(rotated_zero_vec.z, 0.0f));
+ assert(test_near(rotated_zero_vec.x, 0.0f, 0.001f) && test_near(rotated_zero_vec.y, 0.0f, 0.001f) &&
+ test_near(rotated_zero_vec.z, 0.0f, 0.001f));
// Look At
// Looking from origin to +X, with +Y as up.
// The local forward vector (0,0,-1) should be transformed to (1,0,0)
quat l = quat::look_at({0, 0, 0}, {10, 0, 0}, {0, 1, 0});
vec3 f = l.rotate({0, 0, -1});
- assert(near(f.x, 1.0f) && near(f.y, 0.0f) && near(f.z, 0.0f));
+ assert(test_near(f.x, 1.0f, 0.001f) && test_near(f.y, 0.0f, 0.001f) && test_near(f.z, 0.0f, 0.001f));
// Slerp Midpoint
quat q1(0, 0, 0, 1);
quat q2 = quat::from_axis({0, 1, 0}, 1.5708f); // 90 deg
quat mid = slerp(q1, q2, 0.5f); // 45 deg
- assert(near(mid.y, 0.3826f)); // sin(pi/8)
+ assert(test_near(mid.y, 0.3826f, 0.001f)); // sin(pi/8)
// Slerp edge cases
quat slerp_mid_edge = slerp(q1, q2, 0.0f);
- assert(near(slerp_mid_edge.w, q1.w) && near(slerp_mid_edge.x, q1.x) &&
- near(slerp_mid_edge.y, q1.y) && near(slerp_mid_edge.z, q1.z));
+ assert(test_near(slerp_mid_edge.w, q1.w, 0.001f) && test_near(slerp_mid_edge.x, q1.x, 0.001f) &&
+ test_near(slerp_mid_edge.y, q1.y, 0.001f) && test_near(slerp_mid_edge.z, q1.z, 0.001f));
slerp_mid_edge = slerp(q1, q2, 1.0f);
- assert(near(slerp_mid_edge.w, q2.w) && near(slerp_mid_edge.x, q2.x) &&
- near(slerp_mid_edge.y, q2.y) && near(slerp_mid_edge.z, q2.z));
+ assert(test_near(slerp_mid_edge.w, q2.w, 0.001f) && test_near(slerp_mid_edge.x, q2.x, 0.001f) &&
+ test_near(slerp_mid_edge.y, q2.y, 0.001f) && test_near(slerp_mid_edge.z, q2.z, 0.001f));
// FromTo
quat from_to_test =
quat::from_to({1, 0, 0}, {0, 1, 0}); // 90 deg rotation around Z
vec3 rotated = from_to_test.rotate({1, 0, 0});
- assert(near(rotated.y, 1.0f));
+ assert(test_near(rotated.y, 1.0f, 0.001f));
}
// Tests WebGPU specific matrices
@@ -134,8 +129,8 @@ void test_matrices() {
// Z_ndc = (m10 * Z_view + m14) / -Z_view
float z_near = (p.m[10] * -n + p.m[14]) / n;
float z_far = (p.m[10] * -f + p.m[14]) / f;
- assert(near(z_near, 0.0f));
- assert(near(z_far, 1.0f));
+ assert(test_near(z_near, 0.0f, 0.001f));
+ assert(test_near(z_far, 1.0f, 0.001f));
// Test mat4::look_at
vec3 eye(0, 0, 5);
@@ -143,7 +138,7 @@ void test_matrices() {
vec3 up(0, 1, 0);
mat4 view = mat4::look_at(eye, target, up);
// Point (0,0,0) in world should be at (0,0,-5) in view space
- assert(near(view.m[14], -5.0f));
+ assert(test_near(view.m[14], -5.0f, 0.001f));
// Test matrix multiplication
mat4 t = mat4::translate({1, 2, 3});
@@ -153,34 +148,34 @@ void test_matrices() {
// v = (1,1,1,1) -> scale(2,2,2) -> (2,2,2,1) -> translate(1,2,3) -> (3,4,5,1)
vec4 v(1, 1, 1, 1);
vec4 res = ts * v;
- assert(near(res.x, 3.0f));
- assert(near(res.y, 4.0f));
- assert(near(res.z, 5.0f));
+ assert(test_near(res.x, 3.0f, 0.001f));
+ assert(test_near(res.y, 4.0f, 0.001f));
+ assert(test_near(res.z, 5.0f, 0.001f));
// Test Rotation
// Rotate 90 deg around Z. (1,0,0) -> (0,1,0)
mat4 r = mat4::rotate({0, 0, 1}, 1.570796f);
vec4 v_rot = r * vec4(1, 0, 0, 1);
- assert(near(v_rot.x, 0.0f));
- assert(near(v_rot.y, 1.0f));
+ assert(test_near(v_rot.x, 0.0f, 0.001f));
+ assert(test_near(v_rot.y, 1.0f, 0.001f));
}
// Tests easing curves
void test_ease() {
std::cout << "Testing Easing..." << std::endl;
// Boundary tests
- assert(near(ease::out_cubic(0.0f), 0.0f));
- assert(near(ease::out_cubic(1.0f), 1.0f));
- assert(near(ease::in_out_quad(0.0f), 0.0f));
- assert(near(ease::in_out_quad(1.0f), 1.0f));
- assert(near(ease::out_expo(0.0f), 0.0f));
- assert(near(ease::out_expo(1.0f), 1.0f));
+ assert(test_near(ease::out_cubic(0.0f), 0.0f, 0.001f));
+ assert(test_near(ease::out_cubic(1.0f), 1.0f, 0.001f));
+ assert(test_near(ease::in_out_quad(0.0f), 0.0f, 0.001f));
+ assert(test_near(ease::in_out_quad(1.0f), 1.0f, 0.001f));
+ assert(test_near(ease::out_expo(0.0f), 0.0f, 0.001f));
+ assert(test_near(ease::out_expo(1.0f), 1.0f, 0.001f));
// Midpoint/Logic tests
assert(ease::out_cubic(0.5f) >
0.5f); // Out curves should exceed linear value early
assert(
- near(ease::in_out_quad(0.5f), 0.5f)); // Symmetric curves hit 0.5 at 0.5
+ test_near(ease::in_out_quad(0.5f), 0.5f, 0.001f)); // Symmetric curves hit 0.5 at 0.5
assert(ease::out_expo(0.5f) > 0.5f); // Exponential out should be above linear
}
@@ -198,7 +193,7 @@ void test_spring() {
v = 0;
for (int i = 0; i < 200; ++i)
spring::solve(p, v, 10.0f, 0.5f, 0.016f);
- assert(near(p, 10.0f, 0.1f)); // Should be very close to target
+ assert(test_near(p, 10.0f, 0.1f)); // Should be very close to target
// Test vector spring
vec3 vp(0, 0, 0), vv(0, 0, 0), vt(10, 0, 0);
@@ -210,7 +205,7 @@ void test_spring() {
void check_identity(const mat4& m) {
for (int i = 0; i < 16; ++i) {
float expected = (i % 5 == 0) ? 1.0f : 0.0f;
- if (!near(m.m[i], expected, 0.005f)) {
+ if (!test_near(m.m[i], expected, 0.005f)) {
std::cerr << "Matrix not Identity at index " << i << ": got " << m.m[i]
<< " expected " << expected << std::endl;
assert(false);
@@ -254,7 +249,7 @@ void test_matrix_inversion() {
mat4 trs_t = mat4::transpose(trs);
mat4 trs_tt = mat4::transpose(trs_t);
for (int i = 0; i < 16; ++i) {
- assert(near(trs.m[i], trs_tt.m[i]));
+ assert(test_near(trs.m[i], trs_tt.m[i], 0.001f));
}
// 7. Manual "stress" matrix (some small values, some large)