Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,76 @@
using StellaOps.Concelier.Merge.Comparers;
using StellaOps.Concelier.Normalization.Distro;
namespace StellaOps.Concelier.Merge.Tests;
public sealed class ApkVersionComparerTests
{
public static TheoryData<string, string, int, string> ComparisonCases => BuildComparisonCases();
[Theory]
[MemberData(nameof(ComparisonCases))]
public void Compare_ApkVersions_ReturnsExpectedOrder(string left, string right, int expected, string note)
{
var actual = Math.Sign(ApkVersionComparer.Instance.Compare(left, right));
Assert.True(expected == actual, $"[{note}] '{left}' vs '{right}': expected {expected}, got {actual}");
}
[Fact]
public void Compare_ParsesApkVersionComponents()
{
var result = ApkVersionComparer.Instance.Compare(
ApkVersion.Parse("3.1.4-r0"),
ApkVersion.Parse("3.1.3-r2"));
Assert.True(result > 0);
}
private static TheoryData<string, string, int, string> BuildComparisonCases()
{
var data = new TheoryData<string, string, int, string>();
// Suffix ordering.
data.Add("1.0_alpha", "1.0_beta", -1, "suffix ordering: alpha < beta");
data.Add("1.0_beta", "1.0_pre", -1, "suffix ordering: beta < pre");
data.Add("1.0_pre", "1.0_rc", -1, "suffix ordering: pre < rc");
data.Add("1.0_rc", "1.0", -1, "suffix ordering: rc < none");
data.Add("1.0", "1.0_p", -1, "suffix ordering: none < p");
data.Add("1.0_alpha", "1.0_p", -1, "suffix ordering: alpha < p");
// Suffix numeric ordering.
data.Add("1.0_alpha1", "1.0_alpha2", -1, "suffix numeric ordering");
data.Add("1.0_alpha2", "1.0_alpha10", -1, "suffix numeric ordering");
data.Add("1.0_pre1", "1.0_pre2", -1, "suffix numeric ordering");
data.Add("1.0_rc1", "1.0_rc2", -1, "suffix numeric ordering");
data.Add("1.0_beta1", "1.0_beta01", 0, "suffix numeric leading zeros ignored");
// Numeric ordering in version.
data.Add("1.2.3", "1.2.10", -1, "numeric segment ordering");
data.Add("1.10.0", "1.2.9", 1, "numeric segment ordering");
data.Add("2.0", "1.9", 1, "major version ordering");
data.Add("1.02", "1.2", 0, "leading zeros ignored");
data.Add("1.2.03", "1.2.3", 0, "leading zeros ignored");
// Alpha segment ordering.
data.Add("1.2.3a", "1.2.3b", -1, "alpha segment ordering");
data.Add("1.2.3a", "1.2.3", 1, "alpha sorts after empty");
data.Add("1.2.3", "1.2.3a", -1, "empty sorts before alpha");
data.Add("1.2.3aa", "1.2.3b", -1, "alpha lexical ordering");
// Package release ordering.
data.Add("1.2.3-r0", "1.2.3-r1", -1, "pkgrel ordering");
data.Add("1.2.3-r1", "1.2.3-r2", -1, "pkgrel ordering");
data.Add("1.2.3-r2", "1.2.3-r10", -1, "pkgrel numeric ordering");
data.Add("1.2.3-r10", "1.2.3-r2", 1, "pkgrel numeric ordering");
data.Add("1.2.3", "1.2.3-r0", -1, "implicit release sorts before explicit r0");
// Combined ordering.
data.Add("1.2.3_p1-r0", "1.2.3_p1-r1", -1, "pkgrel ordering after suffix");
data.Add("1.2.3_rc1-r1", "1.2.3-r0", -1, "rc sorts before release even with higher pkgrel");
data.Add("1.2.3_p1-r0", "1.2.3-r9", 1, "patch suffix sorts after release");
data.Add("1.2.3_pre2-r3", "1.2.3_pre10-r1", -1, "suffix numeric ordering beats pkgrel");
data.Add("1.2.3", "1.2.3", 0, "exact match");
return data;
}
}

View File

