613 lines
18 KiB
C
613 lines
18 KiB
C
#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;
|
||
}
|