Merge Ε-W1-T3: OriginKind::PackageImport for the file-tracking ledger
This commit is contained in:
@@ -38,6 +38,7 @@ OriginKind kindFromInt(int v) {
|
|||||||
case 2: return OriginKind::Ingest;
|
case 2: return OriginKind::Ingest;
|
||||||
case 3: return OriginKind::Recapture;
|
case 3: return OriginKind::Recapture;
|
||||||
case 4: return OriginKind::Resample;
|
case 4: return OriginKind::Resample;
|
||||||
|
case 5: return OriginKind::PackageImport;
|
||||||
default: return OriginKind::Unknown;
|
default: return OriginKind::Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ enum class OriginKind {
|
|||||||
Ingest = 2,
|
Ingest = 2,
|
||||||
Recapture = 3, // regenerated in place from its recorded source recipe
|
Recapture = 3, // regenerated in place from its recorded source recipe
|
||||||
Resample = 4, // baked from an instrument's own processing chain
|
Resample = 4, // baked from an instrument's own processing chain
|
||||||
|
PackageImport = 5, // package-sourced vs Ingest's user-picked; unrecoverable once merged
|
||||||
};
|
};
|
||||||
|
|
||||||
// One system-created file's birth record. `relativePath` is the key and is ALWAYS
|
// One system-created file's birth record. `relativePath` is the key and is ALWAYS
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
// Standalone tests for reasampler::tracking::OriginLedger — no REAPER, no framework.
|
// Standalone tests for reasampler::tracking::OriginLedger — no REAPER, no framework.
|
||||||
//
|
//
|
||||||
// The record family behind file tracking. Covers: the relative-paths-only invariant,
|
// Covers: relative-paths-only, exact-string ownership, dedup, insertion order, the
|
||||||
// exact-string ownership, dedup, insertion order, the JSON round-trip (incl. golden
|
// JSON round-trip (incl. golden bytes over every persisted enum value), the append-a-
|
||||||
// byte literals over every persisted enum value), the no-backfill rule, the legacy
|
// kind rules, the no-backfill rule, the legacy path-only lift, and the Fresh / Loaded
|
||||||
// path-only lift, and the Fresh / Loaded / Unreadable / FutureVersion classification
|
// / Unreadable / FutureVersion classification.
|
||||||
// that keeps never-recorded apart from the two degraded states.
|
|
||||||
|
|
||||||
#include "../src/core/tracking/origin_ledger.h"
|
#include "../src/core/tracking/origin_ledger.h"
|
||||||
|
|
||||||
@@ -162,13 +161,15 @@ static void testSerializeGoldenLiteralPinsEveryPersistedKind() {
|
|||||||
l.record(rec("bank/ingest.wav", OriginKind::Ingest, "S-b"));
|
l.record(rec("bank/ingest.wav", OriginKind::Ingest, "S-b"));
|
||||||
l.record(rec("bank/recapture.wav", OriginKind::Recapture, "S-c", "S-a"));
|
l.record(rec("bank/recapture.wav", OriginKind::Recapture, "S-c", "S-a"));
|
||||||
l.record(rec("bank/resample.wav", OriginKind::Resample, "S-d", "S-a"));
|
l.record(rec("bank/resample.wav", OriginKind::Resample, "S-d", "S-a"));
|
||||||
|
l.record(rec("bank/import.wav", OriginKind::PackageImport, "S-e"));
|
||||||
const std::string expected =
|
const std::string expected =
|
||||||
"{\"v\":2,\"records\":["
|
"{\"v\":2,\"records\":["
|
||||||
"{\"path\":\"bank/unknown.wav\",\"kind\":0,\"sample\":\"\",\"parent\":\"\"},"
|
"{\"path\":\"bank/unknown.wav\",\"kind\":0,\"sample\":\"\",\"parent\":\"\"},"
|
||||||
"{\"path\":\"bank/capture.wav\",\"kind\":1,\"sample\":\"S-a\",\"parent\":\"\"},"
|
"{\"path\":\"bank/capture.wav\",\"kind\":1,\"sample\":\"S-a\",\"parent\":\"\"},"
|
||||||
"{\"path\":\"bank/ingest.wav\",\"kind\":2,\"sample\":\"S-b\",\"parent\":\"\"},"
|
"{\"path\":\"bank/ingest.wav\",\"kind\":2,\"sample\":\"S-b\",\"parent\":\"\"},"
|
||||||
"{\"path\":\"bank/recapture.wav\",\"kind\":3,\"sample\":\"S-c\",\"parent\":\"S-a\"},"
|
"{\"path\":\"bank/recapture.wav\",\"kind\":3,\"sample\":\"S-c\",\"parent\":\"S-a\"},"
|
||||||
"{\"path\":\"bank/resample.wav\",\"kind\":4,\"sample\":\"S-d\",\"parent\":\"S-a\"}"
|
"{\"path\":\"bank/resample.wav\",\"kind\":4,\"sample\":\"S-d\",\"parent\":\"S-a\"},"
|
||||||
|
"{\"path\":\"bank/import.wav\",\"kind\":5,\"sample\":\"S-e\",\"parent\":\"\"}"
|
||||||
"]}";
|
"]}";
|
||||||
CHECK(l.serialize() == expected);
|
CHECK(l.serialize() == expected);
|
||||||
|
|
||||||
@@ -180,6 +181,71 @@ static void testSerializeGoldenLiteralPinsEveryPersistedKind() {
|
|||||||
CHECK(back->find("bank/ingest.wav")->kind == OriginKind::Ingest);
|
CHECK(back->find("bank/ingest.wav")->kind == OriginKind::Ingest);
|
||||||
CHECK(back->find("bank/recapture.wav")->kind == OriginKind::Recapture);
|
CHECK(back->find("bank/recapture.wav")->kind == OriginKind::Recapture);
|
||||||
CHECK(back->find("bank/resample.wav")->kind == OriginKind::Resample);
|
CHECK(back->find("bank/resample.wav")->kind == OriginKind::Resample);
|
||||||
|
CHECK(back->find("bank/import.wav")->kind == OriginKind::PackageImport);
|
||||||
|
CHECK(*back == l); // every field, not just the kind, survives the trip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pins the emitted "v" byte, not the internal constant — an older build reads bytes.
|
||||||
|
static void testAppendingAKindDoesNotMoveTheDocumentVersion() {
|
||||||
|
OriginLedger l;
|
||||||
|
l.record(rec("bank/import.wav", OriginKind::PackageImport, "S-e"));
|
||||||
|
const std::string json = l.serialize();
|
||||||
|
CHECK(json.rfind("{\"v\":2,", 0) == 0);
|
||||||
|
CHECK(loadLedger(json).status == LedgerStatus::Loaded);
|
||||||
|
|
||||||
|
// The ceiling did not move with it: v3 is still a future document shape.
|
||||||
|
CHECK(loadLedger("{\"v\":3,\"records\":[]}").status == LedgerStatus::FutureVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pins three unrecognized kind values (future, far-future, negative) all landing on
|
||||||
|
// Unknown with the ledger still Loaded and the path still owned.
|
||||||
|
static void testUnknownKindDegradesWithoutBlockingTheLedger() {
|
||||||
|
const std::string blob =
|
||||||
|
"{\"v\":2,\"records\":["
|
||||||
|
"{\"path\":\"bank/next.wav\",\"kind\":6,\"sample\":\"S-1\",\"parent\":\"\"},"
|
||||||
|
"{\"path\":\"bank/far.wav\",\"kind\":99,\"sample\":\"S-2\",\"parent\":\"\"},"
|
||||||
|
"{\"path\":\"bank/bogus.wav\",\"kind\":-1,\"sample\":\"S-3\",\"parent\":\"\"}]}";
|
||||||
|
|
||||||
|
const LedgerLoad load = loadLedger(blob);
|
||||||
|
CHECK(load.status == LedgerStatus::Loaded);
|
||||||
|
CHECK(!ledgerDegraded(load.status));
|
||||||
|
CHECK(load.ledger.size() == 3);
|
||||||
|
CHECK(load.ledger.find("bank/next.wav")->kind == OriginKind::Unknown);
|
||||||
|
CHECK(load.ledger.find("bank/far.wav")->kind == OriginKind::Unknown);
|
||||||
|
CHECK(load.ledger.find("bank/bogus.wav")->kind == OriginKind::Unknown);
|
||||||
|
|
||||||
|
// Every path still owned, in order — the protection prune reads is untouched.
|
||||||
|
const std::vector<std::string> paths = load.ledger.ownedPaths();
|
||||||
|
CHECK(paths.size() == 3);
|
||||||
|
CHECK(paths[0] == "bank/next.wav");
|
||||||
|
CHECK(paths[1] == "bank/far.wav");
|
||||||
|
CHECK(paths[2] == "bank/bogus.wav");
|
||||||
|
|
||||||
|
// The rest of the record survives the degrade; only the kind is lost.
|
||||||
|
CHECK(load.ledger.find("bank/next.wav")->sampleId == "S-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ownedPaths() is the ONLY thing pruneProtection reads out of a ledger, and it is
|
||||||
|
// kind-blind: two ledgers agreeing on paths and differing on every kind, new value
|
||||||
|
// included, yield identical protection input. Adding a kind therefore cannot change
|
||||||
|
// a prune decision for any existing kind.
|
||||||
|
static void testOwnedPathsAreKindIndependent() {
|
||||||
|
const OriginKind kinds[] = {OriginKind::Unknown, OriginKind::Capture,
|
||||||
|
OriginKind::Ingest, OriginKind::Recapture,
|
||||||
|
OriginKind::Resample, OriginKind::PackageImport};
|
||||||
|
const std::size_t n = sizeof(kinds) / sizeof(kinds[0]);
|
||||||
|
|
||||||
|
OriginLedger baseline;
|
||||||
|
OriginLedger varied;
|
||||||
|
for (std::size_t i = 0; i < n; ++i) {
|
||||||
|
const std::string path = "bank/f" + std::to_string(i) + ".wav";
|
||||||
|
baseline.record(rec(path, OriginKind::Unknown, "S"));
|
||||||
|
varied.record(rec(path, kinds[i], "S"));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(baseline.ownedPaths() == varied.ownedPaths());
|
||||||
|
CHECK(baseline.ownedPaths().size() == n);
|
||||||
|
CHECK(!(baseline == varied)); // the ledgers really do differ, kind by kind
|
||||||
}
|
}
|
||||||
|
|
||||||
// contains() is an EXACT-string predicate, never a prefix or substring match — the
|
// contains() is an EXACT-string predicate, never a prefix or substring match — the
|
||||||
@@ -383,6 +449,9 @@ int main() {
|
|||||||
testRoundTripWithLineage();
|
testRoundTripWithLineage();
|
||||||
testRoundTripWithJsonMetacharacters();
|
testRoundTripWithJsonMetacharacters();
|
||||||
testSerializeGoldenLiteralPinsEveryPersistedKind();
|
testSerializeGoldenLiteralPinsEveryPersistedKind();
|
||||||
|
testAppendingAKindDoesNotMoveTheDocumentVersion();
|
||||||
|
testUnknownKindDegradesWithoutBlockingTheLedger();
|
||||||
|
testOwnedPathsAreKindIndependent();
|
||||||
testMalformedParsesToNullopt();
|
testMalformedParsesToNullopt();
|
||||||
testTrailingGarbageIsRejected();
|
testTrailingGarbageIsRejected();
|
||||||
testLegacyShapeTypeErrorsAreRejectedButEmptyIsValid();
|
testLegacyShapeTypeErrorsAreRejectedButEmptyIsValid();
|
||||||
|
|||||||
Reference in New Issue
Block a user