@@ -81,4 +81,70 @@ public sealed class DebianEvrComparerTests
Assert.Equal(expected, actual);
}
public static TheoryData<string, string, int, string> ComparisonCases => BuildComparisonCases();
[Theory]
[MemberData(nameof(ComparisonCases))]
public void Compare_DebianEvr_ReturnsExpectedOrder(string left, string right, int expected, string note)
{
var actual = Math.Sign(DebianEvrComparer.Instance.Compare(left, right));
Assert.True(expected == actual, $"[{note}] '{left}' vs '{right}': expected {expected}, got {actual}");
}
private static TheoryData<string, string, int, string> BuildComparisonCases()
{
var data = new TheoryData<string, string, int, string>();
// Epoch precedence.
data.Add("0:1.0-1", "1:1.0-1", -1, "epoch precedence: 0 < 1");
data.Add("1:1.0-1", "0:9.9-9", 1, "epoch precedence: 1 > 0");
data.Add("2:0.1-1", "1:9.9-9", 1, "epoch precedence: 2 > 1");
data.Add("3:1.0-1", "4:0.1-1", -1, "epoch precedence: 3 < 4");
data.Add("5:2.0-1", "4:9.9-9", 1, "epoch precedence: 5 > 4");
data.Add("1:2.0-1", "2:1.0-1", -1, "epoch precedence: 1 < 2");
// Numeric ordering in upstream version.
for (var i = 1; i <= 12; i++)
{
data.Add($"0:1.{i}-1", $"0:1.{i + 1}-1", -1, "numeric segment ordering");
}
data.Add("0:1.09-1", "0:1.9-1", 0, "leading zeros ignored");
data.Add("0:2.001-1", "0:2.1-1", 0, "leading zeros ignored");
// Tilde pre-releases.
data.Add("0:1.0~alpha1-1", "0:1.0~alpha2-1", -1, "tilde pre-release ordering");
data.Add("0:1.0~rc1-1", "0:1.0-1", -1, "tilde sorts before release");
data.Add("0:1.0~~-1", "0:1.0~-1", -1, "double tilde sorts earlier");
data.Add("0:2.0~beta-1", "0:2.0~rc-1", -1, "tilde alpha ordering");
data.Add("0:1.0~rc1-1", "0:1.0~rc2-1", -1, "tilde rc ordering");
data.Add("0:1.0~rc-1", "0:1.0~rc-2", -1, "revision breaks tilde ties");
// Debian revision ordering.
for (var i = 1; i <= 10; i++)
{
data.Add($"0:1.0-{i}", $"0:1.0-{i + 1}", -1, "revision numeric ordering");
}
data.Add("0:1.0-1", "0:1.0-1ubuntu0.1", -1, "ubuntu security backport");
data.Add("0:1.0-1ubuntu0.1", "0:1.0-1ubuntu0.2", -1, "ubuntu incremental backport");
data.Add("0:1.0-1ubuntu1", "0:1.0-1ubuntu2", -1, "ubuntu delta update");
data.Add("0:1.0-1build1", "0:1.0-1build2", -1, "ubuntu rebuild");
data.Add("0:1.0-1+deb12u1", "0:1.0-1+deb12u2", -1, "debian stable update");
data.Add("0:1.0-1ubuntu0.2", "0:1.0-1ubuntu1", -1, "ubuntu ordering baseline");
data.Add("0:1.0-1ubuntu1", "0:1.0-1ubuntu1.1", -1, "ubuntu dotted revision");
data.Add("0:1.0-1ubuntu1.1", "0:1.0-1ubuntu1.2", -1, "ubuntu dotted revision ordering");
data.Add("0:1.0-1+deb12u1", "0:1.0-1ubuntu1", -1, "debian update before ubuntu delta");
data.Add("0:1.0-1ubuntu2", "0:1.0-1ubuntu10", -1, "ubuntu numeric ordering");
// Native package handling.
data.Add("0:1.0", "0:1.0-1", -1, "native package sorts before revisioned");
data.Add("0:1.0", "0:1.0+deb12u1", -1, "native package sorts before debian update");
data.Add("1:1.0", "1:1.0-1", -1, "native package sorts before revisioned");
data.Add("0:2.0", "0:2.0-0", -1, "native package sorts before zero revision");
data.Add("0:2.0-0", "0:2.0-1", -1, "zero revision sorts before higher revision");
return data;
}
}

View File

