diff --git a/stress_test.c b/stress_test.c new file mode 100644 index 0000000..f292c79 --- /dev/null +++ b/stress_test.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include +#include +#include "engine.h" + +static volatile int keep_running = 1; + +static void handle_sigint(int sig) { + (void)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); + + 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; + } + + return engine; +} + +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); + } +} + +// 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(); + + 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 + } + + // Process all commands + engine_process_commands(engine); + + // 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); + + destroy_stress_engine(engine); + printf(" PASSED (%d commands processed)\n", num_ops); +} + +// 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(); + + 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); + } else { + int clip_idx = i % MAX_CLIPS; + engine_submit_command(engine, CMD_TRIGGER_CLIP, clip_idx, 0); + } + } + + engine_process_commands(engine); + + // Verify queue drained + CommandQueue *q = &engine->command_queue; + assert(atomic_load(&q->write_index) == atomic_load(&q->read_index)); + + destroy_stress_engine(engine); + 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(); + + // Submit more than MAX_QUEUED_COMMANDS + int num_ops = MAX_QUEUED_COMMANDS * 2; + int dropped = 0; + 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++; + } + + assert(dropped > 0); // some should have been dropped + printf(" Dropped %d commands (expected)\n", dropped); + + // Process what we can + engine_process_commands(engine); + + destroy_stress_engine(engine); + printf(" PASSED\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 +static void stress_undo_redo(void) { + printf("Stress test 5: Undo/Redo stress...\n"); + Engine *engine = create_stress_engine(); + + 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); + + // Undo every other operation + if (i % 2 == 0) { + engine_submit_command(engine, CMD_UNDO, 0, 0); + engine_process_commands(engine); + } + } + + // Redo some + for (int i = 0; i < 100; i++) { + engine_submit_command(engine, CMD_REDO, 0, 0); + engine_process_commands(engine); + } + + destroy_stress_engine(engine); + printf(" PASSED\n"); +} + +// Stress test 6: Transport state changes +static void stress_transport(void) { + printf("Stress test 6: Transport state changes...\n"); + Engine *engine = create_stress_engine(); + + 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); + break; + case 1: + engine_submit_command(engine, CMD_TRANSPORT_PAUSE, 0, 0); + break; + case 2: + engine_submit_command(engine, CMD_TRANSPORT_STOP, 0, 0); + break; + case 3: + engine_submit_command(engine, CMD_TRANSPORT_TOGGLE_PLAY, 0, 0); + break; + case 4: + engine_submit_command(engine, CMD_SET_CLOCK_SOURCE, (int)CLOCK_SOURCE_MIDI, 0); + break; + } + engine_process_commands(engine); + } + + destroy_stress_engine(engine); + printf(" PASSED\n"); +} + +// Stress test 7: Quantize mode changes +static void stress_quantize(void) { + printf("Stress test 7: Quantize mode changes...\n"); + Engine *engine = create_stress_engine(); + + 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); + } + + destroy_stress_engine(engine); + printf(" PASSED\n"); +} + +// Stress test 8: 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(); + + 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); + + if (i % 10 == 0) { + engine_submit_command(engine, CMD_RESET_CLIP, clip_idx, 0); + engine_process_commands(engine); + } + } + + destroy_stress_engine(engine); + printf(" PASSED\n"); +} + +int main(void) { + signal(SIGINT, handle_sigint); + + printf("Running JACK Looper stress tests...\n\n"); + + stress_command_queue(); + stress_mixed_triggers(); + stress_queue_overflow(); + stress_trigger_pool(); + stress_undo_redo(); + stress_transport(); + stress_quantize(); + stress_reset_while_triggering(); + + printf("\nAll stress tests passed!\n"); + return 0; +}