1use std::collections::{BTreeSet, HashSet, VecDeque};
14use std::fmt;
15
16use crate::description::Description;
17use crate::matcher::{MatchResult, Matcher, Mismatch};
18
19pub trait Sequence {
26 type Item;
28
29 fn sequence_items(&self) -> Vec<&Self::Item>;
31}
32
33impl<T> Sequence for [T] {
34 type Item = T;
35
36 fn sequence_items(&self) -> Vec<&T> {
37 self.iter().collect()
38 }
39}
40
41impl<T, const N: usize> Sequence for [T; N] {
42 type Item = T;
43
44 fn sequence_items(&self) -> Vec<&T> {
45 self.iter().collect()
46 }
47}
48
49impl<T> Sequence for Vec<T> {
50 type Item = T;
51
52 fn sequence_items(&self) -> Vec<&T> {
53 self.iter().collect()
54 }
55}
56
57impl<T> Sequence for VecDeque<T> {
58 type Item = T;
59
60 fn sequence_items(&self) -> Vec<&T> {
61 self.iter().collect()
62 }
63}
64
65impl<T> Sequence for BTreeSet<T> {
66 type Item = T;
67
68 fn sequence_items(&self) -> Vec<&T> {
69 self.iter().collect()
70 }
71}
72
73impl<T> Sequence for HashSet<T> {
74 type Item = T;
75
76 fn sequence_items(&self) -> Vec<&T> {
77 self.iter().collect()
78 }
79}
80
81impl<S: Sequence + ?Sized> Sequence for &S {
82 type Item = S::Item;
83
84 fn sequence_items(&self) -> Vec<&S::Item> {
85 (**self).sequence_items()
86 }
87}
88
89pub struct Items<T>(Vec<T>);
100
101impl<T> Sequence for Items<T> {
102 type Item = T;
103
104 fn sequence_items(&self) -> Vec<&T> {
105 self.0.iter().collect()
106 }
107}
108
109#[must_use]
122pub fn items<I>(iter: I) -> Items<I::Item>
123where
124 I: IntoIterator,
125{
126 Items(iter.into_iter().collect())
127}
128
129struct LenMatcher {
131 expected: usize,
132}
133
134impl<C> Matcher<C> for LenMatcher
135where
136 C: Sequence + ?Sized,
137{
138 fn check(&self, actual: &C) -> MatchResult {
139 let len = actual.sequence_items().len();
140 if len == self.expected {
141 MatchResult::pass()
142 } else {
143 MatchResult::fail(Mismatch::new(
144 Description::text(format!("a sequence of length {}", self.expected)),
145 format!("a sequence of length {len}"),
146 ))
147 }
148 }
149
150 fn description(&self) -> Description {
151 Description::text(format!("a sequence of length {}", self.expected))
152 }
153}
154
155#[must_use]
167pub fn have_len<C>(n: usize) -> impl Matcher<C>
168where
169 C: Sequence + ?Sized,
170{
171 LenMatcher { expected: n }
172}
173
174struct EmptyMatcher {
176 want_empty: bool,
177}
178
179impl<C> Matcher<C> for EmptyMatcher
180where
181 C: Sequence + ?Sized,
182{
183 fn check(&self, actual: &C) -> MatchResult {
184 let len = actual.sequence_items().len();
185 if (len == 0) == self.want_empty {
186 MatchResult::pass()
187 } else if self.want_empty {
188 MatchResult::fail(Mismatch::new(
189 self.description_text(),
190 format!("a sequence of length {len}"),
191 ))
192 } else {
193 MatchResult::fail(Mismatch::new(self.description_text(), "an empty sequence"))
194 }
195 }
196
197 fn description(&self) -> Description {
198 self.description_text()
199 }
200}
201
202impl EmptyMatcher {
203 fn description_text(&self) -> Description {
204 Description::text(if self.want_empty {
205 "an empty sequence"
206 } else {
207 "a non-empty sequence"
208 })
209 }
210}
211
212#[must_use]
224pub fn is_empty<C>() -> impl Matcher<C>
225where
226 C: Sequence + ?Sized,
227{
228 EmptyMatcher { want_empty: true }
229}
230
231#[must_use]
243pub fn is_not_empty<C>() -> impl Matcher<C>
244where
245 C: Sequence + ?Sized,
246{
247 EmptyMatcher { want_empty: false }
248}
249
250struct AnyItemMatcher<M> {
253 inner: M,
254 header: &'static str,
257}
258
259impl<C, M> Matcher<C> for AnyItemMatcher<M>
260where
261 C: Sequence + ?Sized,
262 C::Item: fmt::Debug,
263 M: Matcher<C::Item>,
264{
265 fn check(&self, actual: &C) -> MatchResult {
266 let items = actual.sequence_items();
267 if items.iter().any(|item| self.inner.check(item).matched) {
268 MatchResult::pass()
269 } else {
270 MatchResult::fail(Mismatch::new(
271 Description::labeled(self.header, self.inner.description()),
272 format!("{items:?}"),
273 ))
274 }
275 }
276
277 fn description(&self) -> Description {
278 Description::labeled(self.header, self.inner.description())
279 }
280}
281
282#[must_use]
294pub fn contains<C, M>(matcher: M) -> impl Matcher<C>
295where
296 C: Sequence + ?Sized,
297 C::Item: fmt::Debug,
298 M: Matcher<C::Item>,
299{
300 AnyItemMatcher {
301 inner: matcher,
302 header: "a sequence containing an item that is",
303 }
304}
305
306#[must_use]
321pub fn at_least_one<C, M>(matcher: M) -> impl Matcher<C>
322where
323 C: Sequence + ?Sized,
324 C::Item: fmt::Debug,
325 M: Matcher<C::Item>,
326{
327 AnyItemMatcher {
328 inner: matcher,
329 header: "at least one item to satisfy",
330 }
331}
332
333struct EveryMatcher<M> {
335 inner: M,
336}
337
338impl<C, M> Matcher<C> for EveryMatcher<M>
339where
340 C: Sequence + ?Sized,
341 C::Item: fmt::Debug,
342 M: Matcher<C::Item>,
343{
344 fn check(&self, actual: &C) -> MatchResult {
345 let items = actual.sequence_items();
346 for (index, item) in items.iter().enumerate() {
347 if let Some(failure) = self.inner.check(item).failure {
348 return MatchResult::fail(Mismatch::new(
349 Matcher::<C>::description(self),
352 format!("item at index {index} was {}", failure.actual),
353 ));
354 }
355 }
356 MatchResult::pass()
357 }
358
359 fn description(&self) -> Description {
360 Description::labeled("every item to satisfy", self.inner.description())
361 }
362}
363
364#[must_use]
378pub fn every<C, M>(matcher: M) -> impl Matcher<C>
379where
380 C: Sequence + ?Sized,
381 C::Item: fmt::Debug,
382 M: Matcher<C::Item>,
383{
384 EveryMatcher { inner: matcher }
385}
386
387struct InOrderMatcher<M, const N: usize> {
389 matchers: [M; N],
390}
391
392impl<C, M, const N: usize> Matcher<C> for InOrderMatcher<M, N>
393where
394 C: Sequence + ?Sized,
395 C::Item: fmt::Debug,
396 M: Matcher<C::Item>,
397{
398 fn check(&self, actual: &C) -> MatchResult {
399 let items = actual.sequence_items();
400 let mut next = 0;
401 for item in &items {
402 if next < N && self.matchers[next].check(item).matched {
403 next += 1;
404 }
405 }
406 if next == N {
407 MatchResult::pass()
408 } else {
409 MatchResult::fail(Mismatch::new(
410 Matcher::<C>::description(self),
411 format!(
412 "a sequence matching {next} of {N} in order \
413 (no later item satisfied matcher at index {next}): {items:?}"
414 ),
415 ))
416 }
417 }
418
419 fn description(&self) -> Description {
420 let joined = self
421 .matchers
422 .iter()
423 .map(|m| m.description().to_string())
424 .collect::<Vec<_>>()
425 .join(", then ");
426 Description::text(format!("a sequence containing, in order: {joined}"))
427 }
428}
429
430#[must_use]
446pub fn contains_in_order<C, M, const N: usize>(matchers: [M; N]) -> impl Matcher<C>
447where
448 C: Sequence + ?Sized,
449 C::Item: fmt::Debug,
450 M: Matcher<C::Item>,
451{
452 InOrderMatcher { matchers }
453}
454
455pub trait ContainsAll<Item> {
460 fn first_unsatisfied(&self, items: &[&Item]) -> Option<Description>;
463
464 fn describe(&self) -> Description;
466}
467
468macro_rules! impl_contains_all {
471 ($first:ident, $($rest:ident),+) => {
472 #[allow(non_snake_case)]
473 impl<Item, $first, $($rest,)+> ContainsAll<Item> for ($first, $($rest,)+)
474 where
475 $first: Matcher<Item>,
476 $($rest: Matcher<Item>,)+
477 {
478 fn first_unsatisfied(&self, items: &[&Item]) -> Option<Description> {
479 let ($first, $($rest,)+) = self;
480 if !items.iter().any(|item| $first.check(item).matched) {
481 return Some($first.description());
482 }
483 $(
484 if !items.iter().any(|item| $rest.check(item).matched) {
485 return Some($rest.description());
486 }
487 )+
488 None
489 }
490
491 fn describe(&self) -> Description {
492 let ($first, $($rest,)+) = self;
493 let desc = $first.description();
494 $( let desc = desc.and($rest.description()); )+
495 desc
496 }
497 }
498 };
499}
500
501impl_contains_all!(M1, M2);
502impl_contains_all!(M1, M2, M3);
503impl_contains_all!(M1, M2, M3, M4);
504impl_contains_all!(M1, M2, M3, M4, M5);
505impl_contains_all!(M1, M2, M3, M4, M5, M6);
506impl_contains_all!(M1, M2, M3, M4, M5, M6, M7);
507impl_contains_all!(M1, M2, M3, M4, M5, M6, M7, M8);
508
509struct ContainsAllMatcher<Tup> {
511 matchers: Tup,
512}
513
514impl<C, Tup> Matcher<C> for ContainsAllMatcher<Tup>
515where
516 C: Sequence + ?Sized,
517 C::Item: fmt::Debug,
518 Tup: ContainsAll<C::Item>,
519{
520 fn check(&self, actual: &C) -> MatchResult {
521 let items = actual.sequence_items();
522 match self.matchers.first_unsatisfied(&items) {
523 None => MatchResult::pass(),
524 Some(unsatisfied) => MatchResult::fail(Mismatch::new(
525 Description::labeled("a sequence containing an item that is", unsatisfied),
526 format!("{items:?}"),
527 )),
528 }
529 }
530
531 fn description(&self) -> Description {
532 Description::labeled("a sequence containing all of", self.matchers.describe())
533 }
534}
535
536#[must_use]
551pub fn contains_all<C, Tup>(matchers: Tup) -> impl Matcher<C>
552where
553 C: Sequence + ?Sized,
554 C::Item: fmt::Debug,
555 Tup: ContainsAll<C::Item>,
556{
557 ContainsAllMatcher { matchers }
558}
559
560#[cfg(test)]
561mod tests {
562 use std::collections::{BTreeSet, HashSet, VecDeque};
563
564 use test_better_core::{OrFail, TestResult};
565
566 use super::*;
567 use crate::{check, eq, gt, is_false, is_true, lt};
568
569 #[test]
570 fn have_len_matches_the_exact_length() -> TestResult {
571 check!(have_len(3).check(&vec![1, 2, 3]).matched).satisfies(is_true())?;
572 let failure = have_len(3)
573 .check(&vec![1, 2])
574 .failure
575 .or_fail_with("length 2 is not 3")?;
576 check!(failure.expected.to_string()).satisfies(eq("a sequence of length 3".to_string()))?;
577 check!(failure.actual).satisfies(eq("a sequence of length 2".to_string()))?;
578 Ok(())
579 }
580
581 #[test]
582 fn items_collects_an_iterator_into_a_sequence() -> TestResult {
583 let lazy = (1..=3).map(|n| n * 10);
588 let collected = items(lazy);
589 check!(have_len(3).check(&collected).matched).satisfies(is_true())?;
590 check!(contains(eq(20)).check(&collected).matched).satisfies(is_true())?;
591 let failure = contains(eq(99))
592 .check(&collected)
593 .failure
594 .or_fail_with("99 is not in the iterator")?;
595 check!(failure.actual).satisfies(eq("[10, 20, 30]".to_string()))?;
596 Ok(())
597 }
598
599 #[test]
600 fn items_on_an_empty_iterator_is_empty() -> TestResult {
601 let empty: Items<i32> = items(std::iter::empty());
602 check!(is_empty().check(&empty).matched).satisfies(is_true())?;
603 Ok(())
604 }
605
606 #[test]
607 fn is_empty_and_is_not_empty_are_opposites() -> TestResult {
608 check!(is_empty().check(&Vec::<i32>::new()).matched).satisfies(is_true())?;
609 check!(is_empty().check(&vec![1]).matched).satisfies(is_false())?;
610 check!(is_not_empty().check(&vec![1]).matched).satisfies(is_true())?;
611 check!(is_not_empty().check(&Vec::<i32>::new()).matched).satisfies(is_false())?;
612 Ok(())
613 }
614
615 #[test]
616 fn contains_finds_a_matching_item() -> TestResult {
617 check!(contains(eq(2)).check(&vec![1, 2, 3]).matched).satisfies(is_true())?;
618 let failure = contains(eq(9))
619 .check(&vec![1, 2, 3])
620 .failure
621 .or_fail_with("9 is not in the sequence")?;
622 check!(failure.actual).satisfies(eq("[1, 2, 3]".to_string()))?;
623 Ok(())
624 }
625
626 #[test]
627 fn every_names_the_index_of_the_first_failure() -> TestResult {
628 check!(every(gt(0)).check(&vec![1, 2, 3]).matched).satisfies(is_true())?;
629 let failure = every(gt(0))
630 .check(&vec![1, 2, -1, 4])
631 .failure
632 .or_fail_with("-1 is not greater than 0")?;
633 check!(failure.actual.contains("index 2")).satisfies(is_true())?;
634 Ok(())
635 }
636
637 #[test]
638 fn at_least_one_matches_when_some_item_does() -> TestResult {
639 check!(at_least_one(gt(2)).check(&vec![1, 2, 3]).matched).satisfies(is_true())?;
640 check!(at_least_one(gt(9)).check(&vec![1, 2, 3]).matched).satisfies(is_false())?;
641 Ok(())
642 }
643
644 #[test]
645 fn contains_in_order_respects_order_but_not_adjacency() -> TestResult {
646 check!(
647 contains_in_order([eq(2), eq(4)])
648 .check(&vec![1, 2, 3, 4])
649 .matched
650 )
651 .satisfies(is_true())?;
652 let failure = contains_in_order([eq(4), eq(2)])
653 .check(&vec![1, 2, 3, 4])
654 .failure
655 .or_fail_with("2 does not come after 4")?;
656 check!(failure.actual.contains("matcher at index 1")).satisfies(is_true())?;
657 Ok(())
658 }
659
660 #[test]
661 fn contains_all_requires_every_matcher_to_be_satisfied() -> TestResult {
662 check!(contains_all((eq(1), gt(2))).check(&vec![1, 2, 3]).matched).satisfies(is_true())?;
663 let failure = contains_all((eq(1), gt(9)))
664 .check(&vec![1, 2, 3])
665 .failure
666 .or_fail_with("nothing is greater than 9")?;
667 check!(failure.expected.to_string().contains("greater than 9")).satisfies(is_true())?;
668 Ok(())
669 }
670
671 #[test]
672 fn collection_matchers_work_across_collection_types() -> TestResult {
673 let deque: VecDeque<i32> = VecDeque::from(vec![1, 2, 3]);
674 check!(have_len(3).check(&deque).matched).satisfies(is_true())?;
675
676 let btree: BTreeSet<i32> = BTreeSet::from([1, 2, 3]);
677 check!(contains(eq(2)).check(&btree).matched).satisfies(is_true())?;
678
679 let set: HashSet<i32> = HashSet::from([1, 2, 3]);
680 check!(every(gt(0)).check(&set).matched).satisfies(is_true())?;
681
682 let slice: &[i32] = &[10, 20, 30];
683 check!(contains_in_order([eq(10), eq(30)]).check(&slice).matched).satisfies(is_true())?;
684
685 let array = [1, 2, 3];
686 check!(every(lt(4)).check(&array).matched).satisfies(is_true())?;
687 Ok(())
688 }
689}