// 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 #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) // --- appVersion: exact-string fidelity, leading zero preserved ---------------- static void testVersionConstantRendersExactString() { // The Daniel-fixed string: EXACTLY "0.9.01", two-digit zero-padded patch. 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, and this // assertion fails against the actual CMake-configured value, not a re-derivation. CHECK(appVersion() == "0.9.01"); } // --- 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(); 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; }