Merge fix: project-object identity stops forked-bank cross-contamination
This commit is contained in:
@@ -178,64 +178,92 @@ static void testRelocationPlanEmptyInputsNoOp() {
|
||||
CHECK(!deriveRelocationPlan("", "").needed);
|
||||
}
|
||||
|
||||
// --- Project-identity transition (M4 defect fix) ----------------------------
|
||||
// --- Project-identity transition (W10 forked-project fix) -------------------
|
||||
//
|
||||
// Signature: classifyProjectTransition(sameProjectObject, lastGuid, lastPath,
|
||||
// currentGuid, currentPath).
|
||||
// The first arg — did the SAME ReaProject* stay active across the two ticks —
|
||||
// is the primary signal; poll() computes it as `proj == lastProject_`.
|
||||
|
||||
static void testTransitionRecycledPointerLoadsNotRelocates() {
|
||||
// THE ORIGINAL BUG. Project A (guidA, /a/a.rpp) is closed; REAPER makes a
|
||||
// different saved project B active with a recycled pointer. B carries its own
|
||||
// GUID and a different path. With identity keyed on the GUID this is a Load,
|
||||
// NOT a Save-As — so the bank is never clobbered.
|
||||
CHECK(classifyProjectTransition("guidA", "/a/a.rpp", "guidB", "/b/b.rpp")
|
||||
static void testTransitionForkTabSwitchLoadsNeverRelocates() {
|
||||
// THE W10 BUG. proj2 and proj3 are forked siblings (Save-As copied the .rpp
|
||||
// incl. our GUID), so BOTH carry the same non-empty GUID on disk but sit at
|
||||
// different paths. Tab-switching between them is a DIFFERENT project object
|
||||
// (sameProjectObject == false). The old GUID-only classifier read this as a
|
||||
// Save-As and clobbered a bank; with the pointer back it is a Load, so neither
|
||||
// bank is ever relocated. This is the regression made provable outside the DAW.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/false,
|
||||
"guidShared", "/proj2/p.rpp",
|
||||
"guidShared", "/proj3/p.rpp")
|
||||
== ProjectTransition::Load);
|
||||
// Even the adversarial shape — recycled pointer AND B happens to sit at a
|
||||
// path the old check would misread — resolves to Load purely on the GUID.
|
||||
CHECK(classifyProjectTransition("guidA", "/a/a.rpp", "guidB", "/a/other.rpp")
|
||||
// Switching back the other way is likewise a different object -> Load.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/false,
|
||||
"guidShared", "/proj3/p.rpp",
|
||||
"guidShared", "/proj2/p.rpp")
|
||||
== ProjectTransition::Load);
|
||||
}
|
||||
|
||||
static void testTransitionGenuineSaveAsRelocates() {
|
||||
// Same project (same non-empty GUID) saved to a new .rpp location -> the one
|
||||
// case that legitimately relocates the bank.
|
||||
CHECK(classifyProjectTransition("guidA", "/a/a.rpp", "guidA", "/b/b.rpp")
|
||||
// The SAME project object (pointer unchanged) saved to a new .rpp location ->
|
||||
// the one case that legitimately relocates the bank. Same GUID, new path.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/true,
|
||||
"guidA", "/a/a.rpp", "guidA", "/b/b.rpp")
|
||||
== ProjectTransition::SaveAsRelocate);
|
||||
}
|
||||
|
||||
static void testTransitionTabSwitchLoads() {
|
||||
// Switching to another open project (different GUID) -> Load, never relocate,
|
||||
// regardless of whether the paths differ.
|
||||
CHECK(classifyProjectTransition("guidA", "/a/a.rpp", "guidB", "/b/b.rpp")
|
||||
static void testTransitionPointerReuseDifferentGuidLoads() {
|
||||
// Pointer *reuse* for a genuinely different project: at THIS tick the object
|
||||
// differs (sameProjectObject == false) and its GUID differs too -> Load. Never
|
||||
// a relocate. This is the case the M4 GUID was originally added to handle;
|
||||
// the pointer check subsumes it (different object) and the GUID corroborates.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/false,
|
||||
"guidA", "/a/a.rpp", "guidB", "/b/b.rpp")
|
||||
== ProjectTransition::Load);
|
||||
// Switching back also loads (its GUID differs from the one just seen).
|
||||
CHECK(classifyProjectTransition("guidB", "/b/b.rpp", "guidA", "/a/a.rpp")
|
||||
}
|
||||
|
||||
static void testTransitionTabSwitchDistinctProjectsLoads() {
|
||||
// Ordinary tab-switch between two distinct (non-forked) saved projects:
|
||||
// different object, different GUID -> Load, regardless of paths.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/false,
|
||||
"guidA", "/a/a.rpp", "guidB", "/b/b.rpp")
|
||||
== ProjectTransition::Load);
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/false,
|
||||
"guidB", "/b/b.rpp", "guidA", "/a/a.rpp")
|
||||
== ProjectTransition::Load);
|
||||
}
|
||||
|
||||
static void testTransitionSaveInPlaceIsNoOp() {
|
||||
// Same GUID, same path (idle tick, or a Save that did not move the .rpp) ->
|
||||
// Same object, same path (idle tick, or a Save that did not move the .rpp) ->
|
||||
// nothing to do.
|
||||
CHECK(classifyProjectTransition("guidA", "/a/a.rpp", "guidA", "/a/a.rpp")
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/true,
|
||||
"guidA", "/a/a.rpp", "guidA", "/a/a.rpp")
|
||||
== ProjectTransition::NoOp);
|
||||
// Empty GUID (unsaved project sitting idle), same (empty) path -> NoOp too.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/true, "", "", "", "")
|
||||
== ProjectTransition::NoOp);
|
||||
}
|
||||
|
||||
static void testTransitionEmptyGuidPathChangeLoadsNeverRelocates() {
|
||||
// No GUID on either side (unsaved projects / first-save) means we CANNOT
|
||||
// prove the two ticks saw the same project. A path change is then a Load
|
||||
// (first save, or a switch between unsaved projects) — never a relocate,
|
||||
// which is the safe direction (no destructive copy-over without proof).
|
||||
CHECK(classifyProjectTransition("", "", "", "/a/a.rpp")
|
||||
== ProjectTransition::Load);
|
||||
CHECK(classifyProjectTransition("", "/tmp/untitled.rpp", "", "/a/a.rpp")
|
||||
== ProjectTransition::Load);
|
||||
// Both empty, same path -> idle unsaved project -> NoOp.
|
||||
CHECK(classifyProjectTransition("", "", "", "")
|
||||
== ProjectTransition::NoOp);
|
||||
static void testTransitionFirstSaveSameObjectRelocatesButPlanNoOps() {
|
||||
// Same object, empty GUID, path appears (first save of an untitled project).
|
||||
// The classifier says SaveAsRelocate, but the empty-GUID safety is preserved
|
||||
// at execution: the old project dir is empty, so deriveRelocationPlan makes
|
||||
// `needed` false and NOTHING is physically relocated; poll() just mints a GUID.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/true,
|
||||
"", "", "", "/a/a.rpp")
|
||||
== ProjectTransition::SaveAsRelocate);
|
||||
// Prove the safety end-to-end: the relocation plan for an empty old dir no-ops.
|
||||
CHECK(!deriveRelocationPlan(/*oldProjectDir=*/"", "/a").needed);
|
||||
}
|
||||
|
||||
static void testTransitionGuidAppearingLoads() {
|
||||
// A project that had no stored GUID (last seen empty) now reports one (we just
|
||||
// minted+wrote it, or an older project gained one). The GUID changed, so it
|
||||
// classifies as Load — harmless: loadFromProject re-reads the same ext state.
|
||||
CHECK(classifyProjectTransition("", "/a/a.rpp", "guidA", "/a/a.rpp")
|
||||
static void testTransitionSameObjectSharedGuidStillLoadsWhenObjectDiffers() {
|
||||
// Divergence guard's precondition, from the pure side: even if a forked sibling
|
||||
// still shares our GUID, as long as the OBJECT differs the verdict is Load
|
||||
// (never Save-As). The actual re-GUID that makes the siblings diverge is a
|
||||
// REAPER-facing action in poll() (SetProjExtState + MarkProjectDirty) and is
|
||||
// covered by the DAW procedure; here we lock the pure verdict that gates it.
|
||||
CHECK(classifyProjectTransition(/*sameProjectObject=*/false,
|
||||
"guidShared", "/proj2/p.rpp",
|
||||
"guidShared", "/proj3/p.rpp")
|
||||
== ProjectTransition::Load);
|
||||
}
|
||||
|
||||
@@ -255,12 +283,13 @@ int main() {
|
||||
testRelocationPlanForSaveAs();
|
||||
testRelocationPlanNotNeededForSaveInPlace();
|
||||
testRelocationPlanEmptyInputsNoOp();
|
||||
testTransitionRecycledPointerLoadsNotRelocates();
|
||||
testTransitionForkTabSwitchLoadsNeverRelocates();
|
||||
testTransitionGenuineSaveAsRelocates();
|
||||
testTransitionTabSwitchLoads();
|
||||
testTransitionPointerReuseDifferentGuidLoads();
|
||||
testTransitionTabSwitchDistinctProjectsLoads();
|
||||
testTransitionSaveInPlaceIsNoOp();
|
||||
testTransitionEmptyGuidPathChangeLoadsNeverRelocates();
|
||||
testTransitionGuidAppearingLoads();
|
||||
testTransitionFirstSaveSameObjectRelocatesButPlanNoOps();
|
||||
testTransitionSameObjectSharedGuidStillLoadsWhenObjectDiffers();
|
||||
|
||||
if (g_fail == 0) std::printf("capture_paths: all tests passed\n");
|
||||
else std::printf("capture_paths: %d CHECK(s) FAILED\n", g_fail);
|
||||
|
||||
Reference in New Issue
Block a user