1 | use regex::Regex; |
2 | |
3 | use crate::util::split_vec::SplitVec; |
4 | |
5 | /// Filters which benchmark/group to run based on its path. |
6 | pub(crate) enum Filter { |
7 | Regex(Regex), |
8 | Exact(String), |
9 | } |
10 | |
11 | impl Filter { |
12 | fn is_match(&self, s: &str) -> bool { |
13 | match self { |
14 | Self::Regex(regex: &Regex) => regex.is_match(haystack:s), |
15 | Self::Exact(exact: &String) => exact == s, |
16 | } |
17 | } |
18 | } |
19 | |
20 | /// Collection of inclusive and exclusive filters. |
21 | /// |
22 | /// Inclusive filters indicate that a benchmark/group path should be run without |
23 | /// running other benchmarks (unless also included). |
24 | /// |
25 | /// Exclusive filters make all matching candidate benchmarks be skipped (even if |
26 | /// explicitly included). As a result, they have priority over inclusive |
27 | /// filters. |
28 | #[derive (Default)] |
29 | pub(crate) struct FilterSet { |
30 | /// Stores exclusive filters followed by inclusive filters. |
31 | filters: SplitVec<Filter>, |
32 | } |
33 | |
34 | impl FilterSet { |
35 | #[inline ] |
36 | pub fn reserve_exact(&mut self, additional: usize) { |
37 | self.filters.reserve_exact(additional); |
38 | } |
39 | |
40 | #[inline ] |
41 | pub fn include(&mut self, filter: Filter) { |
42 | self.insert_filter(filter, true); |
43 | } |
44 | |
45 | #[inline ] |
46 | pub fn exclude(&mut self, filter: Filter) { |
47 | self.insert_filter(filter, false); |
48 | } |
49 | |
50 | fn insert_filter(&mut self, filter: Filter, inclusive: bool) { |
51 | self.filters.insert(filter, inclusive); |
52 | } |
53 | |
54 | /// Returns `true` if a benchmark/group path matches these filters, and thus |
55 | /// the entry should be included. |
56 | /// |
57 | /// Negative filters are prioritized over inclusive filters. |
58 | pub fn is_match(&self, entry_path: &str) -> bool { |
59 | let filters = self.filters.all(); |
60 | let inclusive_start = self.filters.split_index(); |
61 | |
62 | // If any filter matches, return whether it was inclusive or negative. |
63 | // Negative filters are placed before inclusive filters because they have |
64 | // priority. |
65 | if let Some(index) = filters.iter().position(|f| f.is_match(entry_path)) { |
66 | return index >= inclusive_start; |
67 | } |
68 | |
69 | // Otherwise succeed only if there are no inclusive filters. |
70 | filters.len() == inclusive_start |
71 | } |
72 | } |
73 | |
74 | #[cfg (test)] |
75 | mod tests { |
76 | use super::*; |
77 | |
78 | /// Empty filter sets should match all strings. |
79 | #[test ] |
80 | fn empty() { |
81 | let filters = FilterSet::default(); |
82 | assert!(filters.is_match("abc" )); |
83 | assert!(filters.is_match("123" )); |
84 | } |
85 | |
86 | mod single { |
87 | use super::*; |
88 | |
89 | #[test ] |
90 | fn inclusive_exact() { |
91 | let mut filters = FilterSet::default(); |
92 | |
93 | filters.insert_filter(Filter::Exact("abc" .into()), true); |
94 | |
95 | assert!(filters.is_match("abc" )); |
96 | assert!(!filters.is_match("ab" )); |
97 | assert!(!filters.is_match("abcd" )); |
98 | } |
99 | |
100 | #[test ] |
101 | fn exclusive_exact() { |
102 | let mut filters = FilterSet::default(); |
103 | |
104 | filters.insert_filter(Filter::Exact("abc" .into()), false); |
105 | |
106 | assert!(!filters.is_match("abc" )); |
107 | assert!(filters.is_match("ab" )); |
108 | assert!(filters.is_match("abcd" )); |
109 | } |
110 | |
111 | #[test ] |
112 | fn inclusive_regex() { |
113 | let mut filters = FilterSet::default(); |
114 | let regex = Regex::new("abc.*123" ).unwrap(); |
115 | |
116 | filters.insert_filter(Filter::Regex(regex), true); |
117 | |
118 | assert!(!filters.is_match("abc" )); |
119 | assert!(filters.is_match("abc123" )); |
120 | assert!(filters.is_match("abc::123" )); |
121 | } |
122 | |
123 | #[test ] |
124 | fn exclusive_regex() { |
125 | let mut filters = FilterSet::default(); |
126 | let regex = Regex::new("abc.*123" ).unwrap(); |
127 | |
128 | filters.insert_filter(Filter::Regex(regex), false); |
129 | |
130 | assert!(filters.is_match("abc" )); |
131 | assert!(!filters.is_match("abc123" )); |
132 | assert!(!filters.is_match("abc::123" )); |
133 | } |
134 | } |
135 | |
136 | /// Multiple inclusive filters should not be restrictive, whereas negative |
137 | /// filters are increasingly restrictive. |
138 | mod multi { |
139 | use super::*; |
140 | |
141 | #[test ] |
142 | fn exact() { |
143 | let mut filters = FilterSet::default(); |
144 | |
145 | filters.insert_filter(Filter::Exact("abc" .into()), true); |
146 | filters.insert_filter(Filter::Exact("123" .into()), true); |
147 | |
148 | assert!(filters.is_match("abc" )); |
149 | assert!(filters.is_match("123" )); |
150 | assert!(!filters.is_match("xyz" )); |
151 | } |
152 | } |
153 | |
154 | /// Negative filters override inclusive filters. |
155 | mod overridden { |
156 | use super::*; |
157 | |
158 | #[test ] |
159 | fn exact() { |
160 | let mut filters = FilterSet::default(); |
161 | |
162 | filters.insert_filter(Filter::Exact("abc" .into()), true); |
163 | filters.insert_filter(Filter::Exact("abc" .into()), false); |
164 | |
165 | assert!(!filters.is_match("abc" )); |
166 | } |
167 | |
168 | #[test ] |
169 | fn regex() { |
170 | let mut filters = FilterSet::default(); |
171 | let regex = Regex::new("abc.*123" ).unwrap(); |
172 | |
173 | filters.insert_filter(Filter::Regex(regex.clone()), true); |
174 | filters.insert_filter(Filter::Regex(regex), false); |
175 | |
176 | assert!(!filters.is_match("abc::123" )); |
177 | assert!(!filters.is_match("123::abc" )); |
178 | } |
179 | } |
180 | } |
181 | |