Skip to content

Commit d04e128

Browse files
committed
libtest: add glob support to test name filters
Tests can currently only be filtered by substring or exact match. This makes it difficult to have finer-grained control over which tests to run, such as running only tests with a given suffix in a module, all tests without a given suffix, etc. This PR adds a `--glob` flag to make test name filters use glob patterns instead of string substring matching. This allows commands such as: --glob mymod::*_fast or --glob --skip *_slow The `--glob` flag can normally be omitted, as it is inferred whenever a pattern includes one of the glob special characters (`*`, `?`, or `[`). These characters cannot appear in ordinary test names, so this should not cause unexpected results. If both `--exact` *and* `--glob` are given, `--exact` takes precedence. Fixes rust-lang#46408.
1 parent 804b15b commit d04e128

File tree

5 files changed

+64
-2
lines changed

5 files changed

+64
-2
lines changed

src/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/libtest/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ crate-type = ["dylib", "rlib"]
1010

1111
[dependencies]
1212
getopts = "0.2"
13+
glob = "0.2"
1314
term = { path = "../libterm" }

src/libtest/lib.rs

+55-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#![feature(panic_unwind)]
4141
#![feature(staged_api)]
4242

43+
extern crate glob;
4344
extern crate getopts;
4445
extern crate term;
4546
#[cfg(unix)]
@@ -339,6 +340,7 @@ pub struct TestOpts {
339340
pub list: bool,
340341
pub filter: Option<String>,
341342
pub filter_exact: bool,
343+
pub filter_glob: bool,
342344
pub run_ignored: bool,
343345
pub run_tests: bool,
344346
pub bench_benchmarks: bool,
@@ -358,6 +360,7 @@ impl TestOpts {
358360
list: false,
359361
filter: None,
360362
filter_exact: false,
363+
filter_glob: false,
361364
run_ignored: false,
362365
run_tests: false,
363366
bench_benchmarks: false,
@@ -392,6 +395,7 @@ fn optgroups() -> getopts::Options {
392395
be used multiple times)","FILTER")
393396
.optflag("q", "quiet", "Display one character per test instead of one line")
394397
.optflag("", "exact", "Exactly match filters rather than by substring")
398+
.optflag("g", "glob", "Use glob patterns for matching test names")
395399
.optopt("", "color", "Configure coloring of output:
396400
auto = colorize if stdout is a tty and tests are run on serially (default);
397401
always = always colorize output;
@@ -445,7 +449,11 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
445449
return None;
446450
}
447451

452+
let mut glob = matches.opt_present("glob");
448453
let filter = if !matches.free.is_empty() {
454+
if matches.free[0].chars().any(|c| ['*', '?', '['].contains(&c)) {
455+
glob = true;
456+
}
449457
Some(matches.free[0].clone())
450458
} else {
451459
None
@@ -500,6 +508,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
500508
list,
501509
filter,
502510
filter_exact: exact,
511+
filter_glob: glob,
503512
run_ignored,
504513
run_tests,
505514
bench_benchmarks,
@@ -1324,9 +1333,17 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
13241333
filtered = match opts.filter {
13251334
None => filtered,
13261335
Some(ref filter) => {
1336+
let glob = if opts.filter_glob && !opts.filter_exact {
1337+
glob::Pattern::new(filter).ok()
1338+
} else {
1339+
None
1340+
};
1341+
13271342
filtered.into_iter()
13281343
.filter(|test| {
1329-
if opts.filter_exact {
1344+
if let Some(ref glob) = glob {
1345+
glob.matches(&test.desc.name.as_slice())
1346+
} else if opts.filter_exact {
13301347
test.desc.name.as_slice() == &filter[..]
13311348
} else {
13321349
test.desc.name.as_slice().contains(&filter[..])
@@ -1339,6 +1356,12 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
13391356
// Skip tests that match any of the skip filters
13401357
filtered = filtered.into_iter()
13411358
.filter(|t| !opts.skip.iter().any(|sf| {
1359+
if opts.filter_glob && !opts.filter_exact {
1360+
if let Ok(glob) = glob::Pattern::new(sf) {
1361+
return glob.matches(&t.desc.name.as_slice());
1362+
}
1363+
}
1364+
13421365
if opts.filter_exact {
13431366
t.desc.name.as_slice() == &sf[..]
13441367
} else {
@@ -1920,7 +1943,7 @@ mod tests {
19201943
}
19211944

19221945
#[test]
1923-
pub fn exact_filter_match() {
1946+
pub fn filter_type_match() {
19241947
fn tests() -> Vec<TestDescAndFn> {
19251948
vec!["base",
19261949
"base::test",
@@ -1989,6 +2012,36 @@ mod tests {
19892012
..TestOpts::new()
19902013
}, tests());
19912014
assert_eq!(exact.len(), 1);
2015+
2016+
let exact = filter_tests(&TestOpts {
2017+
filter: Some("b".into()),
2018+
filter_glob: true, ..TestOpts::new()
2019+
}, tests());
2020+
assert_eq!(exact.len(), 0);
2021+
2022+
let exact = filter_tests(&TestOpts {
2023+
filter: Some("base".into()),
2024+
filter_glob: true, ..TestOpts::new()
2025+
}, tests());
2026+
assert_eq!(exact.len(), 1);
2027+
2028+
let exact = filter_tests(&TestOpts {
2029+
filter: Some("base*".into()),
2030+
filter_glob: true, ..TestOpts::new()
2031+
}, tests());
2032+
assert_eq!(exact.len(), 4);
2033+
2034+
let exact = filter_tests(&TestOpts {
2035+
filter: Some("base::test?".into()),
2036+
filter_glob: true, ..TestOpts::new()
2037+
}, tests());
2038+
assert_eq!(exact.len(), 2);
2039+
2040+
let exact = filter_tests(&TestOpts {
2041+
filter: Some("base::test[2-9]".into()),
2042+
filter_glob: true, ..TestOpts::new()
2043+
}, tests());
2044+
assert_eq!(exact.len(), 1);
19922045
}
19932046

19942047
#[test]

src/tools/compiletest/src/common.rs

+3
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ pub struct Config {
146146
/// Exactly match the filter, rather than a substring
147147
pub filter_exact: bool,
148148

149+
/// Match the test name using a glob
150+
pub filter_glob: bool,
151+
149152
/// Write out a parseable log of tests that were run
150153
pub logfile: Option<PathBuf>,
151154

src/tools/compiletest/src/main.rs

+4
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
8383
run-pass-valgrind|pretty|debug-info|incremental|mir-opt)")
8484
.optflag("", "ignored", "run tests marked as ignored")
8585
.optflag("", "exact", "filters match exactly")
86+
.optflag("", "glob", "match test names using a glob")
8687
.optopt("", "runtool", "supervisor program to run tests under \
8788
(eg. emulator, valgrind)", "PROGRAM")
8889
.optopt("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
@@ -174,6 +175,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
174175
run_ignored: matches.opt_present("ignored"),
175176
filter: matches.free.first().cloned(),
176177
filter_exact: matches.opt_present("exact"),
178+
filter_glob: matches.opt_present("glob"),
177179
logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
178180
runtool: matches.opt_str("runtool"),
179181
host_rustcflags: matches.opt_str("host-rustcflags"),
@@ -227,6 +229,7 @@ pub fn log_config(config: &Config) {
227229
.as_ref()
228230
.map(|re| re.to_owned()))));
229231
logv(c, format!("filter_exact: {}", config.filter_exact));
232+
logv(c, format!("filter_glob: {}", config.filter_glob));
230233
logv(c, format!("runtool: {}", opt_str(&config.runtool)));
231234
logv(c, format!("host-rustcflags: {}",
232235
opt_str(&config.host_rustcflags)));
@@ -339,6 +342,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
339342
test::TestOpts {
340343
filter: config.filter.clone(),
341344
filter_exact: config.filter_exact,
345+
filter_glob: config.filter_glob
342346
run_ignored: config.run_ignored,
343347
quiet: config.quiet,
344348
logfile: config.logfile.clone(),

0 commit comments

Comments
 (0)