Merge R1: prune-reconcile pure core
This commit is contained in:
@@ -214,6 +214,18 @@ target_link_libraries(bank_book PUBLIC bank_model)
|
||||
add_library(owned_manifest STATIC src/owned_manifest.cpp)
|
||||
target_include_directories(owned_manifest PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2g''') Pure prune_reconcile library — NO REAPER, NO SWELL, NO filesystem. The
|
||||
# Phase R (Reclaim) Wave-1 safety-critical decision: given the files present
|
||||
# in the bank folder, the union of files referenced across every bank, and the
|
||||
# owned-file manifest, compute the orphan set (owned ∩ present) − referenced.
|
||||
# Mirror of view_mode_model::reconcile one level down (files, not GUIDs). Small
|
||||
# pure function; R2/R3 wrap the two ends (folder enumeration + deletion) in the
|
||||
# shell. Standalone — depends only on the standard library.
|
||||
# ---------------------------------------------------------------------------
|
||||
add_library(prune_reconcile STATIC src/prune_reconcile.cpp)
|
||||
target_include_directories(prune_reconcile PUBLIC src)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2g) Pure realtime_record library — NO REAPER, NO SWELL. The M8 realtime-record
|
||||
# logic: capture scope + FX-tap point -> I_RECMODE / I_RECMODE_FLAGS values,
|
||||
@@ -338,6 +350,10 @@ add_executable(owned_manifest_tests tests/test_owned_manifest.cpp)
|
||||
target_link_libraries(owned_manifest_tests PRIVATE owned_manifest)
|
||||
add_test(NAME owned_manifest_tests COMMAND owned_manifest_tests)
|
||||
|
||||
add_executable(prune_reconcile_tests tests/test_prune_reconcile.cpp)
|
||||
target_link_libraries(prune_reconcile_tests PRIVATE prune_reconcile bank_book)
|
||||
add_test(NAME prune_reconcile_tests COMMAND prune_reconcile_tests)
|
||||
|
||||
add_executable(app_version_tests tests/test_app_version.cpp)
|
||||
target_link_libraries(app_version_tests PRIVATE app_version)
|
||||
add_test(NAME app_version_tests COMMAND app_version_tests)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <unordered_set>
|
||||
|
||||
// bank_book implementation.
|
||||
//
|
||||
@@ -321,6 +322,22 @@ bool BankBook::hashReferencedElsewhere(const std::string& hash,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> BankBook::referencedPaths() const {
|
||||
// Union across the whole book (pool first, then named banks in ordinal order —
|
||||
// banks_ is kept ordinal-sorted). De-duplicate by exact string so a file a copy
|
||||
// put in two banks appears once. Skip empty paths (they reference no file).
|
||||
std::vector<std::string> paths;
|
||||
std::unordered_set<std::string> seen;
|
||||
for (const auto& b : banks_) {
|
||||
for (const auto& s : b.index.all()) {
|
||||
if (s.relativePath.empty()) continue;
|
||||
if (seen.insert(s.relativePath).second)
|
||||
paths.push_back(s.relativePath);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// JSON — writer
|
||||
// ===========================================================================
|
||||
|
||||
@@ -220,6 +220,17 @@ public:
|
||||
bool hashReferencedElsewhere(const std::string& hash,
|
||||
const std::string& exceptBankId) const;
|
||||
|
||||
// Every project-relative file path referenced by ANY bank in the book, pool
|
||||
// included — the union across the whole book (Phase R, prune). This is the
|
||||
// safety-critical referenced-set the prune core subtracts: a file referenced by
|
||||
// any bank (INCLUDING via a copy into a second bank) appears here, so prune never
|
||||
// reclaims it. Paths are returned VERBATIM (Sample.relativePath, exact strings —
|
||||
// no normalization), first-seen order across banks in ordinal order then sample
|
||||
// insertion order, and DE-DUPLICATED (one file referenced by N banks appears
|
||||
// once). An empty relativePath is skipped (it references no file). Additive
|
||||
// read-only query; adds no mutation and no coupling to Phase R.
|
||||
std::vector<std::string> referencedPaths() const;
|
||||
|
||||
// -- Query ---------------------------------------------------------------
|
||||
|
||||
// The bank with `id`, or nullptr. Pointer invalidated by any mutating call.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "prune_reconcile.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
// prune_reconcile implementation — the one set-algebra computation, kept trivially
|
||||
// auditable: build the referenced and owned lookup sets, then walk `present` once,
|
||||
// keeping a path iff it is owned AND not referenced. Walking `present` (not owned)
|
||||
// gives the ∩-present clause for free and yields output in folder-enumeration order.
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
std::vector<std::string> pruneOrphans(const std::vector<std::string>& present,
|
||||
const std::vector<std::string>& referenced,
|
||||
const std::vector<std::string>& owned) {
|
||||
// Exact-string membership — the model's canonical relative-path comparison
|
||||
// (Sample.relativePath / OwnedFileManifest::contains). std::string hashes/compares
|
||||
// byte-for-byte, so no normalization creeps in.
|
||||
const std::unordered_set<std::string> referencedSet(referenced.begin(),
|
||||
referenced.end());
|
||||
const std::unordered_set<std::string> ownedSet(owned.begin(), owned.end());
|
||||
|
||||
std::vector<std::string> orphans;
|
||||
std::unordered_set<std::string> emitted; // de-dup repeated spellings in `present`
|
||||
|
||||
for (const std::string& path : present) {
|
||||
// (owned ∩ present) − referenced: present is the walk; owned and !referenced
|
||||
// are the two membership tests; emitted guards against a duplicate `present`.
|
||||
if (ownedSet.count(path) == 0) continue; // not our leaving — skip
|
||||
if (referencedSet.count(path) != 0) continue; // some bank references it
|
||||
if (!emitted.insert(path).second) continue; // already emitted
|
||||
orphans.push_back(path);
|
||||
}
|
||||
return orphans;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
// prune_reconcile — the pure core of Phase R (Reclaim), Wave 1. The safety-critical
|
||||
// "which files are orphans" decision, computed with NO filesystem I/O and NO REAPER
|
||||
// types. The mirror of view_mode_model's reconcile(liveGuids), one level DOWN: it
|
||||
// reconciles FILES ON DISK against REFERENCED FILES (the union across every bank),
|
||||
// where reconcile reconciled membership entries against live tracks.
|
||||
//
|
||||
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
|
||||
// vendor/ includes, NO filesystem calls. Standard library only. Unit-tested outside
|
||||
// the DAW — this is the safety-critical part (it decides which bytes get deleted in
|
||||
// R2/R3), so it is hard-tested here before any I/O exists.
|
||||
//
|
||||
// -- The one computation ------------------------------------------------------
|
||||
//
|
||||
// orphans = (owned ∩ present) − referenced
|
||||
//
|
||||
// * present — files enumerated in the resolved current bank folder (R2 shell).
|
||||
// * referenced — every project-relative path referenced by ANY bank in the book,
|
||||
// pool included (union across the whole book — see BankBook::
|
||||
// referencedPaths). A file referenced by any bank — including via a
|
||||
// COPY into a second bank — is NEVER an orphan (the prune null test).
|
||||
// * owned — the owned-file manifest: the files the bank system itself created
|
||||
// (OwnedFileManifest). A present-but-unowned (hand-dropped) file is
|
||||
// NEVER reclaimed — prune reclaims only the system's own leavings.
|
||||
//
|
||||
// The three settled guardrails fall straight out of the set algebra:
|
||||
// * ∩ present — never proposes deleting a file that is not on disk (an owned-
|
||||
// but-absent manifest entry yields no orphan, no error).
|
||||
// * ∩ owned — never a hand-dropped file (ownership attribution, fork R-D).
|
||||
// * − referenced — never a file any bank references (union safety, prune null test).
|
||||
//
|
||||
// -- Path representation: EXACT-STRING match (safety-critical) -----------------
|
||||
//
|
||||
// Every path in the model is a project-relative string compared VERBATIM: Sample.
|
||||
// relativePath, OwnedFileManifest::contains (p == relativePath), and BankIndex all
|
||||
// use raw std::string equality — no separator normalization, no case-folding, no
|
||||
// trailing-slash trimming. This core MATCHES that convention exactly: it compares
|
||||
// the raw strings the shell supplies. Feeding a consistent spelling across the three
|
||||
// inputs is the R2 shell's contract (it enumerates the folder, unions the book, and
|
||||
// reads the manifest against the SAME resolved current folder). Diverging from exact
|
||||
// match here (e.g. case-insensitive compare) would be the unsafe direction — it could
|
||||
// let one spelling of a referenced file be treated as an orphan under another.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// Computes the prune orphan set: (owned ∩ present) − referenced.
|
||||
//
|
||||
// Returns the subset of `present` that is BOTH owned AND unreferenced, in the ORDER
|
||||
// they appear in `present` (deterministic output — mirror of the insertion-order
|
||||
// determinism the index / manifest keep; the R2 dry-run reports a stable file list).
|
||||
// Duplicate spellings within `present` are de-duplicated in the result (a folder
|
||||
// enumeration yields distinct names, but the core does not rely on that).
|
||||
//
|
||||
// Pure: no I/O, no REAPER, no hidden state. All three inputs are project-relative
|
||||
// path strings, compared by exact std::string equality (see header note).
|
||||
std::vector<std::string> pruneOrphans(const std::vector<std::string>& present,
|
||||
const std::vector<std::string>& referenced,
|
||||
const std::vector<std::string>& owned);
|
||||
|
||||
} // namespace reasampler
|
||||
@@ -0,0 +1,283 @@
|
||||
// Standalone tests for reasampler::pruneOrphans (Phase R, Wave 1) and the additive
|
||||
// BankBook::referencedPaths() union query it consumes — no REAPER, no framework.
|
||||
//
|
||||
// The safety-critical computation: orphans = (owned ∩ present) − referenced. Every
|
||||
// test below would FAIL if the set algebra were wrong (a missing ∩ present, a missing
|
||||
// − referenced, or a non-union referenced-set) — see the negative assertions.
|
||||
//
|
||||
// Covers (brief-named):
|
||||
// 1. Prune null test: all present files referenced -> empty orphan set.
|
||||
// 2. Formula: orphans = (owned ∩ present) − referenced, mixed populations.
|
||||
// 3. Copied file referenced by a SECOND bank survives (union semantics).
|
||||
// 4. Present-but-unowned (hand-dropped) file is never reclaimed.
|
||||
// 5. Edge cases: empty folder, empty book/referenced-set, empty manifest.
|
||||
// 6. Owned-but-absent file (manifest entry, no file on disk) -> no orphan, no error.
|
||||
// Plus: determinism (present-order output), duplicate-`present` de-dup, exact-string
|
||||
// (non-normalizing) match, and the referencedPaths() union query directly.
|
||||
|
||||
#include "../src/bank_book.h"
|
||||
#include "../src/prune_reconcile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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)
|
||||
|
||||
// --- helpers -----------------------------------------------------------------
|
||||
|
||||
static bool contains(const std::vector<std::string>& v, const std::string& s) {
|
||||
return std::find(v.begin(), v.end(), s) != v.end();
|
||||
}
|
||||
|
||||
// A minimal Sample carrying just the fields prune cares about (id + relativePath).
|
||||
static Sample sampleAt(const std::string& id, const std::string& relPath,
|
||||
const std::string& hash = "") {
|
||||
Sample s;
|
||||
s.id = id;
|
||||
s.relativePath = relPath;
|
||||
s.contentHash = hash;
|
||||
return s;
|
||||
}
|
||||
|
||||
// --- 1. prune null test: all present referenced -> empty --------------------
|
||||
|
||||
static void testNullTestAllReferenced() {
|
||||
const std::vector<std::string> present = {"bank/a.wav", "bank/b.wav", "bank/c.wav"};
|
||||
const std::vector<std::string> owned = {"bank/a.wav", "bank/b.wav", "bank/c.wav"};
|
||||
const std::vector<std::string> referenced= {"bank/a.wav", "bank/b.wav", "bank/c.wav"};
|
||||
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
// The core invariant: a folder whose every file is referenced deletes NOTHING.
|
||||
CHECK(orphans.empty());
|
||||
}
|
||||
|
||||
// --- 2. formula: (owned ∩ present) − referenced, mixed populations ----------
|
||||
|
||||
static void testFormulaMixedPopulations() {
|
||||
// present : a b c d (on disk)
|
||||
// owned : a b d e (system created; e is owned-but-absent)
|
||||
// ref'd : a (still in an index)
|
||||
// expected orphans: owned ∩ present = {a,b,d}; minus referenced {a} = {b,d}
|
||||
// - c present but NOT owned -> excluded (hand-dropped)
|
||||
// - e owned but NOT present -> excluded (no file on disk)
|
||||
// - a owned+present but referenced -> excluded
|
||||
const std::vector<std::string> present = {"bank/a.wav", "bank/b.wav",
|
||||
"bank/c.wav", "bank/d.wav"};
|
||||
const std::vector<std::string> owned = {"bank/a.wav", "bank/b.wav",
|
||||
"bank/d.wav", "bank/e.wav"};
|
||||
const std::vector<std::string> referenced = {"bank/a.wav"};
|
||||
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
CHECK(orphans.size() == 2);
|
||||
CHECK(contains(orphans, "bank/b.wav"));
|
||||
CHECK(contains(orphans, "bank/d.wav"));
|
||||
// Negative assertions — each would fire if a clause of the formula were dropped:
|
||||
CHECK(!contains(orphans, "bank/a.wav")); // − referenced
|
||||
CHECK(!contains(orphans, "bank/c.wav")); // ∩ owned (hand-dropped)
|
||||
CHECK(!contains(orphans, "bank/e.wav")); // ∩ present (absent)
|
||||
}
|
||||
|
||||
// --- 3. copied file referenced by a second bank survives (union) ------------
|
||||
|
||||
static void testCopiedFileSurvivesViaUnion() {
|
||||
// A file present + owned, whose ONLY index reference lives in a second (non-active)
|
||||
// bank via a copy. The union across the whole book must keep it out of the orphan
|
||||
// set. Built through the real BankBook so referencedPaths()'s union is exercised.
|
||||
BankBook book;
|
||||
book.createBank("b1", "Drums");
|
||||
// Add to the pool, then copy into b1 (same file referenced by two banks).
|
||||
book.index(std::string(kPoolBankId))->add(sampleAt("s1", "bank/shared.wav", "h1"));
|
||||
CHECK(book.copySample("s1", kPoolBankId, "b1") == TransferResult::Copied);
|
||||
|
||||
const auto referenced = book.referencedPaths();
|
||||
// Union de-dups: one path though two banks reference it.
|
||||
CHECK(referenced.size() == 1);
|
||||
CHECK(referenced[0] == "bank/shared.wav");
|
||||
|
||||
const std::vector<std::string> present = {"bank/shared.wav"};
|
||||
const std::vector<std::string> owned = {"bank/shared.wav"};
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
// Referenced by b1 (the copy) -> never an orphan even if the pool later dropped it.
|
||||
CHECK(orphans.empty());
|
||||
}
|
||||
|
||||
// A sharper union test: remove the file from the pool but keep the copy in b1. The
|
||||
// file must STILL survive (referenced by b1 alone) — the whole point of union safety.
|
||||
static void testUnionSurvivesWhenOnlySecondBankReferences() {
|
||||
BankBook book;
|
||||
book.createBank("b1", "Drums");
|
||||
book.index(std::string(kPoolBankId))->add(sampleAt("s1", "bank/shared.wav", "h1"));
|
||||
CHECK(book.copySample("s1", kPoolBankId, "b1") == TransferResult::Copied);
|
||||
// Drop the pool's reference; b1 still references the file.
|
||||
CHECK(book.removeSample("s1", kPoolBankId) == RemoveResult::Removed);
|
||||
|
||||
const auto referenced = book.referencedPaths();
|
||||
CHECK(referenced.size() == 1);
|
||||
CHECK(contains(referenced, "bank/shared.wav"));
|
||||
|
||||
const auto orphans = pruneOrphans({"bank/shared.wav"}, referenced, {"bank/shared.wav"});
|
||||
CHECK(orphans.empty()); // still referenced by b1 -> not reclaimable
|
||||
}
|
||||
|
||||
// --- 4. present-but-unowned (hand-dropped) file never reclaimed -------------
|
||||
|
||||
static void testHandDroppedNeverReclaimed() {
|
||||
// A file on disk that no index references AND the manifest does not own. It is a
|
||||
// hand-dropped file — prune must never reclaim it (ownership attribution, fork R-D).
|
||||
const std::vector<std::string> present = {"bank/user_drop.wav", "bank/ours.wav"};
|
||||
const std::vector<std::string> owned = {"bank/ours.wav"}; // NOT user_drop
|
||||
const std::vector<std::string> referenced = {}; // neither referenced
|
||||
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
CHECK(orphans.size() == 1);
|
||||
CHECK(contains(orphans, "bank/ours.wav")); // ours, unreferenced -> orphan
|
||||
CHECK(!contains(orphans, "bank/user_drop.wav")); // hand-dropped -> protected
|
||||
}
|
||||
|
||||
// --- 5. edge cases: empty folder / book / manifest --------------------------
|
||||
|
||||
static void testEmptyFolder() {
|
||||
// No files present -> nothing to reclaim regardless of owned/referenced.
|
||||
const auto orphans = pruneOrphans({}, {"bank/a.wav"}, {"bank/a.wav"});
|
||||
CHECK(orphans.empty());
|
||||
}
|
||||
|
||||
static void testEmptyReferencedSet() {
|
||||
// Empty book (nothing referenced): every present ∩ owned file is an orphan.
|
||||
const std::vector<std::string> present = {"bank/a.wav", "bank/b.wav"};
|
||||
const std::vector<std::string> owned = {"bank/a.wav", "bank/b.wav"};
|
||||
const auto orphans = pruneOrphans(present, /*referenced*/ {}, owned);
|
||||
CHECK(orphans.size() == 2);
|
||||
CHECK(contains(orphans, "bank/a.wav"));
|
||||
CHECK(contains(orphans, "bank/b.wav"));
|
||||
}
|
||||
|
||||
static void testEmptyManifest() {
|
||||
// Empty manifest (owns nothing): nothing is reclaimable even if present+unreferenced.
|
||||
const std::vector<std::string> present = {"bank/a.wav", "bank/b.wav"};
|
||||
const std::vector<std::string> referenced = {};
|
||||
const auto orphans = pruneOrphans(present, referenced, /*owned*/ {});
|
||||
CHECK(orphans.empty());
|
||||
}
|
||||
|
||||
static void testAllEmpty() {
|
||||
const auto orphans = pruneOrphans({}, {}, {});
|
||||
CHECK(orphans.empty());
|
||||
}
|
||||
|
||||
// --- 6. owned-but-absent file -> no orphan, no error ------------------------
|
||||
|
||||
static void testOwnedButAbsentNoOrphan() {
|
||||
// A manifest entry whose file is NOT on disk (e.g. deleted out-of-band). It must
|
||||
// yield no orphan entry (∩ present excludes it) and no error/crash.
|
||||
const std::vector<std::string> present = {"bank/here.wav"};
|
||||
const std::vector<std::string> owned = {"bank/here.wav", "bank/gone.wav"};
|
||||
const std::vector<std::string> referenced = {};
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
CHECK(orphans.size() == 1);
|
||||
CHECK(contains(orphans, "bank/here.wav"));
|
||||
CHECK(!contains(orphans, "bank/gone.wav")); // owned but absent -> not an orphan
|
||||
}
|
||||
|
||||
// --- determinism + duplicate-present + exact-string match -------------------
|
||||
|
||||
static void testOutputPreservesPresentOrder() {
|
||||
// Output order follows `present` order (deterministic dry-run file list), NOT the
|
||||
// owned/referenced order.
|
||||
const std::vector<std::string> present = {"bank/z.wav", "bank/y.wav", "bank/x.wav"};
|
||||
const std::vector<std::string> owned = {"bank/x.wav", "bank/y.wav", "bank/z.wav"};
|
||||
const std::vector<std::string> referenced = {};
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
CHECK(orphans.size() == 3);
|
||||
CHECK(orphans[0] == "bank/z.wav");
|
||||
CHECK(orphans[1] == "bank/y.wav");
|
||||
CHECK(orphans[2] == "bank/x.wav");
|
||||
}
|
||||
|
||||
static void testDuplicatePresentDeduped() {
|
||||
// A defensive property: a duplicated `present` spelling appears once in the output.
|
||||
const std::vector<std::string> present = {"bank/a.wav", "bank/a.wav"};
|
||||
const std::vector<std::string> owned = {"bank/a.wav"};
|
||||
const auto orphans = pruneOrphans(present, /*referenced*/ {}, owned);
|
||||
CHECK(orphans.size() == 1);
|
||||
CHECK(orphans[0] == "bank/a.wav");
|
||||
}
|
||||
|
||||
static void testExactStringMatchNotNormalized() {
|
||||
// Safety-critical: the core matches VERBATIM (mirror of the model's exact-string
|
||||
// convention). A referenced file spelled with backslashes is a DIFFERENT string
|
||||
// from the forward-slash present spelling — the core does NOT normalize them to
|
||||
// equal. This documents the invariant: consistent spelling is the R2 shell's
|
||||
// contract. If the core silently normalized, this would (wrongly) treat the file
|
||||
// as referenced and the assertion below would fail.
|
||||
const std::vector<std::string> present = {"bank/a.wav"};
|
||||
const std::vector<std::string> owned = {"bank/a.wav"};
|
||||
const std::vector<std::string> referenced = {"bank\\a.wav"}; // different spelling
|
||||
const auto orphans = pruneOrphans(present, referenced, owned);
|
||||
CHECK(orphans.size() == 1); // not matched -> still an orphan
|
||||
CHECK(orphans[0] == "bank/a.wav");
|
||||
}
|
||||
|
||||
// --- BankBook::referencedPaths() union query directly -----------------------
|
||||
|
||||
static void testReferencedPathsUnionAcrossBanksAndPool() {
|
||||
BankBook book;
|
||||
book.createBank("b1", "Drums");
|
||||
book.createBank("b2", "Bass");
|
||||
book.index(std::string(kPoolBankId))->add(sampleAt("p1", "bank/pool.wav", "hp"));
|
||||
book.index("b1")->add(sampleAt("d1", "bank/drum.wav", "hd"));
|
||||
book.index("b2")->add(sampleAt("s1", "bank/bass.wav", "hb"));
|
||||
|
||||
const auto refs = book.referencedPaths();
|
||||
// Union includes the pool AND every named bank.
|
||||
CHECK(refs.size() == 3);
|
||||
CHECK(contains(refs, "bank/pool.wav"));
|
||||
CHECK(contains(refs, "bank/drum.wav"));
|
||||
CHECK(contains(refs, "bank/bass.wav"));
|
||||
// Pool-first ordinal order.
|
||||
CHECK(refs[0] == "bank/pool.wav");
|
||||
}
|
||||
|
||||
static void testReferencedPathsEmptyBook() {
|
||||
BankBook book; // pool only, no samples
|
||||
CHECK(book.referencedPaths().empty());
|
||||
}
|
||||
|
||||
static void testReferencedPathsSkipsEmptyPath() {
|
||||
// A sample with an empty relativePath references no file — it must not appear.
|
||||
BankBook book;
|
||||
book.index(std::string(kPoolBankId))->add(sampleAt("p1", "", "hp"));
|
||||
book.index(std::string(kPoolBankId))->add(sampleAt("p2", "bank/real.wav", "hr"));
|
||||
const auto refs = book.referencedPaths();
|
||||
CHECK(refs.size() == 1);
|
||||
CHECK(refs[0] == "bank/real.wav");
|
||||
}
|
||||
|
||||
int main() {
|
||||
testNullTestAllReferenced();
|
||||
testFormulaMixedPopulations();
|
||||
testCopiedFileSurvivesViaUnion();
|
||||
testUnionSurvivesWhenOnlySecondBankReferences();
|
||||
testHandDroppedNeverReclaimed();
|
||||
testEmptyFolder();
|
||||
testEmptyReferencedSet();
|
||||
testEmptyManifest();
|
||||
testAllEmpty();
|
||||
testOwnedButAbsentNoOrphan();
|
||||
testOutputPreservesPresentOrder();
|
||||
testDuplicatePresentDeduped();
|
||||
testExactStringMatchNotNormalized();
|
||||
testReferencedPathsUnionAcrossBanksAndPool();
|
||||
testReferencedPathsEmptyBook();
|
||||
testReferencedPathsSkipsEmptyPath();
|
||||
|
||||
if (g_fail == 0) std::printf("prune_reconcile_tests: ALL PASS\n");
|
||||
else std::printf("prune_reconcile_tests: %d FAILURE(S)\n", g_fail);
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user