@@ -0,0 +1,121 @@
param(
[string]$Configuration = "Debug"
)
$root = Resolve-Path (Join-Path $PSScriptRoot "..\\..\\..\\..")
$mergePath = Join-Path $root "__Libraries\\StellaOps.Concelier.Merge\\bin\\$Configuration\\net10.0\\StellaOps.Concelier.Merge.dll"
$normPath = Join-Path $root "__Libraries\\StellaOps.Concelier.Normalization\\bin\\$Configuration\\net10.0\\StellaOps.Concelier.Normalization.dll"
if (-not (Test-Path $mergePath)) {
throw "Build StellaOps.Concelier.Merge first. Missing: $mergePath"
}
[System.Reflection.Assembly]::LoadFrom($mergePath) | Out-Null
if (Test-Path $normPath) {
[System.Reflection.Assembly]::LoadFrom($normPath) | Out-Null
}
$nevraComparer = [StellaOps.Concelier.Merge.Comparers.NevraComparer]::Instance
$debComparer = [StellaOps.Concelier.Merge.Comparers.DebianEvrComparer]::Instance
$apkComparer = [StellaOps.Concelier.Merge.Comparers.ApkVersionComparer]::Instance
$rpmVersions = @(
'kernel-0:4.18.0-80.el8.x86_64',
'kernel-1:4.18.0-80.el8.x86_64',
'kernel-0:4.18.11-80.el8.x86_64',
'pkg-0:1.0-1.el9.noarch',
'pkg-0:1.0-1.el9.x86_64',
'pkg-0:1.0-2.el9.x86_64',
'pkg-0:1.0-10.el9.x86_64',
'pkg-0:1.0~rc1-1.el9.x86_64',
'pkg-0:1.0-1.fc35.x86_64',
'pkg-0:1.0-1.fc36.x86_64',
'openssl-1:1.1.1k-7.el8.x86_64',
'openssl-3:1.1.1k-7.el8.x86_64',
'podman-1:4.5.0-1.el9.x86_64',
'podman-2:4.4.0-1.el9.x86_64',
'glibc-4:2.36-9.el9.x86_64',
'glibc-5:2.36-8.el9.x86_64'
)
$debVersions = @(
'0:1.0-1',
'1.0-1',
'1.0-1ubuntu0.1',
'1.0-1ubuntu0.2',
'1.0-1ubuntu1',
'1.0-1ubuntu2',
'1.0-1+deb12u1',
'1.0-1+deb12u2',
'1:1.1.1n-0+deb11u2',
'1:1.1.1n-0+deb11u5',
'2.0~beta1-1',
'2.0~rc1-1',
'2.0-1',
'1.2.3-1',
'1.2.10-1',
'3.0.0-1'
)
$apkVersions = @(
'1.0_alpha1-r0',
'1.0_beta1-r0',
'1.0_pre1-r0',
'1.0_rc1-r0',
'1.0-r0',
'1.0_p1-r0',
'1.2.3-r0',
'1.2.10-r0',
'2.0-r0',
'1.2.3a-r0',
'1.2.3b-r0',
'1.2.3-r1',
'1.2.3-r2',
'1.2.3_p1-r1',
'3.9.1-r0',
'3.1.1-r0'
)
function New-Cases {
param(
[string[]]$Versions,
[string]$Distro,
$Comparer
)
$cases = New-Object System.Collections.Generic.List[object]
for ($i = 0; $i -lt $Versions.Count; $i++) {
for ($j = $i + 1; $j -lt $Versions.Count; $j++) {
$left = $Versions[$i]
$right = $Versions[$j]
$expected = [Math]::Sign($Comparer.Compare($left, $right))
$cases.Add([ordered]@{
left = $left
right = $right
expected = $expected
distro = $Distro
note = "pairwise"
})
}
}
return $cases
}
function Write-GoldenFile {
param(
[string]$Path,
$Cases
)
$lines = foreach ($case in $Cases) {
$case | ConvertTo-Json -Compress
}
Set-Content -Path $Path -Value ($lines -join "`n") -Encoding ascii
}
$rpmCases = New-Cases -Versions $rpmVersions -Distro "rpm" -Comparer $nevraComparer
$debCases = New-Cases -Versions $debVersions -Distro "deb" -Comparer $debComparer
$apkCases = New-Cases -Versions $apkVersions -Distro "apk" -Comparer $apkComparer
Write-GoldenFile -Path (Join-Path $PSScriptRoot "rpm_version_comparison.golden.ndjson") -Cases $rpmCases
Write-GoldenFile -Path (Join-Path $PSScriptRoot "deb_version_comparison.golden.ndjson") -Cases $debCases
Write-GoldenFile -Path (Join-Path $PSScriptRoot "apk_version_comparison.golden.ndjson") -Cases $apkCases

View File

