xtc/tests/integration/test_reliability.c

392 lines
20 KiB
C

#define _XOPEN_SOURCE 700
/* 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 "xtc/xtc_editing.h"
#include "xtc/xtc_audio.h"
#include "xtc/xtc_sample.h"
#include <dirent.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<7;++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 if(id>=5) {float right[256];assert(xtc_plugin_process_stereo_at(plugin,in,in,out,right,256,0)==XTC_OK);}
else assert(xtc_plugin_process(plugin,in,out,256)==XTC_OK);
xtc_plugin_reset_history(plugin);
processing=0;xtc_plugin_destroy(plugin);succeeded=1;break;
}
assert(succeeded);
}
}
static void modern_edit_failures(void)
{
xtc_project *p=NULL;xtc_clipboard *board=NULL;xtc_mix_snapshot *mix=NULL;
uint64_t track,clip,note;int operation,failure;
xtc_midi_transform edit={0};edit.grid=240;edit.swing=50;edit.strength=100;
assert(xtc_project_create(&p)==XTC_OK);
assert(xtc_project_add_track(p,"Track",&track)==XTC_OK);
assert(xtc_project_add_midi_clip(p,track,960,1920,&clip)==XTC_OK);
assert(xtc_project_add_note(p,clip,230,120,61,100,0,&note)==XTC_OK);
assert(xtc_clipboard_create(p,&clip,1,&board)==XTC_OK);
assert(xtc_mix_snapshot_create(p,&mix)==XTC_OK);
for(operation=0;operation<7;++operation) {
int succeeded=0;
for(failure=0;failure<32;++failure) {
xtc_project *copy=NULL;xtc_clipboard *created=NULL;xtc_mix_snapshot *captured=NULL;
uint64_t output=999;xtc_clip_info c;xtc_note_info n;size_t tracks,clips;xtc_result r;
assert(xtc_project_clone(p,&copy)==XTC_OK);
fail_after=failure;
switch(operation) {
case 0:r=xtc_project_transform_clips(copy,&clip,1,XTC_CLIPS_DUPLICATE,1920,&output);break;
case 1:r=xtc_clipboard_create(copy,&clip,1,&created);break;
case 2:r=xtc_clipboard_paste(board,copy,track,3840,&output);break;
case 3:r=xtc_project_repeat_clips(copy,&clip,1,2);break;
case 4:r=xtc_project_transform_midi(copy,&clip,1,&edit);break;
case 5:r=xtc_mix_snapshot_create(copy,&captured);break;
default:r=xtc_mix_snapshot_recall(mix,copy);break;
}
fail_after=-1;
if(r==XTC_OK)succeeded=1;
else {
assert(r==XTC_ERR_OUT_OF_MEMORY && output==999 && !created && !captured);
assert(xtc_project_get_counts(copy,&tracks,&clips)==XTC_OK && clips==1);
assert(xtc_project_find_clip(copy,clip,&c)==XTC_OK && c.start_tick==960);
assert(xtc_project_get_note(copy,0,&n)==XTC_OK && n.start_tick==230 && n.id==note);
{uint64_t probe;assert(xtc_project_duplicate_clip(copy,clip,track,3840,&probe)==XTC_OK && probe==clip+1);}
}
xtc_clipboard_destroy(created);xtc_mix_snapshot_destroy(captured);xtc_project_destroy(copy);
if(succeeded)break;
}
assert(succeeded);
}
xtc_clipboard_destroy(board);xtc_mix_snapshot_destroy(mix);xtc_project_destroy(p);
}
static void composition_failures(void)
{
xtc_project *p=NULL;uint64_t t,ids[2],note,audio;char sample[4096],root[]="/tmp/xtc-compose-fail-XXXXXX";
int operation,failure;xtc_midi_transform human={0};int intervals[2]={4,7};
assert(mkdtemp(root));assert(getcwd(sample,sizeof(sample)));assert(strlen(sample)+40<sizeof(sample));strcat(sample,"/examples/projects/original-pluck.wav");
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Track",&t)==XTC_OK);
assert(xtc_project_add_midi_clip(p,t,0,1920,&ids[0])==XTC_OK && xtc_project_add_midi_clip(p,t,3840,1920,&ids[1])==XTC_OK);
assert(xtc_project_add_note(p,ids[0],480,240,60,100,0,&note)==XTC_OK && xtc_project_add_note(p,ids[1],480,240,64,100,0,&note)==XTC_OK);
assert(xtc_project_import_audio(p,t,sample,7680,&audio)==XTC_OK);
fail_after=0;assert(xtc_project_set_marker(p,3840,"Section")==XTC_OK);fail_after=-1;
human.type=XTC_MIDI_TRANSFORM_HUMANIZE;human.seed=42;human.timing_variation=12;human.velocity_variation=8;
for(operation=0;operation<9;++operation) {
int succeeded=0;
for(failure=0;failure<128;++failure) {
xtc_project *copy=NULL;xtc_clip_info c;uint64_t output=999;xtc_result r;size_t tracks,clips,notes;
char dest[512],file[512];snprintf(dest,sizeof(dest),"%s/copy-%d-%d",root,operation,failure);
assert(xtc_project_clone(p,&copy)==XTC_OK);fail_after=failure;
switch(operation) {
case 0:r=xtc_project_ripple_time(copy,1920,960,0);break;
case 1:r=xtc_project_duplicate_track(copy,t,"Copy",&output);break;
case 2:r=xtc_project_split_midi(copy,ids[0],960,&output);break;
case 3:r=xtc_project_merge_midi(copy,ids,2,&output);break;
case 4:r=xtc_project_transform_midi(copy,ids,2,&human);break;
case 5:r=xtc_project_chordify(copy,ids,2,intervals,2);break;
case 6:r=xtc_project_arpeggiate(copy,ids,2,120,50,0);break;
case 7:r=xtc_project_normalize_audio(copy,&audio,1,.25f);break;
default:r=xtc_project_collect(copy,dest);break;
}
fail_after=-1;
if(r==XTC_OK)succeeded=1;
else {
assert((r==XTC_ERR_OUT_OF_MEMORY || (operation==8 && r==XTC_ERR_IO)) && output==999);
assert(xtc_project_get_counts(copy,&tracks,&clips)==XTC_OK && tracks==1 && clips==3);
assert(xtc_project_note_count(copy,&notes)==XTC_OK && notes==2);
assert(xtc_project_find_clip(copy,ids[1],&c)==XTC_OK && c.start_tick==3840);
assert(access(dest,F_OK)!=0);
{uint64_t probe;assert(xtc_project_duplicate_clip(copy,ids[0],t,9600,&probe)==XTC_OK && probe==3);}
}
if(operation==8 && succeeded){snprintf(file,sizeof(file),"%s/copy-%d-%d/project.xtc",root,operation,failure);assert(unlink(file)==0);snprintf(file,sizeof(file),"%s/copy-%d-%d/media-000.wav",root,operation,failure);assert(unlink(file)==0 && rmdir(dest)==0);}
xtc_project_destroy(copy);if(succeeded)break;
}
assert(succeeded);
}
xtc_project_destroy(p);assert(rmdir(root)==0);
}
static void sample_failures(void)
{
xtc_sample *source=NULL;xtc_project *p=NULL;char path[4096],root[]="/tmp/xtc-sample-fail-XXXXXX",dest[256];uint64_t t,id;int op,failure;
assert(getcwd(path,sizeof(path)));assert(strlen(path)+40<sizeof(path));strcat(path,"/examples/projects/original-pluck.wav");assert(mkdtemp(root));
snprintf(dest,sizeof(dest),"%s/copy.wav",root);
assert(xtc_sample_load(path,&source)==XTC_OK && xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Audio",&t)==XTC_OK && xtc_project_import_audio(p,t,path,0,&id)==XTC_OK);
for(op=0;op<12;++op){int succeeded=0;
for(failure=0;failure<128;++failure){xtc_sample *output=NULL;xtc_project *copy=NULL;xtc_result r;size_t made=999,cuts[]={2400,4800};
assert(xtc_project_clone(p,&copy)==XTC_OK);fail_after=failure;
if(op<9)r=xtc_sample_process(source,100,1000,(xtc_sample_operation)op,op==XTC_SAMPLE_GAIN ? 2 : .5,&output);
else if(op==9)r=xtc_sample_load(path,&output);
else if(op==10)r=xtc_sample_write(source,100,1000,dest);
else r=xtc_project_slice_source_frames(copy,id,48000,cuts,2,&made);
fail_after=-1;
if(r==XTC_OK)succeeded=1;
else {assert(r==XTC_ERR_OUT_OF_MEMORY||r==XTC_ERR_IO);assert(output==NULL && made==999 && access(dest,F_OK)!=0);
{size_t tracks,clips;uint64_t probe;assert(xtc_project_get_counts(copy,&tracks,&clips)==XTC_OK && clips==1);assert(xtc_project_duplicate_clip(copy,id,t,10000,&probe)==XTC_OK && probe==1);}}
if(op==10&&succeeded)assert(unlink(dest)==0);
{DIR *d=opendir(root);struct dirent *entry;assert(d);while((entry=readdir(d)))assert(!strcmp(entry->d_name,".")||!strcmp(entry->d_name,".."));assert(closedir(d)==0);}
xtc_sample_destroy(output);xtc_project_destroy(copy);if(succeeded)break;
}assert(succeeded);
}
xtc_sample_destroy(source);xtc_project_destroy(p);assert(rmdir(root)==0);
}
static void strum_failures(void)
{
xtc_project *p=NULL;uint64_t t,c,n;int succeeded=0;
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Chord",&t)==XTC_OK);
assert(xtc_project_add_midi_clip(p,t,0,1920,&c)==XTC_OK);
assert(xtc_project_add_note(p,c,0,200,60,100,0,&n)==XTC_OK && xtc_project_add_note(p,c,0,200,67,100,0,&n)==XTC_OK);
for(int failure=0;failure<128;++failure){
xtc_project *copy=NULL;xtc_note_info note;xtc_result r;
assert(xtc_project_clone(p,&copy)==XTC_OK);fail_after=failure;
r=xtc_project_strum(copy,&c,1,30,0);fail_after=-1;
if(r==XTC_OK)succeeded=1;
else {assert(r==XTC_ERR_OUT_OF_MEMORY);assert(xtc_project_get_note(copy,1,&note)==XTC_OK && note.id==n && note.start_tick==0 && note.length_tick==200);}
xtc_project_destroy(copy);if(succeeded)break;
}
assert(succeeded);xtc_project_destroy(p);
}
static void track_insert_failures(void)
{
xtc_project *p=NULL;xtc_track_chain c={0};uint64_t t,clip;int mode,failure;
assert(xtc_project_create(&p)==XTC_OK && xtc_project_add_track(p,"Track",&t)==XTC_OK);
assert(xtc_plugin_probe_format(XTC_PLUGIN_BUILTIN,XTC_BUILTIN_PATH,2,48000,&c.slots[0].plugin)==XTC_OK);
c.count=1;c.output_gain=1;c.slots[0].wet=.5f;c.slots[0].enabled=1;
assert(xtc_project_set_chain(p,t,&c)==XTC_OK);
for(mode=0;mode<2;++mode) {
int succeeded=0;
if(mode)assert(xtc_project_add_midi_clip(p,t,0,960,&clip)==XTC_OK);
for(failure=0;failure<128;++failure) {
xtc_audio_plan *a=NULL;xtc_midi_plan *m=NULL;float out[512];xtc_result r;
fail_after=failure;
r=mode ? xtc_midi_plan_create(p,48000,&m) : xtc_audio_plan_create(p,48000,&a);
fail_after=-1;
if(r==XTC_OK) {
processing=1;
if(mode)assert(xtc_midi_plan_render(m,0,NULL,0,out,512)==XTC_OK);
else assert(xtc_audio_plan_render(a,0,out,512)==XTC_OK);
processing=0;succeeded=1;
}else assert(r==XTC_ERR_OUT_OF_MEMORY && !a && !m);
xtc_audio_plan_destroy(a);xtc_midi_plan_destroy(m);
if(succeeded)break;
}
assert(succeeded);
}
xtc_project_destroy(p);
}
int main(void)
{
strum_failures();
track_insert_failures();
sample_failures();
composition_failures();
modern_edit_failures();
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;
}