nextest_runner/
test_filter.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Filtering tests based on user-specified parameters.
5//!
6//! The main structure in this module is [`TestFilter`], which is created by a [`TestFilterBuilder`].
7
8use crate::{
9    errors::TestFilterBuilderError,
10    list::RustTestArtifact,
11    partition::{Partitioner, PartitionerBuilder},
12    run_mode::NextestRunMode,
13};
14use aho_corasick::AhoCorasick;
15use nextest_filtering::{EvalContext, Filterset, TestQuery};
16use nextest_metadata::{FilterMatch, MismatchReason, RustTestKind, TestCaseName};
17use std::{collections::HashSet, fmt, mem};
18
19/// Whether to run ignored tests.
20#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
21pub enum RunIgnored {
22    /// Only run tests that aren't ignored.
23    ///
24    /// This is the default.
25    #[default]
26    Default,
27
28    /// Only run tests that are ignored.
29    Only,
30
31    /// Run both ignored and non-ignored tests.
32    All,
33}
34
35/// A higher-level filter.
36#[derive(Clone, Copy, Debug)]
37pub enum FilterBound {
38    /// Filter with the default set.
39    DefaultSet,
40
41    /// Do not perform any higher-level filtering.
42    All,
43}
44
45#[derive(Clone, Debug, Eq, PartialEq)]
46/// Filters Binaries based on `TestFilterExprs`.
47pub struct BinaryFilter {
48    exprs: TestFilterExprs,
49}
50
51impl BinaryFilter {
52    /// Creates a new `BinaryFilter` from `exprs`.
53    ///
54    /// If `exprs` is an empty slice, all binaries will match.
55    pub fn new(exprs: Vec<Filterset>) -> Self {
56        let exprs = if exprs.is_empty() {
57            TestFilterExprs::All
58        } else {
59            TestFilterExprs::Sets(exprs)
60        };
61        Self { exprs }
62    }
63
64    /// Returns a value indicating whether this binary should or should not be run to obtain the
65    /// list of tests within it.
66    pub fn check_match(
67        &self,
68        test_binary: &RustTestArtifact<'_>,
69        ecx: &EvalContext<'_>,
70        bound: FilterBound,
71    ) -> FilterBinaryMatch {
72        let query = test_binary.to_binary_query();
73        let expr_result = match &self.exprs {
74            TestFilterExprs::All => FilterBinaryMatch::Definite,
75            TestFilterExprs::Sets(exprs) => exprs.iter().fold(
76                FilterBinaryMatch::Mismatch {
77                    // Just use this as a placeholder as the lowest possible value.
78                    reason: BinaryMismatchReason::Expression,
79                },
80                |acc, expr| {
81                    acc.logic_or(FilterBinaryMatch::from_result(
82                        expr.matches_binary(&query, ecx),
83                        BinaryMismatchReason::Expression,
84                    ))
85                },
86            ),
87        };
88
89        // If none of the expressions matched, then there's no need to check the default set.
90        if !expr_result.is_match() {
91            return expr_result;
92        }
93
94        match bound {
95            FilterBound::All => expr_result,
96            FilterBound::DefaultSet => expr_result.logic_and(FilterBinaryMatch::from_result(
97                ecx.default_filter.matches_binary(&query, ecx),
98                BinaryMismatchReason::DefaultSet,
99            )),
100        }
101    }
102}
103
104/// A builder for `TestFilter` instances.
105#[derive(Clone, Debug, Eq, PartialEq)]
106pub struct TestFilterBuilder {
107    mode: NextestRunMode,
108    run_ignored: RunIgnored,
109    partitioner_builder: Option<PartitionerBuilder>,
110    patterns: ResolvedFilterPatterns,
111    binary_filter: BinaryFilter,
112}
113
114#[derive(Clone, Debug, Eq, PartialEq)]
115enum TestFilterExprs {
116    /// No filtersets specified to filter against -- match the default set of tests.
117    All,
118
119    /// Filtersets to match against. A match can be against any of the sets.
120    Sets(Vec<Filterset>),
121}
122
123/// A set of string-based patterns for test filters.
124#[derive(Clone, Debug, Eq, PartialEq)]
125pub enum TestFilterPatterns {
126    /// The only patterns specified (if any) are skip patterns: match the default set of tests minus
127    /// the skip patterns.
128    SkipOnly {
129        /// Skip patterns.
130        skip_patterns: Vec<String>,
131
132        /// Skip patterns to match exactly.
133        skip_exact_patterns: HashSet<String>,
134    },
135
136    /// At least one substring or exact pattern is specified.
137    ///
138    /// In other words, at least one of `patterns` or `exact_patterns` should be non-empty.
139    ///
140    /// A fully empty `Patterns` is logically sound (will match no tests), but never created by
141    /// nextest itself.
142    Patterns {
143        /// Substring patterns.
144        patterns: Vec<String>,
145
146        /// Patterns to match exactly.
147        exact_patterns: HashSet<String>,
148
149        /// Patterns passed in via `--skip`.
150        skip_patterns: Vec<String>,
151
152        /// Skip patterns to match exactly.
153        skip_exact_patterns: HashSet<String>,
154    },
155}
156
157impl Default for TestFilterPatterns {
158    fn default() -> Self {
159        Self::SkipOnly {
160            skip_patterns: Vec::new(),
161            skip_exact_patterns: HashSet::new(),
162        }
163    }
164}
165
166impl TestFilterPatterns {
167    /// Initializes a new `TestFilterPatterns` with a set of substring patterns specified before
168    /// `--`.
169    ///
170    /// An empty slice matches all tests.
171    pub fn new(substring_patterns: Vec<String>) -> Self {
172        if substring_patterns.is_empty() {
173            Self::default()
174        } else {
175            Self::Patterns {
176                patterns: substring_patterns,
177                exact_patterns: HashSet::new(),
178                skip_patterns: Vec::new(),
179                skip_exact_patterns: HashSet::new(),
180            }
181        }
182    }
183
184    /// Adds a regular pattern to the set of patterns.
185    pub fn add_substring_pattern(&mut self, pattern: String) {
186        match self {
187            Self::SkipOnly {
188                skip_patterns,
189                skip_exact_patterns,
190            } => {
191                *self = Self::Patterns {
192                    patterns: vec![pattern],
193                    exact_patterns: HashSet::new(),
194                    skip_patterns: mem::take(skip_patterns),
195                    skip_exact_patterns: mem::take(skip_exact_patterns),
196                };
197            }
198            Self::Patterns { patterns, .. } => {
199                patterns.push(pattern);
200            }
201        }
202    }
203
204    /// Adds an exact pattern to the set of patterns.
205    pub fn add_exact_pattern(&mut self, pattern: String) {
206        match self {
207            Self::SkipOnly {
208                skip_patterns,
209                skip_exact_patterns,
210            } => {
211                *self = Self::Patterns {
212                    patterns: Vec::new(),
213                    exact_patterns: [pattern].into_iter().collect(),
214                    skip_patterns: mem::take(skip_patterns),
215                    skip_exact_patterns: mem::take(skip_exact_patterns),
216                };
217            }
218            Self::Patterns { exact_patterns, .. } => {
219                exact_patterns.insert(pattern);
220            }
221        }
222    }
223
224    /// Adds a skip pattern to the set of patterns.
225    pub fn add_skip_pattern(&mut self, pattern: String) {
226        match self {
227            Self::SkipOnly { skip_patterns, .. } => {
228                skip_patterns.push(pattern);
229            }
230            Self::Patterns { skip_patterns, .. } => {
231                skip_patterns.push(pattern);
232            }
233        }
234    }
235
236    /// Adds a skip pattern to match exactly.
237    pub fn add_skip_exact_pattern(&mut self, pattern: String) {
238        match self {
239            Self::SkipOnly {
240                skip_exact_patterns,
241                ..
242            } => {
243                skip_exact_patterns.insert(pattern);
244            }
245            Self::Patterns {
246                skip_exact_patterns,
247                ..
248            } => {
249                skip_exact_patterns.insert(pattern);
250            }
251        }
252    }
253
254    fn resolve(self) -> Result<ResolvedFilterPatterns, TestFilterBuilderError> {
255        match self {
256            Self::SkipOnly {
257                mut skip_patterns,
258                skip_exact_patterns,
259            } => {
260                if skip_patterns.is_empty() {
261                    Ok(ResolvedFilterPatterns::All)
262                } else {
263                    // sort_unstable allows the PartialEq implementation to work correctly.
264                    skip_patterns.sort_unstable();
265                    let skip_pattern_matcher = Box::new(AhoCorasick::new(&skip_patterns)?);
266                    Ok(ResolvedFilterPatterns::SkipOnly {
267                        skip_patterns,
268                        skip_pattern_matcher,
269                        skip_exact_patterns,
270                    })
271                }
272            }
273            Self::Patterns {
274                mut patterns,
275                exact_patterns,
276                mut skip_patterns,
277                skip_exact_patterns,
278            } => {
279                // sort_unstable allows the PartialEq implementation to work correctly.
280                patterns.sort_unstable();
281                skip_patterns.sort_unstable();
282
283                let pattern_matcher = Box::new(AhoCorasick::new(&patterns)?);
284                let skip_pattern_matcher = Box::new(AhoCorasick::new(&skip_patterns)?);
285
286                Ok(ResolvedFilterPatterns::Patterns {
287                    patterns,
288                    exact_patterns,
289                    skip_patterns,
290                    skip_exact_patterns,
291                    pattern_matcher,
292                    skip_pattern_matcher,
293                })
294            }
295        }
296    }
297}
298
299#[derive(Clone, Debug, Default)]
300enum ResolvedFilterPatterns {
301    /// Match all tests.
302    ///
303    /// This is mostly for convenience -- it's equivalent to `SkipOnly` with an empty set of skip
304    /// patterns.
305    #[default]
306    All,
307
308    /// Match all tests except those that match the skip patterns.
309    SkipOnly {
310        skip_patterns: Vec<String>,
311        skip_pattern_matcher: Box<AhoCorasick>,
312        skip_exact_patterns: HashSet<String>,
313    },
314
315    /// Match tests that match the patterns and don't match the skip patterns.
316    Patterns {
317        patterns: Vec<String>,
318        exact_patterns: HashSet<String>,
319        skip_patterns: Vec<String>,
320        skip_exact_patterns: HashSet<String>,
321        pattern_matcher: Box<AhoCorasick>,
322        skip_pattern_matcher: Box<AhoCorasick>,
323    },
324}
325
326impl ResolvedFilterPatterns {
327    fn name_match(&self, test_name: &TestCaseName) -> FilterNameMatch {
328        let test_name = test_name.as_str();
329        match self {
330            Self::All => FilterNameMatch::MatchEmptyPatterns,
331            Self::SkipOnly {
332                // skip_patterns is covered by the matcher.
333                skip_patterns: _,
334                skip_exact_patterns,
335                skip_pattern_matcher,
336            } => {
337                if skip_exact_patterns.contains(test_name)
338                    || skip_pattern_matcher.is_match(test_name)
339                {
340                    FilterNameMatch::Mismatch(MismatchReason::String)
341                } else {
342                    FilterNameMatch::MatchWithPatterns
343                }
344            }
345            Self::Patterns {
346                // patterns is covered by the matcher.
347                patterns: _,
348                exact_patterns,
349                // skip_patterns is covered by the matcher.
350                skip_patterns: _,
351                skip_exact_patterns,
352                pattern_matcher,
353                skip_pattern_matcher,
354            } => {
355                // skip overrides all other patterns.
356                if skip_exact_patterns.contains(test_name)
357                    || skip_pattern_matcher.is_match(test_name)
358                {
359                    FilterNameMatch::Mismatch(MismatchReason::String)
360                } else if exact_patterns.contains(test_name) || pattern_matcher.is_match(test_name)
361                {
362                    FilterNameMatch::MatchWithPatterns
363                } else {
364                    FilterNameMatch::Mismatch(MismatchReason::String)
365                }
366            }
367        }
368    }
369}
370
371impl PartialEq for ResolvedFilterPatterns {
372    fn eq(&self, other: &Self) -> bool {
373        match (self, other) {
374            (Self::All, Self::All) => true,
375            (
376                Self::SkipOnly {
377                    skip_patterns,
378                    skip_exact_patterns,
379                    // The matcher is derived from `skip_patterns`, so it can be ignored.
380                    skip_pattern_matcher: _,
381                },
382                Self::SkipOnly {
383                    skip_patterns: other_skip_patterns,
384                    skip_exact_patterns: other_skip_exact_patterns,
385                    skip_pattern_matcher: _,
386                },
387            ) => {
388                skip_patterns == other_skip_patterns
389                    && skip_exact_patterns == other_skip_exact_patterns
390            }
391            (
392                Self::Patterns {
393                    patterns,
394                    exact_patterns,
395                    skip_patterns,
396                    skip_exact_patterns,
397                    // The matchers are derived from `patterns` and `skip_patterns`, so they can be
398                    // ignored.
399                    pattern_matcher: _,
400                    skip_pattern_matcher: _,
401                },
402                Self::Patterns {
403                    patterns: other_patterns,
404                    exact_patterns: other_exact_patterns,
405                    skip_patterns: other_skip_patterns,
406                    skip_exact_patterns: other_skip_exact_patterns,
407                    pattern_matcher: _,
408                    skip_pattern_matcher: _,
409                },
410            ) => {
411                patterns == other_patterns
412                    && exact_patterns == other_exact_patterns
413                    && skip_patterns == other_skip_patterns
414                    && skip_exact_patterns == other_skip_exact_patterns
415            }
416            _ => false,
417        }
418    }
419}
420
421impl Eq for ResolvedFilterPatterns {}
422
423impl TestFilterBuilder {
424    /// Creates a new `TestFilterBuilder` from the given patterns.
425    ///
426    /// If an empty slice is passed, the test filter matches all possible test names.
427    pub fn new(
428        mode: NextestRunMode,
429        run_ignored: RunIgnored,
430        partitioner_builder: Option<PartitionerBuilder>,
431        patterns: TestFilterPatterns,
432        exprs: Vec<Filterset>,
433    ) -> Result<Self, TestFilterBuilderError> {
434        let patterns = patterns.resolve()?;
435
436        let binary_filter = BinaryFilter::new(exprs);
437
438        Ok(Self {
439            mode,
440            run_ignored,
441            partitioner_builder,
442            patterns,
443            binary_filter,
444        })
445    }
446
447    /// Returns a value indicating whether this binary should or should not be run to obtain the
448    /// list of tests within it.
449    ///
450    /// This method is implemented directly on `TestFilterBuilder`. The statefulness of `TestFilter`
451    /// is only used for counted test partitioning, and is not currently relevant for binaries.
452    pub fn filter_binary_match(
453        &self,
454        test_binary: &RustTestArtifact<'_>,
455        ecx: &EvalContext<'_>,
456        bound: FilterBound,
457    ) -> FilterBinaryMatch {
458        self.binary_filter.check_match(test_binary, ecx, bound)
459    }
460
461    /// Creates a new `TestFilterBuilder` that matches the default set of tests.
462    pub fn default_set(mode: NextestRunMode, run_ignored: RunIgnored) -> Self {
463        let binary_filter = BinaryFilter::new(Vec::new());
464        Self {
465            mode,
466            run_ignored,
467            partitioner_builder: None,
468            patterns: ResolvedFilterPatterns::default(),
469            binary_filter,
470        }
471    }
472
473    /// Returns the nextest execution mode.
474    pub fn mode(&self) -> NextestRunMode {
475        self.mode
476    }
477
478    /// Creates a new test filter scoped to a single binary.
479    ///
480    /// This test filter may be stateful.
481    pub fn build(&self) -> TestFilter<'_> {
482        let partitioner = self
483            .partitioner_builder
484            .as_ref()
485            .map(|partitioner_builder| partitioner_builder.build());
486        TestFilter {
487            builder: self,
488            partitioner,
489        }
490    }
491}
492
493/// Whether a binary matched filters and should be run to obtain the list of tests within.
494///
495/// The result of [`TestFilterBuilder::filter_binary_match`].
496#[derive(Copy, Clone, Debug)]
497pub enum FilterBinaryMatch {
498    /// This is a definite match -- binaries should be run.
499    Definite,
500
501    /// We don't know for sure -- binaries should be run.
502    Possible,
503
504    /// This is a definite mismatch -- binaries should not be run.
505    Mismatch {
506        /// The reason for the mismatch.
507        reason: BinaryMismatchReason,
508    },
509}
510
511impl FilterBinaryMatch {
512    fn from_result(result: Option<bool>, reason: BinaryMismatchReason) -> Self {
513        match result {
514            Some(true) => Self::Definite,
515            None => Self::Possible,
516            Some(false) => Self::Mismatch { reason },
517        }
518    }
519
520    fn is_match(self) -> bool {
521        match self {
522            Self::Definite | Self::Possible => true,
523            Self::Mismatch { .. } => false,
524        }
525    }
526
527    fn logic_or(self, other: Self) -> Self {
528        match (self, other) {
529            (Self::Definite, _) | (_, Self::Definite) => Self::Definite,
530            (Self::Possible, _) | (_, Self::Possible) => Self::Possible,
531            (Self::Mismatch { reason: r1 }, Self::Mismatch { reason: r2 }) => Self::Mismatch {
532                reason: r1.prefer_expression(r2),
533            },
534        }
535    }
536
537    fn logic_and(self, other: Self) -> Self {
538        match (self, other) {
539            (Self::Definite, Self::Definite) => Self::Definite,
540            (Self::Definite, Self::Possible)
541            | (Self::Possible, Self::Definite)
542            | (Self::Possible, Self::Possible) => Self::Possible,
543            (Self::Mismatch { reason: r1 }, Self::Mismatch { reason: r2 }) => {
544                // If one of the mismatch reasons is `Expression` and the other is `DefaultSet`, we
545                // return Expression.
546                Self::Mismatch {
547                    reason: r1.prefer_expression(r2),
548                }
549            }
550            (Self::Mismatch { reason }, _) | (_, Self::Mismatch { reason }) => {
551                Self::Mismatch { reason }
552            }
553        }
554    }
555}
556
557/// The reason for a binary mismatch.
558///
559/// Part of [`FilterBinaryMatch`], as returned by [`TestFilterBuilder::filter_binary_match`].
560#[derive(Copy, Clone, Debug, Eq, PartialEq)]
561pub enum BinaryMismatchReason {
562    /// The binary doesn't match any of the provided filtersets.
563    Expression,
564
565    /// No filtersets were specified and the binary doesn't match the default set.
566    DefaultSet,
567}
568
569impl BinaryMismatchReason {
570    fn prefer_expression(self, other: Self) -> Self {
571        match (self, other) {
572            (Self::Expression, _) | (_, Self::Expression) => Self::Expression,
573            (Self::DefaultSet, Self::DefaultSet) => Self::DefaultSet,
574        }
575    }
576}
577
578impl fmt::Display for BinaryMismatchReason {
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        match self {
581            Self::Expression => write!(f, "didn't match filtersets"),
582            Self::DefaultSet => write!(f, "didn't match the default set"),
583        }
584    }
585}
586
587/// Test filter, scoped to a single binary.
588#[derive(Debug)]
589pub struct TestFilter<'builder> {
590    builder: &'builder TestFilterBuilder,
591    partitioner: Option<Box<dyn Partitioner>>,
592}
593
594impl TestFilter<'_> {
595    /// Returns an enum describing the match status of this filter.
596    pub fn filter_match(
597        &mut self,
598        test_binary: &RustTestArtifact<'_>,
599        test_name: &TestCaseName,
600        test_kind: &RustTestKind,
601        ecx: &EvalContext<'_>,
602        bound: FilterBound,
603        ignored: bool,
604    ) -> FilterMatch {
605        if let Some(mismatch) = self.filter_benchmark_mismatch(test_kind) {
606            return mismatch;
607        }
608
609        if let Some(mismatch) = self.filter_ignored_mismatch(ignored) {
610            return mismatch;
611        }
612
613        {
614            // ---
615            // NOTE
616            // ---
617            //
618            // Previously, if either expression OR string filters matched, we'd run the tests.
619            // The current (stable) implementation is that *both* the expression AND the string
620            // filters should match.
621            //
622            // This is because we try and skip running test binaries which don't match
623            // expression filters. So for example:
624            //
625            //     cargo nextest run -E 'binary(foo)' test_bar
626            //
627            // would not even get to the point of enumerating the tests not in binary(foo), thus
628            // not running any test_bars in the workspace. But, with the OR semantics:
629            //
630            //     cargo nextest run -E 'binary(foo) or test(test_foo)' test_bar
631            //
632            // would run all the test_bars in the repo. This is inconsistent, so nextest must
633            // use AND semantics.
634            use FilterNameMatch::*;
635            match (
636                self.filter_name_match(test_name),
637                self.filter_expression_match(test_binary, test_name, ecx, bound),
638            ) {
639                // Tests must be accepted by both expressions and filters.
640                (
641                    MatchEmptyPatterns | MatchWithPatterns,
642                    MatchEmptyPatterns | MatchWithPatterns,
643                ) => {}
644                // If rejected by at least one of the filtering strategies, the test is
645                // rejected. Note we use the _name_ mismatch reason first. That's because
646                // expression-based matches can also match against the default set. If a test
647                // fails both name and expression matches, then the name reason is more directly
648                // relevant.
649                (Mismatch(reason), _) | (_, Mismatch(reason)) => {
650                    return FilterMatch::Mismatch { reason };
651                }
652            }
653        }
654
655        // Note that partition-based filtering MUST come after all other kinds
656        // of filtering, so that count-based bucketing applies after ignored,
657        // name and expression matching. This also means that mutable count
658        // state must be maintained by the partitioner.
659        if let Some(mismatch) = self.filter_partition_mismatch(test_name) {
660            return mismatch;
661        }
662
663        FilterMatch::Matches
664    }
665
666    fn filter_benchmark_mismatch(&self, test_kind: &RustTestKind) -> Option<FilterMatch> {
667        if self.builder.mode == NextestRunMode::Benchmark && test_kind != &RustTestKind::BENCH {
668            Some(FilterMatch::Mismatch {
669                reason: MismatchReason::NotBenchmark,
670            })
671        } else {
672            None
673        }
674    }
675
676    fn filter_ignored_mismatch(&self, ignored: bool) -> Option<FilterMatch> {
677        match self.builder.run_ignored {
678            RunIgnored::Only => {
679                if !ignored {
680                    return Some(FilterMatch::Mismatch {
681                        reason: MismatchReason::Ignored,
682                    });
683                }
684            }
685            RunIgnored::Default => {
686                if ignored {
687                    return Some(FilterMatch::Mismatch {
688                        reason: MismatchReason::Ignored,
689                    });
690                }
691            }
692            _ => {}
693        }
694        None
695    }
696
697    fn filter_name_match(&self, test_name: &TestCaseName) -> FilterNameMatch {
698        self.builder.patterns.name_match(test_name)
699    }
700
701    fn filter_expression_match(
702        &self,
703        test_binary: &RustTestArtifact<'_>,
704        test_name: &TestCaseName,
705        ecx: &EvalContext<'_>,
706        bound: FilterBound,
707    ) -> FilterNameMatch {
708        let query = TestQuery {
709            binary_query: test_binary.to_binary_query(),
710            test_name,
711        };
712
713        let expr_result = match &self.builder.binary_filter.exprs {
714            TestFilterExprs::All => FilterNameMatch::MatchEmptyPatterns,
715            TestFilterExprs::Sets(exprs) => {
716                if exprs.iter().any(|expr| expr.matches_test(&query, ecx)) {
717                    FilterNameMatch::MatchWithPatterns
718                } else {
719                    return FilterNameMatch::Mismatch(MismatchReason::Expression);
720                }
721            }
722        };
723
724        match bound {
725            FilterBound::All => expr_result,
726            FilterBound::DefaultSet => {
727                if ecx.default_filter.matches_test(&query, ecx) {
728                    expr_result
729                } else {
730                    FilterNameMatch::Mismatch(MismatchReason::DefaultFilter)
731                }
732            }
733        }
734    }
735
736    fn filter_partition_mismatch(&mut self, test_name: &TestCaseName) -> Option<FilterMatch> {
737        let partition_match = match &mut self.partitioner {
738            Some(partitioner) => partitioner.test_matches(test_name.as_str()),
739            None => true,
740        };
741        if partition_match {
742            None
743        } else {
744            Some(FilterMatch::Mismatch {
745                reason: MismatchReason::Partition,
746            })
747        }
748    }
749}
750
751#[derive(Clone, Debug, Eq, PartialEq)]
752enum FilterNameMatch {
753    /// Match because there are no patterns.
754    MatchEmptyPatterns,
755    /// Matches with non-empty patterns.
756    MatchWithPatterns,
757    /// Mismatch.
758    Mismatch(MismatchReason),
759}
760
761impl FilterNameMatch {
762    #[cfg(test)]
763    fn is_match(&self) -> bool {
764        match self {
765            Self::MatchEmptyPatterns | Self::MatchWithPatterns => true,
766            Self::Mismatch(_) => false,
767        }
768    }
769}
770
771#[cfg(test)]
772mod tests {
773    use super::*;
774    use proptest::{collection::vec, prelude::*};
775    use test_strategy::proptest;
776
777    #[proptest(cases = 50)]
778    fn proptest_empty(#[strategy(vec(any::<String>(), 0..16))] test_names: Vec<String>) {
779        let patterns = TestFilterPatterns::default();
780        let test_filter = TestFilterBuilder::new(
781            NextestRunMode::Test,
782            RunIgnored::Default,
783            None,
784            patterns,
785            Vec::new(),
786        )
787        .unwrap();
788        let single_filter = test_filter.build();
789        for test_name in test_names {
790            let test_name = TestCaseName::new(&test_name);
791            prop_assert!(single_filter.filter_name_match(&test_name).is_match());
792        }
793    }
794
795    // Test that exact names match.
796    #[proptest(cases = 50)]
797    fn proptest_exact(#[strategy(vec(any::<String>(), 0..16))] test_names: Vec<String>) {
798        // Test with the default matcher.
799        let patterns = TestFilterPatterns::new(test_names.clone());
800        let test_filter = TestFilterBuilder::new(
801            NextestRunMode::Test,
802            RunIgnored::Default,
803            None,
804            patterns,
805            Vec::new(),
806        )
807        .unwrap();
808        let single_filter = test_filter.build();
809        for test_name in &test_names {
810            let test_name = TestCaseName::new(test_name);
811            prop_assert!(single_filter.filter_name_match(&test_name).is_match());
812        }
813
814        // Test with the exact matcher.
815        let mut patterns = TestFilterPatterns::default();
816        for test_name in &test_names {
817            patterns.add_exact_pattern(test_name.clone());
818        }
819        let test_filter = TestFilterBuilder::new(
820            NextestRunMode::Test,
821            RunIgnored::Default,
822            None,
823            patterns,
824            Vec::new(),
825        )
826        .unwrap();
827        let single_filter = test_filter.build();
828        for test_name in &test_names {
829            let test_name = TestCaseName::new(test_name);
830            prop_assert!(single_filter.filter_name_match(&test_name).is_match());
831        }
832    }
833
834    // Test that substrings match.
835    #[proptest(cases = 50)]
836    fn proptest_substring(
837        #[strategy(vec([any::<String>(); 3], 0..16))] substring_prefix_suffixes: Vec<[String; 3]>,
838    ) {
839        let mut patterns = TestFilterPatterns::default();
840        let mut test_names = Vec::with_capacity(substring_prefix_suffixes.len());
841        for [substring, prefix, suffix] in substring_prefix_suffixes {
842            test_names.push(prefix + &substring + &suffix);
843            patterns.add_substring_pattern(substring);
844        }
845
846        let test_filter = TestFilterBuilder::new(
847            NextestRunMode::Test,
848            RunIgnored::Default,
849            None,
850            patterns,
851            Vec::new(),
852        )
853        .unwrap();
854        let single_filter = test_filter.build();
855        for test_name in test_names {
856            let test_name = TestCaseName::new(&test_name);
857            prop_assert!(single_filter.filter_name_match(&test_name).is_match());
858        }
859    }
860
861    // Test that dropping a character from a string doesn't match.
862    #[proptest(cases = 50)]
863    fn proptest_no_match(substring: String, prefix: String, suffix: String) {
864        prop_assume!(!substring.is_empty() && !prefix.is_empty() && !suffix.is_empty());
865        let pattern = prefix + &substring + &suffix;
866        let patterns = TestFilterPatterns::new(vec![pattern]);
867        let test_filter = TestFilterBuilder::new(
868            NextestRunMode::Test,
869            RunIgnored::Default,
870            None,
871            patterns,
872            Vec::new(),
873        )
874        .unwrap();
875        let single_filter = test_filter.build();
876        let substring = TestCaseName::new(&substring);
877        prop_assert!(!single_filter.filter_name_match(&substring).is_match());
878    }
879
880    fn test_name(s: &str) -> TestCaseName {
881        TestCaseName::new(s)
882    }
883
884    #[test]
885    fn pattern_examples() {
886        let mut patterns = TestFilterPatterns::new(vec!["foo".to_string()]);
887        patterns.add_substring_pattern("bar".to_string());
888        patterns.add_exact_pattern("baz".to_string());
889        patterns.add_skip_pattern("quux".to_string());
890        patterns.add_skip_exact_pattern("quuz".to_string());
891
892        let resolved = patterns.clone().resolve().unwrap();
893
894        // Test substring matches.
895        assert_eq!(
896            resolved.name_match(&test_name("foo")),
897            FilterNameMatch::MatchWithPatterns,
898        );
899        assert_eq!(
900            resolved.name_match(&test_name("1foo2")),
901            FilterNameMatch::MatchWithPatterns,
902        );
903        assert_eq!(
904            resolved.name_match(&test_name("bar")),
905            FilterNameMatch::MatchWithPatterns,
906        );
907        assert_eq!(
908            resolved.name_match(&test_name("x_bar_y")),
909            FilterNameMatch::MatchWithPatterns,
910        );
911
912        // Test exact matches.
913        assert_eq!(
914            resolved.name_match(&test_name("baz")),
915            FilterNameMatch::MatchWithPatterns,
916        );
917        assert_eq!(
918            resolved.name_match(&test_name("abazb")),
919            FilterNameMatch::Mismatch(MismatchReason::String),
920        );
921
922        // Both substring and exact matches.
923        assert_eq!(
924            resolved.name_match(&test_name("bazfoo")),
925            FilterNameMatch::MatchWithPatterns,
926        );
927
928        // Skip patterns.
929        assert_eq!(
930            resolved.name_match(&test_name("quux")),
931            FilterNameMatch::Mismatch(MismatchReason::String),
932        );
933        assert_eq!(
934            resolved.name_match(&test_name("1quux2")),
935            FilterNameMatch::Mismatch(MismatchReason::String),
936        );
937
938        // Skip and substring patterns.
939        assert_eq!(
940            resolved.name_match(&test_name("quuxbar")),
941            FilterNameMatch::Mismatch(MismatchReason::String),
942        );
943
944        // Skip-exact patterns.
945        assert_eq!(
946            resolved.name_match(&test_name("quuz")),
947            FilterNameMatch::Mismatch(MismatchReason::String),
948        );
949
950        // Skip overrides regular patterns -- in this case, add `baz` to the skip list.
951        patterns.add_skip_pattern("baz".to_string());
952        let resolved = patterns.resolve().unwrap();
953        assert_eq!(
954            resolved.name_match(&test_name("quuxbaz")),
955            FilterNameMatch::Mismatch(MismatchReason::String),
956        );
957    }
958
959    #[test]
960    fn skip_only_pattern_examples() {
961        let mut patterns = TestFilterPatterns::default();
962        patterns.add_skip_pattern("foo".to_string());
963        patterns.add_skip_pattern("bar".to_string());
964        patterns.add_skip_exact_pattern("baz".to_string());
965
966        let resolved = patterns.clone().resolve().unwrap();
967
968        // Test substring matches.
969        assert_eq!(
970            resolved.name_match(&test_name("foo")),
971            FilterNameMatch::Mismatch(MismatchReason::String),
972        );
973        assert_eq!(
974            resolved.name_match(&test_name("1foo2")),
975            FilterNameMatch::Mismatch(MismatchReason::String),
976        );
977        assert_eq!(
978            resolved.name_match(&test_name("bar")),
979            FilterNameMatch::Mismatch(MismatchReason::String),
980        );
981        assert_eq!(
982            resolved.name_match(&test_name("x_bar_y")),
983            FilterNameMatch::Mismatch(MismatchReason::String),
984        );
985
986        // Test exact matches.
987        assert_eq!(
988            resolved.name_match(&test_name("baz")),
989            FilterNameMatch::Mismatch(MismatchReason::String),
990        );
991        assert_eq!(
992            resolved.name_match(&test_name("abazb")),
993            FilterNameMatch::MatchWithPatterns,
994        );
995
996        // Anything that doesn't match the skip filter should match.
997        assert_eq!(
998            resolved.name_match(&test_name("quux")),
999            FilterNameMatch::MatchWithPatterns,
1000        );
1001    }
1002
1003    #[test]
1004    fn empty_pattern_examples() {
1005        let patterns = TestFilterPatterns::default();
1006        let resolved = patterns.resolve().unwrap();
1007        assert_eq!(resolved, ResolvedFilterPatterns::All);
1008
1009        // Anything matches.
1010        assert_eq!(
1011            resolved.name_match(&test_name("foo")),
1012            FilterNameMatch::MatchEmptyPatterns,
1013        );
1014        assert_eq!(
1015            resolved.name_match(&test_name("1foo2")),
1016            FilterNameMatch::MatchEmptyPatterns,
1017        );
1018        assert_eq!(
1019            resolved.name_match(&test_name("bar")),
1020            FilterNameMatch::MatchEmptyPatterns,
1021        );
1022        assert_eq!(
1023            resolved.name_match(&test_name("x_bar_y")),
1024            FilterNameMatch::MatchEmptyPatterns,
1025        );
1026        assert_eq!(
1027            resolved.name_match(&test_name("baz")),
1028            FilterNameMatch::MatchEmptyPatterns,
1029        );
1030        assert_eq!(
1031            resolved.name_match(&test_name("abazb")),
1032            FilterNameMatch::MatchEmptyPatterns,
1033        );
1034    }
1035}