1use std::cmp::Ordering;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Version {
18 Semver(semver::Version),
21 Numeric(Vec<u64>),
24 Deb {
31 epoch: u64,
32 upstream: String,
33 revision: String,
34 },
35 Opaque(String),
37}
38
39impl Version {
40 pub fn parse_lenient(s: &str) -> Self {
58 let stripped = s
59 .strip_prefix('v')
60 .or_else(|| s.strip_prefix('V'))
61 .unwrap_or(s);
62
63 if let Ok(v) = semver::Version::parse(stripped) {
64 return Version::Semver(v);
65 }
66
67 let parts: Vec<&str> = stripped.splitn(3, '.').collect();
69 let padded = match parts.len() {
70 1 => Some(format!("{}.0.0", parts[0])),
71 2 => Some(format!("{}.{}.0", parts[0], parts[1])),
72 _ => None,
73 };
74 if let Some(ref padded) = padded {
75 if let Ok(v) = semver::Version::parse(padded) {
76 return Version::Semver(v);
77 }
78 }
79
80 if let Some(segments) = parse_numeric(stripped) {
81 return Version::Numeric(segments);
82 }
83
84 if let Some(deb) = parse_deb(stripped) {
85 return deb;
86 }
87
88 Version::Opaque(s.to_string())
89 }
90
91 pub fn is_downgrade(&self, new: &Self) -> bool {
118 match (self, new) {
119 (Version::Semver(old), Version::Semver(new)) => {
120 new.cmp_precedence(old) == Ordering::Less
121 }
122 (Version::Numeric(old), Version::Numeric(new)) => numeric_downgrade(old, new),
123 (Version::Semver(old), Version::Numeric(new_segs)) => {
124 let old_segs = [old.major, old.minor, old.patch];
125 numeric_downgrade(&old_segs, new_segs)
126 }
127 (Version::Numeric(old_segs), Version::Semver(new)) => {
128 let new_segs = [new.major, new.minor, new.patch];
129 numeric_downgrade(old_segs, &new_segs)
130 }
131 (
132 Version::Deb {
133 epoch: oe,
134 upstream: ou,
135 revision: orev,
136 },
137 Version::Deb {
138 epoch: ne,
139 upstream: nu,
140 revision: nrev,
141 },
142 ) => deb_cmp((*ne, nu, nrev), (*oe, ou, orev)) == Ordering::Less,
143 _ => false,
144 }
145 }
146}
147
148fn numeric_downgrade(old: &[u64], new: &[u64]) -> bool {
150 let max_len = old.len().max(new.len());
151 for i in 0..max_len {
152 let o = old.get(i).copied().unwrap_or(0);
153 let n = new.get(i).copied().unwrap_or(0);
154 if n < o {
155 return true;
156 }
157 if n > o {
158 return false;
159 }
160 }
161 false
162}
163
164fn parse_numeric(stripped: &str) -> Option<Vec<u64>> {
168 let mut segments = Vec::new();
169 for part in stripped.split('.') {
170 segments.push(part.parse::<u64>().ok()?);
171 }
172 if segments.is_empty() {
173 None
174 } else {
175 Some(segments)
176 }
177}
178
179fn parse_deb(stripped: &str) -> Option<Version> {
187 let (epoch, rest) = split_epoch(stripped);
188
189 if !rest.starts_with(|c: char| c.is_ascii_digit()) {
190 return None;
191 }
192 if !rest.chars().all(is_deb_char) {
193 return None;
194 }
195
196 let (upstream, revision) = match rest.rfind('-') {
199 Some(idx) => (rest[..idx].to_string(), rest[idx + 1..].to_string()),
200 None => (rest.to_string(), String::new()),
201 };
202
203 Some(Version::Deb {
204 epoch,
205 upstream,
206 revision,
207 })
208}
209
210fn split_epoch(s: &str) -> (u64, &str) {
213 if let Some(idx) = s.find([':', '!']) {
214 let (head, tail) = s.split_at(idx);
215 if !head.is_empty() && head.bytes().all(|b| b.is_ascii_digit()) {
216 if let Ok(epoch) = head.parse::<u64>() {
217 return (epoch, &tail[1..]);
218 }
219 }
220 }
221 (0, s)
222}
223
224fn is_deb_char(c: char) -> bool {
226 c.is_ascii_alphanumeric() || matches!(c, '.' | '+' | '-' | '~' | ':')
227}
228
229fn deb_cmp(a: (u64, &str, &str), b: (u64, &str, &str)) -> Ordering {
233 a.0.cmp(&b.0)
234 .then_with(|| verrevcmp(a.1, b.1))
235 .then_with(|| verrevcmp(a.2, b.2))
236}
237
238fn verrevcmp(a: &str, b: &str) -> Ordering {
248 let a = a.as_bytes();
249 let b = b.as_bytes();
250 let mut i = 0;
251 let mut j = 0;
252
253 while i < a.len() || j < b.len() {
254 while (i < a.len() && !a[i].is_ascii_digit()) || (j < b.len() && !b[j].is_ascii_digit()) {
255 let ac = a.get(i).map_or(0, |&c| deb_order(c));
256 let bc = b.get(j).map_or(0, |&c| deb_order(c));
257 if ac != bc {
258 return ac.cmp(&bc);
259 }
260 i += 1;
261 j += 1;
262 }
263
264 while i < a.len() && a[i] == b'0' {
265 i += 1;
266 }
267 while j < b.len() && b[j] == b'0' {
268 j += 1;
269 }
270
271 let mut first_diff = 0i32;
272 while i < a.len() && a[i].is_ascii_digit() && j < b.len() && b[j].is_ascii_digit() {
273 if first_diff == 0 {
274 first_diff = i32::from(a[i]) - i32::from(b[j]);
275 }
276 i += 1;
277 j += 1;
278 }
279 if i < a.len() && a[i].is_ascii_digit() {
282 return Ordering::Greater;
283 }
284 if j < b.len() && b[j].is_ascii_digit() {
285 return Ordering::Less;
286 }
287 if first_diff != 0 {
288 return first_diff.cmp(&0);
289 }
290 }
291
292 Ordering::Equal
293}
294
295fn deb_order(c: u8) -> i32 {
301 if c.is_ascii_digit() {
302 0
303 } else if c.is_ascii_alphabetic() {
304 i32::from(c)
305 } else if c == b'~' {
306 -1
307 } else {
308 i32::from(c) + 256
309 }
310}
311
312pub fn is_version_downgrade(old_ver: &str, new_ver: &str) -> bool {
317 Version::parse_lenient(old_ver).is_downgrade(&Version::parse_lenient(new_ver))
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn parse_standard_semver() {
326 let v = Version::parse_lenient("1.2.3");
327 assert_eq!(v, Version::Semver(semver::Version::new(1, 2, 3)));
328 }
329
330 #[test]
331 fn parse_v_prefix() {
332 assert_eq!(
333 Version::parse_lenient("v1.2.3"),
334 Version::Semver(semver::Version::new(1, 2, 3))
335 );
336 assert_eq!(
337 Version::parse_lenient("V1.2.3"),
338 Version::Semver(semver::Version::new(1, 2, 3))
339 );
340 }
341
342 #[test]
343 fn parse_two_parts() {
344 assert_eq!(
345 Version::parse_lenient("1.2"),
346 Version::Semver(semver::Version::new(1, 2, 0))
347 );
348 }
349
350 #[test]
351 fn parse_single_part() {
352 assert_eq!(
353 Version::parse_lenient("42"),
354 Version::Semver(semver::Version::new(42, 0, 0))
355 );
356 }
357
358 #[test]
359 fn parse_prerelease() {
360 let v = Version::parse_lenient("1.2.3-beta.1");
361 match v {
362 Version::Semver(sv) => {
363 assert_eq!(sv.major, 1);
364 assert_eq!(sv.minor, 2);
365 assert_eq!(sv.patch, 3);
366 assert!(!sv.pre.is_empty());
367 }
368 other => panic!("expected Semver, got {:?}", other),
369 }
370 }
371
372 #[test]
373 fn parse_build_metadata() {
374 let v = Version::parse_lenient("1.2.3+build.456");
375 match v {
376 Version::Semver(sv) => {
377 assert_eq!((sv.major, sv.minor, sv.patch), (1, 2, 3));
378 assert!(!sv.build.is_empty());
379 }
380 other => panic!("expected Semver, got {:?}", other),
381 }
382 }
383
384 #[test]
385 fn parse_prerelease_and_build() {
386 let v = Version::parse_lenient("1.0.0-alpha.1+build.789");
387 match v {
388 Version::Semver(sv) => {
389 assert_eq!(sv.major, 1);
390 assert!(!sv.pre.is_empty());
391 assert!(!sv.build.is_empty());
392 }
393 other => panic!("expected Semver, got {:?}", other),
394 }
395 }
396
397 #[test]
398 fn parse_v_prefix_two_parts() {
399 assert_eq!(
400 Version::parse_lenient("v1.2"),
401 Version::Semver(semver::Version::new(1, 2, 0))
402 );
403 }
404
405 #[test]
406 fn parse_v_prefix_single_part() {
407 assert_eq!(
408 Version::parse_lenient("v5"),
409 Version::Semver(semver::Version::new(5, 0, 0))
410 );
411 }
412
413 #[test]
414 fn parse_v_prefix_prerelease() {
415 let v = Version::parse_lenient("v2.0.0-rc.1");
416 match v {
417 Version::Semver(sv) => {
418 assert_eq!(sv.major, 2);
419 assert!(!sv.pre.is_empty());
420 }
421 other => panic!("expected Semver, got {:?}", other),
422 }
423 }
424
425 #[test]
426 fn parse_zero_version() {
427 assert_eq!(
428 Version::parse_lenient("0.0.0"),
429 Version::Semver(semver::Version::new(0, 0, 0))
430 );
431 }
432
433 #[test]
434 fn parse_large_numbers() {
435 assert_eq!(
436 Version::parse_lenient("999.888.777"),
437 Version::Semver(semver::Version::new(999, 888, 777))
438 );
439 }
440
441 #[test]
442 fn parse_single_zero() {
443 assert_eq!(
444 Version::parse_lenient("0"),
445 Version::Semver(semver::Version::new(0, 0, 0))
446 );
447 }
448
449 #[test]
450 fn parse_four_part_is_numeric() {
451 assert_eq!(
452 Version::parse_lenient("1.2.3.4"),
453 Version::Numeric(vec![1, 2, 3, 4])
454 );
455 }
456
457 #[test]
458 fn parse_date_based_is_numeric() {
459 assert_eq!(
461 Version::parse_lenient("2024.01.15"),
462 Version::Numeric(vec![2024, 1, 15])
463 );
464 }
465
466 #[test]
467 fn parse_v_prefix_four_part_is_numeric() {
468 assert_eq!(
470 Version::parse_lenient("v1.2.3.4"),
471 Version::Numeric(vec![1, 2, 3, 4])
472 );
473 assert_eq!(
474 Version::parse_lenient("V1.2.3.4"),
475 Version::Numeric(vec![1, 2, 3, 4])
476 );
477 }
478
479 #[test]
480 fn parse_v_prefix_date_based_is_numeric() {
481 assert_eq!(
482 Version::parse_lenient("v2024.01.15"),
483 Version::Numeric(vec![2024, 1, 15])
484 );
485 }
486
487 #[test]
488 fn parse_leading_zeros_is_numeric() {
489 assert_eq!(
490 Version::parse_lenient("01.02.03"),
491 Version::Numeric(vec![1, 2, 3])
492 );
493 }
494
495 #[test]
496 fn parse_non_numeric_is_opaque() {
497 assert_eq!(Version::parse_lenient("abc"), Version::Opaque("abc".into()));
498 assert_eq!(
499 Version::parse_lenient("foo.bar.baz"),
500 Version::Opaque("foo.bar.baz".into())
501 );
502 }
503
504 #[test]
505 fn parse_whitespace_is_opaque() {
506 assert!(matches!(
507 Version::parse_lenient(" 1.2.3"),
508 Version::Opaque(_)
509 ));
510 assert!(matches!(
511 Version::parse_lenient("1.2.3 "),
512 Version::Opaque(_)
513 ));
514 }
515
516 #[test]
517 fn parse_empty_string_is_opaque() {
518 assert!(matches!(Version::parse_lenient(""), Version::Opaque(_)));
519 }
520
521 #[test]
522 fn downgrade_semver() {
523 assert!(is_version_downgrade("2.0.0", "1.5.0"));
524 assert!(is_version_downgrade("1.1.0", "1.0.0"));
525 assert!(is_version_downgrade("1.0.1", "1.0.0"));
526 }
527
528 #[test]
529 fn upgrade_semver_not_flagged() {
530 assert!(!is_version_downgrade("1.0.0", "1.1.0"));
531 assert!(!is_version_downgrade("1.0.0", "2.0.0"));
532 assert!(!is_version_downgrade("1.0.0", "1.0.1"));
533 }
534
535 #[test]
536 fn equal_semver_not_flagged() {
537 assert!(!is_version_downgrade("1.0.0", "1.0.0"));
538 }
539
540 #[test]
541 fn downgrade_v_prefix() {
542 assert!(is_version_downgrade("v2.0.0", "v1.0.0"));
543 assert!(!is_version_downgrade("v1.0.0", "v2.0.0"));
544 }
545
546 #[test]
547 fn downgrade_prerelease() {
548 assert!(is_version_downgrade("1.0.0", "1.0.0-rc1"));
549 assert!(!is_version_downgrade("1.0.0-rc1", "1.0.0"));
550 }
551
552 #[test]
553 fn downgrade_build_metadata() {
554 assert!(!is_version_downgrade("1.0.0+build.1", "1.0.0+build.2"));
558 assert!(!is_version_downgrade("1.0.0+build.2", "1.0.0+build.1"));
559 assert!(!is_version_downgrade("1.0.0+build.1", "1.0.0+build.1"));
560 assert!(!is_version_downgrade("1.0.0+c144a98", "1.0.0+bc17664"));
563 assert!(!is_version_downgrade("1.0.0+build.10", "1.0.0+build.9"));
564 }
565
566 #[test]
567 fn downgrade_mixed_v_prefix() {
568 assert!(is_version_downgrade("v2.0.0", "1.0.0"));
569 assert!(is_version_downgrade("2.0.0", "v1.0.0"));
570 assert!(!is_version_downgrade("v1.0.0", "2.0.0"));
571 assert!(!is_version_downgrade("1.0.0", "v2.0.0"));
572 }
573
574 #[test]
575 fn downgrade_prerelease_ordering() {
576 assert!(is_version_downgrade("1.0.0-beta.1", "1.0.0-alpha.1"));
577 assert!(is_version_downgrade("1.0.0-rc.1", "1.0.0-beta.1"));
578 assert!(!is_version_downgrade("1.0.0-alpha.1", "1.0.0-beta.1"));
579 assert!(!is_version_downgrade("1.0.0-beta.1", "1.0.0-rc.1"));
580 }
581
582 #[test]
583 fn downgrade_prerelease_numeric_ordering() {
584 assert!(is_version_downgrade("1.0.0-rc.2", "1.0.0-rc.1"));
585 assert!(!is_version_downgrade("1.0.0-rc.1", "1.0.0-rc.2"));
586 }
587
588 #[test]
589 fn downgrade_equal_with_v_prefix() {
590 assert!(!is_version_downgrade("v1.0.0", "v1.0.0"));
591 }
592
593 #[test]
594 fn downgrade_padded_two_part() {
595 assert!(is_version_downgrade("1.2", "1.1"));
596 assert!(!is_version_downgrade("1.1", "1.2"));
597 assert!(!is_version_downgrade("1.2", "1.2"));
598 }
599
600 #[test]
601 fn downgrade_padded_single_part() {
602 assert!(is_version_downgrade("2", "1"));
603 assert!(!is_version_downgrade("1", "2"));
604 assert!(!is_version_downgrade("5", "5"));
605 }
606
607 #[test]
608 fn downgrade_mixed_part_counts_semver() {
609 assert!(is_version_downgrade("2.0", "1.9.9"));
610 assert!(!is_version_downgrade("1.9.9", "2.0"));
611 }
612
613 #[test]
614 fn downgrade_v_prefix_two_part() {
615 assert!(is_version_downgrade("v2.0", "v1.0"));
616 assert!(!is_version_downgrade("v1.0", "v2.0"));
617 }
618
619 #[test]
620 fn downgrade_four_part() {
621 assert!(is_version_downgrade("1.2.3.4", "1.2.3.3"));
622 assert!(!is_version_downgrade("1.2.3.3", "1.2.3.4"));
623 assert!(!is_version_downgrade("1.2.3.4", "1.2.3.4"));
624 }
625
626 #[test]
627 fn downgrade_date_based() {
628 assert!(is_version_downgrade("2024.01.15", "2023.12.01"));
629 assert!(!is_version_downgrade("2023.12.01", "2024.01.15"));
630 }
631
632 #[test]
633 fn downgrade_v_prefix_four_part() {
634 assert!(is_version_downgrade("v1.2.3.4", "v1.2.3.3"));
637 assert!(!is_version_downgrade("v1.2.3.3", "v1.2.3.4"));
638 assert!(!is_version_downgrade("v1.2.3.4", "v1.2.3.4"));
639 }
640
641 #[test]
642 fn downgrade_v_prefix_date_based() {
643 assert!(is_version_downgrade("v2024.01.15", "v2023.12.01"));
644 assert!(!is_version_downgrade("v2023.12.01", "v2024.01.15"));
645 }
646
647 #[test]
648 fn downgrade_non_numeric_not_flagged() {
649 assert!(!is_version_downgrade("abc", "def"));
650 assert!(!is_version_downgrade("foo.bar", "foo.baz"));
651 }
652
653 #[test]
654 fn downgrade_numeric_unequal_length() {
655 assert!(is_version_downgrade("1.2.3.4", "1.2.3"));
656 assert!(!is_version_downgrade("1.2.3", "1.2.3.4"));
657 }
658
659 #[test]
660 fn downgrade_large_major_numeric_equal() {
661 assert!(!is_version_downgrade("2024.1.15", "2024.1.15"));
663 }
664
665 #[test]
666 fn downgrade_semver_vs_four_part() {
667 assert!(!is_version_downgrade("1.2.3", "1.2.3.4"));
670 assert!(is_version_downgrade("1.2.3.4", "1.2.3"));
671 }
672
673 #[test]
674 fn downgrade_v_prefix_vs_four_part() {
675 assert!(!is_version_downgrade("v1.2.3", "1.2.3.4"));
677 assert!(is_version_downgrade("1.2.3.4", "v1.2.3"));
678 }
679
680 #[test]
681 fn downgrade_empty_strings() {
682 assert!(!is_version_downgrade("", "1.0.0"));
683 assert!(!is_version_downgrade("1.0.0", ""));
684 assert!(!is_version_downgrade("", ""));
685 }
686
687 #[test]
690 fn parse_epoch_is_deb() {
691 assert!(matches!(
693 Version::parse_lenient("2:1.0"),
694 Version::Deb { .. }
695 ));
696 assert!(matches!(
697 Version::parse_lenient("1:9.0"),
698 Version::Deb { .. }
699 ));
700 }
701
702 #[test]
703 fn parse_revision_is_deb() {
704 assert!(matches!(
706 Version::parse_lenient("5.1-3"),
707 Version::Deb { .. }
708 ));
709 }
710
711 #[test]
712 fn parse_deb_fields() {
713 match Version::parse_lenient("2:1.2.3-4") {
714 Version::Deb {
715 epoch,
716 upstream,
717 revision,
718 } => {
719 assert_eq!(epoch, 2);
720 assert_eq!(upstream, "1.2.3");
721 assert_eq!(revision, "4");
722 }
723 other => panic!("expected Deb, got {:?}", other),
724 }
725 }
726
727 #[test]
728 fn parse_deb_revision_splits_at_last_hyphen() {
729 match Version::parse_lenient("1.2-2-1") {
732 Version::Deb {
733 epoch,
734 upstream,
735 revision,
736 } => {
737 assert_eq!(epoch, 0);
738 assert_eq!(upstream, "1.2-2");
739 assert_eq!(revision, "1");
740 }
741 other => panic!("expected Deb, got {:?}", other),
742 }
743 }
744
745 #[test]
746 fn parse_pep440_epoch_is_deb() {
747 match Version::parse_lenient("1!2.0") {
748 Version::Deb {
749 epoch,
750 upstream,
751 revision,
752 } => {
753 assert_eq!(epoch, 1);
754 assert_eq!(upstream, "2.0");
755 assert_eq!(revision, "");
756 }
757 other => panic!("expected Deb, got {:?}", other),
758 }
759 }
760
761 #[test]
762 fn parse_tilde_prerelease_is_deb() {
763 assert!(matches!(
765 Version::parse_lenient("1.0.0~rc1"),
766 Version::Deb { .. }
767 ));
768 }
769
770 #[test]
771 fn parse_codename_stays_opaque() {
772 assert!(matches!(
774 Version::parse_lenient("focal-1"),
775 Version::Opaque(_)
776 ));
777 assert!(matches!(
778 Version::parse_lenient("stable"),
779 Version::Opaque(_)
780 ));
781 assert!(matches!(
783 Version::parse_lenient("1:stable"),
784 Version::Opaque(_)
785 ));
786 }
787
788 #[test]
791 fn downgrade_epoch() {
792 assert!(is_version_downgrade("2:1.0", "1:9.0"));
794 assert!(!is_version_downgrade("1:9.0", "2:1.0"));
795 assert!(!is_version_downgrade("1:1.0", "2:0.1"));
797 assert!(is_version_downgrade("2:0.1", "1:1.0"));
798 }
799
800 #[test]
801 fn downgrade_epoch_equal_upstream() {
802 assert!(is_version_downgrade("1:2.0", "1:1.0"));
803 assert!(!is_version_downgrade("1:1.0", "1:2.0"));
804 assert!(!is_version_downgrade("1:1.0", "1:1.0"));
805 }
806
807 #[test]
808 fn downgrade_implicit_epoch_zero() {
809 assert!(!is_version_downgrade("5.1-1", "1:0.1-1"));
812 assert!(is_version_downgrade("1:0.1-1", "0:0.1-1"));
813 }
814
815 #[test]
816 fn downgrade_revision() {
817 assert!(is_version_downgrade("5.1-3", "5.1-2"));
818 assert!(!is_version_downgrade("5.1-2", "5.1-3"));
819 assert!(!is_version_downgrade("5.1-2", "5.1-2"));
820 }
821
822 #[test]
823 fn downgrade_upstream_trumps_revision() {
824 assert!(is_version_downgrade("1:5.2-1", "1:5.1-1"));
826 assert!(!is_version_downgrade("1:5.1-9", "1:5.2-1"));
828 }
829
830 #[test]
831 fn downgrade_absent_revision_equals_zero() {
832 assert!(is_version_downgrade("1:2.0-1", "1:2.0"));
835 assert!(!is_version_downgrade("1:2.0", "1:2.0-1"));
836 }
837
838 #[test]
839 fn downgrade_rpm_release_with_epoch() {
840 assert!(is_version_downgrade("1:1.2.3-2.el8", "1:1.2.3-1.el8"));
843 assert!(!is_version_downgrade("1:1.2.3-1.el8", "1:1.2.3-2.el8"));
844 assert!(is_version_downgrade("1:1.2.3-1.el8", "1:1.2.3-1.el7"));
846 assert!(!is_version_downgrade("1:1.2.3-1.el7", "1:1.2.3-1.el8"));
847 }
848
849 #[test]
850 fn downgrade_deb_numeric_not_lexical() {
851 assert!(is_version_downgrade("1.10-1", "1.9-1"));
853 assert!(!is_version_downgrade("1.9-1", "1.10-1"));
854 }
855
856 #[test]
857 fn downgrade_deb_tilde_prerelease() {
858 assert!(is_version_downgrade("1.0.0~rc2", "1.0.0~rc1"));
860 assert!(!is_version_downgrade("1.0.0~rc1", "1.0.0~rc2"));
861 assert!(is_version_downgrade("1:1.0~rc1", "1:1.0~beta1"));
862 }
863
864 #[test]
865 fn downgrade_real_world_deb() {
866 assert!(is_version_downgrade(
868 "1:1.1.1f-1ubuntu2.16",
869 "1:1.1.1f-1ubuntu2.15"
870 ));
871 assert!(!is_version_downgrade(
872 "1:1.1.1f-1ubuntu2.15",
873 "1:1.1.1f-1ubuntu2.16"
874 ));
875 }
876
877 #[test]
878 fn downgrade_deb_opaque_not_flagged() {
879 assert!(!is_version_downgrade("focal", "bionic"));
881 assert!(!is_version_downgrade("1:stable", "1:oldstable"));
882 }
883
884 #[test]
885 fn downgrade_deb_vs_semver_not_flagged() {
886 assert!(!is_version_downgrade("2:1.0", "1.0.0"));
888 assert!(!is_version_downgrade("1.0.0", "2:1.0"));
889 }
890
891 #[test]
892 fn deb_canonical_ordering_vectors() {
893 use Ordering::{Equal, Greater, Less};
894
895 let cases = [
904 ("1:1.0~~", "1:1.0~~a", Less),
907 ("1:1.0~~a", "1:1.0~", Less),
908 ("1:1.0~", "1:1.0", Less),
909 ("1:1.0", "1:1.0a", Less),
910 ("1:1.0~rc1", "1:1.0", Less),
913 ("1:1.0~rc1", "1:1.0~rc2", Less),
914 ("1:1.10", "1:1.9", Greater),
916 ("1:1.0", "1:1.00", Equal),
918 ("1:1.01", "1:1.1", Equal),
919 ("1:1.a", "1:1.1", Greater),
921 ("1:1.0a", "1:1.01", Less),
923 ("2:0.1", "1:9.9", Greater),
925 ("1:5.2-1", "1:5.1-9", Greater),
927 ("1:2.0", "1:2.0-0", Equal),
929 ("1:2.0-10", "1:2.0-9", Greater),
931 ];
932
933 for (a, b, expected) in cases {
934 assert!(
937 matches!(Version::parse_lenient(a), Version::Deb { .. }),
938 "{a} no longer parses as Deb"
939 );
940 assert!(
941 matches!(Version::parse_lenient(b), Version::Deb { .. }),
942 "{b} no longer parses as Deb"
943 );
944 match expected {
945 Less => {
947 assert!(is_version_downgrade(b, a), "expected {a} < {b}");
948 assert!(!is_version_downgrade(a, b), "expected {a} < {b}");
949 }
950 Greater => {
952 assert!(is_version_downgrade(a, b), "expected {a} > {b}");
953 assert!(!is_version_downgrade(b, a), "expected {a} > {b}");
954 }
955 Equal => {
957 assert!(!is_version_downgrade(a, b), "expected {a} == {b}");
958 assert!(!is_version_downgrade(b, a), "expected {a} == {b}");
959 }
960 }
961 }
962 }
963}