use color_eyre::Result;
use crate::models::Package;
use crate::models::PackageFormat;
use crate::parse_requires::{VersionConstraint, Operator};
use crate::version_constraint::check_version_satisfies_constraints;
use crate::parse_version::PackageVersion;
fn extract_version_from_remainder<'a>(remainder: &'a str, provide_entry: &'a str) -> Option<(&'a str, &'a str)> {
if remainder.starts_with('=') {
Some(("=", &remainder[1..]))
} else if remainder.starts_with(" = ") {
Some(("=", &remainder[3..]))
} else if remainder.starts_with("(= ") {
let version_start = 3;
if let Some(close_pos) = remainder[version_start..].find(')') {
Some(("=", &remainder[version_start..version_start + close_pos]))
} else {
Some(("=", &remainder[version_start..]))
}
} else if let Some(pos) = provide_entry.find('=') {
if pos > 0 && pos < provide_entry.len() - 1 {
let before = &provide_entry[pos - 1..pos];
let after = &provide_entry[pos + 1..pos + 2];
if before != " " && after != " " {
Some(("=", &provide_entry[pos + 1..]))
} else if before == " " {
Some(("=", &provide_entry[pos + 3..]))
} else {
Some(("=", &provide_entry[pos + 1..]))
}
} else {
Some(("=", &provide_entry[pos + 1..]))
}
} else if let Some(pos) = provide_entry.find(" = ") {
Some(("=", &provide_entry[pos + 3..]))
} else if let Some(pos) = provide_entry.find("(= ") {
let version_start = pos + 3;
if let Some(close_pos) = provide_entry[version_start..].find(')') {
Some(("=", &provide_entry[version_start..version_start + close_pos]))
} else {
Some(("=", &provide_entry[version_start..]))
}
} else {
None
}
}
fn check_implicit_provide(
provider_pkgkey: &str,
base_capability: &str,
provider_pkg: &Package,
constraints: &Vec<VersionConstraint>,
format: PackageFormat,
) -> Result<bool> {
if let Ok(pkgname) = crate::package::pkgkey2pkgname(provider_pkgkey) {
if base_capability == pkgname {
let provided_version = provider_pkg.version.trim();
log::trace!(
"Provider {} implicitly provides '{}' (its own name) with version '{}'",
provider_pkgkey, base_capability, provided_version
);
if check_version_satisfies_constraints(provided_version, constraints, format)? {
log::debug!(
"Provider {} implicitly provides '{}' version '{}' satisfies all constraints",
provider_pkgkey, base_capability, provided_version
);
return Ok(true);
}
}
}
Ok(false)
}
fn provide_remainder_has_ignored_comparison_operators(remainder_trimmed: &str) -> bool {
!remainder_trimmed.is_empty()
&& (remainder_trimmed.starts_with(">=")
|| remainder_trimmed.starts_with("<=")
|| remainder_trimmed.starts_with(" > ")
|| remainder_trimmed.starts_with(" < ")
|| (remainder_trimmed.starts_with('>') && !remainder_trimmed.starts_with(">="))
|| (remainder_trimmed.starts_with('<') && !remainder_trimmed.starts_with("<=")))
}
fn single_provide_entry_satisfies(
provider_pkgkey: &str,
capability: &str,
base_capability: &str,
bundled_variant: &str,
provider_pkg_version: &str,
constraints: &Vec<VersionConstraint>,
format: PackageFormat,
provide_entry: &str,
) -> Result<bool> {
let provide_entry_trimmed = provide_entry.trim();
let matches_direct = provide_entry_trimmed.starts_with(base_capability);
let matches_bundled = provide_entry_trimmed.starts_with(bundled_variant);
if !matches_direct && !matches_bundled {
return Ok(false);
}
let matched_capability = if matches_bundled {
bundled_variant
} else {
base_capability
};
let remainder = &provide_entry_trimmed[matched_capability.len()..];
let remainder_trimmed = remainder.trim_start();
if provide_remainder_has_ignored_comparison_operators(remainder_trimmed) {
return Ok(false);
}
if !remainder_trimmed.is_empty()
&& !remainder_trimmed.starts_with('=')
&& !remainder_trimmed.starts_with("(= ")
{
return Ok(false);
}
if format == PackageFormat::Pacman && remainder_trimmed.starts_with('=') {
let after_equals = &remainder_trimmed[1..];
if after_equals.contains(".so") && !after_equals.chars().next().map_or(false, |c| c.is_ascii_digit()) {
return Ok(true);
}
}
let provide_version = extract_version_from_remainder(remainder_trimmed, provide_entry);
let has_non_conditional_constraints = constraints
.iter()
.any(|c| !matches!(c.operator, Operator::IfInstall));
let mut used_package_version_directly = false;
let provided_version = if let Some((_, version_str)) = provide_version {
version_str.trim()
} else {
if has_non_conditional_constraints {
used_package_version_directly = true;
provider_pkg_version
} else {
return Ok(true);
}
};
if check_version_satisfies_constraints(provided_version, constraints, format)? {
log::debug!(
"Provider {} provides '{}' version '{}' satisfies all constraints",
provider_pkgkey, capability, provided_version
);
return Ok(true);
}
if !used_package_version_directly
&& format == PackageFormat::Rpm
&& rpm_constraints_require_release(constraints)
&& rpm_provide_missing_release(provided_version, provider_pkg_version)
{
if check_version_satisfies_constraints(provider_pkg_version, constraints, format)? {
log::debug!(
"Provider {} fallback: using package version '{}' for capability '{}' satisfies constraints {:?}",
provider_pkgkey,
provider_pkg_version,
capability,
constraints
);
return Ok(true);
}
}
Ok(false)
}
pub fn check_provider_satisfies_constraints(
provider_pkg: &Package,
capability: &str,
constraints: &Vec<VersionConstraint>,
format: PackageFormat,
) -> Result<bool> {
let provider_pkgkey = &provider_pkg.pkgkey;
let base_capability = if let Some(pos) = capability.find(|c: char| c == '>' || c == '<' || c == '=' || c == '!') {
capability[..pos].trim_end()
} else {
capability
};
let provider_pkg_version = provider_pkg.version.trim();
log::trace!(
"Checking provider {} for capability '{}' (base: '{}') with constraints {:?}",
provider_pkgkey, capability, base_capability, constraints
);
let bundled_variant = format!("bundled({})", base_capability);
for provide_entry_string in &provider_pkg.provides {
let provide_items: Vec<&str> = if format == PackageFormat::Apk || format == PackageFormat::Pacman {
provide_entry_string.split_whitespace().collect()
} else {
vec![provide_entry_string.as_str()]
};
for provide_entry in provide_items {
if single_provide_entry_satisfies(
provider_pkgkey,
capability,
base_capability,
&bundled_variant,
provider_pkg_version,
constraints,
format,
provide_entry,
)? {
return Ok(true);
}
}
}
if check_implicit_provide(provider_pkgkey, &base_capability, &provider_pkg, constraints, format)? {
return Ok(true);
}
log::debug!(
"Provider {} does not provide '{}' with version satisfying constraints",
provider_pkgkey, capability
);
Ok(false)
}
fn rpm_provide_missing_release(provided_version: &str, package_version: &str) -> bool {
let provided = match PackageVersion::parse(provided_version) {
Ok(parsed) => parsed,
Err(_) => return false,
};
let package = match PackageVersion::parse(package_version) {
Ok(parsed) => parsed,
Err(_) => return false,
};
provided.epoch == package.epoch
&& provided.upstream == package.upstream
&& provided.revision == "0"
&& package.revision != "0"
}
fn rpm_constraints_require_release(constraints: &Vec<VersionConstraint>) -> bool {
constraints
.iter()
.filter(|c| !matches!(c.operator, Operator::IfInstall))
.filter_map(|c| PackageVersion::parse(c.operand.trim()).ok())
.any(|parsed| parsed.revision != "0")
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use crate::models::{PackageFormat, PACKAGE_CACHE};
use crate::package_cache::add_package_to_cache;
#[test]
fn test_check_provider_satisfies_constraints_with_or_conditions() {
PACKAGE_CACHE.clear();
let mut provider_pkg = Package {
pkgname: "python3-isort".to_string(),
version: "6.1.0-1.fc42".to_string(),
arch: "noarch".to_string(),
pkgkey: "python3-isort__6.1.0-1.fc42__noarch".to_string(),
provides: vec!["python3.13dist(isort) = 6.1".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "5.13".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "5.13".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "7".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "4.2.5".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(isort)",
&constraints,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 6.1 should satisfy OR condition (> 5.13) and AND conditions (< 7, >= 4.2.5)");
provider_pkg.provides = vec!["python3.13dist(isort) = 5.13".to_string()];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(isort)",
&constraints,
PackageFormat::Rpm,
);
assert!(!result.unwrap(), "Version 5.13 should NOT satisfy OR condition (neither < 5.13 nor > 5.13, exactly equal)");
provider_pkg.provides = vec!["python3.13dist(isort) = 5.0".to_string()];
let constraints_v2 = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "5.13".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "5.13".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "4.2.5".to_string(),
},
];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(isort)",
&constraints_v2,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 5.0 should satisfy OR condition (< 5.13) and AND condition (>= 4.2.5)");
let constraints_and_only = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "4.2.5".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "7".to_string(),
},
];
provider_pkg.provides = vec!["python3.13dist(isort) = 6.1".to_string()];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(isort)",
&constraints_and_only,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 6.1 should satisfy all AND constraints");
let constraints_fail = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "4.2.5".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "5.0".to_string(),
},
];
provider_pkg.provides = vec!["python3.13dist(isort) = 6.1".to_string()];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(isort)",
&constraints_fail,
PackageFormat::Rpm,
);
assert!(!result.unwrap(), "Version 6.1 should NOT satisfy constraint < 5.0");
let constraints_multiple_or = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "3".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "3".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "7".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "7".to_string(),
},
];
provider_pkg.provides = vec!["python3.13dist(isort) = 5.0".to_string()];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(isort)",
&constraints_multiple_or,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 5.0 should satisfy both OR groups (> 3 and < 7)");
}
#[test]
fn test_or_group_detection_with_different_operands() {
PACKAGE_CACHE.clear();
let mut provider_pkg = Package {
pkgname: "python3-google-api-core".to_string(),
version: "2.11.1-11.fc42".to_string(),
arch: "noarch".to_string(),
pkgkey: "python3-google-api-core__1:2.11.1-11.fc42__noarch".to_string(),
provides: vec!["python3.13dist(google-api-core) = 2.11.1".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "2.1".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.2".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "1.31.6".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(google-api-core)",
&constraints,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 2.11.1 should satisfy OR condition (>= 2.2) and AND condition (>= 1.31.6)");
provider_pkg.provides = vec!["python3.13dist(google-api-core) = 2.0.5".to_string()];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(google-api-core)",
&constraints,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 2.0.5 should satisfy OR condition (< 2.1) and AND condition (>= 1.31.6)");
}
#[test]
fn test_or_group_detection_multiple_with_clauses() {
PACKAGE_CACHE.clear();
let provider_pkg = Package {
pkgname: "python3-google-api-core".to_string(),
version: "2.5.0-1.fc42".to_string(),
arch: "noarch".to_string(),
pkgkey: "python3-google-api-core__1:2.5.0-1.fc42__noarch".to_string(),
provides: vec!["python3.13dist(google-api-core) = 2.5.0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "2.1".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.2".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "2.2".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.3".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "2.3".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "2.3".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "3".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "1.31.6".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"python3.13dist(google-api-core)",
&constraints,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 2.5.0 should satisfy all OR groups and AND constraints");
}
#[test]
fn test_or_group_detection_same_operand_strict() {
PACKAGE_CACHE.clear();
let mut provider_pkg = Package {
pkgname: "test-pkg".to_string(),
version: "2.3.0-1".to_string(),
arch: "noarch".to_string(),
pkgkey: "test-pkg__2.3.0-1__noarch".to_string(),
provides: vec!["test-capability = 2.3".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints_not_exclusive = vec![
VersionConstraint {
operator: Operator::VersionLessThanEqual,
operand: "2.3".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.3".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"test-capability",
&constraints_not_exclusive,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 2.3 should satisfy both <= 2.3 and >= 2.3 (not mutually exclusive)");
let constraints_exclusive = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "2.3".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "2.3".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"test-capability",
&constraints_exclusive,
PackageFormat::Rpm,
);
assert!(!result.unwrap(), "Version 2.3 should NOT satisfy both < 2.3 and > 2.3 (mutually exclusive)");
provider_pkg.provides = vec!["test-capability = 2.2".to_string()];
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let result = check_provider_satisfies_constraints(
&provider_pkg,
"test-capability",
&constraints_exclusive,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Version 2.2 should satisfy OR condition (< 2.3)");
}
#[test]
fn test_debian_provide_format_with_parentheses() {
PACKAGE_CACHE.clear();
let provider_pkg = Package {
pkgname: "libgcc-s1".to_string(),
version: "14.2.0-19".to_string(),
arch: "amd64".to_string(),
pkgkey: "libgcc-s1__14.2.0-19__amd64".to_string(),
provides: vec!["libgcc1 (= 1:14.2.0-19)".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "1:3.0".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"libgcc1",
&constraints,
PackageFormat::Deb,
);
assert!(result.unwrap(), "Version 1:14.2.0-19 should satisfy >= 1:3.0");
}
#[test]
fn test_debian_provide_format_various_operators() {
PACKAGE_CACHE.clear();
let test_cases = vec![
("test-pkg (= 1.0.0)", "test-pkg", Operator::VersionEqual, "1.0.0", true),
("test-pkg (= 1.0.0)", "test-pkg", Operator::VersionEqual, "1.0.1", false),
("test-pkg (= 2.0.0)", "test-pkg", Operator::VersionGreaterThanEqual, "1.5.0", true),
("test-pkg (= 2.0.0)", "test-pkg", Operator::VersionGreaterThanEqual, "2.5.0", false),
("test-pkg (= 3.0.0)", "test-pkg", Operator::VersionLessThanEqual, "3.5.0", true),
("test-pkg (= 3.0.0)", "test-pkg", Operator::VersionLessThanEqual, "2.5.0", false),
("test-pkg (= 4.5.0)", "test-pkg", Operator::VersionGreaterThan, "4.0.0", true),
("test-pkg (= 4.0.0)", "test-pkg", Operator::VersionGreaterThan, "4.0.0", false),
("test-pkg (= 4.5.0)", "test-pkg", Operator::VersionLessThan, "5.0.0", true),
("test-pkg (= 5.0.0)", "test-pkg", Operator::VersionLessThan, "5.0.0", false),
];
for (provide_entry, capability, constraint_op, constraint_operand, expected) in test_cases {
let provider_pkg = Package {
pkgname: "test-provider".to_string(),
version: "1.0.0".to_string(),
arch: "amd64".to_string(),
pkgkey: format!("test-provider__1.0.0__amd64"),
provides: vec![provide_entry.to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: constraint_op.clone(),
operand: constraint_operand.to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
capability,
&constraints,
PackageFormat::Deb,
);
assert_eq!(
result.unwrap(),
expected,
"Failed for provide_entry: '{}', constraint: {:?} '{}'",
provide_entry,
constraint_op,
constraint_operand
);
}
}
#[test]
fn test_epoch_version_comparison() {
PACKAGE_CACHE.clear();
let provider_pkg = Package {
pkgname: "libgcc-s1".to_string(),
version: "14.2.0-19".to_string(),
arch: "amd64".to_string(),
pkgkey: "libgcc-s1__14.2.0-19__amd64".to_string(),
provides: vec!["libgcc1 (= 1:14.2.0-19)".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let test_cases = vec![
("1:14.2.0-19", Operator::VersionGreaterThanEqual, "1:3.0", true),
("1:14.2.0-19", Operator::VersionGreaterThanEqual, "1:14.2.0-19", true),
("1:14.2.0-19", Operator::VersionGreaterThanEqual, "1:15.0.0", false),
("1:14.2.0-19", Operator::VersionGreaterThanEqual, "2:1.0.0", false),
("2:1.0.0", Operator::VersionGreaterThanEqual, "1:14.2.0-19", true),
("1:14.2.0-19", Operator::VersionLessThanEqual, "1:15.0.0", true),
("1:14.2.0-19", Operator::VersionLessThanEqual, "1:14.2.0-19", true),
("1:14.2.0-19", Operator::VersionLessThanEqual, "1:3.0", false),
("1:14.2.0-19", Operator::VersionEqual, "1:14.2.0-19", true),
("1:14.2.0-19", Operator::VersionEqual, "1:14.2.0-20", false),
];
for (provided_version, constraint_op, constraint_operand, expected) in test_cases {
let mut test_pkg = provider_pkg.clone();
test_pkg.provides = vec![format!("libgcc1 (= {})", provided_version)];
test_pkg.pkgkey = format!("libgcc-s1__{}__amd64", provided_version.replace(':', "_"));
add_package_to_cache(Arc::new(test_pkg.clone()), PackageFormat::Deb);
let constraints = vec![
VersionConstraint {
operator: constraint_op.clone(),
operand: constraint_operand.to_string(),
},
];
let result = check_provider_satisfies_constraints(
&test_pkg,
"libgcc1",
&constraints,
PackageFormat::Deb,
);
assert_eq!(
result.unwrap(),
expected,
"Failed for provided_version: '{}', constraint: {:?} '{}'",
provided_version,
constraint_op,
constraint_operand
);
}
}
#[test]
fn test_rpm_vs_debian_provide_format() {
PACKAGE_CACHE.clear();
let rpm_provider = Package {
pkgname: "rpm-provider".to_string(),
version: "1.0.0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "rpm-provider__1.0.0__x86_64".to_string(),
provides: vec!["test-capability = 1.0.0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(rpm_provider.clone()), PackageFormat::Rpm);
let rpm_constraints = vec![
VersionConstraint {
operator: Operator::VersionEqual,
operand: "1.0.0".to_string(),
},
];
let rpm_result = check_provider_satisfies_constraints(
&rpm_provider,
"test-capability",
&rpm_constraints,
PackageFormat::Rpm,
);
assert!(rpm_result.unwrap(), "RPM format should work: 'test-capability = 1.0.0'");
let deb_provider = Package {
pkgname: "deb-provider".to_string(),
version: "1.0.0".to_string(),
arch: "amd64".to_string(),
pkgkey: "deb-provider__1.0.0__amd64".to_string(),
provides: vec!["test-capability (= 1.0.0)".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(deb_provider.clone()), PackageFormat::Deb);
let deb_constraints = vec![
VersionConstraint {
operator: Operator::VersionEqual,
operand: "1.0.0".to_string(),
},
];
let deb_result = check_provider_satisfies_constraints(
&deb_provider,
"test-capability",
&deb_constraints,
PackageFormat::Deb,
);
assert!(deb_result.unwrap(), "Debian format should work: 'test-capability (= 1.0.0)'");
}
#[test]
fn test_alpine_pkgconfig_multiple_provides() {
PACKAGE_CACHE.clear();
let provider_pkg = Package {
pkgname: "pcre2-dev".to_string(),
version: "10.46-r0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "pcre2-dev__10.46-r0__x86_64".to_string(),
provides: vec![
"pc:libpcre2-16=10.46 pc:libpcre2-32=10.46 pc:libpcre2-8=10.46 pc:libpcre2-posix=10.46 cmd:pcre2-config=10.46-r0".to_string(),
],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "10.32".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"pc:libpcre2-8",
&constraints,
PackageFormat::Apk,
).unwrap();
assert!(result, "pc:libpcre2-8=10.46 should satisfy pc:libpcre2-8>=10.32");
let constraints_fail = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "10.50".to_string(),
},
];
let result_fail = check_provider_satisfies_constraints(
&provider_pkg,
"pc:libpcre2-8",
&constraints_fail,
PackageFormat::Apk,
).unwrap();
assert!(!result_fail, "pc:libpcre2-8=10.46 should NOT satisfy pc:libpcre2-8>=10.50");
let result_with_constraint_in_name = check_provider_satisfies_constraints(
&provider_pkg,
"pc:libpcre2-8>=10.32",
&constraints,
PackageFormat::Apk,
).unwrap();
assert!(result_with_constraint_in_name, "Should handle capability name with version constraints");
let constraints_posix = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "10.0".to_string(),
},
];
let result_posix = check_provider_satisfies_constraints(
&provider_pkg,
"pc:libpcre2-posix",
&constraints_posix,
PackageFormat::Apk,
).unwrap();
assert!(result_posix, "pc:libpcre2-posix=10.46 should satisfy pc:libpcre2-posix>=10.0");
}
#[test]
fn test_check_provider_satisfies_constraints_implicit_provide() {
PACKAGE_CACHE.clear();
let bluez_pkg = Package {
pkgname: "bluez".to_string(),
version: "5.82.0-r0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "bluez__5.82.0-r0__x86_64".to_string(),
provides: vec![],
..Default::default()
};
add_package_to_cache(Arc::new(bluez_pkg.clone()), PackageFormat::Apk);
let constraints_compatible = vec![
VersionConstraint {
operator: Operator::VersionCompatible,
operand: "5.82".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&bluez_pkg,
"bluez",
&constraints_compatible,
PackageFormat::Apk,
);
assert!(result.unwrap(), "bluez__5.82.0-r0__x86_64 should satisfy bluez~5.82 via implicit provide");
let constraints_gte = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "5.80".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&bluez_pkg,
"bluez",
&constraints_gte,
PackageFormat::Apk,
);
assert!(result.unwrap(), "bluez__5.82.0-r0__x86_64 should satisfy bluez>=5.80 via implicit provide");
let constraints_fail = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "5.80".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&bluez_pkg,
"bluez",
&constraints_fail,
PackageFormat::Apk,
);
assert!(!result.unwrap(), "bluez__5.82.0-r0__x86_64 should NOT satisfy bluez<5.80");
let result = check_provider_satisfies_constraints(
&bluez_pkg,
"different-package",
&constraints_compatible,
PackageFormat::Apk,
);
assert!(!result.unwrap(), "Should not use implicit provide when capability name doesn't match package name");
let bluez_with_explicit = Package {
pkgname: "bluez".to_string(),
version: "5.82.0-r0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "bluez__5.82.0-r0__x86_64_explicit".to_string(),
provides: vec!["bluez = 5.82.0-r0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(bluez_with_explicit.clone()), PackageFormat::Apk);
let result = check_provider_satisfies_constraints(
&bluez_with_explicit,
"bluez",
&constraints_compatible,
PackageFormat::Apk,
);
assert!(result.unwrap(), "Explicit provide should work and take precedence over implicit");
let constraints_multiple = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "5.80".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "6.0".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&bluez_pkg,
"bluez",
&constraints_multiple,
PackageFormat::Apk,
);
assert!(result.unwrap(), "bluez__5.82.0-r0__x86_64 should satisfy bluez>=5.80,<6.0 via implicit provide");
let constraints_or = vec![
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "5.80".to_string(),
},
VersionConstraint {
operator: Operator::VersionGreaterThan,
operand: "5.80".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&bluez_pkg,
"bluez",
&constraints_or,
PackageFormat::Apk,
);
assert!(result.unwrap(), "bluez__5.82.0-r0__x86_64 should satisfy bluez<5.80 OR bluez>5.80 via implicit provide");
}
#[test]
fn test_rpm_provide_with_version_operators() {
PACKAGE_CACHE.clear();
let provider_pkg = Package {
pkgname: "test-provider".to_string(),
version: "3.0.0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "test-provider__3.0.0__x86_64".to_string(),
provides: vec!["test-cap = 3.0.0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.0.0".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&provider_pkg,
"test-cap",
&constraints,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "Package providing test-cap=3.0.0 should satisfy requirement >=2.0.0");
let provider_pkg_low = Package {
pkgname: "test-pkg-low".to_string(),
version: "1.0.0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "test-pkg-low__1.0.0__x86_64".to_string(),
provides: vec!["test-cap = 1.0.0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg_low.clone()), PackageFormat::Rpm);
let constraints_high = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.0.0".to_string(),
},
];
let result_low = check_provider_satisfies_constraints(
&provider_pkg_low,
"test-cap",
&constraints_high,
PackageFormat::Rpm,
);
assert!(!result_low.unwrap(), "Package providing test-cap=1.0.0 should NOT satisfy requirement >=2.0.0");
let provider_pkg_multi = Package {
pkgname: "test-pkg-multi".to_string(),
version: "3.0.0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "test-pkg-multi__3.0.0__x86_64".to_string(),
provides: vec!["test-cap = 3.0.0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg_multi.clone()), PackageFormat::Rpm);
let constraints_multi = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "2.5.0".to_string(),
},
VersionConstraint {
operator: Operator::VersionLessThan,
operand: "4.0.0".to_string(),
},
];
let result_multi = check_provider_satisfies_constraints(
&provider_pkg_multi,
"test-cap",
&constraints_multi,
PackageFormat::Rpm,
);
assert!(result_multi.unwrap(), "Package providing test-cap=3.0.0 should satisfy requirement >=2.5.0,<4.0.0");
let provider_pkg_eq = Package {
pkgname: "test-pkg-eq".to_string(),
version: "5.0.0".to_string(),
arch: "x86_64".to_string(),
pkgkey: "test-pkg-eq__5.0.0__x86_64".to_string(),
provides: vec!["test-cap = 3.0.0".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(provider_pkg_eq.clone()), PackageFormat::Rpm);
let constraints_eq = vec![
VersionConstraint {
operator: Operator::VersionEqual,
operand: "3.0.0".to_string(),
},
];
let result_eq = check_provider_satisfies_constraints(
&provider_pkg_eq,
"test-cap",
&constraints_eq,
PackageFormat::Rpm,
);
assert!(result_eq.unwrap(), "Package providing test-cap=3.0.0 should satisfy requirement =3.0.0 (using provided version, not package version)");
let mesa_provider = Package {
pkgname: "mesa-dri-drivers".to_string(),
version: "25.1.9-1.fc42".to_string(),
arch: "x86_64".to_string(),
pkgkey: "mesa-dri-drivers__25.1.9-1.fc42__x86_64".to_string(),
provides: vec!["mesa-libglapi = 25.1.9-1.fc42".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(mesa_provider.clone()), PackageFormat::Rpm);
let mesa_constraints = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "25.0.0~rc2-1".to_string(),
},
];
let mesa_result = check_provider_satisfies_constraints(
&mesa_provider,
"mesa-libglapi",
&mesa_constraints,
PackageFormat::Rpm,
);
assert!(mesa_result.unwrap(), "mesa-dri-drivers providing mesa-libglapi=25.1.9-1.fc42 should satisfy requirement >=25.0.0~rc2-1");
}
#[test]
fn test_check_provider_satisfies_constraints_rpm_composer_fallback() {
PACKAGE_CACHE.clear();
let php_geshi = Package {
pkgname: "php-geshi".to_string(),
version: "1.0.9.1-18.20230219git7884d22.fc42".to_string(),
arch: "noarch".to_string(),
pkgkey: "php-geshi__1.0.9.1-18.20230219git7884d22.fc42__noarch".to_string(),
provides: vec!["php-composer(geshi/geshi) = 1.0.9.1".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(php_geshi.clone()), PackageFormat::Rpm);
let constraints = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "1.0.9.1-5".to_string(),
},
];
let result = check_provider_satisfies_constraints(
&php_geshi,
"php-composer(geshi/geshi)",
&constraints,
PackageFormat::Rpm,
);
assert!(result.unwrap(), "php-geshi providing php-composer(geshi/geshi)=1.0.9.1 should satisfy requirement >=1.0.9.1-5 via fallback to package version 1.0.9.1-18.20230219git7884d22.fc42");
let constraints_no_release = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "1.0.9.1".to_string(),
},
];
let result_no_release = check_provider_satisfies_constraints(
&php_geshi,
"php-composer(geshi/geshi)",
&constraints_no_release,
PackageFormat::Rpm,
);
assert!(result_no_release.unwrap(), "php-geshi providing php-composer(geshi/geshi)=1.0.9.1 should satisfy requirement >=1.0.9.1 (no release needed)");
let constraints_too_high = vec![
VersionConstraint {
operator: Operator::VersionGreaterThanEqual,
operand: "1.0.9.1-100".to_string(),
},
];
let result_too_high = check_provider_satisfies_constraints(
&php_geshi,
"php-composer(geshi/geshi)",
&constraints_too_high,
PackageFormat::Rpm,
);
assert!(!result_too_high.unwrap(), "php-geshi version 1.0.9.1-18.20230219git7884d22.fc42 should NOT satisfy requirement >=1.0.9.1-100");
let php_geshi_with_release = Package {
pkgname: "php-geshi-release".to_string(),
version: "1.0.9.1-18.20230219git7884d22.fc42".to_string(),
arch: "noarch".to_string(),
pkgkey: "php-geshi-release__1.0.9.1-18.20230219git7884d22.fc42__noarch".to_string(),
provides: vec!["php-composer(geshi/geshi) = 1.0.9.1-18.20230219git7884d22.fc42".to_string()],
..Default::default()
};
add_package_to_cache(Arc::new(php_geshi_with_release.clone()), PackageFormat::Rpm);
let result_with_release = check_provider_satisfies_constraints(
&php_geshi_with_release,
"php-composer(geshi/geshi)",
&constraints,
PackageFormat::Rpm,
);
assert!(result_with_release.unwrap(), "php-geshi-release providing php-composer(geshi/geshi)=1.0.9.1-18.20230219git7884d22.fc42 should satisfy requirement >=1.0.9.1-5 (no fallback needed)");
}
}