From 3158599a99ef98fac930f45ff6a7749daf6fcb44 Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Sun, 3 May 2026 21:02:31 +0000 Subject: [PATCH] test: add MIDI grid tests and fix test program hang Co-authored-by: aider (deepseek/deepseek-coder) --- test_engine.c | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) diff --git a/test_engine.c b/test_engine.c index e87b8d5..eb2e195 100644 --- a/test_engine.c +++ b/test_engine.c @@ -696,6 +696,307 @@ void test_quit_action(void) { printf("PASSED\n"); } +// ============================================================ +// Test 29: MIDI clip initial state +// ============================================================ +void test_midi_clip_initial_state(void) { + printf("Test 29: MIDI clip initial state... "); + AppState state = create_test_state(); + + for (int i = 0; i < MAX_CLIPS; i++) { + assert(state.midi_clips[i].state == CLIP_EMPTY); + assert(state.midi_clips[i].event_count == 0); + assert(state.midi_clips[i].read_index == 0); + assert(state.midi_clips[i].events != NULL); + assert(state.midi_clips[i].max_events == MAX_MIDI_EVENTS); + } + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 30: MIDI clip trigger empty starts recording +// ============================================================ +void test_midi_clip_trigger_empty_starts_recording(void) { + printf("Test 30: MIDI clip trigger empty starts recording... "); + AppState state = create_test_state(); + + Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + state = reducer(state, action); + + assert(state.midi_clips[0].state == CLIP_RECORDING); + assert(state.midi_clips[0].event_count == 0); + assert(state.midi_clips[0].read_index == 0); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 31: MIDI clip trigger recording starts looping +// ============================================================ +void test_midi_clip_trigger_recording_starts_looping(void) { + printf("Test 31: MIDI clip trigger recording starts looping... "); + AppState state = create_test_state(); + + // Start recording + Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_RECORDING); + + // Simulate some recorded events + state.midi_clips[0].event_count = 10; + + // Trigger again to stop recording and start looping + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_LOOPING); + assert(state.midi_clips[0].read_index == 0); + assert(state.midi_clips[0].event_count == 10); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 32: MIDI clip trigger looping stops it +// ============================================================ +void test_midi_clip_trigger_looping_stops(void) { + printf("Test 32: MIDI clip trigger looping stops it... "); + AppState state = create_test_state(); + + // Set up a looping MIDI clip + state.midi_clips[0].state = CLIP_LOOPING; + state.midi_clips[0].event_count = 10; + state.midi_clips[0].read_index = 5; + + Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_STOPPED); + assert(state.midi_clips[0].read_index == 0); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 33: MIDI clip trigger stopped resumes looping +// ============================================================ +void test_midi_clip_trigger_stopped_resumes_looping(void) { + printf("Test 33: MIDI clip trigger stopped resumes looping... "); + AppState state = create_test_state(); + + // Set up a stopped MIDI clip + state.midi_clips[0].state = CLIP_STOPPED; + state.midi_clips[0].event_count = 10; + + Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_LOOPING); + assert(state.midi_clips[0].read_index == 0); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 34: MIDI clip full cycle +// ============================================================ +void test_midi_clip_full_cycle(void) { + printf("Test 34: MIDI clip full cycle... "); + AppState state = create_test_state(); + + Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + + // Empty -> Recording + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_RECORDING); + + // Recording -> Looping + state.midi_clips[0].event_count = 20; + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_LOOPING); + assert(state.midi_clips[0].event_count == 20); + + // Looping -> Stopped + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_STOPPED); + + // Stopped -> Looping + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_LOOPING); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 35: MIDI clip reset +// ============================================================ +void test_midi_clip_reset(void) { + printf("Test 35: MIDI clip reset... "); + AppState state = create_test_state(); + + // Set up a MIDI clip with data + state.midi_clips[0].state = CLIP_LOOPING; + state.midi_clips[0].event_count = 20; + state.midi_clips[0].read_index = 10; + + Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = 0 } }; + state = reducer(state, action); + assert(state.midi_clips[0].state == CLIP_EMPTY); + assert(state.midi_clips[0].event_count == 0); + assert(state.midi_clips[0].read_index == 0); + // events pointer should still be valid + assert(state.midi_clips[0].events != NULL); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 36: MIDI clip multiple clips work independently +// ============================================================ +void test_midi_clip_multiple_clips(void) { + printf("Test 36: MIDI clip multiple clips work independently... "); + AppState state = create_test_state(); + + Action action0 = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + Action action1 = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 1 } }; + Action action2 = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 2 } }; + + // Clip 0: Empty -> Recording + state = reducer(state, action0); + assert(state.midi_clips[0].state == CLIP_RECORDING); + + // Clip 1: Empty -> Recording + state = reducer(state, action1); + assert(state.midi_clips[1].state == CLIP_RECORDING); + + // Clip 0: Recording -> Looping + state.midi_clips[0].event_count = 10; + state = reducer(state, action0); + assert(state.midi_clips[0].state == CLIP_LOOPING); + assert(state.midi_clips[1].state == CLIP_RECORDING); // Clip 1 unaffected + + // Clip 2: Empty -> Recording + state = reducer(state, action2); + assert(state.midi_clips[2].state == CLIP_RECORDING); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 37: MIDI clip invalid index +// ============================================================ +void test_midi_clip_invalid_index(void) { + printf("Test 37: MIDI clip invalid index... "); + AppState state = create_test_state(); + + // These should not crash + Action action_neg = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = -1 } }; + Action action_max = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = MAX_CLIPS } }; + Action reset_neg = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = -1 } }; + Action reset_max = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = MAX_CLIPS } }; + + state = reducer(state, action_neg); + state = reducer(state, action_max); + state = reducer(state, reset_neg); + state = reducer(state, reset_max); + + // Verify no state changes + assert(state.midi_clips[0].state == CLIP_EMPTY); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 38: MIDI clip show_midi_grid toggle +// ============================================================ +void test_midi_clip_show_grid_toggle(void) { + printf("Test 38: MIDI clip show_midi_grid toggle... "); + AppState state = create_test_state(); + + assert(state.show_midi_grid == false); + + Action action_on = { .type = ACTION_SET_SHOW_MIDI_GRID, .data.set_show_midi_grid = { .show = true } }; + state = reducer(state, action_on); + assert(state.show_midi_grid == true); + + Action action_off = { .type = ACTION_SET_SHOW_MIDI_GRID, .data.set_show_midi_grid = { .show = false } }; + state = reducer(state, action_off); + assert(state.show_midi_grid == false); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 39: MIDI clip events pointer persists after reset +// ============================================================ +void test_midi_clip_events_persist_after_reset(void) { + printf("Test 39: MIDI clip events pointer persists after reset... "); + AppState state = create_test_state(); + + // Get the original events pointer + MidiEvent *original_events = state.midi_clips[0].events; + assert(original_events != NULL); + + // Set up some events + state.midi_clips[0].events[0].note = 60; + state.midi_clips[0].events[0].velocity = 100; + state.midi_clips[0].events[0].timestamp = 0; + state.midi_clips[0].event_count = 1; + state.midi_clips[0].state = CLIP_LOOPING; + + // Reset the clip + Action action = { .type = ACTION_MIDI_CLIP_RESET, .data.midi_clip_reset = { .clip_index = 0 } }; + state = reducer(state, action); + + // Events pointer should still be the same (not freed) + assert(state.midi_clips[0].events == original_events); + assert(state.midi_clips[0].state == CLIP_EMPTY); + assert(state.midi_clips[0].event_count == 0); + + destroy_test_state(&state); + printf("PASSED\n"); +} + +// ============================================================ +// Test 40: MIDI clip trigger with events in buffer +// ============================================================ +void test_midi_clip_trigger_with_events(void) { + printf("Test 40: MIDI clip trigger with events in buffer... "); + AppState state = create_test_state(); + + // Set up a MIDI clip with events already recorded + state.midi_clips[0].events[0].note = 60; + state.midi_clips[0].events[0].velocity = 100; + state.midi_clips[0].events[0].timestamp = 0; + state.midi_clips[0].events[1].note = 64; + state.midi_clips[0].events[1].velocity = 80; + state.midi_clips[0].events[1].timestamp = 100; + state.midi_clips[0].event_count = 2; + state.midi_clips[0].state = CLIP_STOPPED; + + // Trigger to resume looping + Action action = { .type = ACTION_MIDI_CLIP_TRIGGER, .data.midi_clip_trigger = { .clip_index = 0 } }; + state = reducer(state, action); + + assert(state.midi_clips[0].state == CLIP_LOOPING); + assert(state.midi_clips[0].event_count == 2); + assert(state.midi_clips[0].read_index == 0); + + // Verify events are intact + assert(state.midi_clips[0].events[0].note == 60); + assert(state.midi_clips[0].events[1].note == 64); + + destroy_test_state(&state); + printf("PASSED\n"); +} + // ============================================================ // Main // ============================================================ @@ -731,6 +1032,20 @@ int main(void) { test_save_load_clip(); test_quit_action(); + test_midi_clip_initial_state(); + test_midi_clip_trigger_empty_starts_recording(); + test_midi_clip_trigger_recording_starts_looping(); + test_midi_clip_trigger_looping_stops(); + test_midi_clip_trigger_stopped_resumes_looping(); + test_midi_clip_full_cycle(); + test_midi_clip_reset(); + test_midi_clip_multiple_clips(); + test_midi_clip_invalid_index(); + test_midi_clip_show_grid_toggle(); + test_midi_clip_events_persist_after_reset(); + test_midi_clip_trigger_with_events(); + printf("\nAll tests passed!\n"); + exit(0); return 0; }