173 lines
6.9 KiB
C++
173 lines
6.9 KiB
C++
// Standalone tests for reasampler::OwnedFileManifest — no REAPER, no framework.
|
|
// The owned-file manifest seam (Phase B B-cap): a deduplicated, insertion-ordered
|
|
// set of project-relative files the capture path created, with JSON round-trip.
|
|
//
|
|
// Covers (brief-named): JSON round-trip, dedup of repeated adds, the empty manifest.
|
|
// Plus: the relative-paths-only invariant (reject empty / absolute), contains()
|
|
// semantics, insertion-order preservation, malformed-parse -> nullopt (the persist
|
|
// shell's warn+fallback hinges on it), and round-trip of paths with JSON metacharacters.
|
|
|
|
#include "../src/core/model/owned_manifest.h"
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
using namespace reasampler;
|
|
using namespace reasampler::model;
|
|
|
|
static int g_fail = 0;
|
|
#define CHECK(cond) do { if(!(cond)) { \
|
|
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
|
|
|
// --- empty manifest ----------------------------------------------------------
|
|
|
|
static void testEmptyManifest() {
|
|
OwnedFileManifest m;
|
|
CHECK(m.empty());
|
|
CHECK(m.size() == 0);
|
|
CHECK(m.paths().empty());
|
|
CHECK(!m.contains("anything.wav"));
|
|
|
|
// An empty manifest serializes to a well-formed shape and round-trips to empty.
|
|
const std::string json = m.serialize();
|
|
auto back = OwnedFileManifest::deserialize(json);
|
|
CHECK(back.has_value());
|
|
CHECK(*back == m);
|
|
CHECK(back->empty());
|
|
}
|
|
|
|
// --- add / contains / order --------------------------------------------------
|
|
|
|
static void testAddAndContains() {
|
|
OwnedFileManifest m;
|
|
CHECK(m.add("reasampler_bank/a.wav") == ManifestAddResult::Added);
|
|
CHECK(m.add("reasampler_bank/b.wav") == ManifestAddResult::Added);
|
|
|
|
CHECK(m.size() == 2);
|
|
CHECK(m.contains("reasampler_bank/a.wav"));
|
|
CHECK(m.contains("reasampler_bank/b.wav"));
|
|
CHECK(!m.contains("reasampler_bank/c.wav"));
|
|
// Exact-string match — not a prefix / substring match.
|
|
CHECK(!m.contains("reasampler_bank/a"));
|
|
CHECK(!m.contains("a.wav"));
|
|
|
|
// Insertion order is preserved (deterministic ext-state).
|
|
CHECK(m.paths().size() == 2);
|
|
CHECK(m.paths()[0] == "reasampler_bank/a.wav");
|
|
CHECK(m.paths()[1] == "reasampler_bank/b.wav");
|
|
}
|
|
|
|
// --- dedup of repeated adds --------------------------------------------------
|
|
|
|
static void testDedupRepeatedAdds() {
|
|
OwnedFileManifest m;
|
|
CHECK(m.add("reasampler_bank/take.wav") == ManifestAddResult::Added);
|
|
// A repeat capture of an identical request must not double-record the file.
|
|
CHECK(m.add("reasampler_bank/take.wav") == ManifestAddResult::AlreadyPresent);
|
|
CHECK(m.add("reasampler_bank/take.wav") == ManifestAddResult::AlreadyPresent);
|
|
CHECK(m.size() == 1);
|
|
CHECK(m.paths().size() == 1);
|
|
}
|
|
|
|
// --- relative-paths-only invariant -------------------------------------------
|
|
|
|
static void testRejectsEmptyAndAbsolute() {
|
|
OwnedFileManifest m;
|
|
CHECK(m.add("") == ManifestAddResult::RejectedEmptyPath);
|
|
// Every absolute form bank_model rejects, the manifest rejects too.
|
|
CHECK(m.add("/abs/take.wav") == ManifestAddResult::RejectedAbsolutePath); // POSIX root
|
|
CHECK(m.add("\\\\host\\share\\x.wav") == ManifestAddResult::RejectedAbsolutePath); /* UNC */
|
|
CHECK(m.add("C:/bank/x.wav") == ManifestAddResult::RejectedAbsolutePath); // Win drive /
|
|
CHECK(m.add("C:\\bank\\x.wav") == ManifestAddResult::RejectedAbsolutePath); /* Win drive backslash */
|
|
CHECK(m.add("C:x.wav") == ManifestAddResult::RejectedAbsolutePath); // drive-relative
|
|
// A rejected add never mutates.
|
|
CHECK(m.empty());
|
|
CHECK(!m.contains("/abs/take.wav"));
|
|
}
|
|
|
|
// --- JSON round-trip ---------------------------------------------------------
|
|
|
|
static void testRoundTrip() {
|
|
OwnedFileManifest m;
|
|
m.add("reasampler_bank/one.wav");
|
|
m.add("reasampler_bank/two.wav");
|
|
m.add("reasampler_bank/three.wav");
|
|
|
|
const std::string json = m.serialize();
|
|
auto back = OwnedFileManifest::deserialize(json);
|
|
CHECK(back.has_value());
|
|
CHECK(*back == m);
|
|
// Order + membership survive.
|
|
CHECK(back->paths().size() == 3);
|
|
CHECK(back->paths()[0] == "reasampler_bank/one.wav");
|
|
CHECK(back->paths()[2] == "reasampler_bank/three.wav");
|
|
|
|
// serialize(deserialize(serialize(x))) is stable.
|
|
CHECK(back->serialize() == json);
|
|
}
|
|
|
|
// A path carrying JSON metacharacters must survive the escape/unescape round-trip.
|
|
static void testRoundTripEscaping() {
|
|
OwnedFileManifest m;
|
|
m.add("reasampler_bank/od\"d name.wav"); // embedded quote
|
|
m.add("reasampler_bank/back\\slash.wav"); // embedded backslash
|
|
m.add("reasampler_bank/tab\tafter.wav"); // control char
|
|
|
|
auto back = OwnedFileManifest::deserialize(m.serialize());
|
|
CHECK(back.has_value());
|
|
CHECK(*back == m);
|
|
CHECK(back->contains("reasampler_bank/od\"d name.wav"));
|
|
CHECK(back->contains("reasampler_bank/back\\slash.wav"));
|
|
CHECK(back->contains("reasampler_bank/tab\tafter.wav"));
|
|
}
|
|
|
|
// --- malformed / tolerant parse ----------------------------------------------
|
|
|
|
static void testMalformedParse() {
|
|
// The persist shell's warn+fallback hinges on nullopt for a corrupt blob.
|
|
CHECK(!OwnedFileManifest::deserialize("").has_value()); // empty string
|
|
CHECK(!OwnedFileManifest::deserialize("not json").has_value());
|
|
CHECK(!OwnedFileManifest::deserialize("{\"owned\":[").has_value()); // unterminated array
|
|
CHECK(!OwnedFileManifest::deserialize("{\"owned\":[1,2]}").has_value()); // non-string element
|
|
CHECK(!OwnedFileManifest::deserialize("{\"owned\":\"x\"}").has_value()); // wrong value type
|
|
|
|
// An explicit empty array parses to an empty manifest.
|
|
auto empty = OwnedFileManifest::deserialize("{\"owned\":[]}");
|
|
CHECK(empty.has_value());
|
|
CHECK(empty->empty());
|
|
|
|
// An unknown sibling key is tolerated (forward-compat) — the owned array still loads.
|
|
auto fwd = OwnedFileManifest::deserialize(
|
|
"{\"future\":{\"nested\":[1,2]},\"owned\":[\"reasampler_bank/x.wav\"]}");
|
|
CHECK(fwd.has_value());
|
|
CHECK(fwd->size() == 1);
|
|
CHECK(fwd->contains("reasampler_bank/x.wav"));
|
|
|
|
// A stored blob cannot smuggle a duplicate or absolute path past the load-time
|
|
// invariant re-assertion (deserialize routes each element through add()).
|
|
auto dupe = OwnedFileManifest::deserialize(
|
|
"{\"owned\":[\"reasampler_bank/x.wav\",\"reasampler_bank/x.wav\"]}");
|
|
CHECK(dupe.has_value());
|
|
CHECK(dupe->size() == 1);
|
|
|
|
auto absolute = OwnedFileManifest::deserialize(
|
|
"{\"owned\":[\"reasampler_bank/ok.wav\",\"/etc/evil.wav\"]}");
|
|
CHECK(absolute.has_value());
|
|
CHECK(absolute->size() == 1);
|
|
CHECK(absolute->contains("reasampler_bank/ok.wav"));
|
|
CHECK(!absolute->contains("/etc/evil.wav"));
|
|
}
|
|
|
|
int main() {
|
|
testEmptyManifest();
|
|
testAddAndContains();
|
|
testDedupRepeatedAdds();
|
|
testRejectsEmptyAndAbsolute();
|
|
testRoundTrip();
|
|
testRoundTripEscaping();
|
|
testMalformedParse();
|
|
|
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|