Skip to content

Commit

Permalink
libtest: add glob support to test name filters
Browse files Browse the repository at this point in the history
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 #46408.
  • Loading branch information
jonhoo committed Dec 1, 2017
1 parent 804b15b commit d04e128
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/libtest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ crate-type = ["dylib", "rlib"]

[dependencies]
getopts = "0.2"
glob = "0.2"
term = { path = "../libterm" }
57 changes: 55 additions & 2 deletions src/libtest/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#![feature(panic_unwind)]
#![feature(staged_api)]

extern crate glob;
extern crate getopts;
extern crate term;
#[cfg(unix)]
Expand Down Expand Up @@ -339,6 +340,7 @@ pub struct TestOpts {
pub list: bool,
pub filter: Option<String>,
pub filter_exact: bool,
pub filter_glob: bool,
pub run_ignored: bool,
pub run_tests: bool,
pub bench_benchmarks: bool,
Expand All @@ -358,6 +360,7 @@ impl TestOpts {
list: false,
filter: None,
filter_exact: false,
filter_glob: false,
run_ignored: false,
run_tests: false,
bench_benchmarks: false,
Expand Down Expand Up @@ -392,6 +395,7 @@ fn optgroups() -> getopts::Options {
be used multiple times)","FILTER")
.optflag("q", "quiet", "Display one character per test instead of one line")
.optflag("", "exact", "Exactly match filters rather than by substring")
.optflag("g", "glob", "Use glob patterns for matching test names")
.optopt("", "color", "Configure coloring of output:
auto = colorize if stdout is a tty and tests are run on serially (default);
always = always colorize output;
Expand Down Expand Up @@ -445,7 +449,11 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
return None;
}

let mut glob = matches.opt_present("glob");
let filter = if !matches.free.is_empty() {
if matches.free[0].chars().any(|c| ['*', '?', '['].contains(&c)) {
glob = true;
}
Some(matches.free[0].clone())
} else {
None
Expand Down Expand Up @@ -500,6 +508,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
list,
filter,
filter_exact: exact,
filter_glob: glob,
run_ignored,
run_tests,
bench_benchmarks,
Expand Down Expand Up @@ -1324,9 +1333,17 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
filtered = match opts.filter {
None => filtered,
Some(ref filter) => {
let glob = if opts.filter_glob && !opts.filter_exact {
glob::Pattern::new(filter).ok()
} else {
None
};

filtered.into_iter()
.filter(|test| {
if opts.filter_exact {
if let Some(ref glob) = glob {
glob.matches(&test.desc.name.as_slice())
} else if opts.filter_exact {
test.desc.name.as_slice() == &filter[..]
} else {
test.desc.name.as_slice().contains(&filter[..])
Expand All @@ -1339,6 +1356,12 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
// Skip tests that match any of the skip filters
filtered = filtered.into_iter()
.filter(|t| !opts.skip.iter().any(|sf| {
if opts.filter_glob && !opts.filter_exact {
if let Ok(glob) = glob::Pattern::new(sf) {
return glob.matches(&t.desc.name.as_slice());
}
}

if opts.filter_exact {
t.desc.name.as_slice() == &sf[..]
} else {
Expand Down Expand Up @@ -1920,7 +1943,7 @@ mod tests {
}

#[test]
pub fn exact_filter_match() {
pub fn filter_type_match() {
fn tests() -> Vec<TestDescAndFn> {
vec!["base",
"base::test",
Expand Down Expand Up @@ -1989,6 +2012,36 @@ mod tests {
..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 1);

let exact = filter_tests(&TestOpts {
filter: Some("b".into()),
filter_glob: true, ..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 0);

let exact = filter_tests(&TestOpts {
filter: Some("base".into()),
filter_glob: true, ..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 1);

let exact = filter_tests(&TestOpts {
filter: Some("base*".into()),
filter_glob: true, ..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 4);

let exact = filter_tests(&TestOpts {
filter: Some("base::test?".into()),
filter_glob: true, ..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 2);

let exact = filter_tests(&TestOpts {
filter: Some("base::test[2-9]".into()),
filter_glob: true, ..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 1);
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions src/tools/compiletest/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ pub struct Config {
/// Exactly match the filter, rather than a substring
pub filter_exact: bool,

/// Match the test name using a glob
pub filter_glob: bool,

/// Write out a parseable log of tests that were run
pub logfile: Option<PathBuf>,

Expand Down
4 changes: 4 additions & 0 deletions src/tools/compiletest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
run-pass-valgrind|pretty|debug-info|incremental|mir-opt)")
.optflag("", "ignored", "run tests marked as ignored")
.optflag("", "exact", "filters match exactly")
.optflag("", "glob", "match test names using a glob")
.optopt("", "runtool", "supervisor program to run tests under \
(eg. emulator, valgrind)", "PROGRAM")
.optopt("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS")
Expand Down Expand Up @@ -174,6 +175,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
run_ignored: matches.opt_present("ignored"),
filter: matches.free.first().cloned(),
filter_exact: matches.opt_present("exact"),
filter_glob: matches.opt_present("glob"),
logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
runtool: matches.opt_str("runtool"),
host_rustcflags: matches.opt_str("host-rustcflags"),
Expand Down Expand Up @@ -227,6 +229,7 @@ pub fn log_config(config: &Config) {
.as_ref()
.map(|re| re.to_owned()))));
logv(c, format!("filter_exact: {}", config.filter_exact));
logv(c, format!("filter_glob: {}", config.filter_glob));
logv(c, format!("runtool: {}", opt_str(&config.runtool)));
logv(c, format!("host-rustcflags: {}",
opt_str(&config.host_rustcflags)));
Expand Down Expand Up @@ -339,6 +342,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
test::TestOpts {
filter: config.filter.clone(),
filter_exact: config.filter_exact,
filter_glob: config.filter_glob
run_ignored: config.run_ignored,
quiet: config.quiet,
logfile: config.logfile.clone(),
Expand Down

0 comments on commit d04e128

Please sign in to comment.