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