198 lines
8.7 KiB
C++
198 lines
8.7 KiB
C++
// Standalone tests for the Design View park surface's pure decisions — no
|
|
// REAPER, no test framework.
|
|
//
|
|
// The properties under test: a pre-park snapshot is never taken from a chain a
|
|
// park has already touched, the refusal that follows names its tracks and is
|
|
// reported once per changed set, and one mode apply leaves at most ONE undo
|
|
// point — none at all when a reapply found everything already where it wanted it.
|
|
|
|
#include "../src/shell/view/view_fx_park.h"
|
|
#include "../src/shell/view/view.h" // applyMintsUndoPoint — the apply's undo-point fold
|
|
|
|
#include "core/view/view_mode_model.h" // makeParkPlan — the park flags' only producer
|
|
|
|
#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)
|
|
|
|
// -- may this chain be snapshotted? -------------------------------------------
|
|
//
|
|
// Rationale: the snapshot-source invariant (view_fx_park.h, this directory's
|
|
// CLAUDE.md).
|
|
|
|
// 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, FlagValues{0, 0, 0, 0}));
|
|
}
|
|
|
|
static void testOneFlagStillAtTheUsersValueMeansNoParkReachedTheChain() {
|
|
const TrackPlan park = makeParkPlan("{A}", /*fxCount=*/0);
|
|
|
|
// 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 testAnEmptyPlanProvesNothing() {
|
|
CHECK(!parkFlagsAlreadyApplied(std::vector<TrackFlagOp>{}, FlagValues{0, 0, 0, 0}));
|
|
}
|
|
|
|
static void testEitherHalfOfTheChainReadingParkedIsEnoughToRefuse() {
|
|
// Disjunction pinned; rationale at chainReadsParked (view_fx_park.h).
|
|
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() {
|
|
CHECK(decidePark(/*haveSnapshot=*/false, /*chainReadsParked=*/false) ==
|
|
ParkAction::SnapshotThenPark);
|
|
}
|
|
|
|
static void testAHeldSnapshotIsNeverOverwrittenWhateverTheChainReads() {
|
|
// The held snapshot IS the pre-park truth, so the chain is not consulted —
|
|
// which is what lets the shell skip the live flag reads on this path.
|
|
CHECK(decidePark(/*haveSnapshot=*/true, /*chainReadsParked=*/false) == ParkAction::ParkOnly);
|
|
CHECK(decidePark(/*haveSnapshot=*/true, /*chainReadsParked=*/true) == ParkAction::ParkOnly);
|
|
}
|
|
|
|
static void testAParkedChainWithNoSnapshotIsRefusedRatherThanResnapshotted() {
|
|
// The defect this whole section exists for: the truth is gone, so the only
|
|
// non-destructive act is to leave the track alone. Snapshotting here commits
|
|
// park state as the user's state and no later restore can undo it.
|
|
CHECK(decidePark(/*haveSnapshot=*/false, /*chainReadsParked=*/true) == ParkAction::Refuse);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// -- the refusal memo's print-suppression gate --------------------------------
|
|
//
|
|
// reportRefusedParks' own memo (owner + last-reported names, both REAPER-side
|
|
// statics) can't be driven from here, but the pure gate behind it can: prints
|
|
// (i.e. updates the memo) on any change, stays silent on an exact repeat, and
|
|
// treats a fresh empty set as a change too, so a later real refusal is never
|
|
// mistaken for a repeat of one that already healed.
|
|
|
|
static void testUnchangedOwnerAndSetStaysSilent() {
|
|
CHECK(!shouldReport(/*sameOwnerAsLast=*/true, {"Bass"}, {"Bass"}));
|
|
}
|
|
|
|
static void testADifferentNamedSetReports() {
|
|
CHECK(shouldReport(/*sameOwnerAsLast=*/true, {"Bass"}, {"Bass", "Drum bus"}));
|
|
}
|
|
|
|
static void testADifferentOwnerReportsEvenWithTheSameNames() {
|
|
// Two alternating project tabs must not suppress each other's first refusal.
|
|
CHECK(shouldReport(/*sameOwnerAsLast=*/false, {"Bass"}, {"Bass"}));
|
|
}
|
|
|
|
static void testAFreshEmptySetReportsAndResetsTheMemo() {
|
|
CHECK(shouldReport(/*sameOwnerAsLast=*/true, {"Bass"}, {}));
|
|
// Already empty, same owner: nothing changed, stays silent.
|
|
CHECK(!shouldReport(/*sameOwnerAsLast=*/true, {}, {}));
|
|
}
|
|
|
|
// -- the apply's one undo point ----------------------------------------------
|
|
//
|
|
// These pin applyMintsUndoPoint's fold only. The `wroteAnything` verdict it
|
|
// consumes — writeIfChanged / parkTrack / restoreTrack, all running against live
|
|
// REAPER calls — cannot be pinned here; docs/VERIFICATION.md §"Mode switching"
|
|
// is the sole cover for that plumbing being wired correctly.
|
|
|
|
static void testARealSwitchAlwaysLeavesAPointEvenWithNothingToWrite() {
|
|
// An explicitly fired action stays undoable even when the plan found nothing
|
|
// to write — the discard form is for reapplies only.
|
|
CHECK(applyMintsUndoPoint(/*realSwitch=*/true, /*wroteAnything=*/false));
|
|
CHECK(applyMintsUndoPoint(/*realSwitch=*/true, /*wroteAnything=*/true));
|
|
}
|
|
|
|
static void testAReapplyThatWroteSomethingLeavesAPoint() {
|
|
// A tag/untag reapply that actually parked a track: real project state moved.
|
|
CHECK(applyMintsUndoPoint(/*realSwitch=*/false, /*wroteAnything=*/true));
|
|
}
|
|
|
|
static void testAReapplyThatWroteNothingLeavesNoPointAtAll() {
|
|
// The project-load reapply over a project saved fully parked: every flag and
|
|
// every FX already sit where the plan wants them. Opening a project must not
|
|
// cost the user an undo step.
|
|
CHECK(!applyMintsUndoPoint(/*realSwitch=*/false, /*wroteAnything=*/false));
|
|
}
|
|
|
|
int main() {
|
|
testAChainSittingAtEveryValueTheParkWouldWriteReadsAsParked();
|
|
testOneFlagStillAtTheUsersValueMeansNoParkReachedTheChain();
|
|
testAnEmptyPlanProvesNothing();
|
|
testEitherHalfOfTheChainReadingParkedIsEnoughToRefuse();
|
|
testACleanChainIsSnapshottedThenParked();
|
|
testAHeldSnapshotIsNeverOverwrittenWhateverTheChainReads();
|
|
testAParkedChainWithNoSnapshotIsRefusedRatherThanResnapshotted();
|
|
testRefusalNamesEveryRefusedTrackAndSaysNothingWhenNoneWere();
|
|
testRefusalRecoveryNamesThePerFxHalfAndAssertsNoCause();
|
|
|
|
testUnchangedOwnerAndSetStaysSilent();
|
|
testADifferentNamedSetReports();
|
|
testADifferentOwnerReportsEvenWithTheSameNames();
|
|
testAFreshEmptySetReportsAndResetsTheMemo();
|
|
|
|
testARealSwitchAlwaysLeavesAPointEvenWithNothingToWrite();
|
|
testAReapplyThatWroteSomethingLeavesAPoint();
|
|
testAReapplyThatWroteNothingLeavesNoPointAtAll();
|
|
|
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|