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