Skip to main content

rpm_version/
lib.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt;
4use std::hash::{Hash, Hasher};
5
6#[cfg(feature = "python")]
7pub mod python;
8
9mod sortkey;
10pub use sortkey::*;
11
12/// A full RPM "NEVRA" consists of 5 different components - Name, Epoch, Version, Release, and Architecture.
13///
14/// Name is the name of the package.
15///
16/// Epoch overrides all other fields and is generally only used as a last resort - in cases where
17/// a change to the versioning scheme or packaging error creates a situation where newer packages
18/// might otherwise sort as being older.
19///
20/// Version is the normal version string used by the upstream project. This shouldn't be tweaked
21/// by the packager.
22///
23/// Release indicates firstly the number of times this package has been released - for instance,
24/// with custom patches and backports not present in the upstream, but may also indicate other
25/// details such as the OS it was built for (fc38, el9) or portions of a git commit hash.
26///
27/// Architecture indicates the CPU architecture that this package is intended to support.
28///
29/// In many contexts (on a system, in a repository), package NEVRAs are meant to be unique. You can have
30/// different packages with the same NEVRA - but you can't install both, or put them both in a repo.
31#[derive(Clone, Debug, Default, Eq, PartialEq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Nevra<'a> {
34    name: Cow<'a, str>,
35    evr: Evr<'a>,
36    arch: Cow<'a, str>,
37}
38
39impl<'a> Nevra<'a> {
40    /// Create a new NEVRA
41    pub fn new<T: Into<Cow<'a, str>>>(
42        name: T,
43        epoch: T,
44        version: T,
45        release: T,
46        arch: T,
47    ) -> Nevra<'a> {
48        Self {
49            name: name.into(),
50            evr: Evr::new(epoch, version, release),
51            arch: arch.into(),
52        }
53    }
54
55    /// Create a NEVRA parsed from a string
56    pub fn parse(nevra: &'a str) -> Self {
57        let (n, e, v, r, a) = Nevra::parse_values(nevra);
58        Self::new(n, e, v, r, a)
59    }
60
61    /// The name value
62    pub fn name(&self) -> &str {
63        &self.name
64    }
65
66    /// The EVR
67    pub fn evr(&'a self) -> &'a Evr<'a> {
68        &self.evr
69    }
70
71    /// The epoch value
72    pub fn epoch(&self) -> &str {
73        &self.evr.epoch
74    }
75
76    /// The version value
77    pub fn version(&self) -> &str {
78        &self.evr.version
79    }
80
81    /// The release value
82    pub fn release(&self) -> &str {
83        &self.evr.release
84    }
85
86    /// The arch value
87    pub fn arch(&self) -> &str {
88        &self.arch
89    }
90
91    /// Return the epoch, version and release values as a 5-element tuple
92    pub fn values(&self) -> (&str, &str, &str, &str, &str) {
93        (
94            &self.name,
95            &self.evr.epoch,
96            &self.evr.version,
97            &self.evr.release,
98            &self.arch,
99        )
100    }
101
102    /// Parse the name, epoch, version, release and arch values and return them as a 5-element tuple
103    pub fn parse_values(nevra: &'a str) -> (&'a str, &'a str, &'a str, &'a str, &'a str) {
104        // 1. Split Architecture from the right.
105        // Example: "foo-1:2.3-4.x86_64" -> ("foo-1:2.3-4", "x86_64")
106        let (nevr, arch) = nevra.rsplit_once('.').unwrap_or((nevra, ""));
107
108        // 2. Split Release from the right of the remainder.
109        // Example: "foo-1:2.3-4" -> ("foo-1:2.3", "4")
110        let (nev, release) = nevr.rsplit_once('-').unwrap_or((nevr, ""));
111
112        // 3. Split Version (with potential Epoch) from the right of the remainder.
113        // Example: "foo-1:2.3" -> ("foo", "1:2.3")
114        let (name, version_epoch) = nev.rsplit_once('-').unwrap_or((nev, ""));
115
116        // 4. Check the version part for an Epoch.
117        // The epoch is separated by a colon. If no colon exists, the epoch is empty.
118        let (epoch, version) = match version_epoch.split_once(':') {
119            // Example: "1:2.3" -> ("1", "2.3")
120            Some((e, v)) => (e, v),
121            // Example: "2.3" -> ("", "2.3")
122            None => ("", version_epoch),
123        };
124
125        (name, epoch, version, release, arch)
126    }
127
128    /// Returns the name-epoch-version-release.arch string (NEVRA), e.g. `"foo-1:2.0-3.x86_64"`.
129    ///
130    /// A package having no epoch value is equivalent to having an epoch of zero, hence,
131    /// when the epoch is not present it prints an epoch of 0 - e.g. `"0:1.2.3-4"`
132    ///
133    /// This is a normalized form, if you want the more display-friendly form, use [`nevra_short()`]
134    pub fn nevra(&self) -> String {
135        format!("{}-{}.{}", self.name, self.evr.evr(), self.arch)
136    }
137
138    /// Returns the name-epoch-version-release.arch string (NEVRA), e.g. `"foo-1:2.0-3.x86_64"`.
139    ///
140    /// Unlike [`nevra()`], this doesn't print the epoch if it is 0 or non-existing, e.g.
141    /// `"foo-2.0-3.x86_64"`, but does print it otherwise.
142    ///
143    /// Same as [`to_string()`]
144    pub fn nevra_short(&self) -> String {
145        self.to_string()
146    }
147
148    /// Returns the name-version-release.arch string (NVRA)
149    ///
150    /// This is the form typically used for RPM filenames. It is similar to NEVRA,
151    /// but does not include epoch (even when it is present)
152    /// e.g. `"foo-2.0-3.x86_64"`.
153    pub fn nvra(&self) -> String {
154        format!(
155            "{}-{}-{}.{}",
156            self.name, self.evr.version, self.evr.release, self.arch
157        )
158    }
159}
160
161impl Hash for Nevra<'_> {
162    fn hash<H: Hasher>(&self, state: &mut H) {
163        self.name.hash(state);
164        self.evr.hash(state);
165        self.arch.hash(state);
166    }
167}
168
169impl fmt::Display for Nevra<'_> {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        write!(f, "{}-{}.{}", self.name, self.evr, self.arch)
172    }
173}
174
175impl PartialOrd for Nevra<'_> {
176    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
177        Some(self.cmp(other))
178    }
179}
180
181impl Ord for Nevra<'_> {
182    fn cmp(&self, other: &Self) -> Ordering {
183        let name_cmp = compare_version_string(&self.name, &other.name);
184        if name_cmp != Ordering::Equal {
185            return name_cmp;
186        }
187
188        let evr_cmp = self.evr.cmp(&other.evr);
189        if evr_cmp != Ordering::Equal {
190            return evr_cmp;
191        }
192
193        compare_version_string(&self.arch, &other.arch)
194    }
195}
196
197/// A full RPM "version" specifier has 3 different components - Epoch, Version, and Release.
198///
199/// You are not expected to create these manually, but rather from existing RPMs.
200///
201/// Epoch overrides all other fields and is generally only used as a last resort - in cases where
202/// a change to the versioning scheme or packaging error creates a situation where newer packages
203/// might otherwise sort as being older.
204///
205/// Version is the normal version string used by the upstream project. This shouldn't be tweaked
206/// by the packager.
207///
208/// Release indicates firstly the number of times this package has been released - for instance,
209/// with custom patches and backports not present in the upstream, but may also indicate other
210/// details such as the OS it was built for (fc38, el9) or portions of a git commit hash.
211///
212/// Tilde (~) and caret (^) are special values used in particular situations. Including ~ in
213/// a version is used for denoting pre-releases and will force it to sort as less than a version
214/// without a caret, e.g. 0.5.0 vs 0.5.0~rc1. Including ^ in a version is used for denoting snapshots
215/// not directly associated with an upstream release and will force it to sort higher, e.g.
216/// 0.5.0 vs 0.5.0^deadbeef
217#[derive(Clone, Debug, Default, Eq)]
218#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219pub struct Evr<'a> {
220    epoch: Cow<'a, str>,
221    version: Cow<'a, str>,
222    release: Cow<'a, str>,
223}
224
225impl<'a> Evr<'a> {
226    /// Create a new EVR
227    pub fn new<T: Into<Cow<'a, str>>>(epoch: T, version: T, release: T) -> Evr<'a> {
228        Evr {
229            epoch: epoch.into(),
230            version: version.into(),
231            release: release.into(),
232        }
233    }
234
235    /// Create an EVR parsed from a string
236    pub fn parse(evr: &'a str) -> Self {
237        Evr::parse_values(evr).into()
238    }
239
240    /// The epoch value
241    pub fn epoch(&self) -> &str {
242        &self.epoch
243    }
244
245    /// The version value
246    pub fn version(&self) -> &str {
247        &self.version
248    }
249
250    /// The release value
251    pub fn release(&self) -> &str {
252        &self.release
253    }
254
255    /// Set the epoch value
256    pub fn set_epoch(&mut self, epoch: impl Into<Cow<'a, str>>) {
257        self.epoch = epoch.into();
258    }
259
260    /// Set the version value
261    pub fn set_version(&mut self, version: impl Into<Cow<'a, str>>) {
262        self.version = version.into();
263    }
264
265    /// Set the release value
266    pub fn set_release(&mut self, release: impl Into<Cow<'a, str>>) {
267        self.release = release.into();
268    }
269
270    /// Return an epoch:version-release (EVR) string in a normalized form which always
271    /// includes an epoch.
272    ///
273    /// A null epoch is equivalent to 0, hence, this uses an epoch of 0
274    /// when the epoch is not present. e.g. `"0:1.2.3-4"`
275    pub fn evr(&self) -> String {
276        let epoch = if self.epoch.is_empty() {
277            "0"
278        } else {
279            self.epoch.as_ref()
280        };
281
282        format!("{}:{}-{}", epoch, self.version(), self.release())
283    }
284
285    /// Return an epoch:version-release (EVR) string in short form.
286    ///
287    /// Does does not print the epoch when it is not present, e.g. `"1.2.3-4"`.
288    ///
289    /// Same as [`to_string()`]
290    pub fn evr_short(&self) -> String {
291        self.to_string()
292    }
293
294    /// Return the epoch, version and release values as a 3-element tuple
295    pub fn values(&self) -> (&str, &str, &str) {
296        (self.epoch(), self.version(), self.release())
297    }
298
299    /// Parse the epoch, version and release values and return them as a 3-element tuple
300    pub fn parse_values(evr: &'a str) -> (&'a str, &'a str, &'a str) {
301        let (epoch, vr) = evr.split_once(':').unwrap_or(("", evr));
302        let (version, release) = vr.split_once('-').unwrap_or((vr, ""));
303
304        (epoch, version, release)
305    }
306
307    /// Encode this EVR as a memcmp-sortable binary key.
308    pub fn sortkey(&self) -> EvrSortKey {
309        EvrSortKey::from_values(&self.epoch, &self.version, &self.release)
310    }
311}
312
313impl<'a> From<(&'a str, &'a str, &'a str)> for Evr<'a> {
314    fn from(val: (&'a str, &'a str, &'a str)) -> Self {
315        Evr::new(val.0, val.1, val.2)
316    }
317}
318
319impl PartialEq for Evr<'_> {
320    #[allow(clippy::comparison_to_empty)]
321    fn eq(&self, other: &Self) -> bool {
322        ((self.epoch == other.epoch)
323            || (self.epoch == "" && other.epoch == "0")
324            || (self.epoch == "0" && other.epoch == ""))
325            && self.version == other.version
326            && self.release == other.release
327    }
328}
329
330impl Hash for Evr<'_> {
331    fn hash<H: Hasher>(&self, state: &mut H) {
332        let epoch = if self.epoch.is_empty() {
333            "0"
334        } else {
335            &self.epoch
336        };
337        epoch.hash(state);
338        self.version.hash(state);
339        self.release.hash(state);
340    }
341}
342
343impl fmt::Display for Evr<'_> {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        if !self.epoch.is_empty() {
346            write!(f, "{}:", self.epoch)?;
347        }
348
349        write!(f, "{}-{}", self.version, self.release)
350    }
351}
352
353impl PartialOrd for Evr<'_> {
354    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
355        Some(self.cmp(other))
356    }
357}
358
359impl Ord for Evr<'_> {
360    fn cmp(&self, other: &Self) -> Ordering {
361        let epoch_1 = if self.epoch.is_empty() {
362            "0"
363        } else {
364            &self.epoch
365        };
366        let epoch_2 = if other.epoch.is_empty() {
367            "0"
368        } else {
369            &other.epoch
370        };
371
372        let epoch_cmp = compare_version_string(epoch_1, epoch_2);
373        if epoch_cmp != Ordering::Equal {
374            return epoch_cmp;
375        }
376
377        let version_cmp = compare_version_string(&self.version, &other.version);
378        if version_cmp != Ordering::Equal {
379            return version_cmp;
380        }
381
382        compare_version_string(&self.release, &other.release)
383    }
384}
385
386/// internal use: each individual component of the EVR is compared using this function
387fn compare_version_string(version1: &str, version2: &str) -> Ordering {
388    if version1 == version2 {
389        return Ordering::Equal;
390    }
391
392    let mut version1_part = version1;
393    let mut version2_part = version2;
394
395    let not_alphanumeric_tilde_or_caret =
396        |c: char| !c.is_ascii_alphanumeric() && c != '~' && c != '^';
397
398    loop {
399        // Strip any leading non-alphanumeric, non-tilde, non-caret characters
400        version1_part = version1_part.trim_start_matches(not_alphanumeric_tilde_or_caret);
401        version2_part = version2_part.trim_start_matches(not_alphanumeric_tilde_or_caret);
402
403        // Tilde separator parses as "older" or lesser version
404        match (
405            version1_part.strip_prefix('~'),
406            version2_part.strip_prefix('~'),
407        ) {
408            (Some(_), None) => return Ordering::Less,
409            (None, Some(_)) => return Ordering::Greater,
410            (Some(a), Some(b)) => {
411                version1_part = a;
412                version2_part = b;
413                continue;
414            }
415            _ => (),
416        }
417
418        // if two strings are equal but one is longer, the longer one is considered greater
419        // ...unless it ends on a caret, which parses as a lesser version (tilde doesn't have this caveat)
420        match (
421            version1_part.strip_prefix('^'),
422            version2_part.strip_prefix('^'),
423        ) {
424            (Some(_), None) => match version2_part.is_empty() {
425                true => return Ordering::Greater,
426                false => return Ordering::Less,
427            },
428            (None, Some(_)) => match version1_part.is_empty() {
429                true => return Ordering::Less,
430                false => return Ordering::Greater,
431            },
432            (Some(a), Some(b)) => {
433                version1_part = a;
434                version2_part = b;
435                continue;
436            }
437            _ => (),
438        }
439
440        if version1_part.is_empty() || version2_part.is_empty() {
441            break;
442        }
443
444        /// Match a contiguous string of characters matching the provided pattern
445        /// and return it, along with the rest of the string, if one was found.
446        fn matching_contiguous<F>(string: &str, pat: F) -> Option<(&str, &str)>
447        where
448            F: Fn(char) -> bool,
449        {
450            let end = string.find(|c| !pat(c)).unwrap_or(string.len());
451            if end == 0 {
452                None
453            } else {
454                Some(string.split_at(end))
455            }
456        }
457
458        if version1_part.starts_with(|c: char| c.is_ascii_digit()) {
459            match (
460                matching_contiguous(version1_part, |c| c.is_ascii_digit()),
461                matching_contiguous(version2_part, |c| c.is_ascii_digit()),
462            ) {
463                (Some((prefix1, rest1)), Some((prefix2, rest2))) => {
464                    version1_part = rest1;
465                    version2_part = rest2;
466
467                    let prefix1 = prefix1.trim_start_matches('0');
468                    let prefix2 = prefix2.trim_start_matches('0');
469
470                    let ordering = prefix1.len().cmp(&prefix2.len());
471                    if ordering != Ordering::Equal {
472                        return ordering;
473                    }
474                    let ordering = prefix1.cmp(prefix2);
475                    if ordering != Ordering::Equal {
476                        return ordering;
477                    }
478                }
479                (Some(_), None) => return Ordering::Greater,
480                _ => unreachable!(),
481            }
482        } else {
483            match (
484                matching_contiguous(version1_part, |c| c.is_ascii_alphabetic()),
485                matching_contiguous(version2_part, |c| c.is_ascii_alphabetic()),
486            ) {
487                (Some((prefix1, rest1)), Some((prefix2, rest2))) => {
488                    version1_part = rest1;
489                    version2_part = rest2;
490
491                    let ordering = prefix1.cmp(prefix2);
492                    if ordering != Ordering::Equal {
493                        return ordering;
494                    }
495                }
496                (Some(_), None) => return Ordering::Less,
497                _ => unreachable!(),
498            }
499        }
500    }
501
502    version1_part.len().cmp(&version2_part.len())
503}
504
505/// Compare two strings as RPM EVR values
506pub fn rpm_evr_compare(evr1: &str, evr2: &str) -> Ordering {
507    let evr1 = Evr::parse(evr1);
508    let evr2 = Evr::parse(evr2);
509    evr1.cmp(&evr2)
510}
511
512/// The comparison operator in an RPM dependency requirement.
513#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
514#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
515pub enum ReqOperator {
516    LT,
517    LE,
518    EQ,
519    GE,
520    GT,
521}
522
523impl fmt::Display for ReqOperator {
524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
525        f.write_str(match self {
526            ReqOperator::LT => "<",
527            ReqOperator::LE => "<=",
528            ReqOperator::EQ => "=",
529            ReqOperator::GE => ">=",
530            ReqOperator::GT => ">",
531        })
532    }
533}
534
535/// An RPM dependency requirement: a package name with an optional version constraint.
536///
537/// A requirement like `foo >= 1:2.0-1` means "package foo with EVR at least 1:2.0-1".
538/// A requirement with no operator/EVR (just a name) is satisfied by any version.
539#[derive(Clone, Debug, PartialEq, Eq, Hash)]
540#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
541pub struct Requirement<'a> {
542    name: Cow<'a, str>,
543    constraint: Option<(ReqOperator, Evr<'a>)>,
544}
545
546impl<'a> Requirement<'a> {
547    /// Create a requirement with no version constraint (any version satisfies).
548    pub fn new<T: Into<Cow<'a, str>>>(name: T) -> Self {
549        Self {
550            name: name.into(),
551            constraint: None,
552        }
553    }
554
555    /// Create a requirement with a version constraint.
556    pub fn with_constraint<T: Into<Cow<'a, str>>>(name: T, op: ReqOperator, evr: Evr<'a>) -> Self {
557        Self {
558            name: name.into(),
559            constraint: Some((op, evr)),
560        }
561    }
562
563    /// The required package name.
564    pub fn name(&self) -> &str {
565        &self.name
566    }
567
568    /// The version constraint, if any.
569    pub fn constraint(&self) -> Option<(ReqOperator, &Evr<'a>)> {
570        self.constraint.as_ref().map(|(op, evr)| (*op, evr))
571    }
572
573    /// Check whether a given package name and EVR satisfy this requirement.
574    pub fn satisfies(&self, name: &str, evr: &Evr) -> bool {
575        if self.name != name {
576            return false;
577        }
578        match &self.constraint {
579            None => true,
580            Some((op, req_evr)) => {
581                let ord = evr.cmp(req_evr);
582                match op {
583                    ReqOperator::LT => ord == Ordering::Less,
584                    ReqOperator::LE => ord != Ordering::Greater,
585                    ReqOperator::EQ => ord == Ordering::Equal,
586                    ReqOperator::GE => ord != Ordering::Less,
587                    ReqOperator::GT => ord == Ordering::Greater,
588                }
589            }
590        }
591    }
592}
593
594impl fmt::Display for Requirement<'_> {
595    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596        match &self.constraint {
597            None => write!(f, "{}", self.name),
598            Some((op, evr)) => write!(f, "{} {} {}", self.name, op, evr),
599        }
600    }
601}
602
603#[cfg(test)]
604mod test {
605    use super::*;
606
607    /// Helper: assert that both compare_version_string and version_sortkey
608    /// produce the expected ordering (cross-validates both implementations)
609    fn assert_ver_order(a: &str, b: &str, expected: Ordering) {
610        assert_eq!(
611            compare_version_string(a, b),
612            expected,
613            "compare_version_string({a:?}, {b:?})"
614        );
615        assert_eq!(
616            version_sortkey(a).cmp(&version_sortkey(b)),
617            expected,
618            "version_sortkey({a:?}) vs version_sortkey({b:?})"
619        );
620    }
621
622    /// Helper: assert that both Evr::cmp and Evr::sortkey produce the expected
623    /// ordering (cross-validates both implementations)
624    fn assert_evr_order(a: Evr, b: Evr, expected: Ordering) {
625        assert_eq!(a.cmp(&b), expected, "Evr::cmp({a:?}, {b:?})");
626        assert_eq!(
627            a.sortkey().cmp(&b.sortkey()),
628            expected,
629            "Evr::sortkey({a:?}) vs Evr::sortkey({b:?})"
630        );
631    }
632
633    /// Test that NEVRAs are printed as expected
634    #[test]
635    fn test_nevra_tostr() {
636        let nevra = Nevra::new("foo", "", "1.2.3", "45", "x86_64");
637        assert_eq!("foo-1.2.3-45.x86_64", nevra.to_string());
638        assert_eq!("foo-1.2.3-45.x86_64", nevra.nevra_short());
639        assert_eq!("foo-0:1.2.3-45.x86_64", nevra.nevra());
640
641        let nevra = Nevra::new("foo", "0", "1.2.3", "45", "x86_64");
642        assert_eq!("foo-0:1.2.3-45.x86_64", nevra.to_string());
643        assert_eq!("foo-0:1.2.3-45.x86_64", nevra.nevra());
644
645        let nevra = Nevra::new("foo", "1", "2.3.4", "5", "x86_64");
646        assert_eq!("foo-1:2.3.4-5.x86_64", nevra.to_string());
647        assert_eq!("foo-1:2.3.4-5.x86_64", nevra.nevra());
648
649        let nevra = Nevra::new("python3.9", "0", "3.9.11", "2.fc38", "x86_64");
650        assert_eq!("python3.9-0:3.9.11-2.fc38.x86_64", nevra.to_string());
651        assert_eq!("python3.9-0:3.9.11-2.fc38.x86_64", nevra.nevra());
652    }
653
654    /// Test that a correctly formed EVR string is parsed correctly
655    #[test]
656    fn test_nevra_parse() {
657        let nevra = Nevra::new("foo", "", "1.2.3", "45", "x86_64");
658        assert_eq!(Nevra::parse("foo-1.2.3-45.x86_64"), nevra);
659
660        let nevra = Nevra::new("foo", "0", "1.2.3", "45", "x86_64");
661        assert_eq!(Nevra::parse("foo-0:1.2.3-45.x86_64"), nevra);
662
663        let nevra = Nevra::new("foo", "1", "2.3.4", "5", "x86_64");
664        assert_eq!(Nevra::parse("foo-1:2.3.4-5.x86_64"), nevra);
665
666        let nevra = Nevra::new("python3.9", "0", "3.9.11", "2", "x86_64");
667        assert_eq!(Nevra::parse("python3.9-3.9.11-2.x86_64"), nevra);
668
669        let nevra = Nevra::new("python3.9", "0", "3.9.11", "2.fc38", "x86_64");
670        assert_eq!(Nevra::parse("python3.9-3.9.11-2.fc38.x86_64"), nevra);
671    }
672
673    /// Test that various not-well-formed NEVRA strings still get parsed in a sensible way
674    #[test]
675    fn test_nevra_parse_edge_cases() {
676        assert_eq!(Nevra::parse_values("foo"), ("foo", "", "", "", ""));
677        assert_eq!(
678            Nevra::parse_values("foo-1.2-3.bar"),
679            ("foo", "", "1.2", "3", "bar")
680        );
681        assert_eq!(
682            Nevra::parse_values("foo-1.2-3.bar.x86_64"),
683            ("foo", "", "1.2", "3.bar", "x86_64")
684        );
685        assert_eq!(
686            Nevra::parse_values("python3.9-3.9.11-2.fc38.x86_64"),
687            ("python3.9", "", "3.9.11", "2.fc38", "x86_64")
688        );
689
690        let nevra = Nevra::new("python3.9-devel", "0", "3.9.11", "2.fc38", "x86_64");
691        assert_eq!(Nevra::parse("python3.9-devel-3.9.11-2.fc38.x86_64"), nevra);
692
693        let nevra = Nevra::new("foo-bar", "", "1.2.3", "45", "x86_64");
694        assert_eq!(Nevra::parse("foo-bar-1.2.3-45.x86_64"), nevra);
695
696        let nevra = Nevra::new("foo-bar", "0", "1.2.3", "45", "x86_64");
697        assert_eq!(Nevra::parse("foo-bar-0:1.2.3-45.x86_64"), nevra);
698
699        let nevra = Nevra::new("foo-bar-0", "", "1.2.3", "45.el10", "x86_64");
700        assert_eq!(Nevra::parse("foo-bar-0-1.2.3-45.el10.x86_64"), nevra);
701
702        let nevra = Nevra::new("foo-bar-0", "0", "1.2.3", "45.el10", "x86_64");
703        assert_eq!(Nevra::parse("foo-bar-0-0:1.2.3-45.el10.x86_64"), nevra);
704
705        let nevra = Nevra::new("grub2-efi-x64", "1", "2.12", "28.fc42", "x86_64");
706        assert_eq!(Nevra::parse("grub2-efi-x64-1:2.12-28.fc42.x86_64"), nevra);
707    }
708
709    /// Test comparing NEVRAs using comparison operators
710    #[test]
711    fn test_nevra_ord() {
712        let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
713        let nevra2 = Nevra::parse("foo-1.2.3-45.noarch");
714        assert!(nevra1 == nevra2);
715
716        let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
717        let nevra2 = Nevra::parse("foo-0:1.2.3-45.noarch");
718        assert!(nevra1 == nevra2);
719
720        let nevra1 = Nevra::parse("bar-1.2.3-45.noarch");
721        let nevra2 = Nevra::parse("foo-9:1.2.3-45.noarch");
722        assert!(nevra1 < nevra2);
723
724        let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
725        let nevra2 = Nevra::parse("foobar-1.2.3-45.noarch");
726        assert!(nevra1 < nevra2);
727
728        let nevra1 = Nevra::parse("foo-2.3.4-5.noarch");
729        let nevra2 = Nevra::parse("foobar-1.2.3-45.noarch");
730        assert!(nevra1 < nevra2);
731
732        let nevra1 = Nevra::parse("bar-1.2.3-45.noarch");
733        let nevra2 = Nevra::parse("foo-1.2.3-45.noarch");
734        assert!(nevra1 < nevra2);
735
736        let nevra1 = Nevra::parse("foo-1.2.3-45.fc38.noarch");
737        let nevra2 = Nevra::parse("foo-1.2.3-45.fc39.noarch");
738        assert!(nevra1 < nevra2);
739
740        let nevra1 = Nevra::parse("foo-1.2.3-45.fc39.i386");
741        let nevra2 = Nevra::parse("foo-1.2.3-45.fc39.x86_64");
742        assert!(nevra1 < nevra2);
743
744        let nevra1 = Nevra::parse("python3.9-3.9.12-2.fc39.i386");
745        let nevra2 = Nevra::parse("python3.11-3.11.7-2.fc39.x86_64");
746        assert!(nevra1 < nevra2);
747
748        let nevra1 = Nevra::parse("python3.11-3.11.7-2.fc39.x86_64");
749        let nevra2 = Nevra::parse("python3.9-3.9.12-2.fc39.x86_64");
750        assert!(nevra1 > nevra2);
751    }
752
753    /// Test that EVRs are printed as expected
754    #[test]
755    fn test_evr_tostr() {
756        let evr = Evr::new("", "1.2.3", "45");
757        assert_eq!("1.2.3-45", evr.to_string());
758        assert_eq!("1.2.3-45", evr.evr_short());
759        assert_eq!("0:1.2.3-45", evr.evr());
760
761        let evr = Evr::new("0", "1.2.3", "45");
762        assert_eq!("0:1.2.3-45", evr.to_string());
763        assert_eq!("0:1.2.3-45", evr.evr());
764    }
765
766    /// Test that a correctly formed EVR string is parsed correctly
767    #[test]
768    fn test_evr_parse() {
769        let evr = Evr::new("", "1.2.3", "45");
770        assert_eq!(Evr::parse("1.2.3-45"), evr);
771
772        let evr = Evr::new("0", "1.2.3", "45");
773        assert_eq!(Evr::parse("0:1.2.3-45"), evr);
774
775        let evr = Evr::new("1", "2.3.4", "5");
776        assert_eq!(Evr::parse("1:2.3.4-5"), evr);
777    }
778
779    /// Test that various not-well-formed EVR strings still get parsed in a sensible way
780    #[test]
781    fn test_evr_parse_edge_cases() {
782        assert_eq!(Evr::parse_values("-"), ("", "", ""));
783        assert_eq!(Evr::parse_values("."), ("", ".", ""));
784        assert_eq!(Evr::parse_values(":"), ("", "", ""));
785        assert_eq!(Evr::parse_values(":-"), ("", "", ""));
786        assert_eq!(Evr::parse_values(".-"), ("", ".", ""));
787        assert_eq!(Evr::parse_values("0"), ("", "0", ""));
788        assert_eq!(Evr::parse_values("0-"), ("", "0", ""));
789        assert_eq!(Evr::parse_values(":0"), ("", "0", ""));
790        assert_eq!(Evr::parse_values(":0-"), ("", "0", ""));
791        assert_eq!(Evr::parse_values("0:"), ("0", "", ""));
792        assert_eq!(Evr::parse_values("asdf:"), ("asdf", "", ""));
793        assert_eq!(Evr::parse_values("~:"), ("~", "", ""));
794    }
795
796    /// Test direct comparison of rpm EVR strings using rpm_evr_compare
797    #[test]
798    fn test_rpm_evr_compare() {
799        assert_eq!(Ordering::Equal, rpm_evr_compare("0:1.2.3-45", "1.2.3-45"));
800        assert_eq!(Ordering::Less, rpm_evr_compare("1.2.3-45", "1:1.2.3-45"));
801        assert_eq!(Ordering::Greater, rpm_evr_compare("1.2.3-46", "1.2.3-45"));
802    }
803
804    /// Test comparing EVRs using comparison operators
805    #[test]
806    fn test_evr_ord() {
807        // same EVR
808        assert_evr_order(
809            Evr::parse("1.2.3-45"),
810            Evr::parse("1.2.3-45"),
811            Ordering::Equal,
812        );
813        assert_evr_order(
814            Evr::parse("2:1.2.3-45"),
815            Evr::parse("2:1.2.3-45"),
816            Ordering::Equal,
817        );
818        // zero-epoch == default-epoch
819        assert_evr_order(
820            Evr::parse("1.2.3-45"),
821            Evr::parse("0:1.2.3-45"),
822            Ordering::Equal,
823        );
824        // higher epoch wins
825        assert_evr_order(
826            Evr::parse("1.2.3-45"),
827            Evr::parse("1:1.2.3-45"),
828            Ordering::Less,
829        );
830        // epoch dominates version
831        assert_evr_order(
832            Evr::parse("4.2.3-45"),
833            Evr::parse("1:1.2.3-45"),
834            Ordering::Less,
835        );
836
837        // version ordering
838        assert_evr_order(
839            Evr::parse("1.2.3-45"),
840            Evr::parse("1.2.4-45"),
841            Ordering::Less,
842        );
843        assert_evr_order(
844            Evr::parse("1.23.3-45"),
845            Evr::parse("1.2.3-45"),
846            Ordering::Greater,
847        );
848        assert_evr_order(
849            Evr::parse("12.2.3-45"),
850            Evr::parse("1.2.3-45"),
851            Ordering::Greater,
852        );
853        assert_evr_order(
854            Evr::parse("1.2.3-45"),
855            Evr::parse("1.12.3-45"),
856            Ordering::Less,
857        );
858
859        // tilde sorts older
860        assert_evr_order(
861            Evr::parse("~1.2.3-45"),
862            Evr::parse("1.2.3-45"),
863            Ordering::Less,
864        );
865        assert_evr_order(
866            Evr::parse("~12.2.3-45"),
867            Evr::parse("1.2.3-45"),
868            Ordering::Less,
869        );
870        assert_evr_order(
871            Evr::parse("~12.2.3-45"),
872            Evr::parse("~1.2.3-45"),
873            Ordering::Greater,
874        );
875        // higher epoch dominates even with tilde in version
876        assert_evr_order(
877            Evr::parse("3:~1.2.3-45"),
878            Evr::parse("0:1.2.3-45"),
879            Ordering::Greater,
880        );
881
882        // release ordering
883        assert_evr_order(
884            Evr::parse("1.2.3-45"),
885            Evr::parse("1.2.3-46"),
886            Ordering::Less,
887        );
888        assert_evr_order(
889            Evr::parse("1.2.3-45.fc39"),
890            Evr::parse("1.2.3-46.fc38"),
891            Ordering::Less,
892        );
893        assert_evr_order(
894            Evr::parse("1.2.3-3"),
895            Evr::parse("1.2.3-10"),
896            Ordering::Less,
897        );
898        assert_evr_order(
899            Evr::parse("1.2.3-3.fc40"),
900            Evr::parse("1.2.3-10.fc39"),
901            Ordering::Less,
902        );
903    }
904
905    /// Test many different combinations of version string comparison behavior
906    #[test]
907    fn test_compare_version_string() {
908        assert_ver_order("1.0", "1.0", Ordering::Equal);
909        assert_ver_order("1.0", "2.0", Ordering::Less);
910        assert_ver_order("2.0", "1.0", Ordering::Greater);
911
912        assert_ver_order("2.0.1", "2.0.1", Ordering::Equal);
913        assert_ver_order("2.0", "2.0.1", Ordering::Less);
914        assert_ver_order("2.0.1", "2.0", Ordering::Greater);
915
916        assert_ver_order("5.0.1", "5.0.1a", Ordering::Less);
917        assert_ver_order("5.0.1a", "5.0.1", Ordering::Greater);
918
919        assert_ver_order("5.0.a1", "5.0.a1", Ordering::Equal);
920        assert_ver_order("5.0.1a", "5.0.1a", Ordering::Equal);
921        assert_ver_order("5.0.a1", "5.0.a2", Ordering::Less);
922        assert_ver_order("5.0.a2", "5.0.a1", Ordering::Greater);
923
924        assert_ver_order("10abc", "10.1abc", Ordering::Less);
925        assert_ver_order("10.1abc", "10abc", Ordering::Greater);
926
927        assert_ver_order("8.0", "8.0.rc1", Ordering::Less);
928        assert_ver_order("8.0.rc1", "8.0", Ordering::Greater);
929
930        assert_ver_order("10b2", "10a1", Ordering::Greater);
931        assert_ver_order("10a2", "10b2", Ordering::Less);
932
933        assert_ver_order("6.6p1", "7.5p1", Ordering::Less);
934        assert_ver_order("7.5p1", "6.6p1", Ordering::Greater);
935
936        assert_ver_order("6.5p1", "6.5p1", Ordering::Equal);
937        assert_ver_order("6.5p1", "6.5p2", Ordering::Less);
938        assert_ver_order("6.5p2", "6.5p1", Ordering::Greater);
939        assert_ver_order("6.5p2", "6.6p1", Ordering::Less);
940        assert_ver_order("6.6p1", "6.5p2", Ordering::Greater);
941
942        assert_ver_order("6.5p10", "6.5p10", Ordering::Equal);
943        assert_ver_order("6.5p1", "6.5p10", Ordering::Less);
944        assert_ver_order("6.5p10", "6.5p1", Ordering::Greater);
945
946        assert_ver_order("abc10", "abc10", Ordering::Equal);
947        assert_ver_order("abc10", "abc10.1", Ordering::Less);
948        assert_ver_order("abc10.1", "abc10", Ordering::Greater);
949
950        assert_ver_order("abc.4", "abc.4", Ordering::Equal);
951        assert_ver_order("abc.4", "8", Ordering::Less);
952        assert_ver_order("8", "abc.4", Ordering::Greater);
953        assert_ver_order("abc.4", "2", Ordering::Less);
954        assert_ver_order("2", "abc.4", Ordering::Greater);
955
956        assert_ver_order("1.0aa", "1.0aa", Ordering::Equal);
957        assert_ver_order("1.0a", "1.0aa", Ordering::Less);
958        assert_ver_order("1.0aa", "1.0a", Ordering::Greater);
959    }
960
961    /// test handling of numeric-like values in version strings
962    #[test]
963    fn test_version_comparison_numeric_handling() {
964        assert_ver_order("10.0001", "10.0001", Ordering::Equal);
965        // sequences of leading zeroes are meant to be ignored - it's not *actually* treated like a numeric value
966        assert_ver_order("10.0001", "10.1", Ordering::Equal);
967        assert_ver_order("10.1", "10.0001", Ordering::Equal);
968        assert_ver_order("10.0001", "10.0039", Ordering::Less);
969        assert_ver_order("10.0039", "10.0001", Ordering::Greater);
970        // but sequences of zeroes within a numeric segment are not ignored
971        assert_ver_order("10.1", "10.10001", Ordering::Less);
972        assert_ver_order("10.1111", "10.10001", Ordering::Less);
973        assert_ver_order("10.11111", "10.10001", Ordering::Greater);
974
975        assert_ver_order("20240521", "20240521", Ordering::Equal);
976        assert_ver_order("20240521", "20240522", Ordering::Less);
977        assert_ver_order("20240522", "20240521", Ordering::Greater);
978        assert_ver_order("20240521", "202405210", Ordering::Less);
979    }
980
981    /// Test behavior of tilde and caret operators
982    #[test]
983    fn test_version_comparison_tilde_and_caret() {
984        assert_ver_order("1.0~rc1", "1.0~rc1", Ordering::Equal);
985        assert_ver_order("1.0~rc1", "1.0", Ordering::Less);
986        assert_ver_order("1.0", "1.0~rc1", Ordering::Greater);
987        assert_ver_order("1.0~rc1", "1.0~rc2", Ordering::Less);
988        assert_ver_order("1.0~rc2", "1.0~rc1", Ordering::Greater);
989        assert_ver_order("1.0~rc1~git123", "1.0~rc1~git123", Ordering::Equal);
990        assert_ver_order("1.0~rc1~git123", "1.0~rc1", Ordering::Less);
991        assert_ver_order("1.0~rc1", "1.0~rc1~git123", Ordering::Greater);
992
993        assert_ver_order("1.0^", "1.0^", Ordering::Equal);
994        assert_ver_order("1.0", "1.0^", Ordering::Less);
995        assert_ver_order("1.0^", "1.0", Ordering::Greater);
996
997        assert_ver_order("1.0", "1.0git1^", Ordering::Less);
998        assert_ver_order("1.0^git1", "1.0^git2", Ordering::Less);
999        assert_ver_order("1.01", "1.0^git1", Ordering::Greater);
1000        assert_ver_order("1.0^20240501", "1.0^20240501", Ordering::Equal);
1001        assert_ver_order("1.0^20240501", "1.0.1", Ordering::Less);
1002        assert_ver_order("1.0^20240501^git1", "1.0^20240501^git1", Ordering::Equal);
1003        assert_ver_order("1.0^20240502", "1.0^20240501^git1", Ordering::Greater);
1004        assert_ver_order("1.0~rc1^git1", "1.0~rc1^git1", Ordering::Equal);
1005        assert_ver_order("1.0~rc1", "1.0~rc1^git1", Ordering::Less);
1006        assert_ver_order("1.0~rc1^git1", "1.0~rc1", Ordering::Greater);
1007        assert_ver_order("1.0^git1~pre", "1.0^git1~pre", Ordering::Equal);
1008        assert_ver_order("1.0^git1~pre", "1.0^git1", Ordering::Less);
1009        assert_ver_order("1.0^git1", "1.0^git1~pre", Ordering::Greater);
1010    }
1011
1012    /// Test some version comparison behavior that is a bit non-intuitive
1013    /// (but needs to be maintained for compatibility)
1014    #[test]
1015    fn test_non_intuitive_comparison_behavior() {
1016        assert_ver_order("1e.fc33", "1.fc33", Ordering::Less);
1017        assert_ver_order("1g.fc33", "1.fc33", Ordering::Greater);
1018    }
1019
1020    /// Test handling of non-alphanumeric ascii characters (excluding separators)
1021    #[test]
1022    fn test_non_alphanumeric_equivalence() {
1023        // the existence of sequences of non-alphanumeric characters should not impact the version comparison at all
1024        assert_ver_order("b", "b", Ordering::Equal);
1025        assert_ver_order("b+", "b+", Ordering::Equal);
1026        assert_ver_order("b+", "b_", Ordering::Equal);
1027        assert_ver_order("b_", "b+", Ordering::Equal);
1028        assert_ver_order("+b", "+b", Ordering::Equal);
1029        assert_ver_order("+b", "_b", Ordering::Equal);
1030        assert_ver_order("_b", "+b", Ordering::Equal);
1031
1032        assert_ver_order("+b", "++b", Ordering::Equal);
1033        assert_ver_order("+b", "+b+", Ordering::Equal);
1034
1035        assert_ver_order("+.", "+_", Ordering::Equal);
1036        assert_ver_order("_+", "+.", Ordering::Equal);
1037        assert_ver_order("+", ".", Ordering::Equal);
1038        assert_ver_order(",", "+", Ordering::Equal);
1039
1040        assert_ver_order("++", "_", Ordering::Equal);
1041        assert_ver_order("+", "..", Ordering::Equal);
1042
1043        assert_ver_order("4_0", "4_0", Ordering::Equal);
1044        assert_ver_order("4_0", "4.0", Ordering::Equal);
1045        assert_ver_order("4.0", "4_0", Ordering::Equal);
1046
1047        assert_ver_order("4.999", "5.0", Ordering::Less);
1048        assert_ver_order("4.999.9", "5.0", Ordering::Less);
1049        assert_ver_order("5.0", "4.999_9", Ordering::Greater);
1050
1051        // except when it comes to breaking up sequences of alphanumeric characters that do impact the comparison
1052        assert_ver_order("4.999", "4.999.9", Ordering::Less);
1053        assert_ver_order("4.999", "4.99.9", Ordering::Greater);
1054    }
1055
1056    /// Test handling of non-ascii characters
1057    #[test]
1058    fn test_non_ascii_character_equivalence() {
1059        // the existence of sequences of non-ascii characters should not impact the version comparison at all
1060        assert_ver_order("1.1.Á.1", "1.1.1", Ordering::Equal);
1061        assert_ver_order("1.1.Á", "1.1.Á", Ordering::Equal);
1062        assert_ver_order("1.1.Á", "1.1.Ê", Ordering::Equal);
1063        assert_ver_order("1.1.ÁÁ", "1.1.Á", Ordering::Equal);
1064        assert_ver_order("1.1.Á", "1.1.ÊÊ", Ordering::Equal);
1065
1066        // except when it comes to breaking up sequences of ascii characters that do impact the comparison
1067        assert_ver_order("1.1Á1", "1.11", Ordering::Less);
1068    }
1069
1070    /// Test that Hash is consistent with PartialEq (equal values must hash the same)
1071    #[test]
1072    fn test_evr_hash_consistency() {
1073        use std::hash::{DefaultHasher, Hash, Hasher};
1074
1075        fn hash_of<T: Hash>(val: &T) -> u64 {
1076            let mut h = DefaultHasher::new();
1077            val.hash(&mut h);
1078            h.finish()
1079        }
1080
1081        // empty epoch and "0" epoch are equal, so must hash the same
1082        let evr1 = Evr::parse("1.2.3-45");
1083        let evr2 = Evr::parse("0:1.2.3-45");
1084        assert_eq!(evr1, evr2);
1085        assert_eq!(hash_of(&evr1), hash_of(&evr2));
1086
1087        // identical EVRs hash the same
1088        let evr3 = Evr::parse("2:1.2.3-45");
1089        let evr4 = Evr::parse("2:1.2.3-45");
1090        assert_eq!(hash_of(&evr3), hash_of(&evr4));
1091
1092        // different EVRs should (almost certainly) hash differently
1093        assert_ne!(hash_of(&evr1), hash_of(&evr3));
1094    }
1095
1096    /// Test that Nevra Hash is consistent with PartialEq
1097    #[test]
1098    fn test_nevra_hash_consistency() {
1099        use std::hash::{DefaultHasher, Hash, Hasher};
1100
1101        fn hash_of<T: Hash>(val: &T) -> u64 {
1102            let mut h = DefaultHasher::new();
1103            val.hash(&mut h);
1104            h.finish()
1105        }
1106
1107        let nevra1 = Nevra::parse("foo-1.2.3-45.noarch");
1108        let nevra2 = Nevra::parse("foo-0:1.2.3-45.noarch");
1109        assert_eq!(nevra1, nevra2);
1110        assert_eq!(hash_of(&nevra1), hash_of(&nevra2));
1111
1112        let nevra3 = Nevra::parse("foo-1:1.2.3-45.noarch");
1113        assert_ne!(hash_of(&nevra1), hash_of(&nevra3));
1114    }
1115
1116    #[cfg(feature = "serde")]
1117    #[test]
1118    fn test_evr_serde_roundtrip() {
1119        let evr = Evr::parse("1:2.3.4-5");
1120        let json = serde_json::to_string(&evr).unwrap();
1121        let evr2: Evr = serde_json::from_str(&json).unwrap();
1122        assert_eq!(evr, evr2);
1123    }
1124
1125    #[cfg(feature = "serde")]
1126    #[test]
1127    fn test_nevra_serde_roundtrip() {
1128        let nevra = Nevra::parse("foo-1:2.3.4-5.x86_64");
1129        let json = serde_json::to_string(&nevra).unwrap();
1130        let nevra2: Nevra = serde_json::from_str(&json).unwrap();
1131        assert_eq!(nevra, nevra2);
1132    }
1133
1134    #[test]
1135    fn test_requirement_no_constraint() {
1136        let req = Requirement::new("foo");
1137        assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
1138        assert!(req.satisfies("foo", &Evr::parse("999.0-1")));
1139        assert!(!req.satisfies("bar", &Evr::parse("1.0-1")));
1140    }
1141
1142    #[test]
1143    fn test_requirement_eq() {
1144        let req = Requirement::with_constraint("foo", ReqOperator::EQ, Evr::parse("1.0-1"));
1145        assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
1146        assert!(req.satisfies("foo", &Evr::parse("0:1.0-1")));
1147        assert!(!req.satisfies("foo", &Evr::parse("1.0-2")));
1148        assert!(!req.satisfies("foo", &Evr::parse("2.0-1")));
1149    }
1150
1151    #[test]
1152    fn test_requirement_ge() {
1153        let req = Requirement::with_constraint("foo", ReqOperator::GE, Evr::parse("1.0-1"));
1154        assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
1155        assert!(req.satisfies("foo", &Evr::parse("2.0-1")));
1156        assert!(!req.satisfies("foo", &Evr::parse("0.9-1")));
1157    }
1158
1159    #[test]
1160    fn test_requirement_lt() {
1161        let req = Requirement::with_constraint("foo", ReqOperator::LT, Evr::parse("2.0-1"));
1162        assert!(req.satisfies("foo", &Evr::parse("1.0-1")));
1163        assert!(!req.satisfies("foo", &Evr::parse("2.0-1")));
1164        assert!(!req.satisfies("foo", &Evr::parse("3.0-1")));
1165    }
1166
1167    #[test]
1168    fn test_requirement_display() {
1169        let req = Requirement::new("foo");
1170        assert_eq!(req.to_string(), "foo");
1171
1172        let req = Requirement::with_constraint("foo", ReqOperator::GE, Evr::parse("1:2.0-1"));
1173        assert_eq!(req.to_string(), "foo >= 1:2.0-1");
1174    }
1175}