about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2014-05-15 11:22:02 -0700
committerbors <bors@rust-lang.org>2014-05-15 11:22:02 -0700
commitba5f53009aab826d8bb8fa97f3213b2fee803e29 (patch)
treeb749932a3bac83abb73d22ee28a5ade72b77f117 /src
parentfbd8f4a3a363877b244a88ba3b4fd5d4fd6288cf (diff)
parent18c13de5e6dfd4631f9ed05e0ab49305bf0384ac (diff)
downloadrust-ba5f53009aab826d8bb8fa97f3213b2fee803e29.tar.gz
rust-ba5f53009aab826d8bb8fa97f3213b2fee803e29.zip
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.)
Diffstat (limited to 'src')
-rw-r--r--src/compiletest/common.rs3
-rw-r--r--src/compiletest/compiletest.rs26
-rw-r--r--src/doc/guide-testing.md34
-rw-r--r--src/libstd/unstable/dynamic_lib.rs14
-rw-r--r--src/libtest/lib.rs145
5 files changed, 137 insertions, 85 deletions
diff --git a/src/compiletest/common.rs b/src/compiletest/common.rs
index 9934a48c856..b1f1e69c5a1 100644
--- a/src/compiletest/common.rs
+++ b/src/compiletest/common.rs
@@ -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>,
diff --git a/src/compiletest/compiletest.rs b/src/compiletest/compiletest.rs
index 32bd66c2004..3b57e3e98ca 100644
--- a/src/compiletest/compiletest.rs
+++ b/src/compiletest/compiletest.rs
@@ -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(),
diff --git a/src/doc/guide-testing.md b/src/doc/guide-testing.md
index 0be831c5132..057849f1bca 100644
--- a/src/doc/guide-testing.md
+++ b/src/doc/guide-testing.md
@@ -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
 
+Using a plain string:
+
+~~~ {.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 mytest1
+$ mytests 'mytest[145]'
 
-running 11 tests
+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
diff --git a/src/libstd/unstable/dynamic_lib.rs b/src/libstd/unstable/dynamic_lib.rs
index 68bfde84e2a..4a9c5349459 100644
--- a/src/libstd/unstable/dynamic_lib.rs
+++ b/src/libstd/unstable/dynamic_lib.rs
@@ -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());
     }
 
diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs
index ba8b8f776d9..3273e53ed8a 100644
--- a/src/libtest/lib.rs
+++ b/src/libtest/lib.rs
@@ -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]
@@ -1550,6 +1538,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();
         let mut m2 = MetricMap::new();