Files
reasampler/tests/test_app_version.cpp
T
daniel 5c0e3a8ccf feat(version): V4 beta-in-isolation channel via -DREASAMPLER_CHANNEL
Compile-time channel flag forks a fully isolated reaper_reasampler_beta
(namespace, command-id prefix, action names, dock ident, -beta render) from
one auditable app_version definition. Stable identity byte-unchanged.
2026-07-26 16:43:23 -04:00

224 lines
10 KiB
C++

// Standalone tests for reasampler::app_version — no REAPER, no framework. Covers the
// Phase V (V1) pure version-identity core: the exact-string fidelity of the CMake-sourced
// constant (leading-zero preserved), the semver parse/compare arithmetic a within-channel
// forward migration leans on, and the three-way writing-version classification persist reads
// back from ext state (PreVersioning / Unknown / Stamped). The ext-state I/O (persist) and
// the show-version action (main) are DAW-verified shell.
#include "../src/app_version.h"
#include <cstdio>
#include <string>
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)
// --- appVersion: exact-string fidelity, leading zero preserved ----------------
//
// The channel is a COMPILE-TIME fact (REASAMPLER_CHANNEL_IS_BETA, threaded through the same
// configure_file'd header): the stable `build` tree compiles these tests with the flag = 0,
// the `build-beta` tree with = 1. So each config's ctest run validates ITS OWN channel's
// derivation. The channel-dependent assertions below branch on isBeta() so the ONE test
// source is correct in both configs — and each branch would fail if the derivation regressed
// (a beta build rendering "0.9.01" without the suffix, or stable rendering "-beta", trips it).
static void testVersionConstantRendersExactString() {
// The Daniel-fixed base string: EXACTLY "0.9.01", two-digit zero-padded patch — the
// numeric triple, IDENTICAL on both channels (it is also the stamp value). This is the
// whole point of sourcing the STRING (not reconstructing from numeric components): a
// normalized "0.9.1" here would be a leading-zero-fidelity regression. stampVersion()
// is the pure numeric triple regardless of channel.
CHECK(stampVersion() == "0.9.01");
}
// --- channel-derived rendering (V4) -------------------------------------------
static void testChannelDerivedRendering() {
// The DISPLAY render. Stable: exactly the numeric string. Beta: numeric + "-beta"
// (a plain suffix, V2). This is the show-version + panel-readout value. Each branch is
// the assertion the OTHER config's build would need to fail — i.e. a stable build that
// wrongly rendered "-beta", or a beta build that dropped it, is caught here.
if (isBeta()) {
CHECK(channel() == Channel::Beta);
CHECK(appVersion() == "0.9.01-beta");
} else {
CHECK(channel() == Channel::Stable);
CHECK(appVersion() == "0.9.01");
}
// The STAMP value is the numeric triple on BOTH channels — never suffixed — so it stays
// classifiable (see the stamp-classifiability test) and byte-identical to stable.
CHECK(stampVersion() == "0.9.01");
}
static void testChannelDerivedIdentityStrings() {
// Namespace, command-id prefix, action-name prefix, binary + dock idents all fork from
// the one channel bit. Stable values are BYTE-IDENTICAL to the pre-V4 build — any drift
// in the stable branch is a shipped-identity defect.
if (isBeta()) {
CHECK(extStateNamespace() == "reasampler_beta");
CHECK(commandIdPrefix() == "CEREBELLUM_REASAMPLER_BETA_");
CHECK(actionDisplayPrefix() == "ReaSampler beta: ");
CHECK(binaryName() == "reaper_reasampler_beta");
CHECK(dockTitle() == "ReaSampler Bank beta");
CHECK(dockIdent() == "reasampler_bank_panel_beta");
} else {
CHECK(extStateNamespace() == "reasampler");
CHECK(commandIdPrefix() == "CEREBELLUM_REASAMPLER_");
CHECK(actionDisplayPrefix() == "ReaSampler: ");
CHECK(binaryName() == "reaper_reasampler");
CHECK(dockTitle() == "ReaSampler Bank");
CHECK(dockIdent() == "reasampler_bank_panel");
}
}
static void testChannelQualifiedIdAndNameComposition() {
// The two composition helpers the shells funnel through. A representative shipped id
// (CAPTURE_TRACK) and phrase must compose to the exact channel-qualified strings — this
// is what guarantees stable rebuilds its shipped id and beta gets the isolated one.
if (isBeta()) {
CHECK(channelCommandId("CAPTURE_TRACK") == "CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK");
CHECK(channelActionName("capture selected track(s)") ==
"ReaSampler beta: capture selected track(s)");
} else {
CHECK(channelCommandId("CAPTURE_TRACK") == "CEREBELLUM_REASAMPLER_CAPTURE_TRACK");
CHECK(channelActionName("capture selected track(s)") ==
"ReaSampler: capture selected track(s)");
}
}
static void testStampClassifiesAsStampedOnOwnChannel() {
// The V4 stamp-classifiability requirement: the value a channel WRITES (stampVersion())
// must classify as Stamped when that same channel reads it back — on BOTH channels. A
// "-beta"-suffixed stamp would classify as Unknown, so this fails if a build ever stamped
// appVersion() instead of stampVersion().
WritingVersion wv = classifyWritingVersion(stampVersion());
CHECK(wv.kind == WritingVersion::Kind::Stamped);
CHECK(wv.raw == "0.9.01");
CHECK(wv.parsed.major == 0 && wv.parsed.minor == 9 && wv.parsed.patch == 1);
// The OTHER channel's raw stamp value is handled without throwing per the design: since
// both channels stamp the identical numeric triple, the other channel's value is the same
// "0.9.01" and also classifies Stamped. (The DISPLAY string "0.9.01-beta", by contrast,
// is intentionally NOT the stamp value — confirm it classifies Unknown, proving why the
// stamp must stay the numeric triple.)
CHECK(classifyWritingVersion("0.9.01-beta").kind == WritingVersion::Kind::Unknown);
}
// --- parseVersion: well-formed, leading zeros, and rejection ------------------
static void testParseWellFormed() {
auto v = parseVersion("0.9.01");
CHECK(v.has_value());
CHECK(v && v->major == 0 && v->minor == 9 && v->patch == 1); // "01" -> 1
auto w = parseVersion("1.10.2");
CHECK(w.has_value());
CHECK(w && w->major == 1 && w->minor == 10 && w->patch == 2);
}
static void testParseRejectsMalformed() {
CHECK(!parseVersion("").has_value()); // empty
CHECK(!parseVersion("1.2").has_value()); // too few components
CHECK(!parseVersion("1.2.3.4").has_value()); // too many components
CHECK(!parseVersion("1..2").has_value()); // empty middle component
CHECK(!parseVersion(".1.2").has_value()); // empty leading component
CHECK(!parseVersion("1.2.").has_value()); // empty trailing component
CHECK(!parseVersion("1.2.x").has_value()); // non-digit
CHECK(!parseVersion("-1.2.3").has_value()); // sign
CHECK(!parseVersion("1.2.3-beta").has_value()); // trailing garbage
CHECK(!parseVersion("v1.2.3").has_value()); // prefix
CHECK(!parseVersion(" 1.2.3").has_value()); // leading whitespace
}
// --- versionLess: NUMERIC ordering (10 > 9, not lexicographic) ----------------
static void testOrderingByPatch() {
// 0.9.02 > 0.9.01 (the per-test-build patch increment the spec calls out).
auto a = parseVersion("0.9.01");
auto b = parseVersion("0.9.02");
CHECK(a && b);
CHECK(a && b && versionLess(*a, *b));
CHECK(a && b && !versionLess(*b, *a));
}
static void testOrderingIsNumericNotLexicographic() {
// 0.10.01 > 0.9.02 — the case a string compare would get WRONG ("10" < "9"
// lexicographically). This assertion fails if compare regressed to string order.
auto a = parseVersion("0.9.02");
auto b = parseVersion("0.10.01");
CHECK(a && b);
CHECK(a && b && versionLess(*a, *b));
// And a major bump outranks a large minor: 1.0.0 > 0.99.99.
auto c = parseVersion("0.99.99");
auto d = parseVersion("1.0.0");
CHECK(c && d && versionLess(*c, *d));
}
static void testOrderingIrreflexive() {
// Equal versions are not less-than either way.
auto a = parseVersion("0.9.01");
auto b = parseVersion("0.9.01");
CHECK(a && b && !versionLess(*a, *b));
CHECK(a && b && !versionLess(*b, *a));
}
// --- classifyWritingVersion: the three-way stamp classification ----------------
static void testAbsentStampIsPreVersioning() {
// An absent key -> getProjExtStateString returns "" -> PreVersioning (a project
// saved before this feature). Silent, not an error, no throw.
WritingVersion wv = classifyWritingVersion("");
CHECK(wv.kind == WritingVersion::Kind::PreVersioning);
CHECK(wv.raw.empty());
}
static void testMalformedStampIsUnknown() {
// A present-but-unparseable stamp -> Unknown (ignored, never a throw). Keeps the raw
// value for diagnostics but does not pretend it is a version.
WritingVersion wv = classifyWritingVersion("garbage-not-a-version");
CHECK(wv.kind == WritingVersion::Kind::Unknown);
CHECK(wv.raw == "garbage-not-a-version");
}
static void testWellFormedStampIsStamped() {
// A well-formed stamp -> Stamped, carrying the EXACT stored string plus the parsed
// triple for comparison. Round-trips the current version through classify.
WritingVersion wv = classifyWritingVersion("0.9.01");
CHECK(wv.kind == WritingVersion::Kind::Stamped);
CHECK(wv.raw == "0.9.01");
CHECK(wv.parsed.major == 0 && wv.parsed.minor == 9 && wv.parsed.patch == 1);
}
static void testStampedStampIsOrderableAgainstCurrent() {
// The migration use case: a stamp read back is comparable to the running build. An
// older stamp (0.9.01) precedes a newer one (0.9.05) numerically.
WritingVersion older = classifyWritingVersion("0.9.01");
WritingVersion newer = classifyWritingVersion("0.9.05");
CHECK(older.kind == WritingVersion::Kind::Stamped);
CHECK(newer.kind == WritingVersion::Kind::Stamped);
CHECK(versionLess(older.parsed, newer.parsed));
}
int main() {
testVersionConstantRendersExactString();
testChannelDerivedRendering();
testChannelDerivedIdentityStrings();
testChannelQualifiedIdAndNameComposition();
testStampClassifiesAsStampedOnOwnChannel();
testParseWellFormed();
testParseRejectsMalformed();
testOrderingByPatch();
testOrderingIsNumericNotLexicographic();
testOrderingIrreflexive();
testAbsentStampIsPreVersioning();
testMalformedStampIsUnknown();
testWellFormedStampIsStamped();
testStampedStampIsOrderableAgainstCurrent();
if (g_fail == 0) std::printf("app_version: all tests passed\n");
else std::printf("app_version: %d CHECK(s) FAILED\n", g_fail);
return g_fail == 0 ? 0 : 1;
}