1#![deny(missing_docs)]
2#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
3#![allow(deprecated)]
5
6use lazy_regex::{regex_captures, regex_replace};
7use num_bigint::BigInt;
8use std::cmp::Ordering;
9use std::str::FromStr;
10
11pub mod upstream;
12pub mod vcs;
13pub mod vendor;
14
15#[derive(Debug, Clone)]
19pub struct Version {
20 pub epoch: Option<u32>,
22
23 pub upstream_version: String,
25
26 pub debian_revision: Option<String>,
28}
29
30fn non_digit_cmp(va: &str, vb: &str) -> Ordering {
31 fn order(x: char) -> i32 {
32 match x {
33 '~' => -1,
34 '0'..='9' => unreachable!(),
35 'A'..='Z' | 'a'..='z' => x as i32,
36 _ => x as i32 + 256,
37 }
38 }
39
40 va.chars()
41 .map(order)
42 .chain(std::iter::repeat(0))
43 .zip(vb.chars().map(order).chain(std::iter::repeat(0)))
44 .take(va.len().max(vb.len()))
45 .find_map(|(a, b)| match a.cmp(&b) {
46 Ordering::Equal => None,
47 other => Some(other),
48 })
49 .unwrap_or(Ordering::Equal)
50}
51
52#[test]
53fn test_non_digit_cmp() {
54 assert_eq!(non_digit_cmp("a", "b"), Ordering::Less);
55 assert_eq!(non_digit_cmp("b", "a"), Ordering::Greater);
56 assert_eq!(non_digit_cmp("a", "a"), Ordering::Equal);
57 assert_eq!(non_digit_cmp("a", "-"), Ordering::Less);
58 assert_eq!(non_digit_cmp("a", "+"), Ordering::Less);
59 assert_eq!(non_digit_cmp("a", ""), Ordering::Greater);
60 assert_eq!(non_digit_cmp("", "a"), Ordering::Less);
61 assert_eq!(non_digit_cmp("", ""), Ordering::Equal);
62 assert_eq!(non_digit_cmp("~", ""), Ordering::Less);
63 assert_eq!(non_digit_cmp("~~", "~"), Ordering::Less);
64 assert_eq!(non_digit_cmp("~~", "~~a"), Ordering::Less);
65 assert_eq!(non_digit_cmp("~~a", "~"), Ordering::Less);
66 assert_eq!(non_digit_cmp("~", "a"), Ordering::Less);
67 assert_eq!(non_digit_cmp("!", "@"), Ordering::Less); assert_eq!(non_digit_cmp("@", "!"), Ordering::Greater);
70 assert_eq!(non_digit_cmp("#", "$"), Ordering::Less); assert_eq!(non_digit_cmp("|", "}"), Ordering::Less); }
73
74fn drop_leading_zeroes(s: &str) -> &str {
75 let bytes = s.as_bytes();
77 let mut start = 0;
78 while start + 1 < bytes.len() && bytes[start] == b'0' && bytes[start + 1].is_ascii_digit() {
79 start += 1;
80 }
81 &s[start..]
82}
83
84fn version_cmp_part(mut a: &str, mut b: &str) -> Ordering {
85 while !a.is_empty() || !b.is_empty() {
86 let a_non_digit = &a[..a
88 .chars()
89 .position(|c| c.is_ascii_digit())
90 .unwrap_or(a.len())];
91 let b_non_digit = &b[..b
92 .chars()
93 .position(|c| c.is_ascii_digit())
94 .unwrap_or(b.len())];
95
96 match non_digit_cmp(a_non_digit, b_non_digit) {
98 Ordering::Equal => (),
99 ordering => return ordering,
100 }
101
102 a = &a[a_non_digit.len()..];
104 b = &b[b_non_digit.len()..];
105
106 let a_digit = &a[..a
108 .chars()
109 .position(|c| !c.is_ascii_digit())
110 .unwrap_or(a.len())];
111 let b_digit = &b[..b
112 .chars()
113 .position(|c| !c.is_ascii_digit())
114 .unwrap_or(b.len())];
115
116 let ordering = match (a_digit.len(), b_digit.len()) {
118 (0, 0) => Ordering::Equal,
119 (0, _) => Ordering::Less,
120 (_, 0) => Ordering::Greater,
121 (a_len, b_len) if a_len <= 19 && b_len <= 19 => {
123 match (a_digit.parse::<u64>(), b_digit.parse::<u64>()) {
124 (Ok(a_num), Ok(b_num)) => a_num.cmp(&b_num),
125 _ => a_digit
127 .parse::<BigInt>()
128 .unwrap()
129 .cmp(&b_digit.parse::<BigInt>().unwrap()),
130 }
131 }
132 _ => a_digit
134 .parse::<BigInt>()
135 .unwrap()
136 .cmp(&b_digit.parse::<BigInt>().unwrap()),
137 };
138
139 match ordering {
140 Ordering::Equal => (),
141 ordering => return ordering,
142 }
143
144 a = &a[a_digit.len()..];
146 b = &b[b_digit.len()..];
147 }
148 Ordering::Equal
149}
150
151impl Ord for Version {
152 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
153 let self_norm = self.explicit();
154 let other_norm = other.explicit();
155 if self_norm.0 != other_norm.0 {
156 return std::cmp::Ord::cmp(&self_norm.0, &other_norm.0);
157 }
158
159 match version_cmp_part(self_norm.1, other_norm.1) {
160 Ordering::Equal => (),
161 ordering => return ordering,
162 }
163
164 version_cmp_part(self_norm.2, other_norm.2)
165 }
166}
167
168impl PartialOrd for Version {
169 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
170 Some(self.cmp(other))
171 }
172}
173
174impl PartialEq for Version {
175 fn eq(&self, other: &Self) -> bool {
176 self.partial_cmp(other) == Some(std::cmp::Ordering::Equal)
177 }
178}
179
180impl Eq for Version {}
181
182#[derive(Debug, PartialEq, Eq)]
184pub struct ParseError(String);
185
186impl std::fmt::Display for ParseError {
187 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
188 f.write_str(&self.0)
189 }
190}
191
192impl std::error::Error for ParseError {}
193
194impl FromStr for Version {
195 type Err = ParseError;
196
197 fn from_str(text: &str) -> Result<Self, Self::Err> {
198 let (_, epoch, upstream_version, debian_revision) = if let Some(c) = regex_captures!(
199 r"^(?:(\d+):)?([A-Za-z0-9.+:~-]+?)(?:-([A-Za-z0-9+.~]+))?$",
200 text
201 ) {
202 c
203 } else {
204 return Err(ParseError(format!("Invalid version string: {}", text)));
205 };
206
207 let epoch = Some(epoch)
208 .filter(|e| !e.is_empty())
209 .map(|e| {
210 e.parse()
211 .map_err(|e| ParseError(format!("Error parsing epoch: {}", e)))
212 })
213 .transpose()?;
214
215 let debian_revision = if debian_revision.is_empty() {
216 None
217 } else {
218 Some(debian_revision.to_string())
219 };
220
221 Ok(Version {
222 epoch,
223 upstream_version: upstream_version.to_string(),
224 debian_revision,
225 })
226 }
227}
228
229impl std::fmt::Display for Version {
230 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
231 if let Some(epoch) = self.epoch.as_ref() {
232 write!(f, "{}:", epoch)?;
233 }
234 f.write_str(&self.upstream_version)?;
235 if let Some(debian_revision) = self.debian_revision.as_ref() {
236 write!(f, "-{}", debian_revision)?;
237 }
238 Ok(())
239 }
240}
241
242impl std::hash::Hash for Version {
243 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
244 (
245 self.epoch,
246 self.upstream_version.as_str(),
247 self.debian_revision.as_deref(),
248 )
249 .hash(state);
250 }
251}
252
253impl Version {
254 fn explicit(&self) -> (u32, &str, &str) {
259 (
260 self.epoch.unwrap_or(0),
261 self.upstream_version.as_str(),
262 self.debian_revision.as_deref().unwrap_or("0"),
263 )
264 }
265
266 pub fn is_bin_nmu(&self) -> bool {
284 self.bin_nmu_count().is_some()
285 }
286
287 pub fn bin_nmu_count(&self) -> Option<i32> {
291 fn bin_nmu_suffix(s: &str) -> Option<i32> {
292 s.split_once("+b").and_then(|(_, rest)| rest.parse().ok())
293 }
294 if let Some(debian_revision) = self.debian_revision.as_ref() {
295 bin_nmu_suffix(debian_revision)
296 } else {
297 bin_nmu_suffix(self.upstream_version.as_str())
298 }
299 }
300
301 pub fn increment_bin_nmu(self) -> Version {
306 fn increment_bin_nmu_suffix(s: &str) -> String {
307 match s.split_once("+b") {
308 Some((prefix, rest)) => match rest.parse::<i32>() {
309 Ok(num) => format!("{}+b{}", prefix, num + 1),
310 Err(_) => format!("{}+b1", s),
311 },
312 None => format!("{}+b1", s),
313 }
314 }
315
316 if let Some(debian_revision) = self.debian_revision.as_ref() {
317 Version {
318 epoch: self.epoch,
319 upstream_version: self.upstream_version,
320 debian_revision: Some(increment_bin_nmu_suffix(debian_revision)),
321 }
322 } else {
323 Version {
324 epoch: self.epoch,
325 upstream_version: increment_bin_nmu_suffix(&self.upstream_version),
326 debian_revision: self.debian_revision,
327 }
328 }
329 }
330
331 pub fn is_nmu(&self) -> bool {
338 self.nmu_count().is_some()
339 }
340
341 pub fn nmu_count(&self) -> Option<i32> {
346 fn nmu_suffix(s: &str) -> Option<i32> {
347 s.split_once("+nmu").and_then(|(_, rest)| rest.parse().ok())
348 }
349 if let Some(debian_revision) = self.debian_revision.as_ref() {
350 nmu_suffix(debian_revision)
351 } else {
352 nmu_suffix(self.upstream_version.as_str())
353 }
354 }
355
356 pub fn canonicalize(&self) -> Version {
366 let epoch = match self.epoch {
367 Some(0) => None,
368 epoch => epoch,
369 };
370
371 let upstream_version_stripped = drop_leading_zeroes(&self.upstream_version);
372 let upstream_version = if upstream_version_stripped == self.upstream_version {
373 self.upstream_version.clone()
374 } else {
375 upstream_version_stripped.to_string()
376 };
377
378 let debian_revision = match self.debian_revision.as_ref() {
379 Some(r) if r.chars().all(|c| c == '0') => None,
380 None => None,
381 Some(revision) => {
382 let stripped = drop_leading_zeroes(revision);
383 if stripped == revision {
384 Some(revision.clone())
385 } else {
386 Some(stripped.to_string())
387 }
388 }
389 };
390
391 Version {
392 epoch,
393 upstream_version,
394 debian_revision,
395 }
396 }
397
398 pub fn increment_debian(&mut self) {
403 if let Some(ref mut debian_revision) = self.debian_revision {
404 *debian_revision = regex_replace!(r"\d+$", debian_revision, |x: &str| {
405 (x.parse::<i32>().unwrap() + 1).to_string()
406 })
407 .into_owned();
408 } else {
409 self.upstream_version = regex_replace!(r"\d+$", &self.upstream_version, |x: &str| {
410 (x.parse::<i32>().unwrap() + 1).to_string()
411 })
412 .into_owned();
413 }
414 }
415
416 pub fn is_native(&self) -> bool {
418 self.debian_revision.is_none()
419 }
420}
421
422#[cfg(feature = "sqlx")]
423use sqlx::{postgres::PgTypeInfo, Postgres};
424
425#[cfg(feature = "sqlx")]
426impl sqlx::Type<Postgres> for Version {
427 fn type_info() -> PgTypeInfo {
428 PgTypeInfo::with_name("debversion")
429 }
430}
431
432#[cfg(feature = "sqlx")]
433impl sqlx::Encode<'_, Postgres> for Version {
434 fn encode_by_ref(
435 &self,
436 buf: &mut sqlx::postgres::PgArgumentBuffer,
437 ) -> Result<sqlx::encode::IsNull, Box<dyn std::error::Error + Send + Sync>> {
438 let version_str = self.to_string();
439 sqlx::Encode::<Postgres>::encode_by_ref(&version_str.as_str(), buf)
440 }
441}
442
443#[cfg(feature = "sqlx")]
444impl sqlx::Decode<'_, Postgres> for Version {
445 fn decode(
446 value: sqlx::postgres::PgValueRef<'_>,
447 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
448 let s: &str = sqlx::Decode::<Postgres>::decode(value)?;
449 Ok(s.parse::<Version>()?)
450 }
451}
452
453#[cfg(all(feature = "sqlx", test))]
454mod sqlx_tests {
455 #[test]
456 fn type_info() {
457 use super::Version;
458 use sqlx::postgres::PgTypeInfo;
459 use sqlx::Type;
460
461 assert_eq!(PgTypeInfo::with_name("debversion"), Version::type_info());
462 }
463}
464
465#[cfg(feature = "python-debian")]
466use pyo3::prelude::*;
467
468#[cfg(feature = "python-debian")]
469impl FromPyObject<'_, '_> for Version {
470 type Error = PyErr;
471
472 fn extract(ob: pyo3::Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
473 let debian_support = Python::import(ob.py(), "debian.debian_support")?;
474 let version_cls = debian_support.getattr("Version")?;
475 if !ob.is_instance(&version_cls)? {
476 return Err(pyo3::exceptions::PyTypeError::new_err("Expected a Version"));
477 }
478 Ok(Version {
479 epoch: ob
480 .getattr("epoch")?
481 .extract::<Option<String>>()?
482 .map(|s| s.parse().unwrap()),
483 upstream_version: ob.getattr("upstream_version")?.extract::<String>()?,
484 debian_revision: ob.getattr("debian_revision")?.extract::<Option<String>>()?,
485 })
486 }
487}
488
489#[cfg(feature = "python-debian")]
490impl<'py> IntoPyObject<'py> for Version {
491 type Target = PyAny;
492
493 type Output = Bound<'py, Self::Target>;
494
495 type Error = PyErr;
496
497 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
498 let debian_support = py.import("debian.debian_support").unwrap();
499 let version_cls = debian_support.getattr("Version").unwrap();
500 version_cls.call1((self.to_string(),))
501 }
502}
503
504#[cfg(feature = "python-debian")]
505mod python_tests {
506 #[test]
507 fn test_from_pyobject() {
508 use super::Version;
509 use pyo3::prelude::*;
510 use std::ffi::CString;
511
512 Python::with_gil(|py| {
513 let globals = pyo3::types::PyDict::new(py);
514 globals
515 .set_item(
516 "debian_support",
517 py.import("debian.debian_support").unwrap(),
518 )
519 .unwrap();
520 let v = py
521 .eval(
522 &CString::new("debian_support.Version('1.0-1')").unwrap(),
523 Some(&globals),
524 None,
525 )
526 .unwrap()
527 .extract::<Version>()
528 .unwrap();
529 assert_eq!(
530 v,
531 Version {
532 epoch: None,
533 upstream_version: "1.0".to_string(),
534 debian_revision: Some("1".to_string())
535 }
536 );
537 });
538 }
539
540 #[test]
541 fn test_to_pyobject() {
542 use super::Version;
543 use pyo3::prelude::*;
544
545 Python::with_gil(|py| {
546 let v = Version {
547 epoch: Some(1),
548 upstream_version: "1.0".to_string(),
549 debian_revision: Some("1".to_string()),
550 };
551 let v = v.into_pyobject(py).unwrap();
552 let expected: Version = "1:1.0-1".parse().unwrap();
553 assert_eq!(v.get_type().name().unwrap(), "Version");
554 assert_eq!(v.unbind().extract::<Version>(py).unwrap(), expected);
555 });
556 }
557
558 #[test]
559 fn test_from_pyobject_error() {
560 use super::Version;
561 use pyo3::prelude::*;
562 use std::ffi::CString;
563
564 Python::with_gil(|py| {
565 let string_obj = py
567 .eval(&CString::new("'not a version'").unwrap(), None, None)
568 .unwrap();
569 let result = string_obj.extract::<Version>();
570 assert!(result.is_err());
571 });
572 }
573}
574
575#[cfg(feature = "serde")]
576impl serde::Serialize for Version {
577 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
578 where
579 S: serde::Serializer,
580 {
581 serializer.serialize_str(&self.to_string())
582 }
583}
584
585#[cfg(feature = "serde")]
586impl<'de> serde::Deserialize<'de> for Version {
587 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
588 where
589 D: serde::Deserializer<'de>,
590 {
591 let concatenated: String = String::deserialize(deserializer)?;
592 concatenated.parse().map_err(serde::de::Error::custom)
593 }
594}
595
596pub trait AsVersion {
598 fn into_version(self) -> Result<Version, ParseError>;
600}
601
602impl AsVersion for &str {
603 fn into_version(self) -> Result<Version, ParseError> {
604 self.parse()
605 }
606}
607
608impl AsVersion for String {
609 fn into_version(self) -> Result<Version, ParseError> {
610 self.parse()
611 }
612}
613
614impl AsVersion for Version {
615 fn into_version(self) -> Result<Version, ParseError> {
616 Ok(self)
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::{version_cmp_part, ParseError, Version};
623 use std::cmp::Ordering;
624
625 #[test]
626 fn test_canonicalize() {
627 assert_eq!(
628 "1.0-1".parse::<Version>().unwrap().canonicalize(),
629 "1.0-1".parse::<Version>().unwrap()
630 );
631 assert_eq!(
632 "1.0-0".parse::<Version>().unwrap().canonicalize(),
633 "1.0".parse::<Version>().unwrap()
634 );
635 assert_eq!(
636 "0:1.0-2".parse::<Version>().unwrap().canonicalize(),
637 "1.0-2".parse::<Version>().unwrap()
638 );
639 assert_eq!(
640 "0001.0-0".parse::<Version>().unwrap().canonicalize(),
641 "1.0".parse::<Version>().unwrap()
642 );
643 assert_eq!(
644 "000.1".parse::<Version>().unwrap().canonicalize(),
645 "0.1".parse::<Version>().unwrap()
646 );
647 }
648
649 #[test]
650 fn test_explicit() {
651 assert_eq!(
652 (0, "1.0", "1"),
653 "1.0-1".parse::<Version>().unwrap().explicit()
654 );
655 assert_eq!(
656 (1, "1.0", "1"),
657 "1:1.0-1".parse::<Version>().unwrap().explicit()
658 );
659 assert_eq!(
660 (0, "1.0", "0"),
661 "1.0".parse::<Version>().unwrap().explicit()
662 );
663 assert_eq!(
664 (0, "1.0", "0"),
665 "1.0-0".parse::<Version>().unwrap().explicit()
666 );
667 assert_eq!(
668 (1, "1.0", "0"),
669 "1:1.0-0".parse::<Version>().unwrap().explicit()
670 );
671 assert_eq!(
672 (0, "000.1", "0"),
673 "000.1".parse::<Version>().unwrap().explicit()
674 );
675 }
676
677 macro_rules! assert_cmp(
678 ($a:expr, $b:expr, $cmp:tt) => {
679 assert_eq!($a.parse::<Version>().unwrap().cmp(&$b.parse::<Version>().unwrap()), std::cmp::Ordering::$cmp);
680 }
681 );
682
683 #[test]
684 fn test_version_cmp_part() {
685 assert_eq!(version_cmp_part("1.0", "1.0"), Ordering::Equal);
686 assert_eq!(version_cmp_part("0.1", "0.1"), Ordering::Equal);
687 assert_eq!(version_cmp_part("000.1", "0.1"), Ordering::Equal);
688 assert_eq!(version_cmp_part("1.0", "2.0"), Ordering::Less);
689 assert_eq!(version_cmp_part("1.0", "0.0"), Ordering::Greater);
690 assert_eq!(version_cmp_part("10.0", "2.0"), Ordering::Greater);
691 assert_eq!(version_cmp_part("1.0~rc1", "1.0"), Ordering::Less);
692 }
693
694 #[test]
695 fn test_cmp() {
696 assert_cmp!("1.0-1", "1.0-1", Equal);
697 assert_cmp!("1.0-1", "1.0-2", Less);
698 assert_cmp!("1.0-2", "1.0-1", Greater);
699 assert_cmp!("1.0-1", "1.0", Greater);
700 assert_cmp!("1.0", "1.0-1", Less);
701 assert_cmp!("2.50.0", "10.0.1", Less);
702
703 assert_cmp!("1:1.0-1", "1.0-1", Greater);
705 assert_cmp!("1.0-1", "1:1.0-1", Less);
706 assert_cmp!("1:1.0-1", "1:1.0-1", Equal);
707 assert_cmp!("1:1.0-1", "2:1.0-1", Less);
708 assert_cmp!("2:1.0-1", "1:1.0-1", Greater);
709
710 assert_cmp!("1.0~rc1-1", "1.0-1", Less);
712 assert_cmp!("1.0-1", "1.0~rc1-1", Greater);
713 assert_cmp!("1.0~rc1-1", "1.0~rc1-1", Equal);
714 assert_cmp!("1.0~rc1-1", "1.0~rc2-1", Less);
715 assert_cmp!("1.0~rc2-1", "1.0~rc1-1", Greater);
716
717 assert_cmp!("1.0a-1", "1.0-1", Greater);
719 assert_cmp!("1.0-1", "1.0a-1", Less);
720 assert_cmp!("1.0a-1", "1.0a-1", Equal);
721
722 assert_cmp!("23.13.9-7", "0.6.45-2", Greater);
724 }
725
726 #[test]
727 fn test_parse() {
728 assert_eq!(
729 Version {
730 epoch: None,
731 upstream_version: "1.0".to_string(),
732 debian_revision: Some("1".to_string())
733 },
734 "1.0-1".parse().unwrap()
735 );
736
737 assert_eq!(
738 Version {
739 epoch: None,
740 upstream_version: "1.0".to_string(),
741 debian_revision: None
742 },
743 "1.0".parse().unwrap()
744 );
745
746 assert_eq!(
747 Version {
748 epoch: Some(1),
749 upstream_version: "1.0".to_string(),
750 debian_revision: Some("1".to_string())
751 },
752 "1:1.0-1".parse().unwrap()
753 );
754 assert_eq!(
755 "1:;a".parse::<Version>().unwrap_err(),
756 ParseError("Invalid version string: 1:;a".to_string())
757 );
758 }
759
760 #[test]
761 fn test_parse_error_display() {
762 let error = ParseError("test error message".to_string());
763 assert_eq!(format!("{}", error), "test error message");
764 assert_eq!(error.to_string(), "test error message");
765 }
766
767 #[test]
768 fn test_to_string() {
769 assert_eq!(
770 "1.0-1",
771 Version {
772 epoch: None,
773 upstream_version: "1.0".to_string(),
774 debian_revision: Some("1".to_string())
775 }
776 .to_string()
777 );
778 assert_eq!(
779 "1.0",
780 Version {
781 epoch: None,
782 upstream_version: "1.0".to_string(),
783 debian_revision: None,
784 }
785 .to_string()
786 );
787 }
788
789 #[test]
790 fn test_eq() {
791 assert_eq!(
792 "1.0-1".parse::<Version>().unwrap(),
793 "1.0-1".parse::<Version>().unwrap()
794 );
795 }
796
797 #[test]
798 fn test_hash() {
799 use std::collections::hash_map::DefaultHasher;
800 use std::hash::{Hash, Hasher};
801
802 let mut hasher1 = DefaultHasher::new();
803 let mut hasher2 = DefaultHasher::new();
804 let mut hasher3 = DefaultHasher::new();
805
806 "1.0-1".parse::<Version>().unwrap().hash(&mut hasher1);
807 "1.0-1".parse::<Version>().unwrap().hash(&mut hasher2);
808 "0:1.0-1".parse::<Version>().unwrap().hash(&mut hasher3);
809
810 let hash1 = hasher1.finish();
811 let hash2 = hasher2.finish();
812 let hash3 = hasher3.finish();
813
814 assert_eq!(hash1, hash2);
815 assert_ne!(hash1, hash3);
816 }
817
818 #[test]
819 fn to_string() {
820 assert_eq!(
821 "1.0-1",
822 Version {
823 epoch: None,
824 upstream_version: "1.0".to_string(),
825 debian_revision: Some("1".to_string())
826 }
827 .to_string()
828 );
829 assert_eq!(
830 "1.0",
831 Version {
832 epoch: None,
833 upstream_version: "1.0".to_string(),
834 debian_revision: None,
835 }
836 .to_string()
837 );
838 assert_eq!(
839 "1:1.0",
840 Version {
841 epoch: Some(1),
842 upstream_version: "1.0".to_string(),
843 debian_revision: None,
844 }
845 .to_string()
846 );
847 }
848
849 #[test]
850 fn partial_eq() {
851 assert!("1.0-1"
852 .parse::<Version>()
853 .unwrap()
854 .eq(&"1.0-1".parse::<Version>().unwrap()));
855 }
856
857 #[test]
858 fn increment() {
859 let mut v = "1.0-1".parse::<Version>().unwrap();
860 v.increment_debian();
861
862 assert_eq!("1.0-2".parse::<Version>().unwrap(), v);
863
864 let mut v = "1.0".parse::<Version>().unwrap();
865 v.increment_debian();
866 assert_eq!("1.1".parse::<Version>().unwrap(), v);
867
868 let mut v = "1.0ubuntu1".parse::<Version>().unwrap();
869 v.increment_debian();
870 assert_eq!("1.0ubuntu2".parse::<Version>().unwrap(), v);
871
872 let mut v = "1.0-0ubuntu1".parse::<Version>().unwrap();
873 v.increment_debian();
874 assert_eq!("1.0-0ubuntu2".parse::<Version>().unwrap(), v);
875 }
876
877 #[test]
878 fn is_native() {
879 assert!(!"1.0-1".parse::<Version>().unwrap().is_native());
880 assert!("1.0".parse::<Version>().unwrap().is_native());
881 assert!(!"1.0-0".parse::<Version>().unwrap().is_native());
882 }
883
884 #[test]
885 fn test_is_binnmu() {
886 assert!("1.0+b1".parse::<Version>().unwrap().is_bin_nmu());
887 assert!("1.0-1+b1".parse::<Version>().unwrap().is_bin_nmu());
888 assert!(!"1.0-1".parse::<Version>().unwrap().is_bin_nmu());
889 assert!(!"1.0".parse::<Version>().unwrap().is_bin_nmu());
890 }
891
892 #[test]
893 fn test_bin_nmu_count() {
894 assert_eq!(
895 Some(1),
896 "1.0+b1".parse::<Version>().unwrap().bin_nmu_count()
897 );
898 assert_eq!(
899 Some(1),
900 "1.0-1+b1".parse::<Version>().unwrap().bin_nmu_count()
901 );
902 assert_eq!(None, "1.0-1".parse::<Version>().unwrap().bin_nmu_count());
903 assert_eq!(None, "1.0".parse::<Version>().unwrap().bin_nmu_count());
904 }
905
906 #[test]
907 fn test_increment_bin_nmu() {
908 assert_eq!(
909 "1.0+b2".parse::<Version>().unwrap(),
910 "1.0+b1".parse::<Version>().unwrap().increment_bin_nmu()
911 );
912 assert_eq!(
913 "1.0-1+b2".parse::<Version>().unwrap(),
914 "1.0-1+b1".parse::<Version>().unwrap().increment_bin_nmu()
915 );
916 assert_eq!(
917 "1.0+b1".parse::<Version>().unwrap(),
918 "1.0".parse::<Version>().unwrap().increment_bin_nmu()
919 );
920 assert_eq!(
921 "1.0-1+b1".parse::<Version>().unwrap(),
922 "1.0-1".parse::<Version>().unwrap().increment_bin_nmu()
923 );
924 }
925
926 #[test]
927 fn test_nmu_count() {
928 assert_eq!(Some(1), "1.0+nmu1".parse::<Version>().unwrap().nmu_count());
929 assert_eq!(
930 Some(1),
931 "1.0-1+nmu1".parse::<Version>().unwrap().nmu_count()
932 );
933 assert_eq!(None, "1.0-1".parse::<Version>().unwrap().nmu_count());
934 assert_eq!(None, "1.0".parse::<Version>().unwrap().nmu_count());
935 }
936
937 #[test]
938 fn test_is_nmu() {
939 assert!("1.0+nmu1".parse::<Version>().unwrap().is_nmu());
940 assert!("1.0-1+nmu1".parse::<Version>().unwrap().is_nmu());
941 assert!(!"1.0-1".parse::<Version>().unwrap().is_nmu());
942 assert!(!"1.0".parse::<Version>().unwrap().is_nmu());
943 }
944
945 #[test]
946 fn test_comparing_very_long_versions() {
947 let a = "1:11.1.0~++20210314110124+1fdec59bffc1-1~exp1~20210314220751.162";
949 let b = "1:11.1.0~++20211011013104+1fdec59bffc1-1~exp1~20211011133507.6";
950 assert_cmp!(a, b, Less);
951 }
952
953 #[test]
954 fn test_drop_leading_zeroes() {
955 use super::drop_leading_zeroes;
956
957 assert_eq!(drop_leading_zeroes("1.0"), "1.0");
959 assert_eq!(drop_leading_zeroes("001.0"), "1.0");
960 assert_eq!(drop_leading_zeroes("000.1"), "0.1");
961
962 assert_eq!(drop_leading_zeroes("0"), "0");
964 assert_eq!(drop_leading_zeroes("00"), "0");
965 assert_eq!(drop_leading_zeroes("0a"), "0a");
966 assert_eq!(drop_leading_zeroes("01a"), "1a");
967
968 assert_eq!(drop_leading_zeroes("a"), "a");
970
971 assert_eq!(drop_leading_zeroes(""), "");
973 }
974
975 #[test]
976 fn test_version_cmp_part_edge_cases() {
977 assert_eq!(version_cmp_part("", ""), Ordering::Equal);
981 assert_eq!(version_cmp_part("", "1"), Ordering::Less);
982 assert_eq!(version_cmp_part("1", ""), Ordering::Greater);
983
984 assert_eq!(version_cmp_part("1", "1"), Ordering::Equal);
986 assert_eq!(version_cmp_part("01", "1"), Ordering::Equal);
987
988 let long_a = "123456789012345678901234567890";
990 let long_b = "123456789012345678901234567891";
991 assert_eq!(version_cmp_part(long_a, long_b), Ordering::Less);
992
993 assert_eq!(version_cmp_part("1a2", "1a3"), Ordering::Less);
995 assert_eq!(version_cmp_part("1a2", "1b1"), Ordering::Less);
996
997 assert_eq!(version_cmp_part("1~", "1"), Ordering::Less);
999 assert_eq!(version_cmp_part("~1", "1"), Ordering::Less);
1000 }
1001
1002 #[test]
1003 fn test_canonicalize_edge_cases() {
1004 let v1 = Version {
1008 epoch: Some(0),
1009 upstream_version: "1.0".to_string(),
1010 debian_revision: None,
1011 };
1012 let canonical = v1.canonicalize();
1013 assert_eq!(canonical.epoch, None);
1014
1015 let v2 = Version {
1017 epoch: None,
1018 upstream_version: "1.0".to_string(),
1019 debian_revision: Some("000".to_string()),
1020 };
1021 let canonical2 = v2.canonicalize();
1022 assert_eq!(canonical2.debian_revision, None);
1023
1024 let v3 = Version {
1026 epoch: None,
1027 upstream_version: "1.0".to_string(),
1028 debian_revision: Some("001".to_string()),
1029 };
1030 let canonical3 = v3.canonicalize();
1031 assert_eq!(canonical3.debian_revision, Some("1".to_string()));
1032
1033 let v4 = Version {
1035 epoch: None,
1036 upstream_version: "001.0".to_string(),
1037 debian_revision: None,
1038 };
1039 let canonical4 = v4.canonicalize();
1040 assert_eq!(canonical4.upstream_version, "1.0");
1041
1042 let v5 = Version {
1044 epoch: Some(1),
1045 upstream_version: "1.0".to_string(),
1046 debian_revision: Some("1".to_string()),
1047 };
1048 let canonical5 = v5.canonicalize();
1049 assert_eq!(canonical5.upstream_version, "1.0");
1050 assert_eq!(canonical5.debian_revision, Some("1".to_string()));
1051 }
1052
1053 #[test]
1054 fn test_partial_eq_false() {
1055 assert!("1.0-1"
1057 .parse::<Version>()
1058 .unwrap()
1059 .ne(&"1.0-2".parse::<Version>().unwrap()));
1060
1061 assert!("1.0-1"
1062 .parse::<Version>()
1063 .unwrap()
1064 .ne(&"2.0-1".parse::<Version>().unwrap()));
1065
1066 assert!("1:1.0-1"
1067 .parse::<Version>()
1068 .unwrap()
1069 .ne(&"2:1.0-1".parse::<Version>().unwrap()));
1070 }
1071
1072 #[test]
1073 fn test_non_digit_cmp_edge_cases() {
1074 use super::non_digit_cmp;
1075
1076 assert_eq!(non_digit_cmp("~", "a"), Ordering::Less);
1078 assert_eq!(non_digit_cmp("~", "A"), Ordering::Less);
1079 assert_eq!(non_digit_cmp("~", "!"), Ordering::Less);
1080
1081 assert_eq!(non_digit_cmp("!", "@"), Ordering::Less);
1083 assert_eq!(non_digit_cmp("@", "A"), Ordering::Greater);
1084 assert_eq!(non_digit_cmp("Z", "["), Ordering::Less);
1085
1086 assert_eq!(non_digit_cmp("", ""), Ordering::Equal);
1088 assert_eq!(non_digit_cmp("", "a"), Ordering::Less);
1089 assert_eq!(non_digit_cmp("a", ""), Ordering::Greater);
1090 }
1091}