From d1a26764628efbc7580936c02328256a5f134c9b Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Sat, 22 Dec 2018 21:17:26 +0100 Subject: [PATCH] split libtest types and formatting into a libtest-utils crate --- src/libtest/Cargo.toml | 6 +- src/libtest/formatters/mod.rs | 32 - src/libtest/lib.rs | 977 ++++++------------ src/libtest/libtest-utils/Cargo.toml | 9 + src/libtest/libtest-utils/src/bench.rs | 115 +++ .../{ => libtest-utils/src/bench}/stats.rs | 33 +- src/libtest/libtest-utils/src/format.rs | 176 ++++ .../src/format}/json.rs | 58 +- .../src/format}/pretty.rs | 72 +- .../src/format}/terse.rs | 71 +- .../libtest-utils/src/get_concurrency.rs | 145 +++ src/libtest/libtest-utils/src/isatty.rs | 33 + src/libtest/libtest-utils/src/lib.rs | 26 + src/libtest/libtest-utils/src/test.rs | 85 ++ src/libtest/libtest-utils/src/types.rs | 4 + src/llvm | 2 +- src/stdsimd | 2 +- src/tools/cargo | 2 +- src/tools/clang | 2 +- src/tools/clippy | 2 +- src/tools/lld | 2 +- src/tools/lldb | 2 +- 22 files changed, 1044 insertions(+), 812 deletions(-) delete mode 100644 src/libtest/formatters/mod.rs create mode 100644 src/libtest/libtest-utils/Cargo.toml create mode 100644 src/libtest/libtest-utils/src/bench.rs rename src/libtest/{ => libtest-utils/src/bench}/stats.rs (97%) create mode 100644 src/libtest/libtest-utils/src/format.rs rename src/libtest/{formatters => libtest-utils/src/format}/json.rs (80%) rename src/libtest/{formatters => libtest-utils/src/format}/pretty.rs (80%) rename src/libtest/{formatters => libtest-utils/src/format}/terse.rs (80%) create mode 100644 src/libtest/libtest-utils/src/get_concurrency.rs create mode 100644 src/libtest/libtest-utils/src/isatty.rs create mode 100644 src/libtest/libtest-utils/src/lib.rs create mode 100644 src/libtest/libtest-utils/src/test.rs create mode 100644 src/libtest/libtest-utils/src/types.rs diff --git a/src/libtest/Cargo.toml b/src/libtest/Cargo.toml index aade10ed6c324..3ae42a259a238 100644 --- a/src/libtest/Cargo.toml +++ b/src/libtest/Cargo.toml @@ -10,7 +10,5 @@ crate-type = ["dylib", "rlib"] [dependencies] getopts = "0.2" -term = { path = "../libterm" } - -# not actually used but needed to always have proc_macro in the sysroot -proc_macro = { path = "../libproc_macro" } +term = "0.5.1" +libtest-utils = { path = "libtest-utils" } \ No newline at end of file diff --git a/src/libtest/formatters/mod.rs b/src/libtest/formatters/mod.rs deleted file mode 100644 index 24c7929076c1d..0000000000000 --- a/src/libtest/formatters/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2012-2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::*; - -mod pretty; -mod json; -mod terse; - -pub(crate) use self::pretty::PrettyFormatter; -pub(crate) use self::json::JsonFormatter; -pub(crate) use self::terse::TerseFormatter; - -pub(crate) trait OutputFormatter { - fn write_run_start(&mut self, test_count: usize) -> io::Result<()>; - fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()>; - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()>; - fn write_result( - &mut self, - desc: &TestDesc, - result: &TestResult, - stdout: &[u8], - ) -> io::Result<()>; - fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result; -} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 1c8734913c723..4445eaefab422 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -29,9 +29,12 @@ #![crate_name = "test"] #![unstable(feature = "test", issue = "27812")] -#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", - html_favicon_url = "https://doc.rust-lang.org/favicon.ico", - html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))] +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://doc.rust-lang.org/favicon.ico", + html_root_url = "https://doc.rust-lang.org/nightly/", + test(attr(deny(warnings))) +)] #![feature(asm)] #![feature(fnbox)] #![cfg_attr(any(unix, target_os = "cloudabi"), feature(libc))] @@ -45,6 +48,7 @@ extern crate getopts; #[cfg(any(unix, target_os = "cloudabi"))] extern crate libc; +extern crate libtest_utils; extern crate term; // FIXME(#54291): rustc and/or LLVM don't yet support building with panic-unwind @@ -56,115 +60,51 @@ extern crate term; #[cfg(not(all(windows, target_arch = "aarch64")))] extern crate panic_unwind; -pub use self::TestFn::*; -pub use self::ColorConfig::*; -pub use self::TestResult::*; -pub use self::TestName::*; use self::TestEvent::*; -use self::NamePadding::*; -use self::OutputLocation::*; +pub use self::TestFn::*; -use std::panic::{catch_unwind, AssertUnwindSafe}; use std::any::Any; use std::boxed::FnBox; use std::cmp; -use std::collections::BTreeMap; use std::env; use std::fmt; -use std::fs::File; -use std::io::prelude::*; use std::io; +use std::io::prelude::*; +use std::panic::{catch_unwind, AssertUnwindSafe}; use std::path::PathBuf; +use std::process; use std::process::Termination; use std::sync::mpsc::{channel, Sender}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use std::borrow::Cow; -use std::process; -const TEST_WARN_TIMEOUT_S: u64 = 60; -const QUIET_MODE_MAX_COLUMN: usize = 100; // insert a '\n' after 100 tests in quiet mode +pub use libtest_utils::{ + bench::{stats, BenchSamples, Metric, MetricMap}, + format::{ + ColorConfig, ConsoleTestState, JsonFormatter, OutputFormat, OutputFormatter, + OutputLocation, Padding, PrettyFormatter, TerseFormatter, + }, + get_concurrency, stdout_isatty, test as test_, bench as bench_, ShouldPanic, TEST_WARN_TIMEOUT_S, +}; // to be used by rustc to compile tests in libtest pub mod test { - pub use {assert_test_result, filter_tests, parse_opts, run_test, test_main, test_main_static, - Bencher, DynTestFn, DynTestName, Metric, MetricMap, Options, RunIgnored, ShouldPanic, - StaticBenchFn, StaticTestFn, StaticTestName, TestDesc, TestDescAndFn, TestName, - TestOpts, TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk}; + pub use { + assert_test_result, filter_tests, parse_opts, run_test, test_::Desc as TestDesc, + test_::Name as TestName, test_::Name::Dyn, test_::Name::Static, + test_::Result as TestResult, test_::Result::Failed, test_::Result::FailedMsg, + test_::Result::Ignored, test_::Result::Ok, test_main, test_main_static, Bencher, DescAndFn, + DynTestFn, Metric, MetricMap, Options, RunIgnored, ShouldPanic, StaticBenchFn, + StaticTestFn, TestOpts, + }; } -pub mod stats; -mod formatters; - -use formatters::{JsonFormatter, OutputFormatter, PrettyFormatter, TerseFormatter}; - /// Whether to execute tests concurrently or not #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Concurrent { Yes, No } - -// The name of a test. By convention this follows the rules for rust -// paths; i.e., it should be a series of identifiers separated by double -// colons. This way if some test runner wants to arrange the tests -// hierarchically it may. - -#[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub enum TestName { - StaticTestName(&'static str), - DynTestName(String), - AlignedTestName(Cow<'static, str>, NamePadding), -} -impl TestName { - fn as_slice(&self) -> &str { - match *self { - StaticTestName(s) => s, - DynTestName(ref s) => s, - AlignedTestName(ref s, _) => &*s, - } - } - - fn padding(&self) -> NamePadding { - match self { - &AlignedTestName(_, p) => p, - _ => PadNone, - } - } - - fn with_padding(&self, padding: NamePadding) -> TestName { - let name = match self { - &TestName::StaticTestName(name) => Cow::Borrowed(name), - &TestName::DynTestName(ref name) => Cow::Owned(name.clone()), - &TestName::AlignedTestName(ref name, _) => name.clone(), - }; - - TestName::AlignedTestName(name, padding) - } -} -impl fmt::Display for TestName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self.as_slice(), f) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub enum NamePadding { - PadNone, - PadOnRight, -} - -impl TestDesc { - fn padded_name(&self, column_count: usize, align: NamePadding) -> String { - let mut name = String::from(self.name.as_slice()); - let fill = column_count.saturating_sub(name.len()); - let pad = " ".repeat(fill); - match align { - PadNone => name, - PadOnRight => { - name.push_str(&pad); - name - } - } - } +pub enum Concurrent { + Yes, + No, } /// Represents a benchmark function. @@ -184,12 +124,12 @@ pub enum TestFn { } impl TestFn { - fn padding(&self) -> NamePadding { + fn padding(&self) -> Padding { match *self { - StaticTestFn(..) => PadNone, - StaticBenchFn(..) => PadOnRight, - DynTestFn(..) => PadNone, - DynBenchFn(..) => PadOnRight, + StaticTestFn(..) => Padding::None, + StaticBenchFn(..) => Padding::OnRight, + DynTestFn(..) => Padding::None, + DynBenchFn(..) => Padding::OnRight, } } } @@ -223,41 +163,12 @@ pub enum BenchMode { Single, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum ShouldPanic { - No, - Yes, - YesWithMessage(&'static str), -} - -// The definition of a single test. A test runner will run a list of -// these. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct TestDesc { - pub name: TestName, - pub ignore: bool, - pub should_panic: ShouldPanic, - pub allow_fail: bool, -} - #[derive(Debug)] -pub struct TestDescAndFn { - pub desc: TestDesc, +pub struct DescAndFn { + pub desc: test_::Desc, pub testfn: TestFn, } -#[derive(Clone, PartialEq, Debug, Copy)] -pub struct Metric { - value: f64, - noise: f64, -} - -impl Metric { - pub fn new(value: f64, noise: f64) -> Metric { - Metric { value, noise } - } -} - /// In case we want to add other options as well, just add them in this struct. #[derive(Copy, Clone, Debug)] pub struct Options { @@ -279,7 +190,7 @@ impl Options { // The default console test runner. It accepts the command line // arguments and a vector of test_descs. -pub fn test_main(args: &[String], tests: Vec, options: Options) { +pub fn test_main(args: &[String], tests: Vec, options: Options) { let mut opts = match parse_opts(args) { Some(Ok(o)) => o, Some(Err(msg)) => { @@ -311,19 +222,19 @@ pub fn test_main(args: &[String], tests: Vec, options: Options) { // This will panic (intentionally) when fed any dynamic tests, because // it is copying the static values out into a dynamic vector and cannot // copy dynamic values. It is doing this because from this point on -// a Vec is used in order to effect ownership-transfer +// a Vec is used in order to effect ownership-transfer // semantics into parallel test runners, which in turn requires a Vec<> // rather than a &[]. -pub fn test_main_static(tests: &[&TestDescAndFn]) { +pub fn test_main_static(tests: &[&DescAndFn]) { let args = env::args().collect::>(); let owned_tests = tests .iter() .map(|t| match t.testfn { - StaticTestFn(f) => TestDescAndFn { + StaticTestFn(f) => DescAndFn { testfn: StaticTestFn(f), desc: t.desc.clone(), }, - StaticBenchFn(f) => TestDescAndFn { + StaticBenchFn(f) => DescAndFn { testfn: StaticBenchFn(f), desc: t.desc.clone(), }, @@ -339,28 +250,13 @@ pub fn test_main_static(tests: &[&TestDescAndFn]) { pub fn assert_test_result(result: T) { let code = result.report(); assert_eq!( - code, - 0, + code, 0, "the test returned a termination value with a non-zero status code ({}) \ which indicates a failure", code ); } -#[derive(Copy, Clone, Debug)] -pub enum ColorConfig { - AutoColor, - AlwaysColor, - NeverColor, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum OutputFormat { - Pretty, - Terse, - Json, -} - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RunIgnored { Yes, @@ -397,7 +293,7 @@ impl TestOpts { bench_benchmarks: false, logfile: None, nocapture: false, - color: AutoColor, + color: ColorConfig::AutoColor, format: OutputFormat::Pretty, test_threads: None, skip: vec![], @@ -568,14 +464,16 @@ pub fn parse_opts(args: &[String]) -> Option { let include_ignored = matches.opt_present("include-ignored"); if !allow_unstable && include_ignored { return Some(Err( - "The \"include-ignored\" flag is only accepted on the nightly compiler".into() + "The \"include-ignored\" flag is only accepted on the nightly compiler".into(), )); } let run_ignored = match (include_ignored, matches.opt_present("ignored")) { - (true, true) => return Some(Err( - "the options --include-ignored and --ignored are mutually exclusive".into() - )), + (true, true) => { + return Some(Err( + "the options --include-ignored and --ignored are mutually exclusive".into(), + )); + } (true, false) => RunIgnored::Yes, (false, true) => RunIgnored::Only, (false, false) => RunIgnored::No, @@ -607,23 +505,23 @@ pub fn parse_opts(args: &[String]) -> Option { "argument for --test-threads must be a number > 0 \ (error: {})", e - ))) + ))); } }, None => None, }; let color = match matches.opt_str("color").as_ref().map(|s| &**s) { - Some("auto") | None => AutoColor, - Some("always") => AlwaysColor, - Some("never") => NeverColor, + Some("auto") | None => ColorConfig::AutoColor, + Some("always") => ColorConfig::AlwaysColor, + Some("never") => ColorConfig::NeverColor, Some(v) => { return Some(Err(format!( "argument for --color must be auto, always, or never (was \ {})", v - ))) + ))); } }; @@ -645,7 +543,7 @@ pub fn parse_opts(args: &[String]) -> Option { "argument for --format must be pretty, terse, or json (was \ {})", v - ))) + ))); } }; @@ -668,166 +566,19 @@ pub fn parse_opts(args: &[String]) -> Option { Some(Ok(test_opts)) } -#[derive(Clone, PartialEq)] -pub struct BenchSamples { - ns_iter_summ: stats::Summary, - mb_s: usize, -} - -#[derive(Clone, PartialEq)] -pub enum TestResult { - TrOk, - TrFailed, - TrFailedMsg(String), - TrIgnored, - TrAllowedFail, - TrBench(BenchSamples), -} - -unsafe impl Send for TestResult {} - -enum OutputLocation { - Pretty(Box), - Raw(T), -} - -impl Write for OutputLocation { - fn write(&mut self, buf: &[u8]) -> io::Result { - match *self { - Pretty(ref mut term) => term.write(buf), - Raw(ref mut stdout) => stdout.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match *self { - Pretty(ref mut term) => term.flush(), - Raw(ref mut stdout) => stdout.flush(), - } - } -} - -struct ConsoleTestState { - log_out: Option, - total: usize, - passed: usize, - failed: usize, - ignored: usize, - allowed_fail: usize, - filtered_out: usize, - measured: usize, - metrics: MetricMap, - failures: Vec<(TestDesc, Vec)>, - not_failures: Vec<(TestDesc, Vec)>, - options: Options, -} - -impl ConsoleTestState { - pub fn new(opts: &TestOpts) -> io::Result { - let log_out = match opts.logfile { - Some(ref path) => Some(File::create(path)?), - None => None, - }; - - Ok(ConsoleTestState { - log_out, - total: 0, - passed: 0, - failed: 0, - ignored: 0, - allowed_fail: 0, - filtered_out: 0, - measured: 0, - metrics: MetricMap::new(), - failures: Vec::new(), - not_failures: Vec::new(), - options: opts.options, - }) - } - - pub fn write_log>(&mut self, msg: S) -> io::Result<()> { - let msg = msg.as_ref(); - match self.log_out { - None => Ok(()), - Some(ref mut o) => o.write_all(msg.as_bytes()), - } - } - - pub fn write_log_result(&mut self, test: &TestDesc, result: &TestResult) -> io::Result<()> { - self.write_log(format!( - "{} {}\n", - match *result { - TrOk => "ok".to_owned(), - TrFailed => "failed".to_owned(), - TrFailedMsg(ref msg) => format!("failed: {}", msg), - TrIgnored => "ignored".to_owned(), - TrAllowedFail => "failed (allowed)".to_owned(), - TrBench(ref bs) => fmt_bench_samples(bs), - }, - test.name - )) - } - - fn current_test_count(&self) -> usize { - self.passed + self.failed + self.ignored + self.measured + self.allowed_fail - } -} - -// Format a number with thousands separators -fn fmt_thousands_sep(mut n: usize, sep: char) -> String { - use std::fmt::Write; - let mut output = String::new(); - let mut trailing = false; - for &pow in &[9, 6, 3, 0] { - let base = 10_usize.pow(pow); - if pow == 0 || trailing || n / base != 0 { - if !trailing { - output.write_fmt(format_args!("{}", n / base)).unwrap(); - } else { - output.write_fmt(format_args!("{:03}", n / base)).unwrap(); - } - if pow != 0 { - output.push(sep); - } - trailing = true; - } - n %= base; - } - - output -} - -pub fn fmt_bench_samples(bs: &BenchSamples) -> String { - use std::fmt::Write; - let mut output = String::new(); - - let median = bs.ns_iter_summ.median as usize; - let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize; - - output - .write_fmt(format_args!( - "{:>11} ns/iter (+/- {})", - fmt_thousands_sep(median, ','), - fmt_thousands_sep(deviation, ',') - )) - .unwrap(); - if bs.mb_s != 0 { - output - .write_fmt(format_args!(" = {} MB/s", bs.mb_s)) - .unwrap(); - } - output -} - // List the tests to console, and optionally to logfile. Filters are honored. -pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Result<()> { +pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Result<()> { + use libtest_utils::format::Options; let mut output = match term::stdout() { - None => Raw(io::stdout()), - Some(t) => Pretty(t), + None => OutputLocation::Raw(io::stdout()), + Some(t) => OutputLocation::Pretty(t), }; let quiet = opts.format == OutputFormat::Terse; - let mut st = ConsoleTestState::new(opts)?; + let mut st = ConsoleTestState::new(Options { + logfile: opts.logfile.clone(), + display_output: opts.options.display_output + })?; let mut ntest = 0; let mut nbench = 0; @@ -835,8 +586,8 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Res for test in filter_tests(&opts, tests) { use TestFn::*; - let TestDescAndFn { - desc: TestDesc { name, .. }, + let DescAndFn { + desc: test_::Desc { name, .. }, testfn, } = test; @@ -879,7 +630,7 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Res } // A simple console test runner -pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Result { +pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Result { fn callback( event: &TestEvent, st: &mut ConsoleTestState, @@ -893,29 +644,21 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu TeFilteredOut(filtered_out) => Ok(st.filtered_out = filtered_out), TeWait(ref test) => out.write_test_start(test), TeTimeout(ref test) => out.write_timeout(test), - TeResult(test, result, stdout) => { - st.write_log_result(&test, &result)?; - out.write_result(&test, &result, &*stdout)?; + TeTestResult(test, result, stdout) => { + st.write_log_test_result(&test, &result)?; + out.write_test_result(&test, &result, &*stdout)?; match result { - TrOk => { + test_::Result::Ok => { st.passed += 1; st.not_failures.push((test, stdout)); } - TrIgnored => st.ignored += 1, - TrAllowedFail => st.allowed_fail += 1, - TrBench(bs) => { - 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 - } - TrFailed => { + test_::Result::Ignored => st.ignored += 1, + test_::Result::AllowedFail => st.allowed_fail += 1, + test_::Result::Failed => { st.failed += 1; st.failures.push((test, stdout)); } - TrFailedMsg(msg) => { + test_::Result::FailedMsg(msg) => { st.failed += 1; let mut stdout = stdout; stdout.extend_from_slice(format!("note: {}", msg).as_bytes()); @@ -924,12 +667,31 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu } Ok(()) } + TeBenchResult(bench, result, stdout) => { + st.write_log_bench_result(&bench, &result)?; + out.write_bench_result(&bench, &result, &*stdout)?; + match result { + bench_::Result::Ok(bs) => { + st.metrics.insert_metric( + bench.name.as_slice(), + bs.ns_iter_summ.median, + bs.ns_iter_summ.max - bs.ns_iter_summ.min, + ); + st.measured += 1 + } + bench_::Result::Failed => { + st.failed += 1; + st.failures.push((bench, stdout)); + } + } + Ok(()) + } } } let output = match term::stdout() { - None => Raw(io::stdout()), - Some(t) => Pretty(t), + None => OutputLocation::Raw(io::stdout()), + Some(t) => OutputLocation::Pretty(t), }; let max_name_len = tests @@ -955,11 +717,15 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu )), OutputFormat::Json => Box::new(JsonFormatter::new(output)), }; - let mut st = ConsoleTestState::new(opts)?; - fn len_if_padded(t: &TestDescAndFn) -> usize { + use libtest_utils::format::Options; + let mut st = ConsoleTestState::new(Options { + logfile: opts.logfile.clone(), + display_output: opts.options.display_output + })?; + fn len_if_padded(t: &DescAndFn) -> usize { match t.testfn.padding() { - PadNone => 0, - PadOnRight => t.desc.name.as_slice().len(), + Padding::None => 0, + Padding::OnRight => t.desc.name.as_slice().len(), } } @@ -970,98 +736,29 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu return out.write_run_finish(&st); } -#[test] -fn should_sort_failures_before_printing_them() { - let test_a = TestDesc { - name: StaticTestName("a"), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }; - - let test_b = TestDesc { - name: StaticTestName("b"), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }; - - let mut out = PrettyFormatter::new(Raw(Vec::new()), false, 10, false); - - let st = ConsoleTestState { - log_out: None, - total: 0, - passed: 0, - failed: 0, - ignored: 0, - allowed_fail: 0, - filtered_out: 0, - measured: 0, - metrics: MetricMap::new(), - failures: vec![(test_b, Vec::new()), (test_a, Vec::new())], - options: Options::new(), - not_failures: Vec::new(), - }; - - out.write_failures(&st).unwrap(); - let s = match out.output_location() { - &Raw(ref m) => String::from_utf8_lossy(&m[..]), - &Pretty(_) => unreachable!(), - }; - - let apos = s.find("a").unwrap(); - let bpos = s.find("b").unwrap(); - assert!(apos < bpos); -} - fn use_color(opts: &TestOpts) -> bool { match opts.color { - AutoColor => !opts.nocapture && stdout_isatty(), - AlwaysColor => true, - NeverColor => false, - } -} - -#[cfg(any(target_os = "cloudabi", - target_os = "redox", - all(target_arch = "wasm32", not(target_os = "emscripten")), - target_env = "sgx"))] -fn stdout_isatty() -> bool { - // FIXME: Implement isatty on Redox and SGX - false -} -#[cfg(unix)] -fn stdout_isatty() -> bool { - unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 } -} -#[cfg(windows)] -fn stdout_isatty() -> bool { - type DWORD = u32; - type BOOL = i32; - type HANDLE = *mut u8; - type LPDWORD = *mut u32; - const STD_OUTPUT_HANDLE: DWORD = -11i32 as DWORD; - extern "system" { - fn GetStdHandle(which: DWORD) -> HANDLE; - fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL; - } - unsafe { - let handle = GetStdHandle(STD_OUTPUT_HANDLE); - let mut out = 0; - GetConsoleMode(handle, &mut out) != 0 + ColorConfig::AutoColor => !opts.nocapture && stdout_isatty(), + ColorConfig::AlwaysColor => true, + ColorConfig::NeverColor => false, } } #[derive(Clone)] pub enum TestEvent { - TeFiltered(Vec), - TeWait(TestDesc), - TeResult(TestDesc, TestResult, Vec), - TeTimeout(TestDesc), + TeFiltered(Vec), + TeWait(test_::Desc), + TeTestResult(test_::Desc, test_::Result, Vec), + TeBenchResult(bench_::Desc, bench_::Result, Vec), + TeTimeout(test_::Desc), TeFilteredOut(usize), } -pub type MonitorMsg = (TestDesc, TestResult, Vec); +#[derive(Clone, PartialEq)] +pub enum MonitorMsg { + Test(test_::Desc, test_::Result, Vec), + Bench(bench_::Desc, bench_::Result, Vec) +} struct Sink(Arc>>); impl Write for Sink { @@ -1073,7 +770,7 @@ impl Write for Sink { } } -pub fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) -> io::Result<()> +pub fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) -> io::Result<()> where F: FnMut(TestEvent) -> io::Result<()>, { @@ -1082,7 +779,7 @@ where use std::sync::mpsc::RecvTimeoutError; // Use a deterministic hasher type TestMap = - HashMap>; + HashMap>; let tests_len = tests.len(); @@ -1123,7 +820,7 @@ where let mut running_tests: TestMap = HashMap::default(); - fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec { + fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec { let now = Instant::now(); let timed_out = running_tests .iter() @@ -1157,8 +854,10 @@ where let test = remaining.pop().unwrap(); callback(TeWait(test.desc.clone()))?; run_test(opts, !opts.run_tests, test, tx.clone(), Concurrent::No); - let (test, result, stdout) = rx.recv().unwrap(); - callback(TeResult(test, result, stdout))?; + match rx.recv().unwrap() { + MonitorMsg::Test(test, result, stdout) => callback(TeTestResult(test, result, stdout))?, + MonitorMsg::Bench(bench, result, stdout) => unreachable!(), + }; } } else { while pending > 0 || !remaining.is_empty() { @@ -1187,11 +886,14 @@ where } } - let (desc, result, stdout) = res.unwrap(); - running_tests.remove(&desc); - - callback(TeResult(desc, result, stdout))?; - pending -= 1; + match rx.recv().unwrap() { + MonitorMsg::Test(test, result, stdout) => { + running_tests.remove(&test); + callback(TeTestResult(test, result, stdout))?; + pending -= 1; + } + MonitorMsg::Bench(bench, result, stdout) => unreachable!(), + }; } } @@ -1200,144 +902,20 @@ where for b in filtered_benchs { callback(TeWait(b.desc.clone()))?; run_test(opts, false, b, tx.clone(), Concurrent::No); - let (test, result, stdout) = rx.recv().unwrap(); - callback(TeResult(test, result, stdout))?; - } - } - Ok(()) -} - -#[allow(deprecated)] -fn get_concurrency() -> usize { - return match env::var("RUST_TEST_THREADS") { - Ok(s) => { - let opt_n: Option = s.parse().ok(); - match opt_n { - Some(n) if n > 0 => n, - _ => panic!( - "RUST_TEST_THREADS is `{}`, should be a positive integer.", - s - ), - } - } - Err(..) => num_cpus(), - }; - - #[cfg(windows)] - #[allow(nonstandard_style)] - fn num_cpus() -> usize { - #[repr(C)] - struct SYSTEM_INFO { - wProcessorArchitecture: u16, - wReserved: u16, - dwPageSize: u32, - lpMinimumApplicationAddress: *mut u8, - lpMaximumApplicationAddress: *mut u8, - dwActiveProcessorMask: *mut u8, - dwNumberOfProcessors: u32, - dwProcessorType: u32, - dwAllocationGranularity: u32, - wProcessorLevel: u16, - wProcessorRevision: u16, - } - extern "system" { - fn GetSystemInfo(info: *mut SYSTEM_INFO) -> i32; - } - unsafe { - let mut sysinfo = std::mem::zeroed(); - GetSystemInfo(&mut sysinfo); - sysinfo.dwNumberOfProcessors as usize - } - } - - #[cfg(target_os = "redox")] - fn num_cpus() -> usize { - // FIXME: Implement num_cpus on Redox - 1 - } - - #[cfg(any(all(target_arch = "wasm32", not(target_os = "emscripten")), target_env = "sgx"))] - fn num_cpus() -> usize { - 1 - } - - #[cfg(any(target_os = "android", target_os = "cloudabi", target_os = "emscripten", - target_os = "fuchsia", target_os = "ios", target_os = "linux", - target_os = "macos", target_os = "solaris"))] - fn num_cpus() -> usize { - unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as usize } - } - - #[cfg(any(target_os = "freebsd", target_os = "dragonfly", target_os = "bitrig", - target_os = "netbsd"))] - fn num_cpus() -> usize { - use std::ptr; - - let mut cpus: libc::c_uint = 0; - let mut cpus_size = std::mem::size_of_val(&cpus); - - unsafe { - cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint; - } - if cpus < 1 { - let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; - unsafe { - libc::sysctl( - mib.as_mut_ptr(), - 2, - &mut cpus as *mut _ as *mut _, - &mut cpus_size as *mut _ as *mut _, - ptr::null_mut(), - 0, - ); - } - if cpus < 1 { - cpus = 1; + match rx.recv().unwrap() { + MonitorMsg::Bench(bench, result, stdout) => { + callback(TeBenchResult(bench, result, stdout))?; + } + _ => unreachable!(), } } - cpus as usize - } - - #[cfg(target_os = "openbsd")] - fn num_cpus() -> usize { - use std::ptr; - - let mut cpus: libc::c_uint = 0; - let mut cpus_size = std::mem::size_of_val(&cpus); - let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; - - unsafe { - libc::sysctl( - mib.as_mut_ptr(), - 2, - &mut cpus as *mut _ as *mut _, - &mut cpus_size as *mut _ as *mut _, - ptr::null_mut(), - 0, - ); - } - if cpus < 1 { - cpus = 1; - } - cpus as usize - } - - #[cfg(target_os = "haiku")] - fn num_cpus() -> usize { - // FIXME: implement - 1 - } - - #[cfg(target_os = "l4re")] - fn num_cpus() -> usize { - // FIXME: implement - 1 } + Ok(()) } -pub fn filter_tests(opts: &TestOpts, tests: Vec) -> Vec { +pub fn filter_tests(opts: &TestOpts, tests: Vec) -> Vec { let mut filtered = tests; - let matches_filter = |test: &TestDescAndFn, filter: &str| { + let matches_filter = |test: &DescAndFn, filter: &str| { let test_name = test.desc.name.as_slice(); match opts.filter_exact { @@ -1352,18 +930,20 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec) -> Vec { - 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 => {} } @@ -1374,7 +954,7 @@ pub fn filter_tests(opts: &TestOpts, tests: Vec) -> Vec) -> Vec { +pub fn convert_benchmarks_to_tests(tests: Vec) -> Vec { // convert benchmarks to tests, if we're not benchmarking them tests .into_iter() @@ -1388,7 +968,7 @@ pub fn convert_benchmarks_to_tests(tests: Vec) -> Vec f, }; - TestDescAndFn { + DescAndFn { desc: x.desc, testfn, } @@ -1399,22 +979,25 @@ pub fn convert_benchmarks_to_tests(tests: Vec) -> Vec, concurrency: Concurrent, ) { - let TestDescAndFn { desc, testfn } = test; + let DescAndFn { desc, testfn } = test; - let ignore_because_panic_abort = cfg!(target_arch = "wasm32") && !cfg!(target_os = "emscripten") + let ignore_because_panic_abort = cfg!(target_arch = "wasm32") + && !cfg!(target_os = "emscripten") && desc.should_panic != ShouldPanic::No; if force_ignore || desc.ignore || ignore_because_panic_abort { - monitor_ch.send((desc, TrIgnored, Vec::new())).unwrap(); + monitor_ch + .send(MonitorMsg::Test(desc, test_::Result::Ignored, Vec::new())) + .unwrap(); return; } fn run_test_inner( - desc: TestDesc, + desc: test_::Desc, monitor_ch: Sender, nocapture: bool, testfn: Box, @@ -1445,7 +1028,7 @@ pub fn run_test( let test_result = calc_result(&desc, result); let stdout = data.lock().unwrap().to_vec(); monitor_ch - .send((desc.clone(), test_result, stdout)) + .send(MonitorMsg::Test(desc.clone(), test_result, stdout)) .unwrap(); }; @@ -1492,62 +1075,31 @@ fn __rust_begin_short_backtrace(f: F) { f() } -fn calc_result(desc: &TestDesc, task_result: Result<(), Box>) -> TestResult { +fn calc_result(desc: &test_::Desc, task_result: Result<(), Box>) -> test_::Result { match (&desc.should_panic, task_result) { - (&ShouldPanic::No, Ok(())) | (&ShouldPanic::Yes, Err(_)) => TrOk, + (&ShouldPanic::No, Ok(())) | (&ShouldPanic::Yes, Err(_)) => test_::Result::Ok, (&ShouldPanic::YesWithMessage(msg), Err(ref err)) => { - if err.downcast_ref::() + if err + .downcast_ref::() .map(|e| &**e) .or_else(|| err.downcast_ref::<&'static str>().map(|e| *e)) .map(|e| e.contains(msg)) .unwrap_or(false) { - TrOk + test_::Result::Ok } else { if desc.allow_fail { - TrAllowedFail + test_::Result::AllowedFail } else { - TrFailedMsg(format!("Panic did not include expected string '{}'", msg)) + test_::Result::FailedMsg(format!( + "Panic did not include expected string '{}'", + msg + )) } } } - _ if desc.allow_fail => TrAllowedFail, - _ => TrFailed, - } -} - -#[derive(Clone, PartialEq)] -pub struct MetricMap(BTreeMap); - -impl MetricMap { - pub fn new() -> MetricMap { - MetricMap(BTreeMap::new()) - } - - /// Insert a named `value` (+/- `noise`) metric into the map. The value - /// must be non-negative. The `noise` indicates the uncertainty of the - /// metric, which doubles as the "noise range" of acceptable - /// pairwise-regressions on this named value, when comparing from one - /// metric to the next using `compare_to_old`. - /// - /// If `noise` is positive, then it means this metric is of a value - /// you want to see grow smaller, so a change larger than `noise` in the - /// positive direction represents a regression. - /// - /// If `noise` is negative, then it means this metric is of a value - /// you want to see grow larger, so a change larger than `noise` in the - /// negative direction represents a regression. - pub fn insert_metric(&mut self, name: &str, value: f64, noise: f64) { - let m = Metric { value, noise }; - self.0.insert(name.to_owned(), m); - } - - pub fn fmt_metrics(&self) -> String { - let v = self.0 - .iter() - .map(|(k, v)| format!("{}: {} (+/- {})", *k, v.value, v.noise)) - .collect::>(); - v.join(", ") + _ if desc.allow_fail => test_::Result::AllowedFail, + _ => test_::Result::Failed, } } @@ -1652,7 +1204,8 @@ where // If we've run for 100ms and seem to have converged to a // stable median. - if loop_run > Duration::from_millis(100) && summ.median_abs_dev_pct < 1.0 + if loop_run > Duration::from_millis(100) + && summ.median_abs_dev_pct < 1.0 && summ.median - summ5.median < summ5.median_abs_dev { return summ5; @@ -1678,12 +1231,14 @@ where } pub mod bench { - use std::panic::{catch_unwind, AssertUnwindSafe}; + use libtest_utils::{ + bench::Result, test::Desc as TestDesc}; + use super::{BenchMode, BenchSamples, Bencher, MonitorMsg, Sender, Sink,}; + use stats; use std::cmp; use std::io; + use std::panic::{catch_unwind, AssertUnwindSafe}; use std::sync::{Arc, Mutex}; - use stats; - use super::{BenchMode, BenchSamples, Bencher, MonitorMsg, Sender, Sink, TestDesc, TestResult}; pub fn benchmark(desc: TestDesc, monitor_ch: Sender, nocapture: bool, f: F) where @@ -1724,7 +1279,7 @@ pub mod bench { ns_iter_summ, mb_s: mb_s as usize, }; - TestResult::TrBench(bs) + Result::Ok(bs) } Ok(None) => { // iter not called, so no data. @@ -1734,13 +1289,13 @@ pub mod bench { ns_iter_summ: stats::Summary::new(samples), mb_s: 0, }; - TestResult::TrBench(bs) + Result::Ok(bs) } - Err(_) => TestResult::TrFailed, + Err(_) => Result::Failed, }; let stdout = data.lock().unwrap().to_vec(); - monitor_ch.send((desc, test_result, stdout)).unwrap(); + monitor_ch.send(MonitorMsg::Bench(desc, test_result, stdout)).unwrap(); } pub fn run_once(f: F) @@ -1758,29 +1313,29 @@ pub mod bench { #[cfg(test)] mod tests { - use test::{filter_tests, parse_opts, run_test, DynTestFn, DynTestName, MetricMap, RunIgnored, - ShouldPanic, StaticTestName, TestDesc, TestDescAndFn, TestOpts, TrFailed, - TrFailedMsg, TrIgnored, TrOk}; - use std::sync::mpsc::channel; use bench; + use std::sync::mpsc::channel; + use test::*; use Bencher; use Concurrent; + use MonitorMsg; + use libtest_utils::{test, format::{OutputLocation, ConsoleTestState, PrettyFormatter}}; - fn one_ignored_one_unignored_test() -> Vec { + fn one_ignored_one_unignored_test() -> Vec { vec![ - TestDescAndFn { + DescAndFn { desc: TestDesc { - name: StaticTestName("1"), + name: test::Name::Static("1"), ignore: true, should_panic: ShouldPanic::No, allow_fail: false, }, testfn: DynTestFn(Box::new(move || {})), }, - TestDescAndFn { + DescAndFn { desc: TestDesc { - name: StaticTestName("2"), + name: test::Name::Static("2"), ignore: false, should_panic: ShouldPanic::No, allow_fail: false, @@ -1795,9 +1350,9 @@ mod tests { fn f() { panic!(); } - let desc = TestDescAndFn { + let desc = DescAndFn { desc: TestDesc { - name: StaticTestName("whatever"), + name: test::Name::Static("whatever"), ignore: true, should_panic: ShouldPanic::No, allow_fail: false, @@ -1806,16 +1361,19 @@ mod tests { }; let (tx, rx) = channel(); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res != TrOk); + if let MonitorMsg::Test(_, res, _) = rx.recv().unwrap() { + assert!(res != test::Result::Ok); + } else { + unreachable!() + } } #[test] pub fn ignored_tests_result_in_ignored() { fn f() {} - let desc = TestDescAndFn { + let desc = DescAndFn { desc: TestDesc { - name: StaticTestName("whatever"), + name: test::Name::Static("whatever"), ignore: true, should_panic: ShouldPanic::No, allow_fail: false, @@ -1824,8 +1382,11 @@ mod tests { }; let (tx, rx) = channel(); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrIgnored); + if let MonitorMsg::Test(_, res, _) = rx.recv().unwrap() { + assert_eq!(res, test::Result::Ignored); + } else { + unreachable!() + } } #[test] @@ -1833,9 +1394,9 @@ mod tests { fn f() { panic!(); } - let desc = TestDescAndFn { + let desc = DescAndFn { desc: TestDesc { - name: StaticTestName("whatever"), + name: test::Name::Static("whatever"), ignore: false, should_panic: ShouldPanic::Yes, allow_fail: false, @@ -1844,8 +1405,11 @@ mod tests { }; let (tx, rx) = channel(); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrOk); + if let MonitorMsg::Test(_, res, _) = rx.recv().unwrap() { + assert_eq!(res, test::Result::Ok); + } else { + unreachable!() + } } #[test] @@ -1853,9 +1417,9 @@ mod tests { fn f() { panic!("an error message"); } - let desc = TestDescAndFn { + let desc = DescAndFn { desc: TestDesc { - name: StaticTestName("whatever"), + name: test::Name::Static("whatever"), ignore: false, should_panic: ShouldPanic::YesWithMessage("error message"), allow_fail: false, @@ -1864,8 +1428,11 @@ mod tests { }; let (tx, rx) = channel(); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrOk); + if let MonitorMsg::Test(_, res, _) = rx.recv().unwrap() { + assert_eq!(res, test::Result::Ok); + } else { + unreachable!() + } } #[test] @@ -1875,9 +1442,9 @@ mod tests { } let expected = "foobar"; let failed_msg = "Panic did not include expected string"; - let desc = TestDescAndFn { + let desc = DescAndFn { desc: TestDesc { - name: StaticTestName("whatever"), + name: test::Name::Static("whatever"), ignore: false, should_panic: ShouldPanic::YesWithMessage(expected), allow_fail: false, @@ -1886,16 +1453,19 @@ mod tests { }; let (tx, rx) = channel(); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrFailedMsg(format!("{} '{}'", failed_msg, expected))); + if let MonitorMsg::Test(_, res, _) = rx.recv().unwrap() { + assert_eq!(res, test::Result::FailedMsg(format!("{} '{}'", failed_msg, expected))); + } else { + unreachable!() + } } #[test] fn test_should_panic_but_succeeds() { fn f() {} - let desc = TestDescAndFn { + let desc = DescAndFn { desc: TestDesc { - name: StaticTestName("whatever"), + name: test::Name::Static("whatever"), ignore: false, should_panic: ShouldPanic::Yes, allow_fail: false, @@ -1904,8 +1474,11 @@ mod tests { }; let (tx, rx) = channel(); run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrFailed); + if let MonitorMsg::Test(_, res, _) = rx.recv().unwrap() { + assert_eq!(res, test::Result::Failed); + } else { + unreachable!() + } } #[test] @@ -1967,12 +1540,12 @@ mod tests { #[test] pub fn exact_filter_match() { - fn tests() -> Vec { + fn tests() -> Vec { vec!["base", "base::test", "base::test1", "base::test2"] .into_iter() - .map(|name| TestDescAndFn { + .map(|name| DescAndFn { desc: TestDesc { - name: StaticTestName(name), + name: test::Name::Static(name), ignore: false, should_panic: ShouldPanic::No, allow_fail: false, @@ -2081,9 +1654,9 @@ mod tests { fn testfn() {} let mut tests = Vec::new(); for name in &names { - let test = TestDescAndFn { + let test = DescAndFn { desc: TestDesc { - name: DynTestName((*name).clone()), + name: test::Name::Dyn((*name).clone()), ignore: false, should_panic: ShouldPanic::No, allow_fail: false, @@ -2159,7 +1732,7 @@ mod tests { let (tx, rx) = channel(); let desc = TestDesc { - name: StaticTestName("f"), + name: test::Name::Static("f"), ignore: false, should_panic: ShouldPanic::No, allow_fail: false, @@ -2178,7 +1751,7 @@ mod tests { let (tx, rx) = channel(); let desc = TestDesc { - name: StaticTestName("f"), + name: test::Name::Static("f"), ignore: false, should_panic: ShouldPanic::No, allow_fail: false, @@ -2187,4 +1760,74 @@ mod tests { ::bench::benchmark(desc, tx, true, f); rx.recv().unwrap(); } + + #[test] + fn should_sort_failures_before_printing_them() { + let test_a = TestDesc { + name: test::Name::Static("a"), + ignore: false, + should_panic: ShouldPanic::No, + allow_fail: false, + }; + + let test_b = TestDesc { + name: test::Name::Static("b"), + ignore: false, + should_panic: ShouldPanic::No, + allow_fail: false, + }; + + let mut out = PrettyFormatter::new(OutputLocation::Raw(Vec::new()), false, 10, false); + + let st = ConsoleTestState { + log_out: None, + total: 0, + passed: 0, + failed: 0, + ignored: 0, + allowed_fail: 0, + filtered_out: 0, + measured: 0, + metrics: MetricMap::new(), + failures: vec![(test_b, Vec::new()), (test_a, Vec::new())], + options: libtest_utils::format::Options::new(), + not_failures: Vec::new(), + }; + + out.write_failures(&st).unwrap(); + let s = match out.output_location() { + &OutputLocation::Raw(ref m) => String::from_utf8_lossy(&m[..]), + &OutputLocation::Pretty(_) => unreachable!(), + }; + + let apos = s.find("a").unwrap(); + let bpos = s.find("b").unwrap(); + assert!(apos < bpos); + } +} + +#[cfg(test)] +mod benches { + extern crate test; + use self::test::Bencher; + use stats::Stats; + + #[bench] + pub fn sum_three_items(b: &mut Bencher) { + b.iter(|| { + [1e20f64, 1.5f64, -1e20f64].sum(); + }) + } + #[bench] + pub fn sum_many_f64(b: &mut Bencher) { + let nums = [-1e30f64, 1e60, 1e30, 1.0, -1e60]; + let v = (0..500).map(|i| nums[i % 5]).collect::>(); + + b.iter(|| { + v.sum(); + }) + } + + #[bench] + pub fn no_iter(_: &mut Bencher) {} } diff --git a/src/libtest/libtest-utils/Cargo.toml b/src/libtest/libtest-utils/Cargo.toml new file mode 100644 index 0000000000000..595d84f24d9bf --- /dev/null +++ b/src/libtest/libtest-utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "libtest-utils" +version = "0.1.0" +authors = ["gnzlbg "] +edition = "2018" + +[dependencies] +term = "0.5.1" +libc = "0.2" \ No newline at end of file diff --git a/src/libtest/libtest-utils/src/bench.rs b/src/libtest/libtest-utils/src/bench.rs new file mode 100644 index 0000000000000..cef6a3dbacd7c --- /dev/null +++ b/src/libtest/libtest-utils/src/bench.rs @@ -0,0 +1,115 @@ +//! Benchmark utilities + +pub mod stats; + +use std::{collections::BTreeMap, fmt}; + +pub type Desc = crate::test::Desc; + +#[derive(Clone, Debug, PartialEq)] +pub struct BenchSamples { + pub ns_iter_summ: stats::Summary, + pub mb_s: usize, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Result { + Ok(BenchSamples), + Failed, +} + +// Format a number with thousands separators +fn fmt_thousands_sep(mut n: usize, sep: char) -> String { + use std::fmt::Write; + let mut output = String::new(); + let mut trailing = false; + for &pow in &[9, 6, 3, 0] { + let base = 10_usize.pow(pow); + if pow == 0 || trailing || n / base != 0 { + if !trailing { + output.write_fmt(format_args!("{}", n / base)).unwrap(); + } else { + output.write_fmt(format_args!("{:03}", n / base)).unwrap(); + } + if pow != 0 { + output.push(sep); + } + trailing = true; + } + n %= base; + } + + output +} + +impl fmt::Display for BenchSamples { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use std::fmt::Write; + let mut output = String::new(); + + let median = self.ns_iter_summ.median as usize; + let deviation = (self.ns_iter_summ.max - self.ns_iter_summ.min) as usize; + + output + .write_fmt(format_args!( + "{:>11} ns/iter (+/- {})", + fmt_thousands_sep(median, ','), + fmt_thousands_sep(deviation, ',') + )) + .unwrap(); + if self.mb_s != 0 { + output + .write_fmt(format_args!(" = {} MB/s", self.mb_s)) + .unwrap(); + } + write!(f, "{}", output) + } +} + +#[derive(Clone, PartialEq)] +pub struct MetricMap(pub BTreeMap); + +impl MetricMap { + pub fn new() -> MetricMap { + MetricMap(BTreeMap::new()) + } + + /// Insert a named `value` (+/- `noise`) metric into the map. The value + /// must be non-negative. The `noise` indicates the uncertainty of the + /// metric, which doubles as the "noise range" of acceptable + /// pairwise-regressions on this named value, when comparing from one + /// metric to the next using `compare_to_old`. + /// + /// If `noise` is positive, then it means this metric is of a value + /// you want to see grow smaller, so a change larger than `noise` in the + /// positive direction represents a regression. + /// + /// If `noise` is negative, then it means this metric is of a value + /// you want to see grow larger, so a change larger than `noise` in the + /// negative direction represents a regression. + pub fn insert_metric(&mut self, name: &str, value: f64, noise: f64) { + let m = Metric { value, noise }; + self.0.insert(name.to_owned(), m); + } + + pub fn fmt_metrics(&self) -> String { + let v = self + .0 + .iter() + .map(|(k, v)| format!("{}: {} (+/- {})", *k, v.value, v.noise)) + .collect::>(); + v.join(", ") + } +} + +#[derive(Clone, PartialEq, Debug, Copy)] +pub struct Metric { + pub value: f64, + pub noise: f64, +} + +impl Metric { + pub fn new(value: f64, noise: f64) -> Metric { + Metric { value, noise } + } +} diff --git a/src/libtest/stats.rs b/src/libtest/libtest-utils/src/bench/stats.rs similarity index 97% rename from src/libtest/stats.rs rename to src/libtest/libtest-utils/src/bench/stats.rs index 1fad612b946b4..6df1098df7c85 100644 --- a/src/libtest/stats.rs +++ b/src/libtest/libtest-utils/src/bench/stats.rs @@ -122,7 +122,7 @@ pub trait Stats { } /// Extracted collection of all the summary statistics of a sample set. -#[derive(Clone, PartialEq, Copy)] +#[derive(Clone, PartialEq, Copy, Debug)] #[allow(missing_docs)] pub struct Summary { pub sum: f64, @@ -329,11 +329,10 @@ pub fn winsorize(samples: &mut [f64], pct: f64) { #[cfg(test)] mod tests { - use stats::Stats; - use stats::Summary; + use super::{Stats, Summary}; use std::f64; - use std::io::prelude::*; use std::io; + use std::io::prelude::*; macro_rules! assert_approx_eq { ($a: expr, $b: expr) => {{ @@ -904,29 +903,3 @@ mod tests { assert_eq!([1e30f64, 1.2f64, -1e30f64].sum(), 1.2); } } - -#[cfg(test)] -mod bench { - extern crate test; - use self::test::Bencher; - use stats::Stats; - - #[bench] - pub fn sum_three_items(b: &mut Bencher) { - b.iter(|| { - [1e20f64, 1.5f64, -1e20f64].sum(); - }) - } - #[bench] - pub fn sum_many_f64(b: &mut Bencher) { - let nums = [-1e30f64, 1e60, 1e30, 1.0, -1e60]; - let v = (0..500).map(|i| nums[i % 5]).collect::>(); - - b.iter(|| { - v.sum(); - }) - } - - #[bench] - pub fn no_iter(_: &mut Bencher) {} -} diff --git a/src/libtest/libtest-utils/src/format.rs b/src/libtest/libtest-utils/src/format.rs new file mode 100644 index 0000000000000..3af94f5239090 --- /dev/null +++ b/src/libtest/libtest-utils/src/format.rs @@ -0,0 +1,176 @@ +//! libtest formatting + +use crate::{bench, test}; +use std::{fs, io, path}; + +/// Test name padding. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum Padding { + None, + OnRight, +} + +mod json; +pub use self::json::JsonFormatter; + +mod pretty; +pub use self::pretty::PrettyFormatter; + +mod terse; +pub use self::terse::TerseFormatter; + +pub trait OutputFormatter { + fn write_run_start(&mut self, test_count: usize) -> io::Result<()>; + fn write_test_start(&mut self, desc: &test::Desc) -> io::Result<()>; + fn write_timeout(&mut self, desc: &test::Desc) -> io::Result<()>; + fn write_test_result( + &mut self, + desc: &test::Desc, + result: &test::Result, + stdout: &[u8], + ) -> io::Result<()>; + fn write_bench_result( + &mut self, + desc: &bench::Desc, + result: &bench::Result, + stdout: &[u8], + ) -> io::Result<()>; + fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result; +} + +pub enum OutputLocation { + Pretty(Box), + Raw(T), +} + +impl io::Write for OutputLocation { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + OutputLocation::Pretty(ref mut term) => term.write(buf), + OutputLocation::Raw(ref mut stdout) => stdout.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match *self { + OutputLocation::Pretty(ref mut term) => term.flush(), + OutputLocation::Raw(ref mut stdout) => stdout.flush(), + } + } +} + +#[derive(Debug)] +pub struct Options { + pub logfile: Option, + pub display_output: bool, +} + +impl Options { + pub fn new() -> Self { + Self { + logfile: None, + display_output: false, + + } + + } +} + +pub struct ConsoleTestState { + pub log_out: Option, + pub total: usize, + pub passed: usize, + pub failed: usize, + pub ignored: usize, + pub allowed_fail: usize, + pub filtered_out: usize, + pub measured: usize, + pub metrics: bench::MetricMap, + pub failures: Vec<(test::Desc, Vec)>, + pub not_failures: Vec<(test::Desc, Vec)>, + pub options: Options, +} + +impl ConsoleTestState { + pub fn new(options: Options) -> io::Result { + let log_out = match options.logfile { + Some(ref path) => Some(fs::File::create(path)?), + None => None, + }; + + Ok(ConsoleTestState { + log_out, + total: 0, + passed: 0, + failed: 0, + ignored: 0, + allowed_fail: 0, + filtered_out: 0, + measured: 0, + metrics: bench::MetricMap::new(), + failures: Vec::new(), + not_failures: Vec::new(), + options, + }) + } + + pub fn write_log>(&mut self, msg: S) -> io::Result<()> { + use io::Write; + let msg = msg.as_ref(); + match self.log_out { + None => Ok(()), + Some(ref mut o) => o.write_all(msg.as_bytes()), + } + } + + pub fn write_log_test_result( + &mut self, + test: &test::Desc, + result: &test::Result, + ) -> io::Result<()> { + self.write_log(format!( + "{} {}\n", + match *result { + test::Result::Ok => "ok".to_owned(), + test::Result::Failed => "failed".to_owned(), + test::Result::FailedMsg(ref msg) => format!("failed: {}", msg), + test::Result::Ignored => "ignored".to_owned(), + test::Result::AllowedFail => "failed (allowed)".to_owned(), + }, + test.name + )) + } + + pub fn write_log_bench_result( + &mut self, + test: &bench::Desc, + result: &bench::Result, + ) -> io::Result<()> { + self.write_log(format!( + "{} {}\n", + match *result { + bench::Result::Ok(ref bs) => format!("{}", bs), + bench::Result::Failed => "failed".to_owned(), + }, + test.name + )) + } + + pub fn current_test_count(&self) -> usize { + self.passed + self.failed + self.ignored + self.measured + self.allowed_fail + } +} + +#[derive(Copy, Clone, Debug)] +pub enum ColorConfig { + AutoColor, + AlwaysColor, + NeverColor, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum OutputFormat { + Pretty, + Terse, + Json, +} diff --git a/src/libtest/formatters/json.rs b/src/libtest/libtest-utils/src/format/json.rs similarity index 80% rename from src/libtest/formatters/json.rs rename to src/libtest/libtest-utils/src/format/json.rs index 3c803ad82337f..2475f8ddd88f2 100644 --- a/src/libtest/formatters/json.rs +++ b/src/libtest/libtest-utils/src/format/json.rs @@ -8,18 +8,23 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use super::*; +use crate::{ + bench, + format::{io, ConsoleTestState, OutputFormatter, OutputLocation}, + test, +}; -pub(crate) struct JsonFormatter { +pub struct JsonFormatter { out: OutputLocation, } -impl JsonFormatter { +impl JsonFormatter { pub fn new(out: OutputLocation) -> Self { Self { out } } fn write_message(&mut self, s: &str) -> io::Result<()> { + use io::Write; assert!(!s.contains('\n')); self.out.write_all(s.as_ref())?; @@ -47,7 +52,7 @@ impl JsonFormatter { } } -impl OutputFormatter for JsonFormatter { +impl OutputFormatter for JsonFormatter { fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { self.write_message(&*format!( r#"{{ "type": "suite", "event": "started", "test_count": {} }}"#, @@ -55,23 +60,23 @@ impl OutputFormatter for JsonFormatter { )) } - fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_test_start(&mut self, desc: &test::Desc) -> io::Result<()> { self.write_message(&*format!( r#"{{ "type": "test", "event": "started", "name": "{}" }}"#, desc.name )) } - fn write_result( + fn write_test_result( &mut self, - desc: &TestDesc, - result: &TestResult, + desc: &test::Desc, + result: &test::Result, stdout: &[u8], ) -> io::Result<()> { match *result { - TrOk => self.write_event("test", desc.name.as_slice(), "ok", None), + test::Result::Ok => self.write_event("test", desc.name.as_slice(), "ok", None), - TrFailed => { + test::Result::Failed => { let extra_data = if stdout.len() > 0 { Some(format!( r#""stdout": "{}""#, @@ -84,20 +89,31 @@ impl OutputFormatter for JsonFormatter { self.write_event("test", desc.name.as_slice(), "failed", extra_data) } - TrFailedMsg(ref m) => self.write_event( + test::Result::FailedMsg(ref m) => self.write_event( "test", desc.name.as_slice(), "failed", Some(format!(r#""message": "{}""#, EscapedString(m))), ), - TrIgnored => self.write_event("test", desc.name.as_slice(), "ignored", None), + test::Result::Ignored => { + self.write_event("test", desc.name.as_slice(), "ignored", None) + } - TrAllowedFail => { + test::Result::AllowedFail => { self.write_event("test", desc.name.as_slice(), "allowed_failure", None) } + } + } - TrBench(ref bs) => { + fn write_bench_result( + &mut self, + desc: &bench::Desc, + result: &bench::Result, + stdout: &[u8], + ) -> io::Result<()> { + match *result { + bench::Result::Ok(ref bs) => { let median = bs.ns_iter_summ.median as usize; let deviation = (bs.ns_iter_summ.max - bs.ns_iter_summ.min) as usize; @@ -117,10 +133,22 @@ impl OutputFormatter for JsonFormatter { self.write_message(&*line) } + bench::Result::Failed => { + let extra_data = if stdout.len() > 0 { + Some(format!( + r#""stdout": "{}""#, + EscapedString(String::from_utf8_lossy(stdout)) + )) + } else { + None + }; + + self.write_event("benchmark", desc.name.as_slice(), "failed", extra_data) + } } } - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_timeout(&mut self, desc: &test::Desc) -> io::Result<()> { self.write_message(&*format!( r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#, desc.name diff --git a/src/libtest/formatters/pretty.rs b/src/libtest/libtest-utils/src/format/pretty.rs similarity index 80% rename from src/libtest/formatters/pretty.rs rename to src/libtest/libtest-utils/src/format/pretty.rs index f94780682a0c0..b5f117a11f9e9 100644 --- a/src/libtest/formatters/pretty.rs +++ b/src/libtest/libtest-utils/src/format/pretty.rs @@ -1,16 +1,12 @@ -// Copyright 2012-2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::*; - -pub(crate) struct PrettyFormatter { +//! Pretty format + +use crate::{ + bench, + format::{io, ConsoleTestState, OutputFormatter, OutputLocation}, + test, TEST_WARN_TIMEOUT_S, +}; + +pub struct PrettyFormatter { out: OutputLocation, use_color: bool, @@ -20,7 +16,7 @@ pub(crate) struct PrettyFormatter { is_multithreaded: bool, } -impl PrettyFormatter { +impl PrettyFormatter { pub fn new( out: OutputLocation, use_color: bool, @@ -35,7 +31,6 @@ impl PrettyFormatter { } } - #[cfg(test)] pub fn output_location(&self) -> &OutputLocation { &self.out } @@ -71,7 +66,7 @@ impl PrettyFormatter { pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { match self.out { - Pretty(ref mut term) => { + OutputLocation::Pretty(ref mut term) => { if self.use_color { term.fg(color)?; } @@ -81,7 +76,7 @@ impl PrettyFormatter { } term.flush() } - Raw(ref mut stdout) => { + OutputLocation::Raw(ref mut stdout) => { stdout.write_all(word.as_bytes())?; stdout.flush() } @@ -89,6 +84,7 @@ impl PrettyFormatter { } pub fn write_plain>(&mut self, s: S) -> io::Result<()> { + use io::Write; let s = s.as_ref(); self.out.write_all(s.as_bytes())?; self.out.flush() @@ -146,7 +142,7 @@ impl PrettyFormatter { Ok(()) } - fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_test_name(&mut self, desc: &test::Desc) -> io::Result<()> { let name = desc.padded_name(self.max_name_len, desc.name.padding()); self.write_plain(&format!("test {} ... ", name))?; @@ -154,13 +150,13 @@ impl PrettyFormatter { } } -impl OutputFormatter for PrettyFormatter { +impl OutputFormatter for PrettyFormatter { fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { let noun = if test_count != 1 { "tests" } else { "test" }; self.write_plain(&format!("\nrunning {} {}\n", test_count, noun)) } - fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_test_start(&mut self, desc: &test::Desc) -> io::Result<()> { // When running tests concurrently, we should not print // the test's name as the result will be mis-aligned. // When running the tests serially, we print the name here so @@ -172,24 +168,44 @@ impl OutputFormatter for PrettyFormatter { Ok(()) } - fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[u8]) -> io::Result<()> { + fn write_test_result( + &mut self, + desc: &test::Desc, + result: &test::Result, + _: &[u8], + ) -> io::Result<()> { if self.is_multithreaded { self.write_test_name(desc)?; } match *result { - TrOk => self.write_ok(), - TrFailed | TrFailedMsg(_) => self.write_failed(), - TrIgnored => self.write_ignored(), - TrAllowedFail => self.write_allowed_fail(), - TrBench(ref bs) => { + test::Result::Ok => self.write_ok(), + test::Result::Failed | test::Result::FailedMsg(_) => self.write_failed(), + test::Result::Ignored => self.write_ignored(), + test::Result::AllowedFail => self.write_allowed_fail(), + } + } + + fn write_bench_result( + &mut self, + desc: &bench::Desc, + result: &bench::Result, + _: &[u8], + ) -> io::Result<()> { + match *result { + bench::Result::Ok(ref samples) => { + if self.is_multithreaded { + self.write_test_name(desc)?; + } + self.write_bench()?; - self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) + self.write_plain(&format!(": {}\n", samples)) } + bench::Result::Failed => self.write_failed(), } } - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_timeout(&mut self, desc: &test::Desc) -> io::Result<()> { if self.is_multithreaded { self.write_test_name(desc)?; } diff --git a/src/libtest/formatters/terse.rs b/src/libtest/libtest-utils/src/format/terse.rs similarity index 80% rename from src/libtest/formatters/terse.rs rename to src/libtest/libtest-utils/src/format/terse.rs index cf15c89fac241..158ef0888dd41 100644 --- a/src/libtest/formatters/terse.rs +++ b/src/libtest/libtest-utils/src/format/terse.rs @@ -1,16 +1,12 @@ -// Copyright 2012-2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use super::*; - -pub(crate) struct TerseFormatter { +//! Terse format + +use crate::{ + bench, + format::{io, ConsoleTestState, OutputFormatter, OutputLocation, Padding}, + test, QUIET_MODE_MAX_COLUMN, TEST_WARN_TIMEOUT_S, +}; + +pub struct TerseFormatter { out: OutputLocation, use_color: bool, is_multithreaded: bool, @@ -21,7 +17,7 @@ pub(crate) struct TerseFormatter { total_test_count: usize, } -impl TerseFormatter { +impl TerseFormatter { pub fn new( out: OutputLocation, use_color: bool, @@ -68,7 +64,7 @@ impl TerseFormatter { // we insert a new line every 100 dots in order to flush the // screen when dealing with line-buffered output (e.g., piping to // `stamp` in the rust CI). - let out = format!(" {}/{}\n", self.test_count+1, self.total_test_count); + let out = format!(" {}/{}\n", self.test_count + 1, self.total_test_count); self.write_plain(&out)?; } @@ -78,7 +74,7 @@ impl TerseFormatter { pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { match self.out { - Pretty(ref mut term) => { + OutputLocation::Pretty(ref mut term) => { if self.use_color { term.fg(color)?; } @@ -88,7 +84,7 @@ impl TerseFormatter { } term.flush() } - Raw(ref mut stdout) => { + OutputLocation::Raw(ref mut stdout) => { stdout.write_all(word.as_bytes())?; stdout.flush() } @@ -96,6 +92,7 @@ impl TerseFormatter { } pub fn write_plain>(&mut self, s: S) -> io::Result<()> { + use io::Write; let s = s.as_ref(); self.out.write_all(s.as_bytes())?; self.out.flush() @@ -153,7 +150,7 @@ impl TerseFormatter { Ok(()) } - fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_test_name(&mut self, desc: &test::Desc) -> io::Result<()> { let name = desc.padded_name(self.max_name_len, desc.name.padding()); self.write_plain(&format!("test {} ... ", name))?; @@ -161,42 +158,58 @@ impl TerseFormatter { } } -impl OutputFormatter for TerseFormatter { +impl OutputFormatter for TerseFormatter { fn write_run_start(&mut self, test_count: usize) -> io::Result<()> { self.total_test_count = test_count; let noun = if test_count != 1 { "tests" } else { "test" }; self.write_plain(&format!("\nrunning {} {}\n", test_count, noun)) } - fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_test_start(&mut self, desc: &test::Desc) -> io::Result<()> { // Remnants from old libtest code that used the padding value // in order to indicate benchmarks. // When running benchmarks, terse-mode should still print their name as if // it is the Pretty formatter. - if !self.is_multithreaded && desc.name.padding() == PadOnRight { + if !self.is_multithreaded && desc.name.padding() == Padding::OnRight { self.write_test_name(desc)?; } Ok(()) } - fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[u8]) -> io::Result<()> { + fn write_test_result( + &mut self, + _desc: &test::Desc, + result: &test::Result, + _: &[u8], + ) -> io::Result<()> { + match *result { + test::Result::Ok => self.write_ok(), + test::Result::Failed | test::Result::FailedMsg(_) => self.write_failed(), + test::Result::Ignored => self.write_ignored(), + test::Result::AllowedFail => self.write_allowed_fail(), + } + } + + fn write_bench_result( + &mut self, + desc: &bench::Desc, + result: &bench::Result, + _: &[u8], + ) -> io::Result<()> { match *result { - TrOk => self.write_ok(), - TrFailed | TrFailedMsg(_) => self.write_failed(), - TrIgnored => self.write_ignored(), - TrAllowedFail => self.write_allowed_fail(), - TrBench(ref bs) => { + bench::Result::Ok(ref bs) => { if self.is_multithreaded { self.write_test_name(desc)?; } self.write_bench()?; - self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) + self.write_plain(&format!(": {}\n", bs)) } + bench::Result::Failed => self.write_failed(), } } - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { + fn write_timeout(&mut self, desc: &test::Desc) -> io::Result<()> { self.write_plain(&format!( "test {} has been running for over {} seconds\n", desc.name, TEST_WARN_TIMEOUT_S diff --git a/src/libtest/libtest-utils/src/get_concurrency.rs b/src/libtest/libtest-utils/src/get_concurrency.rs new file mode 100644 index 0000000000000..7a7efa2b5a1a4 --- /dev/null +++ b/src/libtest/libtest-utils/src/get_concurrency.rs @@ -0,0 +1,145 @@ +//! Get concurrency + +use std::env; + +#[allow(deprecated)] +pub fn get_concurrency() -> usize { + return match env::var("RUST_TEST_THREADS") { + Ok(s) => { + let opt_n: Option = s.parse().ok(); + match opt_n { + Some(n) if n > 0 => n, + _ => panic!( + "RUST_TEST_THREADS is `{}`, should be a positive integer.", + s + ), + } + } + Err(..) => num_cpus(), + }; + + #[cfg(windows)] + #[allow(nonstandard_style)] + fn num_cpus() -> usize { + #[repr(C)] + struct SYSTEM_INFO { + wProcessorArchitecture: u16, + wReserved: u16, + dwPageSize: u32, + lpMinimumApplicationAddress: *mut u8, + lpMaximumApplicationAddress: *mut u8, + dwActiveProcessorMask: *mut u8, + dwNumberOfProcessors: u32, + dwProcessorType: u32, + dwAllocationGranularity: u32, + wProcessorLevel: u16, + wProcessorRevision: u16, + } + extern "system" { + fn GetSystemInfo(info: *mut SYSTEM_INFO) -> i32; + } + unsafe { + let mut sysinfo = std::mem::zeroed(); + GetSystemInfo(&mut sysinfo); + sysinfo.dwNumberOfProcessors as usize + } + } + + #[cfg(target_os = "redox")] + fn num_cpus() -> usize { + // FIXME: Implement num_cpus on Redox + 1 + } + + #[cfg(any( + all(target_arch = "wasm32", not(target_os = "emscripten")), + target_env = "sgx" + ))] + fn num_cpus() -> usize { + 1 + } + + #[cfg(any( + target_os = "android", + target_os = "cloudabi", + target_os = "emscripten", + target_os = "fuchsia", + target_os = "ios", + target_os = "linux", + target_os = "macos", + target_os = "solaris" + ))] + fn num_cpus() -> usize { + unsafe { libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as usize } + } + + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "bitrig", + target_os = "netbsd" + ))] + fn num_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + + unsafe { + cpus = libc::sysconf(libc::_SC_NPROCESSORS_ONLN) as libc::c_uint; + } + if cpus < 1 { + let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; + unsafe { + libc::sysctl( + mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0, + ); + } + if cpus < 1 { + cpus = 1; + } + } + cpus as usize + } + + #[cfg(target_os = "openbsd")] + fn num_cpus() -> usize { + use std::ptr; + + let mut cpus: libc::c_uint = 0; + let mut cpus_size = std::mem::size_of_val(&cpus); + let mut mib = [libc::CTL_HW, libc::HW_NCPU, 0, 0]; + + unsafe { + libc::sysctl( + mib.as_mut_ptr(), + 2, + &mut cpus as *mut _ as *mut _, + &mut cpus_size as *mut _ as *mut _, + ptr::null_mut(), + 0, + ); + } + if cpus < 1 { + cpus = 1; + } + cpus as usize + } + + #[cfg(target_os = "haiku")] + fn num_cpus() -> usize { + // FIXME: implement + 1 + } + + #[cfg(target_os = "l4re")] + fn num_cpus() -> usize { + // FIXME: implement + 1 + } +} diff --git a/src/libtest/libtest-utils/src/isatty.rs b/src/libtest/libtest-utils/src/isatty.rs new file mode 100644 index 0000000000000..e93a1f1005418 --- /dev/null +++ b/src/libtest/libtest-utils/src/isatty.rs @@ -0,0 +1,33 @@ +//! Is stdout a tty ? + +#[cfg(any( + target_os = "cloudabi", + target_os = "redox", + all(target_arch = "wasm32", not(target_os = "emscripten")), + target_env = "sgx" +))] +pub fn stdout_isatty() -> bool { + // FIXME: Implement isatty on Redox and SGX + false +} +#[cfg(unix)] +pub fn stdout_isatty() -> bool { + unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 } +} +#[cfg(windows)] +pub fn stdout_isatty() -> bool { + type DWORD = u32; + type BOOL = i32; + type HANDLE = *mut u8; + type LPDWORD = *mut u32; + const STD_OUTPUT_HANDLE: DWORD = -11i32 as DWORD; + extern "system" { + fn GetStdHandle(which: DWORD) -> HANDLE; + fn GetConsoleMode(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL; + } + unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + let mut out = 0; + GetConsoleMode(handle, &mut out) != 0 + } +} diff --git a/src/libtest/libtest-utils/src/lib.rs b/src/libtest/libtest-utils/src/lib.rs new file mode 100644 index 0000000000000..5dbe9376521f5 --- /dev/null +++ b/src/libtest/libtest-utils/src/lib.rs @@ -0,0 +1,26 @@ +//! libtest utilities + +#![feature(uniform_paths)] + +pub mod bench; +pub mod format; +pub mod test; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ShouldPanic { + No, + Yes, + YesWithMessage(&'static str), +} + +/// Seconds after which a test-timed-out warning is emitted +pub const TEST_WARN_TIMEOUT_S: u64 = 60; + +/// Insert a '\n' after 100 tests in quiet mode +pub const QUIET_MODE_MAX_COLUMN: usize = 100; + +mod isatty; +pub use isatty::stdout_isatty; + +mod get_concurrency; +pub use get_concurrency::get_concurrency; diff --git a/src/libtest/libtest-utils/src/test.rs b/src/libtest/libtest-utils/src/test.rs new file mode 100644 index 0000000000000..7346dd830e635 --- /dev/null +++ b/src/libtest/libtest-utils/src/test.rs @@ -0,0 +1,85 @@ +//! Tests utilities + +use crate::{format::Padding, ShouldPanic}; +use std::{borrow::Cow, fmt}; + +/// Test description. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Desc { + pub name: Name, + pub ignore: bool, + pub should_panic: ShouldPanic, + pub allow_fail: bool, +} + +impl Desc { + /// Padded test name + pub fn padded_name(&self, column_count: usize, align: Padding) -> String { + let mut name = String::from(self.name.as_slice()); + let fill = column_count.saturating_sub(name.len()); + let pad = " ".repeat(fill); + match align { + Padding::None => name, + Padding::OnRight => { + name.push_str(&pad); + name + } + } + } +} + +/// The name of a test. +/// +/// By convention this follows the rules for rust paths; i.e., it should be a +/// series of identifiers separated by double colons. This way if some test +/// runner wants to arrange the tests hierarchically it may. +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub enum Name { + Static(&'static str), + Dyn(String), + Aligned(Cow<'static, str>, Padding), +} + +impl Name { + pub fn as_slice(&self) -> &str { + match *self { + Name::Static(s) => s, + Name::Dyn(ref s) => s, + Name::Aligned(ref s, _) => &*s, + } + } + + pub fn padding(&self) -> Padding { + match self { + &Name::Aligned(_, p) => p, + _ => Padding::None, + } + } + + pub fn with_padding(&self, padding: Padding) -> Name { + let name = match self { + &Name::Static(name) => Cow::Borrowed(name), + &Name::Dyn(ref name) => Cow::Owned(name.clone()), + &Name::Aligned(ref name, _) => name.clone(), + }; + + Name::Aligned(name, padding) + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self.as_slice(), f) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Result { + Ok, + Failed, + FailedMsg(String), + Ignored, + AllowedFail, +} + +unsafe impl Send for Result {} diff --git a/src/libtest/libtest-utils/src/types.rs b/src/libtest/libtest-utils/src/types.rs new file mode 100644 index 0000000000000..fd40910d9e70d --- /dev/null +++ b/src/libtest/libtest-utils/src/types.rs @@ -0,0 +1,4 @@ + + + + diff --git a/src/llvm b/src/llvm index f4728ed8fa229..a784eca10d2c1 160000 --- a/src/llvm +++ b/src/llvm @@ -1 +1 @@ -Subproject commit f4728ed8fa2296c5b009bb85550e157e1e57ed0b +Subproject commit a784eca10d2c1f09e65d67e16eca266485e1eac3 diff --git a/src/stdsimd b/src/stdsimd index 3c0503db84399..5e628c5120c61 160000 --- a/src/stdsimd +++ b/src/stdsimd @@ -1 +1 @@ -Subproject commit 3c0503db8439928e42c1175f0009c506fc874ae9 +Subproject commit 5e628c5120c619a22799187371f057ec41e06f87 diff --git a/src/tools/cargo b/src/tools/cargo index 2cf1f5dda2f7e..28fb20034a5bb 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit 2cf1f5dda2f7ed84e94c4d32f643e0f1f15352f0 +Subproject commit 28fb20034a5bb42ea589664de2617dd1840506d3 diff --git a/src/tools/clang b/src/tools/clang index 032312dd0140a..d0fc1788123de 160000 --- a/src/tools/clang +++ b/src/tools/clang @@ -1 +1 @@ -Subproject commit 032312dd0140a7074c9b89d305fe44eb0e44e407 +Subproject commit d0fc1788123de9844c8088b977cd142021cea1f2 diff --git a/src/tools/clippy b/src/tools/clippy index a416c5e0f7c4c..a3c77f6ad1c1c 160000 --- a/src/tools/clippy +++ b/src/tools/clippy @@ -1 +1 @@ -Subproject commit a416c5e0f7c4c9473069a58410d3ec3e86b1ac0d +Subproject commit a3c77f6ad1c1c185e561e9cd7fdec7db569169d1 diff --git a/src/tools/lld b/src/tools/lld index 1928c5eeb613a..2a9b88b8b419d 160000 --- a/src/tools/lld +++ b/src/tools/lld @@ -1 +1 @@ -Subproject commit 1928c5eeb613a4c6d232fc47ae91914bbfd92a79 +Subproject commit 2a9b88b8b419d094fb2185c0ca31c28d31bdca00 diff --git a/src/tools/lldb b/src/tools/lldb index 8ad0817ce45b0..fdea743be550e 160000 --- a/src/tools/lldb +++ b/src/tools/lldb @@ -1 +1 @@ -Subproject commit 8ad0817ce45b0eef9d374691b23f2bd69c164254 +Subproject commit fdea743be550ed8d7b61b2c908944cdd1290a6ad