Defer Design View's per-FX park to an idle tick; honest undo mask, compare-before-write, PreventUIRefresh bracket, O(1) handle resolve
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
// Standalone tests for the deferred FX-park queue's re-entrancy rule — no
|
||||
// REAPER, no test framework.
|
||||
//
|
||||
// The property under test: a mode switch leaves its per-FX offline work here, so
|
||||
// a second switch arriving before the first drained must leave every track in the
|
||||
// state the SECOND switch specifies — never the first's, never both replayed.
|
||||
|
||||
#include "../src/shell/view/view_fx_park.h"
|
||||
|
||||
#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)
|
||||
|
||||
// -- helpers -----------------------------------------------------------------
|
||||
|
||||
// The ops applyMode hands a restore: one per FX captured in the track's snapshot.
|
||||
static std::vector<FxOfflineOp> ops(const std::string& fxGuid, bool offline) {
|
||||
return {FxOfflineOp{"{TRACK}", FxKeying::Identity, fxGuid, 0, offline}};
|
||||
}
|
||||
|
||||
static const FxParkIntent* intentFor(const FxParkQueue& q, const std::string& guid) {
|
||||
for (const FxParkIntent& i : q.pending())
|
||||
if (i.guid == guid) return &i;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// -- tests -------------------------------------------------------------------
|
||||
|
||||
static void testParkEnqueuesOneIntentCarryingNoOps() {
|
||||
FxParkQueue q;
|
||||
q.park("{A}");
|
||||
|
||||
CHECK(q.pending().size() == 1);
|
||||
const FxParkIntent* held = intentFor(q, "{A}");
|
||||
CHECK(held != nullptr);
|
||||
CHECK(held && held->park);
|
||||
CHECK(held && held->restoreOps.empty());
|
||||
}
|
||||
|
||||
static void testRestoreOnAnUndrainedParkCancelsRatherThanStacks() {
|
||||
// The park never ran, so the track's FX still hold their captured state —
|
||||
// exactly what the restore would write. Replaying both would unload every
|
||||
// plugin only to reload it.
|
||||
FxParkQueue q;
|
||||
q.park("{A}");
|
||||
q.restore("{A}", ops("{FX}", false));
|
||||
|
||||
CHECK(q.empty());
|
||||
}
|
||||
|
||||
static void testParkOnAnUndrainedRestoreCancelsRatherThanStacks() {
|
||||
// The mirror case: the restore never ran, so the FX are still parked offline,
|
||||
// which is where the new park wants them.
|
||||
FxParkQueue q;
|
||||
q.restore("{A}", ops("{FX}", false));
|
||||
q.park("{A}");
|
||||
|
||||
CHECK(q.empty());
|
||||
}
|
||||
|
||||
static void testRepeatedParkStaysOneIntent() {
|
||||
FxParkQueue q;
|
||||
q.park("{A}");
|
||||
q.park("{A}");
|
||||
q.park("{A}");
|
||||
|
||||
CHECK(q.pending().size() == 1);
|
||||
CHECK(intentFor(q, "{A}") && intentFor(q, "{A}")->park);
|
||||
}
|
||||
|
||||
static void testLaterRestoreReplacesTheEarlierOnesOps() {
|
||||
FxParkQueue q;
|
||||
q.restore("{A}", ops("{OLD}", false));
|
||||
q.restore("{A}", ops("{NEW}", true));
|
||||
|
||||
CHECK(q.pending().size() == 1);
|
||||
const FxParkIntent* held = intentFor(q, "{A}");
|
||||
CHECK(held && !held->park);
|
||||
CHECK(held && held->restoreOps.size() == 1);
|
||||
CHECK(held && held->restoreOps.front().fxGuid == "{NEW}");
|
||||
CHECK(held && held->restoreOps.front().offline);
|
||||
}
|
||||
|
||||
static void testOneTracksCancelLeavesEveryOtherTrackAlone() {
|
||||
FxParkQueue q;
|
||||
q.park("{A}");
|
||||
q.park("{B}");
|
||||
q.park("{C}");
|
||||
q.restore("{B}", ops("{FX}", false)); // cancels B only
|
||||
|
||||
CHECK(q.pending().size() == 2);
|
||||
CHECK(intentFor(q, "{A}") != nullptr);
|
||||
CHECK(intentFor(q, "{B}") == nullptr);
|
||||
CHECK(intentFor(q, "{C}") != nullptr);
|
||||
// Order survives the middle erase: the drain applies in enqueue order.
|
||||
CHECK(q.pending()[0].guid == "{A}");
|
||||
CHECK(q.pending()[1].guid == "{C}");
|
||||
}
|
||||
|
||||
static void testCancelledTrackCanBeQueuedAgain() {
|
||||
// Two rapid switches then a third: the third is the one that must land.
|
||||
FxParkQueue q;
|
||||
q.park("{A}");
|
||||
q.restore("{A}", ops("{FX}", false));
|
||||
q.park("{A}");
|
||||
|
||||
CHECK(q.pending().size() == 1);
|
||||
CHECK(intentFor(q, "{A}") && intentFor(q, "{A}")->park);
|
||||
}
|
||||
|
||||
static void testClearDropsEverythingPending() {
|
||||
FxParkQueue q;
|
||||
q.park("{A}");
|
||||
q.restore("{B}", ops("{FX}", true));
|
||||
q.clear();
|
||||
|
||||
CHECK(q.empty());
|
||||
CHECK(q.pending().empty());
|
||||
}
|
||||
|
||||
int main() {
|
||||
testParkEnqueuesOneIntentCarryingNoOps();
|
||||
testRestoreOnAnUndrainedParkCancelsRatherThanStacks();
|
||||
testParkOnAnUndrainedRestoreCancelsRatherThanStacks();
|
||||
testRepeatedParkStaysOneIntent();
|
||||
testLaterRestoreReplacesTheEarlierOnesOps();
|
||||
testOneTracksCancelLeavesEveryOtherTrackAlone();
|
||||
testCancelledTrackCanBeQueuedAgain();
|
||||
testClearDropsEverythingPending();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user