| 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 | |