xtc/tests/integration/test_reliability.c

224 lines
10 KiB
C

/* GNU ld allocation interposition: fail preparation at every allocation and
* reject host allocation/reclamation inside processing. No production hooks. */
#include "xtc/xtc_realtime.h"
#include "xtc/xtc_native.h"
#include "xtc/xtc_control.h"
#include "xtc/xtc_plugin.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *__real_malloc(size_t size);
void *__real_calloc(size_t count, size_t size);
void *__real_realloc(void *pointer, size_t size);
void __real_free(void *pointer);
static int processing, fail_after = -1;
/* Catch direct host I/O and blocking synchronization in guarded callbacks.
* Dynamic dependencies have their own symbols and are outside this audit. */
FILE *__real_fopen(const char *path, const char *mode);
size_t __real_fread(void *buffer, size_t size, size_t count, FILE *file);
size_t __real_fwrite(const void *buffer, size_t size, size_t count, FILE *file);
ssize_t __real_read(int fd, void *buffer, size_t size);
ssize_t __real_write(int fd, const void *buffer, size_t size);
int __real_pthread_mutex_lock(pthread_mutex_t *mutex);
int __real_pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *mutex);
FILE *__wrap_fopen(const char *path, const char *mode)
{ assert(!processing); return __real_fopen(path, mode); }
size_t __wrap_fread(void *buffer, size_t size, size_t count, FILE *file)
{ assert(!processing); return __real_fread(buffer, size, count, file); }
size_t __wrap_fwrite(const void *buffer, size_t size, size_t count, FILE *file)
{ assert(!processing); return __real_fwrite(buffer, size, count, file); }
ssize_t __wrap_read(int fd, void *buffer, size_t size)
{ assert(!processing); return __real_read(fd, buffer, size); }
ssize_t __wrap_write(int fd, const void *buffer, size_t size)
{ assert(!processing); return __real_write(fd, buffer, size); }
int __wrap_pthread_mutex_lock(pthread_mutex_t *mutex)
{ assert(!processing); return __real_pthread_mutex_lock(mutex); }
int __wrap_pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *mutex)
{ assert(!processing); return __real_pthread_cond_wait(condition, mutex); }
static unsigned int prepared, destroyed;
static int fail_allocation(void)
{
assert(!processing);
if (fail_after < 0) return 0;
if (fail_after == 0) return 1;
--fail_after;
return 0;
}
void *__wrap_malloc(size_t size) { return fail_allocation() ? NULL : __real_malloc(size); }
void *__wrap_calloc(size_t count, size_t size) { return fail_allocation() ? NULL : __real_calloc(count, size); }
void *__wrap_realloc(void *pointer, size_t size) { return fail_allocation() ? NULL : __real_realloc(pointer, size); }
void __wrap_free(void *pointer) { assert(!processing); __real_free(pointer); }
static xtc_result prepare(double rate, size_t frames, float parameter, void **state)
{
(void)rate; (void)frames;
assert(!processing);
*state = malloc(sizeof(float));
if (!*state) return XTC_ERR_OUT_OF_MEMORY;
*(float *)*state = parameter;
++prepared;
return XTC_OK;
}
static void reset(void *state) { (void)state; }
static xtc_result process(void *state, const xtc_native_block *block)
{
size_t i;
assert(processing);
for (i = 0; i < block->frames; ++i)
block->output[i] = block->input[i] * *(float *)state;
return XTC_OK;
}
static void destroy(void *state)
{
assert(!processing);
if (state) ++destroyed;
free(state);
}
static void render(xtc_realtime *engine, float expected)
{
float input[17], left[17], right[17];
size_t i;
for (i = 0; i < 17; ++i) input[i] = 1;
processing = 1;
assert(xtc_realtime_process_stereo(engine, input, NULL, NULL, NULL, NULL, left, right, 17) == XTC_OK);
processing = 0;
for (i = 0; i < 17; ++i) assert(left[i] == expected && right[i] == expected);
}
/* Maximum-sized reverse-insertion chain: long cycle rollback must preserve
* routing, and compiled snapshots survive destruction of the editable model. */
static void graph_limits(void)
{
xtc_graph *graph = NULL;
xtc_plan *plan = NULL;
size_t nodes[256], i;
float input[3] = {1, -0.5f, 0.25f}, output[3];
assert(xtc_graph_create(256, &graph) == XTC_OK);
for (i = 0; i < 256; ++i)
assert(xtc_graph_add_gain(graph, 1, &nodes[i]) == XTC_OK);
for (i = 255; i > 0; --i)
assert(xtc_graph_connect(graph, nodes[i], nodes[i - 1]) == XTC_OK);
assert(xtc_graph_connect(graph, nodes[0], nodes[255]) == XTC_ERR_GRAPH_CYCLE);
assert(xtc_graph_validate(graph) == XTC_OK);
assert(xtc_graph_compile(graph, 48000, 3, &plan) == XTC_OK);
xtc_graph_destroy(graph);
processing = 1;
assert(xtc_plan_process(plan, input, output, 3) == XTC_OK);
processing = 0;
for (i = 0; i < 3; ++i) assert(output[i] == input[i]);
xtc_plan_destroy(plan);
}
static void history_reservations(void)
{
xtc_project *project = NULL, *snapshot = NULL;
xtc_history *history = NULL;
uint64_t track, bus;
unsigned int map[3] = {1,74,11}, actual[3];
size_t undo_count, redo_count, tracks, clips;
int failure, succeeded = 0;
assert(xtc_project_create(&project) == XTC_OK);
assert(xtc_project_add_track(project,"History",&track) == XTC_OK);
assert(xtc_history_create(project,&history) == XTC_OK);
assert(xtc_project_set_mix(project,track,.5f,0,0,0) == XTC_OK);
assert(xtc_history_record(history,project) == XTC_OK);
assert(xtc_history_checkout(history,0,&snapshot) == XTC_OK && xtc_history_accept(history,0) == XTC_OK);
xtc_project_destroy(project); project = snapshot; snapshot = NULL;
for (failure = 0; failure < 12; ++failure) {
xtc_result r;
fail_after = failure;
r = xtc_history_reserve(history);
fail_after = -1;
assert(xtc_history_counts(history,&undo_count,&redo_count) == XTC_OK && undo_count == 0 && redo_count == 1);
if (r == XTC_OK) { succeeded = 1; break; }
assert(r == XTC_ERR_OUT_OF_MEMORY);
}
assert(succeeded);
fail_after = 0; // Fail every allocation: reserved record must still succeed.
assert(xtc_history_reserve(history) == XTC_OK);
assert(xtc_project_add_bus(project,"Bus",&bus) == XTC_OK);
assert(xtc_project_route_track(project,track,bus) == XTC_OK);
assert(xtc_project_set_control_map(project,0,map) == XTC_OK);
assert(xtc_history_record(history,project) == XTC_OK);
fail_after = -1;
assert(xtc_history_counts(history,&undo_count,&redo_count) == XTC_OK && undo_count == 1 && redo_count == 0);
assert(xtc_history_checkout(history,0,&snapshot) == XTC_OK);
assert(xtc_project_get_counts(snapshot,&tracks,&clips) == XTC_OK && tracks == 1 && clips == 0);
assert(xtc_project_get_control_map(snapshot,0,actual) == XTC_OK && actual[0] == 7);
xtc_project_destroy(snapshot); snapshot = NULL;
assert(xtc_history_accept(history,0) == XTC_OK && xtc_history_checkout(history,1,&snapshot) == XTC_OK);
assert(xtc_project_get_counts(snapshot,&tracks,&clips) == XTC_OK && tracks == 2);
assert(xtc_project_get_control_map(snapshot,0,actual) == XTC_OK && actual[0] == 1 && actual[1] == 74);
xtc_project_destroy(snapshot); xtc_project_destroy(project); xtc_history_destroy(history);
}
static void builtin_failures(void)
{
unsigned int id;
for(id=0;id<5;++id) {
int failure, succeeded=0; xtc_plugin_spec spec;
assert(xtc_plugin_probe(XTC_BUILTIN_PATH,id,48000,&spec)==XTC_OK);
if(id==4) {assert(getcwd(spec.sample_path,sizeof(spec.sample_path)));strcat(spec.sample_path,"/examples/projects/original-pluck.wav");}
for(failure=0;failure<16;++failure) {
xtc_plugin *plugin=NULL; xtc_result result; float in[256]={0},out[256];
fail_after=failure;result=xtc_plugin_create(&spec,48000,256,&plugin);fail_after=-1;
if(result!=XTC_OK) {assert(result==XTC_ERR_OUT_OF_MEMORY && !plugin);continue;}
processing=1;
if(spec.instrument) {xtc_midi_event event={0,XTC_MIDI_NOTE_ON,0,60,100};
assert(xtc_plugin_process_midi(plugin,&event,1,out,256)==XTC_OK);
} else assert(xtc_plugin_process(plugin,in,out,256)==XTC_OK);
processing=0;xtc_plugin_destroy(plugin);succeeded=1;break;
}
assert(succeeded);
}
}
int main(void)
{
builtin_failures();
graph_limits();
history_reservations();
xtc_native_descriptor descriptor = {XTC_NATIVE_ABI_VERSION, sizeof(descriptor),
"reliability.gain", XTC_NATIVE_EFFECT, prepare, reset, process, destroy};
xtc_graph *graph = NULL;
size_t node;
unsigned int iteration;
assert(xtc_graph_create(1, &graph) == XTC_OK);
assert(xtc_graph_add_native(graph, &descriptor, 2, &node) == XTC_OK);
for (iteration = 0; iteration < 100; ++iteration) {
xtc_realtime *engine = NULL;
int failure, succeeded = 0;
assert(xtc_realtime_create(48000, 17, &engine) == XTC_OK);
assert(xtc_realtime_submit(engine, graph) == XTC_OK);
render(engine, 2);
for (failure = 0; failure < 64; ++failure) {
xtc_result result;
fail_after = failure;
result = xtc_realtime_submit(engine, graph);
fail_after = -1;
if (result == XTC_OK) { succeeded = 1; break; }
assert(result == XTC_ERR_OUT_OF_MEMORY);
/* Failed replacement preserves the active DSP and revisions. */
{
xtc_realtime_status status;
assert(xtc_realtime_get_status(engine, &status) == XTC_OK);
assert(status.active_revision == 1 && status.submitted_revision == 1);
}
render(engine, 2);
}
assert(succeeded);
render(engine, 2); /* Leaves previous native instances retired. */
assert(destroyed + 4 == prepared);
xtc_realtime_collect(engine);
assert(destroyed + 2 == prepared);
assert(xtc_realtime_submit(engine, graph) == XTC_OK);
/* Destruction must also clean an unadopted pending plan. */
xtc_realtime_destroy(engine);
assert(prepared == destroyed);
}
xtc_graph_destroy(graph);
puts("Allocation failures, callback allocation guard, and native reclamation passed.");
return 0;
}