Skip to content

Commit 8fa7024

Browse files
Googlercopybara-github
authored andcommitted
Support negated patterns with - and change from : to , for pattern separation.
We adjust the supported pattern syntax to include negated patterns, improving alignment with [GTest C++](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests). Unfortunately, we have to deviate from GTest C++ with respect to the pattern separator `:` for ergonomic reasons. The colon character `:` doesn't work well to separate the patterns because `::` is Rust's namespace separator. This clash makes it much too easy to make unintentional mistakes when trying to write filter patterns with module paths. To avoid this conflict, we switch to `,` as the pattern separator. PiperOrigin-RevId: 734611957
1 parent f4ef948 commit 8fa7024

File tree

3 files changed

+142
-17
lines changed

3 files changed

+142
-17
lines changed

googletest/src/internal/test_filter.rs

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@
55
//!
66
//! * TESTBRIDGE_TEST_ONLY: string passed from --test_filter
77
//!
8-
//! We interpret it as a colon-separated list of glob patterns. If
9-
//! any pattern in the list succeeds, the filter passes.
8+
//! The format of a filter is a ‘,‘-separated list of wildcard
9+
//! patterns (called the positive patterns) optionally followed by a
10+
//! ‘-’ and another ‘,‘-separated pattern list (called the negative
11+
//! patterns). A test matches the filter if and only if it matches any
12+
//! of the positive patterns but does not match any of the negative
13+
//! patterns. (Note that this is a deliberate devation from GTest
14+
//! C++, which uses colons to separate the patterns, as colons will
15+
//! unfortunately clash with Rust's "::" namespacing operator.)
16+
//!
17+
//! As an example: "*mount*-*doom*" will accept any string that contains the
18+
//! substring "mount", as long as it also doesn't contain "doom"
1019
use crate::internal::glob::{is_glob_pattern, Pattern};
1120
use std::sync::OnceLock;
1221

@@ -51,23 +60,56 @@ impl TestFilter for Matches {
5160
}
5261

5362
struct Collection {
54-
equals: Box<[Equals]>,
55-
matches: Box<[Matches]>,
63+
// The positive portion:
64+
positive_equals: Box<[Equals]>,
65+
positive_matches: Box<[Matches]>,
66+
67+
// The negative portion:
68+
negative_equals: Box<[Equals]>,
69+
negative_matches: Box<[Matches]>,
5670
}
5771

5872
impl TestFilter for Collection {
5973
fn filter(&self, test_name: &str) -> bool {
60-
self.equals.iter().any(|f| f.filter(test_name))
61-
|| self.matches.iter().any(|f| f.filter(test_name))
74+
(self.positive_equals.iter().any(|f| f.filter(test_name))
75+
|| self.positive_matches.iter().any(|f| f.filter(test_name)))
76+
&& (!self.negative_equals.iter().any(|f| f.filter(test_name)))
77+
&& (!self.negative_matches.iter().any(|f| f.filter(test_name)))
6278
}
6379
}
6480

6581
fn get_test_filter(testbridge_test_only: &str) -> Collection {
66-
let (with_globs, literals): (Vec<_>, Vec<_>) =
67-
testbridge_test_only.split(':').partition(|s| is_glob_pattern(s));
82+
let positive_negative: Vec<&str> = testbridge_test_only.splitn(2, '-').collect();
83+
84+
let (positive_with_globs, positive_literals): (Vec<_>, Vec<_>) = {
85+
let positive = positive_negative[0];
86+
if positive.is_empty() {
87+
// Forces the empty positive filter to accept everything:
88+
(vec!["*"], vec![])
89+
} else {
90+
positive.split(',').partition(|s| is_glob_pattern(s))
91+
}
92+
};
93+
94+
let (negative_with_globs, negative_literals): (Vec<_>, Vec<_>) = match positive_negative.get(1)
95+
{
96+
Some(negative) if !negative.is_empty() => {
97+
negative.split(',').partition(|s| is_glob_pattern(s))
98+
}
99+
_ => (vec![], vec![]),
100+
};
101+
68102
Collection {
69-
equals: literals.into_iter().map(|s| Equals(s.to_string())).collect(),
70-
matches: with_globs.into_iter().map(|s| Matches(Pattern::new(s.to_string()))).collect(),
103+
positive_equals: positive_literals.into_iter().map(|s| Equals(s.to_string())).collect(),
104+
positive_matches: positive_with_globs
105+
.into_iter()
106+
.map(|s| Matches(Pattern::new(s.to_string())))
107+
.collect(),
108+
negative_equals: negative_literals.into_iter().map(|s| Equals(s.to_string())).collect(),
109+
negative_matches: negative_with_globs
110+
.into_iter()
111+
.map(|s| Matches(Pattern::new(s.to_string())))
112+
.collect(),
71113
}
72114
}
73115

@@ -86,11 +128,20 @@ mod tests {
86128
}
87129

