Auto merge of #38181 - jsgf:test-filter-exact, r=alexcrichton

libtest: add --exact to make filter matching exact

Filter matching is by substring by default. This makes it impossible
to run a single test if its name is a substring of some other test.
For example, its not possible to run just `mymod::test` with these
tests:

```
  mymod::test
  mymod::test1
  mymod::test_module::moretests
```

You could declare by convention that no test has a name that's a
substring of another test, but that's not really practical.

This PR adds the `--exact` flag, to make filter matching exactly
match the complete name.
This commit is contained in:
bors 2016-12-14 00:31:48 +00:00
commit aa7a2e9e61
3 changed files with 97 additions and 2 deletions

View File

@ -301,6 +301,7 @@ pub enum ColorConfig {
pub struct TestOpts {
pub filter: Option<String>,
pub filter_exact: bool,
pub run_ignored: bool,
pub run_tests: bool,
pub bench_benchmarks: bool,
@ -317,6 +318,7 @@ impl TestOpts {
fn new() -> TestOpts {
TestOpts {
filter: None,
filter_exact: false,
run_ignored: false,
run_tests: false,
bench_benchmarks: false,
@ -348,6 +350,7 @@ fn optgroups() -> Vec<getopts::OptGroup> {
getopts::optmulti("", "skip", "Skip tests whose names contain FILTER (this flag can \
be used multiple times)","FILTER"),
getopts::optflag("q", "quiet", "Display one character per test instead of one line"),
getopts::optflag("", "exact", "Exactly match filters rather than by substring"),
getopts::optopt("", "color", "Configure coloring of output:
auto = colorize if stdout is a tty and tests are run on serially (default);
always = always colorize output;
@ -407,6 +410,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
let run_ignored = matches.opt_present("ignored");
let quiet = matches.opt_present("quiet");
let exact = matches.opt_present("exact");
let logfile = matches.opt_str("logfile");
let logfile = logfile.map(|s| PathBuf::from(&s));
@ -448,6 +452,7 @@ pub fn parse_opts(args: &[String]) -> Option<OptRes> {
let test_opts = TestOpts {
filter: filter,
filter_exact: exact,
run_ignored: run_ignored,
run_tests: run_tests,
bench_benchmarks: bench_benchmarks,
@ -1118,14 +1123,26 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> Vec<TestDescA
None => filtered,
Some(ref filter) => {
filtered.into_iter()
.filter(|test| test.desc.name.as_slice().contains(&filter[..]))
.filter(|test| {
if opts.filter_exact {
test.desc.name.as_slice() == &filter[..]
} else {
test.desc.name.as_slice().contains(&filter[..])
}
})
.collect()
}
};
// Skip tests that match any of the skip filters
filtered = filtered.into_iter()
.filter(|t| !opts.skip.iter().any(|sf| t.desc.name.as_slice().contains(&sf[..])))
.filter(|t| !opts.skip.iter().any(|sf| {
if opts.filter_exact {
t.desc.name.as_slice() == &sf[..]
} else {
t.desc.name.as_slice().contains(&sf[..])
}
}))
.collect();
// Maybe pull out the ignored test and unignore them
@ -1654,6 +1671,77 @@ mod tests {
assert!(!filtered[0].desc.ignore);
}
#[test]
pub fn exact_filter_match() {
fn tests() -> Vec<TestDescAndFn> {
vec!["base",
"base::test",
"base::test1",
"base::test2",
].into_iter()
.map(|name| TestDescAndFn {
desc: TestDesc {
name: StaticTestName(name),
ignore: false,
should_panic: ShouldPanic::No,
},
testfn: DynTestFn(Box::new(move |()| {}))
})
.collect()
}
let substr = filter_tests(&TestOpts {
filter: Some("base".into()),
..TestOpts::new()
}, tests());
assert_eq!(substr.len(), 4);
let substr = filter_tests(&TestOpts {
filter: Some("bas".into()),
..TestOpts::new()
}, tests());
assert_eq!(substr.len(), 4);
let substr = filter_tests(&TestOpts {
filter: Some("::test".into()),
..TestOpts::new()
}, tests());
assert_eq!(substr.len(), 3);
let substr = filter_tests(&TestOpts {
filter: Some("base::test".into()),
..TestOpts::new()
}, tests());
assert_eq!(substr.len(), 3);
let exact = filter_tests(&TestOpts {
filter: Some("base".into()),
filter_exact: true, ..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 1);
let exact = filter_tests(&TestOpts {
filter: Some("bas".into()),
filter_exact: true,
..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 0);
let exact = filter_tests(&TestOpts {
filter: Some("::test".into()),
filter_exact: true,
..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 0);
let exact = filter_tests(&TestOpts {
filter: Some("base::test".into()),
filter_exact: true,
..TestOpts::new()
}, tests());
assert_eq!(exact.len(), 1);
}
#[test]
pub fn sort_tests() {
let mut opts = TestOpts::new();

View File

@ -127,6 +127,9 @@ pub struct Config {
// Only run tests that match this filter
pub filter: Option<String>,
// Exactly match the filter, rather than a substring
pub filter_exact: bool,
// Write out a parseable log of tests that were run
pub logfile: Option<PathBuf>,

View File

@ -89,6 +89,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
"(compile-fail|parse-fail|run-fail|run-pass|\
run-pass-valgrind|pretty|debug-info|incremental|mir-opt)"),
optflag("", "ignored", "run tests marked as ignored"),
optflag("", "exact", "filters match exactly"),
optopt("", "runtool", "supervisor program to run tests under \
(eg. emulator, valgrind)", "PROGRAM"),
optopt("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS"),
@ -167,6 +168,7 @@ pub fn parse_config(args: Vec<String> ) -> Config {
mode: matches.opt_str("mode").unwrap().parse().ok().expect("invalid mode"),
run_ignored: matches.opt_present("ignored"),
filter: matches.free.first().cloned(),
filter_exact: matches.opt_present("exact"),
logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)),
runtool: matches.opt_str("runtool"),
host_rustcflags: matches.opt_str("host-rustcflags"),
@ -216,6 +218,7 @@ pub fn log_config(config: &Config) {
opt_str(&config.filter
.as_ref()
.map(|re| re.to_owned()))));
logv(c, format!("filter_exact: {}", config.filter_exact));
logv(c, format!("runtool: {}", opt_str(&config.runtool)));
logv(c, format!("host-rustcflags: {}",
opt_str(&config.host_rustcflags)));
@ -309,6 +312,7 @@ pub fn run_tests(config: &Config) {
pub fn test_opts(config: &Config) -> test::TestOpts {
test::TestOpts {
filter: config.filter.clone(),
filter_exact: config.filter_exact,
run_ignored: config.run_ignored,
quiet: config.quiet,
logfile: config.logfile.clone(),