// 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 #include #include 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 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; }