alpm_types/version/requirement.rs
1//! Version requirement declarations and comparisons based on them.
2
3use std::{
4 cmp::Ordering,
5 fmt::{Display, Formatter},
6 str::FromStr,
7};
8
9use alpm_parsers::{
10 iter_str_context,
11 traits::{AlpmParser, ParserUntil},
12};
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15use strum::VariantNames;
16use winnow::{
17 ModalResult,
18 Parser,
19 combinator::{alt, fail, opt, peek, seq},
20 error::{ContextError, ErrMode, StrContext, StrContextValue},
21 token::one_of,
22};
23
24use crate::{Error, Version};
25
26/// A version requirement, e.g. for a dependency package.
27///
28/// It consists of a target version and a comparison function. A version requirement of `>=1.5` has
29/// a target version of `1.5` and a comparison function of [`VersionComparison::GreaterOrEqual`].
30/// See [alpm-comparison] for details on the format.
31///
32/// ## Examples
33///
34/// ```
35/// use std::str::FromStr;
36///
37/// use alpm_types::{Version, VersionComparison, VersionRequirement};
38///
39/// # fn main() -> Result<(), alpm_types::Error> {
40/// let requirement = VersionRequirement::from_str(">=1.5")?;
41///
42/// assert_eq!(requirement.comparison, VersionComparison::GreaterOrEqual);
43/// assert_eq!(requirement.version, Version::from_str("1.5")?);
44/// # Ok(())
45/// # }
46/// ```
47///
48/// [alpm-comparison]: https://alpm.archlinux.page/specifications/alpm-comparison.7.html
49#[derive(Clone, Debug, Eq, PartialEq)]
50#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
51pub struct VersionRequirement {
52 /// Version comparison function
53 pub comparison: VersionComparison,
54 /// Target version
55 pub version: Version,
56}
57
58impl VersionRequirement {
59 /// Create a new `VersionRequirement`
60 pub fn new(comparison: VersionComparison, version: Version) -> Self {
61 VersionRequirement {
62 comparison,
63 version,
64 }
65 }
66
67 /// Returns `true` if the requirement is satisfied by the given package version.
68 ///
69 /// ## Examples
70 ///
71 /// ```
72 /// use std::str::FromStr;
73 ///
74 /// use alpm_types::{Version, VersionRequirement};
75 ///
76 /// # fn main() -> Result<(), alpm_types::Error> {
77 /// let requirement = VersionRequirement::from_str(">=1.5-3")?;
78 ///
79 /// assert!(!requirement.is_satisfied_by(&Version::from_str("1.5")?));
80 /// assert!(requirement.is_satisfied_by(&Version::from_str("1.5-3")?));
81 /// assert!(requirement.is_satisfied_by(&Version::from_str("1.6")?));
82 /// assert!(requirement.is_satisfied_by(&Version::from_str("2:1.0")?));
83 /// assert!(!requirement.is_satisfied_by(&Version::from_str("1.0")?));
84 ///
85 /// // If pkgrel is not specified in the requirement, it is ignored in the comparison.
86 /// let requirement = VersionRequirement::from_str("=1.5")?;
87 /// assert!(requirement.is_satisfied_by(&Version::from_str("1.5-3")?));
88 /// # Ok(())
89 /// # }
90 /// ```
91 pub fn is_satisfied_by(&self, ver: &Version) -> bool {
92 // If the requirement does not specify a pkgrel, we ignore it in the comparison.
93 // so that `foo=1` can be satisfied by `foo=1-1`.
94 let other_version = if self.version.pkgrel.is_none() {
95 &Version {
96 pkgrel: None,
97 ..ver.clone()
98 }
99 } else {
100 ver
101 };
102 self.comparison
103 .is_compatible_with(other_version.cmp(&self.version))
104 }
105
106 /// Checks whether another [`VersionRequirement`] forms an intersection with this one.
107 ///
108 /// The intersection operation `∩` on versions simply checks if there is _any_ possible set of
109 /// versions that can exist while upholding the constraints (e.g. `>`/`<=`) on both versions.
110 ///
111 /// # Examples
112 ///
113 /// - The expression `<3 ∩ <1` forms the intersection of all versions `<1`
114 /// - The expression `<2 ∩ >1` forms the intersection `X` of all versions `1<X<2`
115 /// - The expression `=2 ∩ <3` forms the intersection of the exact version `2`
116 ///
117 /// ```
118 /// use std::str::FromStr;
119 ///
120 /// use alpm_types::VersionRequirement;
121 ///
122 /// # fn main() -> testresult::TestResult {
123 /// let requirement: VersionRequirement = "<1".parse()?;
124 /// assert!(requirement.is_intersection(&"<0.1".parse()?));
125 ///
126 /// let requirement: VersionRequirement = "<2".parse()?;
127 /// assert!(requirement.is_intersection(&">1".parse()?));
128 ///
129 /// let requirement: VersionRequirement = "=2".parse()?;
130 /// assert!(!requirement.is_intersection(&"<3".parse()?));
131 /// # Ok(())
132 /// # }
133 /// ```
134 pub fn is_intersection(&self, other: &VersionRequirement) -> bool {
135 // This documentation uses the `∩` set intersection operator to better visualize examples.
136 //
137 // In the following, we need to consider the ordering relationship between the actual
138 // versions of the two `VersionRequirement`s.
139 // If we have `self = ">1.0.1"` and `other = "<2"`, this handles the part of
140 // `"1.0.1".cmp("2")`.
141 let version_comparison = self.version.cmp(&other.version);
142
143 match self.comparison {
144 // Consider the case where we have a `Less`, e.g. `<1`.
145 VersionComparison::Less => {
146 match version_comparison {
147 // The other version is greater, so its comparison must be "Less" or
148 // "LessOrEqual" to form an intersection.
149 //
150 // Example:
151 // - `<1.0.1 ∩ <2` forms the intersection of all versions `<1.0.1`
152 Ordering::Less => matches!(
153 other.comparison,
154 VersionComparison::Less | VersionComparison::LessOrEqual
155 ),
156 // Both versions are matching. The comparison for other must be "Less" or
157 // "LessOrEqual"
158 //
159 // Example:
160 // - `<=2 ∩ <2` forms the intersection of all versions `<2`
161 // - `<2 ∩ <=2` forms the intersection of all versions `<2`
162 Ordering::Equal => matches!(
163 other.comparison,
164 VersionComparison::Less | VersionComparison::LessOrEqual
165 ),
166
167 // The other version is smaller.
168 // Since `self` enforces the `Less` constraint, there will always be at least
169 // **some** intersection.
170 //
171 // Example: Even if `other` also has a "Less" constraint, the expression
172 // `<3 ∩ <1` forms the intersection of all versions `<1`
173 Ordering::Greater => true,
174 }
175 }
176 // Consider the case where we have a `LessOrEqual`, e.g. `<=1`.
177 VersionComparison::LessOrEqual => {
178 match version_comparison {
179 // The other version is greater, so its comparison must be "Less" or
180 // "LessOrEqual" to form an intersection.
181 //
182 // Example:
183 // - `>=1.0.1 ∩ <2` forms the intersection of all versions `1.0.1<=X<2`
184 // - `>= 1.0.1 <= 1.2` forms the intersection of all versions `1.0.1<=X<=1.2`
185 Ordering::Less => matches!(
186 other.comparison,
187 VersionComparison::Less | VersionComparison::LessOrEqual
188 ),
189 // Both versions are matching, the comparison for other must be either "Less"
190 // or one of "Equal", "LessOrEqual", or "GreaterOrEqual".
191 // Any `other` "*Equal" constraint will directly match the `self`
192 // "Less**Equal**" constraint.
193 //
194 // Examples:
195 // - `<=1 ∩ >=1` forms the intersection of the version `1`
196 // - `<=1 ∩ <1` forms the intersection of all version `<1`
197 // - `<=1 ∩ <=1` forms the intersection of all version `<=1`
198 Ordering::Equal => matches!(
199 other.comparison,
200 VersionComparison::Less
201 | VersionComparison::LessOrEqual
202 | VersionComparison::Equal
203 | VersionComparison::GreaterOrEqual
204 ),
205 // The other version is smaller.
206 // Since `self` enforces the `Less` constraint, there will always be at least
207 // **some** intersection.
208 //
209 // Example: Even if `other` also has a "Less" constraint, the expression
210 // `=<3 ∩ <1` forms the intersection of all versions `<1`
211 Ordering::Greater => true,
212 }
213 }
214 // Consider the case where we have a `Equal`, e.g. `=1`.
215 VersionComparison::Equal => match version_comparison {
216 // Both versions are matching, the comparison for `other` must be
217 // "LessOrEqual", "Equal", or "GreaterOrEqual" to match the "Equal" constraint on
218 // `self`
219 //
220 // Examples:
221 // - `=1 ∩ >=1` forms the intersection of the version `1`
222 // - `=1 ∩ <=1` forms the intersection of the version `1`
223 // - `=2 ∩ =2` forms the intersection of the version `2`
224 Ordering::Equal => matches!(
225 other.comparison,
226 VersionComparison::LessOrEqual
227 | VersionComparison::Equal
228 | VersionComparison::GreaterOrEqual
229 ),
230 // The other version must be greater or smaller, so it can be inherently not be
231 // equal.
232 Ordering::Less | Ordering::Greater => false,
233 },
234 // Consider the case where we have a `GreaterOrEqual`, e.g. `>=1`.
235 VersionComparison::GreaterOrEqual => match version_comparison {
236 // The other version is greater.
237 // Since `self` enforces a `Greater` constraint, so there will always be at least
238 // **some** intersection.
239 //
240 // Example: Even if `other` also has a "Less" constraint, the expression
241 // `>=1 ∩ <3` forms the intersection of all versions `1<=X<3`
242 Ordering::Less => true,
243 // Both versions are matching, the comparison for other must be either "Greater"
244 // or one of "Equal", "LessOrEqual", or "GreaterOrEqual".
245 // Any `other` "*Equal" constraint will directly match `self`'s
246 // "LesserOr**Equal**" constraint.
247 //
248 // Examples:
249 // - `>=1 ∩ <=1` forms the intersection of the version `1`
250 // - `>=1 ∩ >1` forms the intersection of all version `>1`
251 // - `>=1 ∩ >=1` forms the intersection of all version `>=1`
252 Ordering::Equal => matches!(
253 other.comparison,
254 VersionComparison::LessOrEqual
255 | VersionComparison::Equal
256 | VersionComparison::GreaterOrEqual
257 | VersionComparison::Greater
258 ),
259 // The other version is smaller, so its comparison must be at least "Greater" or
260 // "GreaterOrEqual" to form an intersection.
261 //
262 // Example:
263 // - `>=2 ∩ >1.1` forms the intersection of all versions `>=2`
264 Ordering::Greater => matches!(
265 other.comparison,
266 VersionComparison::GreaterOrEqual | VersionComparison::Greater
267 ),
268 },
269 // Consider the case where we have a `Greater`, e.g. `>1`.
270 VersionComparison::Greater => {
271 match version_comparison {
272 // The other version is greater.
273 // Since `self` enforces a `Greater` constraint, so there will always be at
274 // least **some** intersection.
275 //
276 // Example: Even if `other` also has a "Less" constraint, the expression
277 // `>1 ∩ <3` forms the intersection of all versions `1<X<3`
278 Ordering::Less => true,
279 // Both versions are matching. The comparison for other must be "Greater" or
280 // "GreaterOrEqual"
281 //
282 // Example:
283 // - `>2 ∩ >2` forms the intersection of all versions `>2`
284 // - `>2 ∩ >=2` forms the intersection of all versions `>2`
285 Ordering::Equal => matches!(
286 other.comparison,
287 VersionComparison::GreaterOrEqual | VersionComparison::Greater
288 ),
289 // The other version is smaller, so its comparison must be at least "Greater" or
290 // "GreaterOrEqual" to form an intersection.
291 //
292 // Example:
293 // - `>2 ∩ >=1.1` forms the intersection of all versions `>=2`
294 Ordering::Greater => matches!(
295 other.comparison,
296 VersionComparison::GreaterOrEqual | VersionComparison::Greater
297 ),
298 }
299 }
300 }
301 }
302}
303
304impl AlpmParser for VersionRequirement {
305 /// Recognizes a [`VersionRequirement`] in a string slice.
306 ///
307 /// # Errors
308 ///
309 /// Returns an error if `input` does not begin with a valid `VersionRequirement`.
310 fn parser(input: &mut &str) -> ModalResult<Self> {
311 seq!(Self {
312 comparison: VersionComparison::parser,
313 version: Version::parser,
314 })
315 .parse_next(input)
316 }
317
318 fn delimiter_error_context<'a, O, P>(
319 parser: P,
320 ) -> impl Parser<&'a str, O, ErrMode<ContextError>>
321 where
322 P: Parser<&'a str, O, ErrMode<ContextError>>,
323 {
324 parser
325 .context(StrContext::Label("version requirement"))
326 .context(StrContext::Expected(StrContextValue::Description(
327 "end of version requirement.",
328 )))
329 }
330}
331
332impl Display for VersionRequirement {
333 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
334 write!(f, "{}{}", self.comparison, self.version)
335 }
336}
337
338impl FromStr for VersionRequirement {
339 type Err = Error;
340
341 /// Creates a new [`VersionRequirement`] from a string slice.
342 ///
343 /// Delegates to [`VersionRequirement::parser`].
344 ///
345 /// # Errors
346 ///
347 /// Returns an error if [`VersionRequirement::parser`] fails.
348 fn from_str(s: &str) -> Result<Self, Self::Err> {
349 Ok(Self::parser_until_eof.parse(s)?)
350 }
351}
352
353/// Specifies the comparison function for a [`VersionRequirement`].
354///
355/// The package version can be required to be:
356/// - less than (`<`)
357/// - less than or equal to (`<=`)
358/// - equal to (`=`)
359/// - greater than or equal to (`>=`)
360/// - greater than (`>`)
361///
362/// the specified version.
363///
364/// See [alpm-comparison] for details on the format.
365///
366/// ## Note
367///
368/// The variants of this enum are sorted in a way, that prefers the two-letter comparators over
369/// the one-letter ones.
370/// This is because when splitting a string on the string representation of [`VersionComparison`]
371/// variant and relying on the ordering of [`strum::EnumIter`], the two-letter comparators must be
372/// checked before checking the one-letter ones to yield robust results.
373///
374/// [alpm-comparison]: https://alpm.archlinux.page/specifications/alpm-comparison.7.html
375#[derive(
376 strum::AsRefStr,
377 Clone,
378 Copy,
379 Debug,
380 strum::Display,
381 strum::EnumIter,
382 PartialEq,
383 Eq,
384 strum::VariantNames,
385)]
386#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
387pub enum VersionComparison {
388 /// Less than or equal to
389 #[strum(to_string = "<=")]
390 LessOrEqual,
391
392 /// Greater than or equal to
393 #[strum(to_string = ">=")]
394 GreaterOrEqual,
395
396 /// Equal to
397 #[strum(to_string = "=")]
398 Equal,
399
400 /// Less than
401 #[strum(to_string = "<")]
402 Less,
403
404 /// Greater than
405 #[strum(to_string = ">")]
406 Greater,
407}
408
409impl VersionComparison {
410 /// Returns `true` if the result of a comparison between the actual and required package
411 /// versions satisfies the comparison function.
412 fn is_compatible_with(self, ord: Ordering) -> bool {
413 match (self, ord) {
414 (VersionComparison::Less, Ordering::Less)
415 | (VersionComparison::LessOrEqual, Ordering::Less | Ordering::Equal)
416 | (VersionComparison::Equal, Ordering::Equal)
417 | (VersionComparison::GreaterOrEqual, Ordering::Greater | Ordering::Equal)
418 | (VersionComparison::Greater, Ordering::Greater) => true,
419
420 (VersionComparison::Less, Ordering::Equal | Ordering::Greater)
421 | (VersionComparison::LessOrEqual, Ordering::Greater)
422 | (VersionComparison::Equal, Ordering::Less | Ordering::Greater)
423 | (VersionComparison::GreaterOrEqual, Ordering::Less)
424 | (VersionComparison::Greater, Ordering::Less | Ordering::Equal) => false,
425 }
426 }
427}
428
429impl AlpmParser for VersionComparison {
430 /// Recognizes a [`VersionComparison`] in a string slice.
431 ///
432 /// # Errors
433 ///
434 /// Returns an error if `input` does not begin with a valid [`alpm-comparison`], **or** if
435 /// `input` begins with a valid [`alpm-comparison`], but is then followed by any further
436 /// comparison character (`<`, `>`, `=`).
437 ///
438 /// [`alpm-comparison`]: https://alpm.archlinux.page/specifications/alpm-comparison.7.html
439 fn parser(input: &mut &str) -> ModalResult<Self> {
440 // Consume the long expressions first!
441 // Otherwise, we would terminate early and not contain the full comparison operator.
442 let variant = opt(alt((
443 "<=".value(Self::LessOrEqual),
444 ">=".value(Self::GreaterOrEqual),
445 "=".value(Self::Equal),
446 "<".value(Self::Less),
447 ">".value(Self::Greater),
448 )))
449 .parse_next(input)?;
450
451 if let Some(variant) = variant {
452 // We found a valid variant in the beginning of the input.
453 // Now, make sure that there's not another comparison character following up.
454 let invalid_char = peek(opt(one_of(('<', '>', '=')))).parse_next(input)?;
455 if invalid_char.is_some() {
456 fail.context(StrContext::Label("comparison operator"))
457 .context_with(iter_str_context!([VersionComparison::VARIANTS]))
458 .parse_next(input)?;
459 }
460
461 Ok(variant)
462 } else {
463 fail.context(StrContext::Label("comparison operator"))
464 .context_with(iter_str_context!([VersionComparison::VARIANTS]))
465 .parse_next(input)
466 }
467 }
468
469 fn delimiter_error_context<'a, O, P>(
470 parser: P,
471 ) -> impl Parser<&'a str, O, ErrMode<ContextError>>
472 where
473 P: Parser<&'a str, O, ErrMode<ContextError>>,
474 {
475 parser
476 .context(StrContext::Label("comparison operator"))
477 .context_with(iter_str_context!([VersionComparison::VARIANTS]))
478 }
479}
480
481impl FromStr for VersionComparison {
482 type Err = Error;
483
484 /// Creates a new [`VersionComparison`] from a string slice.
485 ///
486 /// Delegates to [`VersionComparison::parser`].
487 ///
488 /// # Errors
489 ///
490 /// Returns an error if [`VersionComparison::parser`] fails.
491 fn from_str(s: &str) -> Result<Self, Self::Err> {
492 Ok(Self::parser_until_eof.parse(s)?)
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use insta::assert_snapshot;
499 use rstest::rstest;
500 use testresult::TestResult;
501
502 use super::*;
503 use crate::configure_insta;
504
505 /// Ensure that valid version comparison strings can be parsed.
506 #[rstest]
507 #[case("<", VersionComparison::Less)]
508 #[case("<=", VersionComparison::LessOrEqual)]
509 #[case("=", VersionComparison::Equal)]
510 #[case(">=", VersionComparison::GreaterOrEqual)]
511 #[case(">", VersionComparison::Greater)]
512 fn valid_version_comparison(#[case] comparison: &str, #[case] expected: VersionComparison) {
513 assert_eq!(comparison.parse(), Ok(expected));
514 }
515
516 /// Ensure that invalid version comparisons will throw an error.
517 #[rstest]
518 #[case("")]
519 #[case("<<")]
520 #[case("==")]
521 #[case("!=")]
522 #[case(" =")]
523 #[case("= ")]
524 #[case("<1")]
525 fn invalid_version_comparison(#[case] comparison: &str) {
526 let Err(Error::ParseError(_)) = VersionComparison::from_str(comparison) else {
527 panic!("'{comparison}' did not fail as expected")
528 };
529 }
530
531 /// Test successful parsing for version requirement strings.
532 #[rstest]
533 #[case("=1", VersionRequirement {
534 comparison: VersionComparison::Equal,
535 version: Version::from_str("1").unwrap(),
536 })]
537 #[case("<=42:abcd-2.4", VersionRequirement {
538 comparison: VersionComparison::LessOrEqual,
539 version: Version::from_str("42:abcd-2.4").unwrap(),
540 })]
541 #[case(">3.1", VersionRequirement {
542 comparison: VersionComparison::Greater,
543 version: Version::from_str("3.1").unwrap(),
544 })]
545 fn valid_version_requirement(#[case] requirement: &str, #[case] expected: VersionRequirement) {
546 assert_eq!(
547 requirement.parse(),
548 Ok(expected),
549 "Expected successful parse for version requirement '{requirement}'"
550 );
551 }
552
553 #[rstest]
554 #[case::bad_operator("<>3.1")]
555 #[case::no_operator("3.1")]
556 #[case::arrow_operator("=>3.1")]
557 #[case::no_version("<=")]
558 #[case::invalid_pkgver("<3.1>3.2")]
559 fn invalid_version_requirement(#[case] requirement: &str) {
560 let Err(Error::ParseError(err_msg)) = VersionRequirement::from_str(requirement) else {
561 panic!("'{requirement}' erroneously parsed as VersionRequirement")
562 };
563
564 let (test_name, _guard) = configure_insta();
565 assert_snapshot!(test_name, err_msg.to_string());
566 }
567
568 /// Check whether a version requirement (>= 1.0) is fulfilled by a given version string.
569 #[rstest]
570 #[case("=1", "1", true)]
571 #[case("=1", "1.0", false)]
572 #[case("=1", "1-1", true)]
573 #[case("=1", "1:1", false)]
574 #[case("=1", "0.9", false)]
575 #[case("<42", "41", true)]
576 #[case("<42", "42", false)]
577 #[case("<42", "43", false)]
578 #[case("<=42", "41", true)]
579 #[case("<=42", "42", true)]
580 #[case("<=42", "43", false)]
581 #[case(">42", "41", false)]
582 #[case(">42", "42", false)]
583 #[case(">42", "43", true)]
584 #[case(">=42", "41", false)]
585 #[case(">=42", "42", true)]
586 #[case(">=42", "43", true)]
587 fn version_requirement_satisfied(
588 #[case] requirement: &str,
589 #[case] version: &str,
590 #[case] result: bool,
591 ) {
592 let requirement = VersionRequirement::from_str(requirement).unwrap();
593 let version = Version::from_str(version).unwrap();
594 assert_eq!(requirement.is_satisfied_by(&version), result);
595 }
596
597 #[rstest]
598 #[case::self_less_matching_other_less("<1", "<1")]
599 #[case::self_less_matching_other_less_or_equal("<1", "<=1")]
600 #[case::self_less_bigger_other_less("<1", "<2")]
601 #[case::self_less_bigger_other_less_or_equal("<1", "<=2")]
602 #[case::self_less_smaller_other_less("<1", "<0.1")]
603 #[case::self_less_smaller_other_less_or_equal("<1", "<=0.1")]
604 #[case::self_less_smaller_other_equal("<1", "=0.1")]
605 #[case::self_less_smaller_other_greater_or_equal("<1", ">=0.1")]
606 #[case::self_less_smaller_other_greater("<1", ">0.1")]
607 #[case::self_less_smaller_other_equal("<1", "=0.1")]
608 #[case::self_less_or_equal_matching_other_less("<=1", "<1")]
609 #[case::self_less_or_equal_matching_other_less_or_equal("<=1", "<=1")]
610 #[case::self_less_or_equal_matching_other_equal("<=1", "=1")]
611 #[case::self_less_or_equal_matching_other_greater_or_equal("<=1", ">=1")]
612 #[case::self_less_or_equal_bigger_other_less("<=1", "<2")]
613 #[case::self_less_or_equal_bigger_other_less_or_equal("<=1", "<=2")]
614 #[case::self_less_or_equal_smaller_other_greater_or_equal("<=1", ">=0.1")]
615 #[case::self_less_or_equal_smaller_other_greater("<=1", ">0.1")]
616 #[case::self_equal_matching_other_less_or_equal("=1", "<=1")]
617 #[case::self_equal_matching_other_equal("=1", "=1")]
618 #[case::self_equal_matching_other_greater_or_equal("=1", ">=1")]
619 #[case::self_greater_or_equal_matching_other_less_or_equal(">=1", "<=1")]
620 #[case::self_greater_or_equal_matching_other_equal(">=1", "=1")]
621 #[case::self_greater_or_equal_matching_other_greater_or_equal(">=1", ">=1")]
622 #[case::self_greater_or_equal_matching_other_greater(">=1", ">1")]
623 #[case::self_greater_or_equal_bigger_other_less(">=1", "<2")]
624 #[case::self_greater_or_equal_bigger_other_less_or_equal(">=1", "<=2")]
625 #[case::self_greater_or_equal_bigger_other_equal(">=1", "=2")]
626 #[case::self_greater_or_equal_bigger_other_greater_or_equal(">=1", ">=2")]
627 #[case::self_greater_or_equal_bigger_other_greater(">=1", ">2")]
628 #[case::self_greater_or_equal_smaller_other_greater_or_equal(">=1", ">=0.1")]
629 #[case::self_greater_or_equal_smaller_other_greater(">=1", ">0.1")]
630 #[case::self_greater_matching_other_greater_or_equal(">1", ">=1")]
631 #[case::self_greater_matching_other_greater(">1", ">1")]
632 #[case::self_greater_bigger_other_less(">1", "<2")]
633 #[case::self_greater_bigger_other_less_or_equal(">1", "<=2")]
634 #[case::self_greater_bigger_other_equal(">1", "=2")]
635 #[case::self_greater_bigger_other_greater_or_equal(">1", ">=2")]
636 #[case::self_greater_bigger_other_greater(">1", ">2")]
637 #[case::self_greater_smaller_other_greater_or_equal(">1", ">=0.1")]
638 #[case::self_greater_smaller_other_greater(">1", ">0.1")]
639 fn version_requirements_form_intersection(
640 #[case] self_requirement: &str,
641 #[case] other_requirement: &str,
642 ) -> TestResult {
643 let self_requirement: VersionRequirement = self_requirement.parse()?;
644 let other_requirement: VersionRequirement = other_requirement.parse()?;
645
646 assert!(self_requirement.is_intersection(&other_requirement));
647
648 Ok(())
649 }
650
651 #[rstest]
652 #[case::self_less_matching_other_equal("<1", "=1")]
653 #[case::self_less_matching_other_greater_or_equal("<1", ">=1")]
654 #[case::self_less_matching_other_greater("<1", ">1")]
655 #[case::self_less_or_equal_matching_other_greater("<=1", ">1")]
656 #[case::self_equal_matching_other_less("=1", "<1")]
657 #[case::self_equal_matching_other_greater("=1", ">1")]
658 #[case::self_equal_bigger_other_less("=1", "<2")]
659 #[case::self_equal_bigger_other_greater("=1", ">2")]
660 #[case::self_equal_smaller_other_less("=1", "<0.1")]
661 #[case::self_equal_smaller_other_greater("=1", ">0.1")]
662 #[case::self_greater_or_equal_matching_other_less(">=1", "<1")]
663 #[case::self_greater_matching_other_less(">1", "<1")]
664 #[case::self_greater_matching_other_less_or_equal(">1", "<=1")]
665 #[case::self_greater_matching_other_equal(">1", "=1")]
666 fn version_requirements_do_not_form_intersection(
667 #[case] self_requirement: &str,
668 #[case] other_requirement: &str,
669 ) -> TestResult {
670 let self_requirement: VersionRequirement = self_requirement.parse()?;
671 let other_requirement: VersionRequirement = other_requirement.parse()?;
672
673 assert!(!self_requirement.is_intersection(&other_requirement));
674 Ok(())
675 }
676}