xtc/tests/unit/test_project.c

132 lines
7.3 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "xtc/xtc_audio.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
static void write_bytes(const char *path, const char *bytes, size_t count)
{
FILE *f = fopen(path, "wb"); assert(f);
assert(fwrite(bytes, 1, count, f) == count); assert(fclose(f) == 0);
}
static size_t read_bytes(const char *path, char *bytes)
{
FILE *f = fopen(path, "rb"); size_t n; assert(f);
n = fread(bytes, 1, 8191, f); assert(!ferror(f)); assert(fclose(f) == 0); bytes[n] = 0; return n;
}
int main(void)
{
xtc_project *p = NULL, *q = NULL;
uint64_t a, b, c, id, sample, tick;
size_t tracks, clips, nodes, edges;
xtc_clip_info info, before;
xtc_graph *graph = NULL;
size_t gain, output, discarded;
xtc_graph *edited = NULL;
xtc_graph_edit edit = {0};
xtc_plan *plan = NULL;
float input = 1.0f, rendered = 0.0f;
char dir[] = "/tmp/xtc-project-XXXXXX", path[256], other[256], bad[256];
char data[8192], again[8192], damaged[8192];
size_t length, i;
assert(mkdtemp(dir));
snprintf(path, sizeof(path), "%s/song.xtc", dir);
snprintf(other, sizeof(other), "%s/copy.xtc", dir);
snprintf(bad, sizeof(bad), "%s/bad.xtc", dir);
assert(xtc_project_create(&p) == XTC_OK);
assert(xtc_project_create(&q) == XTC_OK);
assert(xtc_project_add_track(p, "Drums ä", &a) == XTC_OK && a == 0);
assert(xtc_project_add_track(p, "Bass", &b) == XTC_OK && b == 1);
assert(xtc_project_add_track(p, "\300\200", &id) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_add_track(p, "Bad\n", &id) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_add_clip(p, a, "Verse", 3840, 3840, &c) == XTC_OK);
assert(xtc_project_edit_clip(p, c, b, 4800, 2880, 960) == XTC_OK);
assert(xtc_project_find_clip(p, c, &before) == XTC_OK);
assert(xtc_project_edit_clip(p, c, b, 0, 3840, 960) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_find_clip(p, c, &info) == XTC_OK && memcmp(&info, &before, sizeof(info)) == 0);
assert(xtc_project_add_clip(p, 99, "Bad", 0, 960, &id) == XTC_ERR_NOT_FOUND);
assert(xtc_project_add_clip(p, a, "Bad", XTC_PROJECT_MAX_TICK, 1, &id) == XTC_ERR_INVALID_ARGUMENT);
assert(xtc_project_tick_to_sample(p, 960, 48000, &sample) == XTC_OK && sample == 24000);
assert(xtc_project_sample_to_tick(p, sample, 48000, &tick) == XTC_OK && tick == 960);
assert(xtc_project_set_tempo(p, 20000) == XTC_OK);
assert(xtc_project_tick_to_sample(p, XTC_PROJECT_MAX_TICK, 768000, &sample) == XTC_OK);
assert(xtc_project_sample_to_tick(p, sample, 768000, &tick) == XTC_OK && tick == XTC_PROJECT_MAX_TICK);
assert(xtc_project_sample_to_tick(p, UINT64_MAX, 1, &tick) == XTC_ERR_CAPACITY);
assert(xtc_project_set_tempo(p, 120000) == XTC_OK);
assert(xtc_graph_create(64, &graph) == XTC_OK);
assert(xtc_graph_add_gain(graph, 0.25f, &gain) == XTC_OK);
assert(xtc_graph_add_output(graph, &output) == XTC_OK);
assert(xtc_graph_connect(graph, gain, output) == XTC_OK);
assert(xtc_graph_require_output_nodes(graph, 1) == XTC_OK);
assert(xtc_graph_add_gain(graph, 0.5f, &discarded) == XTC_OK);
edit.kind = XTC_EDIT_REMOVE_NODE; edit.data.node_id = discarded;
assert(xtc_graph_apply_edit(graph, &edit, &edited, NULL) == XTC_OK);
xtc_graph_destroy(graph); graph = edited; edited = NULL;
assert(xtc_project_set_graph(p, graph) == XTC_OK);
xtc_graph_destroy(graph);
assert(xtc_project_remove_track(p, a) == XTC_OK);
assert(xtc_project_save(p, path) == XTC_OK);
length = read_bytes(path, data);
assert(xtc_project_load(q, path) == XTC_OK);
assert(xtc_project_find_clip(q, c, &info) == XTC_OK && memcmp(&info, &before, sizeof(info)) == 0);
assert(xtc_graph_get_counts(xtc_project_graph(q), &nodes, &edges) == XTC_OK && nodes == 2 && edges == 1);
assert(xtc_graph_compile(xtc_project_graph(q), 48000.0, 1, &plan) == XTC_OK);
assert(xtc_plan_process(plan, &input, &rendered, 1) == XTC_OK && rendered == 0.25f);
xtc_plan_destroy(plan);
edit.kind = XTC_EDIT_ADD_NODE; edit.data.add.type = XTC_NODE_GAIN; edit.data.add.value = 1.0f;
assert(xtc_graph_apply_edit(xtc_project_graph(q), &edit, &edited, &gain) == XTC_OK && gain == 3);
xtc_graph_destroy(edited); edited = NULL;
assert(xtc_project_save(q, other) == XTC_OK);
assert(read_bytes(other, again) == length && memcmp(data, again, length) == 0);
/* Every truncation is rejected without altering the existing arrangement. */
for (i = 0; i < length; ++i) {
write_bytes(bad, data, i);
assert(xtc_project_load(q, bad) != XTC_OK);
assert(xtc_project_find_clip(q, c, &info) == XTC_OK && memcmp(&info, &before, sizeof(info)) == 0);
}
memcpy(damaged, data, length); damaged[12] = '9';
write_bytes(bad, damaged, length);
assert(xtc_project_load(q, bad) == XTC_ERR_UNSUPPORTED);
memcpy(damaged, data, length); damaged[length] = '\n';
write_bytes(bad, damaged, length + 1);
assert(xtc_project_load(q, bad) == XTC_ERR_FORMAT);
memcpy(damaged, data, length); damaged[0] = 0;
write_bytes(bad, damaged, length);
assert(xtc_project_load(q, bad) == XTC_ERR_FORMAT);
/* Mutate every byte: accepted variants must still be serializable. */
for (i = 0; i < length; ++i) {
memcpy(damaged, data, length); damaged[i] = '9';
write_bytes(bad, damaged, length);
if (xtc_project_load(q, bad) == XTC_OK) assert(xtc_project_save(q, other) == XTC_OK);
}
assert(xtc_project_load(q, path) == XTC_OK);
assert(xtc_project_add_track(q, "New", &id) == XTC_OK && id == 2);
assert(xtc_project_remove_track(q, b) == XTC_OK);
assert(xtc_project_get_counts(q, &tracks, &clips) == XTC_OK && tracks == 1 && clips == 0);
assert(xtc_project_save(p, dir) == XTC_ERR_IO); /* Rename onto directory fails, temp removed. */
assert(read_bytes(path, again) == length && memcmp(data, again, length) == 0);
for (i = 1; i < XTC_PROJECT_MAX_TRACKS; ++i) assert(xtc_project_add_track(q, "Track", &id) == XTC_OK);
assert(xtc_project_add_track(q, "Full", &id) == XTC_ERR_CAPACITY);
assert(xtc_project_get_track(q, 0, &(xtc_track_info){0}) == XTC_OK);
for (i = 0; i < XTC_PROJECT_MAX_CLIPS; ++i) {
char asset[XTC_ASSET_PATH_BYTES];
memset(asset, 'a', sizeof(asset)); asset[0] = '/'; asset[sizeof(asset) - 1] = 0;
assert(xtc_project_add_clip(q, 2, "Region", 0, 960, &id) == XTC_OK);
assert(xtc_project_set_clip_asset(q, id, asset) == XTC_OK);
}
assert(xtc_project_add_clip(q, 2, "Full", 0, 960, &id) == XTC_ERR_CAPACITY);
assert(xtc_project_save(q, other) == XTC_OK && xtc_project_load(p, other) == XTC_OK);
{
const char *v1 = "XTC_PROJECT 1\nAPP 0 1 0\nTEMPO 120000\nIDS 1 1\nGRAPH 1 0 0 0\nTRACKS 1\nTRACK 0 41\nCLIPS 1\nCLIP 0 0 0 960 0 960 42\nEND\n";
write_bytes(bad, v1, strlen(v1));
assert(xtc_project_load(p, bad) == XTC_OK);
assert(xtc_project_get_clip(p, 0, &info) == XTC_OK && info.asset_path[0] == 0);
}
xtc_project_destroy(p); xtc_project_destroy(q);
assert(unlink(path) == 0 && unlink(other) == 0 && unlink(bad) == 0);
assert(rmdir(dir) == 0); /* Also verifies no abandoned save temporaries. */
puts("Project model, persistence roundtrip, malformed input and cleanup passed.");
return 0;
}