@@ -0,0 +1,28 @@
# Distro Version Comparison Goldens
Golden files store pairwise version comparison results in NDJSON to guard
regressions in distro-specific comparers (RPM, Debian, Alpine APK).
## Format
Each line is a single JSON object:
```
{"left":"0:1.0-1.el8","right":"1:0.1-1.el8","expected":-1,"distro":"rpm","note":"pairwise"}
```
Fields:
- left/right: version strings as understood by the target comparer.
- expected: comparison result (-1, 0, 1) after Math.Sign.
- distro: rpm | deb | apk.
- note: optional human note or generation hint.
## Updating goldens
1) Build the comparers:
`dotnet build ..\..\..\..\__Libraries\StellaOps.Concelier.Merge\StellaOps.Concelier.Merge.csproj`
2) Regenerate:
`pwsh .\GenerateGoldenComparisons.ps1`
Files:
- rpm_version_comparison.golden.ndjson
- deb_version_comparison.golden.ndjson
- apk_version_comparison.golden.ndjson

View File

@@ -0,0 +1,120 @@
{"left":"1.0_alpha1-r0","right":"1.0_beta1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.0_pre1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.0_rc1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.0_p1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.3-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_alpha1-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.0_pre1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.0_rc1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.0_p1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.3-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_beta1-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.0_rc1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.0_p1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.3-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_pre1-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.0_p1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.3-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_rc1-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.0_p1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.3-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.3-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.0_p1-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"1.2.10-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"1.2.3a-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"1.2.3-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"2.0-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"1.2.3a-r0","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"1.2.3b-r0","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"1.2.3-r1","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"1.2.3-r2","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"1.2.3_p1-r1","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.10-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"1.2.3a-r0","expected":1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"1.2.3b-r0","expected":1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"1.2.3-r1","expected":1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"1.2.3-r2","expected":1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"1.2.3_p1-r1","expected":1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"2.0-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3a-r0","right":"1.2.3b-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3a-r0","right":"1.2.3-r1","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3a-r0","right":"1.2.3-r2","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3a-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3a-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3a-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3b-r0","right":"1.2.3-r1","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3b-r0","right":"1.2.3-r2","expected":1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3b-r0","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3b-r0","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3b-r0","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r1","right":"1.2.3-r2","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r1","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r1","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r1","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r2","right":"1.2.3_p1-r1","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r2","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3-r2","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3_p1-r1","right":"3.9.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"1.2.3_p1-r1","right":"3.1.1-r0","expected":-1,"distro":"apk","note":"pairwise"}
{"left":"3.9.1-r0","right":"3.1.1-r0","expected":1,"distro":"apk","note":"pairwise"}

View File

