Add determinism tests for verdict artifact generation and update SHA256 sums script
- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering. - Created helper methods for generating sample verdict inputs and computing canonical hashes. - Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics. - Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// VersionComparisonPropertyTests.cs
|
||||
// Sprint: SPRINT_5100_0009_0001 (Scanner Tests)
|
||||
// Task: SCANNER-5100-001 - Property tests for version/range resolution
|
||||
// Description: Property-based tests for version comparers verifying
|
||||
// monotonicity, transitivity, reflexivity, and boundary behavior.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using FsCheck;
|
||||
using FsCheck.Xunit;
|
||||
using StellaOps.VersionComparison.Comparers;
|
||||
using StellaOps.VersionComparison.Models;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace StellaOps.VersionComparison.Tests.Properties;
|
||||
|
||||
/// <summary>
|
||||
/// Property-based tests for version comparers.
|
||||
/// Verifies mathematical properties required for correct sorting:
|
||||
/// - Reflexivity: Compare(x, x) == 0
|
||||
/// - Anti-symmetry: Compare(x, y) == -Compare(y, x)
|
||||
/// - Transitivity: if Compare(x, y) <= 0 and Compare(y, z) <= 0 then Compare(x, z) <= 0
|
||||
/// </summary>
|
||||
[Trait("Category", "Property")]
|
||||
public class VersionComparisonPropertyTests
|
||||
{
|
||||
#region RPM Version Property Tests
|
||||
|
||||
/// <summary>
|
||||
/// Reflexivity: Any version equals itself.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_Reflexivity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
version => RpmVersionComparer.Instance.Compare(version, version) == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anti-symmetry: Compare(x, y) == -Compare(y, x).
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_AntiSymmetry()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
RpmVersionArb(),
|
||||
(x, y) =>
|
||||
{
|
||||
var cmpXY = Math.Sign(RpmVersionComparer.Instance.Compare(x, y));
|
||||
var cmpYX = Math.Sign(RpmVersionComparer.Instance.Compare(y, x));
|
||||
return cmpXY == -cmpYX;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transitivity: if x <= y and y <= z then x <= z.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_Transitivity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
RpmVersionArb(),
|
||||
RpmVersionArb(),
|
||||
(x, y, z) =>
|
||||
{
|
||||
var comparer = RpmVersionComparer.Instance;
|
||||
var cmpXY = comparer.Compare(x, y);
|
||||
var cmpYZ = comparer.Compare(y, z);
|
||||
var cmpXZ = comparer.Compare(x, z);
|
||||
|
||||
// If x <= y and y <= z, then x <= z
|
||||
if (cmpXY <= 0 && cmpYZ <= 0)
|
||||
{
|
||||
return cmpXZ <= 0;
|
||||
}
|
||||
|
||||
// If x >= y and y >= z, then x >= z
|
||||
if (cmpXY >= 0 && cmpYZ >= 0)
|
||||
{
|
||||
return cmpXZ >= 0;
|
||||
}
|
||||
|
||||
return true; // No constraint for mixed cases
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monotonicity: Incrementing epoch always results in newer version.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_EpochMonotonicity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(0, 10).ToArbitrary(),
|
||||
Gen.Elements("1.0", "2.5", "1.0.1", "10.20.30").ToArbitrary(),
|
||||
Gen.Elements("1", "2.el8", "3.fc38").ToArbitrary(),
|
||||
(epoch, version, release) =>
|
||||
{
|
||||
var lower = $"{epoch}:{version}-{release}";
|
||||
var higher = $"{epoch + 1}:{version}-{release}";
|
||||
return RpmVersionComparer.Instance.Compare(lower, higher) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monotonicity: Major version increments are always newer.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_MajorVersionMonotonicity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(1, 100).ToArbitrary(),
|
||||
Gen.Choose(0, 99).ToArbitrary(),
|
||||
Gen.Choose(0, 99).ToArbitrary(),
|
||||
(major, minor, patch) =>
|
||||
{
|
||||
var lower = $"{major}.{minor}.{patch}-1";
|
||||
var higher = $"{major + 1}.{minor}.{patch}-1";
|
||||
return RpmVersionComparer.Instance.Compare(lower, higher) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boundary: Tilde pre-release always sorts before release.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_TildePreReleaseBehavior()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Elements("1.0", "2.0", "3.5.1").ToArbitrary(),
|
||||
Gen.Elements("alpha", "beta", "rc1", "rc2").ToArbitrary(),
|
||||
(version, prerelease) =>
|
||||
{
|
||||
var preReleaseVersion = $"{version}~{prerelease}-1";
|
||||
var releaseVersion = $"{version}-1";
|
||||
return RpmVersionComparer.Instance.Compare(preReleaseVersion, releaseVersion) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consistency: Same input produces same result (determinism).
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_IsDeterministic()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
RpmVersionArb(),
|
||||
(x, y) =>
|
||||
{
|
||||
var result1 = RpmVersionComparer.Instance.Compare(x, y);
|
||||
var result2 = RpmVersionComparer.Instance.Compare(x, y);
|
||||
return result1 == result2;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proof lines are non-empty for valid comparisons.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_ProofLinesNonEmpty()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
RpmVersionArb(),
|
||||
(x, y) =>
|
||||
{
|
||||
var result = RpmVersionComparer.Instance.CompareWithProof(x, y);
|
||||
return result.ProofLines.Length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debian Version Property Tests
|
||||
|
||||
/// <summary>
|
||||
/// Reflexivity: Any version equals itself.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_Reflexivity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
version => DebianVersionComparer.Instance.Compare(version, version) == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anti-symmetry: Compare(x, y) == -Compare(y, x).
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_AntiSymmetry()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
DebianVersionArb(),
|
||||
(x, y) =>
|
||||
{
|
||||
var cmpXY = Math.Sign(DebianVersionComparer.Instance.Compare(x, y));
|
||||
var cmpYX = Math.Sign(DebianVersionComparer.Instance.Compare(y, x));
|
||||
return cmpXY == -cmpYX;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transitivity: if x <= y and y <= z then x <= z.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_Transitivity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
DebianVersionArb(),
|
||||
DebianVersionArb(),
|
||||
(x, y, z) =>
|
||||
{
|
||||
var comparer = DebianVersionComparer.Instance;
|
||||
var cmpXY = comparer.Compare(x, y);
|
||||
var cmpYZ = comparer.Compare(y, z);
|
||||
var cmpXZ = comparer.Compare(x, z);
|
||||
|
||||
if (cmpXY <= 0 && cmpYZ <= 0)
|
||||
{
|
||||
return cmpXZ <= 0;
|
||||
}
|
||||
|
||||
if (cmpXY >= 0 && cmpYZ >= 0)
|
||||
{
|
||||
return cmpXZ >= 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Monotonicity: Incrementing epoch always results in newer version.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_EpochMonotonicity()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(0, 10).ToArbitrary(),
|
||||
Gen.Elements("1.0", "2.5", "1.0.1", "10.20.30").ToArbitrary(),
|
||||
Gen.Elements("1", "1ubuntu1", "2build1").ToArbitrary(),
|
||||
(epoch, version, revision) =>
|
||||
{
|
||||
var lower = $"{epoch}:{version}-{revision}";
|
||||
var higher = $"{epoch + 1}:{version}-{revision}";
|
||||
return DebianVersionComparer.Instance.Compare(lower, higher) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boundary: Tilde pre-release always sorts before release.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_TildePreReleaseBehavior()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Elements("1.0", "2.0", "3.5.1").ToArbitrary(),
|
||||
Gen.Elements("alpha", "beta", "rc1", "rc2").ToArbitrary(),
|
||||
(version, prerelease) =>
|
||||
{
|
||||
var preReleaseVersion = $"{version}~{prerelease}-1";
|
||||
var releaseVersion = $"{version}-1";
|
||||
return DebianVersionComparer.Instance.Compare(preReleaseVersion, releaseVersion) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consistency: Same input produces same result (determinism).
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_IsDeterministic()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
DebianVersionArb(),
|
||||
(x, y) =>
|
||||
{
|
||||
var result1 = DebianVersionComparer.Instance.Compare(x, y);
|
||||
var result2 = DebianVersionComparer.Instance.Compare(x, y);
|
||||
return result1 == result2;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Proof lines are non-empty for valid comparisons.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_ProofLinesNonEmpty()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
DebianVersionArb(),
|
||||
(x, y) =>
|
||||
{
|
||||
var result = DebianVersionComparer.Instance.CompareWithProof(x, y);
|
||||
return result.ProofLines.Length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Boundary Behavior Tests
|
||||
|
||||
/// <summary>
|
||||
/// Null handling: null is less than any valid version.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_NullIsLessThanAnyVersion()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
version => ((IComparer<string>)RpmVersionComparer.Instance).Compare(null, version) < 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null handling: any valid version is greater than null.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RpmComparer_AnyVersionGreaterThanNull()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
RpmVersionArb(),
|
||||
version => ((IComparer<string>)RpmVersionComparer.Instance).Compare(version, null) > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null handling: null equals null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RpmComparer_NullEqualsNull()
|
||||
{
|
||||
((IComparer<string>)RpmVersionComparer.Instance).Compare(null, null).Should().Be(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null handling: null is less than any valid Debian version.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_NullIsLessThanAnyVersion()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
version => ((IComparer<string>)DebianVersionComparer.Instance).Compare(null, version) < 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null handling: any valid Debian version is greater than null.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DebianComparer_AnyVersionGreaterThanNull()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
DebianVersionArb(),
|
||||
version => ((IComparer<string>)DebianVersionComparer.Instance).Compare(version, null) > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Null handling: null equals null for Debian.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DebianComparer_NullEqualsNull()
|
||||
{
|
||||
((IComparer<string>)DebianVersionComparer.Instance).Compare(null, null).Should().Be(0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Version Ordering Tests
|
||||
|
||||
/// <summary>
|
||||
/// Leading zeros in numeric segments are ignored for RPM.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property RpmComparer_LeadingZerosIgnored()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(1, 99).ToArbitrary(),
|
||||
Gen.Choose(1, 99).ToArbitrary(),
|
||||
(major, minor) =>
|
||||
{
|
||||
var withLeading = $"0{major}.0{minor}-1";
|
||||
var withoutLeading = $"{major}.{minor}-1";
|
||||
return RpmVersionComparer.Instance.Compare(withLeading, withoutLeading) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Leading zeros in numeric segments are ignored for Debian.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property DebianComparer_LeadingZerosIgnored()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(1, 99).ToArbitrary(),
|
||||
Gen.Choose(1, 99).ToArbitrary(),
|
||||
(major, minor) =>
|
||||
{
|
||||
var withLeading = $"0{major}.0{minor}-1";
|
||||
var withoutLeading = $"{major}.{minor}-1";
|
||||
return DebianVersionComparer.Instance.Compare(withLeading, withoutLeading) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numeric segments sort numerically, not lexicographically (9 < 10).
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property RpmComparer_NumericSegmentsNotLexicographic()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(1, 9).ToArbitrary(),
|
||||
(single) =>
|
||||
{
|
||||
var singleDigit = $"1.{single}-1";
|
||||
var doubleDigit = $"1.{single + 10}-1";
|
||||
return RpmVersionComparer.Instance.Compare(singleDigit, doubleDigit) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Numeric segments sort numerically for Debian (9 < 10).
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property DebianComparer_NumericSegmentsNotLexicographic()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Choose(1, 9).ToArbitrary(),
|
||||
(single) =>
|
||||
{
|
||||
var singleDigit = $"1.{single}-1";
|
||||
var doubleDigit = $"1.{single + 10}-1";
|
||||
return DebianVersionComparer.Instance.Compare(singleDigit, doubleDigit) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generators
|
||||
|
||||
/// <summary>
|
||||
/// Generator for valid RPM version strings.
|
||||
/// </summary>
|
||||
private static Arbitrary<string> RpmVersionArb()
|
||||
{
|
||||
var epochGen = Gen.Frequency(
|
||||
Tuple.Create(3, Gen.Constant("")),
|
||||
Tuple.Create(1, Gen.Choose(0, 5).Select(e => $"{e}:")));
|
||||
|
||||
var versionGen = Gen.Elements(
|
||||
"1.0", "1.1", "1.2", "2.0", "2.1",
|
||||
"1.0.0", "1.0.1", "1.1.0", "2.0.0",
|
||||
"1.9", "1.10", "1.99", "1.100",
|
||||
"1.0~alpha", "1.0~beta", "1.0~rc1",
|
||||
"10.20.30", "0.1", "0.0.1");
|
||||
|
||||
var releaseGen = Gen.Elements(
|
||||
"1", "2", "3",
|
||||
"1.el8", "1.el8_5", "1.el9",
|
||||
"1.fc38", "1.fc39",
|
||||
"1ubuntu1", "1build1");
|
||||
|
||||
return (from epoch in epochGen
|
||||
from version in versionGen
|
||||
from release in releaseGen
|
||||
select $"{epoch}{version}-{release}").ToArbitrary();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generator for valid Debian version strings.
|
||||
/// </summary>
|
||||
private static Arbitrary<string> DebianVersionArb()
|
||||
{
|
||||
var epochGen = Gen.Frequency(
|
||||
Tuple.Create(3, Gen.Constant("")),
|
||||
Tuple.Create(1, Gen.Choose(0, 5).Select(e => $"{e}:")));
|
||||
|
||||
var versionGen = Gen.Elements(
|
||||
"1.0", "1.1", "1.2", "2.0", "2.1",
|
||||
"1.0.0", "1.0.1", "1.1.0", "2.0.0",
|
||||
"1.9", "1.10", "1.99", "1.100",
|
||||
"1.0~alpha", "1.0~beta", "1.0~rc1",
|
||||
"10.20.30", "0.1", "0.0.1",
|
||||
"1.0+dfsg", "1.0+really1.1");
|
||||
|
||||
var revisionGen = Gen.Elements(
|
||||
"1", "2", "3",
|
||||
"1ubuntu1", "1ubuntu2",
|
||||
"1build1", "1build2",
|
||||
"1+deb11u1", "1+deb12u1");
|
||||
|
||||
return (from epoch in epochGen
|
||||
from version in versionGen
|
||||
from revision in revisionGen
|
||||
select $"{epoch}{version}-{revision}").ToArbitrary();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Property tests for version range matching and resolution.
|
||||
/// Verifies range containment, boundary conditions, and semantic consistency.
|
||||
/// </summary>
|
||||
[Trait("Category", "Property")]
|
||||
public class VersionRangePropertyTests
|
||||
{
|
||||
/// <summary>
|
||||
/// For any version v, range "[v,v]" contains exactly v.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property ExactVersionRange_ContainsOnlyExactVersion()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Elements("1.0-1", "2.0-1", "1.0.1-1", "3.0~rc1-1").ToArbitrary(),
|
||||
(string version) =>
|
||||
{
|
||||
// Exact range should match
|
||||
var comparer = RpmVersionComparer.Instance;
|
||||
return ((IComparer<string>)comparer).Compare(version, version) == 0;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For versions a < b, range [a, b] contains both endpoints.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property ClosedRange_ContainsBothEndpoints()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Elements(
|
||||
("1.0-1", "2.0-1"),
|
||||
("1.0-1", "1.1-1"),
|
||||
("1.0~alpha-1", "1.0-1"),
|
||||
("0:1.0-1", "1:1.0-1")).ToArbitrary(),
|
||||
((string lower, string upper) range) =>
|
||||
{
|
||||
var (lower, upper) = range;
|
||||
var comparer = (IComparer<string>)RpmVersionComparer.Instance;
|
||||
|
||||
// Lower is contained: lower >= lower && lower <= upper
|
||||
var lowerContained = comparer.Compare(lower, lower) >= 0 &&
|
||||
comparer.Compare(lower, upper) <= 0;
|
||||
|
||||
// Upper is contained: upper >= lower && upper <= upper
|
||||
var upperContained = comparer.Compare(upper, lower) >= 0 &&
|
||||
comparer.Compare(upper, upper) <= 0;
|
||||
|
||||
return lowerContained && upperContained;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For versions a < b, range (a, b) excludes both endpoints.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property OpenRange_ExcludesBothEndpoints()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Elements(
|
||||
("1.0-1", "2.0-1"),
|
||||
("1.0-1", "1.1-1"),
|
||||
("0:1.0-1", "1:1.0-1")).ToArbitrary(),
|
||||
((string lower, string upper) range) =>
|
||||
{
|
||||
var (lower, upper) = range;
|
||||
var comparer = (IComparer<string>)RpmVersionComparer.Instance;
|
||||
|
||||
// In open range (a, b): version must be strictly > a and strictly < b
|
||||
// Since we're testing with just endpoints, they should NOT be in the open range
|
||||
var lowerInOpen = comparer.Compare(lower, lower) > 0; // false
|
||||
var upperInOpen = comparer.Compare(upper, upper) < 0; // false
|
||||
|
||||
return !lowerInOpen && !upperInOpen;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Version ordering is consistent with range containment.
|
||||
/// If a is in range [lower, upper] and b > upper, then a < b.
|
||||
/// </summary>
|
||||
[Property(MaxTest = 50)]
|
||||
public Property RangeContainment_ConsistentWithOrdering()
|
||||
{
|
||||
return Prop.ForAll(
|
||||
Gen.Elements("1.0-1", "1.5-1").ToArbitrary(),
|
||||
Gen.Elements("2.0-1", "3.0-1").ToArbitrary(),
|
||||
(string inRange, string outOfRange) =>
|
||||
{
|
||||
var comparer = (IComparer<string>)RpmVersionComparer.Instance;
|
||||
// If inRange < outOfRange, this should hold
|
||||
return comparer.Compare(inRange, outOfRange) < 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="FsCheck" Version="2.16.6" />
|
||||
<PackageReference Include="FsCheck.Xunit" Version="2.16.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user