test_case_core/
complex_expr.rs

1use crate::utils::fmt_syn;
2use proc_macro2::Group;
3use proc_macro2::Span;
4use proc_macro2::TokenStream;
5use quote::{quote, TokenStreamExt};
6use std::fmt::{Display, Formatter};
7use syn::parse::{Parse, ParseStream};
8use syn::{parse_quote, Expr};
9
10mod kw {
11    syn::custom_keyword!(eq);
12    syn::custom_keyword!(equal_to);
13    syn::custom_keyword!(lt);
14    syn::custom_keyword!(less_than);
15    syn::custom_keyword!(gt);
16    syn::custom_keyword!(greater_than);
17    syn::custom_keyword!(leq);
18    syn::custom_keyword!(less_or_equal_than);
19    syn::custom_keyword!(geq);
20    syn::custom_keyword!(greater_or_equal_than);
21    syn::custom_keyword!(almost);
22    syn::custom_keyword!(almost_equal_to);
23    syn::custom_keyword!(precision);
24    syn::custom_keyword!(existing_path);
25    syn::custom_keyword!(directory);
26    syn::custom_keyword!(dir);
27    syn::custom_keyword!(file);
28    syn::custom_keyword!(contains);
29    syn::custom_keyword!(contains_in_order);
30    syn::custom_keyword!(not);
31    syn::custom_keyword!(and);
32    syn::custom_keyword!(or);
33    syn::custom_keyword!(len);
34    syn::custom_keyword!(has_length);
35    syn::custom_keyword!(count);
36    syn::custom_keyword!(has_count);
37    syn::custom_keyword!(empty);
38    syn::custom_keyword!(matching_regex);
39    syn::custom_keyword!(matches_regex);
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub enum OrderingToken {
44    Eq,
45    Lt,
46    Gt,
47    Leq,
48    Geq,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub enum PathToken {
53    Any,
54    Dir,
55    File,
56}
57
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub struct Ord {
60    pub token: OrderingToken,
61    pub expected_value: Box<Expr>,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct AlmostEqual {
66    pub expected_value: Box<Expr>,
67    pub precision: Box<Expr>,
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct Path {
72    pub token: PathToken,
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct Contains {
77    pub expected_element: Box<Expr>,
78}
79
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct ContainsInOrder {
82    pub expected_slice: Box<Expr>,
83}
84
85#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct Len {
87    pub expected_len: Box<Expr>,
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct Count {
92    pub expected_len: Box<Expr>,
93}
94
95#[cfg(feature = "with-regex")]
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct Regex {
98    pub expected_regex: Box<Expr>,
99}
100
101#[derive(Clone, Debug, PartialEq)]
102pub enum ComplexTestCase {
103    Not(Box<ComplexTestCase>),
104    And(Vec<ComplexTestCase>),
105    Or(Vec<ComplexTestCase>),
106    Ord(Ord),
107    AlmostEqual(AlmostEqual),
108    Path(Path),
109    Contains(Contains),
110    ContainsInOrder(ContainsInOrder),
111    Len(Len),
112    Count(Count),
113    Empty,
114    #[cfg(feature = "with-regex")]
115    Regex(Regex),
116}
117
118impl Parse for ComplexTestCase {
119    fn parse(input: ParseStream) -> syn::Result<Self> {
120        let item = Self::parse_single_item(input)?;
121
122        Ok(if input.peek(kw::and) {
123            ComplexTestCase::And(parse_kw_repeat::<kw::and>(item, input)?)
124        } else if input.peek(kw::or) {
125            ComplexTestCase::Or(parse_kw_repeat::<kw::or>(item, input)?)
126        } else {
127            item
128        })
129    }
130}
131
132impl Display for OrderingToken {
133    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134        match self {
135            OrderingToken::Eq => f.write_str("eq"),
136            OrderingToken::Lt => f.write_str("lt"),
137            OrderingToken::Gt => f.write_str("gt"),
138            OrderingToken::Leq => f.write_str("leq"),
139            OrderingToken::Geq => f.write_str("geq"),
140        }
141    }
142}
143
144impl Display for PathToken {
145    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146        match self {
147            PathToken::Any => f.write_str("path"),
148            PathToken::Dir => f.write_str("dir"),
149            PathToken::File => f.write_str("file"),
150        }
151    }
152}
153
154impl Display for ComplexTestCase {
155    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156        match self {
157            ComplexTestCase::Not(not) => write!(f, "not {not}"),
158            ComplexTestCase::And(cases) => {
159                write!(f, "{}", cases[0])?;
160                for case in cases[1..].iter() {
161                    write!(f, " and {case}")?;
162                }
163                Ok(())
164            }
165            ComplexTestCase::Or(cases) => {
166                write!(f, "{}", cases[0])?;
167                for case in cases[1..].iter() {
168                    write!(f, " or {case}")?;
169                }
170                Ok(())
171            }
172            ComplexTestCase::Ord(Ord {
173                token,
174                expected_value,
175            }) => write!(f, "{} {}", token, fmt_syn(expected_value)),
176            ComplexTestCase::AlmostEqual(AlmostEqual {
177                expected_value,
178                precision,
179            }) => write!(
180                f,
181                "almost {} p {}",
182                fmt_syn(expected_value),
183                fmt_syn(precision)
184            ),
185            ComplexTestCase::Path(Path { token }) => write!(f, "path {token}"),
186            ComplexTestCase::Contains(Contains { expected_element }) => {
187                write!(f, "contains {}", fmt_syn(expected_element))
188            }
189            ComplexTestCase::ContainsInOrder(ContainsInOrder { expected_slice }) => {
190                write!(f, "contains in order {}", fmt_syn(expected_slice))
191            }
192            ComplexTestCase::Len(Len { expected_len }) => {
193                write!(f, "len {}", fmt_syn(expected_len))
194            }
195            ComplexTestCase::Count(Count { expected_len }) => {
196                write!(f, "count {}", fmt_syn(expected_len))
197            }
198            ComplexTestCase::Empty => {
199                write!(f, "empty")
200            }
201            #[cfg(feature = "with-regex")]
202            ComplexTestCase::Regex(Regex { expected_regex }) => {
203                write!(f, "regex {}", fmt_syn(expected_regex))
204            }
205        }
206    }
207}
208
209impl ComplexTestCase {
210    pub fn assertion(&self) -> TokenStream {
211        let tokens = self.boolean_check();
212
213        quote! { assert!(#tokens) }
214    }
215
216    fn boolean_check(&self) -> TokenStream {
217        match self {
218            ComplexTestCase::Not(not) => not_assertion(not),
219            ComplexTestCase::And(cases) => and_assertion(cases),
220            ComplexTestCase::Or(cases) => or_assertion(cases),
221            ComplexTestCase::Ord(Ord {
222                token,
223                expected_value,
224            }) => ord_assertion(token, expected_value),
225            ComplexTestCase::AlmostEqual(AlmostEqual {
226                expected_value,
227                precision,
228            }) => almost_equal_assertion(expected_value, precision),
229            ComplexTestCase::Path(Path { token }) => path_assertion(token),
230            ComplexTestCase::Contains(Contains { expected_element }) => {
231                contains_assertion(expected_element)
232            }
233            ComplexTestCase::ContainsInOrder(ContainsInOrder { expected_slice }) => {
234                contains_in_order_assertion(expected_slice)
235            }
236            ComplexTestCase::Len(Len { expected_len }) => len_assertion(expected_len),
237            ComplexTestCase::Count(Count { expected_len }) => count_assertion(expected_len),
238            ComplexTestCase::Empty => empty_assertion(),
239            #[cfg(feature = "with-regex")]
240            ComplexTestCase::Regex(Regex { expected_regex }) => regex_assertion(expected_regex),
241        }
242    }
243
244    fn parse_single_item(input: ParseStream) -> syn::Result<ComplexTestCase> {
245        Ok(if let Ok(group) = Group::parse(input) {
246            syn::parse2(group.stream())?
247        } else if input.parse::<kw::eq>().is_ok() || input.parse::<kw::equal_to>().is_ok() {
248            ComplexTestCase::Ord(Ord {
249                token: OrderingToken::Eq,
250                expected_value: input.parse()?,
251            })
252        } else if input.parse::<kw::lt>().is_ok() || input.parse::<kw::less_than>().is_ok() {
253            ComplexTestCase::Ord(Ord {
254                token: OrderingToken::Lt,
255                expected_value: input.parse()?,
256            })
257        } else if input.parse::<kw::gt>().is_ok() || input.parse::<kw::greater_than>().is_ok() {
258            ComplexTestCase::Ord(Ord {
259                token: OrderingToken::Gt,
260                expected_value: input.parse()?,
261            })
262        } else if input.parse::<kw::leq>().is_ok()
263            || input.parse::<kw::less_or_equal_than>().is_ok()
264        {
265            ComplexTestCase::Ord(Ord {
266                token: OrderingToken::Leq,
267                expected_value: input.parse()?,
268            })
269        } else if input.parse::<kw::geq>().is_ok()
270            || input.parse::<kw::greater_or_equal_than>().is_ok()
271        {
272            ComplexTestCase::Ord(Ord {
273                token: OrderingToken::Geq,
274                expected_value: input.parse()?,
275            })
276        } else if input.parse::<kw::almost>().is_ok()
277            || input.parse::<kw::almost_equal_to>().is_ok()
278        {
279            let target = input.parse()?;
280            let _ = input.parse::<kw::precision>()?;
281            let precision = input.parse()?;
282            ComplexTestCase::AlmostEqual(AlmostEqual {
283                expected_value: target,
284                precision,
285            })
286        } else if input.parse::<kw::existing_path>().is_ok() {
287            ComplexTestCase::Path(Path {
288                token: PathToken::Any,
289            })
290        } else if input.parse::<kw::directory>().is_ok() || input.parse::<kw::dir>().is_ok() {
291            ComplexTestCase::Path(Path {
292                token: PathToken::Dir,
293            })
294        } else if input.parse::<kw::file>().is_ok() {
295            ComplexTestCase::Path(Path {
296                token: PathToken::File,
297            })
298        } else if input.parse::<kw::contains>().is_ok() {
299            ComplexTestCase::Contains(Contains {
300                expected_element: input.parse()?,
301            })
302        } else if input.parse::<kw::contains_in_order>().is_ok() {
303            ComplexTestCase::ContainsInOrder(ContainsInOrder {
304                expected_slice: input.parse()?,
305            })
306        } else if input.parse::<kw::not>().is_ok() {
307            ComplexTestCase::Not(Box::new(input.parse()?))
308        } else if input.parse::<kw::len>().is_ok() || input.parse::<kw::has_length>().is_ok() {
309            ComplexTestCase::Len(Len {
310                expected_len: input.parse()?,
311            })
312        } else if input.parse::<kw::count>().is_ok() || input.parse::<kw::has_count>().is_ok() {
313            ComplexTestCase::Count(Count {
314                expected_len: input.parse()?,
315            })
316        } else if input.parse::<kw::empty>().is_ok() {
317            ComplexTestCase::Empty
318        } else if input.parse::<kw::matching_regex>().is_ok()
319            || input.parse::<kw::matches_regex>().is_ok()
320        {
321            cfg_if::cfg_if! {
322                if #[cfg(feature = "with-regex")] {
323                    ComplexTestCase::Regex(Regex {
324                        expected_regex: input.parse()?,
325                    })
326                } else {
327                    return Err(input.error("'with-regex' feature is required to use 'matches-regex' keyword"));
328                }
329            }
330        } else {
331            return Err(input.error("cannot parse complex expression"));
332        })
333    }
334}
335
336fn and_assertion(cases: &[ComplexTestCase]) -> TokenStream {
337    let ts = cases[0].boolean_check();
338    let mut ts: TokenStream = parse_quote! { #ts };
339
340    for case in cases.iter().skip(1) {
341        let case = case.boolean_check();
342        let case: TokenStream = parse_quote! { && #case };
343        ts.append_all(case);
344    }
345
346    ts
347}
348
349fn or_assertion(cases: &[ComplexTestCase]) -> TokenStream {
350    let ts = cases[0].boolean_check();
351    let mut ts: TokenStream = parse_quote! { #ts };
352
353    for case in cases.iter().skip(1) {
354        let case = case.boolean_check();
355        let case: TokenStream = parse_quote! { || #case };
356        ts.append_all(case);
357    }
358
359    ts
360}
361
362fn parse_kw_repeat<Keyword: Parse>(
363    first: ComplexTestCase,
364    input: ParseStream,
365) -> syn::Result<Vec<ComplexTestCase>> {
366    let mut acc = vec![first];
367    while input.parse::<Keyword>().is_ok() {
368        acc.push(ComplexTestCase::parse_single_item(input)?);
369    }
370    Ok(acc)
371}
372
373fn negate(tokens: TokenStream) -> TokenStream {
374    quote! {
375        !{#tokens}
376    }
377}
378
379fn contains_in_order_assertion(expected_slice: &Expr) -> TokenStream {
380    parse_quote! {
381        {
382            let mut _tc_outcome = false;
383            for i in 0..=_result.len() - #expected_slice.len() {
384                if #expected_slice == _result[i..i+#expected_slice.len()] {
385                    _tc_outcome = true;
386                }
387            }
388            _tc_outcome
389        }
390    }
391}
392
393fn contains_assertion(expected_element: &Expr) -> TokenStream {
394    parse_quote! { _result.iter().find(|i| i.eq(&&#expected_element)).is_some() }
395}
396
397fn path_assertion(token: &PathToken) -> TokenStream {
398    match token {
399        PathToken::Any => parse_quote! { std::path::Path::new(&_result).exists() },
400        PathToken::Dir => parse_quote! { std::path::Path::new(&_result).is_dir() },
401        PathToken::File => parse_quote! { std::path::Path::new(&_result).is_file() },
402    }
403}
404
405fn almost_equal_assertion(expected_value: &Expr, precision: &Expr) -> TokenStream {
406    quote! { (_result - #expected_value).abs() < #precision }
407}
408
409fn ord_assertion(token: &OrderingToken, expected_value: &Expr) -> TokenStream {
410    let ts: TokenStream = match token {
411        OrderingToken::Eq => parse_quote! { == },
412        OrderingToken::Lt => parse_quote! { < },
413        OrderingToken::Gt => parse_quote! { > },
414        OrderingToken::Leq => parse_quote! { <= },
415        OrderingToken::Geq => parse_quote! { >= },
416    };
417
418    quote! {
419        _result #ts #expected_value
420    }
421}
422
423fn len_assertion(expected_len: &Expr) -> TokenStream {
424    quote! {
425        _result.len() == #expected_len
426    }
427}
428
429fn count_assertion(expected_len: &Expr) -> TokenStream {
430    quote! {
431        std::iter::IntoIterator::into_iter(_result).count() == #expected_len
432    }
433}
434
435fn empty_assertion() -> TokenStream {
436    quote! {
437        _result.is_empty()
438    }
439}
440
441#[cfg(feature = "with-regex")]
442fn regex_assertion(expected_regex: &Expr) -> TokenStream {
443    quote! {
444        {
445            let re = ::test_case::Regex::new(#expected_regex).expect("Regex::new");
446            re.is_match(_result)
447        }
448    }
449}
450
451fn not_assertion(not: &ComplexTestCase) -> TokenStream {
452    match not {
453        ComplexTestCase::Not(_) => {
454            let msg = "multiple negations on single item are forbidden";
455            syn::Error::new(Span::call_site(), msg).into_compile_error()
456        }
457        ComplexTestCase::And(cases) => negate(and_assertion(cases)),
458        ComplexTestCase::Or(cases) => negate(or_assertion(cases)),
459        ComplexTestCase::Ord(Ord {
460            token,
461            expected_value,
462        }) => negate(ord_assertion(token, expected_value)),
463        ComplexTestCase::AlmostEqual(AlmostEqual {
464            expected_value,
465            precision,
466        }) => negate(almost_equal_assertion(expected_value, precision)),
467        ComplexTestCase::Path(Path { token }) => negate(path_assertion(token)),
468        ComplexTestCase::Contains(Contains { expected_element }) => {
469            negate(contains_assertion(expected_element))
470        }
471        ComplexTestCase::ContainsInOrder(ContainsInOrder { expected_slice }) => {
472            negate(contains_in_order_assertion(expected_slice))
473        }
474        ComplexTestCase::Len(Len { expected_len }) => negate(len_assertion(expected_len)),
475        ComplexTestCase::Count(Count { expected_len }) => negate(count_assertion(expected_len)),
476        ComplexTestCase::Empty => negate(empty_assertion()),
477        #[cfg(feature = "with-regex")]
478        ComplexTestCase::Regex(Regex { expected_regex }) => negate(regex_assertion(expected_regex)),
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use crate::complex_expr::{
485        AlmostEqual, ComplexTestCase, Contains, ContainsInOrder, Count, Len, OrderingToken, Path,
486        PathToken,
487    };
488    use syn::{parse_quote, LitFloat, LitInt, LitStr};
489
490    macro_rules! assert_ord {
491        ($actual:tt, $token:path, $value:tt) => {
492            if let ComplexTestCase::Ord(ord) = $actual {
493                assert_eq!(ord.token, $token);
494                let lit = ord.expected_value;
495                let actual_expr: LitFloat = parse_quote! { #lit };
496                assert_eq!(actual_expr.base10_parse::<f64>().unwrap(), $value)
497            } else {
498                panic!("invalid enum variant")
499            }
500        };
501    }
502
503    macro_rules! assert_almost_eq {
504        ($actual:tt, $value:tt, $precision:tt) => {
505            if let ComplexTestCase::AlmostEqual(AlmostEqual {
506                expected_value,
507                precision,
508            }) = $actual
509            {
510                let expected_value: LitFloat = parse_quote! { #expected_value };
511                assert_eq!(expected_value.base10_parse::<f64>().unwrap(), $value);
512                let precision: LitFloat = parse_quote! { #precision };
513                assert_eq!(precision.base10_parse::<f64>().unwrap(), $precision);
514            } else {
515                panic!("invalid enum variant")
516            }
517        };
518    }
519
520    #[test]
521    #[allow(clippy::float_cmp)]
522    fn parses_ord_token_stream() {
523        let actual: ComplexTestCase = parse_quote! { equal_to 1.0 };
524        assert_ord!(actual, OrderingToken::Eq, 1.0);
525        let actual: ComplexTestCase = parse_quote! { eq 0.0 };
526        assert_ord!(actual, OrderingToken::Eq, 0.0);
527
528        let actual: ComplexTestCase = parse_quote! { less_than 2.0 };
529        assert_ord!(actual, OrderingToken::Lt, 2.0);
530        let actual: ComplexTestCase = parse_quote! { lt 2.0 };
531        assert_ord!(actual, OrderingToken::Lt, 2.0);
532
533        let actual: ComplexTestCase = parse_quote! { greater_than 2.0 };
534        assert_ord!(actual, OrderingToken::Gt, 2.0);
535        let actual: ComplexTestCase = parse_quote! { gt 2.0 };
536        assert_ord!(actual, OrderingToken::Gt, 2.0);
537
538        let actual: ComplexTestCase = parse_quote! { less_or_equal_than 2.0 };
539        assert_ord!(actual, OrderingToken::Leq, 2.0);
540        let actual: ComplexTestCase = parse_quote! { leq 2.0 };
541        assert_ord!(actual, OrderingToken::Leq, 2.0);
542
543        let actual: ComplexTestCase = parse_quote! { greater_or_equal_than 2.0 };
544        assert_ord!(actual, OrderingToken::Geq, 2.0);
545        let actual: ComplexTestCase = parse_quote! { geq 2.0 };
546        assert_ord!(actual, OrderingToken::Geq, 2.0);
547    }
548
549    #[test]
550    fn can_parse_eq_other_types() {
551        let actual: ComplexTestCase = parse_quote! { equal_to "abcde" };
552        if let ComplexTestCase::Ord(ord) = actual {
553            assert_eq!(ord.token, OrderingToken::Eq);
554            let lit = ord.expected_value;
555            let actual_expr: LitStr = parse_quote! { #lit };
556            assert_eq!(actual_expr.value(), "abcde")
557        } else {
558            panic!("invalid enum variant")
559        }
560
561        let actual: ComplexTestCase = parse_quote! { equal_to 1 };
562        if let ComplexTestCase::Ord(ord) = actual {
563            assert_eq!(ord.token, OrderingToken::Eq);
564            let lit = ord.expected_value;
565            let actual_expr: LitInt = parse_quote! { #lit };
566            assert_eq!(actual_expr.base10_parse::<i64>().unwrap(), 1)
567        } else {
568            panic!("invalid enum variant")
569        }
570    }
571
572    #[test]
573    #[allow(clippy::float_cmp)]
574    fn parses_almost_equal_token_stream() {
575        let actual: ComplexTestCase = parse_quote! { almost_equal_to 1.0 precision 0.1 };
576        assert_almost_eq!(actual, 1.0, 0.1);
577        let actual: ComplexTestCase = parse_quote! { almost_equal_to 1.0 precision 0.0f32 };
578        assert_almost_eq!(actual, 1.0, 0.0);
579    }
580
581    #[test]
582    fn parses_path_token_stream() {
583        let actual: ComplexTestCase = parse_quote! { existing_path };
584        assert_eq!(
585            actual,
586            ComplexTestCase::Path(Path {
587                token: PathToken::Any
588            })
589        );
590        let actual: ComplexTestCase = parse_quote! { file };
591        assert_eq!(
592            actual,
593            ComplexTestCase::Path(Path {
594                token: PathToken::File
595            })
596        );
597        let actual: ComplexTestCase = parse_quote! { dir };
598        assert_eq!(
599            actual,
600            ComplexTestCase::Path(Path {
601                token: PathToken::Dir
602            })
603        );
604        let actual: ComplexTestCase = parse_quote! { directory };
605        assert_eq!(
606            actual,
607            ComplexTestCase::Path(Path {
608                token: PathToken::Dir
609            })
610        );
611    }
612
613    #[test]
614    fn parses_contains_token_stream() {
615        let actual: ComplexTestCase = parse_quote! { contains 1.0 };
616        assert_eq!(
617            actual,
618            ComplexTestCase::Contains(Contains {
619                expected_element: Box::new(parse_quote! { 1.0 })
620            })
621        );
622        let actual: ComplexTestCase = parse_quote! { contains "abcde" };
623        assert_eq!(
624            actual,
625            ComplexTestCase::Contains(Contains {
626                expected_element: Box::new(parse_quote! { "abcde" })
627            })
628        );
629        let actual: ComplexTestCase = parse_quote! { contains true };
630        assert_eq!(
631            actual,
632            ComplexTestCase::Contains(Contains {
633                expected_element: Box::new(parse_quote! { true })
634            })
635        );
636    }
637
638    #[test]
639    fn parses_contains_in_order_token_stream() {
640        let actual: ComplexTestCase = parse_quote! { contains_in_order [1, 2, 3] };
641        assert_eq!(
642            actual,
643            ComplexTestCase::ContainsInOrder(ContainsInOrder {
644                expected_slice: Box::new(parse_quote! { [1, 2, 3] })
645            })
646        )
647    }
648
649    #[test]
650    fn parses_len_token_stream() {
651        let actual1: ComplexTestCase = parse_quote! { len 10 };
652        let actual2: ComplexTestCase = parse_quote! { has_length 11 };
653        assert_eq!(
654            actual1,
655            ComplexTestCase::Len(Len {
656                expected_len: Box::new(parse_quote! { 10 })
657            })
658        );
659
660        assert_eq!(
661            actual2,
662            ComplexTestCase::Len(Len {
663                expected_len: Box::new(parse_quote! { 11 })
664            })
665        )
666    }
667
668    #[test]
669    fn parses_count_token_stream() {
670        let actual1: ComplexTestCase = parse_quote! { count 10 };
671        let actual2: ComplexTestCase = parse_quote! { has_count 11 };
672        assert_eq!(
673            actual1,
674            ComplexTestCase::Count(Count {
675                expected_len: Box::new(parse_quote! { 10 })
676            })
677        );
678        assert_eq!(
679            actual2,
680            ComplexTestCase::Count(Count {
681                expected_len: Box::new(parse_quote! { 11 })
682            })
683        )
684    }
685
686    #[test]
687    fn parses_empty() {
688        let actual: ComplexTestCase = parse_quote! { empty };
689        assert_eq!(actual, ComplexTestCase::Empty,)
690    }
691
692    #[test]
693    fn parses_negation() {
694        let actual: ComplexTestCase = parse_quote! { not eq 1.0 };
695        match actual {
696            ComplexTestCase::Not(_) => {}
697            _ => panic!("test failed"),
698        };
699    }
700
701    #[test]
702    #[allow(clippy::float_cmp)]
703    fn parses_grouping() {
704        let actual: ComplexTestCase = parse_quote! { (lt 1.0) };
705        assert_ord!(actual, OrderingToken::Lt, 1.0);
706        let actual: ComplexTestCase = parse_quote! { (((lt 1.0))) };
707        assert_ord!(actual, OrderingToken::Lt, 1.0);
708        let actual: ComplexTestCase = parse_quote! { ({[(lt 1.0)]}) };
709        assert_ord!(actual, OrderingToken::Lt, 1.0)
710    }
711
712    #[test]
713    fn parses_logic() {
714        let actual: ComplexTestCase = parse_quote! { lt 1.0 and gt 0.0 };
715        match actual {
716            ComplexTestCase::And(v) if v.len() == 2 => {}
717            _ => panic!("test failed"),
718        }
719        let actual: ComplexTestCase = parse_quote! { lt 0.0 or gt 1.0 };
720        match actual {
721            ComplexTestCase::Or(v) if v.len() == 2 => {}
722            _ => panic!("test failed"),
723        }
724        let actual: ComplexTestCase = parse_quote! { lt 1.0 and gt 0.0 and eq 0.5 };
725        match actual {
726            ComplexTestCase::And(v) if v.len() == 3 => {}
727            _ => panic!("test failed"),
728        }
729        let actual: ComplexTestCase = parse_quote! { lt 0.0 or gt 1.0 or eq 2.0 };
730        match actual {
731            ComplexTestCase::Or(v) if v.len() == 3 => {}
732            _ => panic!("test failed"),
733        }
734        let actual: ComplexTestCase = parse_quote! { (lt 0.0 or gt 1.0) and eq 2.0 };
735        match actual {
736            ComplexTestCase::And(v) if v.len() == 2 => {}
737            _ => panic!("test failed"),
738        }
739        let actual: ComplexTestCase = parse_quote! { (lt 1.0 and gt 0.0) or eq 2.0 };
740        match actual {
741            ComplexTestCase::Or(v) if v.len() == 2 => {}
742            _ => panic!("test failed"),
743        }
744    }
745}