Phase 28: add project diagnostics, dependency inspection and durability regressions
This commit is contained in:
parent
c4daef7c42
commit
ca6c869b0b
16 changed files with 495 additions and 42 deletions
13
Makefile
13
Makefile
|
|
@ -417,3 +417,16 @@ test-parameters: $(BUILD_DIR)/test_parameters
|
|||
./$(BUILD_DIR)/test_parameters
|
||||
test: test-parameters
|
||||
-include $(BUILD_DIR)/tests/unit/test_parameters.d
|
||||
|
||||
$(BUILD_DIR)/test_project_diagnostics: $(BUILD_DIR)/tests/unit/test_project_diagnostics.o $(BUILD_DIR)/libxtc.a
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lm -ldl -Wl,--wrap=fflush,--wrap=fclose,--wrap=fsync,--wrap=rename
|
||||
.PHONY: test-project-diagnostics
|
||||
test-project-diagnostics: $(BUILD_DIR)/test_project_diagnostics
|
||||
./$(BUILD_DIR)/test_project_diagnostics
|
||||
test: test-project-diagnostics
|
||||
-include $(BUILD_DIR)/tests/unit/test_project_diagnostics.d
|
||||
|
||||
.PHONY: test-project-inspection
|
||||
test-project-inspection: $(BUILD_DIR)/xtc-check
|
||||
$(PYTHON) tests/integration/test_project_inspection.py $(BUILD_DIR)/xtc-check
|
||||
test: test-project-inspection
|
||||
|
|
|
|||
|
|
@ -153,3 +153,11 @@ per-channel live MIDI CC bindings. Project v10 reads v1–v9. See
|
|||
Phase 27 reserves undo storage before edits/publication, groups completed takes,
|
||||
preserves surviving editor selections and improves context menus/focus feedback.
|
||||
See [EDITING.md](docs/EDITING.md) for the workflow audit and failure boundaries.
|
||||
|
||||
Project troubleshooting: `xtc-check --validate PROJECT.xtc` reports metadata
|
||||
errors with the expected record and syntax line. `xtc-check --inspect PROJECT.xtc`
|
||||
lists resolved media/module paths and plugin identities without executing plugins
|
||||
or starting JACK. Exit codes: 0 = valid metadata and available files, 2 = missing
|
||||
or unreadable references, 1 = metadata/I/O error. File availability does not
|
||||
certify WAV contents or plugin compatibility. See [PROJECT_FORMAT.md](docs/PROJECT_FORMAT.md)
|
||||
for the compatibility and relocation policy.
|
||||
|
|
|
|||
|
|
@ -65,3 +65,11 @@ PERFORMANCE.md, PLUGIN_HOSTING.md and RECOVERY.md for tested contracts/evidence.
|
|||
or cancellable progress. Surviving selections are retained across undo but
|
||||
historical selection/layout is not stored. Native context-menu/focus manual
|
||||
qualification and broad accessibility testing remain pending. See EDITING.md.
|
||||
|
||||
Phase 28 project diagnostics use syntax-line locations; semantic cross-reference
|
||||
failures identify the MODEL stage without a source line. `xtc-check --inspect`
|
||||
checks dependency file availability only, without loading code or WAV contents.
|
||||
The project format is Linux/POSIX oriented; absolute plugin paths, architecture,
|
||||
ABI and JACK/device configuration must be supplied on another system. Unknown
|
||||
fields are rejected, not preserved. See PROJECT_FORMAT.md for versions 1–10 and
|
||||
RECOVERY.md for save failures after rename.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Project format v10 (reads v1–v9)
|
||||
|
||||
Phases 5–26 use a bounded text format implemented in C99 without a parser library.
|
||||
Phases 5–28 use a bounded text format implemented in C99 without a parser library.
|
||||
The extension is `.xtc`. File operations run exclusively on the control thread.
|
||||
The project owns its graph, tracks and silent/audio arrangement regions. Audio samples remain in external WAV
|
||||
files; MIDI notes are stored inline.
|
||||
|
|
@ -104,8 +104,8 @@ node types 1..4, migrating their clips to empty asset paths. Version 2 accepts
|
|||
node types 1..5 and ASSET records. Both migrate to MIDI=0 and no notes.
|
||||
Version 3 accepts instrument nodes and MIDI/NOTES records. Versions 1–3 migrate
|
||||
all tracks to unity gain, centered pan, mute/solo off and no automation.
|
||||
Saving always writes v7. Unknown versions are rejected without replacing the destination; older
|
||||
applications cannot read v7. Migration and maximum-path roundtrips are tested.
|
||||
Saving always writes v10. Unknown versions are rejected without replacing the destination; older
|
||||
applications without v10 support cannot read current saves. Migration and maximum-path roundtrips are tested.
|
||||
|
||||
## Ownership and failures
|
||||
|
||||
|
|
@ -114,8 +114,9 @@ the destination. Any failure retains the complete previous project. Saving
|
|||
validates the project, creates a mode-0600 temporary in the destination
|
||||
directory, writes, flushes, fsyncs, closes and renames over the destination.
|
||||
Failures before rename retain the previous destination and remove the temporary.
|
||||
The parent directory is not fsynced: survival of the rename through sudden power
|
||||
loss is not guaranteed. Save replaces symlinks themselves and does not preserve
|
||||
After rename, the parent directory is fsynced. A directory-sync failure reports
|
||||
I/O error even though the new complete destination may already be present.
|
||||
Storage and filesystem behavior still determine power-loss durability. Save replaces symlinks themselves and does not preserve
|
||||
existing file permissions. POSIX I/O is private to the Linux-first persistence
|
||||
module; public headers contain no POSIX types.
|
||||
|
||||
|
|
@ -123,7 +124,8 @@ Saved data reproduces the arrangement, graph, track mix and automation, includin
|
|||
ID allocation state. Undo history is not saved. See MIXER.md for pan/interpolation.
|
||||
Graph node positions, timeline zoom/scroll/selection, transport position and DSP
|
||||
oscillator phase are GUI/session state and are not saved. Asset references are
|
||||
external absolute paths, not copied on Save As. Metadata loading preserves
|
||||
absolute in memory; saves use relative paths for media inside the project folder,
|
||||
otherwise absolute paths. Media are not copied on Save As. Metadata loading preserves
|
||||
missing references; audio-plan preparation reports unreadable/invalid assets
|
||||
and renders them silently. Relinking replaces a selected clip's path without
|
||||
changing its IDs or arrangement. See AUDIO_FILES.md for recovery and limitations.
|
||||
|
|
@ -139,7 +141,7 @@ writer and unfinished-recording behavior. Metadata validation is available via
|
|||
|
||||
## Phase 15: version 5 plugin metadata
|
||||
|
||||
The writer now emits `XTC_PROJECT 5`; versions 1–4 still load. All v4 records and
|
||||
Phase 15 introduced `XTC_PROJECT 5`; its reader also supported versions 1–4. All v4 records and
|
||||
order remain the same. Node type 8 is a LADSPA plugin; native pointer-bearing type
|
||||
7 is still unsupported in files. Immediately after each type-8 `NODE id 8
|
||||
00000000` record come:
|
||||
|
|
@ -163,7 +165,7 @@ stored. See PLUGIN_HOSTING.md and decision 0009 for supported processors/limits.
|
|||
|
||||
## Phase 16: version 6
|
||||
|
||||
Writer emits v6 and readers accept v1–v6. After each plugin's CONTROL records,
|
||||
Phase 16 introduced v6 and its reader accepted v1–v6. After each plugin's CONTROL records,
|
||||
`PLUGIN_AUTOMATION count` is followed by exactly count (<=32) records:
|
||||
`PARAM tick port mode float32_hex_bits`. Mode is 0 linear or 1 step. Duplicate
|
||||
port/tick pairs, nonfinite values, unknown control ports and ticks beyond the
|
||||
|
|
@ -205,7 +207,7 @@ the selected document. In-memory models and the existing destination are preserv
|
|||
on load failure. No automatic unknown-field stripping or media deletion occurs.
|
||||
|
||||
`make test-portability test test-plugins test-recovery test-recording` covers checked-in
|
||||
v1–v6 fixtures (including missing v5/v6 plugins), v7 exact roundtrips, moved-folder
|
||||
v1–v10 fixtures (including missing v5/v6 plugins), current exact roundtrips, moved-folder
|
||||
playback from a different cwd, missing media retention, unknown records, every
|
||||
truncation and deterministic byte mutations, malformed plugin metadata, interrupted
|
||||
writers and write-limit failures. ASan/UBSan/static analysis additionally audit the
|
||||
|
|
@ -240,7 +242,7 @@ format change (`make test test-portability test-audio-editing`).
|
|||
|
||||
## Phase 24: version 9 mix routing
|
||||
|
||||
The writer emits v9. Immediately after each TRACK's MIX record it writes
|
||||
Phase 24 introduced v9. Immediately after each TRACK's MIX record it writes
|
||||
`ROUTE is_bus bus_id`. `is_bus` is 0 or 1; `bus_id` is an existing bus track ID,
|
||||
or decimal UINT64_MAX (18446744073709551615) for master. Buses must route to
|
||||
master and cannot contain clips. Invalid destinations, flags and nested routing
|
||||
|
|
@ -266,6 +268,72 @@ bindings and invalid interpolation modes reject the complete candidate. v1–v9
|
|||
supply default 7/10/64 bindings on every channel. No event history/current live
|
||||
controller values are serialized. Older readers reject v10.
|
||||
|
||||
Fixtures cover v1–v9 migration. Exact roundtrips, malformed metadata, step/linear
|
||||
Fixtures cover v1–v10 loading and migration. Exact roundtrips, malformed metadata, step/linear
|
||||
renders and remapped live events cover the new writer/reader. Public clients
|
||||
must rebuild and initialize the appended `xtc_automation_point.step` field.
|
||||
|
||||
## Phase 28 compatibility and diagnostic contract
|
||||
|
||||
The current reader accepts versions 1–10 and the writer always emits version 10.
|
||||
There is no format change in Phase 28. Saving an older document upgrades it;
|
||||
retain an original backup if an older application must still open it. APP is
|
||||
informational; compatibility depends on the format version, not the producer's
|
||||
application version. A future incompatible schema requires a new format number.
|
||||
Public C/C++ consumers must rebuild when public structs change.
|
||||
|
||||
| Version | Added representation | Defaults when loading earlier versions |
|
||||
| --- | --- | --- |
|
||||
| 1 | Graph, tracks, silent regions | Baseline |
|
||||
| 2 | External WAV references | Empty asset references |
|
||||
| 3 | MIDI clips and notes | Non-MIDI clips, no notes |
|
||||
| 4 | Track mix and automation | Unity gain, center pan, mute/solo off, no points |
|
||||
| 5 | LADSPA metadata | No plugin nodes |
|
||||
| 6 | Plugin automation | Empty plugin lanes |
|
||||
| 7 | Relative media references | Earlier media paths must be absolute |
|
||||
| 8 | Clip gain and fades | Unity gain, zero explicit fades |
|
||||
| 9 | Bus routing | Ordinary tracks routed directly to master |
|
||||
| 10 | Track interpolation and controller bindings | Linear points; CC 7/10/64 on every channel |
|
||||
|
||||
The grammar has no optional extension namespace. Unknown records, extra fields,
|
||||
node types or trailing data cannot be preserved by this model and are rejected
|
||||
rather than silently removed. Supported older records migrate in a separate
|
||||
candidate and pass complete model validation before adoption. Failed parsing,
|
||||
reference validation or close retains the existing destination model. GUI Open
|
||||
also prepares the candidate before replacing the live document; missing or
|
||||
incompatible plugins retain the current document and identify the module.
|
||||
Missing media references remain available for relinking and render silently.
|
||||
|
||||
`xtc_project_load_diagnostic` adds a copied result, parsed format version (zero
|
||||
if unavailable), expected record/stage and message. Syntax locations are 1-based;
|
||||
line zero denotes I/O, size, path resolution or whole-model validation. MODEL
|
||||
errors do not identify the original line of a cross-reference. The original
|
||||
`xtc_project_load` remains available with identical transactional semantics.
|
||||
|
||||
`xtc-check --validate PROJECT` checks bounded metadata without loading dependencies.
|
||||
`xtc-check --inspect PROJECT` additionally lists resolved media and module paths,
|
||||
plugin labels, unique IDs and descriptor indices. It checks readable regular-file
|
||||
availability only. Exit status is 0 when references are available, 2 when valid
|
||||
metadata has missing/unreadable references, and 1 for invalid metadata or I/O.
|
||||
Neither command executes plugin code, decodes WAV data, connects JACK or verifies
|
||||
hardware. A readable file can still be an invalid WAV or incompatible plugin;
|
||||
actual preparation performs those checks. Paths and labels are escaped for
|
||||
terminal output. There is no search-path substitution or automatic plugin relink.
|
||||
|
||||
Portability currently targets Linux/POSIX. The text representation is independent
|
||||
of byte order and locale and requires IEEE binary32 floats. IDs used by the graph
|
||||
must fit the receiving host's size_t; a 64-bit file is not guaranteed to fit a
|
||||
32-bit build. Paths follow POSIX semantics; Windows drive letters and separators
|
||||
are not translated. Relative media can travel with their project folder, while
|
||||
absolute references and symlink targets must be restored separately. LADSPA
|
||||
modules are absolute installed references: match architecture, ABI, descriptor
|
||||
identity, ports and legal controls on the destination. Plugin binaries and state
|
||||
are not bundled. JACK availability, sample rate, block limits and device routing
|
||||
are runtime dependencies; loading metadata does not restore device connections.
|
||||
|
||||
`make test` includes v1–v10 migration, malformed/truncated/mutated/oversized input,
|
||||
retained-state diagnostics, CLI dependency inspection, and deterministic save
|
||||
faults at flush, file sync, close, rename and directory sync. Pre-rename faults
|
||||
preserve the old bytes and remove the temporary. After rename, a sync failure
|
||||
reports I/O while leaving a complete new document; inspect it before retrying.
|
||||
Fault wrappers are linked into tests only. These tests complement killed-writer
|
||||
coverage in `make test-recovery`; they do not qualify power-cut behavior.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Persistence and failure recovery
|
||||
|
||||
The project format remains v4. Readers migrate v1/v2/v3, validate bounded input
|
||||
The project format is v10. Readers migrate v1–v9, validate bounded input
|
||||
and atomically install a candidate only after complete success. Existing tests
|
||||
cover exact migration/roundtrip, truncations, malformed references, counts,
|
||||
IDs, nonfinite values and failed GUI/backend installation. Native processors
|
||||
|
|
@ -10,22 +10,26 @@ process addresses or an unreadable project. See PROJECT_FORMAT.md.
|
|||
Project Save writes a mode-0600 same-directory `NAME.tmp.XXXXXX`, flushes and
|
||||
fsyncs it, closes and renames it over the destination. Before rename, handled
|
||||
failures remove the temporary and retain the destination. POSIX rename gives
|
||||
complete old-or-new destination contents during process failure. The directory
|
||||
is not fsynced, so this is not a power-loss durability guarantee. Symlinks are
|
||||
complete old-or-new destination contents during process failure. The containing directory is fsynced after rename. If that sync fails, Save
|
||||
reports I/O failure but the complete new file may already be present. Preserve
|
||||
and validate it before retrying; filesystem/hardware durability still varies. Symlinks are
|
||||
replaced rather than followed at rename; original file permissions are not kept.
|
||||
|
||||
Recording similarly writes/finalizes a same-directory temporary, then renames
|
||||
it. Stop must synchronize callbacks before draining/finalizing. Ring overflow
|
||||
returns an explicit error and may finalize a partial take for manual recovery;
|
||||
I/O failure retains the previous destination. A killed recorder does not finish
|
||||
its WAV header. Source WAV files remain external absolute paths; Save As does
|
||||
not collect them. Back up the project and assets together.
|
||||
its WAV header. Source WAV files remain external; references inside the project folder are
|
||||
saved relative to it. Save As does not collect them. Back up the project and assets together.
|
||||
|
||||
## After a crash
|
||||
|
||||
1. Preserve the destination and any `.tmp.*` files before attempting repair.
|
||||
2. Run `xtc-check --validate project.xtc` for metadata validation without a GUI,
|
||||
display or audio server. This does not verify assets or realtime preparation.
|
||||
display or audio server. Run `xtc-check --inspect project.xtc` to list resolved
|
||||
media/module paths and plugin identities: status 2 means missing/unreadable
|
||||
references, 1 means invalid metadata/I/O, 0 means files are available. Neither
|
||||
command verifies file contents, plugin initialization or realtime preparation.
|
||||
3. A valid destination can be opened normally. If only a temporary contains useful
|
||||
data, copy it to a separate candidate and validate that copy before replacing
|
||||
anything. Partial project files are rejected; there is no salvage parser.
|
||||
|
|
@ -75,3 +79,13 @@ retains the original graph, controls and history position. Cancel in the generic
|
|||
parameter editor discards its temporary values. Reset defaults clears automation
|
||||
only when applied. No host recovery can contain a crash or hang in trusted native
|
||||
plugin code; LADSPA has no runtime error-return channel.
|
||||
|
||||
## Save diagnostics (Phase 28)
|
||||
|
||||
`make test-project-diagnostics` deterministically injects flush, file-sync, close,
|
||||
rename and directory-sync errors. Pre-rename failures retain the previous bytes
|
||||
and clean temporary files. Post-rename sync errors leave a complete new file and
|
||||
return failure; there is no rollback after replacement. Load diagnostics identify
|
||||
the expected record and syntax line, or line zero for whole-model/I/O stages.
|
||||
Unknown versions report the supported range (1–10); unknown records/fields are
|
||||
rejected without partially changing the current project.
|
||||
|
|
|
|||
|
|
@ -774,6 +774,18 @@ Make project files robust, diagnosable, and maintainable across application vers
|
|||
- Version incompatibilities and missing dependencies are clearly reported.
|
||||
- Supported project versions have migration and regression tests.
|
||||
|
||||
### Implementation status
|
||||
Completed: version-10 compatibility policy and v1–v10 fixture migration;
|
||||
transactional diagnostic loading in the core and GUI; metadata-only dependency
|
||||
inspection with explicit availability limits; relocation and unknown-field
|
||||
policies; bounded-input and deterministic pre/post-rename save-failure coverage.
|
||||
No format bump was needed. Linux/POSIX, plugin ABI/path and audio-backend limits
|
||||
are documented in PROJECT_FORMAT.md and RECOVERY.md.
|
||||
|
||||
Validation: full core/GUI/plugin/JACK/workflow/recording/recovery tests, static
|
||||
analysis, staged installation and binary packaging; minimal no-JACK/no-LADSPA
|
||||
build and tests; ASan/UBSan core, reliability and recovery tests.
|
||||
|
||||
## Phase 29 — Performance, Scalability, and Stress Testing
|
||||
|
||||
### Goal
|
||||
|
|
|
|||
|
|
@ -152,3 +152,11 @@ right-click no longer immediately deletes notes. Graph right-click targets the
|
|||
node/wire under the pointer. Undo preserves surviving selections. Completed
|
||||
takes add one undo step; undo removes references and keeps WAV files. See
|
||||
EDITING.md for grouped commands, failure handling and keyboard/focus behavior.
|
||||
|
||||
Project troubleshooting: `xtc-check --validate PROJECT.xtc` reports metadata
|
||||
errors with the expected record and syntax line. `xtc-check --inspect PROJECT.xtc`
|
||||
lists resolved media/module paths and plugin identities without executing plugins
|
||||
or starting JACK. Exit codes: 0 = valid metadata and available files, 2 = missing
|
||||
or unreadable references, 1 = metadata/I/O error. File availability does not
|
||||
certify WAV contents or plugin compatibility. See [PROJECT_FORMAT.md](PROJECT_FORMAT.md)
|
||||
for the compatibility and relocation policy.
|
||||
|
|
|
|||
|
|
@ -224,6 +224,17 @@ xtc_result xtc_project_sample_to_tick(const xtc_project *project, uint64_t sampl
|
|||
* @return XTC_OK on success, or an error code describing the failure.
|
||||
*/
|
||||
xtc_result xtc_project_save(const xtc_project *project, const char *path);
|
||||
/* Optional copied control-thread diagnostics. line is 1-based for syntax errors,
|
||||
* 0 for I/O/whole-model validation; record identifies the expected record or
|
||||
* validation stage, never untrusted file text. format_version is 0 if unknown.
|
||||
* No media/plugin loading, engine creation or mutation on load failure. */
|
||||
typedef struct {
|
||||
xtc_result result;
|
||||
uint64_t format_version;
|
||||
size_t line;
|
||||
char record[32], message[192];
|
||||
} xtc_project_diagnostic;
|
||||
xtc_result xtc_project_load_diagnostic(xtc_project *project, const char *path, xtc_project_diagnostic *diagnostic);
|
||||
/**
|
||||
* @brief Loads and validates a project before transactionally replacing the current state.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -961,8 +961,14 @@ xtc_result MainWindow::open_project(const char *path)
|
|||
std::string loaded_path(path);
|
||||
xtc_project *candidate = nullptr;
|
||||
xtc_result r = xtc_project_create(&candidate);
|
||||
if (r == XTC_OK)
|
||||
r = xtc_project_load(candidate, path);
|
||||
xtc_project_diagnostic diagnostic{};
|
||||
if (r == XTC_OK) {
|
||||
r = xtc_project_load_diagnostic(candidate,path,&diagnostic);
|
||||
if (r != XTC_OK) {
|
||||
project_issue_ = "Project load line " + std::to_string(diagnostic.line) + " (" + diagnostic.record + "): " + diagnostic.message;
|
||||
report_error(project_issue_.c_str(),r);
|
||||
}
|
||||
}
|
||||
if (r == XTC_OK)
|
||||
r = install_project(candidate);
|
||||
if (r != XTC_OK) {
|
||||
|
|
|
|||
80
src/main.c
80
src/main.c
|
|
@ -1,3 +1,4 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc.h"
|
||||
#include "xtc/xtc_version.h"
|
||||
|
||||
|
|
@ -8,11 +9,67 @@
|
|||
#include "xtc/xtc_audio.h"
|
||||
#include "xtc/xtc_midi.h"
|
||||
#include <limits.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include "xtc/xtc_plugin.h"
|
||||
|
||||
/* Dependency inspection checks readable regular files, never plugin code or
|
||||
* audio contents. Escape untrusted path/label bytes in terminal output. */
|
||||
static void quoted(const char *text)
|
||||
{
|
||||
const unsigned char *p = (const unsigned char *)text;
|
||||
putchar('"');
|
||||
for (; *p; ++p) {
|
||||
if (*p == '"' || *p == '\\') printf("\\%c",*p);
|
||||
else if (*p < 32 || *p == 127) printf("\\x%02x",(unsigned int)*p);
|
||||
else putchar(*p);
|
||||
}
|
||||
putchar('"');
|
||||
}
|
||||
static int readable_file(const char *path)
|
||||
{
|
||||
struct stat info;
|
||||
return stat(path,&info) == 0 && S_ISREG(info.st_mode) && access(path,R_OK) == 0;
|
||||
}
|
||||
static int inspect(const xtc_project *project, uint64_t version)
|
||||
{
|
||||
size_t tracks = 0, clips = 0, nodes = 0, edges = 0, i, missing = 0;
|
||||
const xtc_graph *graph = xtc_project_graph(project);
|
||||
xtc_project_get_counts(project,&tracks,&clips);
|
||||
xtc_graph_get_counts(graph,&nodes,&edges);
|
||||
printf("Project format %" PRIu64 " (writer %u): %zu tracks/buses, %zu clips, %zu graph nodes, %zu edges.\n",
|
||||
version,XTC_PROJECT_FORMAT_VERSION,tracks,clips,nodes,edges);
|
||||
for (i = 0; i < clips; ++i) {
|
||||
xtc_clip_info clip;
|
||||
xtc_project_get_clip(project,i,&clip);
|
||||
if (clip.asset_path[0]) {
|
||||
int available = readable_file(clip.asset_path);
|
||||
printf("Media clip %" PRIu64 ": %s ",clip.id,available ? "available" : "missing/unreadable");
|
||||
quoted(clip.asset_path); putchar('\n');
|
||||
if (!available) ++missing;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < nodes; ++i) {
|
||||
xtc_node_info node; xtc_plugin_spec spec;
|
||||
xtc_graph_get_node(graph,i,&node);
|
||||
if (xtc_graph_get_plugin(graph,node.id,&spec) == XTC_OK) {
|
||||
int available = readable_file(spec.path);
|
||||
printf("LADSPA node %zu ID %u descriptor %u ",node.id,spec.unique_id,spec.index);
|
||||
quoted(spec.label); printf(": %s ",available ? "available" : "missing/unreadable");
|
||||
quoted(spec.path); putchar('\n');
|
||||
if (!available) ++missing;
|
||||
}
|
||||
}
|
||||
printf("Dependency references missing/unreadable: %zu.\n",missing);
|
||||
puts("File availability only: WAV contents, plugin identity/initialization, JACK and hardware not checked; no plugin code executed.");
|
||||
return missing ? 2 : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Runs the xtc-check command-line entry point.
|
||||
*
|
||||
* Supports --version, --help, and --validate PROJECT. With no arguments it
|
||||
* Supports version/help, metadata validation and dependency inspection. With no arguments it
|
||||
* creates and destroys the application shell to check core startup/shutdown.
|
||||
* Any other argument count prints usage to stderr.
|
||||
*
|
||||
|
|
@ -38,7 +95,7 @@ int main(int argc, char **argv)
|
|||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc == 2 && !strcmp(argv[1], "--help")) {
|
||||
puts("Usage: xtc-check [--version | --help | --diagnose | --validate PROJECT | --recover-recording TEMP NEW.wav]\nNo arguments checks core startup/shutdown. "
|
||||
puts("Usage: xtc-check [--version | --help | --diagnose | --validate PROJECT | --inspect PROJECT | --recover-recording TEMP NEW.wav]\nNo arguments checks core startup/shutdown. "
|
||||
"Validation checks metadata only.");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -52,21 +109,28 @@ int main(int argc, char **argv)
|
|||
puts("Complete recorded frames recovered; source retained.");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc == 3 && !strcmp(argv[1], "--validate")) {
|
||||
if (argc == 3 && (!strcmp(argv[1], "--validate") || !strcmp(argv[1], "--inspect"))) {
|
||||
xtc_project *project = NULL;
|
||||
xtc_project_diagnostic diagnostic = {0};
|
||||
xtc_result result = xtc_project_create(&project);
|
||||
if (result == XTC_OK)
|
||||
result = xtc_project_load(project, argv[2]);
|
||||
xtc_project_destroy(project);
|
||||
if (result == XTC_OK) result = xtc_project_load_diagnostic(project,argv[2],&diagnostic);
|
||||
if (result != XTC_OK) {
|
||||
fprintf(stderr, "Project validation failed: %s\n", xtc_result_string(result));
|
||||
fprintf(stderr,"Project validation failed: %s; line %zu, %s: %s\n",xtc_result_string(result),
|
||||
diagnostic.line,diagnostic.record,diagnostic.message);
|
||||
xtc_project_destroy(project);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!strcmp(argv[1],"--inspect")) {
|
||||
int code = inspect(project,diagnostic.format_version);
|
||||
xtc_project_destroy(project);
|
||||
return code;
|
||||
}
|
||||
xtc_project_destroy(project);
|
||||
puts("Project metadata valid (external assets not loaded).");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc != 1) {
|
||||
fputs("Usage: xtc-check [--version | --help | --diagnose | --validate PROJECT | --recover-recording TEMP NEW.wav]\n", stderr);
|
||||
fputs("Usage: xtc-check [--version | --help | --diagnose | --validate PROJECT | --inspect PROJECT | --recover-recording TEMP NEW.wav]\n", stderr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,13 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
typedef struct {
|
||||
FILE *file;
|
||||
size_t bytes;
|
||||
size_t bytes, line_number, record_line;
|
||||
uint64_t version;
|
||||
char expected[32];
|
||||
char line[1536];
|
||||
char *tokens[12];
|
||||
size_t count;
|
||||
|
|
@ -33,6 +36,7 @@ static xtc_result line(reader *input)
|
|||
int byte;
|
||||
char *cursor;
|
||||
input->count = 0U;
|
||||
input->record_line = ++input->line_number;
|
||||
while ((byte = fgetc(input->file)) != EOF) {
|
||||
if (++input->bytes > 4194304U)
|
||||
return XTC_ERR_FORMAT;
|
||||
|
|
@ -166,7 +170,9 @@ static int decode_float(const char *text, float *value)
|
|||
|
||||
#define READ(tag, total) \
|
||||
do { \
|
||||
xtc_result read_result = line(input); \
|
||||
xtc_result read_result; \
|
||||
snprintf(input->expected, sizeof(input->expected), "%s", (tag)); \
|
||||
read_result = line(input); \
|
||||
if (read_result != XTC_OK) \
|
||||
return read_result; \
|
||||
if (input->count != (total) || strcmp(input->tokens[0], (tag)) != 0) \
|
||||
|
|
@ -193,6 +199,7 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
|||
xtc_graph *graph = project->graph;
|
||||
READ("XTC_PROJECT", 2U);
|
||||
NUM(1U, version);
|
||||
input->version = version;
|
||||
if (version < 1U || version > XTC_PROJECT_FORMAT_VERSION)
|
||||
return XTC_ERR_UNSUPPORTED;
|
||||
format_version = version;
|
||||
|
|
@ -435,6 +442,8 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
|||
return XTC_ERR_FORMAT;
|
||||
if (ferror(input->file))
|
||||
return XTC_ERR_IO;
|
||||
input->record_line = 0;
|
||||
snprintf(input->expected,sizeof(input->expected),"%s","ASSET_PATH");
|
||||
if (format_version >= 7) for (i = 0; i < project->clip_count; ++i) {
|
||||
char *asset = project->clips[i].asset_path;
|
||||
if (asset[0] && asset[0] != '/') {
|
||||
|
|
@ -444,6 +453,7 @@ static xtc_result read_project(reader *input, xtc_project *project, const char *
|
|||
memcpy(asset,directory,root); asset[root] = '/';
|
||||
}
|
||||
}
|
||||
snprintf(input->expected,sizeof(input->expected),"%s","MODEL");
|
||||
return xtc_project_validate(project);
|
||||
}
|
||||
|
||||
|
|
@ -612,36 +622,64 @@ static char *project_folder(const char *path)
|
|||
* @param path NUL-terminated file or asset path.
|
||||
* @return XTC_OK on success, or an error code describing the failure.
|
||||
*/
|
||||
xtc_result xtc_project_load(xtc_project *project, const char *path)
|
||||
static xtc_result load_report(const reader *input, xtc_result result, xtc_project_diagnostic *out)
|
||||
{
|
||||
xtc_project_diagnostic d = {0};
|
||||
d.result = result;
|
||||
d.format_version = input->version;
|
||||
d.line = result == XTC_OK ? 0 : input->record_line;
|
||||
if (result != XTC_OK) snprintf(d.record,sizeof(d.record),"%s",input->expected);
|
||||
if (result == XTC_OK) snprintf(d.message,sizeof(d.message),"Metadata valid; external media/plugins not loaded.");
|
||||
else if (result == XTC_ERR_UNSUPPORTED && !strcmp(input->expected,"XTC_PROJECT"))
|
||||
snprintf(d.message,sizeof(d.message),"Project format %" PRIu64 " is unsupported; this build reads versions 1..%u.",input->version,XTC_PROJECT_FORMAT_VERSION);
|
||||
else if (result == XTC_ERR_UNSUPPORTED) snprintf(d.message,sizeof(d.message),"Unsupported project feature or node type; no data was discarded.");
|
||||
else if (result == XTC_ERR_FORMAT && !strcmp(input->expected,"SIZE")) snprintf(d.message,sizeof(d.message),"Project exceeds the 4 MiB input limit.");
|
||||
else if (result == XTC_ERR_FORMAT && !strcmp(input->expected,"MODEL")) snprintf(d.message,sizeof(d.message),"Invalid project references, IDs, graph, routing or values.");
|
||||
else if (result == XTC_ERR_FORMAT) snprintf(d.message,sizeof(d.message),"Invalid, unknown or truncated record; expected %s. Unknown fields are rejected.",input->expected);
|
||||
else if (result == XTC_ERR_IO) snprintf(d.message,sizeof(d.message),"Cannot read project or resolve its containing directory; check path and permissions.");
|
||||
else if (result == XTC_ERR_CAPACITY) snprintf(d.message,sizeof(d.message),"Project capacity or resolved media path exceeds supported limits.");
|
||||
else snprintf(d.message,sizeof(d.message),"Project load failed: %s.",xtc_result_string(result));
|
||||
if (out) *out = d;
|
||||
return result;
|
||||
}
|
||||
xtc_result xtc_project_load_diagnostic(xtc_project *project, const char *path, xtc_project_diagnostic *diagnostic)
|
||||
{
|
||||
xtc_project *candidate = NULL;
|
||||
reader input = {0};
|
||||
char *directory;
|
||||
xtc_result result;
|
||||
struct stat info;
|
||||
snprintf(input.expected,sizeof(input.expected),"%s","OPEN");
|
||||
if (project == NULL || path_length(path) == 0U)
|
||||
return XTC_ERR_INVALID_ARGUMENT;
|
||||
return load_report(&input,XTC_ERR_INVALID_ARGUMENT,diagnostic);
|
||||
input.file = fopen(path, "rb");
|
||||
if (input.file == NULL)
|
||||
return XTC_ERR_IO;
|
||||
if (input.file == NULL) return load_report(&input,XTC_ERR_IO,diagnostic);
|
||||
if (fstat(fileno(input.file),&info) == 0 && S_ISREG(info.st_mode) && info.st_size > 4194304) {
|
||||
snprintf(input.expected,sizeof(input.expected),"%s","SIZE");
|
||||
fclose(input.file);
|
||||
return load_report(&input,XTC_ERR_FORMAT,diagnostic);
|
||||
}
|
||||
{
|
||||
char *canonical = realpath(path,NULL);
|
||||
directory = canonical ? project_folder(canonical) : NULL;
|
||||
free(canonical);
|
||||
}
|
||||
if (!directory) { fclose(input.file); return XTC_ERR_IO; }
|
||||
if (!directory) { fclose(input.file); return load_report(&input,XTC_ERR_IO,diagnostic); }
|
||||
result = xtc_project_create(&candidate);
|
||||
if (result == XTC_OK)
|
||||
result = read_project(&input, candidate, directory);
|
||||
if (result == XTC_OK) result = read_project(&input, candidate, directory);
|
||||
free(directory);
|
||||
if (fclose(input.file) != 0 && result == XTC_OK)
|
||||
result = XTC_ERR_IO;
|
||||
if (fclose(input.file) != 0 && result == XTC_OK) result = XTC_ERR_IO;
|
||||
if (result == XTC_OK) {
|
||||
xtc_graph *old = project->graph;
|
||||
*project = *candidate;
|
||||
candidate->graph = old;
|
||||
}
|
||||
xtc_project_destroy(candidate);
|
||||
return result;
|
||||
return load_report(&input,result,diagnostic);
|
||||
}
|
||||
xtc_result xtc_project_load(xtc_project *project, const char *path)
|
||||
{
|
||||
return xtc_project_load_diagnostic(project,path,NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
2
tests/fixtures/README.md
vendored
2
tests/fixtures/README.md
vendored
|
|
@ -3,7 +3,7 @@
|
|||
Store small, redistributable fixtures here. Document their origin and license.
|
||||
Do not commit large recordings or assets with unclear licensing.
|
||||
|
||||
`project-v1.xtc` through `project-v9.xtc` are hand-authored xtc metadata fixtures
|
||||
`project-v1.xtc` through `project-v10.xtc` are hand-authored xtc metadata fixtures
|
||||
created for this repository, under the project license. They contain no audio
|
||||
or third-party resources. The v5/v6 plugin fixtures intentionally reference a
|
||||
missing module to exercise metadata migration without loading code.
|
||||
|
|
|
|||
39
tests/fixtures/project-v10.xtc
vendored
Normal file
39
tests/fixtures/project-v10.xtc
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
XTC_PROJECT 10
|
||||
APP 0 1 0
|
||||
TEMPO 120000
|
||||
IDS 1 1
|
||||
GRAPH 1 2 2 1
|
||||
NODE 0 1 3f000000
|
||||
NODE 1 4 00000000
|
||||
EDGE 0 1
|
||||
TRACKS 1
|
||||
TRACK 0 4b657973
|
||||
MIX 3f800000 00000000 0 0
|
||||
ROUTE 0 18446744073709551615
|
||||
CLIPS 1
|
||||
CLIP 0 0 0 960 0 960 5665727365
|
||||
ASSET -
|
||||
MIDI 1
|
||||
AUDIO 3f800000 0 0
|
||||
NOTES 1 1
|
||||
NOTE 0 0 0 480 69 100 0
|
||||
AUTOMATION 1
|
||||
POINT 0 0 0 3f800000 0
|
||||
CONTROL_MAPS 16
|
||||
MAP 0 7 10 64
|
||||
MAP 1 7 10 64
|
||||
MAP 2 7 10 64
|
||||
MAP 3 7 10 64
|
||||
MAP 4 7 10 64
|
||||
MAP 5 7 10 64
|
||||
MAP 6 7 10 64
|
||||
MAP 7 7 10 64
|
||||
MAP 8 7 10 64
|
||||
MAP 9 7 10 64
|
||||
MAP 10 7 10 64
|
||||
MAP 11 7 10 64
|
||||
MAP 12 7 10 64
|
||||
MAP 13 7 10 64
|
||||
MAP 14 7 10 64
|
||||
MAP 15 7 10 64
|
||||
END
|
||||
|
|
@ -19,7 +19,7 @@ int main(void)
|
|||
assert(mkdtemp(root));
|
||||
assert(xtc_project_create(&p) == XTC_OK);
|
||||
snprintf(file,sizeof(file),"%s/migration.xtc",root);
|
||||
for (version = 1; version <= 9; ++version) {
|
||||
for (version = 1; version <= 10; ++version) {
|
||||
xtc_track_info t; xtc_clip_info c; size_t notes, points;
|
||||
snprintf(old,sizeof(old),"tests/fixtures/project-v%u.xtc",version);
|
||||
assert(xtc_project_load(p,old) == XTC_OK);
|
||||
|
|
@ -89,6 +89,6 @@ int main(void)
|
|||
assert(xtc_project_load(p,file) == XTC_ERR_FORMAT);
|
||||
{ xtc_clip_info c; assert(xtc_project_find_clip(p,clip,&c) == XTC_OK && c.asset_path[0]); } }
|
||||
xtc_project_destroy(p); unlink(file); rmdir(moved);
|
||||
puts("v1-v9 migration, current folder relocation, cwd independence and missing-media preservation passed.");
|
||||
puts("v1-v10 migration, current folder relocation, cwd independence and missing-media preservation passed.");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
52
tests/integration/test_project_inspection.py
Normal file
52
tests/integration/test_project_inspection.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Metadata diagnostics and dependency inspection without a server or modules."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
binary = str(Path(sys.argv[1]).resolve())
|
||||
|
||||
def check(option, path, code, *messages):
|
||||
result = subprocess.run([binary, option, str(path)], capture_output=True, text=True)
|
||||
assert result.returncode == code, (result.returncode, result.stdout, result.stderr)
|
||||
for message in messages:
|
||||
assert message in result.stdout + result.stderr, (message, result.stdout, result.stderr)
|
||||
|
||||
base = Path("tests/fixtures/project-v10.xtc").read_text()
|
||||
check("--inspect", "tests/fixtures/project-v10.xtc", 0, "Project format 10", "missing/unreadable: 0")
|
||||
with tempfile.TemporaryDirectory(prefix="xtc-inspect-") as directory:
|
||||
root = Path(directory)
|
||||
project = root / "project.xtc"
|
||||
media = root / "media.wav"
|
||||
# Relative paths resolve against the project, independently of the subprocess cwd.
|
||||
audio = base.replace("ASSET -", "ASSET " + b"media.wav".hex()).replace("MIDI 1", "MIDI 0")
|
||||
audio = audio.replace("NOTES 1 1\nNOTE 0 0 0 480 69 100 0", "NOTES 0 0")
|
||||
project.write_text(audio)
|
||||
check("--validate", project, 0, "metadata valid")
|
||||
check("--inspect", project, 2, "Media clip 0", str(media), "missing/unreadable: 1")
|
||||
media.write_text("Deliberately not WAV data")
|
||||
check("--inspect", project, 0, "available", "WAV contents", "not checked")
|
||||
module = root / "module.so"
|
||||
plugin = Path("tests/fixtures/project-plugin-v6.xtc").read_text()
|
||||
plugin = plugin.replace(b"/missing/test.so".hex(), str(module).encode().hex())
|
||||
project.write_text(plugin)
|
||||
check("--validate", project, 0)
|
||||
check("--inspect", project, 2, "LADSPA node 0 ID 900000 descriptor 0", "xtc_test_gain", str(module))
|
||||
# Inspection succeeds for readable files without trying dlopen or decoding.
|
||||
module.write_text("Deliberately not an executable module")
|
||||
check("--inspect", project, 0, "no plugin code executed")
|
||||
for text, messages in [
|
||||
(base.replace("XTC_PROJECT 10", "XTC_PROJECT 999"), ("line 1", "999", "1..10")),
|
||||
("XTC_PROJECT 10\n", ("line 2", "APP")),
|
||||
(base.replace("TEMPO 120000", "TEMPO 0"), ("line 3", "TEMPO")),
|
||||
(base + "OPTIONAL_FUTURE 1\n", ("Unknown fields",)),
|
||||
(base.replace("APP 0 1 0", "APP 0 1 0 extra"), ("APP",)),
|
||||
]:
|
||||
project.write_text(text)
|
||||
check("--inspect", project, 1, *messages)
|
||||
with project.open("wb") as stream:
|
||||
stream.truncate(4194305)
|
||||
check("--inspect", project, 1, "SIZE", "4 MiB")
|
||||
check("--inspect", root / "absent.xtc", 1, "OPEN")
|
||||
print("Project inspection availability, diagnostics and strict compatibility passed.")
|
||||
112
tests/unit/test_project_diagnostics.c
Normal file
112
tests/unit/test_project_diagnostics.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "xtc/xtc_project.h"
|
||||
#include <assert.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
/* Linker interposition injects errors only into this test executable. */
|
||||
static int fault;
|
||||
int __real_fflush(FILE *file);
|
||||
int __real_fclose(FILE *file);
|
||||
int __real_fsync(int descriptor);
|
||||
int __real_rename(const char *old, const char *next);
|
||||
int __wrap_fflush(FILE *file)
|
||||
{
|
||||
if (fault == 1) { errno = EIO; return EOF; }
|
||||
return __real_fflush(file);
|
||||
}
|
||||
int __wrap_fclose(FILE *file)
|
||||
{
|
||||
int result = __real_fclose(file);
|
||||
if (fault == 4) { errno = EIO; return EOF; }
|
||||
return result;
|
||||
}
|
||||
int __wrap_fsync(int descriptor)
|
||||
{
|
||||
struct stat info;
|
||||
assert(fstat(descriptor,&info) == 0);
|
||||
if ((fault == 2 && S_ISREG(info.st_mode)) || (fault == 5 && S_ISDIR(info.st_mode))) { errno = EIO; return -1; }
|
||||
return __real_fsync(descriptor);
|
||||
}
|
||||
int __wrap_rename(const char *old, const char *next)
|
||||
{
|
||||
if (fault == 3) { errno = EIO; return -1; }
|
||||
return __real_rename(old,next);
|
||||
}
|
||||
static void write_text(const char *path, const char *text)
|
||||
{
|
||||
FILE *file = fopen(path,"wb"); assert(file);
|
||||
assert(fwrite(text,1,strlen(text),file) == strlen(text)); assert(fclose(file) == 0);
|
||||
}
|
||||
static size_t bytes(const char *path, char out[4096])
|
||||
{
|
||||
FILE *file = fopen(path,"rb"); size_t n;
|
||||
assert(file); n = fread(out,1,4096,file); assert(!ferror(file) && feof(file)); assert(fclose(file) == 0); return n;
|
||||
}
|
||||
static void no_temporaries(const char *path)
|
||||
{
|
||||
DIR *directory = opendir(path); struct dirent *entry; assert(directory);
|
||||
while ((entry = readdir(directory))) assert(!strstr(entry->d_name,".tmp."));
|
||||
assert(closedir(directory) == 0);
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
char root[] = "/tmp/xtc-project-report-XXXXXX", song[256], bad[256];
|
||||
char before[4096], after[4096]; size_t size, n;
|
||||
xtc_project *old = NULL, *next = NULL, *loaded = NULL;
|
||||
xtc_project_diagnostic diagnostic;
|
||||
xtc_track_info track;
|
||||
uint64_t id;
|
||||
int mode;
|
||||
assert(mkdtemp(root));
|
||||
snprintf(song,sizeof(song),"%s/song.xtc",root); snprintf(bad,sizeof(bad),"%s/bad.xtc",root);
|
||||
assert(xtc_project_create(&old) == XTC_OK && xtc_project_create(&next) == XTC_OK && xtc_project_create(&loaded) == XTC_OK);
|
||||
assert(xtc_project_add_track(old,"Old",&id) == XTC_OK && xtc_project_add_track(next,"New",&id) == XTC_OK);
|
||||
assert(xtc_project_save(old,song) == XTC_OK); size = bytes(song,before);
|
||||
assert(xtc_project_load_diagnostic(loaded,song,&diagnostic) == XTC_OK && diagnostic.result == XTC_OK &&
|
||||
diagnostic.format_version == 10 && !diagnostic.line);
|
||||
for (mode = 1; mode <= 5; ++mode) {
|
||||
fault = mode;
|
||||
assert(xtc_project_save(next,song) == XTC_ERR_IO);
|
||||
fault = 0;
|
||||
no_temporaries(root);
|
||||
n = bytes(song,after);
|
||||
if (mode < 5) assert(n == size && !memcmp(before,after,size));
|
||||
assert(xtc_project_load(loaded,song) == XTC_OK);
|
||||
assert(xtc_project_get_track(loaded,0,&track) == XTC_OK && !strcmp(track.name,mode < 5 ? "Old" : "New"));
|
||||
}
|
||||
write_text(bad,"XTC_PROJECT 999\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_UNSUPPORTED && diagnostic.line == 1 &&
|
||||
diagnostic.format_version == 999 && !strcmp(diagnostic.record,"XTC_PROJECT") && strstr(diagnostic.message,"1..10"));
|
||||
write_text(bad,"XTC_PROJECT 10\nAPP 0 1 0\nTEMPO nope\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_FORMAT && diagnostic.line == 3 &&
|
||||
!strcmp(diagnostic.record,"TEMPO"));
|
||||
write_text(bad,"XTC_PROJECT 10\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_FORMAT && diagnostic.line == 2 &&
|
||||
!strcmp(diagnostic.record,"APP"));
|
||||
write_text(bad,"XTC_PROJECT 10\nAPP 0 1 0\nTEMPO 120000\nIDS 0 0\nGRAPH 1 1 1 0\nNODE 0 7 00000000\n");
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_UNSUPPORTED && diagnostic.line == 6 &&
|
||||
!strcmp(diagnostic.record,"NODE") && strstr(diagnostic.message,"node type"));
|
||||
{
|
||||
FILE *file = fopen(bad,"wb"); assert(file);
|
||||
assert(ftruncate(fileno(file),4194305) == 0 && fclose(file) == 0);
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_FORMAT && !diagnostic.line &&
|
||||
!strcmp(diagnostic.record,"SIZE") && strstr(diagnostic.message,"4 MiB"));
|
||||
}
|
||||
{
|
||||
FILE *file = fopen(bad,"wb"); size_t i; assert(file);
|
||||
for (i = 0; i < 1536; ++i) assert(fputc('A',file) != EOF);
|
||||
assert(fputc('\n',file) != EOF && fclose(file) == 0);
|
||||
assert(xtc_project_load_diagnostic(loaded,bad,&diagnostic) == XTC_ERR_FORMAT && diagnostic.line == 1);
|
||||
}
|
||||
assert(xtc_project_get_track(loaded,0,&track) == XTC_OK && !strcmp(track.name,"New"));
|
||||
assert(xtc_project_load_diagnostic(loaded,"/absent/xtc-report.xtc",&diagnostic) == XTC_ERR_IO && !diagnostic.line);
|
||||
xtc_project_destroy(old); xtc_project_destroy(next); xtc_project_destroy(loaded);
|
||||
unlink(song); unlink(bad); assert(rmdir(root) == 0);
|
||||
puts("Project load diagnostics, retained state, bounded input and pre/post-rename save faults passed.");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue