205 lines
8.1 KiB
C++
205 lines
8.1 KiB
C++
// Standalone tests for the Design View repair pass's pure decision — no REAPER, no
|
|
// test framework.
|
|
//
|
|
// The two properties the pass exists for: it drives a flag ONLY where a park record
|
|
// names the track (so a track the tool never parked can never have its main send
|
|
// re-enabled under it), and it fires on applied-output drift rather than on "an undo
|
|
// happened". Both AppliedStates are produced by the real fingerprint
|
|
// (view::appliedState) over real models, so a change to what the fingerprint carries
|
|
// shows up here rather than being papered over by hand-built fixtures.
|
|
|
|
#include "../src/shell/view/view_reconcile.h"
|
|
|
|
#include <cstdio>
|
|
#include <map>
|
|
#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)
|
|
|
|
namespace {
|
|
|
|
TrackSnapshot captured(int showInTcp, int showInMixer, int mainSend, int fxEnable) {
|
|
TrackSnapshot snap;
|
|
snap.showInTcp = showInTcp;
|
|
snap.showInMixer = showInMixer;
|
|
snap.mainSend = mainSend;
|
|
snap.fxEnable = fxEnable;
|
|
return snap;
|
|
}
|
|
|
|
bool hasOp(const std::vector<TrackFlagOp>& ops, const std::string& guid, Flag flag, int value) {
|
|
for (const TrackFlagOp& op : ops)
|
|
if (op.guid == guid && op.flag == flag && op.value == value) return true;
|
|
return false;
|
|
}
|
|
|
|
bool namesTrack(const std::vector<TrackFlagOp>& ops, const std::string& guid) {
|
|
for (const TrackFlagOp& op : ops)
|
|
if (op.guid == guid) return true;
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// -- the trigger: applied-output drift, not "an undo happened" -----------------
|
|
|
|
static void testAnUndoThatLeftOurAppliedOutputAloneRepairsNothing() {
|
|
// The rejected trigger is "did an undo occur" — an undo that touched nothing we
|
|
// drive still answers yes, and repairing on it rewrites state the user owns.
|
|
ViewModeModel model;
|
|
model.setActiveMode(kDesignModeId);
|
|
model.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
|
|
const view::AppliedState state = view::appliedState(model);
|
|
CHECK(planReconcile(state, state).empty());
|
|
}
|
|
|
|
static void testADriftedActiveModeAloneIsNotAReasonToDriveATrack() {
|
|
// Drift is the gate, not the instruction: the record still has an owner, so the
|
|
// reapply that follows this pass is what drives it.
|
|
ViewModeModel applied;
|
|
applied.setActiveMode(kDesignModeId);
|
|
applied.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
|
|
ViewModeModel reloaded; // active mode Arrange — the fingerprint differs
|
|
reloaded.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
|
|
CHECK(view::appliedState(applied) != view::appliedState(reloaded));
|
|
CHECK(planReconcile(view::appliedState(applied), view::appliedState(reloaded)).empty());
|
|
}
|
|
|
|
// -- the scope rule: a record names the track, or nothing is written -----------
|
|
|
|
static void testARecordTheReloadedModelLostIsHandedBackItsCapturedValues() {
|
|
// The undo case: REAPER rolled view_state back to before the switch, so the model
|
|
// no longer records the park — but the flags are still driven on the track, and
|
|
// this memo is the only surviving account of what they were.
|
|
ViewModeModel applied;
|
|
applied.setActiveMode(kDesignModeId);
|
|
applied.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
|
|
const ViewModeModel reloaded; // Arrange, no records at all
|
|
|
|
const std::vector<TrackFlagOp> ops =
|
|
planReconcile(view::appliedState(applied), view::appliedState(reloaded));
|
|
|
|
CHECK(ops.size() == 4);
|
|
CHECK(hasOp(ops, "{A}", Flag::ShowInTcp, 1));
|
|
CHECK(hasOp(ops, "{A}", Flag::ShowInMixer, 1));
|
|
CHECK(hasOp(ops, "{A}", Flag::MainSend, 1));
|
|
CHECK(hasOp(ops, "{A}", Flag::FxEnable, 1));
|
|
}
|
|
|
|
static void testARepairedFlagReturnsToItsCapturedValueNeverToOn() {
|
|
// A track the user had OUT of the master mix and FX-bypassed before we parked it:
|
|
// the repair must hand back exactly that, not a hardcoded "on".
|
|
ViewModeModel applied;
|
|
applied.setActiveMode(kDesignModeId);
|
|
applied.storeSnapshot("{A}", captured(1, 0, 0, 0));
|
|
|
|
const ViewModeModel reloaded;
|
|
|
|
const std::vector<TrackFlagOp> ops =
|
|
planReconcile(view::appliedState(applied), view::appliedState(reloaded));
|
|
|
|
CHECK(hasOp(ops, "{A}", Flag::MainSend, 0));
|
|
CHECK(hasOp(ops, "{A}", Flag::ShowInMixer, 0));
|
|
CHECK(hasOp(ops, "{A}", Flag::FxEnable, 0));
|
|
CHECK(!hasOp(ops, "{A}", Flag::MainSend, 1));
|
|
}
|
|
|
|
static void testATrackNeitherStateRecordsIsNeverDrivenAtAll() {
|
|
// The regression this rule is built against: a track the model merely calls
|
|
// active is NOT a track the tool parked. It holds no captured value, so asserting
|
|
// B_MAINSEND=1 on it would silently re-enable a send the user disabled by hand.
|
|
ViewModeModel applied;
|
|
applied.setActiveMode(kDesignModeId);
|
|
applied.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
|
|
ViewModeModel reloaded;
|
|
reloaded.membership().tag("{B}", kArrangeModeId); // active, never parked, no record
|
|
|
|
const std::vector<TrackFlagOp> ops =
|
|
planReconcile(view::appliedState(applied), view::appliedState(reloaded));
|
|
|
|
CHECK(namesTrack(ops, "{A}"));
|
|
CHECK(!namesTrack(ops, "{B}"));
|
|
}
|
|
|
|
static void testASpentFlagHalfIsNotARecordToRepairFrom() {
|
|
// A restore already handed the flags back and zeroed them on the record; the FX
|
|
// half outlives that. Repairing from the spent half would drive those zeros —
|
|
// i.e. re-park the track — so the fingerprint must not carry it.
|
|
ViewModeModel applied;
|
|
applied.setActiveMode(kDesignModeId);
|
|
applied.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
applied.releaseSnapshotFlags("{A}");
|
|
|
|
const ViewModeModel reloaded;
|
|
|
|
CHECK(planReconcile(view::appliedState(applied), view::appliedState(reloaded)).empty());
|
|
}
|
|
|
|
// -- the per-project memo: a tab switch must not erase another project's account --
|
|
|
|
static void testASecondProjectNotedInBetweenDoesNotEraseTheFirstProjectsMemo() {
|
|
// The bug this replaced: a single global slot meant noting project B's applied
|
|
// state overwrote project A's entirely, so a repair for A run afterward found no
|
|
// memo to compare against and silently skipped -- exactly what a tab-switch then
|
|
// an undo on the ORIGINAL project produces within one timer period.
|
|
ReaProject* const projA = reinterpret_cast<ReaProject*>(1);
|
|
ReaProject* const projB = reinterpret_cast<ReaProject*>(2);
|
|
|
|
ViewModeModel appliedA;
|
|
appliedA.setActiveMode(kDesignModeId);
|
|
appliedA.storeSnapshot("{A}", captured(1, 1, 1, 1));
|
|
|
|
const ViewModeModel appliedB; // untouched — B has nothing driven
|
|
|
|
std::map<ReaProject*, view::AppliedState> memo;
|
|
memo[projA] = view::appliedState(appliedA);
|
|
memo[projB] = view::appliedState(appliedB); // noted in between; must not clobber A
|
|
|
|
const ViewModeModel reloadedA; // A's park record dropped, as an undo would leave it
|
|
|
|
const ProjectReconcileResult result =
|
|
planReconcilePerProject(memo, projA, view::appliedState(reloadedA));
|
|
|
|
CHECK(result.ops.size() == 4);
|
|
CHECK(hasOp(result.ops, "{A}", Flag::MainSend, 1));
|
|
}
|
|
|
|
static void testAProjectWithNoPriorMemoGetsNoRepair() {
|
|
// "A project we hold no memo for gets no repair" -- the tool has no account of
|
|
// driving anything there, so it has no opinion (see the header's SCOPE note).
|
|
ReaProject* const projC = reinterpret_cast<ReaProject*>(3);
|
|
const std::map<ReaProject*, view::AppliedState> memo; // never noted
|
|
|
|
const ViewModeModel reloaded;
|
|
const ProjectReconcileResult result =
|
|
planReconcilePerProject(memo, projC, view::appliedState(reloaded));
|
|
|
|
CHECK(result.ops.empty());
|
|
}
|
|
|
|
int main() {
|
|
testAnUndoThatLeftOurAppliedOutputAloneRepairsNothing();
|
|
testADriftedActiveModeAloneIsNotAReasonToDriveATrack();
|
|
|
|
testARecordTheReloadedModelLostIsHandedBackItsCapturedValues();
|
|
testARepairedFlagReturnsToItsCapturedValueNeverToOn();
|
|
testATrackNeitherStateRecordsIsNeverDrivenAtAll();
|
|
testASpentFlagHalfIsNotARecordToRepairFrom();
|
|
|
|
testASecondProjectNotedInBetweenDoesNotEraseTheFirstProjectsMemo();
|
|
testAProjectWithNoPriorMemoGetsNoRepair();
|
|
|
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
|
return g_fail ? 1 : 0;
|
|
}
|