1use regex::Regex;
2
3use crate::util::split_vec::SplitVec;
4
5/// Filters which benchmark/group to run based on its path.
6pub(crate) enum Filter {
7 Regex(Regex),
8 Exact(String),
9}
10
11impl 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)]
29pub(crate) struct FilterSet {
30 /// Stores exclusive filters followed by inclusive filters.
31 filters: SplitVec<Filter>,
32}
33
34impl 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)]
75mod 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