Phase 13: protect recordings and recover retained audio after write failures
This commit is contained in:
parent
05140c2e93
commit
01d9db90fd
15 changed files with 308 additions and 30 deletions
108
tests/integration/test_recording.c
Normal file
108
tests/integration/test_recording.c
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_audio.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
static void file(const char *path, const char *text)
|
||||
{ FILE *f = fopen(path, "wb"); assert(f); assert(fputs(text, f) >= 0 && fclose(f) == 0); }
|
||||
int main(void)
|
||||
{
|
||||
char dir[] = "/tmp/xtc-recording-XXXXXX", take[256], recovered[256], retained[4096], song[256];
|
||||
xtc_recorder *r = NULL;
|
||||
float samples[4096];
|
||||
struct stat info;
|
||||
uint64_t position;
|
||||
size_t i;
|
||||
assert(mkdtemp(dir));
|
||||
snprintf(take, sizeof(take), "%s/take.wav", dir);
|
||||
snprintf(recovered, sizeof(recovered), "%s/recovered.wav", dir);
|
||||
snprintf(song, sizeof(song), "%s/song.xtc", dir);
|
||||
for (i = 0; i < 4096; ++i) samples[i] = 0.25f;
|
||||
file(take, "existing");
|
||||
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_ERR_IO && !r);
|
||||
assert(unlink(take) == 0 && symlink("missing", take) == 0);
|
||||
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_ERR_IO && !r);
|
||||
assert(unlink(take) == 0);
|
||||
assert(xtc_recorder_create("/missing/xtc/take.wav", 8000, 4096, &r) == XTC_ERR_IO && !r);
|
||||
/* Destination appears after creation: no-clobber publication must preserve it. */
|
||||
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||||
xtc_recorder_push(r, samples, 4096);
|
||||
file(take, "racing writer");
|
||||
assert(xtc_recorder_finish(r) == XTC_ERR_IO);
|
||||
snprintf(retained, sizeof(retained), "%s", xtc_recorder_recovery_path(r));
|
||||
xtc_recorder_destroy(r);
|
||||
assert(stat(take, &info) == 0 && info.st_size == 13);
|
||||
assert(xtc_recorder_recover(retained, take) == XTC_ERR_IO);
|
||||
assert(xtc_recorder_recover(retained, recovered) == XTC_OK);
|
||||
assert(stat(recovered, &info) == 0 && info.st_size == 44 + 4096 * 4);
|
||||
assert(unlink(retained) == 0 && unlink(recovered) == 0 && unlink(take) == 0);
|
||||
/* Last-resort destruction drains accepted samples after producer quiescence. */
|
||||
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||||
xtc_recorder_push(r, samples, 4096); xtc_recorder_destroy(r);
|
||||
assert(stat(take, &info) == 0 && info.st_size == 44 + 4096 * 4);
|
||||
assert(unlink(take) == 0);
|
||||
/* Thirty minutes of virtual 8 kHz audio, repeated ring wrap/drain, no sleep. */
|
||||
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||||
for (position = 0; position < 14400000;) {
|
||||
size_t count = 14400000 - position > 4096 ? 4096 : (size_t)(14400000 - position);
|
||||
xtc_recorder_push(r, samples, count);
|
||||
assert(xtc_recorder_drain(r) == XTC_OK);
|
||||
position += count;
|
||||
}
|
||||
assert(xtc_recorder_finish(r) == XTC_OK); xtc_recorder_destroy(r);
|
||||
assert(stat(take, &info) == 0 && info.st_size == 57600044);
|
||||
assert(unlink(take) == 0);
|
||||
/* Child-local disk-size failure preserves a recoverable on-disk prefix. */
|
||||
{
|
||||
int pipes[2], status;
|
||||
pid_t child;
|
||||
assert(pipe(pipes) == 0);
|
||||
child = fork(); assert(child >= 0);
|
||||
if (!child) {
|
||||
struct rlimit limit = {128, 128};
|
||||
close(pipes[0]); signal(SIGXFSZ, SIG_IGN);
|
||||
assert(setrlimit(RLIMIT_FSIZE, &limit) == 0);
|
||||
assert(xtc_recorder_create(take, 8000, 4096, &r) == XTC_OK);
|
||||
xtc_recorder_push(r, samples, 4096);
|
||||
(void)xtc_recorder_drain(r);
|
||||
assert(xtc_recorder_finish(r) == XTC_ERR_IO);
|
||||
snprintf(retained, sizeof(retained), "%s", xtc_recorder_recovery_path(r));
|
||||
xtc_recorder_destroy(r);
|
||||
assert(write(pipes[1], retained, sizeof(retained)) == (ssize_t)sizeof(retained));
|
||||
_exit(0);
|
||||
}
|
||||
close(pipes[1]);
|
||||
assert(read(pipes[0], retained, sizeof(retained)) == (ssize_t)sizeof(retained)); close(pipes[0]);
|
||||
assert(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
assert(access(take, F_OK) != 0 && stat(retained, &info) == 0 && info.st_size == 128);
|
||||
assert(xtc_recorder_recover(retained, recovered) == XTC_OK);
|
||||
assert(stat(recovered, &info) == 0 && info.st_size == 128);
|
||||
assert(unlink(retained) == 0 && unlink(recovered) == 0);
|
||||
}
|
||||
/* Incomplete and missing references survive save/reopen and are reported. */
|
||||
{
|
||||
xtc_project *p = NULL;
|
||||
xtc_audio_plan *plan = NULL;
|
||||
uint64_t track, clip;
|
||||
const char *issue;
|
||||
assert(xtc_project_create(&p) == XTC_OK && xtc_project_add_track(p, "Incomplete", &track) == XTC_OK);
|
||||
assert(xtc_project_add_clip(p, track, "Broken take", 0, 960, &clip) == XTC_OK);
|
||||
assert(xtc_project_set_clip_asset(p, clip, take) == XTC_OK);
|
||||
file(take, "RIFF incomplete");
|
||||
assert(xtc_project_save(p, song) == XTC_OK && xtc_project_load(p, song) == XTC_OK);
|
||||
assert(xtc_audio_plan_create(p, 8000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, &issue) == 1);
|
||||
assert(!strcmp(issue, take)); xtc_audio_plan_destroy(plan);
|
||||
assert(unlink(take) == 0);
|
||||
assert(xtc_audio_plan_create(p, 8000, &plan) == XTC_OK && xtc_audio_plan_issues(plan, &issue) == 1);
|
||||
xtc_audio_plan_destroy(plan); xtc_project_destroy(p);
|
||||
assert(unlink(song) == 0);
|
||||
}
|
||||
assert(rmdir(dir) == 0);
|
||||
puts("30-minute virtual recording, no-clobber races, disk failure recovery and incomplete/missing media passed.");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue