// running tests while providing a base that other test frameworks may
// build off of.
-// N.B., this is also specified in this crate's Cargo.toml, but libsyntax contains logic specific to
+// N.B., this is also specified in this crate's Cargo.toml, but librustc_ast contains logic specific to
// this crate, which relies on this attribute (rather than the value of `--crate-name` passed by
// cargo) to detect this crate.
-
-#![cfg_attr(feature = "asm_black_box", feature(asm))]
-#![cfg_attr(feature = "capture", feature(set_stdio))]
+#![cfg_attr(feature = "asm_black_box", feature(test))]
+#![cfg_attr(feature = "capture", feature(internal_output_capture))]
// Public reexports
-pub use self::ColorConfig::*;
-pub use self::types::*;
-pub use self::types::TestName::*;
-pub use self::options::{ColorConfig, Options, OutputFormat, RunIgnored, ShouldPanic};
-pub use self::bench::Bencher;
+pub use self::bench::{black_box, Bencher};
pub use self::console::run_tests_console;
+pub use self::options::{ColorConfig, Options, OutputFormat, RunIgnored, ShouldPanic};
+pub use self::types::TestName::*;
+pub use self::types::*;
+pub use self::ColorConfig::*;
pub use cli::TestOpts;
// Module to be used by rustc to compile tests in libtest
pub mod test {
pub use crate::{
+ assert_test_result,
bench::Bencher,
cli::{parse_opts, TestOpts},
+ filter_tests,
helpers::metrics::{Metric, MetricMap},
- options::{ShouldPanic, Options, RunIgnored, RunStrategy},
+ options::{Options, RunIgnored, RunStrategy, ShouldPanic},
+ run_test, test_main, test_main_static,
test_result::{TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk},
- time::{TestTimeOptions, TestExecTime},
+ time::{TestExecTime, TestTimeOptions},
types::{
- DynTestFn, DynTestName, StaticBenchFn, StaticTestFn, StaticTestName,
- TestDesc, TestDescAndFn, TestName, TestType,
+ DynTestFn, DynTestName, StaticBenchFn, StaticTestFn, StaticTestName, TestDesc,
+ TestDescAndFn, TestName, TestType,
},
- assert_test_result, filter_tests, run_test, test_main, test_main_static,
};
}
use std::{
- env,
- io,
+ env, io,
io::prelude::Write,
panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo},
- process,
- process::Command,
+ process::{self, Command},
sync::mpsc::{channel, Sender},
sync::{Arc, Mutex},
thread,
time::{Duration, Instant},
};
-pub mod stats;
pub mod bench;
-mod formatters;
mod cli;
mod console;
mod event;
+mod formatters;
mod helpers;
-mod time;
-mod types;
mod options;
+pub mod stats;
mod test_result;
+mod time;
+mod types;
#[cfg(test)]
mod tests;
-use test_result::*;
-use time::TestExecTime;
-use options::{RunStrategy, Concurrent};
use event::{CompletedTest, TestEvent};
-#[cfg(feature = "capture")]
-use helpers::sink::Sink;
use helpers::concurrency::get_concurrency;
use helpers::exit_code::get_exit_code;
+use options::{Concurrent, RunStrategy};
+use test_result::*;
+use time::TestExecTime;
// Process exit code to be used to indicate test failures.
const ERROR_EXIT_CODE: i32 = 101;
-const SECONDARY_TEST_INVOKER_VAR: &'static str = "__RUST_TEST_INVOKE";
+const SECONDARY_TEST_INVOKER_VAR: &str = "__RUST_TEST_INVOKE";
// The default console test runner. It accepts the command line
// arguments and a vector of test_descs.
// If we're being run in SpawnedSecondary mode, run the test here. run_test
// will then exit the process.
if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) {
+ env::remove_var(SECONDARY_TEST_INVOKER_VAR);
let test = tests
.iter()
.filter(|test| test.desc.name.as_slice() == name)
.map(make_owned_test)
.next()
- .expect("couldn't find a test with the provided name");
+ .unwrap_or_else(|| panic!("couldn't find a test with the provided name '{}'", name));
let TestDescAndFn { desc, testfn } = test;
let testfn = match testfn {
StaticTestFn(f) => f,
/// This will panic when fed any dynamic tests, because they cannot be cloned.
fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
match test.testfn {
- StaticTestFn(f) => TestDescAndFn {
- testfn: StaticTestFn(f),
- desc: test.desc.clone(),
- },
- StaticBenchFn(f) => TestDescAndFn {
- testfn: StaticBenchFn(f),
- desc: test.desc.clone(),
- },
+ StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: test.desc.clone() },
+ StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: test.desc.clone() },
_ => panic!("non-static tests passed to test::test_main_static"),
}
}
}
}
+/// Invoked when unit tests terminate. Should panic if the unit
+/// Tests is considered a failure. By default, invokes `report()`
+/// and checks for a `0` result.
pub fn assert_test_result<T: Termination>(result: T) {
let code = result.report();
assert_eq!(
pub fn run_tests<F>(
opts: &TestOpts,
tests: Vec<TestDescAndFn>,
- mut notify_about_test_event: F
+ mut notify_about_test_event: F,
) -> io::Result<()>
where
F: FnMut(TestEvent) -> io::Result<()>,
let event = TestEvent::TeFiltered(filtered_descs);
notify_about_test_event(event)?;
- let (filtered_tests, filtered_benchs): (Vec<_>, _) =
- filtered_tests.into_iter().partition(|e| match e.testfn {
- StaticTestFn(_) | DynTestFn(_) => true,
- _ => false,
- });
+ let (filtered_tests, filtered_benchs): (Vec<_>, _) = filtered_tests
+ .into_iter()
+ .partition(|e| matches!(e.testfn, StaticTestFn(_) | DynTestFn(_)));
let concurrency = opts.test_threads.unwrap_or_else(get_concurrency);
let now = Instant::now();
let timed_out = running_tests
.iter()
- .filter_map(|(desc, timeout)| {
- if &now >= timeout {
- Some(desc.clone())
- } else {
- None
- }
- })
+ .filter_map(|(desc, timeout)| if &now >= timeout { Some(desc.clone()) } else { None })
.collect();
for test in &timed_out {
running_tests.remove(test);
}
timed_out
- };
+ }
fn calc_timeout(running_tests: &TestMap) -> Option<Duration> {
running_tests.values().min().map(|next_timeout| {
let now = Instant::now();
- if *next_timeout >= now {
- *next_timeout - now
- } else {
- Duration::new(0, 0)
- }
+ if *next_timeout >= now { *next_timeout - now } else { Duration::new(0, 0) }
})
- };
+ }
if concurrency == 1 {
while !remaining.is_empty() {
};
// Remove tests that don't match the test filter
- if let Some(ref filter) = opts.filter {
- filtered.retain(|test| matches_filter(test, filter));
+ if !opts.filters.is_empty() {
+ filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
}
// Skip tests that match any of the skip filters
// maybe unignore tests
match opts.run_ignored {
RunIgnored::Yes => {
- filtered
- .iter_mut()
- .for_each(|test| test.desc.ignore = false);
+ filtered.iter_mut().for_each(|test| test.desc.ignore = false);
}
RunIgnored::Only => {
filtered.retain(|test| test.desc.ignore);
- filtered
- .iter_mut()
- .for_each(|test| test.desc.ignore = false);
+ filtered.iter_mut().for_each(|test| test.desc.ignore = false);
}
RunIgnored::No => {}
}
})),
f => f,
};
- TestDescAndFn {
- desc: x.desc,
- testfn,
- }
+ TestDescAndFn { desc: x.desc, testfn }
})
.collect()
}
// Emscripten can catch panics but other wasm targets cannot
let ignore_because_no_process_support = desc.should_panic != ShouldPanic::No
- && cfg!(target_arch = "wasm32") && !cfg!(target_os = "emscripten");
+ && cfg!(target_arch = "wasm32")
+ && !cfg!(target_os = "emscripten");
if force_ignore || desc.ignore || ignore_because_no_process_support {
let message = CompletedTest::new(desc, TrIgnored, None, Vec::new());
let concurrency = opts.concurrency;
let name = desc.name.clone();
- let runtest = move || {
- match opts.strategy {
- RunStrategy::InProcess =>
- run_test_in_process(
- desc,
- opts.nocapture,
- opts.time.is_some(),
- testfn,
- monitor_ch,
- opts.time
- ),
- RunStrategy::SpawnPrimary =>
- spawn_test_subprocess(desc, opts.time.is_some(), monitor_ch, opts.time),
- }
+ let runtest = move || match opts.strategy {
+ RunStrategy::InProcess => run_test_in_process(
+ desc,
+ opts.nocapture,
+ opts.time.is_some(),
+ testfn,
+ monitor_ch,
+ opts.time,
+ ),
+ RunStrategy::SpawnPrimary => spawn_test_subprocess(
+ desc,
+ opts.nocapture,
+ opts.time.is_some(),
+ monitor_ch,
+ opts.time,
+ ),
};
// If the platform is single-threaded we're just going to run
}
}
- let test_run_opts = TestRunOpts {
- strategy,
- nocapture: opts.nocapture,
- concurrency,
- time: opts.time_options
- };
+ let test_run_opts =
+ TestRunOpts { strategy, nocapture: opts.nocapture, concurrency, time: opts.time_options };
match testfn {
DynBenchFn(bencher) => {
}
StaticBenchFn(benchfn) => {
// Benchmarks aren't expected to panic, so we run them all in-process.
- crate::bench::benchmark(desc, monitor_ch, opts.nocapture, |harness| {
- (benchfn.clone())(harness)
- });
+ crate::bench::benchmark(desc, monitor_ch, opts.nocapture, benchfn);
}
DynTestFn(f) => {
match strategy {
/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
#[inline(never)]
fn __rust_begin_short_backtrace<F: FnOnce()>(f: F) {
- f()
+ f();
+
+ // prevent this frame from being tail-call optimised away
+ black_box(());
}
fn run_test_in_process(
// Buffer for capturing standard I/O
let data = Arc::new(Mutex::new(Vec::new()));
- let _oldio = if !nocapture {
- Some((
- #[cfg(feature = "capture")]
- io::set_print(Some(Sink::new_boxed(&data))),
- #[cfg(not(feature = "capture"))]
- (),
-
- #[cfg(feature = "capture")]
- io::set_panic(Some(Sink::new_boxed(&data))),
- #[cfg(not(feature = "capture"))]
- (),
- ))
- } else {
- None
- };
+ if !nocapture {
+ #[cfg(feature = "capture")]
+ io::set_output_capture(Some(data.clone()));
+ }
- let start = if report_time {
- Some(Instant::now())
- } else {
- None
- };
+ let start = if report_time { Some(Instant::now()) } else { None };
let result = catch_unwind(AssertUnwindSafe(testfn));
let exec_time = start.map(|start| {
let duration = start.elapsed();
});
#[cfg(feature = "capture")]
- {
- if let Some((printio, panicio)) = _oldio {
- io::set_print(printio);
- io::set_panic(panicio);
- }
- }
+ io::set_output_capture(None);
let test_result = match result {
Ok(()) => calc_result(&desc, Ok(()), &time_opts, &exec_time),
Err(e) => calc_result(&desc, Err(e.as_ref()), &time_opts, &exec_time),
};
- let stdout = data.lock().unwrap().to_vec();
- let message = CompletedTest::new(desc.clone(), test_result, exec_time, stdout);
+ let stdout = data.lock().unwrap_or_else(|e| e.into_inner()).to_vec();
+ let message = CompletedTest::new(desc, test_result, exec_time, stdout);
monitor_ch.send(message).unwrap();
}
fn spawn_test_subprocess(
desc: TestDesc,
+ nocapture: bool,
report_time: bool,
monitor_ch: Sender<CompletedTest>,
time_opts: Option<time::TestTimeOptions>,
let args = env::args().collect::<Vec<_>>();
let current_exe = &args[0];
- let start = if report_time {
- Some(Instant::now())
- } else {
- None
+ let mut command = Command::new(current_exe);
+ command.env(SECONDARY_TEST_INVOKER_VAR, desc.name.as_slice());
+ if nocapture {
+ command.stdout(process::Stdio::inherit());
+ command.stderr(process::Stdio::inherit());
+ }
+
+ let start = if report_time { Some(Instant::now()) } else { None };
+ let output = match command.output() {
+ Ok(out) => out,
+ Err(e) => {
+ let err = format!("Failed to spawn {} as child for test: {:?}", args[0], e);
+ return (TrFailed, err.into_bytes(), None);
+ }
};
- let output = match Command::new(current_exe)
- .env(SECONDARY_TEST_INVOKER_VAR, desc.name.as_slice())
- .output() {
- Ok(out) => out,
- Err(e) => {
- let err = format!("Failed to spawn {} as child for test: {:?}", args[0], e);
- return (TrFailed, err.into_bytes(), None);
- }
- };
let exec_time = start.map(|start| {
let duration = start.elapsed();
TestExecTime(duration)
(result, test_output, exec_time)
})();
- let message = CompletedTest::new(desc.clone(), result, exec_time, test_output);
+ let message = CompletedTest::new(desc, result, exec_time, test_output);
monitor_ch.send(message).unwrap();
}
-fn run_test_in_spawned_subprocess(
- desc: TestDesc,
- testfn: Box<dyn FnOnce() + Send>,
-) -> ! {
+fn run_test_in_spawned_subprocess(desc: TestDesc, testfn: Box<dyn FnOnce() + Send>) -> ! {
let builtin_panic_hook = panic::take_hook();
let record_result = Arc::new(move |panic_info: Option<&'_ PanicInfo<'_>>| {
let test_result = match panic_info {