@@ -0,0 +1,120 @@
{"left":"0:1.0-1","right":"1.0-1","expected":0,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.0-1ubuntu0.1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.0-1ubuntu0.2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.0-1ubuntu1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.0-1ubuntu2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.0-1\u002Bdeb12u1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.0-1\u002Bdeb12u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"0:1.0-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.0-1ubuntu0.1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.0-1ubuntu0.2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.0-1ubuntu1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.0-1ubuntu2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.0-1\u002Bdeb12u1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.0-1\u002Bdeb12u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.0-1ubuntu0.2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.0-1ubuntu1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.0-1ubuntu2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.0-1\u002Bdeb12u1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.0-1\u002Bdeb12u2","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1.0-1ubuntu1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1.0-1ubuntu2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1.0-1\u002Bdeb12u1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1.0-1\u002Bdeb12u2","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu0.2","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1.0-1ubuntu2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1.0-1\u002Bdeb12u1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1.0-1\u002Bdeb12u2","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"1.0-1\u002Bdeb12u1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"1.0-1\u002Bdeb12u2","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1ubuntu2","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"1.0-1\u002Bdeb12u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"1:1.1.1n-0\u002Bdeb11u2","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"2.0~beta1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"1.2.3-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.0-1\u002Bdeb12u2","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"1:1.1.1n-0\u002Bdeb11u5","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"2.0~beta1-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"2.0~rc1-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"2.0-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"1.2.3-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"1.2.10-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u2","right":"3.0.0-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u5","right":"2.0~beta1-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u5","right":"2.0~rc1-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u5","right":"2.0-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u5","right":"1.2.3-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u5","right":"1.2.10-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"1:1.1.1n-0\u002Bdeb11u5","right":"3.0.0-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0~beta1-1","right":"2.0~rc1-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"2.0~beta1-1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"2.0~beta1-1","right":"1.2.3-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0~beta1-1","right":"1.2.10-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0~beta1-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"2.0~rc1-1","right":"2.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"2.0~rc1-1","right":"1.2.3-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0~rc1-1","right":"1.2.10-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0~rc1-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"2.0-1","right":"1.2.3-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0-1","right":"1.2.10-1","expected":1,"distro":"deb","note":"pairwise"}
{"left":"2.0-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.2.3-1","right":"1.2.10-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.2.3-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}
{"left":"1.2.10-1","right":"3.0.0-1","expected":-1,"distro":"deb","note":"pairwise"}

View File

@@ -0,0 +1,120 @@
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"kernel-1:4.18.0-80.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"kernel-0:4.18.11-80.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.el9.noarch","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-2.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-10.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.0-80.el8.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"kernel-0:4.18.11-80.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.el9.noarch","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-2.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-10.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-1:4.18.0-80.el8.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0-1.el9.noarch","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0-2.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0-10.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"kernel-0:4.18.11-80.el8.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"pkg-0:1.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"pkg-0:1.0-2.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"pkg-0:1.0-10.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"pkg-0:1.0-1.fc35.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.noarch","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"pkg-0:1.0-2.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"pkg-0:1.0-10.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"pkg-0:1.0-10.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-2.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"pkg-0:1.0~rc1-1.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-10.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"pkg-0:1.0-1.fc35.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0~rc1-1.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"pkg-0:1.0-1.fc36.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc35.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc36.x86_64","right":"openssl-1:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc36.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc36.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc36.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc36.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"pkg-0:1.0-1.fc36.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-1:1.1.1k-7.el8.x86_64","right":"openssl-3:1.1.1k-7.el8.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-1:1.1.1k-7.el8.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-1:1.1.1k-7.el8.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-1:1.1.1k-7.el8.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-1:1.1.1k-7.el8.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-3:1.1.1k-7.el8.x86_64","right":"podman-1:4.5.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-3:1.1.1k-7.el8.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-3:1.1.1k-7.el8.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"openssl-3:1.1.1k-7.el8.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"podman-1:4.5.0-1.el9.x86_64","right":"podman-2:4.4.0-1.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}
{"left":"podman-1:4.5.0-1.el9.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"podman-1:4.5.0-1.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"podman-2:4.4.0-1.el9.x86_64","right":"glibc-4:2.36-9.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"podman-2:4.4.0-1.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":1,"distro":"rpm","note":"pairwise"}
{"left":"glibc-4:2.36-9.el9.x86_64","right":"glibc-5:2.36-8.el9.x86_64","expected":-1,"distro":"rpm","note":"pairwise"}

View File

@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text;
using StellaOps.Concelier.Merge.Comparers;
using Xunit;
namespace StellaOps.Concelier.Merge.Tests;
public sealed class GoldenVersionComparisonTests
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true
};
[Theory]
[InlineData("rpm_version_comparison.golden.ndjson", "rpm")]
[InlineData("deb_version_comparison.golden.ndjson", "deb")]
[InlineData("apk_version_comparison.golden.ndjson", "apk")]
public void GoldenFiles_MatchComparers(string fileName, string distro)
{
if (ShouldUpdateGoldens())
{
WriteGoldens(fileName, distro);
return;
}
var cases = LoadCases(fileName);
Assert.True(cases.Count >= 100, $"Expected at least 100 cases in {fileName}.");
var failures = new List<string>();
foreach (var testCase in cases)
{
var actual = distro switch
{
"rpm" => Math.Sign(NevraComparer.Instance.Compare(testCase.Left, testCase.Right)),
"deb" => Math.Sign(DebianEvrComparer.Instance.Compare(testCase.Left, testCase.Right)),
"apk" => Math.Sign(ApkVersionComparer.Instance.Compare(testCase.Left, testCase.Right)),
_ => throw new InvalidOperationException($"Unsupported distro: {distro}")
};
if (actual != testCase.Expected)
{
failures.Add($"FAIL {distro}: {testCase.Left} vs {testCase.Right} expected {testCase.Expected} got {actual} ({testCase.Note})");
}
}
Assert.Empty(failures);
}
private static List<GoldenComparisonCase> LoadCases(string fileName)
{
var path = ResolveGoldenPath(fileName);
var lines = File.ReadAllLines(path);
var cases = new List<GoldenComparisonCase>(lines.Length);
foreach (var line in lines.Where(static l => !string.IsNullOrWhiteSpace(l)))
{
var testCase = JsonSerializer.Deserialize<GoldenComparisonCase>(line, JsonOptions);
if (testCase is null)
{
continue;
}
cases.Add(testCase);
}
return cases;
}
private static string ResolveGoldenPath(string fileName)
{
var candidates = new[]
{
Path.Combine(AppContext.BaseDirectory, "Fixtures", "Golden", fileName),
Path.Combine(GetProjectRoot(), "Fixtures", "Golden", fileName)
};
foreach (var candidate in candidates)
{
if (File.Exists(candidate))
{
return candidate;
}
}
throw new FileNotFoundException($"Golden file '{fileName}' not found.", fileName);
}
private static string GetProjectRoot()
=> Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", ".."));
private static void WriteGoldens(string fileName, string distro)
{
var cases = BuildCases(distro);
var path = Path.Combine(GetProjectRoot(), "Fixtures", "Golden", fileName);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var lines = cases.Select(testCase => JsonSerializer.Serialize(testCase, JsonOptions));
File.WriteAllText(path, string.Join('\n', lines), Encoding.ASCII);
}
private static List<GoldenComparisonCase> BuildCases(string distro)
{
string[] versions;
Func<string, string, int> comparer;
switch (distro)
{
case "rpm":
versions =
[
"kernel-0:4.18.0-80.el8.x86_64",
"kernel-1:4.18.0-80.el8.x86_64",
"kernel-0:4.18.11-80.el8.x86_64",
"pkg-0:1.0-1.el9.noarch",
"pkg-0:1.0-1.el9.x86_64",
"pkg-0:1.0-2.el9.x86_64",
"pkg-0:1.0-10.el9.x86_64",
"pkg-0:1.0~rc1-1.el9.x86_64",
"pkg-0:1.0-1.fc35.x86_64",
"pkg-0:1.0-1.fc36.x86_64",
"openssl-1:1.1.1k-7.el8.x86_64",
"openssl-3:1.1.1k-7.el8.x86_64",
"podman-1:4.5.0-1.el9.x86_64",
"podman-2:4.4.0-1.el9.x86_64",
"glibc-4:2.36-9.el9.x86_64",
"glibc-5:2.36-8.el9.x86_64"
];
comparer = (left, right) => Math.Sign(NevraComparer.Instance.Compare(left, right));
break;
case "deb":
versions =
[
"0:1.0-1",
"1.0-1",
"1.0-1ubuntu0.1",
"1.0-1ubuntu0.2",
"1.0-1ubuntu1",
"1.0-1ubuntu2",
"1.0-1+deb12u1",
"1.0-1+deb12u2",
"1:1.1.1n-0+deb11u2",
"1:1.1.1n-0+deb11u5",
"2.0~beta1-1",
"2.0~rc1-1",
"2.0-1",
"1.2.3-1",
"1.2.10-1",
"3.0.0-1"
];
comparer = (left, right) => Math.Sign(DebianEvrComparer.Instance.Compare(left, right));
break;
case "apk":
versions =
[
"1.0_alpha1-r0",
"1.0_beta1-r0",
"1.0_pre1-r0",
"1.0_rc1-r0",
"1.0-r0",
"1.0_p1-r0",
"1.2.3-r0",
"1.2.10-r0",
"2.0-r0",
"1.2.3a-r0",
"1.2.3b-r0",
"1.2.3-r1",
"1.2.3-r2",
"1.2.3_p1-r1",
"3.9.1-r0",
"3.1.1-r0"
];
comparer = (left, right) => Math.Sign(ApkVersionComparer.Instance.Compare(left, right));
break;
default:
throw new InvalidOperationException($"Unsupported distro: {distro}");
}
var cases = new List<GoldenComparisonCase>(versions.Length * versions.Length);
for (var i = 0; i < versions.Length; i++)
{
for (var j = i + 1; j < versions.Length; j++)
{
var left = versions[i];
var right = versions[j];
cases.Add(new GoldenComparisonCase(left, right, comparer(left, right), distro, "pairwise"));
}
}
return cases;
}
private static bool ShouldUpdateGoldens()
=> IsTruthy(Environment.GetEnvironmentVariable("UPDATE_GOLDENS"))
|| IsTruthy(Environment.GetEnvironmentVariable("DOTNET_TEST_UPDATE_GOLDENS"));
private static bool IsTruthy(string? value)
=> !string.IsNullOrWhiteSpace(value)
&& (string.Equals(value, "1", StringComparison.OrdinalIgnoreCase)
|| string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)
|| string.Equals(value, "yes", StringComparison.OrdinalIgnoreCase));
private sealed record GoldenComparisonCase(
string Left,
string Right,
int Expected,
string? Distro,
string? Note);
}

