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        match self.run_ignored {
598            RunIgnored::Only => {
599                if !ignored {
600                    return Some(FilterMatch::Mismatch {
601                        reason: MismatchReason::Ignored,
602                    });
603                }
604            }
605            RunIgnored::Default => {
606                if ignored {
607                    return Some(FilterMatch::Mismatch {
608                        reason: MismatchReason::Ignored,
609                    });
610                }
611            }
612            RunIgnored::All => {}
613        }
614        None
615    }
616
617    fn filter_name_match(&self, test_name: &TestCaseName) -> FilterNameMatch {
618        self.patterns.name_match(test_name)
619    }
620
621    fn filter_expression_match(
622        &self,
623        test_binary: &RustTestArtifact<'_>,
624        test_name: &TestCaseName,
625        ecx: &EvalContext<'_>,
626        bound: FilterBound,
627    ) -> FilterNameMatch {
628        let query = TestQuery {
629            binary_query: test_binary.to_binary_query(),
630            test_name,
631        };
632
633        let expr_result = match &self.binary_filter.exprs {
634            TestFilterExprs::All => FilterNameMatch::MatchEmptyPatterns,
635            TestFilterExprs::Sets(exprs) => {
636                if exprs.iter().any(|expr| expr.matches_test(&query, ecx)) {
637                    FilterNameMatch::MatchWithPatterns
638                } else {
639                    return FilterNameMatch::Mismatch(MismatchReason::Expression);
640                }
641            }
642        };
643
644        match bound {
645            FilterBound::All => expr_result,
646            FilterBound::DefaultSet => {
647                if ecx.default_filter.matches_test(&query, ecx) {
648                    expr_result
649                } else {
650                    FilterNameMatch::Mismatch(MismatchReason::DefaultFilter)
651                }
652            }
653        }
654    }
655}
656
657/// Whether a binary matched filters and should be run to obtain the list of tests within.
658///
659/// The result of [`TestFilter::filter_binary_match`].
660#[derive(Copy, Clone, Debug)]
661pub enum FilterBinaryMatch {
662    /// This is a definite match -- binaries should be run.
663    Definite,
664
665    /// We don't know for sure -- binaries should be run.
666    Possible,
667
668    /// This is a definite mismatch -- binaries should not be run.
669    Mismatch {
670        /// The reason for the mismatch.
671        reason: BinaryMismatchReason,
672    },
673}
674
675impl FilterBinaryMatch {
676    fn from_result(result: Option<bool>, reason: BinaryMismatchReason) -> Self {
677        match result {
678            Some(true) => Self::Definite,
679            None => Self::Possible,
680            Some(false) => Self::Mismatch { reason },
681        }
682    }
683
684    fn is_match(self) -> bool {
685        match self {
686            Self::Definite | Self::Possible => true,
687            Self::Mismatch { .. } => false,
688        }
689    }
690
691    fn logic_or(self, other: Self) -> Self {
692        match (self, other) {
693            (Self::Definite, _) | (_, Self::Definite) => Self::Definite,
694            (Self::Possible, _) | (_, Self::Possible) => Self::Possible,
695            (Self::Mismatch { reason: r1 }, Self::Mismatch { reason: r2 }) => Self::Mismatch {
696                reason: r1.prefer_expression(r2),
697            },
698        }
699    }
700
701    fn logic_and(self, other: Self) -> Self {
702        match (self, other) {
703            (Self::Definite, Self::Definite) => Self::Definite,
704            (Self::Definite, Self::Possible)
705            | (Self::Possible, Self::Definite)
706            | (Self::Possible, Self::Possible) => Self::Possible,
707            (Self::Mismatch { reason: r1 }, Self::Mismatch { reason: r2 }) => {
708                // If one of the mismatch reasons is `Expression` and the other is `DefaultSet`, we
709                // return Expression.
710                Self::Mismatch {
711                    reason: r1.prefer_expression(r2),
712                }
713            }
714            (Self::Mismatch { reason }, _) | (_, Self::Mismatch { reason }) => {
715                Self::Mismatch { reason }
716            }
717        }
718    }
719}
720
721/// The reason for a binary mismatch.
722///
723/// Part of [`FilterBinaryMatch`], as returned by [`TestFilter::filter_binary_match`].
724#[derive(Copy, Clone, Debug, Eq, PartialEq)]
725pub enum BinaryMismatchReason {
726    /// The binary doesn't match any of the provided filtersets.
727    Expression,
728
729    /// No filtersets were specified and the binary doesn't match the default set.
730    DefaultSet,
731}
732
733impl BinaryMismatchReason {
734    fn prefer_expression(self, other: Self) -> Self {
735        match (self, other) {
736            (Self::Expression, _) | (_, Self::Expression) => Self::Expression,
737            (Self::DefaultSet, Self::DefaultSet) => Self::DefaultSet,
738        }
739    }
740}
741
742impl fmt::Display for BinaryMismatchReason {
743    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
744        match self {
745            Self::Expression => write!(f, "didn't match filtersets"),
746            Self::DefaultSet => write!(f, "didn't match the default set"),
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 = TestFilter::new(
781            NextestRunMode::Test,
782            RunIgnored::Default,
783            patterns,
784            Vec::new(),
785        )
786        .unwrap();
787        for test_name in test_names {
788            let test_name = TestCaseName::new(&test_name);
789            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
790        }
791    }
792
793    // Test that exact names match.
794    #[proptest(cases = 50)]
795    fn proptest_exact(#[strategy(vec(any::<String>(), 0..16))] test_names: Vec<String>) {
796        // Test with the default matcher.
797        let patterns = TestFilterPatterns::new(test_names.clone());
798        let test_filter = TestFilter::new(
799            NextestRunMode::Test,
800            RunIgnored::Default,
801            patterns,
802            Vec::new(),
803        )
804        .unwrap();
805        for test_name in &test_names {
806            let test_name = TestCaseName::new(test_name);
807            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
808        }
809
810        // Test with the exact matcher.
811        let mut patterns = TestFilterPatterns::default();
812        for test_name in &test_names {
813            patterns.add_exact_pattern(test_name.clone());
814        }
815        let test_filter = TestFilter::new(
816            NextestRunMode::Test,
817            RunIgnored::Default,
818            patterns,
819            Vec::new(),
820        )
821        .unwrap();
822        for test_name in &test_names {
823            let test_name = TestCaseName::new(test_name);
824            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
825        }
826    }
827
828    // Test that substrings match.
829    #[proptest(cases = 50)]
830    fn proptest_substring(
831        #[strategy(vec([any::<String>(); 3], 0..16))] substring_prefix_suffixes: Vec<[String; 3]>,
832    ) {
833        let mut patterns = TestFilterPatterns::default();
834        let mut test_names = Vec::with_capacity(substring_prefix_suffixes.len());
835        for [substring, prefix, suffix] in substring_prefix_suffixes {
836            test_names.push(prefix + &substring + &suffix);
837            patterns.add_substring_pattern(substring);
838        }
839
840        let test_filter = TestFilter::new(
841            NextestRunMode::Test,
842            RunIgnored::Default,
843            patterns,
844            Vec::new(),
845        )
846        .unwrap();
847        for test_name in test_names {
848            let test_name = TestCaseName::new(&test_name);
849            prop_assert!(test_filter.filter_name_match(&test_name).is_match());
850        }
851    }
852
853    // Test that dropping a character from a string doesn't match.
854    #[proptest(cases = 50)]
855    fn proptest_no_match(substring: String, prefix: String, suffix: String) {
856        prop_assume!(!substring.is_empty() && !prefix.is_empty() && !suffix.is_empty());
857        let pattern = prefix + &substring + &suffix;
858        let patterns = TestFilterPatterns::new(vec![pattern]);
859        let test_filter = TestFilter::new(
860            NextestRunMode::Test,
861            RunIgnored::Default,
862            patterns,
863            Vec::new(),
864        )
865        .unwrap();
866        let substring = TestCaseName::new(&substring);
867        prop_assert!(!test_filter.filter_name_match(&substring).is_match());
868    }
869
870    fn test_name(s: &str) -> TestCaseName {
871        TestCaseName::new(s)
872    }
873
874    #[test]
875    fn pattern_examples() {
876        let mut patterns = TestFilterPatterns::new(vec!["foo".to_string()]);
877        patterns.add_substring_pattern("bar".to_string());
878        patterns.add_exact_pattern("baz".to_string());
879        patterns.add_skip_pattern("quux".to_string());
880        patterns.add_skip_exact_pattern("quuz".to_string());
881
882        let resolved = patterns.clone().resolve().unwrap();
883
884        // Test substring matches.
885        assert_eq!(
886            resolved.name_match(&test_name("foo")),
887            FilterNameMatch::MatchWithPatterns,
888        );
889        assert_eq!(
890            resolved.name_match(&test_name("1foo2")),
891            FilterNameMatch::MatchWithPatterns,
892        );
893        assert_eq!(
894            resolved.name_match(&test_name("bar")),
895            FilterNameMatch::MatchWithPatterns,
896        );
897        assert_eq!(
898            resolved.name_match(&test_name("x_bar_y")),
899            FilterNameMatch::MatchWithPatterns,
900        );
901
902        // Test exact matches.
903        assert_eq!(
904            resolved.name_match(&test_name("baz")),
905            FilterNameMatch::MatchWithPatterns,
906        );
907        assert_eq!(
908            resolved.name_match(&test_name("abazb")),
909            FilterNameMatch::Mismatch(MismatchReason::String),
910        );
911
912        // Both substring and exact matches.
913        assert_eq!(
914            resolved.name_match(&test_name("bazfoo")),
915            FilterNameMatch::MatchWithPatterns,
916        );
917
918        // Skip patterns.
919        assert_eq!(
920            resolved.name_match(&test_name("quux")),
921            FilterNameMatch::Mismatch(MismatchReason::String),
922        );
923        assert_eq!(
924            resolved.name_match(&test_name("1quux2")),
925            FilterNameMatch::Mismatch(MismatchReason::String),
926        );
927
928        // Skip and substring patterns.
929        assert_eq!(
930            resolved.name_match(&test_name("quuxbar")),
931            FilterNameMatch::Mismatch(MismatchReason::String),
932        );
933
934        // Skip-exact patterns.
935        assert_eq!(
936            resolved.name_match(&test_name("quuz")),
937            FilterNameMatch::Mismatch(MismatchReason::String),
938        );
939
940        // Skip overrides regular patterns -- in this case, add `baz` to the skip list.
941        patterns.add_skip_pattern("baz".to_string());
942        let resolved = patterns.resolve().unwrap();
943        assert_eq!(
944            resolved.name_match(&test_name("quuxbaz")),
945            FilterNameMatch::Mismatch(MismatchReason::String),
946        );
947    }
948
949    #[test]
950    fn skip_only_pattern_examples() {
951        let mut patterns = TestFilterPatterns::default();
952        patterns.add_skip_pattern("foo".to_string());
953        patterns.add_skip_pattern("bar".to_string());
954        patterns.add_skip_exact_pattern("baz".to_string());
955
956        let resolved = patterns.clone().resolve().unwrap();
957
958        // Test substring matches.
959        assert_eq!(
960            resolved.name_match(&test_name("foo")),
961            FilterNameMatch::Mismatch(MismatchReason::String),
962        );
963        assert_eq!(
964            resolved.name_match(&test_name("1foo2")),
965            FilterNameMatch::Mismatch(MismatchReason::String),
966        );
967        assert_eq!(
968            resolved.name_match(&test_name("bar")),
969            FilterNameMatch::Mismatch(MismatchReason::String),
970        );
971        assert_eq!(
972            resolved.name_match(&test_name("x_bar_y")),
973            FilterNameMatch::Mismatch(MismatchReason::String),
974        );
975
976        // Test exact matches.
977        assert_eq!(
978            resolved.name_match(&test_name("baz")),
979            FilterNameMatch::Mismatch(MismatchReason::String),
980        );
981        assert_eq!(
982            resolved.name_match(&test_name("abazb")),
983            FilterNameMatch::MatchWithPatterns,
984        );
985
986        // Anything that doesn't match the skip filter should match.
987        assert_eq!(
988            resolved.name_match(&test_name("quux")),
989            FilterNameMatch::MatchWithPatterns,
990        );
991    }
992
993    #[test]
994    fn empty_pattern_examples() {
995        let patterns = TestFilterPatterns::default();
996        let resolved = patterns.resolve().unwrap();
997        assert_eq!(resolved, ResolvedFilterPatterns::All);
998
999        // Anything matches.
1000        assert_eq!(
1001            resolved.name_match(&test_name("foo")),
1002            FilterNameMatch::MatchEmptyPatterns,
1003        );
1004        assert_eq!(
1005            resolved.name_match(&test_name("1foo2")),
1006            FilterNameMatch::MatchEmptyPatterns,
1007        );
1008        assert_eq!(
1009            resolved.name_match(&test_name("bar")),
1010            FilterNameMatch::MatchEmptyPatterns,
1011        );
1012        assert_eq!(
1013            resolved.name_match(&test_name("x_bar_y")),
1014            FilterNameMatch::MatchEmptyPatterns,
1015        );
1016        assert_eq!(
1017            resolved.name_match(&test_name("baz")),
1018            FilterNameMatch::MatchEmptyPatterns,
1019        );
1020        assert_eq!(
1021            resolved.name_match(&test_name("abazb")),
1022            FilterNameMatch::MatchEmptyPatterns,
1023        );
1024    }
1025}