use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
#[cfg(feature = "python")]
pub mod python;
mod sortkey;
pub use sortkey::*;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Nevra<'a> {
name: Cow<'a, str>,
evr: Evr<'a>,
arch: Cow<'a, str>,
}
impl<'a> Nevra<'a> {
pub fn new<T: Into<Cow<'a, str>>>(
name: T,
epoch: T,
version: T,
release: T,
arch: T,
) -> Nevra<'a> {
Self {
name: name.into(),
evr: Evr::new(epoch, version, release),
arch: arch.into(),
}
}
pub fn parse(nevra: &'a str) -> Self {
let (n, e, v, r, a) = Nevra::parse_values(nevra);
Self::new(n, e, v, r, a)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn evr(&'a self) -> &'a Evr<'a> {
&self.evr
}
pub fn epoch(&self) -> &str {
&self.evr.epoch
}
pub fn version(&self) -> &str {
&self.evr.version
}
pub fn release(&self) -> &str {
&self.evr.release
}
pub fn arch(&self) -> &str {
&self.arch
}
pub fn values(&self) -> (&str, &str, &str, &str, &str) {
(
&self.name,
&self.evr.epoch,
&self.evr.version,
&self.evr.release,
&self.arch,
)
}
pub fn parse_values(nevra: &'a str) -> (&'a str, &'a str, &'a str, &'a str, &'a str) {
let (nevr, arch) = nevra.rsplit_once('.').unwrap_or((nevra, ""));
let (nev, release) = nevr.rsplit_once('-').unwrap_or((nevr, ""));
let (name, version_epoch) = nev.rsplit_once('-').unwrap_or((nev, ""));
let (epoch, version) = match version_epoch.split_once(':') {
Some((e, v)) => (e, v),
None => ("", version_epoch),
};
(name, epoch, version, release, arch)
}
pub fn nevra(&self) -> String {
format!("{}-{}.{}", self.name, self.evr.evr(), self.arch)
}
pub fn nevra_short(&self) -> String {
self.to_string()
}
pub fn nvra(&self) -> String {
format!(
"{}-{}-{}.{}",
self.name, self.evr.version, self.evr.release, self.arch
)
}
}
impl Hash for Nevra<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.evr.hash(state);
self.arch.hash(state);
}
}
impl fmt::Display for Nevra<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}.{}", self.name, self.evr, self.arch)
}
}
impl PartialOrd for Nevra<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Nevra<'_> {
fn cmp(&self, other: &Self) -> Ordering {
let name_cmp = compare_version_string(&self.name, &other.name);
if name_cmp != Ordering::Equal {
return name_cmp;
}
let evr_cmp = self.evr.cmp(&other.evr);
if evr_cmp != Ordering::Equal {
return evr_cmp;
}
compare_version_string(&self.arch, &other.arch)
}
}
#[derive(Clone, Debug, Default, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Evr<'a> {
epoch: Cow<'a, str>,
version: Cow<'a, str>,
release: Cow<'a, str>,
}
impl<'a> Evr<'a> {
pub fn new<T: Into<Cow<'a, str>>>(epoch: T, version: T, release: T) -> Evr<'a> {
Evr {
epoch: epoch.into(),
version: version.into(),
release: release.into(),
}
}
pub fn parse(evr: &'a str) -> Self {
Evr::parse_values(evr).into()
}
pub fn epoch(&self) -> &str {
&self.epoch
}
pub fn version(&self) -> &str {
&self.version
}
pub fn release(&self) -> &str {
&self.release
}
pub fn set_epoch(&mut self, epoch: impl Into<Cow<'a, str>>) {
self.epoch = epoch.into();
}
pub fn set_version(&mut self, version: impl Into<Cow<'a, str>>) {
self.version = version.into();
}
pub fn set_release(&mut self, release: impl Into<Cow<'a, str>>) {
self.release = release.into();
}
pub fn evr(&self) -> String {
let epoch = if self.epoch.is_empty() {
"0"
} else {
self.epoch.as_ref()
};
format!("{}:{}-{}", epoch, self.version(), self.release())
}
pub fn evr_short(&self) -> String {
self.to_string()
}
pub fn values(&self) -> (&str, &str, &str) {
(self.epoch(), self.version(), self.release())
}
pub fn parse_values(evr: &'a str) -> (&'a str, &'a str, &'a str) {
let (epoch, vr) = evr.split_once(':').unwrap_or(("", evr));
let (version, release) = vr.split_once('-').unwrap_or((vr, ""));
(epoch, version, release)
}
pub fn sortkey(&self) -> EvrSortKey {
EvrSortKey::from_values(&self.epoch, &self.version, &self.release)
}
}
impl<'a> From<(&'a str, &'a str, &'a str)> for Evr<'a> {
fn from(val: (&'a str, &'a str, &'a str)) -> Self {
Evr::new(val.0, val.1, val.2)
}
}
impl PartialEq for Evr<'_> {
#[allow(clippy::comparison_to_empty)]
fn eq(&self, other: &Self) -> bool {
((self.epoch == other.epoch)
|| (self.epoch == "" && other.epoch == "0")
|| (self.epoch == "0" && other.epoch == ""))
&& self.version == other.version
&& self.release == other.release
}
}
impl Hash for Evr<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
let epoch = if self.epoch.is_empty() {
"0"
} else {
&self.epoch
};
epoch.hash(state);
self.version.hash(state);
self.release.hash(state);
}
}
impl fmt::Display for Evr<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.epoch.is_empty() {
write!(f, "{}:", self.epoch)?;
}
write!(f, "{}-{}", self.version, self.release)
}
}
impl PartialOrd for Evr<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Evr<'_> {
fn cmp(&self, other: &Self) -> Ordering {
let epoch_1 = if self.epoch.is_empty() {
"0"
} else {
&self.epoch
};
let epoch_2 = if other.epoch.is_empty() {
"0"
} else {
&other.epoch
};
let epoch_cmp = compare_version_string(epoch_1, epoch_2);
if epoch_cmp != Ordering::Equal {
return epoch_cmp;
}
let version_cmp = compare_version_string(&self.version, &other.version);
if version_cmp != Ordering::Equal {
return version_cmp;
}
compare_version_string(&self.release, &other.release)
}
}
fn compare_version_string(version1: &str, version2: &str) -> Ordering {
if version1 == version2 {
return Ordering::Equal;
}
let mut version1_part = version1;
let mut version2_part = version2;
let not_alphanumeric_tilde_or_caret =
|c: char| !c.is_ascii_alphanumeric() && c != '~' && c != '^';
loop {
version1_part = version1_part.trim_start_matches(not_alphanumeric_tilde_or_caret);
version2_part = version2_part.trim_start_matches(not_alphanumeric_tilde_or_caret);
match (
version1_part.strip_prefix('~'),
version2_part.strip_prefix('~'),
) {
(Some(_), None) => return Ordering::Less,
(None, Some(_)) => return Ordering::Greater,
(Some(a), Some(b)) => {
version1_part = a;
version2_part = b;
continue;
}
_ => (),
}
match (
version1_part.strip_prefix('^'),
version2_part.strip_prefix('^'),
) {
(Some(_), None) => match version2_part.is_empty() {
true => return Ordering::Greater,
false => return Ordering::Less,
},
(None, Some(_)) => match version1_part.is_empty() {
true => return Ordering::Less,
false => return Ordering::Greater,
},
(Some(a), Some(b)) => {
version1_part = a;
version2_part = b;
continue;
}
_ => (),
}
if version1_part.is_empty() || version2_part.is_empty() {
break;
}
fn matching_contiguous<F>(string: &str, pat: F) -> Option<(&str, &str)>
where
F: Fn(char) -> bool,
{
let end = string.find(|c| !pat(c)).unwrap_or(string.len());
if end == 0 {
None
} else {
Some(string.split_at(end))
}
}
if version1_part.starts_with(|c: char| c.is_ascii_digit()) {
match (
matching_contiguous(version1_part, |c| c.is_ascii_digit()),
matching_contiguous(version2_part, |c| c.is_ascii_digit()),
) {
(Some((prefix1, rest1)), Some((prefix2, rest2))) => {
version1_part = rest1;
version2_part = rest2;
let prefix1 = prefix1.trim_start_matches('0');
let prefix2 = prefix2.trim_start_matches('0');
let ordering = prefix1.len().cmp(&prefix2.len());
if ordering != Ordering::Equal {
return ordering;
}
let ordering = prefix1.cmp(prefix2);
if ordering != Ordering::Equal {
return ordering;
}
}
(Some(_), None) => return Ordering::Greater,
_ => unreachable!(),
}
} else {
match (
matching_contiguous(version1_part, |c| c.is_ascii_alphabetic()),
matching_contiguous(version2_part, |c| c.is_ascii_alphabetic()),
) {
(Some((prefix1, rest1)), Some((prefix2, rest2))) => {
version1_part = rest1;
version2_part = rest2;
let ordering = prefix1.cmp(prefix2);
if ordering != Ordering::Equal {
return ordering;
}
}
(Some(_), None) => return Ordering::Less,
_ => unreachable!(),
}
}
}
version1_part.len().cmp(&version2_part.len())
}
pub fn rpm_evr_compare(evr1: &str, evr2: &str) -> Ordering {
let evr1 = Evr::parse(evr1);
let evr2 = Evr::parse(evr2);
evr1.cmp(&evr2)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ReqOperator {
LT,
LE,
EQ,
GE,
GT,
}
impl fmt::Display for ReqOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
ReqOperator::LT => "<",
ReqOperator::LE => "<=",
ReqOperator::EQ => "=",
ReqOperator::GE => ">=",
ReqOperator::GT => ">",
})
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Requirement<'a> {
name: Cow<'a, str>,
constraint: Option<(ReqOperator, Evr<'a>)>,
}
impl<'a> Requirement<'a> {
pub fn new<T: Into<Cow<'a, str>>>(name: T) -> Self {
Self {
name: name.into(),
constraint: None,
}
}
pub fn with_constraint<T: Into<Cow<'a, str>>>(name: T, op: ReqOperator, evr: Evr<'a>) -> Self {
Self {
name: name.into(),
constraint: Some((op, evr)),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn constraint(&self) -> Option<(ReqOperator, &Evr<'a>)> {
self.constraint.as_ref().map(|(op, evr)| (*op, evr))
}
pub fn satisfies(&self, name: &str, evr: &Evr) -> bool {
if self.name != name {
return false;
}
match &self.constraint {
None => true,
Some((op, req_evr)) => {
let ord = evr.cmp(req_evr);
match op {
ReqOperator::LT => ord == Ordering::Less,
ReqOperator::LE => ord != Ordering::Greater,
ReqOperator::EQ => ord == Ordering::Equal,
ReqOperator::GE => ord != Ordering::Less,
ReqOperator::GT => ord == Ordering::Greater,
}
}
}
}
}
impl fmt::Display for Requirement<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.constraint {
None => write!(f, "{}", self.name),
Some((op, evr)) => write!(f, "{} {} {}", self.name, op, evr),
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn assert_ver_order(a: &str, b: &str, expected: Ordering) {
assert_eq!(
compare_version_string(a, b),
expected,
"compare_version_string({a:?}, {b:?})"
);
assert_eq!(
version_sortkey(a).cmp(&version_sortkey(b)),
expected,
"version_sortkey({a:?}) vs version_sortkey({b:?})"
);
}
fn assert_evr_order(a: Evr, b: Evr, expected: Ordering) {
assert_eq!(a.cmp(&b), expected, "Evr::cmp({a:?}, {b:?})");
assert_eq!(
a.sortkey().cmp(&b.sortkey()),
expected,
"Evr::sortkey({a:?}) vs Evr::sortkey({b:?})"
);
}
#[test]
fn test_nevra_tostr() {
let nevra = Nevra::new("foo", "", "1.2.3", "45", "x86_64");
assert_eq!("foo-1.2.3-45.x86_64", nevra.to_string());
assert_eq!("foo-1.2.3-45.x86_64", nevra.nevra_short());
assert_eq!("foo-0:1.2.3-45.x86_64", nevra.nevra());
let nevra = Nevra::new("foo", "0", "1.2.3", "45", "x86_64");
assert_eq!("foo-0:1.2.3-45.x86_64", nevra.to_string());
assert_eq!("foo-0:1.2.3-45.x86_64", nevra.nevra());
let nevra = Nevra::new("foo", "1", "2.3.4", "5", "x86_64");
assert_eq!("foo-1:2.3.4-5.x86_64", nevra.to_string());
assert_eq!("foo-1:2.3.4-5.x86_64", nevra.nevra());
let nevra = Nevra::new("python3.9", "0", "3.9.11", "2.fc38", "x86_64");
assert_eq!("python3.9-0:3.9.11-2.fc38.x86_64", nevra.to_string());
assert_eq!("python3.9-0:3.9.11-2.fc38.x86_64", nevra.nevra());
}
#[test]
fn test_nevra_parse() {
let nevra = Nevra::new("foo", "", "1.2.3", "45", "x86_64");
assert_eq!(Nevra::parse("foo-1.2.3-45.x86_64"), nevra);
let nevra = Nevra::new("foo", "0", "1.2.3", "45", "x86_64");
assert_eq!(Nevra::parse("foo-0:1.2.3-45.x86_64"), nevra);
let nevra = Nevra::new("foo", "1", "2.3.4", "5", "x86_64");
assert_eq!(Nevra::parse("foo-1:2.3.4-5.x86_64"), nevra);
let nevra = Nevra::new("python3.9", "0", "3.9.11", "2", "x86_64");
assert_eq!(Nevra::parse("python3.9-3.9.11-2.x86_64"), nevra);
let nevra = Nevra::new("python3.9", "0", "3.9.11", "2.fc38", "x86_64");
assert_eq!(Nevra::parse("python3.9-3.9.11-2.fc38.x86_64"), nevra);
}
#[test]
fn test_nevra_parse_edge_cases() {
assert_eq!(Nevra::parse_values("foo"), ("foo", "", "", "", ""));
assert_eq!(
Nevra::parse_values("foo-1.2-3.bar"),
("foo", "", "1.2", "3", "bar")
);
assert_eq!(
Nevra::parse_values("foo-1.2-3.bar.x86_64"),
("foo", "", "1.2", "3.bar", "x86_64")
);
assert_eq!(
Nevra::parse_values("python3.9-3.9.11-2.fc38.x86_64"),
("python3.9", "", "3.9.11", "2.fc38", "x86_64")
);
let nevra = Nevra::new("python3.9-devel", "0", "3.9.11", "2.fc38", "x86_64");
assert_eq!(Nevra::parse("python3.9-devel-3.9.11-2.fc38.x86_64"), nevra);
let nevra = Nevra::new("foo-bar", "", "1.2.3", "45", "x86_64");
assert_eq!(Nevra::parse("foo-bar-1.2.3-45.x86_64"), nevra);
let nevra = Nevra::new("foo-bar", "0", "1.2.3", "45", "x86_64");
assert_eq!(Nevra::parse("foo-bar-0:1.2.3-45.x86_64"), nevra);
let nevra = Nevra::new("foo-bar-0", "", "1.2.3", "45.el10", "x86_64");
assert_eq!(Nevra::parse("foo-bar-0-1.2.3-45.el10.x86_64"), nevra);
let nevra = Nevra::new("foo-bar-0", "0", "1.2.3", "45.el10", "x86_64");
assert_eq!(Nevra::parse("foo-bar-0-0:1.2.3-45.el10.x86_64"), nevra);
let nevra = Nevra::new("grub2-efi-x64", "1", "2.12", "28.fc42", "x86_64");
assert_eq!(Nevra::parse("grub2-efi-x64-1:2.12-28.fc42.x86_64"), nevra);
}
#[test]
fn test_nevra_ord() {
let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
let nevra2 = Nevra::parse("foo-1.2.3-45.noarch");
assert!(nevra1 == nevra2);
let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
let nevra2 = Nevra::parse("foo-0:1.2.3-45.noarch");
assert!(nevra1 == nevra2);
let nevra1 = Nevra::parse("bar-1.2.3-45.noarch");
let nevra2 = Nevra::parse("foo-9:1.2.3-45.noarch");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
let nevra2 = Nevra::parse("foobar-1.2.3-45.noarch");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("foo-2.3.4-5.noarch");
let nevra2 = Nevra::parse("foobar-1.2.3-45.noarch");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("bar-1.2.3-45.noarch");
let nevra2 = Nevra::parse("foo-1.2.3-45.noarch");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("foo-1.2.3-45.fc38.noarch");
let nevra2 = Nevra::parse("foo-1.2.3-45.fc39.noarch");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("foo-1.2.3-45.fc39.i386");
let nevra2 = Nevra::parse("foo-1.2.3-45.fc39.x86_64");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("python3.9-3.9.12-2.fc39.i386");
let nevra2 = Nevra::parse("python3.11-3.11.7-2.fc39.x86_64");
assert!(nevra1 < nevra2);
let nevra1 = Nevra::parse("python3.11-3.11.7-2.fc39.x86_64");
let nevra2 = Nevra::parse("python3.9-3.9.12-2.fc39.x86_64");
assert!(nevra1 > nevra2);
}
#[test]
fn test_evr_tostr() {
let evr = Evr::new("", "1.2.3", "45");
assert_eq!("1.2.3-45", evr.to_string());
assert_eq!("1.2.3-45", evr.evr_short());
assert_eq!("0:1.2.3-45", evr.evr());
let evr = Evr::new("0", "1.2.3", "45");
assert_eq!("0:1.2.3-45", evr.to_string());
assert_eq!("0:1.2.3-45", evr.evr());
}
#[test]
fn test_evr_parse() {
let evr = Evr::new("", "1.2.3", "45");
assert_eq!(Evr::parse("1.2.3-45"), evr);
let evr = Evr::new("0", "1.2.3", "45");
assert_eq!(Evr::parse("0:1.2.3-45"), evr);
let evr = Evr::new("1", "2.3.4", "5");
assert_eq!(Evr::parse("1:2.3.4-5"), evr);
}
#[test]
fn test_evr_parse_edge_cases() {
assert_eq!(Evr::parse_values("-"), ("", "", ""));
assert_eq!(Evr::parse_values("."), ("", ".", ""));
assert_eq!(Evr::parse_values(":"), ("", "", ""));
assert_eq!(Evr::parse_values(":-"), ("", "", ""));
assert_eq!(Evr::parse_values(".-"), ("", ".", ""));
assert_eq!(Evr::parse_values("0"), ("", "0", ""));
assert_eq!(Evr::parse_values("0-"), ("", "0", ""));
assert_eq!(Evr::parse_values(":0"), ("", "0", ""));
assert_eq!(Evr::parse_values(":0-"), ("", "0", ""));
assert_eq!(Evr::parse_values("0:"), ("0", "", ""));
assert_eq!(Evr::parse_values("asdf:"), ("asdf", "", ""));
assert_eq!(Evr::parse_values("~:"), ("~", "", ""));
}
#[test]
fn test_rpm_evr_compare() {
assert_eq!(Ordering::Equal, rpm_evr_compare("0:1.2.3-45", "1.2.3-45"));
assert_eq!(Ordering::Less, rpm_evr_compare("1.2.3-45", "1:1.2.3-45"));
assert_eq!(Ordering::Greater, rpm_evr_compare("1.2.3-46", "1.2.3-45"));
}
#[test]
fn test_evr_ord() {
assert_evr_order(
Evr::parse("1.2.3-45"),
Evr::parse("1.2.3-45"),
Ordering::Equal,
);
assert_evr_order(
Evr::parse("2:1.2.3-45"),
Evr::parse("2:1.2.3-45"),
Ordering::Equal,
);
assert_evr_order(
Evr::parse("1.2.3-45"),
Evr::parse("0:1.2.3-45"),
Ordering::Equal,
);
assert_evr_order(
Evr::parse("1.2.3-45"),
Evr::parse("1:1.2.3-45"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("4.2.3-45"),
Evr::parse("1:1.2.3-45"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("1.2.3-45"),
Evr::parse("1.2.4-45"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("1.23.3-45"),
Evr::parse("1.2.3-45"),
Ordering::Greater,
);
assert_evr_order(
Evr::parse("12.2.3-45"),
Evr::parse("1.2.3-45"),
Ordering::Greater,
);
assert_evr_order(
Evr::parse("1.2.3-45"),
Evr::parse("1.12.3-45"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("~1.2.3-45"),
Evr::parse("1.2.3-45"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("~12.2.3-45"),
Evr::parse("1.2.3-45"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("~12.2.3-45"),
Evr::parse("~1.2.3-45"),
Ordering::Greater,
);
assert_evr_order(
Evr::parse("3:~1.2.3-45"),
Evr::parse("0:1.2.3-45"),
Ordering::Greater,
);
assert_evr_order(
Evr::parse("1.2.3-45"),
Evr::parse("1.2.3-46"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("1.2.3-45.fc39"),
Evr::parse("1.2.3-46.fc38"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("1.2.3-3"),
Evr::parse("1.2.3-10"),
Ordering::Less,
);
assert_evr_order(
Evr::parse("1.2.3-3.fc40"),
Evr::parse("1.2.3-10.fc39"),
Ordering::Less,
);
}
#[test]
fn test_compare_version_string() {
assert_ver_order("1.0", "1.0", Ordering::Equal);
assert_ver_order("1.0", "2.0", Ordering::Less);
assert_ver_order("2.0", "1.0", Ordering::Greater);
assert_ver_order("2.0.1", "2.0.1", Ordering::Equal);
assert_ver_order("2.0", "2.0.1", Ordering::Less);
assert_ver_order("2.0.1", "2.0", Ordering::Greater);
assert_ver_order("5.0.1", "5.0.1a", Ordering::Less);
assert_ver_order("5.0.1a", "5.0.1", Ordering::Greater);
assert_ver_order("5.0.a1", "5.0.a1", Ordering::Equal);
assert_ver_order("5.0.1a", "5.0.1a", Ordering::Equal);
assert_ver_order("5.0.a1", "5.0.a2", Ordering::Less);
assert_ver_order("5.0.a2", "5.0.a1", Ordering::Greater);
assert_ver_order("10abc", "10.1abc", Ordering::Less);
assert_ver_order("10.1abc", "10abc", Ordering::Greater);
assert_ver_order("8.0", "8.0.rc1", Ordering::Less);
assert_ver_order("8.0.rc1", "8.0", Ordering::Greater);
assert_ver_order("10b2", "10a1", Ordering::Greater);
assert_ver_order("10a2", "10b2", Ordering::Less);
assert_ver_order("6.6p1", "7.5p1", Ordering::Less);
assert_ver_order("7.5p1", "6.6p1", Ordering::Greater);
assert_ver_order("6.5p1", "6.5p1", Ordering::Equal);
assert_ver_order("6.5p1", "6.5p2", Ordering::Less);
assert_ver_order("6.5p2", "6.5p1", Ordering::Greater);
assert_ver_order("6.5p2", "6.6p1", Ordering::Less);
assert_ver_order("6.6p1", "6.5p2", Ordering::Greater);
assert_ver_order("6.5p10", "6.5p10", Ordering::Equal);
assert_ver_order("6.5p1", "6.5p10", Ordering::Less);
assert_ver_order("6.5p10", "6.5p1", Ordering::Greater);
assert_ver_order("abc10", "abc10", Ordering::Equal);
assert_ver_order("abc10", "abc10.1", Ordering::Less);
assert_ver_order("abc10.1", "abc10", Ordering::Greater);
assert_ver_order("abc.4", "abc.4", Ordering::Equal);
assert_ver_order("abc.4", "8", Ordering::Less);
assert_ver_order("8", "abc.4", Ordering::Greater);
assert_ver_order("abc.4", "2", Ordering::Less);
assert_ver_order("2", "abc.4", Ordering::Greater);
assert_ver_order("1.0aa", "1.0aa", Ordering::Equal);
assert_ver_order("1.0a", "1.0aa", Ordering::Less);
assert_ver_order("1.0aa", "1.0a", Ordering::Greater);
}
#[test]
fn test_version_comparison_numeric_handling() {
assert_ver_order("10.0001", "10.0001", Ordering::Equal);
assert_ver_order("10.0001", "10.1", Ordering::Equal);
assert_ver_order("10.1", "10.0001", Ordering::Equal);
assert_ver_order("10.0001", "10.0039", Ordering::Less);
assert_ver_order("10.0039", "10.0001", Ordering::Greater);
assert_ver_order("10.1", "10.10001", Ordering::Less);
assert_ver_order("10.1111", "10.10001", Ordering::Less);
assert_ver_order("10.11111", "10.10001", Ordering::Greater);
assert_ver_order("20240521", "20240521", Ordering::Equal);
assert_ver_order("20240521", "20240522", Ordering::Less);
assert_ver_order("20240522", "20240521", Ordering::Greater);
assert_ver_order("20240521", "202405210", Ordering::Less);
}
#[test]
fn test_version_comparison_tilde_and_caret() {
assert_ver_order("1.0~rc1", "1.0~rc1", Ordering::Equal);
assert_ver_order("1.0~rc1", "1.0", Ordering::Less);
assert_ver_order("1.0", "1.0~rc1", Ordering::Greater);
assert_ver_order("1.0~rc1", "1.0~rc2", Ordering::Less);
assert_ver_order("1.0~rc2", "1.0~rc1", Ordering::Greater);
assert_ver_order("1.0~rc1~git123", "1.0~rc1~git123", Ordering::Equal);
assert_ver_order("1.0~rc1~git123", "1.0~rc1", Ordering::Less);
assert_ver_order("1.0~rc1", "1.0~rc1~git123", Ordering::Greater);
assert_ver_order("1.0^", "1.0^", Ordering::Equal);
assert_ver_order("1.0", "1.0^", Ordering::Less);
assert_ver_order("1.0^", "1.0", Ordering::Greater);
assert_ver_order("1.0", "1.0git1^", Ordering::Less);
assert_ver_order("1.0^git1", "1.0^git2", Ordering::Less);
assert_ver_order("1.01", "1.0^git1", Ordering::Greater);
assert_ver_order("1.0^20240501", "1.0^20240501", Ordering::Equal);
assert_ver_order("1.0^20240501", "1.0.1", Ordering::Less);
assert_ver_order("1.0^20240501^git1", "1.0^20240501^git1", Ordering::Equal);
assert_ver_order("1.0^20240502", "1.0^20240501^git1", Ordering::Greater);
assert_ver_order("1.0~rc1^git1", "1.0~rc1^git1", Ordering::Equal);
assert_ver_order("1.0~rc1", "1.0~rc1^git1", Ordering::Less);
assert_ver_order("1.0~rc1^git1", "1.0~rc1", Ordering::Greater);
assert_ver_order("1.0^git1~pre", "1.0^git1~pre", Ordering::Equal);
assert_ver_order("1.0^git1~pre", "1.0^git1", Ordering::Less);
assert_ver_order("1.0^git1", "1.0^git1~pre", Ordering::Greater);
}
#[test]
fn test_non_intuitive_comparison_behavior() {
assert_ver_order("1e.fc33", "1.fc33", Ordering::Less);
assert_ver_order("1g.fc33", "1.fc33", Ordering::Greater);
}
#[test]
fn test_non_alphanumeric_equivalence() {
assert_ver_order("b", "b", Ordering::Equal);
assert_ver_order("b+", "b+", Ordering::Equal);
assert_ver_order("b+", "b_", Ordering::Equal);
assert_ver_order("b_", "b+", Ordering::Equal);
assert_ver_order("+b", "+b", Ordering::Equal);
assert_ver_order("+b", "_b", Ordering::Equal);
assert_ver_order("_b", "+b", Ordering::Equal);
assert_ver_order("+b", "++b", Ordering::Equal);
assert_ver_order("+b", "+b+", Ordering::Equal);
assert_ver_order("+.", "+_", Ordering::Equal);
assert_ver_order("_+", "+.", Ordering::Equal);
assert_ver_order("+", ".", Ordering::Equal);
assert_ver_order(",", "+", Ordering::Equal);
assert_ver_order("++", "_", Ordering::Equal);
assert_ver_order("+", "..", Ordering::Equal);
assert_ver_order("4_0", "4_0", Ordering::Equal);
assert_ver_order("4_0", "4.0", Ordering::Equal);
assert_ver_order("4.0", "4_0", Ordering::Equal);
assert_ver_order("4.999", "5.0", Ordering::Less);
assert_ver_order("4.999.9", "5.0", Ordering::Less);
assert_ver_order("5.0", "4.999_9", Ordering::Greater);
assert_ver_order("4.999", "4.999.9", Ordering::Less);
assert_ver_order("4.999", "4.99.9", Ordering::Greater);
}
#[test]
fn test_non_ascii_character_equivalence() {
assert_ver_order("1.1.Á.1", "1.1.1", Ordering::Equal);
assert_ver_order("1.1.Á", "1.1.Á", Ordering::Equal);
assert_ver_order("1.1.Á", "1.1.Ê", Ordering::Equal);
assert_ver_order("1.1.ÁÁ", "1.1.Á", Ordering::Equal);
assert_ver_order("1.1.Á", "1.1.ÊÊ", Ordering::Equal);
assert_ver_order("1.1Á1", "1.11", Ordering::Less);
}
#[test]
fn test_evr_hash_consistency() {
use std::hash::{DefaultHasher, Hash, Hasher};
fn hash_of<T: Hash>(val: &T) -> u64 {
let mut h = DefaultHasher::new();
val.hash(&mut h);
h.finish()
}
let evr1 = Evr::parse("1.2.3-45");
let evr2 = Evr::parse("0:1.2.3-45");
assert_eq!(evr1, evr2);
assert_eq!(hash_of(&evr1), hash_of(&evr2));
let evr3 = Evr::parse("2:1.2.3-45");
let evr4 = Evr::parse("2:1.2.3-45");
assert_eq!(hash_of(&evr3), hash_of(&evr4));
assert_ne!(hash_of(&evr1), hash_of(&evr3));
}
#[test]
fn test_nevra_hash_consistency() {
use std::hash::{DefaultHasher, Hash, Hasher};
fn hash_of<T: Hash>(val: &T) -> u64 {
let mut h = DefaultHasher::new();
val.hash(&mut h);
h.finish()
}
let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
let nevra2 = Nevra::parse("foo-0:1.2.3-45.noarch");
assert_eq!(nevra1, nevra2);
assert_eq!(hash_of(&nevra1), hash_of(&nevra2));
let nevra3 = Nevra::parse("foo-1:1.2.3-45.noarch");
assert_ne!(hash_of(&nevra1), hash_of(&nevra3));
}
#[cfg(feature = "serde")]
#[test]
fn test_evr_serde_roundtrip() {
let evr = Evr::parse("1:2.3.4-5");
let json = serde_json::to_string(&evr).unwrap();
let evr2: Evr = serde_json::from_str(&json).unwrap();
assert_eq!(evr, evr2);
}
#[cfg(feature = "serde")]
#[test]
fn test_nevra_serde_roundtrip() {
let nevra = Nevra::parse("foo-1:2.3.4-5.x86_64");
let json = serde_json::to_string(&nevra).unwrap();
let nevra2: Nevra = serde_json::from_str(&json).unwrap();
assert_eq!(nevra, nevra2);
}
#[test]
fn test_requirement_no_constraint() {
let req = Requirement::new("foo");
assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
assert!(req.satisfies("foo", &Evr::parse("999.0-1")));
assert!(!req.satisfies("bar", &Evr::parse("1.0-1")));
}
#[test]
fn test_requirement_eq() {
let req = Requirement::with_constraint("foo", ReqOperator::EQ, Evr::parse("1.0-1"));
assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
assert!(req.satisfies("foo", &Evr::parse("0:1.0-1")));
assert!(!req.satisfies("foo", &Evr::parse("1.0-2")));
assert!(!req.satisfies("foo", &Evr::parse("2.0-1")));
}
#[test]
fn test_requirement_ge() {
let req = Requirement::with_constraint("foo", ReqOperator::GE, Evr::parse("1.0-1"));
assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
assert!(req.satisfies("foo", &Evr::parse("2.0-1")));
assert!(!req.satisfies("foo", &Evr::parse("0.9-1")));
}
#[test]
fn test_requirement_lt() {
let req = Requirement::with_constraint("foo", ReqOperator::LT, Evr::parse("2.0-1"));
assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
assert!(!req.satisfies("foo", &Evr::parse("2.0-1")));
assert!(!req.satisfies("foo", &Evr::parse("3.0-1")));
}
#[test]
fn test_requirement_display() {
let req = Requirement::new("foo");
assert_eq!(req.to_string(), "foo");
let req = Requirement::with_constraint("foo", ReqOperator::GE, Evr::parse("1:2.0-1"));
assert_eq!(req.to_string(), "foo >= 1:2.0-1");
}
}