diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index cfe2fad0fa469..fb5e83551b2db 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -499,6 +499,7 @@ impl Collector { ignore: should_ignore, // compiler failures are test failures should_panic: testing::ShouldPanic::No, + serial: false, }, testfn: testing::DynTestFn(box move |()| { let panic = io::set_panic(None); diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index b2f52d11db2b1..1d084da0965c1 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -530,6 +530,7 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("derive", Normal, Ungated), ("should_panic", Normal, Ungated), ("ignore", Normal, Ungated), + ("serial", Normal, Ungated), ("no_implicit_prelude", Normal, Ungated), ("reexport_test_harness_main", Normal, Ungated), ("link_args", Normal, Ungated), diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index a0d1785c6ff14..afb35fa95b360 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -52,7 +52,8 @@ struct Test { path: Vec , bench: bool, ignore: bool, - should_panic: ShouldPanic + should_panic: ShouldPanic, + serial: bool } struct TestCtxt<'a> { @@ -133,7 +134,8 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { path: self.cx.path.clone(), bench: is_bench_fn(&self.cx, &i), ignore: is_ignored(&i), - should_panic: should_panic(&i, &self.cx) + should_panic: should_panic(&i, &self.cx), + serial: is_serial(&i) }; self.cx.testfns.push(test); self.tests.push(i.ident); @@ -383,6 +385,10 @@ fn is_ignored(i: &ast::Item) -> bool { i.attrs.iter().any(|attr| attr.check_name("ignore")) } +fn is_serial(i: &ast::Item) -> bool { + i.attrs.iter().any(|attr| attr.check_name("serial")) +} + fn should_panic(i: &ast::Item, cx: &TestCtxt) -> ShouldPanic { match i.attrs.iter().find(|attr| attr.check_name("should_panic")) { Some(attr) => { @@ -655,6 +661,7 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { let should_panic_path = |name| { ecx.path(span, vec![self_id, test_id, ecx.ident_of("ShouldPanic"), ecx.ident_of(name)]) }; + let serial_expr = ecx.expr_bool(span, test.serial); let fail_expr = match test.should_panic { ShouldPanic::No => ecx.expr_path(should_panic_path("No")), ShouldPanic::Yes(msg) => { @@ -675,7 +682,8 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { test_path("TestDesc"), vec![field("name", name_expr), field("ignore", ignore_expr), - field("should_panic", fail_expr)]); + field("should_panic", fail_expr), + field("serial", serial_expr)]); let mut visible_path = match cx.toplevel_reexport { diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index ef048ac8ca355..e9a6f931fc92e 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -212,6 +212,7 @@ pub struct TestDesc { pub name: TestName, pub ignore: bool, pub should_panic: ShouldPanic, + pub serial: bool, } #[derive(Clone)] @@ -423,7 +424,10 @@ Test Attributes: #[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."#, + tests. + #[serial] - When applied to a function which is already attributed as a + test, then the test runner will not run these tests in + parallel with any other tests."#, usage = getopts::usage(&message, &optgroups())); } @@ -944,12 +948,14 @@ fn should_sort_failures_before_printing_them() { name: StaticTestName("a"), ignore: false, should_panic: ShouldPanic::No, + serial: false, }; let test_b = TestDesc { name: StaticTestName("b"), ignore: false, should_panic: ShouldPanic::No, + serial: false, }; let mut st = ConsoleTestState { @@ -1063,7 +1069,9 @@ pub fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) None => get_concurrency(), }; - let mut remaining = filtered_tests; + let partitioned = filtered_tests.into_iter().partition(|t| t.desc.serial); + let serial: Vec<_> = partitioned.0; + let mut remaining: Vec<_> = partitioned.1; remaining.reverse(); let mut pending = 0; @@ -1133,6 +1141,34 @@ pub fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) pending -= 1; } + for test in serial { + callback(TeWait(test.desc.clone(), test.testfn.padding()))?; + let timeout = Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S); + running_tests.insert(test.desc.clone(), timeout); + run_test(opts, !opts.run_tests, test, tx.clone()); + + let mut res; + loop { + if let Some(timeout) = calc_timeout(&running_tests) { + res = rx.recv_timeout(timeout); + for test in get_timed_out_tests(&mut running_tests) { + callback(TeTimeout(test))?; + } + if res != Err(RecvTimeoutError::Timeout) { + break; + } + } else { + res = rx.recv().map_err(|_| RecvTimeoutError::Disconnected); + break; + } + } + + let (desc, result, stdout) = res.unwrap(); + running_tests.remove(&desc); + + callback(TeResult(desc, result, stdout))?; + } + if opts.bench_benchmarks { // All benchmarks run at the end, in serial. // (this includes metric fns) @@ -1690,8 +1726,11 @@ pub mod bench { mod tests { use test::{TrFailed, TrFailedMsg, TrIgnored, TrOk, filter_tests, parse_opts, TestDesc, TestDescAndFn, TestOpts, run_test, MetricMap, StaticTestName, DynTestName, - DynTestFn, ShouldPanic}; + DynTestFn, ShouldPanic, TestResult}; + use super::{TestEvent, run_tests}; use std::sync::mpsc::channel; + use std::sync::{Arc, RwLock}; + use std::thread::sleep; use bench; use Bencher; @@ -1705,6 +1744,7 @@ mod tests { name: StaticTestName("whatever"), ignore: true, should_panic: ShouldPanic::No, + serial: false, }, testfn: DynTestFn(Box::new(move |()| f())), }; @@ -1722,6 +1762,7 @@ mod tests { name: StaticTestName("whatever"), ignore: true, should_panic: ShouldPanic::No, + serial: false, }, testfn: DynTestFn(Box::new(move |()| f())), }; @@ -1741,6 +1782,7 @@ mod tests { name: StaticTestName("whatever"), ignore: false, should_panic: ShouldPanic::Yes, + serial: false, }, testfn: DynTestFn(Box::new(move |()| f())), }; @@ -1760,6 +1802,7 @@ mod tests { name: StaticTestName("whatever"), ignore: false, should_panic: ShouldPanic::YesWithMessage("error message"), + serial: false, }, testfn: DynTestFn(Box::new(move |()| f())), }; @@ -1781,6 +1824,7 @@ mod tests { name: StaticTestName("whatever"), ignore: false, should_panic: ShouldPanic::YesWithMessage(expected), + serial: false, }, testfn: DynTestFn(Box::new(move |()| f())), }; @@ -1798,6 +1842,7 @@ mod tests { name: StaticTestName("whatever"), ignore: false, should_panic: ShouldPanic::Yes, + serial: false, }, testfn: DynTestFn(Box::new(move |()| f())), }; @@ -1831,6 +1876,7 @@ mod tests { name: StaticTestName("1"), ignore: true, should_panic: ShouldPanic::No, + serial: false, }, testfn: DynTestFn(Box::new(move |()| {})), }, @@ -1839,6 +1885,7 @@ mod tests { name: StaticTestName("2"), ignore: false, should_panic: ShouldPanic::No, + serial: false, }, testfn: DynTestFn(Box::new(move |()| {})), }]; @@ -1862,6 +1909,7 @@ mod tests { name: StaticTestName(name), ignore: false, should_panic: ShouldPanic::No, + serial: false, }, testfn: DynTestFn(Box::new(move |()| {})) }) @@ -1943,6 +1991,7 @@ mod tests { name: DynTestName((*name).clone()), ignore: false, should_panic: ShouldPanic::No, + serial: false, }, testfn: DynTestFn(Box::new(move |()| testfn())), }; @@ -1967,6 +2016,95 @@ mod tests { } } + #[test] + pub fn stress_test_serial_tests() { + let mut opts = TestOpts::new(); + opts.run_tests = true; + opts.test_threads = Some(100); + + let limit = 100; + + let lock = Arc::new(RwLock::new(0)); + + let tests = (0..limit) + .map(|n| { + let lock = lock.clone(); + + TestDescAndFn { + desc: TestDesc { + name: DynTestName(format!("stress_{:?}", n)), + ignore: false, + should_panic: ShouldPanic::No, + serial: true, + }, + testfn: DynTestFn(Box::new(move |()| { + let mut c = lock.write().unwrap(); + *c += 1; + })) + } + }) + .collect::>(); + + run_tests(&opts, tests, |e| { + match e { + TestEvent::TeFilteredOut(n) if n > 0 => panic!("filtered out"), + TestEvent::TeTimeout(_) => panic!("timeout"), + TestEvent::TeResult(_, ref result, _) if result != &TestResult::TrOk => + panic!("result not okay"), + _ => Ok(()) + } + }).unwrap(); + + assert_eq!(*(*lock).read().unwrap(), limit); + } + + #[test] + pub fn run_concurrent_tests_concurrently() { + use std::time::Duration; + + let mut opts = TestOpts::new(); + opts.run_tests = true; + opts.test_threads = Some(2); + + let (tx, rx) = channel::<()>(); + + let tests = vec![ + TestDescAndFn { + desc: TestDesc { + name: DynTestName("first".to_string()), + ignore: false, + should_panic: ShouldPanic::No, + serial: false, + }, + testfn: DynTestFn(Box::new(move |()| { + rx.recv_timeout(Duration::from_secs(1)).unwrap(); + })) + }, + TestDescAndFn { + desc: TestDesc { + name: DynTestName("second".to_string()), + ignore: false, + should_panic: ShouldPanic::No, + serial: false, + }, + testfn: DynTestFn(Box::new(move |()| { + sleep(Duration::from_millis(100)); + tx.send(()).unwrap(); + })) + }, + ]; + + run_tests(&opts, tests, |e| { + match e { + TestEvent::TeFilteredOut(n) if n > 0 => panic!("filtered out"), + TestEvent::TeTimeout(_) => panic!("timeout"), + TestEvent::TeResult(_, ref result, _) if result != &TestResult::TrOk => + panic!("result not okay"), + _ => Ok(()) + } + }).unwrap(); + } + #[test] pub fn test_metricmap_compare() { let mut m1 = MetricMap::new(); diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 1bb0b765f9f1c..6986913b0a8e1 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -475,6 +475,7 @@ pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn name: make_test_name(config, testpaths), ignore: ignore, should_panic: should_panic, + serial: false, }, testfn: make_test_closure(config, testpaths), }