Extend the park trust test to the FX chain, name the refused tracks, and stop reprinting an unchanged refusal
This commit is contained in:
+62
-18
@@ -242,31 +242,54 @@ static void testRestorePlanOpsRebuildTheSlotKeyedSnapshotVerbatim() {
|
||||
// an absent snapshot — discardDeferredFxParks drops intents whose flag writes
|
||||
// already landed — so the chain itself has to be asked.
|
||||
|
||||
// The live values the fold reads, keyed by Flag — so an assertion names the flag
|
||||
// it varies rather than a position in makeParkPlan's op order.
|
||||
struct FlagValues {
|
||||
int showInTcp = 0, showInMixer = 0, mainSend = 0, fxEnable = 0;
|
||||
|
||||
int operator()(Flag f) const {
|
||||
switch (f) {
|
||||
case Flag::ShowInTcp: return showInTcp;
|
||||
case Flag::ShowInMixer: return showInMixer;
|
||||
case Flag::MainSend: return mainSend;
|
||||
case Flag::FxEnable: return fxEnable;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
static void testAChainSittingAtEveryValueTheParkWouldWriteReadsAsParked() {
|
||||
// The four zeros are what a parked track's driven flags actually read; if
|
||||
// makeParkPlan ever writes something else, this is the test that says so.
|
||||
const TrackPlan park = makeParkPlan("{A}", /*fxCount=*/0);
|
||||
|
||||
CHECK(park.flags.size() == 4);
|
||||
CHECK(parkFlagsAlreadyApplied(park.flags, {0, 0, 0, 0}));
|
||||
CHECK(parkFlagsAlreadyApplied(park.flags, FlagValues{0, 0, 0, 0}));
|
||||
}
|
||||
|
||||
static void testOneFlagStillAtTheUsersValueMeansNoParkReachedTheChain() {
|
||||
const TrackPlan park = makeParkPlan("{A}", /*fxCount=*/0);
|
||||
|
||||
// Flag order is ShowInTcp, ShowInMixer, MainSend, FxEnable — each alone is
|
||||
// enough to prove the chain was never parked.
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, {1, 0, 0, 0}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, {0, 1, 0, 0}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, {0, 0, 1, 0}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, {0, 0, 0, 1}));
|
||||
// Each case varies exactly one flag, so together they also prove all four are
|
||||
// in the plan: a missing op makes its case read parked and fail here.
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, FlagValues{1, 0, 0, 0}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, FlagValues{0, 1, 0, 0}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, FlagValues{0, 0, 1, 0}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, FlagValues{0, 0, 0, 1}));
|
||||
}
|
||||
|
||||
static void testAnEmptyOrMismatchedPlanProvesNothing() {
|
||||
const TrackPlan park = makeParkPlan("{A}", /*fxCount=*/0);
|
||||
static void testAnEmptyPlanProvesNothing() {
|
||||
CHECK(!parkFlagsAlreadyApplied(std::vector<TrackFlagOp>{}, FlagValues{0, 0, 0, 0}));
|
||||
}
|
||||
|
||||
CHECK(!parkFlagsAlreadyApplied({}, {}));
|
||||
CHECK(!parkFlagsAlreadyApplied(park.flags, {0, 0, 0}));
|
||||
static void testEitherHalfOfTheChainReadingParkedIsEnoughToRefuse() {
|
||||
// The disjunction, pinned: a snapshot has a flag half and an FX half, and each
|
||||
// is the only source of truth for its own. An AND here would re-open the
|
||||
// defect one layer down — flags clean, FX still offline, snapshotted as truth.
|
||||
CHECK(!chainReadsParked(/*parkFlagsRead=*/false, /*anyFxOffline=*/false));
|
||||
CHECK(chainReadsParked(/*parkFlagsRead=*/true, /*anyFxOffline=*/false));
|
||||
CHECK(chainReadsParked(/*parkFlagsRead=*/false, /*anyFxOffline=*/true));
|
||||
CHECK(chainReadsParked(/*parkFlagsRead=*/true, /*anyFxOffline=*/true));
|
||||
}
|
||||
|
||||
static void testACleanChainIsSnapshottedThenParked() {
|
||||
@@ -288,11 +311,30 @@ static void testAParkedChainWithNoSnapshotIsRefusedRatherThanResnapshotted() {
|
||||
CHECK(decidePark(/*haveSnapshot=*/false, /*chainReadsParked=*/true) == ParkAction::Refuse);
|
||||
}
|
||||
|
||||
static void testRefusalIsReportedWithItsTrackCountAndSaysNothingWhenNoneWereRefused() {
|
||||
CHECK(describeRefusedParks(0).empty());
|
||||
CHECK(describeRefusedParks(-1).empty());
|
||||
CHECK(describeRefusedParks(1).find("1 track ") != std::string::npos);
|
||||
CHECK(describeRefusedParks(3).find("3 tracks ") != std::string::npos);
|
||||
static void testRefusalNamesEveryRefusedTrackAndSaysNothingWhenNoneWere() {
|
||||
CHECK(describeRefusedParks({}).empty());
|
||||
|
||||
const std::string one = describeRefusedParks({"Bass"});
|
||||
CHECK(one.find("1 track ") != std::string::npos);
|
||||
CHECK(one.find("Bass") != std::string::npos);
|
||||
|
||||
const std::string two = describeRefusedParks({"Bass", "Drum bus"});
|
||||
CHECK(two.find("2 tracks ") != std::string::npos);
|
||||
CHECK(two.find("Bass") != std::string::npos);
|
||||
CHECK(two.find("Drum bus") != std::string::npos);
|
||||
}
|
||||
|
||||
static void testRefusalRecoveryNamesThePerFxHalfAndAssertsNoCause() {
|
||||
const std::string msg = describeRefusedParks({"Bass"});
|
||||
|
||||
// I_FXEN is the chain bypass: a user who restores only the four flags leaves
|
||||
// every individually offlined FX offline and walks straight back into a
|
||||
// refusal, so the recovery has to spell the per-FX step out.
|
||||
CHECK(msg.find("every FX in its chain online") != std::string::npos);
|
||||
// And it must not name a cause: the pair has several routes, and on a track
|
||||
// the user themselves keeps hidden/bypassed there was no lost state at all.
|
||||
CHECK(msg.find("undo") == std::string::npos);
|
||||
CHECK(msg.find("lost") == std::string::npos);
|
||||
}
|
||||
|
||||
// -- re-entrancy -------------------------------------------------------------
|
||||
@@ -361,11 +403,13 @@ int main() {
|
||||
testRestorePlanOpsRebuildTheSlotKeyedSnapshotVerbatim();
|
||||
testAChainSittingAtEveryValueTheParkWouldWriteReadsAsParked();
|
||||
testOneFlagStillAtTheUsersValueMeansNoParkReachedTheChain();
|
||||
testAnEmptyOrMismatchedPlanProvesNothing();
|
||||
testAnEmptyPlanProvesNothing();
|
||||
testEitherHalfOfTheChainReadingParkedIsEnoughToRefuse();
|
||||
testACleanChainIsSnapshottedThenParked();
|
||||
testAHeldSnapshotIsNeverOverwrittenWhateverTheChainReads();
|
||||
testAParkedChainWithNoSnapshotIsRefusedRatherThanResnapshotted();
|
||||
testRefusalIsReportedWithItsTrackCountAndSaysNothingWhenNoneWereRefused();
|
||||
testRefusalNamesEveryRefusedTrackAndSaysNothingWhenNoneWere();
|
||||
testRefusalRecoveryNamesThePerFxHalfAndAssertsNoCause();
|
||||
|
||||
testTakeDetachesEverythingAndLeavesTheQueueEmpty();
|
||||
testIntentsArrivingDuringADrainSurviveIt();
|
||||
|
||||
@@ -756,9 +756,15 @@ namespace {
|
||||
using LiveFlags = std::map<std::string, int>;
|
||||
|
||||
// Runs one applyMode-equivalent toggle against `vm` + `live`. `guard` selects the
|
||||
// fixed (snapshot-once) behavior vs. the original buggy (snapshot-every-park) one.
|
||||
// fixed behavior vs. the original buggy (snapshot-every-park) one.
|
||||
// Returns nothing; mutates `vm` snapshots/active mode and `live` flags in place,
|
||||
// exactly mirroring view.cpp's park then restore then setActiveMode ordering.
|
||||
//
|
||||
// Guarded, the park mirrors decidePark's three ways: a held snapshot parks
|
||||
// without recapturing, a chain that already reads parked with no snapshot is
|
||||
// REFUSED (left exactly as found), and only a clean chain is snapshotted. The one
|
||||
// `live` flag stands in for the whole park-flag-plus-FX fold the shell reads —
|
||||
// the model half of the defect is identical either way.
|
||||
void simulateApplyMode(ViewModeModel& vm, LiveFlags& live, const FolderTree& tree,
|
||||
const std::string& target, bool guard) {
|
||||
TogglePlan plan = vm.planToggle(tree, target);
|
||||
@@ -767,7 +773,14 @@ void simulateApplyMode(ViewModeModel& vm, LiveFlags& live, const FolderTree& tre
|
||||
for (const auto& tp : plan.park) {
|
||||
if (tp.flags.empty()) continue;
|
||||
const std::string& guid = tp.flags.front().guid;
|
||||
if (!guard || vm.snapshot(guid) == nullptr) {
|
||||
if (guard) {
|
||||
if (vm.snapshot(guid) == nullptr) {
|
||||
if (live[guid] == 0) continue; // reads parked, no snapshot — refuse
|
||||
TrackSnapshot snap;
|
||||
snap.showInTcp = live[guid];
|
||||
vm.storeSnapshot(guid, snap);
|
||||
}
|
||||
} else {
|
||||
TrackSnapshot snap;
|
||||
snap.showInTcp = live[guid]; // capture the LIVE visible flag
|
||||
vm.storeSnapshot(guid, snap);
|
||||
@@ -868,6 +881,74 @@ static void testNestedToggleSnapshotSurvivesRepark() {
|
||||
}
|
||||
}
|
||||
|
||||
// -- 9b. Reload strand: snapshots gone, live flags still parked ---------------
|
||||
//
|
||||
// The pair the park refusal exists for, driven through the same harness. A model
|
||||
// replaced without the project rolling back with it — an unreadable view_state, a
|
||||
// snapshot reconciled away while its track was deleted, a hand or script edit —
|
||||
// leaves tracks sitting at parked values with nothing recording what they were
|
||||
// before. Snapshotting there commits the parked state as the user's, and the very
|
||||
// next toggle writes it back over whatever they have since fixed by hand.
|
||||
|
||||
static void testReloadedModelWithParkedTracksRefusesRatherThanResnapshotting() {
|
||||
const FolderTree tree = nestedTree();
|
||||
|
||||
// GUARDED (fixed shell).
|
||||
{
|
||||
ViewModeModel vm;
|
||||
vm.membership().tag("{L1}", kDesignModeId);
|
||||
LiveFlags live{{"{L1}", 1}, {"{L2}", 1}, {"{L3}", 1}};
|
||||
|
||||
simulateApplyMode(vm, live, tree, kDesignModeId, /*guard=*/true);
|
||||
CHECK(live["{L2}"] == 0 && live["{L3}"] == 0);
|
||||
CHECK(vm.snapshot("{L2}") != nullptr);
|
||||
|
||||
// The reload: the model comes back with no snapshots while the project's
|
||||
// tracks are still parked.
|
||||
vm.clearSnapshot("{L2}");
|
||||
vm.clearSnapshot("{L3}");
|
||||
|
||||
// The load tick reapplies the saved active mode. Both leaves are refused —
|
||||
// nothing written, and nothing false captured.
|
||||
simulateApplyMode(vm, live, tree, kDesignModeId, /*guard=*/true);
|
||||
CHECK(vm.snapshot("{L2}") == nullptr && vm.snapshot("{L3}") == nullptr);
|
||||
CHECK(live["{L2}"] == 0 && live["{L3}"] == 0);
|
||||
|
||||
// The documented hand recovery, on {L2} only, while still in Design.
|
||||
// Toggling back must leave it where the user put it: with no snapshot
|
||||
// there is nothing to restore FROM, so the restore writes nothing.
|
||||
live["{L2}"] = 1;
|
||||
simulateApplyMode(vm, live, tree, kArrangeModeId, /*guard=*/true);
|
||||
CHECK(live["{L2}"] == 1); // the hand fix survives
|
||||
CHECK(live["{L3}"] == 0); // never recovered — stuck, but never falsely committed
|
||||
|
||||
// And {L2} is back under mode control from there: the next park sees a
|
||||
// clean chain, captures the user's value, and restores it.
|
||||
simulateApplyMode(vm, live, tree, kDesignModeId, /*guard=*/true);
|
||||
CHECK(live["{L2}"] == 0 && vm.snapshot("{L2}") != nullptr);
|
||||
simulateApplyMode(vm, live, tree, kArrangeModeId, /*guard=*/true);
|
||||
CHECK(live["{L2}"] == 1);
|
||||
}
|
||||
|
||||
// UNGUARDED (the defect): the reapply after the reload recaptures the parked
|
||||
// zero, so the very next toggle writes it back over the hand recovery.
|
||||
{
|
||||
ViewModeModel vm;
|
||||
vm.membership().tag("{L1}", kDesignModeId);
|
||||
LiveFlags live{{"{L1}", 1}, {"{L2}", 1}, {"{L3}", 1}};
|
||||
|
||||
simulateApplyMode(vm, live, tree, kDesignModeId, /*guard=*/false);
|
||||
vm.clearSnapshot("{L2}");
|
||||
vm.clearSnapshot("{L3}");
|
||||
simulateApplyMode(vm, live, tree, kDesignModeId, /*guard=*/false);
|
||||
|
||||
live["{L2}"] = 1; // the same hand recovery
|
||||
simulateApplyMode(vm, live, tree, kArrangeModeId, /*guard=*/false);
|
||||
|
||||
CHECK(live["{L2}"] == 0); // wiped — the false snapshot won
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// D2 two-canvas lane extension tests
|
||||
// ===========================================================================
|
||||
@@ -2125,6 +2206,7 @@ int main() {
|
||||
testUntaggedLeavesManagedByModeSystem();
|
||||
testTaggedLeafBehaviorUnchangedWithUntagged();
|
||||
testNestedToggleSnapshotSurvivesRepark();
|
||||
testReloadedModelWithParkedTracksRefusesRatherThanResnapshotting();
|
||||
testReconcilePrunesOrphanedSnapshots();
|
||||
testReconcileFullLiveSetIsNoOp();
|
||||
testReconcileThenReparkLifecycleIntact();
|
||||
|
||||
Reference in New Issue
Block a user