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`].
7
8use crate::{
9    errors::TestFilterBuildError, list::RustTestArtifact, record::ComputedRerunInfo,
10    run_mode::NextestRunMode,
11};
12use aho_corasick::AhoCorasick;
13use nextest_filtering::{EvalContext, Filterset, TestQuery};
14use nextest_metadata::{FilterMatch, MismatchReason, RustTestKind, TestCaseName};
15use std::{collections::HashSet, fmt, mem};
16
17/// Whether to run ignored tests.
18#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
19pub enum RunIgnored {
20    /// Only run tests that aren't ignored.
21    ///
22    /// This is the default.
23    #[default]
24    Default,
25
26    /// Only run tests that are ignored.
27    Only,
28
29    /// Run both ignored and non-ignored tests.
30    All,
31}
32
33/// A higher-level filter.
34#[derive(Clone, Copy, Debug)]
35pub enum FilterBound {
36    /// Filter with the default set.
37    DefaultSet,
38
39    /// Do not perform any higher-level filtering.
40    All,
41}
42
43#[derive(Clone, Debug, Eq, PartialEq)]
44/// Filters Binaries based on `TestFilterExprs`.
45pub struct BinaryFilter {
46    exprs: TestFilterExprs,
47}
48
49impl BinaryFilter {
50    /// Creates a new `BinaryFilter` from `exprs`.
51    ///
52    /// If `exprs` is an empty slice, all binaries will match.
53    pub fn new(exprs: Vec<Filterset>) -> Self {
54        let exprs = if exprs.is_empty() {
55            TestFilterExprs::All
56        } else {
57            TestFilterExprs::Sets(exprs)
58        };
59        Self { exprs }
60    }
61
62    /// Returns a value indicating whether this binary should or should not be run to obtain the
63    /// list of tests within it.
64    pub fn check_match(
65        &self,
66        test_binary: &RustTestArtifact<'_>,
67        ecx: &EvalContext<'_>,
68        bound: FilterBound,
69    ) -> FilterBinaryMatch {
70        let query = test_binary.to_binary_query();
71        let expr_result = match &self.exprs {
72            TestFilterExprs::All => FilterBinaryMatch::Definite,
73            TestFilterExprs::Sets(exprs) => exprs.iter().fold(
74                FilterBinaryMatch::Mismatch {
75                    // Just use this as a placeholder as the lowest possible value.
76                    reason: BinaryMismatchReason::Expression,
77                },
78                |acc, expr| {
79                    acc.logic_or(FilterBinaryMatch::from_result(
80                        expr.matches_binary(&query, ecx),
81                        BinaryMismatchReason::Expression,
82                    ))
83                },
84            ),
85        };
86
87        // If none of the expressions matched, then there's no need to check the default set.
88        if !expr_result.is_match() {
89            return expr_result;
90        }
91
92        match bound {
93            FilterBound::All => expr_result,
94            FilterBound::DefaultSet => expr_result.logic_and(FilterBinaryMatch::from_result(
95                ecx.default_filter.matches_binary(&query, ecx),
96                BinaryMismatchReason::DefaultSet,
97            )),
98        }
99    }
100}
101
102/// Filter for selecting which tests to run.
103///
104/// Constructed via [`TestFilter::new`].
105#[derive(Clone, Debug)]
106pub struct TestFilter {
107    mode: NextestRunMode,
108    rerun_info: Option<ComputedRerunInfo>,
109    run_ignored: RunIgnored,
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, TestFilterBuildError> {
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 TestFilter {
424    /// Creates a new `TestFilter` 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        patterns: TestFilterPatterns,
431        exprs: Vec<Filterset>,
432    ) -> Result<Self, TestFilterBuildError> {
433        let patterns = patterns.resolve()?;
434
435        let binary_filter = BinaryFilter::new(exprs);
436
437        Ok(Self {
438            mode,
439            rerun_info: None,
440            run_ignored,
441            patterns,
442            binary_filter,
443        })
444    }
445
446    /// Returns a value indicating whether this binary should or should not be run to obtain the
447    /// list of tests within it.
448    pub fn filter_binary_match(
449        &self,
450        test_binary: &RustTestArtifact<'_>,
451        ecx: &EvalContext<'_>,
452        bound: FilterBound,
453    ) -> FilterBinaryMatch {
454        self.binary_filter.check_match(test_binary, ecx, bound)
455    }
456
457    /// Creates a new `TestFilter` that matches the default set of tests.
458    pub fn default_set(mode: NextestRunMode, run_ignored: RunIgnored) -> Self {
459        let binary_filter = BinaryFilter::new(Vec::new());
460        Self {
461            mode,
462            rerun_info: None,
463            run_ignored,
464            patterns: ResolvedFilterPatterns::default(),
465            binary_filter,
466        }
467    }
468
469    /// Sets the list of outstanding tests, if this is a rerun.
470    pub fn set_outstanding_tests(&mut self, rerun_info: ComputedRerunInfo) {
471        self.rerun_info = Some(rerun_info);
472    }
473
474    /// Returns the nextest execution mode.
475    pub fn mode(&self) -> NextestRunMode {
476        self.mode
477    }
478
479    /// Compares the patterns between two `TestFilter`s.
480    pub fn patterns_eq(&self, other: &Self) -> bool {
481        self.patterns == other.patterns
482    }
483
484    /// Consumes self, returning the underlying [`ComputedRerunInfo`] if any.
485    pub fn into_rerun_info(self) -> Option<ComputedRerunInfo> {
486        self.rerun_info
487    }
488
489    /// Returns an enum describing the match status of this filter.
490    pub fn filter_match(
491        &self,
492        test_binary: &RustTestArtifact<'_>,
493        test_name: &TestCaseName,
494        test_kind: &RustTestKind,
495        ecx: &EvalContext<'_>,
496        bound: FilterBound,
497        ignored: bool,
498    ) -> FilterMatch {
499        // Handle benchmark mismatches first.
500        if let Some(mismatch) = self.filter_benchmark_mismatch(test_kind) {
501            return mismatch;
502        }
503
504        // Check if this test already passed in a prior rerun.
505        if self.is_rerun_already_passed(test_binary, test_name) {
506            return FilterMatch::Mismatch {
507                reason: MismatchReason::RerunAlreadyPassed,
508            };
509        }
510
511        self.filter_match_base(test_binary, test_name, ecx, bound, ignored)
512    }
513
514    /// Core filter matching logic, used by `filter_match`.
515    fn filter_match_base(
516        &self,
517        test_binary: &RustTestArtifact<'_>,
518        test_name: &TestCaseName,
519        ecx: &EvalContext<'_>,
520        bound: FilterBound,
521        ignored: bool,
522    ) -> FilterMatch {
523        if let Some(mismatch) = self.filter_ignored_mismatch(ignored) {
524            return mismatch;
525        }
526
527        {
528            // ---
529            // NOTE
530            // ---
531            //
532            // Previously, if either expression OR string filters matched, we'd run the tests.
533            // The current (stable) implementation is that *both* the expression AND the string
534            // filters should match.
535            //
536            // This is because we try and skip running test binaries which don't match
537            // expression filters. So for example:
538            //
539            //     cargo nextest run -E 'binary(foo)' test_bar
540            //
541            // would not even get to the point of enumerating the tests not in binary(foo), thus
542            // not running any test_bars in the workspace. But, with the OR semantics:
543            //
544            //     cargo nextest run -E 'binary(foo) or test(test_foo)' test_bar
545            //
546            // would run all the test_bars in the repo. This is inconsistent, so nextest must
547            // use AND semantics.
548            use FilterNameMatch::*;
549            match (
550                self.filter_name_match(test_name),
551                self.filter_expression_match(test_binary, test_name, ecx, bound),
552            ) {
553                // Tests must be accepted by both expressions and filters.
554                (
555                    MatchEmptyPatterns | MatchWithPatterns,
556                    MatchEmptyPatterns | MatchWithPatterns,
557                ) => {}
558                // If rejected by at least one of the filtering strategies, the test is
559                // rejected. Note we use the _name_ mismatch reason first. That's because
560                // expression-based matches can also match against the default set. If a test
561                // fails both name and expression matches, then the name reason is more directly
562                // relevant.
563                (Mismatch(reason), _) | (_, Mismatch(reason)) => {
564                    return FilterMatch::Mismatch { reason };
565                }
566            }
567        }
568
569        FilterMatch::Matches
570    }
571
572    /// Returns true if this test already passed in a prior rerun.
573    fn is_rerun_already_passed(
574        &self,
575        test_binary: &RustTestArtifact<'_>,
576        test_name: &TestCaseName,
577    ) -> bool {
578        if let Some(rerun_info) = &self.rerun_info
579            && let Some(suite) = rerun_info.test_suites.get(&test_binary.binary_id)
580        {
581            return suite.passing.contains(test_name);
582        }
583        false
584    }
585
586    fn filter_benchmark_mismatch(&self, test_kind: &RustTestKind) -> Option<FilterMatch> {
587        if self.mode == NextestRunMode::Benchmark && test_kind != &RustTestKind::BENCH {
588            Some(FilterMatch::Mismatch {
589                reason: MismatchReason::NotBenchmark,
590            })
591        } else {
592            None
593        }
594    }
595
596    fn filter_ignored_mismatch(&self, ignored: bool) -> Option<FilterMatch> {
597        let dominated = match self.run_ignored {
598            RunIgnored::Only => !ignored,
599            RunIgnored::Default => ignored,
600            RunIgnored::All => false,
601        };
602        dominated.then_some(FilterMatch::Mismatch {
603            reason: MismatchReason::Ignored,
604        })
605    }
606
607    fn filter_name_match(&self, test_name: &TestCaseName) -> FilterNameMatch {
608        self.patterns.name_match(test_name)
609    }
610
611    fn filter_expression_match(
612        &self,
613        test_binary: &RustTestArtifact<'_>,
614        test_name: &TestCaseName,
615        ecx: &EvalContext<'_>,
616        bound: FilterBound,
617    ) -> FilterNameMatch {
618        let query = TestQuery {
619            binary_query: test_binary.to_binary_query(),
620            test_name,
621        };
622
623        let expr_result = match &self.binary_filter.exprs {
624            TestFilterExprs::All => FilterNameMatch::MatchEmptyPatterns,
625            TestFilterExprs::Sets(exprs) => {
626                if exprs.iter().any(|expr| expr.matches_test(&query, ecx)) {
627                    FilterNameMatch::MatchWithPatterns
628                } else {
629                    return FilterNameMatch::Mismatch(MismatchReason::Expression);
630                }
631            }
632        };
633
634        match bound {
635            FilterBound::All => expr_result,
636            FilterBound::DefaultSet => {
637                if ecx.default_filter.matches_test(&query, ecx) {
638                    expr_result
639                } else {
640                    FilterNameMatch::Mismatch(MismatchReason::DefaultFilter)
641                }
642            }
643        }
644    }
645}
646
647/// Whether a binary matched filters and should be run to obtain the list of tests within.
648///
649/// The result of [`TestFilter::filter_binary_match`].
650#[derive(Copy, Clone, Debug)]
651pub enum FilterBinaryMatch {
652    /// This is a definite match -- binaries should be run.
653    Definite,
654
655    /// We don't know for sure -- binaries should be run.
656    Possible,
657
658    /// This is a definite mismatch -- binaries should not be run.
659    Mismatch {
660        /// The reason for the mismatch.
661        reason: BinaryMismatchReason,
662    },
663}
664
665impl FilterBinaryMatch {
666    fn from_result(result: Option<bool>, reason: BinaryMismatchReason) -> Self {
667        match result {
668            Some(true) => Self::Definite,
669            None => Self::Possible,
670            Some(false) => Self::Mismatch { reason },
671        }
672    }
673
674    fn is_match(self) -> bool {
675        match self {
676            Self::Definite | Self::Possible => true,
677            Self::Mismatch { .. } => false,
678        }
679    }
680
681    fn logic_or(self, other: Self) -> Self {
682        match (self, other) {
683            (Self::Definite, _) | (_, Self::Definite) => Self::Definite,
684            (Self::Possible, _) | (_, Self::Possible) => Self::Possible,
685            (Self::Mismatch { reason: r1 }, Self::Mismatch { reason: r2 }) => Self::Mismatch {
686                reason: r1.prefer_expression(r2),
687            },
688        }
689    }
690
691    fn logic_and(self, other: Self) -> Self {
692        match (self, other) {
693            (Self::Definite, Self::Definite) => Self::Definite,
694            (Self::Definite, Self::Possible)
695            | (Self::Possible, Self::Definite)
696            | (Self::Possible, Self::Possible) => Self::Possible,
697            (Self::Mismatch { reason: r1 }, Self::Mismatch { reason: r2 }) => {
698                // If one of the mismatch reasons is `Expression` and the other is `DefaultSet`, we
699                // return Expression.
700                Self::Mismatch {
701                    reason: r1.prefer_expression(r2),
702                }
703            }
704            (Self::Mismatch { reason }, _) | (_, Self::Mismatch { reason }) => {
705                Self::Mismatch { reason }
706            }
707        }
708    }
709}
710
711/// The reason for a binary mismatch.
712///
713/// Part of [`FilterBinaryMatch`], as returned by [`TestFilter::filter_binary_match`].
714#[derive(Copy, Clone, Debug, Eq, PartialEq)]
715pub enum BinaryMismatchReason {
716    /// The binary doesn't match any of the provided filtersets.
717    Expression,
718
719    /// No filtersets were specified and the binary doesn't match the default set.
720    DefaultSet,
721}
722
723impl BinaryMismatchReason {
724    fn prefer_expression(self, other: Self) -> Self {
725        match (self, other) {
726            (Self::Expression, _) | (_, Self::Expression) => Self::Expression,
727            (Self::DefaultSet, Self::DefaultSet) => Self::DefaultSet,
728        }
729    }
730}
731
732impl fmt::Display for BinaryMismatchReason {
733    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734        match self {
735            Self::Expression => write!(f, "didn't match filtersets"),
736            Self::DefaultSet => write!(f, "didn't match the default set"),
737        }
738    }
739}
740
741#[derive(Clone, Debug, Eq, PartialEq)]
742enum FilterNameMatch {
743    /// Match because there are no patterns.
744    MatchEmptyPatterns,
745    /// Matches with non-empty patterns.
746    MatchWithPatterns,
747    /// Mismatch.
748    Mismatch(MismatchReason),
749}
750
751impl FilterNameMatch {
752    #[cfg(test)]
753    fn is_match(&self) -> bool {
754        match self {
755            Self::MatchEmptyPatterns | Self::MatchWithPatterns => true,
756            Self::Mismatch(_) => false,
757        }
758    }
759}
760
761#[cfg(test)]
762mod tests {
763    use super::*;
764    use proptest::{collection::vec, prelude::*};
765    use test_strategy::proptest;
766
767    #[proptest(cases = 50)]
768    fn proptest_empty(#[strategy(vec(any::<String>(), 0..16))] test_names: Vec<String>) {
769        let patterns = TestFilterPatterns::default();
770        let test_filter = TestFilter::new(
771            NextestRunMode::Test,
772            RunIgnored::Default,
773            patterns,
774            Vec::new(),
775        )
776        .unwrap();
777        for test_name in test_names {
778            let test_name = TestCaseName::new(&test_name);
779            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
780        }
781    }
782
783    // Test that exact names match.
784    #[proptest(cases = 50)]
785    fn proptest_exact(#[strategy(vec(any::<String>(), 0..16))] test_names: Vec<String>) {
786        // Test with the default matcher.
787        let patterns = TestFilterPatterns::new(test_names.clone());
788        let test_filter = TestFilter::new(
789            NextestRunMode::Test,
790            RunIgnored::Default,
791            patterns,
792            Vec::new(),
793        )
794        .unwrap();
795        for test_name in &test_names {
796            let test_name = TestCaseName::new(test_name);
797            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
798        }
799
800        // Test with the exact matcher.
801        let mut patterns = TestFilterPatterns::default();
802        for test_name in &test_names {
803            patterns.add_exact_pattern(test_name.clone());
804        }
805        let test_filter = TestFilter::new(
806            NextestRunMode::Test,
807            RunIgnored::Default,
808            patterns,
809            Vec::new(),
810        )
811        .unwrap();
812        for test_name in &test_names {
813            let test_name = TestCaseName::new(test_name);
814            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
815        }
816    }
817
818    // Test that substrings match.
819    #[proptest(cases = 50)]
820    fn proptest_substring(
821        #[strategy(vec([any::<String>(); 3], 0..16))] substring_prefix_suffixes: Vec<[String; 3]>,
822    ) {
823        let mut patterns = TestFilterPatterns::default();
824        let mut test_names = Vec::with_capacity(substring_prefix_suffixes.len());
825        for [substring, prefix, suffix] in substring_prefix_suffixes {
826            test_names.push(prefix + &substring + &suffix);
827            patterns.add_substring_pattern(substring);
828        }
829
830        let test_filter = TestFilter::new(
831            NextestRunMode::Test,
832            RunIgnored::Default,
833            patterns,
834            Vec::new(),
835        )
836        .unwrap();
837        for test_name in test_names {
838            let test_name = TestCaseName::new(&test_name);
839            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
840        }
841    }
842
843    // Test that dropping a character from a string doesn't match.
844    #[proptest(cases = 50)]
845    fn proptest_no_match(substring: String, prefix: String, suffix: String) {
846        prop_assume!(!substring.is_empty() && !prefix.is_empty() && !suffix.is_empty());
847        let pattern = prefix + &substring + &suffix;
848        let patterns = TestFilterPatterns::new(vec![pattern]);
849        let test_filter = TestFilter::new(
850            NextestRunMode::Test,
851            RunIgnored::Default,
852            patterns,
853            Vec::new(),
854        )
855        .unwrap();
856        let substring = TestCaseName::new(&substring);
857        prop_assert!(!test_filter.filter_name_match(&substring).is_match());
858    }
859
860    fn test_name(s: &str) -> TestCaseName {
861        TestCaseName::new(s)
862    }
863
864    #[test]
865    fn pattern_examples() {
866        let mut patterns = TestFilterPatterns::new(vec!["foo".to_string()]);
867        patterns.add_substring_pattern("bar".to_string());
868        patterns.add_exact_pattern("baz".to_string());
869        patterns.add_skip_pattern("quux".to_string());
870        patterns.add_skip_exact_pattern("quuz".to_string());
871
872        let resolved = patterns.clone().resolve().unwrap();
873
874        // Test substring matches.
875        assert_eq!(
876            resolved.name_match(&test_name("foo")),
877            FilterNameMatch::MatchWithPatterns,
878        );
879        assert_eq!(
880            resolved.name_match(&test_name("1foo2")),
881            FilterNameMatch::MatchWithPatterns,
882        );
883        assert_eq!(
884            resolved.name_match(&test_name("bar")),
885            FilterNameMatch::MatchWithPatterns,
886        );
887        assert_eq!(
888            resolved.name_match(&test_name("x_bar_y")),
889            FilterNameMatch::MatchWithPatterns,
890        );
891
892        // Test exact matches.
893        assert_eq!(
894            resolved.name_match(&test_name("baz")),
895            FilterNameMatch::MatchWithPatterns,
896        );
897        assert_eq!(
898            resolved.name_match(&test_name("abazb")),
899            FilterNameMatch::Mismatch(MismatchReason::String),
900        );
901
902        // Both substring and exact matches.
903        assert_eq!(
904            resolved.name_match(&test_name("bazfoo")),
905            FilterNameMatch::MatchWithPatterns,
906        );
907
908        // Skip patterns.
909        assert_eq!(
910            resolved.name_match(&test_name("quux")),
911            FilterNameMatch::Mismatch(MismatchReason::String),
912        );
913        assert_eq!(
914            resolved.name_match(&test_name("1quux2")),
915            FilterNameMatch::Mismatch(MismatchReason::String),
916        );
917
918        // Skip and substring patterns.
919        assert_eq!(
920            resolved.name_match(&test_name("quuxbar")),
921            FilterNameMatch::Mismatch(MismatchReason::String),
922        );
923
924        // Skip-exact patterns.
925        assert_eq!(
926            resolved.name_match(&test_name("quuz")),
927            FilterNameMatch::Mismatch(MismatchReason::String),
928        );
929
930        // Skip overrides regular patterns -- in this case, add `baz` to the skip list.
931        patterns.add_skip_pattern("baz".to_string());
932        let resolved = patterns.resolve().unwrap();
933        assert_eq!(
934            resolved.name_match(&test_name("quuxbaz")),
935            FilterNameMatch::Mismatch(MismatchReason::String),
936        );
937    }
938
939    #[test]
940    fn skip_only_pattern_examples() {
941        let mut patterns = TestFilterPatterns::default();
942        patterns.add_skip_pattern("foo".to_string());
943        patterns.add_skip_pattern("bar".to_string());
944        patterns.add_skip_exact_pattern("baz".to_string());
945
946        let resolved = patterns.clone().resolve().unwrap();
947
948        // Test substring matches.
949        assert_eq!(
950            resolved.name_match(&test_name("foo")),
951            FilterNameMatch::Mismatch(MismatchReason::String),
952        );
953        assert_eq!(
954            resolved.name_match(&test_name("1foo2")),
955            FilterNameMatch::Mismatch(MismatchReason::String),
956        );
957        assert_eq!(
958            resolved.name_match(&test_name("bar")),
959            FilterNameMatch::Mismatch(MismatchReason::String),
960        );
961        assert_eq!(
962            resolved.name_match(&test_name("x_bar_y")),
963            FilterNameMatch::Mismatch(MismatchReason::String),
964        );
965
966        // Test exact matches.
967        assert_eq!(
968            resolved.name_match(&test_name("baz")),
969            FilterNameMatch::Mismatch(MismatchReason::String),
970        );
971        assert_eq!(
972            resolved.name_match(&test_name("abazb")),
973            FilterNameMatch::MatchWithPatterns,
974        );
975
976        // Anything that doesn't match the skip filter should match.
977        assert_eq!(
978            resolved.name_match(&test_name("quux")),
979            FilterNameMatch::MatchWithPatterns,
980        );
981    }
982
983    #[test]
984    fn empty_pattern_examples() {
985        let patterns = TestFilterPatterns::default();
986        let resolved = patterns.resolve().unwrap();
987        assert_eq!(resolved, ResolvedFilterPatterns::All);
988
989        // Anything matches.
990        assert_eq!(
991            resolved.name_match(&test_name("foo")),
992            FilterNameMatch::MatchEmptyPatterns,
993        );
994        assert_eq!(
995            resolved.name_match(&test_name("1foo2")),
996            FilterNameMatch::MatchEmptyPatterns,
997        );
998        assert_eq!(
999            resolved.name_match(&test_name("bar")),
1000            FilterNameMatch::MatchEmptyPatterns,
1001        );
1002        assert_eq!(
1003            resolved.name_match(&test_name("x_bar_y")),
1004            FilterNameMatch::MatchEmptyPatterns,
1005        );
1006        assert_eq!(
1007            resolved.name_match(&test_name("baz")),
1008            FilterNameMatch::MatchEmptyPatterns,
1009        );
1010        assert_eq!(
1011            resolved.name_match(&test_name("abazb")),
1012            FilterNameMatch::MatchEmptyPatterns,
1013        );
1014    }
1015}