88130
#[test]
89-
fn empty_filter_accepts_only_empty() -> Result<()> {
131+
fn empty_filter_accepts_all() -> Result<()> {
90132
let filter = get_test_filter("");
91133

92134
verify_that!(filter.filter(""), is_true())?;
93-
verify_that!(filter.filter("abcdefg"), is_false())?;
135+
verify_that!(filter.filter("abcdefg"), is_true())?;
136+
Ok(())
137+
}
138+
139+
#[test]
140+
fn empty_negation_filter_accepts_all() -> Result<()> {
141+
let filter = get_test_filter("-");
142+
143+
verify_that!(filter.filter(""), is_true())?;
144+
verify_that!(filter.filter("abcdefg"), is_true())?;
94145
Ok(())
95146
}
96147

@@ -134,7 +185,7 @@ mod tests {
134185

135186
#[test]
136187
fn collection() -> Result<()> {
137-
let filter = get_test_filter("a:b");
188+
let filter = get_test_filter("a,b");
138189

139190
verify_that!(filter.filter(""), is_false())?;
140191
verify_that!(filter.filter("a"), is_true())?;
@@ -148,12 +199,63 @@ mod tests {
148199

149200
#[test]
150201
fn collection_with_globs() -> Result<()> {
151-
let filter = get_test_filter("*test1*:*test2*");
202+
let filter = get_test_filter("*test1*,*test2*");
152203

153204
verify_that!(filter.filter(""), is_false())?;
154205
verify_that!(filter.filter("this is test1"), is_true())?;
155206
verify_that!(filter.filter("and test2 is it"), is_true())?;
156207
verify_that!(filter.filter("but test3 is not"), is_false())?;
157208
Ok(())
158209
}
210+
211+
#[test]
212+
fn collection_with_globs_negation() -> Result<()> {
213+
let filter = get_test_filter("*test*-*testbad");
214+
215+
verify_that!(filter.filter(""), is_false())?;
216+
verify_that!(filter.filter("module"), is_false())?;
217+
verify_that!(filter.filter("module::my_test1"), is_true())?;
218+
verify_that!(filter.filter("module::my_test2"), is_true())?;
219+
verify_that!(filter.filter("module::my_testbad"), is_false())?;
220+
Ok(())
221+
}
222+
223+
#[test]
224+
fn mount_doom() -> Result<()> {
225+
let filter = get_test_filter("*mount*-*doom*");
226+
227+
verify_that!(filter.filter(""), is_false())?;
228+
verify_that!(filter.filter("mount rushmore"), is_true())?;
229+
verify_that!(filter.filter("doom mount"), is_false())?;
230+
verify_that!(filter.filter("dismount"), is_true())?;
231+
verify_that!(filter.filter("mountains of moria"), is_true())?;
232+
verify_that!(filter.filter("frodo and sam went to mount doom"), is_false())?;
233+
Ok(())
234+
}
235+
236+
#[test]
237+
fn collection_with_only_negation() -> Result<()> {
238+
let filter = get_test_filter("-testbad1,testbad2");
239+
240+
verify_that!(filter.filter(""), is_true())?;
241+
verify_that!(filter.filter("test"), is_true())?;
242+
verify_that!(filter.filter("testbad1"), is_false())?;
243+
verify_that!(filter.filter("testbad2"), is_false())?;
244+
verify_that!(filter.filter("testbad3"), is_true())?;
245+
Ok(())
246+
}
247+
248+
#[test]
249+
fn magic_words_a_and_e() -> Result<()> {
250+
let filter = get_test_filter("a*,e*-abracadabra,elbereth,avada kedavra");
251+
252+
verify_that!(filter.filter("alakazam"), is_true())?;
253+
verify_that!(filter.filter("abracadabra"), is_false())?;
254+
verify_that!(filter.filter("avada kedavra"), is_false())?;
255+
verify_that!(filter.filter("enchantment"), is_true())?;
256+
verify_that!(filter.filter("elbereth"), is_false())?;
257+
verify_that!(filter.filter("expecto patronum"), is_true())?;
258+
verify_that!(filter.filter("fuego"), is_false())?;
259+
Ok(())
260+
}
159261
}

integration_tests/src/always_fails.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ mod tests {
1919
use googletest::prelude::*;
2020

2121
#[gtest]
22-
fn always_fails() -> Result<()> {
22+
fn this_always_fails() -> Result<()> {
2323
verify_that!(2, eq(3))
2424
}
2525
}

integration_tests/src/integration_tests.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,15 +1940,38 @@ mod tests {
19401940
}
19411941

19421942
#[gtest]
1943-
fn should_fail_when_failing_test_matches() -> Result<()> {
1943+
fn should_fail_on_always_fails_when_filter_is_empty() -> Result<()> {
1944+
verify_that!(execute_filtered_test("always_fails", "")?, is_false())
1945+
}
1946+
1947+
#[gtest]
1948+
fn should_fail_when_failing_test_matches_exactly() -> Result<()> {
1949+
verify_that!(
1950+
execute_filtered_test("always_fails", "always_fails::tests::this_always_fails")?,
1951+
is_false()
1952+
)
1953+
}
1954+
1955+
#[gtest]
1956+
fn should_fail_when_failing_test_matches_with_globs() -> Result<()> {
19441957
verify_that!(execute_filtered_test("always_fails", "*fails*")?, is_false())
19451958
}
19461959

19471960
#[gtest]
1948-
fn should_pass_when_failing_test_mismatch() -> Result<()> {
1961+
fn should_pass_when_failing_test_mismatches_no_implicit_contains() -> Result<()> {
1962+
verify_that!(execute_filtered_test("always_fails", "fails")?, is_true())
1963+
}
1964+
1965+
#[gtest]
1966+
fn should_pass_when_failing_test_mismatches_obviously() -> Result<()> {
19491967
verify_that!(execute_filtered_test("always_fails", "filter_should_mismatch")?, is_true())
19501968
}
19511969

1970+
#[gtest]
1971+
fn should_pass_when_failing_test_excluded() -> Result<()> {
1972+
verify_that!(execute_filtered_test("always_fails", "-*::this_always_fails")?, is_true())
1973+
}
1974+
19521975
#[::core::prelude::v1::test]
19531976
#[should_panic]
19541977
fn should_panic_when_expect_that_runs_without_attribute_macro_after_another_test() {

0 commit comments

Comments
 (0)