309 lines
8.2 KiB
C
309 lines
8.2 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 "dispatcher.h"
|
|
#include "transport.h"
|
|
|
|
static volatile int keep_running = 1;
|
|
|
|
static void handle_sigint(int sig) {
|
|
(void)sig;
|
|
keep_running = 0;
|
|
}
|
|
|
|
// 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++) {
|
|
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;
|
|
|
|
DispatchFn dispatch = dispatcher_init(state);
|
|
assert(dispatch != NULL);
|
|
dispatcher_start();
|
|
return dispatch;
|
|
}
|
|
|
|
static void teardown_dispatcher(DispatchFn dispatch) {
|
|
(void)dispatch;
|
|
dispatcher_stop();
|
|
}
|
|
|
|
// 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;
|
|
dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
|
}
|
|
|
|
// Give dispatcher time to process
|
|
usleep(100000);
|
|
|
|
// 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);
|
|
|
|
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");
|
|
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;
|
|
dispatch(ACTION_TRIGGER_SCENE, scene_idx, 0);
|
|
} else {
|
|
int clip_idx = i % MAX_CLIPS;
|
|
dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
|
}
|
|
}
|
|
|
|
usleep(100000);
|
|
|
|
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");
|
|
AppState state;
|
|
DispatchFn dispatch = setup_dispatcher(&state);
|
|
|
|
// Submit more than queue capacity (256)
|
|
int num_ops = 500;
|
|
for (int i = 0; i < num_ops; i++) {
|
|
dispatch(ACTION_TRIGGER_CLIP, i % MAX_CLIPS, 0);
|
|
}
|
|
|
|
usleep(100000);
|
|
|
|
teardown_dispatcher(dispatch);
|
|
printf(" PASSED (no crash)\n");
|
|
}
|
|
|
|
// Stress test 4: Undo/Redo stress
|
|
static void stress_undo_redo(void) {
|
|
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;
|
|
dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
|
usleep(1000);
|
|
|
|
// Undo every other operation
|
|
if (i % 2 == 0) {
|
|
dispatch(ACTION_UNDO, 0, 0);
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
// Redo some
|
|
for (int i = 0; i < 100; i++) {
|
|
dispatch(ACTION_REDO, 0, 0);
|
|
usleep(1000);
|
|
}
|
|
|
|
usleep(50000);
|
|
|
|
teardown_dispatcher(dispatch);
|
|
printf(" PASSED\n");
|
|
}
|
|
|
|
// Stress test 5: Transport state changes
|
|
static void stress_transport(void) {
|
|
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:
|
|
dispatch(ACTION_TRANSPORT_PLAY, 0, 0);
|
|
break;
|
|
case 1:
|
|
dispatch(ACTION_TRANSPORT_PAUSE, 0, 0);
|
|
break;
|
|
case 2:
|
|
dispatch(ACTION_TRANSPORT_STOP, 0, 0);
|
|
break;
|
|
case 3:
|
|
dispatch(ACTION_TRANSPORT_TOGGLE_PLAY, 0, 0);
|
|
break;
|
|
case 4:
|
|
dispatch(ACTION_SET_CLOCK_SOURCE, (int)CLOCK_SOURCE_MIDI, 0);
|
|
break;
|
|
}
|
|
usleep(100);
|
|
}
|
|
|
|
usleep(50000);
|
|
|
|
teardown_dispatcher(dispatch);
|
|
printf(" PASSED\n");
|
|
}
|
|
|
|
// Stress test 6: Quantize mode changes
|
|
static void stress_quantize(void) {
|
|
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);
|
|
dispatch(ACTION_SET_QUANTIZE_MODE, (int)mode, 0);
|
|
dispatch(ACTION_SET_QUANTIZE_THRESHOLD, 0, (jack_nframes_t)(i * 100));
|
|
usleep(100);
|
|
}
|
|
|
|
usleep(50000);
|
|
|
|
teardown_dispatcher(dispatch);
|
|
printf(" PASSED\n");
|
|
}
|
|
|
|
// Stress test 7: Reset clips while triggering
|
|
static void stress_reset_while_triggering(void) {
|
|
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;
|
|
dispatch(ACTION_TRIGGER_CLIP, clip_idx, 0);
|
|
usleep(500);
|
|
|
|
if (i % 10 == 0) {
|
|
dispatch(ACTION_RESET_CLIP, clip_idx, 0);
|
|
usleep(500);
|
|
}
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
int main(void) {
|
|
signal(SIGINT, handle_sigint);
|
|
|
|
printf("Running JACK Looper stress tests...\n\n");
|
|
|
|
stress_rapid_triggers();
|
|
stress_mixed_triggers();
|
|
stress_queue_overflow();
|
|
stress_undo_redo();
|
|
stress_transport();
|
|
stress_quantize();
|
|
stress_reset_while_triggering();
|
|
stress_concurrent();
|
|
stress_create_destroy();
|
|
|
|
printf("\nAll stress tests passed!\n");
|
|
return 0;
|
|
}
|