Phase 5: Add project persistence and sequencer foundation
This commit is contained in:
parent
5c41d7f6da
commit
65640c4aad
28 changed files with 1307 additions and 84 deletions
|
|
@ -88,3 +88,17 @@ running. Visual inspection confirmed actual wires and active-state feedback,
|
|||
with zero xruns reported during that smoke run. Zoom, multiselection, editable
|
||||
parameters, project save/load, realtime meter visualization and hardware routing
|
||||
controls are outside this phase. Node layout currently lasts only for a session.
|
||||
|
||||
|
||||
## Phase 5 project and transport coverage
|
||||
|
||||
test_gui adds a track and region, injects FLTK mouse gestures for movement and
|
||||
left trimming, checks zoom, saves/reopens the arrangement, and rejects missing
|
||||
files and simulated backend failures without replacing the document.
|
||||
A sample-status update drives the cursor to an exact tick; a graph edit retains
|
||||
that position. test_gui_jack checks actual playhead advancement and a stable
|
||||
paused cursor across repeated starts/stops. No routes are created.
|
||||
test_project additionally covers serialization, exact graph samples/IDs,
|
||||
truncation/mutation input, capacity, save failures and cleanup (part of make test).
|
||||
The core/backend tests remain unsuppressed under ASan/UBSan; GUI checks retain
|
||||
the narrowly scoped fontconfig-only suppression described above.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "../../src/gui/main_window.hpp"
|
||||
#include "../../src/gui/graph_widget.hpp"
|
||||
#include "../../src/gui/sequencer_widget.hpp"
|
||||
#include <unistd.h>
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Box.H>
|
||||
#include <FL/Fl_Button.H>
|
||||
|
|
@ -49,6 +51,24 @@ bool contains(Fl_Group *group, const char *text)
|
|||
}
|
||||
return false;
|
||||
}
|
||||
SequencerWidget *timeline(Fl_Group *group)
|
||||
{
|
||||
for (int i = 0; i < group->children(); ++i) {
|
||||
if (auto *s = dynamic_cast<SequencerWidget *>(group->child(i))) return s;
|
||||
if (auto *g = dynamic_cast<Fl_Group *>(group->child(i))) if (auto *s = timeline(g)) return s;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void gesture(SequencerWidget *s, int left, int top, int dx, int dy)
|
||||
{
|
||||
int old_x = Fl::e_x, old_y = Fl::e_y, old_key = Fl::e_keysym;
|
||||
Fl::e_x = s->x() + left; Fl::e_y = s->y() + top; Fl::e_keysym = FL_Button + FL_LEFT_MOUSE;
|
||||
CHECK(s->handle(FL_PUSH) == 1);
|
||||
Fl::e_x += dx; Fl::e_y += dy;
|
||||
CHECK(s->handle(FL_DRAG) == 1);
|
||||
CHECK(s->handle(FL_RELEASE) == 1);
|
||||
Fl::e_x = old_x; Fl::e_y = old_y; Fl::e_keysym = old_key;
|
||||
}
|
||||
void press(Fl_Widget *widget)
|
||||
{
|
||||
CHECK(widget != nullptr);
|
||||
|
|
@ -92,6 +112,8 @@ xtc_result xtc_backend_get_status(const xtc_backend *backend, xtc_backend_status
|
|||
status->running = handle.running && !status->server_lost;
|
||||
return status_result;
|
||||
}
|
||||
xtc_result xtc_backend_seek(xtc_backend *, uint64_t sample)
|
||||
{ engine_status.sample_position = sample; return handle.running ? XTC_ERR_INVALID_STATE : XTC_OK; }
|
||||
xtc_result xtc_backend_submit(xtc_backend *backend, const xtc_graph *graph)
|
||||
{
|
||||
CHECK(backend == &handle && graph != nullptr);
|
||||
|
|
@ -111,7 +133,7 @@ int main(int argc, char **argv)
|
|||
{
|
||||
reset();
|
||||
{
|
||||
MainWindow window;
|
||||
MainWindow window(false);
|
||||
window.show(argc, argv);
|
||||
auto *play = find(&window, "Play");
|
||||
auto *stop = find(&window, "Stop");
|
||||
|
|
@ -216,6 +238,37 @@ int main(int argc, char **argv)
|
|||
for (size_t id : {0U, 2U, 3U, 5U}) { CHECK(canvas->select_node(id)); CHECK(canvas->delete_selected() == XTC_OK); }
|
||||
CHECK(xtc_graph_get_counts(canvas->graph(), &nodes, &edges) == XTC_OK && nodes == 0U && edges == 0U);
|
||||
CHECK(xtc_plan_process(handle.plan, nullptr, rendered, 32U) == XTC_OK && rendered[1] == 0.0f);
|
||||
auto *sequence = timeline(&window);
|
||||
CHECK(sequence != nullptr);
|
||||
char directory[] = "/tmp/xtc-gui-XXXXXX"; CHECK(mkdtemp(directory) != nullptr);
|
||||
std::string filename = std::string(directory) + "/song.xtc";
|
||||
press(find(&window, "Add track")); press(find(&window, "Add region"));
|
||||
size_t track_count, clip_count;
|
||||
CHECK(xtc_project_get_counts(sequence->project(), &track_count, &clip_count) == XTC_OK && track_count == 1 && clip_count == 1);
|
||||
xtc_clip_info region{};
|
||||
CHECK(xtc_project_get_clip(sequence->project(), 0, ®ion) == XTC_OK);
|
||||
gesture(sequence, 150, 50, 144, 0); // Move by three beats.
|
||||
gesture(sequence, 265, 50, 48, 0); // Trim left edge by one beat.
|
||||
CHECK(xtc_project_get_clip(sequence->project(), 0, ®ion) == XTC_OK &&
|
||||
region.start_tick == 3840 && region.length_tick == 2880 && region.offset_tick == 960);
|
||||
int width = sequence->w();
|
||||
press(find(&window, "Zoom +")); CHECK(sequence->w() > width);
|
||||
press(find(&window, "Zoom -")); CHECK(sequence->w() == width);
|
||||
CHECK(window.save_project(filename.c_str()) == XTC_OK);
|
||||
const xtc_project *retained = sequence->project();
|
||||
CHECK(window.open_project("/missing/xtc-project") == XTC_ERR_IO && sequence->project() == retained);
|
||||
submit_result = XTC_ERR_CAPACITY;
|
||||
CHECK(window.open_project(filename.c_str()) == XTC_ERR_CAPACITY && sequence->project() == retained);
|
||||
submit_result = XTC_OK;
|
||||
press(find(&window, "Delete region/track"));
|
||||
CHECK(window.open_project(filename.c_str()) == XTC_OK);
|
||||
CHECK(xtc_project_get_clip(sequence->project(), 0, ®ion) == XTC_OK && region.start_tick == 3840 && region.offset_tick == 960);
|
||||
engine_status.sample_position = 48000; pump();
|
||||
CHECK(sequence->position() == 1920);
|
||||
CHECK(canvas->add_node(XTC_NODE_GAIN) == XTC_OK);
|
||||
CHECK(sequence->position() == 1920); // Graph edits preserve the transport.
|
||||
engine_status.sample_position = 0;
|
||||
CHECK(unlink(filename.c_str()) == 0 && rmdir(directory) == 0);
|
||||
engine_status.xruns = 3U;
|
||||
engine_status.processed_blocks = 123U;
|
||||
pump();
|
||||
|
|
@ -250,7 +303,7 @@ int main(int argc, char **argv)
|
|||
for (xtc_result failure : {XTC_ERR_IO, XTC_ERR_UNSUPPORTED}) {
|
||||
reset();
|
||||
open_result = failure;
|
||||
MainWindow window;
|
||||
MainWindow window(false);
|
||||
window.show(argc, argv);
|
||||
CHECK(contains(&window, "Open JACK failed") && !find(&window, "Play")->active());
|
||||
open_result = XTC_OK;
|
||||
|
|
@ -264,14 +317,14 @@ int main(int argc, char **argv)
|
|||
partial_open = true;
|
||||
open_result = XTC_ERR_IO;
|
||||
{
|
||||
MainWindow window;
|
||||
MainWindow window(false);
|
||||
CHECK(alive == 1U && !find(&window, "Play")->active());
|
||||
window.do_callback(); // Partially initialized handles are closed only.
|
||||
CHECK(alive == 0U);
|
||||
}
|
||||
reset();
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
MainWindow window;
|
||||
MainWindow window(false);
|
||||
press(find(&window, "Play"));
|
||||
window.do_callback();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "../../src/gui/main_window.hpp"
|
||||
#include "../../src/gui/graph_widget.hpp"
|
||||
#include "../../src/gui/sequencer_widget.hpp"
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Group.H>
|
||||
#include <chrono>
|
||||
|
|
@ -24,6 +25,14 @@ GraphWidget *canvas_in(Fl_Group &group)
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
SequencerWidget *timeline_in(Fl_Group &group)
|
||||
{
|
||||
for (int i = 0; i < group.children(); ++i) {
|
||||
if (auto *s = dynamic_cast<SequencerWidget *>(group.child(i))) return s;
|
||||
if (auto *g = dynamic_cast<Fl_Group *>(group.child(i))) if (auto *s = timeline_in(*g)) return s;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
void pump()
|
||||
{
|
||||
const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(400);
|
||||
|
|
@ -35,7 +44,7 @@ int main(int argc, char **argv)
|
|||
{
|
||||
// Real JACK backend and real FLTK event loop. No routes, no playback data.
|
||||
for (int iteration = 0; iteration < 3; ++iteration) {
|
||||
MainWindow window;
|
||||
MainWindow window(false);
|
||||
window.show(argc, argv);
|
||||
CHECK(find(window, "ready / stopped") != nullptr);
|
||||
auto *play = find(window, "Play");
|
||||
|
|
@ -44,16 +53,19 @@ int main(int argc, char **argv)
|
|||
CHECK(play != nullptr && stop != nullptr && reconnect != nullptr);
|
||||
auto *canvas = canvas_in(window);
|
||||
CHECK(canvas != nullptr);
|
||||
auto *timeline = timeline_in(window); CHECK(timeline != nullptr);
|
||||
CHECK(canvas->add_node(XTC_NODE_OSCILLATOR) == XTC_OK);
|
||||
CHECK(canvas->add_node(XTC_NODE_GAIN) == XTC_OK);
|
||||
CHECK(canvas->add_node(XTC_NODE_OUTPUT) == XTC_OK);
|
||||
CHECK(canvas->connect_ports({0U, 1U, 1U, 0U}) == XTC_OK);
|
||||
CHECK(canvas->connect_ports({1U, 1U, 2U, 0U}) == XTC_OK);
|
||||
for (int restart = 0; restart < 3; ++restart) {
|
||||
uint64_t previous = timeline->position();
|
||||
CHECK(play->active());
|
||||
play->do_callback();
|
||||
pump();
|
||||
CHECK(find(window, "running — modular graph") != nullptr);
|
||||
CHECK(timeline->position() > previous);
|
||||
CHECK(find(window, "Graph: active in engine") != nullptr);
|
||||
const xtc_graph *unchanged = canvas->graph();
|
||||
CHECK(canvas->connect_ports({1U, 1U, 1U, 0U}) == XTC_ERR_GRAPH_CYCLE);
|
||||
|
|
@ -71,6 +83,8 @@ int main(int argc, char **argv)
|
|||
CHECK(rate > 0U && frames > 0U && blocks > 0U);
|
||||
stop->do_callback();
|
||||
CHECK(find(window, "ready / stopped") != nullptr);
|
||||
uint64_t paused = timeline->position(); pump();
|
||||
CHECK(timeline->position() == paused);
|
||||
}
|
||||
reconnect->do_callback();
|
||||
CHECK(find(window, "ready / stopped") != nullptr);
|
||||
|
|
|
|||
|
|
@ -121,25 +121,29 @@ int main(void)
|
|||
activate_fail = 1;
|
||||
CHECK(xtc_backend_start(backend) == XTC_ERR_IO);
|
||||
activate_fail = 0;
|
||||
CHECK(xtc_backend_seek(backend, 1000U) == XTC_OK);
|
||||
CHECK(xtc_backend_submit(backend, graph) == XTC_OK);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.sample_position == 1000U);
|
||||
CHECK(xtc_backend_start(backend) == XTC_OK);
|
||||
CHECK(xtc_backend_seek(backend, 0) == XTC_ERR_INVALID_STATE);
|
||||
CHECK(xtc_backend_start(backend) == XTC_ERR_INVALID_STATE);
|
||||
CHECK(xtc_backend_connect(backend, XTC_PORT_OUTPUT, "fake:in") == XTC_OK);
|
||||
CHECK(xtc_backend_connect(backend, XTC_PORT_INPUT, "missing:out") == XTC_ERR_IO);
|
||||
CHECK(xtc_backend_disconnect(backend, XTC_PORT_OUTPUT, "fake:in") == XTC_OK);
|
||||
for (i = 0U; i < 128U; ++i) input[i] = 1.0f;
|
||||
CHECK(process_cb(32U, context) == 0 && output[0] == 0.5f);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.active_revision == status.submitted_revision);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.active_revision == status.submitted_revision && status.sample_position == 1032U);
|
||||
CHECK(xtc_backend_submit(backend, invalid_graph) == XTC_ERR_INVALID_ARGUMENT);
|
||||
CHECK(xtc_backend_submit(backend, graph) == XTC_OK);
|
||||
CHECK(xtc_backend_submit(backend, graph) == XTC_ERR_CAPACITY);
|
||||
CHECK(process_cb(32U, context) == 0 && output[0] == 0.5f);
|
||||
CHECK(size_cb(128U, context) == 0);
|
||||
CHECK(process_cb(128U, context) == 0 && output[127] == 0.0f);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.process_result == XTC_ERR_CAPACITY);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.process_result == XTC_ERR_CAPACITY && status.sample_position == 1064U);
|
||||
CHECK(size_cb(16U, context) == 0);
|
||||
CHECK(process_cb(16U, context) == 0 && output[15] == 0.5f);
|
||||
CHECK(xrun_cb(context) == 0);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.xruns == 1U && status.buffer_frames == 16U);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.xruns == 1U && status.buffer_frames == 16U && status.sample_position == 1080U);
|
||||
null_buffer = 1;
|
||||
CHECK(process_cb(16U, context) == 0);
|
||||
null_buffer = 0;
|
||||
|
|
@ -165,7 +169,12 @@ int main(void)
|
|||
CHECK(xtc_backend_start(backend) == XTC_ERR_INVALID_STATE);
|
||||
} else {
|
||||
CHECK(xtc_backend_stop(backend) == XTC_OK);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.sample_position == 32U);
|
||||
CHECK(xtc_backend_seek(backend, 24000U) == XTC_OK);
|
||||
CHECK(xtc_backend_submit(backend, graph) == XTC_OK);
|
||||
CHECK(xtc_backend_start(backend) == XTC_OK);
|
||||
CHECK(process_cb(32U, context) == 0);
|
||||
CHECK(xtc_backend_get_status(backend, &status) == XTC_OK && status.sample_position == 24032U);
|
||||
}
|
||||
CHECK(xtc_backend_submit(backend, graph) == (iteration % 2 == 0 ? XTC_ERR_INVALID_STATE : XTC_OK));
|
||||
close_fail = 1;
|
||||
|
|
|
|||
121
tests/unit/test_project.c
Normal file
121
tests/unit/test_project.c
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_project.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) assert(xtc_project_add_clip(q, 2, "Region", 0, 960, &id) == 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);
|
||||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue