using DodoSSH.Client.App.Platform; namespace DodoSSH.Client.App.Tests; /// /// RpmVersion.Compare against the answers rpm itself gives. /// /// /// /// Every expected value here was produced by rpm and not by reading the algorithm. They came out of /// rpm --eval "%{lua:print(rpm.vercmp(a, b))}" in a Fedora-era container, which is the same /// comparison PackageKit will make when it decides whether to accept the update this class caused to be /// offered. A test written from the specification would agree with a misreading of the specification. /// /// /// The awkward cases are the point of the list. 1a equals 1.a because separators are skipped /// on each side independently; 1.01 equals 1.1 because leading zeros are stripped; /// 1.0_1 equals 1.0.1 for the first reason again; and a is older than 1 because /// rpm calls a numeric segment newer than an alphabetic one whatever the letters say. /// /// /// The four rows that carry the feature are the ~ ones. 0.0.1~alpha.0.143 < 0.0.1 is what /// makes every nightly older than the first real release, and ~alpha.0.9 < ~alpha.0.10 is what /// keeps them in order among themselves — a string comparison would get that backwards and a client would /// stop offering updates after the ninth nightly of a version. /// /// public sealed class RpmVersionTests { [Theory] // Ordinary [InlineData("1.2.3", "1.2.3", 0)] [InlineData("1.2.3", "1.2.4", -1)] [InlineData("1.2.3", "1.2.3.1", -1)] [InlineData("1.2.10", "1.2.9", 1)] [InlineData("2.0", "10.0", -1)] [InlineData("1.0.0", "1.0", 1)] [InlineData("1.0.0.1", "1.0.0", 1)] [InlineData("20101121", "20101122", -1)] // Tilde: the prerelease ordering this feature depends on [InlineData("0.0.1~alpha.0.143", "0.0.1", -1)] [InlineData("0.0.1~alpha.0.143", "0.0.1~alpha.0.144", -1)] [InlineData("0.0.1~alpha.0.143", "0.0.1~alpha.0.99", 1)] [InlineData("0.0.1~alpha.0.9", "0.0.1~alpha.0.10", -1)] [InlineData("1.0", "1.0~rc1", 1)] [InlineData("1~~", "1~", -1)] [InlineData("1.0~beta", "1.0~alpha", 1)] // Caret: after everything except the end of the string [InlineData("1.0^post", "1.0", 1)] [InlineData("1.0^post", "1.0^post2", -1)] [InlineData("1.2.3", "1.2.3^", -1)] // Separators and leading zeros, which are the surprising ones [InlineData("1a", "1.a", 0)] [InlineData("1.01", "1.1", 0)] [InlineData("1.0_1", "1.0.1", 0)] [InlineData("1.0-1", "1.0-2", -1)] // Segments of different kinds [InlineData("1.a", "1.b", -1)] [InlineData("a", "1", -1)] [InlineData("1", "a", 1)] [InlineData("5.5p1", "5.5p2", -1)] [InlineData("xyz10", "xyz10.1", -1)] [InlineData("MDK", "MDK1", -1)] public void ItAgreesWithRpm(string left, string right, int expected) => RpmVersion.Compare(left, right).ShouldBe(expected); /// /// The reverse of every case above, which rpm does not need asserting and this does. /// /// /// The marker handling is the half of the algorithm with a left and a right branch for each case, so an /// asymmetry there would pass every row above and reverse the answer for a client whose installed /// version happened to be the newer one — which is the direction that offers a downgrade. /// [Theory] [InlineData("0.0.1~alpha.0.143", "0.0.1")] [InlineData("1.0^post", "1.0")] [InlineData("1.2.3", "1.2.3^")] [InlineData("a", "1")] [InlineData("1.2.3", "1.2.4")] public void ItIsAntisymmetric(string left, string right) => RpmVersion.Compare(right, left).ShouldBe(-RpmVersion.Compare(left, right)); /// /// Nothing and an empty string are older than anything, and equal to each other. /// /// /// rpm's own binding refuses these outright. Here they arrive off a feed rather than out of a spec /// file, and the answer that leaves a client on the build it already has is better than an exception /// thrown on a six-hourly timer. /// [Theory] [InlineData(null, "1.0", -1)] [InlineData("", "1.0", -1)] [InlineData("1.0", null, 1)] [InlineData(null, null, 0)] [InlineData("", null, 0)] public void ItTreatsNothingAsOldest(string? left, string? right, int expected) => RpmVersion.Compare(left, right).ShouldBe(expected); }