View File

@@ -105,4 +105,81 @@ public sealed class NevraComparerTests
var actual = Math.Sign(NevraComparer.Instance.Compare(left, right));
Assert.Equal(expected, actual);
}
public static TheoryData<string, string, int, string> ComparisonCases => BuildComparisonCases();
[Theory]
[MemberData(nameof(ComparisonCases))]
public void Compare_NevraVersions_ReturnsExpectedOrder(string left, string right, int expected, string note)
{
var actual = Math.Sign(NevraComparer.Instance.Compare(left, right));
Assert.True(expected == actual, $"[{note}] '{left}' vs '{right}': expected {expected}, got {actual}");
}
private static TheoryData<string, string, int, string> BuildComparisonCases()
{
var data = new TheoryData<string, string, int, string>();
// Epoch precedence.
data.Add("kernel-0:4.18.0-80.el8.x86_64", "kernel-1:4.18.0-80.el8.x86_64", -1, "epoch precedence: 0 < 1");
data.Add("kernel-2:4.18.0-80.el8.x86_64", "kernel-1:9.9.9-1.el8.x86_64", 1, "epoch precedence: 2 > 1");
data.Add("openssl-1:1.1.1k-7.el8.x86_64", "openssl-3:1.1.1k-7.el8.x86_64", -1, "epoch precedence: 1 < 3");
data.Add("bash-10:5.1.0-1.el9.x86_64", "bash-9:9.9.9-1.el9.x86_64", 1, "epoch precedence: 10 > 9");
data.Add("podman-1:4.5.0-1.el9.x86_64", "podman-2:4.4.0-1.el9.x86_64", -1, "epoch precedence: 1 < 2");
data.Add("glibc-5:2.36-8.el9.x86_64", "glibc-4:2.36-9.el9.x86_64", 1, "epoch precedence: 5 > 4");
// Numeric ordering.
for (var i = 1; i <= 10; i++)
{
data.Add($"pkg-0:1.{i}-1.el9.x86_64", $"pkg-0:1.{i + 1}-1.el9.x86_64", -1, "numeric segment ordering");
}
data.Add("pkg-0:1.9-1.el9.x86_64", "pkg-0:1.10-1.el9.x86_64", -1, "numeric length ordering");
data.Add("pkg-0:1.02-1.el9.x86_64", "pkg-0:1.2-1.el9.x86_64", 0, "leading zeros ignored");
data.Add("pkg-0:1.002-1.el9.x86_64", "pkg-0:1.2-1.el9.x86_64", 0, "leading zeros ignored");
// Alpha ordering.
data.Add("pkg-0:1.0a-1.el9.x86_64", "pkg-0:1.0b-1.el9.x86_64", -1, "alpha segment ordering");
data.Add("pkg-0:1.0aa-1.el9.x86_64", "pkg-0:1.0b-1.el9.x86_64", -1, "alpha length ordering");
data.Add("pkg-0:1.0b-1.el9.x86_64", "pkg-0:1.0aa-1.el9.x86_64", 1, "alpha segment ordering");
data.Add("pkg-0:1.0a-1.el9.x86_64", "pkg-0:1.0-1.el9.x86_64", 1, "alpha sorts after empty");
data.Add("pkg-0:1.0-1.el9.x86_64", "pkg-0:1.0a-1.el9.x86_64", -1, "empty sorts before alpha");
data.Add("pkg-0:1.0z-1.el9.x86_64", "pkg-0:1.0aa-1.el9.x86_64", 1, "alpha lexical ordering");
// Tilde pre-releases.
data.Add("pkg-0:1.0~rc1-1.el9.x86_64", "pkg-0:1.0-1.el9.x86_64", -1, "tilde sorts before release");
data.Add("pkg-0:1.0~rc1-1.el9.x86_64", "pkg-0:1.0~rc2-1.el9.x86_64", -1, "tilde rc ordering");
data.Add("pkg-0:1.0~~-1.el9.x86_64", "pkg-0:1.0~-1.el9.x86_64", -1, "double tilde sorts earlier");
data.Add("pkg-0:1.0~beta-1.el9.x86_64", "pkg-0:1.0~rc-1.el9.x86_64", -1, "tilde alpha segment ordering");
data.Add("pkg-0:1.0~rc-1.el9.x86_64", "pkg-0:1.0~rc-1.el9.x86_64", 0, "tilde equivalence");
data.Add("pkg-0:1.0~rc-1.el9.x86_64", "pkg-0:1.0~rc-2.el9.x86_64", -1, "release breaks tilde ties");
// Release qualifiers and backports.
data.Add("pkg-0:1.0-1.el8.x86_64", "pkg-0:1.0-1.el9.x86_64", -1, "release qualifier el8 < el9");
data.Add("pkg-0:1.0-1.el8.x86_64", "pkg-0:1.0-1.el8_5.x86_64", -1, "backport suffix ordering");
data.Add("pkg-0:1.0-1.el8_5.x86_64", "pkg-0:1.0-1.el8_5.1.x86_64", -1, "incremental backport");
data.Add("pkg-0:1.0-1.el8_5.1.x86_64", "pkg-0:1.0-2.el8.x86_64", -1, "release increments beat base");
data.Add("pkg-0:1.0-2.el8.x86_64", "pkg-0:1.0-10.el8.x86_64", -1, "release numeric ordering");
data.Add("pkg-0:1.0-10.el8.x86_64", "pkg-0:1.0-2.el8.x86_64", 1, "release numeric ordering");
data.Add("pkg-0:1.0-1.fc35.x86_64", "pkg-0:1.0-1.fc36.x86_64", -1, "release qualifier fc35 < fc36");
data.Add("pkg-0:1.0-1.el8_5.x86_64", "pkg-0:1.0-1.el8_5.0.x86_64", -1, "zero suffix still sorts later");
data.Add("pkg-0:1.0-1.el8_5.0.x86_64", "pkg-0:1.0-1.el8_5.x86_64", 1, "zero suffix still sorts later");
data.Add("pkg-0:1.0-1.el8_5.1.x86_64", "pkg-0:1.0-1.el8_5.2.x86_64", -1, "backport numeric ordering");
// Architecture ordering.
data.Add("pkg-0:1.0-1.el9.noarch", "pkg-0:1.0-1.el9.x86_64", -1, "architecture lexical ordering");
data.Add("pkg-0:1.0-1.el9.aarch64", "pkg-0:1.0-1.el9.x86_64", -1, "architecture lexical ordering");
data.Add("pkg-0:1.0-1.el9.ppc64le", "pkg-0:1.0-1.el9.ppc64", 1, "architecture lexical ordering");
data.Add("pkg-0:1.0-1.el9.s390x", "pkg-0:1.0-1.el9.s390", 1, "architecture lexical ordering");
data.Add("pkg-0:1.0-1.el9.arm64", "pkg-0:1.0-1.el9.aarch64", 1, "architecture lexical ordering");
// Package name ordering.
data.Add("aaa-0:1.0-1.el9.x86_64", "bbb-0:1.0-1.el9.x86_64", -1, "name lexical ordering");
data.Add("openssl-0:1.0-1.el9.x86_64", "openssl-libs-0:1.0-1.el9.x86_64", -1, "name lexical ordering");
data.Add("zlib-0:1.0-1.el9.x86_64", "bzip2-0:1.0-1.el9.x86_64", 1, "name lexical ordering");
data.Add("kernel-0:1.0-1.el9.x86_64", "kernel-core-0:1.0-1.el9.x86_64", -1, "name lexical ordering");
data.Add("glibc-0:1.0-1.el9.x86_64", "glibc-devel-0:1.0-1.el9.x86_64", -1, "name lexical ordering");
return data;
}
}

