Skip to content
1 change: 1 addition & 0 deletions src/librustdoc/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
14 changes: 11 additions & 3 deletions src/libsyntax/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ struct Test {
path: Vec<Ident> ,
bench: bool,
ignore: bool,
should_panic: ShouldPanic
should_panic: ShouldPanic,
serial: bool
}

struct TestCtxt<'a> {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -655,6 +661,7 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> {
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) => {
Expand All @@ -675,7 +682,8 @@ fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> {
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 {
Expand Down
144 changes: 141 additions & 3 deletions src/libtest/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ pub struct TestDesc {
pub name: TestName,
pub ignore: bool,
pub should_panic: ShouldPanic,
pub serial: bool,
}

#[derive(Clone)]
Expand Down Expand Up @@ -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()));
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1063,7 +1069,9 @@ pub fn run_tests<F>(opts: &TestOpts, tests: Vec<TestDescAndFn>, 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;

Expand Down Expand Up @@ -1133,6 +1141,34 @@ pub fn run_tests<F>(opts: &TestOpts, tests: Vec<TestDescAndFn>, 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)
Expand Down Expand Up @@ -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;

Expand All @@ -1705,6 +1744,7 @@ mod tests {
name: StaticTestName("whatever"),
ignore: true,
should_panic: ShouldPanic::No,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| f())),
};
Expand All @@ -1722,6 +1762,7 @@ mod tests {
name: StaticTestName("whatever"),
ignore: true,
should_panic: ShouldPanic::No,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| f())),
};
Expand All @@ -1741,6 +1782,7 @@ mod tests {
name: StaticTestName("whatever"),
ignore: false,
should_panic: ShouldPanic::Yes,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| f())),
};
Expand All @@ -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())),
};
Expand All @@ -1781,6 +1824,7 @@ mod tests {
name: StaticTestName("whatever"),
ignore: false,
should_panic: ShouldPanic::YesWithMessage(expected),
serial: false,
},
testfn: DynTestFn(Box::new(move |()| f())),
};
Expand All @@ -1798,6 +1842,7 @@ mod tests {
name: StaticTestName("whatever"),
ignore: false,
should_panic: ShouldPanic::Yes,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| f())),
};
Expand Down Expand Up @@ -1831,6 +1876,7 @@ mod tests {
name: StaticTestName("1"),
ignore: true,
should_panic: ShouldPanic::No,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| {})),
},
Expand All @@ -1839,6 +1885,7 @@ mod tests {
name: StaticTestName("2"),
ignore: false,
should_panic: ShouldPanic::No,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| {})),
}];
Expand All @@ -1862,6 +1909,7 @@ mod tests {
name: StaticTestName(name),
ignore: false,
should_panic: ShouldPanic::No,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| {}))
})
Expand Down Expand Up @@ -1943,6 +1991,7 @@ mod tests {
name: DynTestName((*name).clone()),
ignore: false,
should_panic: ShouldPanic::No,
serial: false,
},
testfn: DynTestFn(Box::new(move |()| testfn())),
};
Expand All @@ -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::<Vec<_>>();

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();
Expand Down
1 change: 1 addition & 0 deletions src/tools/compiletest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand Down