auto merge of #13948 : huonw/rust/test-regex-filter, r=alexcrichton
This allows writing a regex to filter tests more precisely, rather than having to list long paths e.g. ``` $ ./stdtest-x86_64-unknown-linux-gnu 'vec.*clone' running 2 tests test vec::tests::test_clone ... ok test vec::tests::test_clone_from ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured ``` The regex change is fully backwards compatible, since test names are Rust identifiers + `:`, and hence not special regex characters. (See commits for details.)
This commit is contained in:
commit
ba5f53009a
@ -80,7 +80,7 @@ DEPS_collections := std rand
|
||||
DEPS_fourcc := syntax std
|
||||
DEPS_hexfloat := syntax std
|
||||
DEPS_num := std rand
|
||||
DEPS_test := std collections getopts serialize term time
|
||||
DEPS_test := std collections getopts serialize term time regex
|
||||
DEPS_time := std serialize
|
||||
DEPS_rand := std
|
||||
DEPS_url := std collections
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
use std::from_str::FromStr;
|
||||
use std::fmt;
|
||||
use regex::Regex;
|
||||
|
||||
#[deriving(Clone, Eq)]
|
||||
pub enum Mode {
|
||||
@ -88,7 +89,7 @@ pub struct Config {
|
||||
pub run_ignored: bool,
|
||||
|
||||
// Only run tests that match this filter
|
||||
pub filter: Option<~str>,
|
||||
pub filter: Option<Regex>,
|
||||
|
||||
// Write out a parseable log of tests that were run
|
||||
pub logfile: Option<Path>,
|
||||
|
@ -23,6 +23,8 @@ extern crate log;
|
||||
extern crate green;
|
||||
extern crate rustuv;
|
||||
|
||||
extern crate regex;
|
||||
|
||||
use std::os;
|
||||
use std::io;
|
||||
use std::io::fs;
|
||||
@ -113,6 +115,19 @@ pub fn parse_config(args: Vec<~str> ) -> Config {
|
||||
Path::new(m.opt_str(nm).unwrap())
|
||||
}
|
||||
|
||||
let filter = if !matches.free.is_empty() {
|
||||
let s = matches.free.get(0).as_slice();
|
||||
match regex::Regex::new(s) {
|
||||
Ok(re) => Some(re),
|
||||
Err(e) => {
|
||||
println!("failed to parse filter /{}/: {}", s, e);
|
||||
fail!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Config {
|
||||
compile_lib_path: matches.opt_str("compile-lib-path").unwrap(),
|
||||
run_lib_path: matches.opt_str("run-lib-path").unwrap(),
|
||||
@ -125,12 +140,7 @@ pub fn parse_config(args: Vec<~str> ) -> Config {
|
||||
stage_id: matches.opt_str("stage-id").unwrap(),
|
||||
mode: FromStr::from_str(matches.opt_str("mode").unwrap()).expect("invalid mode"),
|
||||
run_ignored: matches.opt_present("ignored"),
|
||||
filter:
|
||||
if !matches.free.is_empty() {
|
||||
Some((*matches.free.get(0)).clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
filter: filter,
|
||||
logfile: matches.opt_str("logfile").map(|s| Path::new(s)),
|
||||
save_metrics: matches.opt_str("save-metrics").map(|s| Path::new(s)),
|
||||
ratchet_metrics:
|
||||
@ -169,7 +179,7 @@ pub fn log_config(config: &Config) {
|
||||
logv(c, format!("stage_id: {}", config.stage_id));
|
||||
logv(c, format!("mode: {}", config.mode));
|
||||
logv(c, format!("run_ignored: {}", config.run_ignored));
|
||||
logv(c, format!("filter: {}", opt_str(&config.filter)));
|
||||
logv(c, format!("filter: {}", opt_str(&config.filter.as_ref().map(|re| re.to_str()))));
|
||||
logv(c, format!("runtool: {}", opt_str(&config.runtool)));
|
||||
logv(c, format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)));
|
||||
logv(c, format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)));
|
||||
@ -238,7 +248,7 @@ pub fn test_opts(config: &Config) -> test::TestOpts {
|
||||
test::TestOpts {
|
||||
filter: match config.filter {
|
||||
None => None,
|
||||
Some(ref filter) => Some(filter.to_strbuf()),
|
||||
Some(ref filter) => Some(filter.clone()),
|
||||
},
|
||||
run_ignored: config.run_ignored,
|
||||
logfile: config.logfile.clone(),
|
||||
|
@ -90,10 +90,15 @@ fn test_out_of_bounds_failure() {
|
||||
~~~
|
||||
|
||||
A test runner built with the `--test` flag supports a limited set of
|
||||
arguments to control which tests are run: the first free argument
|
||||
passed to a test runner specifies a filter used to narrow down the set
|
||||
of tests being run; the `--ignored` flag tells the test runner to run
|
||||
only tests with the `ignore` attribute.
|
||||
arguments to control which tests are run:
|
||||
|
||||
- the first free argument passed to a test runner is interpreted as a
|
||||
regular expression
|
||||
([syntax reference](regex/index.html#syntax))
|
||||
and is used to narrow down the set of tests being run. Note: a plain
|
||||
string is a valid regular expression that matches itself.
|
||||
- the `--ignored` flag tells the test runner to run only tests with the
|
||||
`ignore` attribute.
|
||||
|
||||
## Parallelism
|
||||
|
||||
@ -146,16 +151,31 @@ result: FAILED. 1 passed; 1 failed; 0 ignored
|
||||
|
||||
### Running a subset of tests
|
||||
|
||||
~~~ {.notrust}
|
||||
$ mytests mytest1
|
||||
Using a plain string:
|
||||
|
||||
running 11 tests
|
||||
~~~ {.notrust}
|
||||
$ mytests mytest23
|
||||
|
||||
running 1 tests
|
||||
running driver::tests::mytest23 ... ok
|
||||
|
||||
result: ok. 1 passed; 0 failed; 0 ignored
|
||||
~~~
|
||||
|
||||
Using some regular expression features:
|
||||
|
||||
~~~ {.notrust}
|
||||
$ mytests 'mytest[145]'
|
||||
|
||||
running 13 tests
|
||||
running driver::tests::mytest1 ... ok
|
||||
running driver::tests::mytest4 ... ok
|
||||
running driver::tests::mytest5 ... ok
|
||||
running driver::tests::mytest10 ... ignored
|
||||
... snip ...
|
||||
running driver::tests::mytest19 ... ok
|
||||
|
||||
result: ok. 11 passed; 0 failed; 1 ignored
|
||||
result: ok. 13 passed; 0 failed; 1 ignored
|
||||
~~~
|
||||
|
||||
# Microbenchmarking
|
||||
|
@ -16,8 +16,8 @@ A simple wrapper over the platform's dynamic library facilities
|
||||
|
||||
*/
|
||||
|
||||
|
||||
use c_str::ToCStr;
|
||||
use iter::Iterator;
|
||||
use mem;
|
||||
use ops::*;
|
||||
use option::*;
|
||||
@ -25,7 +25,7 @@ use os;
|
||||
use path::GenericPath;
|
||||
use path;
|
||||
use result::*;
|
||||
use slice::{Vector,OwnedVector};
|
||||
use slice::Vector;
|
||||
use str;
|
||||
use vec::Vec;
|
||||
|
||||
@ -85,10 +85,12 @@ impl DynamicLibrary {
|
||||
} else {
|
||||
("LD_LIBRARY_PATH", ':' as u8)
|
||||
};
|
||||
let newenv = os::getenv_as_bytes(envvar).unwrap_or(box []);
|
||||
let mut newenv = newenv.move_iter().collect::<Vec<_>>();
|
||||
newenv.push_all(&[sep]);
|
||||
newenv.push_all(path.as_vec());
|
||||
let mut newenv = Vec::from_slice(path.as_vec());
|
||||
newenv.push(sep);
|
||||
match os::getenv_as_bytes(envvar) {
|
||||
Some(bytes) => newenv.push_all(bytes),
|
||||
None => {}
|
||||
}
|
||||
os::setenv(envvar, str::from_utf8(newenv.as_slice()).unwrap());
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
|
||||
extern crate collections;
|
||||
extern crate getopts;
|
||||
extern crate regex;
|
||||
extern crate serialize;
|
||||
extern crate term;
|
||||
extern crate time;
|
||||
@ -45,6 +46,7 @@ use collections::TreeMap;
|
||||
use stats::Stats;
|
||||
use time::precise_time_ns;
|
||||
use getopts::{OptGroup, optflag, optopt};
|
||||
use regex::Regex;
|
||||
use serialize::{json, Decodable};
|
||||
use serialize::json::{Json, ToJson};
|
||||
use term::Terminal;
|
||||
@ -53,6 +55,7 @@ use term::color::{Color, RED, YELLOW, GREEN, CYAN};
|
||||
use std::cmp;
|
||||
use std::f64;
|
||||
use std::fmt;
|
||||
use std::fmt::Show;
|
||||
use std::from_str::FromStr;
|
||||
use std::io::stdio::StdWriter;
|
||||
use std::io::{File, ChanReader, ChanWriter};
|
||||
@ -85,14 +88,19 @@ pub enum TestName {
|
||||
StaticTestName(&'static str),
|
||||
DynTestName(StrBuf)
|
||||
}
|
||||
impl fmt::Show for TestName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
impl TestName {
|
||||
fn as_slice<'a>(&'a self) -> &'a str {
|
||||
match *self {
|
||||
StaticTestName(s) => f.buf.write_str(s),
|
||||
DynTestName(ref s) => f.buf.write_str(s.as_slice()),
|
||||
StaticTestName(s) => s,
|
||||
DynTestName(ref s) => s.as_slice()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Show for TestName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.as_slice().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[deriving(Clone)]
|
||||
enum NamePadding { PadNone, PadOnLeft, PadOnRight }
|
||||
@ -100,7 +108,7 @@ enum NamePadding { PadNone, PadOnLeft, PadOnRight }
|
||||
impl TestDesc {
|
||||
fn padded_name(&self, column_count: uint, align: NamePadding) -> StrBuf {
|
||||
use std::num::Saturating;
|
||||
let mut name = StrBuf::from_str(self.name.to_str());
|
||||
let mut name = StrBuf::from_str(self.name.as_slice());
|
||||
let fill = column_count.saturating_sub(name.len());
|
||||
let mut pad = StrBuf::from_owned_str(" ".repeat(fill));
|
||||
match align {
|
||||
@ -257,7 +265,7 @@ pub fn test_main_static_x(args: &[~str], tests: &[TestDescAndFn]) {
|
||||
}
|
||||
|
||||
pub struct TestOpts {
|
||||
pub filter: Option<StrBuf>,
|
||||
pub filter: Option<Regex>,
|
||||
pub run_ignored: bool,
|
||||
pub run_tests: bool,
|
||||
pub run_benchmarks: bool,
|
||||
@ -312,14 +320,12 @@ fn optgroups() -> Vec<getopts::OptGroup> {
|
||||
task, allow printing directly"))
|
||||
}
|
||||
|
||||
fn usage(binary: &str, helpstr: &str) {
|
||||
fn usage(binary: &str) {
|
||||
let message = format!("Usage: {} [OPTIONS] [FILTER]", binary);
|
||||
println!("{}", getopts::usage(message, optgroups().as_slice()));
|
||||
println!("");
|
||||
if helpstr == "help" {
|
||||
println!("{}", "\
|
||||
The FILTER is matched against the name of all tests to run, and if any tests
|
||||
have a substring match, only those tests are run.
|
||||
println!(r"{usage}
|
||||
|
||||
The FILTER regex is tested against the name of all tests to run, and
|
||||
only those tests that match are run.
|
||||
|
||||
By default, all tests are run in parallel. This can be altered with the
|
||||
RUST_TEST_TASKS environment variable when running tests (set it to 1).
|
||||
@ -330,18 +336,18 @@ environment variable. Logging is not captured by default.
|
||||
|
||||
Test Attributes:
|
||||
|
||||
#[test] - Indicates a function is a test to be run. This function
|
||||
\#[test] - Indicates a function is a test to be run. This function
|
||||
takes no arguments.
|
||||
#[bench] - Indicates a function is a benchmark to be run. This
|
||||
\#[bench] - Indicates a function is a benchmark to be run. This
|
||||
function takes one argument (test::Bencher).
|
||||
#[should_fail] - This function (also labeled with #[test]) will only pass if
|
||||
\#[should_fail] - This function (also labeled with \#[test]) will only pass if
|
||||
the code causes a failure (an assertion failure or fail!)
|
||||
#[ignore] - When applied to a function which is already attributed as a
|
||||
\#[ignore] - When applied to a function which is already attributed as a
|
||||
test, then the test runner will ignore these tests during
|
||||
normal test runs. Running with --ignored will run these
|
||||
tests. This may also be written as #[ignore(cfg(...))] to
|
||||
ignore the test on certain configurations.");
|
||||
}
|
||||
tests. This may also be written as \#[ignore(cfg(...))] to
|
||||
ignore the test on certain configurations.",
|
||||
usage = getopts::usage(message, optgroups().as_slice()));
|
||||
}
|
||||
|
||||
// Parses command line arguments into test options
|
||||
@ -357,21 +363,17 @@ pub fn parse_opts(args: &[StrBuf]) -> Option<OptRes> {
|
||||
Err(f) => return Some(Err(f.to_err_msg().to_strbuf()))
|
||||
};
|
||||
|
||||
if matches.opt_present("h") {
|
||||
usage(args[0].as_slice(), "h");
|
||||
return None;
|
||||
}
|
||||
if matches.opt_present("help") {
|
||||
usage(args[0].as_slice(), "help");
|
||||
return None;
|
||||
}
|
||||
if matches.opt_present("h") { usage(args[0].as_slice()); return None; }
|
||||
|
||||
let filter =
|
||||
if matches.free.len() > 0 {
|
||||
Some((*matches.free.get(0)).to_strbuf())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let filter = if matches.free.len() > 0 {
|
||||
let s = matches.free.get(0).as_slice();
|
||||
match Regex::new(s) {
|
||||
Ok(re) => Some(re),
|
||||
Err(e) => return Some(Err(format_strbuf!("could not parse /{}/: {}", s, e)))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let run_ignored = matches.opt_present("ignored");
|
||||
|
||||
@ -590,7 +592,7 @@ impl<T: Writer> ConsoleTestState<T> {
|
||||
TrIgnored => "ignored".to_strbuf(),
|
||||
TrMetrics(ref mm) => fmt_metrics(mm),
|
||||
TrBench(ref bs) => fmt_bench_samples(bs)
|
||||
}, test.name.to_str());
|
||||
}, test.name.as_slice());
|
||||
o.write(s.as_bytes())
|
||||
}
|
||||
}
|
||||
@ -604,7 +606,7 @@ impl<T: Writer> ConsoleTestState<T> {
|
||||
failures.push(f.name.to_str());
|
||||
if stdout.len() > 0 {
|
||||
fail_out.push_str(format!("---- {} stdout ----\n\t",
|
||||
f.name.to_str()));
|
||||
f.name.as_slice()));
|
||||
let output = str::from_utf8_lossy(stdout.as_slice());
|
||||
fail_out.push_str(output.as_slice().replace("\n", "\n\t"));
|
||||
fail_out.push_str("\n");
|
||||
@ -618,7 +620,7 @@ impl<T: Writer> ConsoleTestState<T> {
|
||||
try!(self.write_plain("\nfailures:\n"));
|
||||
failures.as_mut_slice().sort();
|
||||
for name in failures.iter() {
|
||||
try!(self.write_plain(format!(" {}\n", name.to_str())));
|
||||
try!(self.write_plain(format!(" {}\n", name.as_slice())));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -753,7 +755,7 @@ pub fn run_tests_console(opts: &TestOpts,
|
||||
TrOk => st.passed += 1,
|
||||
TrIgnored => st.ignored += 1,
|
||||
TrMetrics(mm) => {
|
||||
let tname = test.name.to_str();
|
||||
let tname = test.name.as_slice();
|
||||
let MetricMap(mm) = mm;
|
||||
for (k,v) in mm.iter() {
|
||||
st.metrics
|
||||
@ -764,7 +766,7 @@ pub fn run_tests_console(opts: &TestOpts,
|
||||
st.measured += 1
|
||||
}
|
||||
TrBench(bs) => {
|
||||
st.metrics.insert_metric(test.name.to_str(),
|
||||
st.metrics.insert_metric(test.name.as_slice(),
|
||||
bs.ns_iter_summ.median,
|
||||
bs.ns_iter_summ.max - bs.ns_iter_summ.min);
|
||||
st.measured += 1
|
||||
@ -782,12 +784,12 @@ pub fn run_tests_console(opts: &TestOpts,
|
||||
fn len_if_padded(t: &TestDescAndFn) -> uint {
|
||||
match t.testfn.padding() {
|
||||
PadNone => 0u,
|
||||
PadOnLeft | PadOnRight => t.desc.name.to_str().len(),
|
||||
PadOnLeft | PadOnRight => t.desc.name.as_slice().len(),
|
||||
}
|
||||
}
|
||||
match tests.iter().max_by(|t|len_if_padded(*t)) {
|
||||
Some(t) => {
|
||||
let n = t.desc.name.to_str();
|
||||
let n = t.desc.name.as_slice();
|
||||
st.max_name_len = n.len();
|
||||
},
|
||||
None => {}
|
||||
@ -939,26 +941,12 @@ pub fn filter_tests(
|
||||
let mut filtered = tests;
|
||||
|
||||
// Remove tests that don't match the test filter
|
||||
filtered = if opts.filter.is_none() {
|
||||
filtered
|
||||
} else {
|
||||
let filter_str = match opts.filter {
|
||||
Some(ref f) => (*f).clone(),
|
||||
None => "".to_strbuf()
|
||||
};
|
||||
|
||||
fn filter_fn(test: TestDescAndFn, filter_str: &str) ->
|
||||
Option<TestDescAndFn> {
|
||||
if test.desc.name.to_str().contains(filter_str) {
|
||||
return Some(test);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
filtered = match opts.filter {
|
||||
None => filtered,
|
||||
Some(ref re) => {
|
||||
filtered.move_iter()
|
||||
.filter(|test| re.is_match(test.desc.name.as_slice())).collect()
|
||||
}
|
||||
|
||||
filtered.move_iter()
|
||||
.filter_map(|x| filter_fn(x, filter_str.as_slice()))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Maybe pull out the ignored test and unignore them
|
||||
@ -980,7 +968,7 @@ pub fn filter_tests(
|
||||
};
|
||||
|
||||
// Sort the tests alphabetically
|
||||
filtered.sort_by(|t1, t2| t1.desc.name.to_str().cmp(&t2.desc.name.to_str()));
|
||||
filtered.sort_by(|t1, t2| t1.desc.name.as_slice().cmp(&t2.desc.name.as_slice()));
|
||||
|
||||
// Shard the remaining tests, if sharding requested.
|
||||
match opts.test_shard {
|
||||
@ -1445,12 +1433,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn first_free_arg_should_be_a_filter() {
|
||||
let args = vec!("progname".to_strbuf(), "filter".to_strbuf());
|
||||
let args = vec!("progname".to_strbuf(), "some_regex_filter".to_strbuf());
|
||||
let opts = match parse_opts(args.as_slice()) {
|
||||
Some(Ok(o)) => o,
|
||||
_ => fail!("Malformed arg in first_free_arg_should_be_a_filter")
|
||||
};
|
||||
assert!("filter" == opts.filter.clone().unwrap().as_slice());
|
||||
assert!(opts.filter.expect("should've found filter").is_match("some_regex_filter"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1549,6 +1537,37 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn filter_tests_regex() {
|
||||
let mut opts = TestOpts::new();
|
||||
opts.filter = Some(::regex::Regex::new("a.*b.+c").unwrap());
|
||||
|
||||
let mut names = ["yes::abXc", "yes::aXXXbXXXXc",
|
||||
"no::XYZ", "no::abc"];
|
||||
names.sort();
|
||||
|
||||
fn test_fn() {}
|
||||
let tests = names.iter().map(|name| {
|
||||
TestDescAndFn {
|
||||
desc: TestDesc {
|
||||
name: DynTestName(name.to_strbuf()),
|
||||
ignore: false,
|
||||
should_fail: false
|
||||
},
|
||||
testfn: DynTestFn(test_fn)
|
||||
}
|
||||
}).collect();
|
||||
let filtered = filter_tests(&opts, tests);
|
||||
|
||||
let expected: Vec<&str> =
|
||||
names.iter().map(|&s| s).filter(|name| name.starts_with("yes")).collect();
|
||||
|
||||
assert_eq!(filtered.len(), expected.len());
|
||||
for (test, expected_name) in filtered.iter().zip(expected.iter()) {
|
||||
assert_eq!(test.desc.name.as_slice(), *expected_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_metricmap_compare() {
|
||||
let mut m1 = MetricMap::new();
|
||||
|
Loading…
Reference in New Issue
Block a user