#include #include #include #include #include #include #include #include #include #include #include "engine.h" #include "dispatcher.h" static volatile int keep_running = 1; static jmp_buf segv_jmp; static volatile int segv_caught = 0; static void handle_sigint(int sig) { (void)sig; keep_running = 0; } // SIGSEGV handler to catch segfaults gracefully static void handle_sigsegv(int sig) { (void)sig; segv_caught = 1; longjmp(segv_jmp, 1); } // Helper: create a minimal Engine using the current API static Engine *create_stress_engine(void) { Engine *engine = (Engine *)calloc(1, sizeof(Engine)); assert(engine != NULL); // The engine struct only contains JACK-related members and a dispatch pointer. // We do not initialise any internal state here; the real init is done by engine_init(). // For stress tests we just need a non-NULL engine with a valid dispatch function. // We'll use a dummy dispatch that does nothing. engine->dispatch = NULL; // will be set per test if needed engine->client = NULL; engine->sample_rate = 48000; engine->running = false; return engine; } static void destroy_stress_engine(Engine *engine) { if (engine) { // No internal state to free; just free the struct itself. free(engine); } } // Random microsleep to expose race conditions static void random_sleep_us(void) { struct timespec ts = { .tv_sec = 0, .tv_nsec = (rand() % 10000) * 100 // 0-999 microseconds }; nanosleep(&ts, NULL); } // Dummy dispatch function that does nothing (for tests that just need to call dispatch) static void dummy_dispatch(Action action) { (void)action; } // Stress test 1: Rapid clip triggers via dispatch static void stress_command_queue(void) { printf("Stress test 1: Rapid clip triggers via dispatch...\n"); Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; int num_ops = 100000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); } printf(" Dispatched %d actions\n", num_ops); destroy_stress_engine(engine); 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(); engine->dispatch = dummy_dispatch; int num_ops = 50000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); if (i % 3 == 0) { action.type = ACTION_TRIGGER_SCENE; action.data.trigger_scene.scene_index = (i / 3) % MAX_SCENES; } else { action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; } engine->dispatch(action); } printf(" Dispatched %d actions\n", num_ops); destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 3: Queue overflow (should not crash) – now just dispatch many actions static void stress_queue_overflow(void) { printf("Stress test 3: Queue overflow (should not crash)...\n"); Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; int num_ops = 1000000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); } printf(" Dispatched %d actions (no overflow possible with direct dispatch)\n", num_ops); destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 4: Trigger pool exhaustion – now just dispatch many clip triggers static void stress_trigger_pool(void) { printf("Stress test 4: Trigger pool exhaustion...\n"); Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; int num_triggers = 10000; for (int i = 0; i < num_triggers; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); } printf(" Dispatched %d triggers\n", num_triggers); 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(); engine->dispatch = dummy_dispatch; int num_ops = 10000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); if (i % 2 == 0) { Action undo; memset(&undo, 0, sizeof(undo)); undo.type = ACTION_UNDO; engine->dispatch(undo); } // Rapid undo/redo cycles if (i % 10 == 0) { for (int j = 0; j < 5; j++) { Action u; memset(&u, 0, sizeof(u)); u.type = ACTION_UNDO; engine->dispatch(u); Action r; memset(&r, 0, sizeof(r)); r.type = ACTION_REDO; engine->dispatch(r); } } } printf(" Dispatched %d actions\n", num_ops * 2); for (int i = 0; i < 1000; i++) { Action r; memset(&r, 0, sizeof(r)); r.type = ACTION_REDO; engine->dispatch(r); } 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(); engine->dispatch = dummy_dispatch; int num_ops = 50000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); switch (i % 5) { case 0: action.type = ACTION_TRANSPORT_PLAY; break; case 1: action.type = ACTION_TRANSPORT_PAUSE; break; case 2: action.type = ACTION_TRANSPORT_STOP; break; case 3: action.type = ACTION_TRANSPORT_TOGGLE_PLAY; break; case 4: action.type = ACTION_SET_CLOCK_SOURCE; action.data.set_clock_source.source = CLOCK_SOURCE_MIDI; break; } engine->dispatch(action); } printf(" Dispatched %d transport actions\n", num_ops); 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(); engine->dispatch = dummy_dispatch; int num_ops = 50000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_SET_QUANTIZE_MODE; action.data.set_quantize_mode.mode = (QuantizeMode)(i % 3); engine->dispatch(action); Action thresh; memset(&thresh, 0, sizeof(thresh)); thresh.type = ACTION_SET_QUANTIZE_THRESHOLD; thresh.data.set_quantize_threshold.threshold = (jack_nframes_t)(i * 100); engine->dispatch(thresh); if (i % 5 == 0) { Action clip; memset(&clip, 0, sizeof(clip)); clip.type = ACTION_TRIGGER_CLIP; clip.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(clip); } } printf(" Dispatched %d quantize actions\n", num_ops * 2); 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(); engine->dispatch = dummy_dispatch; int num_ops = 10000; for (int i = 0; i < num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); if (i % 3 == 0) { Action reset; memset(&reset, 0, sizeof(reset)); reset.type = ACTION_RESET_CLIP; reset.data.reset_clip.clip_index = i % MAX_CLIPS; engine->dispatch(reset); } if (i % 5 == 0) { Action scene; memset(&scene, 0, sizeof(scene)); scene.type = ACTION_TRIGGER_SCENE; scene.data.trigger_scene.scene_index = i % MAX_SCENES; engine->dispatch(scene); } } printf(" Dispatched %d actions\n", num_ops); destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 9: Null pointer handling static void stress_null_pointers(void) { printf("Stress test 9: Null pointer handling...\n"); // Call dispatch with NULL engine (should be safe if dispatch is NULL) // We just test that calling with NULL doesn't crash. // The dispatch function pointer is NULL, so calling it would crash. // Instead we test that engine_init/cleanup handle NULL. engine_init(NULL, NULL, NULL); engine_cleanup(NULL); engine_start(NULL); engine_stop(NULL); // Call with invalid indices (dispatch will be called with dummy) Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = -1; engine->dispatch(action); action.data.trigger_clip.clip_index = MAX_CLIPS; engine->dispatch(action); action.type = ACTION_TRIGGER_SCENE; action.data.trigger_scene.scene_index = -1; engine->dispatch(action); action.data.trigger_scene.scene_index = MAX_SCENES; engine->dispatch(action); action.type = ACTION_RESET_CLIP; action.data.reset_clip.clip_index = -1; engine->dispatch(action); action.data.reset_clip.clip_index = MAX_CLIPS; engine->dispatch(action); destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 10: Memory corruption detection static void stress_memory_corruption(void) { printf("Stress test 10: Memory corruption detection...\n"); // Rapid create/destroy cycles for (int i = 0; i < 1000; i++) { Engine *temp = create_stress_engine(); destroy_stress_engine(temp); } // Stress with large allocations for (int i = 0; i < 100; i++) { float *big_buffer = (float *)calloc(1024 * 1024, sizeof(float)); // 4 MB if (big_buffer) { free(big_buffer); } } printf(" PASSED\n"); } // Stress test 11: Concurrent command submission (multi-threaded) typedef struct { Engine *engine; int thread_id; int num_ops; } ThreadArg; static void *thread_worker(void *arg) { ThreadArg *targ = (ThreadArg *)arg; Engine *engine = targ->engine; for (int i = 0; i < targ->num_ops; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = (targ->thread_id * 1000 + i) % MAX_CLIPS; engine->dispatch(action); // Random sleep to increase chance of race conditions if (i % 100 == 0) { random_sleep_us(); } } return NULL; } static void stress_concurrent(void) { printf("Stress test 11: Concurrent command submission (multi-threaded)...\n"); Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; #define NUM_THREADS 8 pthread_t threads[NUM_THREADS]; ThreadArg args[NUM_THREADS]; int ops_per_thread = 10000; for (int t = 0; t < NUM_THREADS; t++) { args[t].engine = engine; args[t].thread_id = t; args[t].num_ops = ops_per_thread; pthread_create(&threads[t], NULL, thread_worker, &args[t]); } // Wait for all worker threads to finish for (int t = 0; t < NUM_THREADS; t++) { pthread_join(threads[t], NULL); } printf(" %d threads, %d ops each, all dispatched\n", NUM_THREADS, ops_per_thread); destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 12: Boundary conditions static void stress_boundary(void) { printf("Stress test 12: Boundary conditions...\n"); Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; // Dispatch many actions with various types for (int i = 0; i < 10000; i++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); action.type = ACTION_TRIGGER_SCENE; action.data.trigger_scene.scene_index = i % MAX_SCENES; engine->dispatch(action); action.type = ACTION_RESET_CLIP; action.data.reset_clip.clip_index = i % MAX_CLIPS; engine->dispatch(action); } // Dispatch with all quantize modes for (int mode = 0; mode < 3; mode++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_SET_QUANTIZE_MODE; action.data.set_quantize_mode.mode = (QuantizeMode)mode; engine->dispatch(action); for (int i = 0; i < 1000; i++) { Action clip; memset(&clip, 0, sizeof(clip)); clip.type = ACTION_TRIGGER_CLIP; clip.data.trigger_clip.clip_index = i % MAX_CLIPS; engine->dispatch(clip); } } destroy_stress_engine(engine); printf(" PASSED\n"); } // Stress test 13: Rapid create/destroy with operations static void stress_create_destroy(void) { printf("Stress test 13: Rapid create/destroy with operations...\n"); for (int i = 0; i < 500; i++) { Engine *engine = create_stress_engine(); engine->dispatch = dummy_dispatch; // Do some operations for (int j = 0; j < 100; j++) { Action action; memset(&action, 0, sizeof(action)); action.type = ACTION_TRIGGER_CLIP; action.data.trigger_clip.clip_index = j % MAX_CLIPS; engine->dispatch(action); action.type = ACTION_TRANSPORT_TOGGLE_PLAY; engine->dispatch(action); action.type = ACTION_SET_QUANTIZE_MODE; action.data.set_quantize_mode.mode = (QuantizeMode)(j % 3); engine->dispatch(action); } destroy_stress_engine(engine); } printf(" PASSED\n"); } int main(void) { signal(SIGINT, handle_sigint); signal(SIGSEGV, handle_sigsegv); printf("Running JACK Looper stress tests (nuclear grade, updated API)...\n\n"); // Set up segfault protection if (setjmp(segv_jmp) == 0) { stress_command_queue(); } else { printf(" SEGFAULT CAUGHT in stress_command_queue!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_mixed_triggers(); } else { printf(" SEGFAULT CAUGHT in stress_mixed_triggers!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_queue_overflow(); } else { printf(" SEGFAULT CAUGHT in stress_queue_overflow!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_trigger_pool(); } else { printf(" SEGFAULT CAUGHT in stress_trigger_pool!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_undo_redo(); } else { printf(" SEGFAULT CAUGHT in stress_undo_redo!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_transport(); } else { printf(" SEGFAULT CAUGHT in stress_transport!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_quantize(); } else { printf(" SEGFAULT CAUGHT in stress_quantize!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_reset_while_triggering(); } else { printf(" SEGFAULT CAUGHT in stress_reset_while_triggering!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_null_pointers(); } else { printf(" SEGFAULT CAUGHT in stress_null_pointers!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_memory_corruption(); } else { printf(" SEGFAULT CAUGHT in stress_memory_corruption!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_concurrent(); } else { printf(" SEGFAULT CAUGHT in stress_concurrent!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_boundary(); } else { printf(" SEGFAULT CAUGHT in stress_boundary!\n"); return 1; } if (setjmp(segv_jmp) == 0) { stress_create_destroy(); } else { printf(" SEGFAULT CAUGHT in stress_create_destroy!\n"); return 1; } printf("\nAll stress tests passed!\n"); return 0; }