Files
Dario LipicarandClaude Opus 4.8 202af6fa0f fix: use the shared semver implementation, and stop clobbering newer packages (#25)
* fix: use the shared semver implementation, and stop clobbering newer packages

versionGreaterOrEqual split on '.' and atoi()'d each component, so
"1.0.0-rc1" parsed as 1.0.0 and a pre-release compared EQUAL to its own
release. `install --skip-if-not-newer` therefore refused to upgrade a
1.0.0-rc1 to the real 1.0.0. It now delegates to the shared implementation
in logos-package (include/logos/semver.hpp); the lgpm_version_gte C ABI is
unchanged.

`install --dir` sorted the .lgx files by FILENAME, lexicographically. Since
installPluginFile() overwrites, the last install of a given package wins --
and "foo-1.10.0.lgx" sorts before "foo-1.9.0.lgx" ("1" < "9"), so the older
1.9.0 was installed last and clobbered the newer 1.10.0. Order by the
package's real (name, version) read from the archive instead, version
ascending, so the newest of each package lands last.

tests/test_version.cpp had ten cases and not one pre-release among them.
Adds coverage for the ordering this gate actually depends on.

Requires logos-package#30; flake.lock is pinned at that branch's head and
must be re-pinned to master once it merges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: re-pin logos-package (Linux <cstdint> build fix)

The previous pin predated the <cstdint> include that cpp-semver needs on
libstdc++, so the Linux jobs failed to compile logos/semver.hpp.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: cover the no-dot pre-release regression (1.0.0-rc1)

Addresses Copilot review on #25.

The old atoi() split failed specifically on a single-token pre-release with
no dot: '1.0.0-rc1' -> ['1','0','0-rc1'], atoi('0-rc1') == 0, so it compared
equal to '1.0.0'. The existing tests only used 'rc.1' (with a dot), whose
third component is a clean '0' and wouldn't reproduce the bug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: re-pin logos-package to merged master (#30)

logos-co/logos-package#30 merged; move the branch-head pin to master
(8d4236f).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 10:12:05 -03:00

104 lines
4.5 KiB
C++

#include <gtest/gtest.h>
#include "package_manager_lib.h"
TEST(VersionTest, EqualVersions) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "1.0.0"));
}
TEST(VersionTest, GreaterMajor) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("2.0.0", "1.0.0"));
}
TEST(VersionTest, GreaterMinor) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.2.0", "1.1.0"));
}
TEST(VersionTest, GreaterPatch) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.2", "1.0.1"));
}
TEST(VersionTest, LesserMajor) {
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "2.0.0"));
}
TEST(VersionTest, LesserMinor) {
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "1.1.0"));
}
TEST(VersionTest, LesserPatch) {
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "1.0.1"));
}
TEST(VersionTest, DifferentLengths) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "1.0"));
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0", "1.0.0"));
}
TEST(VersionTest, ShortVersionGreater) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("2.0", "1.0.0"));
}
TEST(VersionTest, EmptyVersions) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("", ""));
}
// ─────────────────────────── pre-releases ───────────────────────────
//
// versionGreaterOrEqual now delegates to the shared implementation in
// logos-package (include/logos/semver.hpp). It used to split on '.' and atoi()
// each component, so "1.0.0-rc1" parsed as 1.0.0 and every pre-release compared
// EQUAL to its own release — meaning `install --skip-if-not-newer` refused to
// upgrade a 1.0.0-rc1 to the real 1.0.0.
//
// Exhaustive precedence coverage lives in logos-package's test_semver.cpp; these
// pin the behaviour this gate actually depends on.
TEST(VersionTest, ReleaseIsNewerThanItsPreRelease) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "1.0.0-rc.1"));
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0-rc.1", "1.0.0"));
}
// The exact regression the old atoi() split produced: a single-token
// pre-release with NO dot. "1.0.0-rc1" split on '.' gave ["1","0","0-rc1"],
// and atoi("0-rc1") == 0, so it compared EQUAL to "1.0.0". "rc.1" (with a dot)
// wouldn't have reproduced it — the third component would have been a clean
// "0". Keep this distinct case so a future regression here is caught.
TEST(VersionTest, SingleTokenPreReleaseIsNewerThanRelease) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "1.0.0-rc1"));
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0-rc1", "1.0.0"));
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0-beta2", "1.0.0-beta1"));
}
// The reported bug: numeric pre-release identifiers compare numerically, so
// rc.11 is newer than rc.2 (a plain string compare says the opposite).
TEST(VersionTest, NumericPreReleaseIdentifiersCompareNumerically) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0-rc.11", "1.0.0-rc.2"));
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0-rc.2", "1.0.0-rc.11"));
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0-beta.11", "1.0.0-beta.2"));
}
TEST(VersionTest, PreReleaseOrderingAcrossIdentifiers) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0-beta", "1.0.0-alpha"));
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0-rc.1", "1.0.0-beta.11"));
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("1.0.0-alpha", "1.0.0-beta"));
}
TEST(VersionTest, EqualPreReleasesAreGreaterOrEqual) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0-rc.1", "1.0.0-rc.1"));
}
// Build metadata is ignored for precedence (spec §10), so these are "equal" and
// the gate treats an incoming build as not-newer.
TEST(VersionTest, BuildMetadataIgnored) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0+build.2", "1.0.0+build.1"));
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0+build.1", "1.0.0+build.2"));
}
// Comparison stays lenient about partial versions (see DifferentLengths above);
// unparseable junk sorts below anything real, so a garbage installed version
// never blocks a genuine upgrade.
TEST(VersionTest, UnparseableVersionsSortLowest) {
EXPECT_TRUE(PackageManagerLib::versionGreaterOrEqual("1.0.0", "banana"));
EXPECT_FALSE(PackageManagerLib::versionGreaterOrEqual("banana", "1.0.0"));
}