git_perf/
filter.rs

1use anyhow::{Context, Result};
2use regex::Regex;
3
4/// Compile filter patterns into regex objects
5pub fn compile_filters(patterns: &[String]) -> Result<Vec<Regex>> {
6    patterns
7        .iter()
8        .map(|pattern| {
9            Regex::new(pattern).with_context(|| format!("Invalid regex pattern: '{}'", pattern))
10        })
11        .collect()
12}
13
14/// Convert measurement names to anchored regex patterns for exact matching
15/// This escapes special regex characters and adds ^ and $ anchors
16pub fn measurements_to_anchored_regex(measurements: &[String]) -> Vec<String> {
17    measurements
18        .iter()
19        .map(|m| format!("^{}$", regex::escape(m)))
20        .collect()
21}
22
23/// Combine measurements (as exact matches) and filter patterns into a single list
24/// Measurements are converted to anchored regex patterns for exact matching
25pub fn combine_measurements_and_filters(
26    measurements: &[String],
27    filter_patterns: &[String],
28) -> Vec<String> {
29    let mut combined = measurements_to_anchored_regex(measurements);
30    combined.extend_from_slice(filter_patterns);
31    combined
32}
33
34/// Check if a measurement name matches any of the compiled filters
35/// Returns true if filters is empty (no filters = match all)
36pub fn matches_any_filter(name: &str, filters: &[Regex]) -> bool {
37    if filters.is_empty() {
38        return true; // No filters = match all
39    }
40    filters.iter().any(|re| re.is_match(name))
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn test_compile_valid_filters() {
49        let patterns = vec!["bench.*".to_string(), "test_.*".to_string()];
50        let result = compile_filters(&patterns);
51        assert!(result.is_ok());
52        assert_eq!(result.unwrap().len(), 2);
53    }
54
55    #[test]
56    fn test_compile_invalid_regex() {
57        let patterns = vec!["[invalid".to_string()];
58        let result = compile_filters(&patterns);
59        assert!(result.is_err());
60    }
61
62    #[test]
63    fn test_matches_any_filter_empty() {
64        let filters = vec![];
65        assert!(matches_any_filter("anything", &filters));
66    }
67
68    #[test]
69    fn test_matches_any_filter_single_match() {
70        let patterns = vec!["bench.*".to_string()];
71        let filters = compile_filters(&patterns).unwrap();
72        assert!(matches_any_filter("benchmark_x64", &filters));
73        assert!(!matches_any_filter("test_foo", &filters));
74    }
75
76    #[test]
77    fn test_matches_any_filter_or_logic() {
78        let patterns = vec!["bench.*".to_string(), "test_.*".to_string()];
79        let filters = compile_filters(&patterns).unwrap();
80        assert!(matches_any_filter("benchmark_x64", &filters));
81        assert!(matches_any_filter("test_foo", &filters));
82        assert!(!matches_any_filter("other_thing", &filters));
83    }
84
85    #[test]
86    fn test_anchored_patterns() {
87        let patterns = vec!["^bench.*$".to_string()];
88        let filters = compile_filters(&patterns).unwrap();
89        assert!(matches_any_filter("benchmark_x64", &filters));
90        assert!(!matches_any_filter("my_benchmark_x64", &filters));
91    }
92
93    #[test]
94    fn test_complex_regex() {
95        let patterns = vec![r"bench_.*_v\d+".to_string()];
96        let filters = compile_filters(&patterns).unwrap();
97        assert!(matches_any_filter("bench_foo_v1", &filters));
98        assert!(matches_any_filter("bench_bar_v23", &filters));
99        assert!(!matches_any_filter("bench_baz_vX", &filters));
100    }
101
102    #[test]
103    fn test_measurements_to_anchored_regex() {
104        let measurements = vec![
105            "benchmark_x64".to_string(),
106            "test.with.dots".to_string(),
107            "name[with]brackets".to_string(),
108        ];
109        let anchored = measurements_to_anchored_regex(&measurements);
110
111        assert_eq!(anchored.len(), 3);
112        assert_eq!(anchored[0], "^benchmark_x64$");
113        assert_eq!(anchored[1], r"^test\.with\.dots$");
114        assert_eq!(anchored[2], r"^name\[with\]brackets$");
115    }
116
117    #[test]
118    fn test_measurements_to_anchored_regex_matches_exactly() {
119        let measurements = vec!["benchmark".to_string()];
120        let anchored = measurements_to_anchored_regex(&measurements);
121        let filters = compile_filters(&anchored).unwrap();
122
123        // Should match exact name
124        assert!(matches_any_filter("benchmark", &filters));
125
126        // Should NOT match partial matches
127        assert!(!matches_any_filter("benchmark_x64", &filters));
128        assert!(!matches_any_filter("my_benchmark", &filters));
129    }
130
131    #[test]
132    fn test_combine_measurements_and_filters() {
133        let measurements = vec!["exact_match".to_string()];
134        let filters = vec!["pattern.*".to_string()];
135
136        let combined = combine_measurements_and_filters(&measurements, &filters);
137
138        assert_eq!(combined.len(), 2);
139        assert_eq!(combined[0], "^exact_match$");
140        assert_eq!(combined[1], "pattern.*");
141    }
142
143    #[test]
144    fn test_combine_measurements_and_filters_matching() {
145        let measurements = vec!["benchmark_x64".to_string()];
146        let filters = vec!["test_.*".to_string()];
147
148        let combined = combine_measurements_and_filters(&measurements, &filters);
149        let compiled = compile_filters(&combined).unwrap();
150
151        // Should match exact measurement name
152        assert!(matches_any_filter("benchmark_x64", &compiled));
153
154        // Should match filter pattern
155        assert!(matches_any_filter("test_foo", &compiled));
156        assert!(matches_any_filter("test_bar", &compiled));
157
158        // Should NOT match other names
159        assert!(!matches_any_filter("benchmark_arm64", &compiled));
160        assert!(!matches_any_filter("other", &compiled));
161    }
162}