1 | //! Version module, which provides the `Version` struct as parsed version representation. |
2 | //! |
3 | //! Version numbers in the form of a string are parsed to a `Version` first, before any comparison |
4 | //! is made. This struct provides many methods and features for easy comparison, probing and other |
5 | //! things. |
6 | |
7 | use std::borrow::Borrow; |
8 | use std::cmp::Ordering; |
9 | use std::fmt; |
10 | use std::iter::Peekable; |
11 | use std::slice::Iter; |
12 | |
13 | use crate::{Cmp, Manifest, Part}; |
14 | |
15 | /// Version struct, wrapping a string, providing useful comparison functions. |
16 | /// |
17 | /// A version in string format can be parsed using methods like `Version::from("1.2.3");`, |
18 | /// returning a `Result` with the parse result. |
19 | /// |
20 | /// The original version string can be accessed using `version.as_str()`. A `Version` that isn't |
21 | /// derrived from a version string returns a generated string. |
22 | /// |
23 | /// The struct provides many methods for easy comparison and probing. |
24 | /// |
25 | /// # Examples |
26 | /// |
27 | /// ``` |
28 | /// use version_compare::{Version}; |
29 | /// |
30 | /// let ver = Version::from("1.2.3" ).unwrap(); |
31 | /// ``` |
32 | #[derive (Clone, Eq)] |
33 | pub struct Version<'a> { |
34 | version: &'a str, |
35 | parts: Vec<Part<'a>>, |
36 | manifest: Option<&'a Manifest>, |
37 | } |
38 | |
39 | impl<'a> Version<'a> { |
40 | /// Create a `Version` instance from a version string. |
41 | /// |
42 | /// The version string should be passed to the `version` parameter. |
43 | /// |
44 | /// # Examples |
45 | /// |
46 | /// ``` |
47 | /// use version_compare::{Cmp, Version}; |
48 | /// |
49 | /// let a = Version::from("1.2.3" ).unwrap(); |
50 | /// let b = Version::from("1.3.0" ).unwrap(); |
51 | /// |
52 | /// assert_eq!(a.compare(b), Cmp::Lt); |
53 | /// ``` |
54 | pub fn from(version: &'a str) -> Option<Self> { |
55 | Some(Version { |
56 | version, |
57 | parts: split_version_str(version, None)?, |
58 | manifest: None, |
59 | }) |
60 | } |
61 | |
62 | /// Create a `Version` instance from already existing parts |
63 | /// |
64 | /// |
65 | /// # Examples |
66 | /// |
67 | /// ``` |
68 | /// use version_compare::{Cmp, Version, Part}; |
69 | /// |
70 | /// let ver = Version::from_parts("1.0" , vec![Part::Number(1), Part::Number(0)]); |
71 | /// ``` |
72 | pub fn from_parts(version: &'a str, parts: Vec<Part<'a>>) -> Self { |
73 | Version { |
74 | version, |
75 | parts, |
76 | manifest: None, |
77 | } |
78 | } |
79 | |
80 | /// Create a `Version` instance from a version string with the given `manifest`. |
81 | /// |
82 | /// The version string should be passed to the `version` parameter. |
83 | /// |
84 | /// # Examples |
85 | /// |
86 | /// ``` |
87 | /// use version_compare::{Cmp, Version, Manifest}; |
88 | /// |
89 | /// let manifest = Manifest::default(); |
90 | /// let ver = Version::from_manifest("1.2.3" , &manifest).unwrap(); |
91 | /// |
92 | /// assert_eq!(ver.compare(Version::from("1.2.3" ).unwrap()), Cmp::Eq); |
93 | /// ``` |
94 | pub fn from_manifest(version: &'a str, manifest: &'a Manifest) -> Option<Self> { |
95 | Some(Version { |
96 | version, |
97 | parts: split_version_str(version, Some(manifest))?, |
98 | manifest: Some(manifest), |
99 | }) |
100 | } |
101 | |
102 | /// Get the version manifest, if available. |
103 | /// |
104 | /// # Examples |
105 | /// |
106 | /// ``` |
107 | /// use version_compare::Version; |
108 | /// |
109 | /// let version = Version::from("1.2.3" ).unwrap(); |
110 | /// |
111 | /// if version.has_manifest() { |
112 | /// println!( |
113 | /// "Maximum version part depth is {} for this version" , |
114 | /// version.manifest().unwrap().max_depth.unwrap_or(0), |
115 | /// ); |
116 | /// } else { |
117 | /// println!("Version has no manifest" ); |
118 | /// } |
119 | /// ``` |
120 | pub fn manifest(&self) -> Option<&Manifest> { |
121 | self.manifest |
122 | } |
123 | |
124 | /// Check whether this version has a manifest. |
125 | /// |
126 | /// # Examples |
127 | /// |
128 | /// ``` |
129 | /// use version_compare::Version; |
130 | /// |
131 | /// let version = Version::from("1.2.3" ).unwrap(); |
132 | /// |
133 | /// if version.has_manifest() { |
134 | /// println!("This version does have a manifest" ); |
135 | /// } else { |
136 | /// println!("This version does not have a manifest" ); |
137 | /// } |
138 | /// ``` |
139 | pub fn has_manifest(&self) -> bool { |
140 | self.manifest().is_some() |
141 | } |
142 | |
143 | /// Set the version manifest. |
144 | /// |
145 | /// # Examples |
146 | /// |
147 | /// ``` |
148 | /// use version_compare::{Version, Manifest}; |
149 | /// |
150 | /// let manifest = Manifest::default(); |
151 | /// let mut version = Version::from("1.2.3" ).unwrap(); |
152 | /// |
153 | /// version.set_manifest(Some(&manifest)); |
154 | /// ``` |
155 | pub fn set_manifest(&mut self, manifest: Option<&'a Manifest>) { |
156 | self.manifest = manifest; |
157 | |
158 | // TODO: Re-parse the version string, because the manifest might have changed. |
159 | } |
160 | |
161 | /// Get the original version string. |
162 | /// |
163 | /// # Examples |
164 | /// |
165 | /// ``` |
166 | /// use version_compare::Version; |
167 | /// |
168 | /// let ver = Version::from("1.2.3" ).unwrap(); |
169 | /// |
170 | /// assert_eq!(ver.as_str(), "1.2.3" ); |
171 | /// ``` |
172 | pub fn as_str(&self) -> &str { |
173 | self.version |
174 | } |
175 | |
176 | /// Get a specific version part by it's `index`. |
177 | /// An error is returned if the given index is out of bound. |
178 | /// |
179 | /// # Examples |
180 | /// |
181 | /// ``` |
182 | /// use version_compare::{Version, Part}; |
183 | /// |
184 | /// let ver = Version::from("1.2.3" ).unwrap(); |
185 | /// |
186 | /// assert_eq!(ver.part(0), Ok(Part::Number(1))); |
187 | /// assert_eq!(ver.part(1), Ok(Part::Number(2))); |
188 | /// assert_eq!(ver.part(2), Ok(Part::Number(3))); |
189 | /// ``` |
190 | #[allow (clippy::result_unit_err)] |
191 | pub fn part(&self, index: usize) -> Result<Part<'a>, ()> { |
192 | // Make sure the index is in-bound |
193 | if index >= self.parts.len() { |
194 | return Err(()); |
195 | } |
196 | |
197 | Ok(self.parts[index]) |
198 | } |
199 | |
200 | /// Get a vector of all version parts. |
201 | /// |
202 | /// # Examples |
203 | /// |
204 | /// ``` |
205 | /// use version_compare::{Version, Part}; |
206 | /// |
207 | /// let ver = Version::from("1.2.3" ).unwrap(); |
208 | /// |
209 | /// assert_eq!(ver.parts(), [ |
210 | /// Part::Number(1), |
211 | /// Part::Number(2), |
212 | /// Part::Number(3) |
213 | /// ]); |
214 | /// ``` |
215 | pub fn parts(&self) -> &[Part<'a>] { |
216 | self.parts.as_slice() |
217 | } |
218 | |
219 | /// Compare this version to the given `other` version using the default `Manifest`. |
220 | /// |
221 | /// This method returns one of the following comparison operators: |
222 | /// |
223 | /// * `Lt` |
224 | /// * `Eq` |
225 | /// * `Gt` |
226 | /// |
227 | /// Other comparison operators can be used when comparing, but aren't returned by this method. |
228 | /// |
229 | /// # Examples: |
230 | /// |
231 | /// ``` |
232 | /// use version_compare::{Cmp, Version}; |
233 | /// |
234 | /// let a = Version::from("1.2" ).unwrap(); |
235 | /// let b = Version::from("1.3.2" ).unwrap(); |
236 | /// |
237 | /// assert_eq!(a.compare(&b), Cmp::Lt); |
238 | /// assert_eq!(b.compare(&a), Cmp::Gt); |
239 | /// assert_eq!(a.compare(&a), Cmp::Eq); |
240 | /// ``` |
241 | pub fn compare<V>(&self, other: V) -> Cmp |
242 | where |
243 | V: Borrow<Version<'a>>, |
244 | { |
245 | compare_iter( |
246 | self.parts.iter().peekable(), |
247 | other.borrow().parts.iter().peekable(), |
248 | self.manifest, |
249 | ) |
250 | } |
251 | |
252 | /// Compare this version to the given `other` version, |
253 | /// and check whether the given comparison operator is valid using the default `Manifest`. |
254 | /// |
255 | /// All comparison operators can be used. |
256 | /// |
257 | /// # Examples: |
258 | /// |
259 | /// ``` |
260 | /// use version_compare::{Cmp, Version}; |
261 | /// |
262 | /// let a = Version::from("1.2" ).unwrap(); |
263 | /// let b = Version::from("1.3.2" ).unwrap(); |
264 | /// |
265 | /// assert!(a.compare_to(&b, Cmp::Lt)); |
266 | /// assert!(a.compare_to(&b, Cmp::Le)); |
267 | /// assert!(a.compare_to(&a, Cmp::Eq)); |
268 | /// assert!(a.compare_to(&a, Cmp::Le)); |
269 | /// ``` |
270 | pub fn compare_to<V>(&self, other: V, operator: Cmp) -> bool |
271 | where |
272 | V: Borrow<Version<'a>>, |
273 | { |
274 | match self.compare(other) { |
275 | Cmp::Eq => matches!(operator, Cmp::Eq | Cmp::Le | Cmp::Ge), |
276 | Cmp::Lt => matches!(operator, Cmp::Ne | Cmp::Lt | Cmp::Le), |
277 | Cmp::Gt => matches!(operator, Cmp::Ne | Cmp::Gt | Cmp::Ge), |
278 | _ => unreachable!(), |
279 | } |
280 | } |
281 | } |
282 | |
283 | impl<'a> fmt::Display for Version<'a> { |
284 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
285 | write!(f, " {}" , self.version) |
286 | } |
287 | } |
288 | |
289 | // Show just the version component parts as debug output |
290 | impl<'a> fmt::Debug for Version<'a> { |
291 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
292 | if f.alternate() { |
293 | write!(f, " {:#?}" , self.parts) |
294 | } else { |
295 | write!(f, " {:?}" , self.parts) |
296 | } |
297 | } |
298 | } |
299 | |
300 | /// Implement the partial ordering trait for the version struct, to easily allow version comparison. |
301 | impl<'a> PartialOrd for Version<'a> { |
302 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
303 | Some(self.compare(other).ord().unwrap()) |
304 | } |
305 | } |
306 | |
307 | /// Implement the partial equality trait for the version struct, to easily allow version comparison. |
308 | impl<'a> PartialEq for Version<'a> { |
309 | fn eq(&self, other: &Self) -> bool { |
310 | self.compare_to(other, operator:Cmp::Eq) |
311 | } |
312 | } |
313 | |
314 | /// Split the given version string, in it's version parts. |
315 | fn split_version_str<'a>( |
316 | version: &'a str, |
317 | manifest: Option<&'a Manifest>, |
318 | ) -> Option<Vec<Part<'a>>> { |
319 | // Split the version string, and create a vector to put the parts in |
320 | let split = version.split(|c| !char::is_alphanumeric(c)); |
321 | let mut parts = Vec::new(); |
322 | |
323 | // Get the manifest to follow |
324 | let mut used_manifest = &Manifest::default(); |
325 | if let Some(m) = manifest { |
326 | used_manifest = m; |
327 | } |
328 | |
329 | // Loop over the parts, and parse them |
330 | for part in split { |
331 | // We may not go over the maximum depth |
332 | if used_manifest.max_depth.is_some() && parts.len() >= used_manifest.max_depth.unwrap_or(0) |
333 | { |
334 | break; |
335 | } |
336 | |
337 | // Skip empty parts |
338 | if part.is_empty() { |
339 | continue; |
340 | } |
341 | |
342 | // Try to parse the value as an number |
343 | match part.parse::<i32>() { |
344 | Ok(number) => { |
345 | // For GNU ordering we parse numbers with leading zero as string |
346 | if number > 0 |
347 | && part.starts_with('0' ) |
348 | && manifest.map(|m| m.gnu_ordering).unwrap_or(false) |
349 | { |
350 | parts.push(Part::Text(part)); |
351 | continue; |
352 | } |
353 | |
354 | // Push the number part to the vector |
355 | parts.push(Part::Number(number)); |
356 | } |
357 | Err(_) => { |
358 | // Ignore text parts if specified |
359 | if used_manifest.ignore_text { |
360 | continue; |
361 | } |
362 | |
363 | // Numbers suffixed by text should be split into a number and text as well, |
364 | // if the number overflows, handle it as text |
365 | let split_at = part |
366 | .char_indices() |
367 | .take(part.len() - 1) |
368 | .take_while(|(_, c)| c.is_ascii_digit()) |
369 | .map(|(i, c)| (i, c, part.chars().nth(i + 1).unwrap())) |
370 | .filter(|(_, _, b)| b.is_alphabetic()) |
371 | .map(|(i, _, _)| i) |
372 | .next(); |
373 | if let Some(at) = split_at { |
374 | if let Ok(n) = part[..=at].parse() { |
375 | parts.push(Part::Number(n)); |
376 | parts.push(Part::Text(&part[at + 1..])); |
377 | } else { |
378 | parts.push(Part::Text(part)); |
379 | } |
380 | continue; |
381 | } |
382 | |
383 | // Push the text part to the vector |
384 | parts.push(Part::Text(part)) |
385 | } |
386 | } |
387 | } |
388 | |
389 | // The version must contain a number part if any part was parsed |
390 | if !parts.is_empty() && !parts.iter().any(|p| matches!(p, Part::Number(_))) { |
391 | return None; |
392 | } |
393 | |
394 | // Return the list of parts |
395 | Some(parts) |
396 | } |
397 | |
398 | /// Compare two version numbers based on the iterators of their version parts. |
399 | /// |
400 | /// This method returns one of the following comparison operators: |
401 | /// |
402 | /// * `Lt` |
403 | /// * `Eq` |
404 | /// * `Gt` |
405 | /// |
406 | /// Other comparison operators can be used when comparing, but aren't returned by this method. |
407 | fn compare_iter<'a>( |
408 | mut iter: Peekable<Iter<Part<'a>>>, |
409 | mut other_iter: Peekable<Iter<Part<'a>>>, |
410 | manifest: Option<&Manifest>, |
411 | ) -> Cmp { |
412 | // Iterate over the iterator, without consuming it |
413 | for part in &mut iter { |
414 | match (part, other_iter.next()) { |
415 | // If we only have a zero on the lhs, continue |
416 | (Part::Number(lhs), None) if lhs == &0 => { |
417 | continue; |
418 | } |
419 | |
420 | // If we only have text on the lhs, it is less |
421 | (Part::Text(_), None) => return Cmp::Lt, |
422 | |
423 | // If we have anything else on the lhs, it is greater |
424 | (_, None) => return Cmp::Gt, |
425 | |
426 | // Compare numbers |
427 | (Part::Number(lhs), Some(Part::Number(rhs))) => match Cmp::from(lhs.cmp(rhs)) { |
428 | Cmp::Eq => {} |
429 | cmp => return cmp, |
430 | }, |
431 | |
432 | // Compare text |
433 | (Part::Text(lhs), Some(Part::Text(rhs))) => { |
434 | // Normalize case and compare text: "RC1" will be less than "RC2" |
435 | match Cmp::from(lhs.to_lowercase().cmp(&rhs.to_lowercase())) { |
436 | Cmp::Eq => {} |
437 | cmp => return cmp, |
438 | } |
439 | } |
440 | |
441 | // For GNU ordering we have a special number/text comparison |
442 | (lhs @ Part::Number(_), Some(rhs @ Part::Text(_))) |
443 | | (lhs @ Part::Text(_), Some(rhs @ Part::Number(_))) |
444 | if manifest.map(|m| m.gnu_ordering).unwrap_or(false) => |
445 | { |
446 | match compare_gnu_number_text(lhs, rhs) { |
447 | Some(Cmp::Eq) | None => {} |
448 | Some(cmp) => return cmp, |
449 | } |
450 | } |
451 | |
452 | // TODO: decide what to do for other type combinations |
453 | _ => {} |
454 | } |
455 | } |
456 | |
457 | // Check whether we should iterate over the other iterator, if it has any items left |
458 | match other_iter.peek() { |
459 | // Compare based on the other iterator |
460 | Some(_) => compare_iter(other_iter, iter, manifest).flip(), |
461 | |
462 | // Nothing more to iterate over, the versions should be equal |
463 | None => Cmp::Eq, |
464 | } |
465 | } |
466 | |
467 | /// Special logic for comparing a number and text with GNU ordering. |
468 | /// |
469 | /// Numbers should be ordered like this: |
470 | /// |
471 | /// - 3 |
472 | /// - 04 |
473 | /// - 4 |
474 | // TODO: this is not efficient, find a better method |
475 | fn compare_gnu_number_text(lhs: &Part, rhs: &Part) -> Option<Cmp> { |
476 | // Both values must be parsable as numbers |
477 | let lhs_num = match lhs { |
478 | Part::Number(n) => *n, |
479 | Part::Text(n) => n.parse().ok()?, |
480 | }; |
481 | let rhs_num = match rhs { |
482 | Part::Number(n) => *n, |
483 | Part::Text(n) => n.parse().ok()?, |
484 | }; |
485 | |
486 | // Return ordering if numeric values are different |
487 | match lhs_num.cmp(&rhs_num).into() { |
488 | Cmp::Eq => {} |
489 | cmp => return Some(cmp), |
490 | } |
491 | |
492 | // Either value must have a leading zero |
493 | if !matches!(lhs, Part::Text(t) if t.starts_with('0' )) |
494 | && !matches!(rhs, Part::Text(t) if t.starts_with('0' )) |
495 | { |
496 | return None; |
497 | } |
498 | |
499 | let lhs = match lhs { |
500 | Part::Number(n) => format!(" {}" , n), |
501 | Part::Text(n) => n.to_string(), |
502 | }; |
503 | let rhs = match rhs { |
504 | Part::Number(n) => format!(" {}" , n), |
505 | Part::Text(n) => n.to_string(), |
506 | }; |
507 | |
508 | Some(lhs.cmp(&rhs).into()) |
509 | } |
510 | |
511 | #[cfg_attr (tarpaulin, skip)] |
512 | #[cfg (test)] |
513 | mod tests { |
514 | use std::cmp; |
515 | |
516 | use crate::test::{COMBIS, VERSIONS, VERSIONS_ERROR}; |
517 | use crate::{Cmp, Manifest, Part}; |
518 | |
519 | use super::Version; |
520 | |
521 | #[test ] |
522 | // TODO: This doesn't really test whether this method fully works |
523 | fn from() { |
524 | // Test whether parsing works for each test version |
525 | for version in VERSIONS { |
526 | assert!(Version::from(version.0).is_some()); |
527 | } |
528 | |
529 | // Test whether parsing works for each test invalid version |
530 | for version in VERSIONS_ERROR { |
531 | assert!(Version::from(version.0).is_none()); |
532 | } |
533 | } |
534 | |
535 | #[test ] |
536 | // TODO: This doesn't really test whether this method fully works |
537 | fn from_manifest() { |
538 | // Create a manifest |
539 | let manifest = Manifest::default(); |
540 | |
541 | // Test whether parsing works for each test version |
542 | for version in VERSIONS { |
543 | assert_eq!( |
544 | Version::from_manifest(version.0, &manifest) |
545 | .unwrap() |
546 | .manifest, |
547 | Some(&manifest) |
548 | ); |
549 | } |
550 | |
551 | // Test whether parsing works for each test invalid version |
552 | for version in VERSIONS_ERROR { |
553 | assert!(Version::from_manifest(version.0, &manifest).is_none()); |
554 | } |
555 | } |
556 | |
557 | #[test ] |
558 | fn manifest() { |
559 | let manifest = Manifest::default(); |
560 | let mut version = Version::from("1.2.3" ).unwrap(); |
561 | |
562 | version.manifest = Some(&manifest); |
563 | assert_eq!(version.manifest(), Some(&manifest)); |
564 | |
565 | version.manifest = None; |
566 | assert_eq!(version.manifest(), None); |
567 | } |
568 | |
569 | #[test ] |
570 | fn has_manifest() { |
571 | let manifest = Manifest::default(); |
572 | let mut version = Version::from("1.2.3" ).unwrap(); |
573 | |
574 | version.manifest = Some(&manifest); |
575 | assert!(version.has_manifest()); |
576 | |
577 | version.manifest = None; |
578 | assert!(!version.has_manifest()); |
579 | } |
580 | |
581 | #[test ] |
582 | fn set_manifest() { |
583 | let manifest = Manifest::default(); |
584 | let mut version = Version::from("1.2.3" ).unwrap(); |
585 | |
586 | version.set_manifest(Some(&manifest)); |
587 | assert_eq!(version.manifest, Some(&manifest)); |
588 | |
589 | version.set_manifest(None); |
590 | assert_eq!(version.manifest, None); |
591 | } |
592 | |
593 | #[test ] |
594 | fn as_str() { |
595 | // Test for each test version |
596 | for version in VERSIONS { |
597 | // The input version string must be the same as the returned string |
598 | assert_eq!(Version::from(version.0).unwrap().as_str(), version.0); |
599 | } |
600 | } |
601 | |
602 | #[test ] |
603 | fn part() { |
604 | // Test for each test version |
605 | for version in VERSIONS { |
606 | // Create a version object |
607 | let ver = Version::from(version.0).unwrap(); |
608 | |
609 | // Loop through each part |
610 | for i in 0..version.1 { |
611 | assert_eq!(ver.part(i), Ok(ver.parts[i])); |
612 | } |
613 | |
614 | // A value outside the range must return an error |
615 | assert!(ver.part(version.1).is_err()); |
616 | } |
617 | } |
618 | |
619 | #[test ] |
620 | fn parts() { |
621 | // Test for each test version |
622 | for version in VERSIONS { |
623 | // The number of parts must match |
624 | assert_eq!(Version::from(version.0).unwrap().parts().len(), version.1); |
625 | } |
626 | } |
627 | |
628 | #[test ] |
629 | fn parts_max_depth() { |
630 | // Create a manifest |
631 | let mut manifest = Manifest::default(); |
632 | |
633 | // Loop through a range of numbers |
634 | for depth in 0..5 { |
635 | // Set the maximum depth |
636 | manifest.max_depth = if depth > 0 { Some(depth) } else { None }; |
637 | |
638 | // Test for each test version with the manifest |
639 | for version in VERSIONS { |
640 | // Create a version object, and count it's parts |
641 | let ver = Version::from_manifest(version.0, &manifest); |
642 | |
643 | // Some versions might be none, because not all of the start with a number when the |
644 | // maximum depth is 1. A version string with only text isn't allowed, |
645 | // resulting in none. |
646 | if ver.is_none() { |
647 | continue; |
648 | } |
649 | |
650 | // Get the part count |
651 | let count = ver.unwrap().parts().len(); |
652 | |
653 | // The number of parts must match |
654 | if depth == 0 { |
655 | assert_eq!(count, version.1); |
656 | } else { |
657 | assert_eq!(count, cmp::min(version.1, depth)); |
658 | } |
659 | } |
660 | } |
661 | } |
662 | |
663 | #[test ] |
664 | fn parts_ignore_text() { |
665 | // Create a manifest |
666 | let mut manifest = Manifest::default(); |
667 | |
668 | // Try this for true and false |
669 | for ignore in &[true, false] { |
670 | // Set to ignore text |
671 | manifest.ignore_text = *ignore; |
672 | |
673 | // Keep track whether any version passed with text |
674 | let mut had_text = false; |
675 | |
676 | // Test each test version |
677 | for version in VERSIONS { |
678 | // Create a version instance, and get it's parts |
679 | let ver = Version::from_manifest(version.0, &manifest).unwrap(); |
680 | |
681 | // Loop through all version parts |
682 | for part in ver.parts() { |
683 | if let Part::Text(_) = part { |
684 | // Set the flag |
685 | had_text = true; |
686 | |
687 | // Break the loop if we already reached text when not ignored |
688 | if !ignore { |
689 | break; |
690 | } |
691 | } |
692 | } |
693 | } |
694 | |
695 | // Assert had text |
696 | assert_eq!(had_text, !ignore); |
697 | } |
698 | } |
699 | |
700 | #[test ] |
701 | fn compare() { |
702 | // Compare each version in the version set |
703 | for entry in COMBIS { |
704 | // Get both versions |
705 | let (a, b) = entry.versions(); |
706 | |
707 | // Compare them |
708 | assert_eq!( |
709 | a.compare(b), |
710 | entry.2.clone(), |
711 | "Testing that {} is {} {}" , |
712 | entry.0, |
713 | entry.2.sign(), |
714 | entry.1, |
715 | ); |
716 | } |
717 | } |
718 | |
719 | #[test ] |
720 | fn compare_to() { |
721 | // Compare each version in the version set |
722 | for entry in COMBIS.iter().filter(|c| c.3.is_none()) { |
723 | // Get both versions |
724 | let (a, b) = entry.versions(); |
725 | |
726 | // Test normally and inverse |
727 | assert!(a.compare_to(&b, entry.2)); |
728 | assert!(!a.compare_to(b, entry.2.invert())); |
729 | } |
730 | |
731 | // Assert an exceptional case, compare to not equal |
732 | assert!(Version::from("1.2" ) |
733 | .unwrap() |
734 | .compare_to(Version::from("1.2.3" ).unwrap(), Cmp::Ne,)); |
735 | } |
736 | |
737 | #[test ] |
738 | fn display() { |
739 | assert_eq!(format!(" {}" , Version::from("1.2.3" ).unwrap()), "1.2.3" ); |
740 | } |
741 | |
742 | #[test ] |
743 | fn debug() { |
744 | assert_eq!( |
745 | format!(" {:?}" , Version::from("1.2.3" ).unwrap()), |
746 | "[Number(1), Number(2), Number(3)]" , |
747 | ); |
748 | assert_eq!( |
749 | format!(" {:#?}" , Version::from("1.2.3" ).unwrap()), |
750 | "[ \n Number( \n 1, \n ), \n Number( \n 2, \n ), \n Number( \n 3, \n ), \n]" , |
751 | ); |
752 | } |
753 | |
754 | #[test ] |
755 | fn partial_cmp() { |
756 | // Compare each version in the version set |
757 | for entry in COMBIS { |
758 | // Get both versions |
759 | let (a, b) = entry.versions(); |
760 | |
761 | // Compare and assert |
762 | match entry.2 { |
763 | Cmp::Eq => assert!(a == b), |
764 | Cmp::Lt => assert!(a < b), |
765 | Cmp::Gt => assert!(a > b), |
766 | _ => {} |
767 | } |
768 | } |
769 | } |
770 | |
771 | #[test ] |
772 | fn partial_eq() { |
773 | // Compare each version in the version set |
774 | for entry in COMBIS { |
775 | // Skip entries that are less or equal, or greater or equal |
776 | match entry.2 { |
777 | Cmp::Le | Cmp::Ge => continue, |
778 | _ => {} |
779 | } |
780 | |
781 | // Get both versions |
782 | let (a, b) = entry.versions(); |
783 | |
784 | // Determine what the result should be |
785 | let result = matches!(entry.2, Cmp::Eq); |
786 | |
787 | // Test |
788 | assert_eq!(a == b, result); |
789 | } |
790 | |
791 | // Assert an exceptional case, compare to not equal |
792 | assert!(Version::from("1.2" ).unwrap() != Version::from("1.2.3" ).unwrap()); |
793 | } |
794 | } |
795 | |