39529a5ac4
Flag writes are silent from a timer, so a block around a reapply or the reconcile repair could only manufacture a point. The real switch's span is unchanged.
206 lines
8.5 KiB
C++
206 lines
8.5 KiB
C++
// Standalone tests for the Design View park surface's pure decisions — no REAPER, no
|
|
// test framework.
|
|
//
|
|
// The properties under test: the four flags a park drives address exactly the four
|
|
// REAPER parameters the tool owns (never mute, never solo), the re-park union, the
|
|
// park-shaped chain read, and the EXPOSE report's wording.
|
|
|
|
#include "../src/shell/view/view_park.h"
|
|
|
|
#include "core/view/view_mode_model.h" // makeParkPlan — the park flags' only producer
|
|
|
|
#include <cstdio>
|
|
#include <set>
|
|
#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)
|
|
|
|
// -- what a park is allowed to address ----------------------------------------
|
|
//
|
|
// trackFlagParm is the ONE mapping the park's pre-park read, the restore's write and
|
|
// the reconcile's repair all share; a mistyped parameter there is silent at compile
|
|
// time and drives the wrong track property forever.
|
|
|
|
static void testEachDrivenFlagAddressesItsOwnReaperParameter() {
|
|
const std::set<std::string> parms{trackFlagParm(Flag::ShowInTcp),
|
|
trackFlagParm(Flag::ShowInMixer),
|
|
trackFlagParm(Flag::MainSend),
|
|
trackFlagParm(Flag::FxEnable)};
|
|
CHECK(parms.size() == 4); // no two flags collapse onto one parameter
|
|
CHECK(parms.count("B_SHOWINTCP") == 1);
|
|
CHECK(parms.count("B_SHOWINMIXER") == 1);
|
|
CHECK(parms.count("B_MAINSEND") == 1);
|
|
CHECK(parms.count("I_FXEN") == 1);
|
|
}
|
|
|
|
static void testNoDrivenFlagCanEverAddressMuteOrSolo() {
|
|
// The never-touch-mute invariant, held at the one place a Flag becomes a REAPER
|
|
// parameter name.
|
|
for (const Flag f : {Flag::ShowInTcp, Flag::ShowInMixer, Flag::MainSend, Flag::FxEnable}) {
|
|
const std::string parm = trackFlagParm(f);
|
|
CHECK(parm != "B_MUTE");
|
|
CHECK(parm != "I_SOLO");
|
|
}
|
|
}
|
|
|
|
static void testAParkPlanDrivesEveryDrivenFlagToZero() {
|
|
// 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}");
|
|
|
|
CHECK(park.flags.size() == 4);
|
|
std::set<std::string> parms;
|
|
for (const TrackFlagOp& op : park.flags) {
|
|
CHECK(op.guid == "{A}");
|
|
CHECK(op.value == 0);
|
|
parms.insert(trackFlagParm(op.flag));
|
|
}
|
|
CHECK(parms.size() == 4);
|
|
}
|
|
|
|
// -- the re-park union: a plugin added between a restore and the next park --------
|
|
//
|
|
// Sequence under test: park -> restore -> user adds plugin P -> re-park. Without the
|
|
// union, the record's prior verbatim-keep would drop P silently -- the reclaimer
|
|
// offlines it with everything else, and no snapshot ever names it to bring it back.
|
|
|
|
static void testAPluginAddedWhileRestoredJoinsTheUnionRatherThanBeingDropped() {
|
|
const std::vector<FxOfflineState> prior{
|
|
FxOfflineState{"{FX-A}", 1}, FxOfflineState{"{FX-B}", 0}};
|
|
const std::vector<FxOfflineState> live{
|
|
FxOfflineState{"{FX-A}", 0}, FxOfflineState{"{FX-B}", 0}, FxOfflineState{"{FX-P}", 0}};
|
|
|
|
const std::vector<FxOfflineState> merged = unionFxOfflineOnRepark(prior, live);
|
|
|
|
CHECK(merged.size() == 3);
|
|
bool foundA = false, foundB = false, foundP = false;
|
|
for (const FxOfflineState& fx : merged) {
|
|
if (fx.fxGuid == "{FX-A}") { foundA = true; CHECK(fx.offline == 1); } // record wins
|
|
if (fx.fxGuid == "{FX-B}") { foundB = true; CHECK(fx.offline == 0); } // record wins
|
|
if (fx.fxGuid == "{FX-P}") { foundP = true; CHECK(fx.offline == 0); } // added from live
|
|
}
|
|
CHECK(foundA && foundB && foundP);
|
|
}
|
|
|
|
static void testAPluginRemovedWhileRestoredStaysInTheUnionForResolveFxRestoreToDrop() {
|
|
// The union is not where a removed plugin's entry disappears -- resolveFxRestore
|
|
// owns that at restore time (missingIdentity, reported), so the merge must keep
|
|
// naming it rather than quietly forgetting it here.
|
|
const std::vector<FxOfflineState> prior{
|
|
FxOfflineState{"{FX-A}", 1}, FxOfflineState{"{FX-REMOVED}", 1}};
|
|
const std::vector<FxOfflineState> live{FxOfflineState{"{FX-A}", 0}};
|
|
|
|
const std::vector<FxOfflineState> merged = unionFxOfflineOnRepark(prior, live);
|
|
|
|
CHECK(merged.size() == 2);
|
|
bool foundRemoved = false;
|
|
for (const FxOfflineState& fx : merged)
|
|
if (fx.fxGuid == "{FX-REMOVED}") foundRemoved = true;
|
|
CHECK(foundRemoved);
|
|
}
|
|
|
|
// -- chainReadsParked: the EXPOSE trigger, flags only -------------------------
|
|
//
|
|
// 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 testAChainSittingAtEveryParkedValueReadsAsParkShaped() {
|
|
const TrackPlan park = makeParkPlan("{A}");
|
|
CHECK(chainReadsParked(park.flags, FlagValues{0, 0, 0, 0}));
|
|
}
|
|
|
|
static void testOneFlagStillAtTheUsersValueMeansNotParkShaped() {
|
|
const TrackPlan park = makeParkPlan("{A}");
|
|
|
|
// 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(!chainReadsParked(park.flags, FlagValues{1, 0, 0, 0}));
|
|
CHECK(!chainReadsParked(park.flags, FlagValues{0, 1, 0, 0}));
|
|
CHECK(!chainReadsParked(park.flags, FlagValues{0, 0, 1, 0}));
|
|
CHECK(!chainReadsParked(park.flags, FlagValues{0, 0, 0, 1}));
|
|
}
|
|
|
|
static void testAnEmptyPlanProvesNothing() {
|
|
CHECK(!chainReadsParked(std::vector<TrackFlagOp>{}, FlagValues{0, 0, 0, 0}));
|
|
}
|
|
|
|
static void testFxOfflineAloneIsNotFoldedIntoTheParkedRead() {
|
|
// chainReadsParked takes no FX argument at all: an FX-only-offline chain
|
|
// cannot make it read parked, by construction -- there is nothing here it
|
|
// could read. That is the resolved design question (see view_park.h):
|
|
// flags-fine/FX-offline stays on the ordinary capture path, never EXPOSE.
|
|
const TrackPlan park = makeParkPlan("{A}");
|
|
CHECK(!chainReadsParked(park.flags, FlagValues{1, 1, 1, 1})); // flags all fine
|
|
}
|
|
|
|
// -- the EXPOSE console report -------------------------------------------------
|
|
|
|
static void testExposedTracksAreNamedAndEmptyListSaysNothing() {
|
|
CHECK(describeExposedTracks({}).empty());
|
|
|
|
const std::string one = describeExposedTracks({"Bass"});
|
|
CHECK(one.find("1 track ") != std::string::npos);
|
|
CHECK(one.find("Bass") != std::string::npos);
|
|
|
|
const std::string two = describeExposedTracks({"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 testExposedReportNeverUsesRefusalWording() {
|
|
// Daniel's ruling was EXPOSE, not refuse -- the wording must never suggest the
|
|
// tool declined to act.
|
|
const std::string msg = describeExposedTracks({"Bass"});
|
|
CHECK(msg.find("refus") == std::string::npos);
|
|
}
|
|
|
|
static void testShouldReportSkipsAnExactRepeatOnly() {
|
|
const std::vector<std::string> a{"Bass"};
|
|
const std::vector<std::string> b{"Bass", "Drum bus"};
|
|
|
|
CHECK(!shouldReport(/*sameOwnerAsLast=*/true, a, a)); // exact repeat: silent
|
|
CHECK(shouldReport(/*sameOwnerAsLast=*/true, a, b)); // set changed: reprint
|
|
CHECK(shouldReport(/*sameOwnerAsLast=*/false, a, a)); // owner (project) changed: reprint
|
|
CHECK(shouldReport(/*sameOwnerAsLast=*/true, a, {})); // healed to none: reprint (resets memo)
|
|
}
|
|
|
|
int main() {
|
|
testEachDrivenFlagAddressesItsOwnReaperParameter();
|
|
testNoDrivenFlagCanEverAddressMuteOrSolo();
|
|
testAParkPlanDrivesEveryDrivenFlagToZero();
|
|
|
|
testAPluginAddedWhileRestoredJoinsTheUnionRatherThanBeingDropped();
|
|
testAPluginRemovedWhileRestoredStaysInTheUnionForResolveFxRestoreToDrop();
|
|
|
|
testAChainSittingAtEveryParkedValueReadsAsParkShaped();
|
|
testOneFlagStillAtTheUsersValueMeansNotParkShaped();
|
|
testAnEmptyPlanProvesNothing();
|
|
testFxOfflineAloneIsNotFoldedIntoTheParkedRead();
|
|
|
|
testExposedTracksAreNamedAndEmptyListSaysNothing();
|
|
testExposedReportNeverUsesRefusalWording();
|
|
testShouldReportSkipsAnExactRepeatOnly();
|
|
|
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|