View File

@@ -0,0 +1,22 @@
# Concelier Merge Tests
This project verifies distro version comparison logic and merge rules.
## Layout
- Comparer unit tests: `*.Tests.cs` in this project (RPM, Debian, APK).
- Golden fixtures: `Fixtures/Golden/*.golden.ndjson`.
- Integration cross-checks: `src/Concelier/__Tests/StellaOps.Concelier.Integration.Tests`.
## Golden files
Golden files capture pairwise comparison results in NDJSON.
See `Fixtures/Golden/README.md` for format and regeneration steps.
## Integration tests
Cross-check tests compare container-installed versions against fixed
versions using the same comparers. They require Docker/Testcontainers.
Enable with:
`$env:STELLAOPS_INTEGRATION_TESTS = "true"`
Run (from repo root):
`dotnet test src/Concelier/__Tests/StellaOps.Concelier.Integration.Tests/StellaOps.Concelier.Integration.Tests.csproj`

View File

@@ -10,4 +10,9 @@
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
</ItemGroup>
</Project>
<ItemGroup>
<None Update="Fixtures\Golden\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
# Concelier Merge Comparator Test Tasks
Local status mirror for `docs/implplan/SPRINT_2000_0003_0002_distro_version_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| T1 | DONE | NEVRA comparison corpus expanded. |
| T2 | DONE | Debian EVR comparison corpus expanded. |
| T3 | DOING | Golden NDJSON fixtures + regression runner. |
| T4 | TODO | Testcontainers real-image cross-checks. |
| T5 | TODO | Test corpus README. |
Last synced: 2025-12-22 (UTC).