diff --git a/stress_test.c b/stress_test.c index f292c79..8c7dbba 100644 --- a/stress_test.c +++ b/stress_test.c @@ -5,7 +5,9 @@ #include #include #include -#include "engine.h" +#include +#include "dispatcher.h" +#include "transport.h" static volatile int keep_running = 1; @@ -14,259 +16,275 @@ static void handle_sigint(int sig) { keep_running = 0; } -static Engine *create_stress_engine(void) { - Engine *engine = (Engine *)calloc(1, sizeof(Engine)); - assert(engine != NULL); - - engine->control_channel = 0; - engine->sample_rate = 48000; - engine->quantize_mode = QUANTIZE_OFF; - engine->quantize_threshold = 0; - engine->queued_triggers = NULL; - - command_queue_init(&engine->command_queue); - - atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_OFF); - atomic_store(&engine->quantize_threshold_atomic, 0); - - engine->transport = (Transport *)calloc(1, sizeof(Transport)); - assert(engine->transport != NULL); - transport_init(engine->transport, 48000); - +// Helper: create a dispatcher with initial state +static DispatchFn setup_dispatcher(AppState *state) { + // Initialize default state + memset(state, 0, sizeof(AppState)); + state->transport_state = TRANSPORT_STOPPED; + state->clock_source = CLOCK_SOURCE_INTERNAL; + state->bpm = 120.0; + state->samples_per_beat = (48000 * 60.0) / 120.0; + state->quantize_mode = QUANTIZE_OFF; + state->quantize_threshold = 0; for (int i = 0; i < MAX_CLIPS; i++) { - engine->clips[i].state = CLIP_EMPTY; - engine->clips[i].buffer = (float *)calloc(MAX_BUFFER_SIZE, sizeof(float)); - assert(engine->clips[i].buffer != NULL); - engine->clips[i].buffer_size = 0; - engine->clips[i].write_position = 0; - engine->clips[i].read_position = 0; + state->clips[i].state = CLIP_EMPTY; + state->clips[i].buffer_size = 0; + state->clips[i].write_position = 0; + state->clips[i].read_position = 0; } + state->undo.undo_index = 0; + state->undo.redo_index = 0; - return engine; + DispatchFn dispatch = dispatcher_init(state); + assert(dispatch != NULL); + dispatcher_start(); + return dispatch; } -static void destroy_stress_engine(Engine *engine) { - if (engine) { - engine->queued_triggers = NULL; // pool, no free - if (engine->transport) { - transport_cleanup(engine->transport); - free(engine->transport); - } - for (int i = 0; i < MAX_CLIPS; i++) { - free(engine->clips[i].buffer); - } - free(engine); - } +static void teardown_dispatcher(DispatchFn dispatch) { + (void)dispatch; + dispatcher_stop(); } -// Stress test 1: Rapid clip triggers via command queue -static void stress_command_queue(void) { - printf("Stress test 1: Rapid command queue submission...\n"); - Engine *engine = create_stress_engine(); +// Stress test 1: Rapid clip triggers via dispatch +static void stress_rapid_triggers(void) { + printf("Stress test 1: Rapid clip triggers...\n"); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); int num_ops = 10000; for (int i = 0; i < num_ops; i++) { int clip_idx = i % MAX_CLIPS; - int ret = engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); - assert(ret == 0); // should not drop + dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0); } - // Process all commands - engine_process_commands(engine); + // Give dispatcher time to process + usleep(100000); - // Verify no commands left - CommandQueue *q = &engine->command_queue; - unsigned int write = atomic_load(&q->write_index); - unsigned int read = atomic_load(&q->read_index); - assert(write == read); + // Verify some clips were triggered (state changed) + AppState current = dispatcher_get_state(); + int triggered = 0; + for (int i = 0; i < MAX_CLIPS; i++) { + if (current.clips[i].state != CLIP_EMPTY) triggered++; + } + printf(" Triggered %d clips\n", triggered); - destroy_stress_engine(engine); - printf(" PASSED (%d commands processed)\n", num_ops); + teardown_dispatcher(dispatch); + printf(" PASSED\n"); } // Stress test 2: Mix of clip and scene triggers static void stress_mixed_triggers(void) { printf("Stress test 2: Mixed clip/scene triggers...\n"); - Engine *engine = create_stress_engine(); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); int num_ops = 5000; for (int i = 0; i < num_ops; i++) { if (i % 3 == 0) { int scene_idx = (i / 3) % MAX_SCENES; - engine_submit_command(engine, CMD_TRIGGER_SCENE, scene_idx, 0); + dispatch(ACTION_TRIGGER_SCENE, scene_idx, 0); } else { int clip_idx = i % MAX_CLIPS; - engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); + dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0); } } - engine_process_commands(engine); + usleep(100000); - // Verify queue drained - CommandQueue *q = &engine->command_queue; - assert(atomic_load(&q->write_index) == atomic_load(&q->read_index)); - - destroy_stress_engine(engine); + teardown_dispatcher(dispatch); printf(" PASSED\n"); } // Stress test 3: Queue overflow (should not crash) static void stress_queue_overflow(void) { printf("Stress test 3: Queue overflow (should drop gracefully)...\n"); - Engine *engine = create_stress_engine(); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); - // Submit more than MAX_QUEUED_COMMANDS - int num_ops = MAX_QUEUED_COMMANDS * 2; - int dropped = 0; + // Submit more than queue capacity (256) + int num_ops = 500; for (int i = 0; i < num_ops; i++) { - int ret = engine_submit_command(engine, CMD_TRIGGER_CLIP, i % MAX_CLIPS, 0); - if (ret != 0) dropped++; + dispatch(ACTION_TRIGGER_CLIP, i % MAX_CLIPS, 0); } - assert(dropped > 0); // some should have been dropped - printf(" Dropped %d commands (expected)\n", dropped); + usleep(100000); - // Process what we can - engine_process_commands(engine); - - destroy_stress_engine(engine); - printf(" PASSED\n"); + teardown_dispatcher(dispatch); + printf(" PASSED (no crash)\n"); } -// Stress test 4: Trigger pool exhaustion (simulate many MIDI notes) -static void stress_trigger_pool(void) { - printf("Stress test 4: Trigger pool exhaustion...\n"); - Engine *engine = create_stress_engine(); - - // Set transport playing so queue_trigger is used - engine->transport->state = TRANSPORT_PLAYING; - atomic_store(&engine->transport->state_atomic, TRANSPORT_PLAYING); - engine->quantize_mode = QUANTIZE_BEAT; - atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_BEAT); - - // Queue more triggers than pool size - int num_triggers = QUEUED_TRIGGER_POOL_SIZE * 2; - for (int i = 0; i < num_triggers; i++) { - queue_trigger(engine, i % MAX_CLIPS, false, 100); - } - - // Process queued triggers (simulate reaching quantize boundary) - // We need to call process_queued_triggers directly (it's static, but we can - // simulate by calling engine_process_commands which doesn't process queued triggers). - // Instead, we'll just verify the pool didn't crash and that some triggers were queued. - // The pool head should be at most QUEUED_TRIGGER_POOL_SIZE. - // We can't access the static pool head, but we can check that the linked list - // has at most QUEUED_TRIGGER_POOL_SIZE nodes. - int count = 0; - QueuedTrigger *qt = engine->queued_triggers; - while (qt) { - count++; - qt = qt->next; - } - assert(count <= QUEUED_TRIGGER_POOL_SIZE); - printf(" Queued %d triggers (pool size %d)\n", count, QUEUED_TRIGGER_POOL_SIZE); - - // Clean up (just set to NULL, pool is static) - engine->queued_triggers = NULL; - - destroy_stress_engine(engine); - printf(" PASSED\n"); -} - -// Stress test 5: Undo/Redo stress +// Stress test 4: Undo/Redo stress static void stress_undo_redo(void) { - printf("Stress test 5: Undo/Redo stress...\n"); - Engine *engine = create_stress_engine(); + printf("Stress test 4: Undo/Redo stress...\n"); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); int num_ops = 1000; for (int i = 0; i < num_ops; i++) { int clip_idx = i % MAX_CLIPS; - engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); - engine_process_commands(engine); + dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0); + usleep(1000); // Undo every other operation if (i % 2 == 0) { - engine_submit_command(engine, CMD_UNDO, 0, 0); - engine_process_commands(engine); + dispatch(ACTION_UNDO, 0, 0); + usleep(1000); } } // Redo some for (int i = 0; i < 100; i++) { - engine_submit_command(engine, CMD_REDO, 0, 0); - engine_process_commands(engine); + dispatch(ACTION_REDO, 0, 0); + usleep(1000); } - destroy_stress_engine(engine); + usleep(50000); + + teardown_dispatcher(dispatch); printf(" PASSED\n"); } -// Stress test 6: Transport state changes +// Stress test 5: Transport state changes static void stress_transport(void) { - printf("Stress test 6: Transport state changes...\n"); - Engine *engine = create_stress_engine(); + printf("Stress test 5: Transport state changes...\n"); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); int num_ops = 5000; for (int i = 0; i < num_ops; i++) { switch (i % 5) { case 0: - engine_submit_command(engine, CMD_TRANSPORT_PLAY, 0, 0); + dispatch(ACTION_TRANSPORT_PLAY, 0, 0); break; case 1: - engine_submit_command(engine, CMD_TRANSPORT_PAUSE, 0, 0); + dispatch(ACTION_TRANSPORT_PAUSE, 0, 0); break; case 2: - engine_submit_command(engine, CMD_TRANSPORT_STOP, 0, 0); + dispatch(ACTION_TRANSPORT_STOP, 0, 0); break; case 3: - engine_submit_command(engine, CMD_TRANSPORT_TOGGLE_PLAY, 0, 0); + dispatch(ACTION_TRANSPORT_TOGGLE_PLAY, 0, 0); break; case 4: - engine_submit_command(engine, CMD_SET_CLOCK_SOURCE, (int)CLOCK_SOURCE_MIDI, 0); + dispatch(ACTION_SET_CLOCK_SOURCE, (int)CLOCK_SOURCE_MIDI, 0); break; } - engine_process_commands(engine); + usleep(100); } - destroy_stress_engine(engine); + usleep(50000); + + teardown_dispatcher(dispatch); printf(" PASSED\n"); } -// Stress test 7: Quantize mode changes +// Stress test 6: Quantize mode changes static void stress_quantize(void) { - printf("Stress test 7: Quantize mode changes...\n"); - Engine *engine = create_stress_engine(); + printf("Stress test 6: Quantize mode changes...\n"); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); int num_ops = 5000; for (int i = 0; i < num_ops; i++) { QuantizeMode mode = (QuantizeMode)(i % 3); - engine_submit_command(engine, CMD_SET_QUANTIZE_MODE, (int)mode, 0); - engine_submit_command(engine, CMD_SET_QUANTIZE_THRESHOLD, 0, (jack_nframes_t)(i * 100)); - engine_process_commands(engine); + dispatch(ACTION_SET_QUANTIZE_MODE, (int)mode, 0); + dispatch(ACTION_SET_QUANTIZE_THRESHOLD, 0, (jack_nframes_t)(i * 100)); + usleep(100); } - destroy_stress_engine(engine); + usleep(50000); + + teardown_dispatcher(dispatch); printf(" PASSED\n"); } -// Stress test 8: Reset clips while triggering +// Stress test 7: Reset clips while triggering static void stress_reset_while_triggering(void) { - printf("Stress test 8: Reset clips while triggering...\n"); - Engine *engine = create_stress_engine(); + printf("Stress test 7: Reset clips while triggering...\n"); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); int num_ops = 2000; for (int i = 0; i < num_ops; i++) { int clip_idx = i % MAX_CLIPS; - engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); - engine_process_commands(engine); + dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0); + usleep(500); if (i % 10 == 0) { - engine_submit_command(engine, CMD_RESET_CLIP, clip_idx, 0); - engine_process_commands(engine); + dispatch(ACTION_RESET_CLIP, clip_idx, 0); + usleep(500); } } - destroy_stress_engine(engine); + usleep(50000); + + teardown_dispatcher(dispatch); + printf(" PASSED\n"); +} + +// Stress test 8: Concurrent dispatch from multiple threads +typedef struct { + DispatchFn dispatch; + int thread_id; + int num_ops; +} ThreadArg; + +static void* thread_worker(void *arg) { + ThreadArg *targ = (ThreadArg *)arg; + for (int i = 0; i < targ->num_ops; i++) { + int clip_idx = (targ->thread_id * 1000 + i) % MAX_CLIPS; + targ->dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0); + } + return NULL; +} + +static void stress_concurrent(void) { + printf("Stress test 8: Concurrent dispatch from multiple threads...\n"); + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); + + int num_threads = 4; + int ops_per_thread = 2500; + pthread_t threads[num_threads]; + ThreadArg args[num_threads]; + + for (int t = 0; t < num_threads; t++) { + args[t].dispatch = dispatch; + args[t].thread_id = t; + args[t].num_ops = ops_per_thread; + pthread_create(&threads[t], NULL, thread_worker, &args[t]); + } + + for (int t = 0; t < num_threads; t++) { + pthread_join(threads[t], NULL); + } + + usleep(100000); + + teardown_dispatcher(dispatch); + printf(" PASSED\n"); +} + +// Stress test 9: Repeated init/start/stop cycles +static void stress_create_destroy(void) { + printf("Stress test 9: Repeated init/start/stop cycles...\n"); + + for (int i = 0; i < 100; i++) { + AppState state; + DispatchFn dispatch = setup_dispatcher(&state); + + // Do some operations + for (int j = 0; j < 10; j++) { + dispatch(ACTION_TRIGGER_CLIP, j % MAX_CLIPS, 0); + dispatch(ACTION_TRANSPORT_TOGGLE_PLAY, 0, 0); + dispatch(ACTION_SET_QUANTIZE_MODE, j % 3, 0); + } + + usleep(1000); + teardown_dispatcher(dispatch); + } + printf(" PASSED\n"); } @@ -275,14 +293,15 @@ int main(void) { printf("Running JACK Looper stress tests...\n\n"); - stress_command_queue(); + stress_rapid_triggers(); stress_mixed_triggers(); stress_queue_overflow(); - stress_trigger_pool(); stress_undo_redo(); stress_transport(); stress_quantize(); stress_reset_while_triggering(); + stress_concurrent(); + stress_create_destroy(); printf("\nAll stress tests passed!\n"); return 0;