Skip to content

Add serial tests #42626

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
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