Files
jack-looper/test_stress.c
Loic Coenen 93f9b60c41 refactor: update stress tests to use new dispatcher API
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
2026-05-02 20:20:53 +00:00

613 lines
18 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdatomic.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <setjmp.h>
#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;
}