Merge plugin-editor drop: a card dragged onto a third-party plugin's editor is handed to the OS as a file drop

This commit is contained in:
2026-09-25 14:15:47 -04:00
19 changed files with 992 additions and 135 deletions
+154 -21
View File
@@ -3,8 +3,9 @@
//
// Covers:
// * The full class matrix: every ReaperSurface x {single, multi} x {track, no track} x
// {over a host window, off it}, inside and outside the client rect, plus the half-open edge
// and a non-zero panel origin.
// {over a host window, off it} x {plugin code under the pointer, not}, inside and outside the
// client rect, plus the half-open edge and a non-zero panel origin.
// * The OS drag loop's per-turn verdict (decideOsDragTurn), exhaustively.
// * Reversibility and speed-independence, stated as properties: a class transition sequence
// resolves the same forwards and backwards, and one unresolvable evaluation cannot change
// any later evaluation's outcome.
@@ -50,6 +51,16 @@ static DropContext offHost(ReaperSurface surface = ReaperSurface::Other, bool si
return c;
}
// The same drag with the shell's plugin-code proof set: the window under the pointer is a plugin's
// own editor. Defaults to where it matters — an FX surface REAPER attributed to a track.
static DropContext overPlugin(ReaperSurface surface = ReaperSurface::FxSurface, bool single = true,
bool haveTrack = true, bool offHostToo = false) {
DropContext c = ctx(surface, single, haveTrack);
c.pointerOverPluginEditor = true;
c.pointerOffHost = offHostToo;
return c;
}
// Every surface the law enumerates, so the matrix tests iterate rather than list.
static const ReaperSurface kAllSurfaces[] = {
ReaperSurface::TrackPanel, ReaperSurface::FxSurface, ReaperSurface::FxEmbed,
@@ -78,10 +89,11 @@ static void testInsideClientIsAlwaysInternal() {
CHECK(decideDropClass(200, 150, kPanel, ctx(s, single)) == DropClass::Internal);
CHECK(decideDropClass(0, 0, kPanel, ctx(s, single)) == DropClass::Internal);
CHECK(decideDropClass(399, 299, kPanel, ctx(s, single)) == DropClass::Internal);
// Even a stale off-host proof cannot reinterpret a point inside our own client rect:
// the inside-client answer is resolved ahead of the hand-off gate, so re-entering the
// panel always resumes the bank-to-bank gesture.
// Even a stale proof of either kind cannot reinterpret a point inside our own client
// rect: the inside-client answer is resolved ahead of both hand-off gates, so
// re-entering the panel always resumes the bank-to-bank gesture.
CHECK(decideDropClass(200, 150, kPanel, offHost(s, single)) == DropClass::Internal);
CHECK(decideDropClass(200, 150, kPanel, overPlugin(s, single)) == DropClass::Internal);
}
}
}
@@ -119,7 +131,8 @@ static void testSingleOverTrackPanelIsInstrumentDrop() {
DropClass::InstrumentDrop);
}
// The FX chain / floating-FX windows keep their existing outcome.
// The FX chain / floating-FX windows' own chrome — REAPER's preset bar, the chain list — keeps its
// existing outcome: no plugin-code proof, so the instrument drop.
static void testSingleOverFxSurfaceIsInstrumentDrop() {
CHECK(decideDropClass(kOutX, kOutY, kPanel, ctx(ReaperSurface::FxSurface)) ==
DropClass::InstrumentDrop);
@@ -198,7 +211,8 @@ static void testNullTrackWithSurfaceRefuses() {
// --- The hand-off gate ---------------------------------------------------------
// THE REGRESSION FLOOR. No reading of REAPER's hit-test can reach the modal OLE loop while the
// pointer is over a host window — asserted over EVERY surface the probe can report, both payload
// pointer is over a host window and no plugin code owns it (ctx() sets neither proof) — asserted
// over EVERY surface the probe can report, both payload
// sizes and both track verdicts, not just the toolbar/transport cell that was reported. The
// defect was structural (an unnamed token read as "the user left REAPER"), so the guarantee has
// to be structural too.
@@ -231,9 +245,10 @@ static void testOffHostHandsOffOnlyWhereReaperNamedNothing() {
}
// The shipped exception the token condition exists for: REAPER runs a bridged plugin's UI in
// reaper_host*.exe, so the window-ownership test reports off-host over a floating bridged FX
// editor even though the pointer never left REAPER. There the recognised token vetoes the
// hand-off and the surface's own REAPER-internal outcome stands — identical to the native case.
// reaper_host*.exe, so the process test reports off-host over a bridged FX window even though the
// pointer never left REAPER. The recognised token vetoes the PROCESS route, so on its own that
// proof changes nothing over a named surface — whether the bridged editor then hands off is the
// plugin-code route's call alone (testBridgedEditorHandsOffThroughThePluginRoute).
static void testOffHostOverANamedSurfaceKeepsTheReaperOutcome() {
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost(ReaperSurface::FxSurface, true, true)) ==
DropClass::InstrumentDrop);
@@ -264,13 +279,71 @@ static void testOffHostOverANamedSurfaceKeepsTheReaperOutcome() {
}
}
// --- The plugin-editor route ---------------------------------------------------
// THE FEATURE: over a plugin's own editor inside an FX window, the files go to the plugin through
// the OS — whatever the payload size and whether or not REAPER attributed a track, since the
// plugin, not a track, is the receiver.
static void testPluginEditorOverFxSurfaceHandsOff() {
for (bool single : {true, false}) {
for (bool haveTrack : {true, false}) {
CHECK(decideDropClass(kOutX, kOutY, kPanel,
overPlugin(ReaperSurface::FxSurface, single, haveTrack)) ==
DropClass::OsHandoff);
}
}
}
// The same point WITHOUT the proof is the FX window's chrome: the instrument drop for a single
// card, the multi refusal otherwise. The proof is the whole difference, in both directions.
static void testSameFxSurfaceWithoutTheProofKeepsItsOutcome() {
CHECK(decideDropClass(kOutX, kOutY, kPanel, ctx(ReaperSurface::FxSurface)) ==
DropClass::InstrumentDrop);
CHECK(decideDropClass(kOutX, kOutY, kPanel, overPlugin(ReaperSurface::FxSurface)) ==
DropClass::OsHandoff);
CHECK(decideDropClass(kOutX, kOutY, kPanel, ctx(ReaperSurface::FxSurface, false)) ==
DropClass::Refuse);
CHECK(decideDropClass(kOutX, kOutY, kPanel, overPlugin(ReaperSurface::FxSurface, false)) ==
DropClass::OsHandoff);
}
// Plugin code under the pointer anywhere REAPER did not name an FX surface — the TCP, the MCP, the
// embed strip, the arrange, an extension window docked in REAPER (Other) — changes nothing: the
// token confines the route to FX windows. Asserted over every such cell, on-host and off.
static void testPluginProofOffAnFxSurfaceChangesNothing() {
for (ReaperSurface s : kAllSurfaces) {
if (s == ReaperSurface::FxSurface) continue;
for (bool single : {true, false}) {
for (bool haveTrack : {true, false}) {
for (bool off : {false, true}) {
const DropContext without =
off ? offHost(s, single, haveTrack) : ctx(s, single, haveTrack);
CHECK(decideDropClass(kOutX, kOutY, kPanel,
overPlugin(s, single, haveTrack, off)) ==
decideDropClass(kOutX, kOutY, kPanel, without));
}
}
}
}
}
// A bridged editor: the process proof AND the plugin proof are both set, REAPER names the FX
// window. The token vetoed the process route; the plugin route still hands off.
static void testBridgedEditorHandsOffThroughThePluginRoute() {
for (bool haveTrack : {true, false}) {
CHECK(decideDropClass(kOutX, kOutY, kPanel,
overPlugin(ReaperSurface::FxSurface, true, haveTrack,
/*offHostToo=*/true)) == DropClass::OsHandoff);
}
}
// --- Not-a-drag ----------------------------------------------------------------
// No armed samples, or not dragging -> None regardless of position or surface.
static void testNoDragOrNoSamplesIsNone() {
for (ReaperSurface s : kAllSurfaces) {
for (bool off : {false, true}) {
DropContext c = off ? offHost(s) : ctx(s);
for (int proof = 0; proof < 3; ++proof) { // none, off-host, plugin editor
DropContext c = proof == 1 ? offHost(s) : proof == 2 ? overPlugin(s) : ctx(s);
c.drag = DragState{/*dragging=*/true, /*hasArmedSamples=*/false};
CHECK(decideDropClass(kOutX, kOutY, kPanel, c) == DropClass::None);
CHECK(decideDropClass(200, 150, kPanel, c) == DropClass::None);
@@ -299,12 +372,22 @@ static void testClassTransitionsAreReversible() {
CHECK(decideDropClass(kOutX, kOutY, kPanel, arrange) == DropClass::ArrangeInsert);
CHECK(decideDropClass(200, 150, kPanel, inside) == DropClass::Internal);
// The hand-off leg is irreversible only because the SHELL goes modal on it. The law itself
// stays stateless across it: evaluating an off-host context leaves the next in-REAPER
// evaluation exactly where it was.
// The hand-off legs stay stateless too: evaluating an off-host or plugin-editor context leaves
// the next in-REAPER evaluation exactly where it was — which is what lets the OS loop withdraw
// on the law's own answer (decideOsDragTurn) rather than on anything remembered.
CHECK(decideDropClass(kOutX, kOutY, kPanel, offHost()) == DropClass::OsHandoff);
CHECK(decideDropClass(kOutX, kOutY, kPanel, arrange) == DropClass::ArrangeInsert);
CHECK(decideDropClass(200, 150, kPanel, inside) == DropClass::Internal);
// Through a plugin's editor and back out over its own window's chrome: the instrument drop is
// still there after the pass, and so is everything after it.
CHECK(decideDropClass(kOutX, kOutY, kPanel, overPlugin()) == DropClass::OsHandoff);
CHECK(decideDropClass(kOutX, kOutY, kPanel, fx) == DropClass::InstrumentDrop);
CHECK(decideDropClass(kOutX, kOutY, kPanel, ctx(ReaperSurface::TrackPanel)) ==
DropClass::InstrumentDrop);
CHECK(decideDropClass(kOutX, kOutY, kPanel, arrange) == DropClass::ArrangeInsert);
CHECK(decideDropClass(kOutX, kOutY, kPanel, overPlugin()) == DropClass::OsHandoff);
CHECK(decideDropClass(200, 150, kPanel, inside) == DropClass::Internal);
}
// One unresolvable evaluation (a surface with no track, which refuses) followed by a resolvable
@@ -331,10 +414,11 @@ static void testDragSpeedCannotChangeTheOutcome() {
const DropContext destination = ctx(ReaperSurface::TrackPanel);
const DropClass flick = decideDropClass(kOutX, kOutY, kPanel, destination);
// The slow path crosses everything else first, off-host legs included.
// The slow path crosses everything else first, off-host and plugin-editor legs included.
for (ReaperSurface s : kAllSurfaces) {
(void)decideDropClass(kOutX - 10, kOutY, kPanel, ctx(s));
(void)decideDropClass(kOutX - 10, kOutY, kPanel, offHost(s));
(void)decideDropClass(kOutX - 10, kOutY, kPanel, overPlugin(s));
(void)decideDropClass(200, 150, kPanel, ctx(s)); // and back through the client
}
CHECK(decideDropClass(kOutX, kOutY, kPanel, destination) == flick);
@@ -350,11 +434,14 @@ static void testNoLiveOutsideCombinationResolvesToNone() {
for (bool single : {true, false}) {
for (bool haveTrack : {true, false}) {
for (bool off : {false, true}) {
const DropClass c = decideDropClass(
kOutX, kOutY, kPanel,
off ? offHost(s, single, haveTrack) : ctx(s, single, haveTrack));
CHECK(c != DropClass::None);
CHECK(c != DropClass::Internal);
for (bool plugin : {false, true}) {
DropContext c0 =
off ? offHost(s, single, haveTrack) : ctx(s, single, haveTrack);
c0.pointerOverPluginEditor = plugin;
const DropClass c = decideDropClass(kOutX, kOutY, kPanel, c0);
CHECK(c != DropClass::None);
CHECK(c != DropClass::Internal);
}
}
}
}
@@ -490,6 +577,44 @@ static void testAllSkippedSelectionKeepsInternalDrag() {
CHECK(!h.handOffToOs);
}
// --- The OS drag loop, once handed off ---------------------------------------------
// The whole truth table, one cell per line so a regression names its cell.
static void testOsDragTurnTruthTable() {
// Button held: stay with the OS while the law still hands off, withdraw the moment it doesn't.
CHECK(decideOsDragTurn(false, true, true) == OsDragTurn::Continue);
CHECK(decideOsDragTurn(false, true, false) == OsDragTurn::Withdraw);
// Released: the OS drops only where the law hands off; over a REAPER surface the release is
// given back to the panel, so REAPER's own outcome runs instead of a CF_HDROP import.
CHECK(decideOsDragTurn(false, false, true) == OsDragTurn::Drop);
CHECK(decideOsDragTurn(false, false, false) == OsDragTurn::WithdrawAtRelease);
// Escape ends the drag outright in every cell — never read as a release, never a withdrawal
// that would resume a drag the user just cancelled.
CHECK(decideOsDragTurn(true, true, true) == OsDragTurn::Cancel);
CHECK(decideOsDragTurn(true, true, false) == OsDragTurn::Cancel);
CHECK(decideOsDragTurn(true, false, true) == OsDragTurn::Cancel);
CHECK(decideOsDragTurn(true, false, false) == OsDragTurn::Cancel);
}
// End to end with the law: a hand-off started over a plugin's editor, then the pointer moves onto
// that FX window's chrome with the button held — withdrawn, and the resumed drag's release there is
// the instrument drop, not an OS drop. Moving back onto the editor keeps the OS drag.
static void testPassOverAPluginEditorIsWithdrawnOnReaperSurfaces() {
auto handsOff = [](const DropContext& c) {
return decideDropClass(kOutX, kOutY, kPanel, c) == DropClass::OsHandoff;
};
CHECK(decideOsDragTurn(false, true, handsOff(overPlugin())) == OsDragTurn::Continue);
CHECK(decideOsDragTurn(false, true, handsOff(ctx(ReaperSurface::FxSurface))) ==
OsDragTurn::Withdraw);
CHECK(decideDropClass(kOutX, kOutY, kPanel, ctx(ReaperSurface::FxSurface)) ==
DropClass::InstrumentDrop);
CHECK(decideOsDragTurn(false, false, handsOff(ctx(ReaperSurface::Arrange))) ==
OsDragTurn::WithdrawAtRelease);
CHECK(decideOsDragTurn(false, false, handsOff(overPlugin())) == OsDragTurn::Drop);
// Leaving REAPER entirely is handed off by the other route and held the same way.
CHECK(decideOsDragTurn(false, true, handsOff(offHost())) == OsDragTurn::Continue);
}
int main() {
testInsideClientIsAlwaysInternal();
testBoundaryHalfOpen();
@@ -513,6 +638,11 @@ int main() {
testOffHostOverANamedSurfaceKeepsTheReaperOutcome();
testNoDragOrNoSamplesIsNone();
testPluginEditorOverFxSurfaceHandsOff();
testSameFxSurfaceWithoutTheProofKeepsItsOutcome();
testPluginProofOffAnFxSurfaceChangesNothing();
testBridgedEditorHandsOffThroughThePluginRoute();
testClassTransitionsAreReversible();
testUnresolvableEvaluationDoesNotAffectTheNext();
testDragSpeedCannotChangeTheOutcome();
@@ -532,6 +662,9 @@ int main() {
testResolvablePayloadHandsOff();
testAllSkippedSelectionKeepsInternalDrag();
testOsDragTurnTruthTable();
testPassOverAPluginEditorIsWithdrawnOnReaperSurfaces();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
}
+237
View File
@@ -0,0 +1,237 @@
// Standalone tests for reasampler::ui::window_origin — no REAPER, no Win32, no test framework.
// Asserts the per-window code fold and the chain rule the plugin-editor hand-off proof rests on.
#include "../src/core/ui/window_origin.h"
#include <cstdio>
#include <string>
#include <vector>
using namespace reasampler::ui;
static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
static const std::string kWinDir = "C:\\Windows";
static WindowFacts inModule(const std::string& path, bool host = false) {
WindowFacts f;
f.procResolved = true;
f.hostModule = host;
f.modulePath = path;
return f;
}
static WindowFacts ownModule(const std::string& path) {
WindowFacts f;
f.procResolved = true;
f.modulePath = path;
f.ownModule = true;
return f;
}
// --- classifyWindowCode --------------------------------------------------------
// REAPER's own chrome and containers: the class procedure lives in the executable.
static void testHostExecutableIsHost() {
CHECK(classifyWindowCode(inModule("C:\\Program Files\\REAPER (x64)\\reaper.exe", true),
kWinDir) == WindowCode::Host);
}
// Standard controls and the dialog class are implemented by OS modules, wherever the OS keeps
// them — System32, SysWOW64 for a 32-bit host, and the side-by-side store comctl32 v6 loads from.
static void testOsModulesAreSystem() {
CHECK(classifyWindowCode(inModule("C:\\Windows\\System32\\user32.dll"), kWinDir) ==
WindowCode::System);
CHECK(classifyWindowCode(inModule("C:\\Windows\\SysWOW64\\user32.dll"), kWinDir) ==
WindowCode::System);
CHECK(classifyWindowCode(
inModule("C:\\Windows\\WinSxS\\amd64_microsoft.windows.common-controls_6595b64144ccf1df"
"_6.0.26100.1_none_x\\comctl32.dll"),
kWinDir) == WindowCode::System);
}
// The same directory spelled differently by two APIs still compares equal: case, separator
// direction, a trailing separator on the root, and the long-path prefix.
static void testSystemMatchIgnoresSpelling() {
CHECK(classifyWindowCode(inModule("c:/WINDOWS/system32/USER32.DLL"), kWinDir) ==
WindowCode::System);
CHECK(classifyWindowCode(inModule("C:\\Windows\\System32\\user32.dll"), "C:\\WINDOWS\\") ==
WindowCode::System);
CHECK(classifyWindowCode(inModule("\\\\?\\C:\\Windows\\System32\\user32.dll"), kWinDir) ==
WindowCode::System);
}
// Containment, not string prefix: a sibling directory whose name merely starts with the OS
// directory's is plugin territory, and so is the OS directory itself named as a file path.
static void testSiblingDirectoryIsNotSystem() {
CHECK(classifyWindowCode(inModule("C:\\WindowsApps\\thing.dll"), kWinDir) ==
WindowCode::Plugin);
CHECK(classifyWindowCode(inModule("C:\\Windows2\\thing.dll"), kWinDir) == WindowCode::Plugin);
CHECK(classifyWindowCode(inModule("C:\\Windows"), kWinDir) == WindowCode::Plugin);
}
// A plugin binary anywhere outside the OS directory — the VST3 bundle, a VST2 folder, a helper
// DLL a plugin loads beside itself — is Plugin.
static void testPluginBinariesArePlugin() {
CHECK(classifyWindowCode(
inModule("C:\\Program Files\\Common Files\\VST3\\Serum2.vst3\\Contents\\x86_64-win\\"
"Serum2.vst3"),
kWinDir) == WindowCode::Plugin);
CHECK(classifyWindowCode(inModule("D:\\VstPlugins\\Qt5Widgets.dll"), kWinDir) ==
WindowCode::Plugin);
}
// Another process's window is plugin code by the process boundary alone, whatever its other facts
// claim: a procedure address from another process resolved in ours is meaningless.
static void testOtherProcessIsPluginRegardlessOfModule() {
WindowFacts f = inModule("C:\\Program Files\\REAPER (x64)\\reaper.exe", true);
f.otherProcess = true;
CHECK(classifyWindowCode(f, kWinDir) == WindowCode::Plugin);
WindowFacts g;
g.otherProcess = true; // nothing resolved at all
CHECK(classifyWindowCode(g, kWinDir) == WindowCode::Plugin);
}
// No module (a charset-mismatch handle, generated code) is no evidence — never Plugin.
static void testUnresolvedProcIsUnknown() {
WindowFacts f;
f.modulePath = "C:\\VST\\x.dll"; // ignored without a resolved procedure
CHECK(classifyWindowCode(f, kWinDir) == WindowCode::Unknown);
}
// Without the OS directory a standard control cannot be told from plugin code, so every non-host
// module degrades to Unknown — the failure must not read REAPER's buttons as a plugin.
static void testMissingSystemRootNeverYieldsPlugin() {
CHECK(classifyWindowCode(inModule("C:\\Windows\\System32\\user32.dll"), "") ==
WindowCode::Unknown);
CHECK(classifyWindowCode(inModule("C:\\VST\\x.dll"), "") == WindowCode::Unknown);
CHECK(classifyWindowCode(inModule("C:\\VST\\x.dll"), "\\") == WindowCode::Unknown);
CHECK(classifyWindowCode(inModule("C:\\REAPER\\reaper.exe", true), "") == WindowCode::Host);
}
// Our own ReaSampler 9000 module — stable or beta, whichever the shell set ownModule for — is
// Instrument, not Plugin: FX chrome we own, so a drag over it must not hand off to the OS.
static void testOwnModuleIsInstrument() {
CHECK(classifyWindowCode(
ownModule("C:\\Program Files\\Common Files\\VST3\\reasampler_9000.vst3"), kWinDir) ==
WindowCode::Instrument);
CHECK(classifyWindowCode(
ownModule("C:\\Program Files\\Common Files\\VST3\\reasampler_9000_beta.vst3"),
kWinDir) == WindowCode::Instrument);
}
// --- isPluginEditorChain -------------------------------------------------------
// Pointer on REAPER's FX-window chrome: a standard control or a REAPER container, under the
// dialog-class root. No plugin code anywhere in the chain.
static void testChromeChainIsNotPlugin() {
CHECK(!isPluginEditorChain({WindowCode::System, WindowCode::Host, WindowCode::System}));
CHECK(!isPluginEditorChain({WindowCode::Host, WindowCode::System}));
CHECK(!isPluginEditorChain({WindowCode::Host}));
}
// Pointer on the plugin's editor window itself.
static void testPluginWindowChainIsPlugin() {
CHECK(isPluginEditorChain({WindowCode::Plugin, WindowCode::Host, WindowCode::System}));
}
// A standard control INSIDE the plugin's editor (a native text box) is System on its own; the
// plugin's window above it is what makes the point the plugin's.
static void testStandardControlInsidePluginIsPlugin() {
CHECK(isPluginEditorChain(
{WindowCode::System, WindowCode::Plugin, WindowCode::Host, WindowCode::System}));
}
// A bridged plugin's editor embedded across the process boundary: the hit window is Plugin by
// process, REAPER's container and root above it are in-process.
static void testBridgedEmbeddedEditorIsPlugin() {
WindowFacts bridged;
bridged.otherProcess = true;
CHECK(isPluginEditorChain({classifyWindowCode(bridged, kWinDir), WindowCode::Host,
WindowCode::System}));
}
// A pointer over our own instrument's editor — Instrument code, not Plugin — is not hand-off
// proof: it stays REAPER's FX chrome, unlike a genuine plugin editor (testPluginWindowChainIsPlugin).
static void testOwnInstrumentChainIsNotPlugin() {
CHECK(!isPluginEditorChain({WindowCode::Instrument, WindowCode::Host, WindowCode::System}));
}
// Another plugin's editor sitting alongside our own instrument in the same chain is still proof —
// only OUR module is exempted, nobody else's.
static void testOtherPluginBesideOwnInstrumentIsStillPlugin() {
CHECK(isPluginEditorChain(
{WindowCode::Plugin, WindowCode::Instrument, WindowCode::Host, WindowCode::System}));
}
// Unknown anywhere, and nothing else, is not proof; an empty chain (no window at all) is not.
static void testUnknownAndEmptyChainsAreNotPlugin() {
CHECK(!isPluginEditorChain({WindowCode::Unknown, WindowCode::Host, WindowCode::System}));
CHECK(!isPluginEditorChain({WindowCode::Unknown}));
CHECK(!isPluginEditorChain({}));
}
// --- isOwnModulePath -----------------------------------------------------------
static void testOwnModulePathCaseFolds() {
CHECK(isOwnModulePath("C:\\VST3\\reasampler_9000.vst3", "reasampler_9000.vst3"));
CHECK(isOwnModulePath("C:\\VST3\\ReaSampler_9000.VST3", "reasampler_9000.vst3"));
}
static void testOwnModulePathBundlePathForwardAndBackSlash() {
CHECK(isOwnModulePath(
"C:\\VST3\\reasampler_9000.vst3\\Contents\\x86_64-win\\reasampler_9000.vst3",
"reasampler_9000.vst3"));
CHECK(isOwnModulePath(
"C:/VST3/reasampler_9000.vst3/Contents/x86_64-win/reasampler_9000.vst3",
"reasampler_9000.vst3"));
}
static void testOwnModulePathStableAndBetaDoNotMatchEachOther() {
CHECK(!isOwnModulePath("C:\\VST3\\reasampler_9000_beta.vst3", "reasampler_9000.vst3"));
CHECK(!isOwnModulePath("C:\\VST3\\reasampler_9000.vst3", "reasampler_9000_beta.vst3"));
}
static void testOwnModulePathBareFilenameWithNoSeparatorMatches() {
CHECK(isOwnModulePath("reasampler_9000.vst3", "reasampler_9000.vst3"));
}
static void testOwnModulePathEmptyPathDoesNotMatch() {
CHECK(!isOwnModulePath("", "reasampler_9000.vst3"));
}
static void testOwnModulePathTrailingSeparatorDoesNotMatch() {
CHECK(!isOwnModulePath("C:\\VST3\\reasampler_9000.vst3\\", "reasampler_9000.vst3"));
}
int main() {
testHostExecutableIsHost();
testOsModulesAreSystem();
testSystemMatchIgnoresSpelling();
testSiblingDirectoryIsNotSystem();
testPluginBinariesArePlugin();
testOtherProcessIsPluginRegardlessOfModule();
testUnresolvedProcIsUnknown();
testMissingSystemRootNeverYieldsPlugin();
testOwnModuleIsInstrument();
testChromeChainIsNotPlugin();
testPluginWindowChainIsPlugin();
testStandardControlInsidePluginIsPlugin();
testBridgedEmbeddedEditorIsPlugin();
testOwnInstrumentChainIsNotPlugin();
testOtherPluginBesideOwnInstrumentIsStillPlugin();
testUnknownAndEmptyChainsAreNotPlugin();
testOwnModulePathCaseFolds();
testOwnModulePathBundlePathForwardAndBackSlash();
testOwnModulePathStableAndBetaDoNotMatchEachOther();
testOwnModulePathBareFilenameWithNoSeparatorMatches();
testOwnModulePathEmptyPathDoesNotMatch();
testOwnModulePathTrailingSeparatorDoesNotMatch();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
}