// Standalone tests for reasampler::sample_usage — no REAPER, no framework. // The pS-usage seam: instances publish held captures; the extension folds live // instances' holds into the prune's `referenced` set. Tested hard here because this is // the prune-protection guarantee: a capture held by a live instance must be IMPOSSIBLE // to prune (the composed proof at the bottom links prune_reconcile and shows // pruneOrphans can never emit a held path), while a stale record from a deleted // instance must NOT permanently block reclaim (the liveness fold). // // Covers: wire round-trip (empty / adversarial bytes), malformed -> nullopt, the // publish plan's four branches (fresh key / clean replace / same-track union / // cross-track re-mint) + skipWrite idempotence, the liveness fold (live, dead-track, // empty-guid fallback, de-dup), and the composed pruneOrphans exclusion proof. #include "../src/sample_usage.h" #include #include #include #include #include "../src/prune_reconcile.h" // mergeReferenced + pruneOrphans (composed proof) using namespace reasampler; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) namespace { UsageRecord makeRecord(const std::string& trackGuid, std::vector holds) { UsageRecord r; r.trackGuid = trackGuid; r.holds = std::move(holds); return r; } } // namespace // --- wire round-trip ---------------------------------------------------------- static void testRoundTrip() { const UsageRecord rec = makeRecord( "{12345678-1234-1234-1234-1234567890AB}", {UsageHold{"cap-1700-kick", "reasampler_bank/kick.wav"}, UsageHold{"cap-1701-snare", "reasampler_bank/snare.wav"}}); const std::string wire = encodeUsageRecord(rec); auto back = decodeUsageRecord(wire); CHECK(back.has_value()); CHECK(*back == rec); CHECK(encodeUsageRecord(*back) == wire); // deterministic re-encode } static void testRoundTripEmptyHoldsAndEmptyGuid() { // An empty-holds record is LEGAL (an instance releasing everything it held), and an // empty trackGuid is legal (no track context at publish -> any-instance fallback). const UsageRecord rec = makeRecord("", {}); auto back = decodeUsageRecord(encodeUsageRecord(rec)); CHECK(back.has_value()); CHECK(back->trackGuid.empty()); CHECK(back->holds.empty()); } static void testRoundTripAdversarialBytes() { // Ids/paths carrying the wire's own metacharacters must survive whole (the whole // point of length-prefixing): ':' delimiters, digits, the magic tag itself. const UsageRecord rec = makeRecord( "12:34:guid-with-colons", {UsageHold{"rsusage1-lookalike", "path with spaces/and:colons/7:x.wav"}}); auto back = decodeUsageRecord(encodeUsageRecord(rec)); CHECK(back.has_value()); CHECK(*back == rec); } static void testDecodeMalformed() { CHECK(!decodeUsageRecord("").has_value()); CHECK(!decodeUsageRecord("garbage").has_value()); CHECK(!decodeUsageRecord("rsusage1").has_value()); // truncated after magic CHECK(!decodeUsageRecord("rsusage2" "0:1:0").has_value()); // wrong magic version // Truncated mid-holds: claims 2 holds, carries 1. UsageRecord one = makeRecord("{G}", {UsageHold{"a", "p.wav"}}); std::string wire = encodeUsageRecord(one); // Rewrite the count field "1:1" -> "1:2" (count is the 2nd field: len 1, value '1'). const std::string needle = "1:1"; // count field for one hold const std::size_t pos = wire.find(needle, std::string("rsusage1").size() + 4); CHECK(pos != std::string::npos); wire[pos + 2] = '2'; CHECK(!decodeUsageRecord(wire).has_value()); // Trailing garbage after a complete record -> reject whole. CHECK(!decodeUsageRecord(encodeUsageRecord(one) + "x").has_value()); } // --- publish plan -------------------------------------------------------------- static void testPlanFreshKey() { const UsageRecord mine = makeRecord("{T1}", {UsageHold{"a", "p.wav"}}); const UsagePublishPlan plan = planUsagePublish(std::nullopt, "", mine); CHECK(!plan.remint); CHECK(!plan.skipWrite); CHECK(plan.wire == encodeUsageRecord(mine)); } static void testPlanCleanReplaceAndSkip() { // The key holds exactly what this lifetime wrote -> clean replace; released holds drop. const UsageRecord prev = makeRecord("{T1}", {UsageHold{"a", "pa.wav"}, UsageHold{"b", "pb.wav"}}); const std::string prevWire = encodeUsageRecord(prev); const UsageRecord mine = makeRecord("{T1}", {UsageHold{"a", "pa.wav"}}); const UsagePublishPlan plan = planUsagePublish(prevWire, prevWire, mine); CHECK(!plan.remint); CHECK(!plan.skipWrite); auto back = decodeUsageRecord(plan.wire); CHECK(back.has_value()); CHECK(back->holds.size() == 1); // 'b' genuinely released — NOT unioned back in // Unchanged play-set -> byte-identical write -> skip (idle reload tick). const UsagePublishPlan idle = planUsagePublish(prevWire, prevWire, prev); CHECK(idle.skipWrite); CHECK(!idle.remint); } static void testPlanSameTrackUnion() { // First publish of a lifetime (lastPublished empty) over a same-track existing value: // my own last-session record OR a same-track copy-sibling — either way UNION, never // drop (the fail-safe direction; a sibling's holds must survive my write). const UsageRecord theirs = makeRecord("{T1}", {UsageHold{"a", "pa.wav"}}); const UsageRecord mine = makeRecord("{T1}", {UsageHold{"b", "pb.wav"}, UsageHold{"a", "pa.wav"}}); const UsagePublishPlan plan = planUsagePublish(encodeUsageRecord(theirs), "", mine); CHECK(!plan.remint); auto back = decodeUsageRecord(plan.wire); CHECK(back.has_value()); CHECK(back->trackGuid == "{T1}"); CHECK(back->holds.size() == 2); // a (existing-first) + b, de-duped CHECK(back->holds[0].sampleId == "a"); CHECK(back->holds[1].sampleId == "b"); } static void testPlanCrossTrackRemint() { // The key holds a foreign record from ANOTHER track: this state was cloned there // (FX copy / track duplication) — take a fresh identity, leave theirs untouched. const UsageRecord theirs = makeRecord("{T1}", {UsageHold{"a", "pa.wav"}}); const UsageRecord mine = makeRecord("{T2}", {UsageHold{"a", "pa.wav"}}); const UsagePublishPlan plan = planUsagePublish(encodeUsageRecord(theirs), "", mine); CHECK(plan.remint); CHECK(plan.wire == encodeUsageRecord(mine)); // written under the NEW key } static void testPlanUndecodableExisting() { // An undecodable existing value protects nothing — overwrite with mine. const UsageRecord mine = makeRecord("{T1}", {UsageHold{"a", "pa.wav"}}); const UsagePublishPlan plan = planUsagePublish(std::string("corrupt"), "", mine); CHECK(!plan.remint); CHECK(plan.wire == encodeUsageRecord(mine)); } // --- liveness fold --------------------------------------------------------------- static void testHeldPathsLiveness() { const std::vector records = { makeRecord("{LIVE}", {UsageHold{"a", "pa.wav"}}), makeRecord("{DEAD}", {UsageHold{"b", "pb.wav"}}), // deleted track/instance makeRecord("", {UsageHold{"c", "pc.wav"}}), // no track context }; const std::unordered_set live = {"{LIVE}"}; // Live-track record counts; dead-track record is EXCLUDED (no stale false-protect); // empty-guid record counts while ANY instance lives (fail-safe fallback). const std::vector withAny = usageHeldPaths(records, live, true); CHECK(withAny.size() == 2); CHECK(withAny[0] == "pa.wav"); CHECK(withAny[1] == "pc.wav"); // No instance anywhere -> empty-guid fallback closes too; only live-track survives. const std::vector noAny = usageHeldPaths(records, live, false); CHECK(noAny.size() == 1); CHECK(noAny[0] == "pa.wav"); // Zero live instances at all -> nothing protected (a project whose instances were // all deleted cannot be permanently blocked by leftover records). const std::vector none = usageHeldPaths(records, std::unordered_set{}, false); CHECK(none.empty()); } static void testHeldPathsDedupAndEmptyPathSkip() { const std::vector records = { makeRecord("{T}", {UsageHold{"a", "shared.wav"}, UsageHold{"x", ""}}), makeRecord("{T}", {UsageHold{"b", "shared.wav"}, UsageHold{"c", "own.wav"}}), }; const std::unordered_set live = {"{T}"}; const std::vector paths = usageHeldPaths(records, live, true); CHECK(paths.size() == 2); // shared.wav de-duped across records; empty path skipped CHECK(paths[0] == "shared.wav"); CHECK(paths[1] == "own.wav"); } // --- the composed prune-protection proof ----------------------------------------- // The definition-of-done property at the pure layer: a capture held by a live instance // lands in the referenced union, and pruneOrphans can NEVER emit it — even when the // bank no longer references it (deleted from the bank while the instance kept its ref) // and it is owned + present (the exact preconditions under which it WOULD be reclaimed). static void testInstanceHoldMakesPathUnprunable() { const std::vector present = {"held.wav", "orphan.wav"}; const std::vector owned = {"held.wav", "orphan.wav"}; const std::vector bankRefs = {}; // bank does NOT reference either // Without instance usage both are orphans (the pre-pS-usage behavior). CHECK(pruneOrphans(present, bankRefs, owned).size() == 2); // A live instance holds held.wav -> the union protects it; orphan.wav still reclaims. const std::vector records = { makeRecord("{T}", {UsageHold{"id-held", "held.wav"}})}; const std::unordered_set live = {"{T}"}; const std::vector referenced = mergeReferenced(bankRefs, usageHeldPaths(records, live, true)); const std::vector orphans = pruneOrphans(present, referenced, owned); CHECK(orphans.size() == 1); CHECK(orphans[0] == "orphan.wav"); // The instance (and its track) deleted -> the record no longer counts -> held.wav // is reclaimable again (no permanent stale-key block). const std::vector refsAfterDelete = mergeReferenced( bankRefs, usageHeldPaths(records, std::unordered_set{}, false)); CHECK(pruneOrphans(present, refsAfterDelete, owned).size() == 2); } static void testMergeReferencedOrderAndDedup() { const std::vector a = {"p1.wav", "p2.wav"}; const std::vector b = {"p2.wav", "p3.wav", "p1.wav"}; const std::vector merged = mergeReferenced(a, b); CHECK(merged.size() == 3); CHECK(merged[0] == "p1.wav"); CHECK(merged[1] == "p2.wav"); CHECK(merged[2] == "p3.wav"); CHECK(mergeReferenced({}, {}).empty()); } int main() { testRoundTrip(); testRoundTripEmptyHoldsAndEmptyGuid(); testRoundTripAdversarialBytes(); testDecodeMalformed(); testPlanFreshKey(); testPlanCleanReplaceAndSkip(); testPlanSameTrackUnion(); testPlanCrossTrackRemint(); testPlanUndecodableExisting(); testHeldPathsLiveness(); testHeldPathsDedupAndEmptyPathSkip(); testInstanceHoldMakesPathUnprunable(); testMergeReferencedOrderAndDedup(); if (g_fail == 0) { std::printf("sample_usage_tests: all tests passed\n"); return 0; } std::printf("sample_usage_tests: %d FAILURES\n", g_fail); return 1; }