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}