diff --git a/src/debian/deps.rs b/src/debian/deps.rs index 481c3a4..7dd90e1 100644 --- a/src/debian/deps.rs +++ b/src/debian/deps.rs @@ -187,12 +187,16 @@ pub fn parse_simple(dep: &str, build_dep: bool) -> Result { let constraint = match (caps.get(5), caps.get(6)) { (Some(op), Some(version)) => { + // The deprecated single-character spellings `<` and `>` were + // "confusingly defined" (Debian Policy §7) to mean earlier-or-equal + // and later-or-equal, i.e. `<=` and `>=`; dpkg still accepts them + // with those non-strict semantics. let relation = match op.as_str() { - "<<" | "<" => Relation::Lt, - "<=" => Relation::Le, + "<<" => Relation::Lt, + "<" | "<=" => Relation::Le, "=" => Relation::Eq, - ">=" => Relation::Ge, - ">>" | ">" => Relation::Gt, + ">" | ">=" => Relation::Ge, + ">>" => Relation::Gt, other => return Err(format!("invalid relation '{other}' in '{dep}'")), }; let version = DebianVersion::parse(version.as_str()) @@ -1024,6 +1028,56 @@ mod tests { assert!(Deps::parse("foo:native", &opts("amd64", &[])).is_ok()); } + /// The deprecated single-character operators `<` and `>` mean `<=` and + /// `>=` (Debian Policy §7, dpkg behavior), and canonicalize on output. + #[test] + fn legacy_single_char_relations() { + let rel = |dep: &str| { + parse_simple(dep, true) + .unwrap() + .constraint + .unwrap() + .relation + }; + + assert_eq!(rel("foo (<< 1.0)"), Relation::Lt); + assert_eq!(rel("foo (< 1.0)"), Relation::Le); + assert_eq!(rel("foo (<= 1.0)"), Relation::Le); + assert_eq!(rel("foo (= 1.0)"), Relation::Eq); + assert_eq!(rel("foo (>= 1.0)"), Relation::Ge); + assert_eq!(rel("foo (> 1.0)"), Relation::Ge); + assert_eq!(rel("foo (>> 1.0)"), Relation::Gt); + + // Rendering always uses the canonical modern spellings. + for (dep, rendered) in [ + ("foo (< 1.0)", "foo (<= 1.0)"), + ("foo (> 1.0)", "foo (>= 1.0)"), + ("foo (<< 1.0)", "foo (<< 1.0)"), + ("foo (>> 1.0)", "foo (>> 1.0)"), + ] { + assert_eq!(parse_simple(dep, true).unwrap().output(), rendered); + } + } + + /// `foo (< 1.0)` is satisfied by an installed `foo 1.0` (the legacy + /// operator is non-strict); `foo (< 0.9)` is not. + #[test] + fn legacy_single_char_evaluation() { + let mut facts = Facts::new("amd64", "amd64"); + facts.add_installed("foo", "1.0", "amd64", "no"); + let o = |s: &str| parse_simple(s, true).unwrap(); + + assert_eq!(facts.evaluate_relation(&o("foo (< 1.0)")), Some(true)); + assert_eq!(facts.evaluate_relation(&o("foo (<= 1.0)")), Some(true)); + assert_eq!(facts.evaluate_relation(&o("foo (< 0.9)")), Some(false)); + assert_eq!(facts.evaluate_relation(&o("foo (<< 1.0)")), Some(false)); + + assert_eq!(facts.evaluate_relation(&o("foo (> 1.0)")), Some(true)); + assert_eq!(facts.evaluate_relation(&o("foo (>= 1.0)")), Some(true)); + assert_eq!(facts.evaluate_relation(&o("foo (> 1.1)")), Some(false)); + assert_eq!(facts.evaluate_relation(&o("foo (>> 1.0)")), Some(false)); + } + /// Ported from dpkg `t/Dpkg_Deps.t`: architecture reduction. #[test] fn arch_reduction() {