foundry_test_utils/
filter.rs

1use foundry_common::TestFilter;
2use regex::Regex;
3use std::path::Path;
4
5#[derive(Clone, Debug)]
6pub struct Filter {
7    test_regex: Regex,
8    contract_regex: Regex,
9    path_regex: Regex,
10    exclude_tests: Option<Regex>,
11    exclude_contracts: Option<Regex>,
12    exclude_paths: Option<Regex>,
13}
14
15impl Filter {
16    pub fn new(test_pattern: &str, contract_pattern: &str, path_pattern: &str) -> Self {
17        Self {
18            test_regex: Regex::new(test_pattern)
19                .unwrap_or_else(|_| panic!("Failed to parse test pattern: `{test_pattern}`")),
20            contract_regex: Regex::new(contract_pattern).unwrap_or_else(|_| {
21                panic!("Failed to parse contract pattern: `{contract_pattern}`")
22            }),
23            path_regex: Regex::new(path_pattern)
24                .unwrap_or_else(|_| panic!("Failed to parse path pattern: `{path_pattern}`")),
25            exclude_tests: None,
26            exclude_contracts: None,
27            exclude_paths: None,
28        }
29    }
30
31    pub fn contract(contract_pattern: &str) -> Self {
32        Self::new(".*", contract_pattern, ".*")
33    }
34
35    pub fn path(path_pattern: &str) -> Self {
36        Self::new(".*", ".*", path_pattern)
37    }
38
39    /// All tests to also exclude
40    ///
41    /// This is a workaround since regex does not support negative look aheads
42    pub fn exclude_tests(mut self, pattern: &str) -> Self {
43        self.exclude_tests = Some(Regex::new(pattern).unwrap());
44        self
45    }
46
47    /// All contracts to also exclude
48    ///
49    /// This is a workaround since regex does not support negative look aheads
50    pub fn exclude_contracts(mut self, pattern: &str) -> Self {
51        self.exclude_contracts = Some(Regex::new(pattern).unwrap());
52        self
53    }
54
55    /// All paths to also exclude
56    ///
57    /// This is a workaround since regex does not support negative look aheads
58    pub fn exclude_paths(mut self, pattern: &str) -> Self {
59        self.exclude_paths = Some(Regex::new(pattern).unwrap());
60        self
61    }
62
63    pub fn matches_all() -> Self {
64        Self {
65            test_regex: Regex::new(".*").unwrap(),
66            contract_regex: Regex::new(".*").unwrap(),
67            path_regex: Regex::new(".*").unwrap(),
68            exclude_tests: None,
69            exclude_contracts: None,
70            exclude_paths: None,
71        }
72    }
73}
74
75impl TestFilter for Filter {
76    fn matches_test(&self, test_name: &str) -> bool {
77        if let Some(exclude) = &self.exclude_tests {
78            if exclude.is_match(test_name) {
79                return false;
80            }
81        }
82        self.test_regex.is_match(test_name)
83    }
84
85    fn matches_contract(&self, contract_name: &str) -> bool {
86        if let Some(exclude) = &self.exclude_contracts {
87            if exclude.is_match(contract_name) {
88                return false;
89            }
90        }
91
92        self.contract_regex.is_match(contract_name)
93    }
94
95    fn matches_path(&self, path: &Path) -> bool {
96        let Some(path) = path.to_str() else {
97            return false;
98        };
99
100        if let Some(exclude) = &self.exclude_paths {
101            if exclude.is_match(path) {
102                return false;
103            }
104        }
105        self.path_regex.is_match(path)
106    }
107}