feat(version): single-source semver, ext-state stamp, show-version action
Version "0.9.01" sourced once from CMake via configure_file'd header; pure app_version module (parse/compare/classify) with CTest. Stamp rides the persist save seam under "reasampler"/"version"; absent stamp reads as pre-versioning. On-demand "show version" action, no startup print.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// 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 ----------------
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user