debian/version: dpkg-compatible version comparison

Implement the documented dpkg ordering algorithm (Debian Policy 5.6.1):
numeric epoch, then upstream/revision compared as alternating non-digit
and digit chunks, with '~' ordering before anything including the empty
chunk and letters before non-letters in non-digit chunks.

Adds Ord/PartialOrd for DebianVersion, a free compare() and a
later_than() convenience.

Unit tests port all vectors from dpkg's scripts/t/Dpkg_Version.t plus
Ubuntu-flavored cases (security uploads, ~ppa1 backports). Differential
gate: every vector cross-checked against real 'dpkg --compare-versions'
for <<, <=, =, >= and >>.
This commit is contained in:
2026-08-24 11:57:54 +02:00
parent 4c52336000
commit 489b2aa29b
3 changed files with 312 additions and 2 deletions
+40
View File
@@ -909,6 +909,46 @@ mod differential_tests {
diff_arch_env_one(None);
}
/// Differential check of [`crate::debian::version`] against real
/// `dpkg --compare-versions` over every ported dpkg test vector and
/// every relation operator.
#[test]
fn diff_version_compare_against_dpkg() {
let vectors = crate::debian::version::test_vectors::COMPARE;
assert!(!vectors.is_empty());
for (a, b, expected) in vectors {
let va = crate::debian::DebianVersion::parse(a)
.unwrap_or_else(|e| panic!("parse {a}: {e}"));
let vb = crate::debian::DebianVersion::parse(b)
.unwrap_or_else(|e| panic!("parse {b}: {e}"));
let ours = match va.cmp(&vb) {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
};
assert_eq!(ours, *expected, "native compare: {a} cmp {b}");
// Cross-check the relation operators against the real tool.
for (op, holds) in [
("<<", *expected < 0),
("<=", *expected <= 0),
("=", *expected == 0),
(">=", *expected >= 0),
(">>", *expected > 0),
] {
let output = Command::new("dpkg")
.args(["--compare-versions", "--", a, op, b])
.status()
.expect("run dpkg --compare-versions");
assert_eq!(
output.success(),
holds,
"dpkg --compare-versions -- {a} {op} {b}"
);
}
}
}
#[test]
fn diff_native_minimal() {
differential_case(&FixtureSpec::new("pkh-diff-a", "1.0-1", "unstable"));