From 95f79380ca06d4ef69f008ae9ead01fa451234b1 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 22 Jan 2019 21:08:27 +0100 Subject: [PATCH 01/12] Move libtest out of rust-lang/rust --- src/libterm/Cargo.toml | 10 - src/libterm/lib.rs | 201 -- src/libterm/terminfo/mod.rs | 265 --- src/libterm/terminfo/parm.rs | 669 ------- src/libterm/terminfo/parser/compiled.rs | 346 ---- src/libterm/terminfo/searcher.rs | 84 - src/libterm/win.rs | 203 -- src/libtest/Cargo.toml | 3 +- src/libtest/formatters/json.rs | 208 --- src/libtest/formatters/mod.rs | 22 - src/libtest/formatters/pretty.rs | 232 --- src/libtest/formatters/terse.rs | 235 --- src/libtest/lib.rs | 2247 ----------------------- src/libtest/stats.rs | 922 ---------- 14 files changed, 1 insertion(+), 5646 deletions(-) delete mode 100644 src/libterm/Cargo.toml delete mode 100644 src/libterm/lib.rs delete mode 100644 src/libterm/terminfo/mod.rs delete mode 100644 src/libterm/terminfo/parm.rs delete mode 100644 src/libterm/terminfo/parser/compiled.rs delete mode 100644 src/libterm/terminfo/searcher.rs delete mode 100644 src/libterm/win.rs delete mode 100644 src/libtest/formatters/json.rs delete mode 100644 src/libtest/formatters/mod.rs delete mode 100644 src/libtest/formatters/pretty.rs delete mode 100644 src/libtest/formatters/terse.rs delete mode 100644 src/libtest/lib.rs delete mode 100644 src/libtest/stats.rs diff --git a/src/libterm/Cargo.toml b/src/libterm/Cargo.toml deleted file mode 100644 index 4eba9a9d79c..00000000000 --- a/src/libterm/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -authors = ["The Rust Project Developers"] -name = "term" -version = "0.0.0" -edition = "2018" - -[lib] -name = "term" -path = "lib.rs" -crate-type = ["dylib", "rlib"] diff --git a/src/libterm/lib.rs b/src/libterm/lib.rs deleted file mode 100644 index 711716d9b92..00000000000 --- a/src/libterm/lib.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Terminal formatting library. -//! -//! This crate provides the `Terminal` trait, which abstracts over an [ANSI -//! Terminal][ansi] to provide color printing, among other things. There are two -//! implementations, the `TerminfoTerminal`, which uses control characters from -//! a [terminfo][ti] database, and `WinConsole`, which uses the [Win32 Console -//! API][win]. -//! -//! # Examples -//! -//! ```no_run -//! # #![feature(rustc_private)] -//! extern crate term; -//! use std::io::prelude::*; -//! -//! fn main() { -//! let mut t = term::stdout().unwrap(); -//! -//! t.fg(term::color::GREEN).unwrap(); -//! write!(t, "hello, ").unwrap(); -//! -//! t.fg(term::color::RED).unwrap(); -//! writeln!(t, "world!").unwrap(); -//! -//! assert!(t.reset().unwrap()); -//! } -//! ``` -//! -//! [ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code -//! [win]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682010%28v=vs.85%29.aspx -//! [ti]: https://en.wikipedia.org/wiki/Terminfo - -#![doc(html_root_url = "https://doc.rust-lang.org/nightly/", - html_playground_url = "https://play.rust-lang.org/", - test(attr(deny(warnings))))] -#![deny(missing_docs)] - -#![deny(rust_2018_idioms)] - -#![cfg_attr(windows, feature(libc))] -// Handle rustfmt skips -#![feature(custom_attribute)] -#![allow(unused_attributes)] - -use std::io::prelude::*; -use std::io::{self, Stdout, Stderr}; - -pub use terminfo::TerminfoTerminal; -#[cfg(windows)] -pub use win::WinConsole; - -pub mod terminfo; - -#[cfg(windows)] -mod win; - -/// Alias for stdout terminals. -pub type StdoutTerminal = dyn Terminal + Send; -/// Alias for stderr terminals. -pub type StderrTerminal = dyn Terminal + Send; - -#[cfg(not(windows))] -/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be -/// opened. -pub fn stdout() -> Option> { - TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box) -} - -#[cfg(windows)] -/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be -/// opened. -pub fn stdout() -> Option> { - TerminfoTerminal::new(io::stdout()) - .map(|t| Box::new(t) as Box) - .or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box)) -} - -#[cfg(not(windows))] -/// Returns a Terminal wrapping stderr, or None if a terminal couldn't be -/// opened. -pub fn stderr() -> Option> { - TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box) -} - -#[cfg(windows)] -/// Returns a Terminal wrapping stderr, or None if a terminal couldn't be -/// opened. -pub fn stderr() -> Option> { - TerminfoTerminal::new(io::stderr()) - .map(|t| Box::new(t) as Box) - .or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box)) -} - - -/// Terminal color definitions -#[allow(missing_docs)] -pub mod color { - /// Number for a terminal color - pub type Color = u16; - - pub const BLACK: Color = 0; - pub const RED: Color = 1; - pub const GREEN: Color = 2; - pub const YELLOW: Color = 3; - pub const BLUE: Color = 4; - pub const MAGENTA: Color = 5; - pub const CYAN: Color = 6; - pub const WHITE: Color = 7; - - pub const BRIGHT_BLACK: Color = 8; - pub const BRIGHT_RED: Color = 9; - pub const BRIGHT_GREEN: Color = 10; - pub const BRIGHT_YELLOW: Color = 11; - pub const BRIGHT_BLUE: Color = 12; - pub const BRIGHT_MAGENTA: Color = 13; - pub const BRIGHT_CYAN: Color = 14; - pub const BRIGHT_WHITE: Color = 15; -} - -/// Terminal attributes for use with term.attr(). -/// -/// Most attributes can only be turned on and must be turned off with term.reset(). -/// The ones that can be turned off explicitly take a boolean value. -/// Color is also represented as an attribute for convenience. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub enum Attr { - /// Bold (or possibly bright) mode - Bold, - /// Dim mode, also called faint or half-bright. Often not supported - Dim, - /// Italics mode. Often not supported - Italic(bool), - /// Underline mode - Underline(bool), - /// Blink mode - Blink, - /// Standout mode. Often implemented as Reverse, sometimes coupled with Bold - Standout(bool), - /// Reverse mode, inverts the foreground and background colors - Reverse, - /// Secure mode, also called invis mode. Hides the printed text - Secure, - /// Convenience attribute to set the foreground color - ForegroundColor(color::Color), - /// Convenience attribute to set the background color - BackgroundColor(color::Color), -} - -/// A terminal with similar capabilities to an ANSI Terminal -/// (foreground/background colors etc). -pub trait Terminal: Write { - /// The terminal's output writer type. - type Output: Write; - - /// Sets the foreground color to the given color. - /// - /// If the color is a bright color, but the terminal only supports 8 colors, - /// the corresponding normal color will be used instead. - /// - /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)` - /// if there was an I/O error. - fn fg(&mut self, color: color::Color) -> io::Result; - - /// Sets the background color to the given color. - /// - /// If the color is a bright color, but the terminal only supports 8 colors, - /// the corresponding normal color will be used instead. - /// - /// Returns `Ok(true)` if the color was set, `Ok(false)` otherwise, and `Err(e)` - /// if there was an I/O error. - fn bg(&mut self, color: color::Color) -> io::Result; - - /// Sets the given terminal attribute, if supported. Returns `Ok(true)` - /// if the attribute was supported, `Ok(false)` otherwise, and `Err(e)` if - /// there was an I/O error. - fn attr(&mut self, attr: Attr) -> io::Result; - - /// Returns `true` if the given terminal attribute is supported. - fn supports_attr(&self, attr: Attr) -> bool; - - /// Resets all terminal attributes and colors to their defaults. - /// - /// Returns `Ok(true)` if the terminal was reset, `Ok(false)` otherwise, and `Err(e)` if there - /// was an I/O error. - /// - /// *Note: This does not flush.* - /// - /// That means the reset command may get buffered so, if you aren't planning on doing anything - /// else that might flush stdout's buffer (e.g., writing a line of text), you should flush after - /// calling reset. - fn reset(&mut self) -> io::Result; - - /// Gets an immutable reference to the stream inside - fn get_ref(&self) -> &Self::Output; - - /// Gets a mutable reference to the stream inside - fn get_mut(&mut self) -> &mut Self::Output; - - /// Returns the contained stream, destroying the `Terminal` - fn into_inner(self) -> Self::Output where Self: Sized; -} diff --git a/src/libterm/terminfo/mod.rs b/src/libterm/terminfo/mod.rs deleted file mode 100644 index be90195065e..00000000000 --- a/src/libterm/terminfo/mod.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! Terminfo database interface. - -use std::collections::HashMap; -use std::env; -use std::error; -use std::fmt; -use std::fs::File; -use std::io::{self, prelude::*, BufReader}; -use std::path::Path; - -use crate::Attr; -use crate::color; -use crate::Terminal; - -use searcher::get_dbpath_for_term; -use parser::compiled::{parse, msys_terminfo}; -use parm::{expand, Variables, Param}; - -/// A parsed terminfo database entry. -#[derive(Debug)] -pub struct TermInfo { - /// Names for the terminal - pub names: Vec, - /// Map of capability name to boolean value - pub bools: HashMap, - /// Map of capability name to numeric value - pub numbers: HashMap, - /// Map of capability name to raw (unexpanded) string - pub strings: HashMap>, -} - -/// A terminfo creation error. -#[derive(Debug)] -pub enum Error { - /// TermUnset Indicates that the environment doesn't include enough information to find - /// the terminfo entry. - TermUnset, - /// MalformedTerminfo indicates that parsing the terminfo entry failed. - MalformedTerminfo(String), - /// io::Error forwards any io::Errors encountered when finding or reading the terminfo entry. - IoError(io::Error), -} - -impl error::Error for Error { - fn description(&self) -> &str { - "failed to create TermInfo" - } - - fn cause(&self) -> Option<&dyn error::Error> { - use Error::*; - match *self { - IoError(ref e) => Some(e), - _ => None, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use Error::*; - match *self { - TermUnset => Ok(()), - MalformedTerminfo(ref e) => e.fmt(f), - IoError(ref e) => e.fmt(f), - } - } -} - -impl TermInfo { - /// Creates a TermInfo based on current environment. - pub fn from_env() -> Result { - let term = match env::var("TERM") { - Ok(name) => TermInfo::from_name(&name), - Err(..) => return Err(Error::TermUnset), - }; - - if term.is_err() && env::var("MSYSCON").ok().map_or(false, |s| "mintty.exe" == s) { - // msys terminal - Ok(msys_terminfo()) - } else { - term - } - } - - /// Creates a TermInfo for the named terminal. - pub fn from_name(name: &str) -> Result { - get_dbpath_for_term(name) - .ok_or_else(|| { - Error::IoError(io::Error::new(io::ErrorKind::NotFound, "terminfo file not found")) - }) - .and_then(|p| TermInfo::from_path(&(*p))) - } - - /// Parse the given TermInfo. - pub fn from_path>(path: P) -> Result { - Self::_from_path(path.as_ref()) - } - // Keep the metadata small - fn _from_path(path: &Path) -> Result { - let file = File::open(path).map_err(Error::IoError)?; - let mut reader = BufReader::new(file); - parse(&mut reader, false).map_err(Error::MalformedTerminfo) - } -} - -pub mod searcher; - -/// TermInfo format parsing. -pub mod parser { - //! ncurses-compatible compiled terminfo format parsing (term(5)) - pub mod compiled; -} -pub mod parm; - - -fn cap_for_attr(attr: Attr) -> &'static str { - match attr { - Attr::Bold => "bold", - Attr::Dim => "dim", - Attr::Italic(true) => "sitm", - Attr::Italic(false) => "ritm", - Attr::Underline(true) => "smul", - Attr::Underline(false) => "rmul", - Attr::Blink => "blink", - Attr::Standout(true) => "smso", - Attr::Standout(false) => "rmso", - Attr::Reverse => "rev", - Attr::Secure => "invis", - Attr::ForegroundColor(_) => "setaf", - Attr::BackgroundColor(_) => "setab", - } -} - -/// A Terminal that knows how many colors it supports, with a reference to its -/// parsed Terminfo database record. -pub struct TerminfoTerminal { - num_colors: u16, - out: T, - ti: TermInfo, -} - -impl Terminal for TerminfoTerminal { - type Output = T; - fn fg(&mut self, color: color::Color) -> io::Result { - let color = self.dim_if_necessary(color); - if self.num_colors > color { - return self.apply_cap("setaf", &[Param::Number(color as i32)]); - } - Ok(false) - } - - fn bg(&mut self, color: color::Color) -> io::Result { - let color = self.dim_if_necessary(color); - if self.num_colors > color { - return self.apply_cap("setab", &[Param::Number(color as i32)]); - } - Ok(false) - } - - fn attr(&mut self, attr: Attr) -> io::Result { - match attr { - Attr::ForegroundColor(c) => self.fg(c), - Attr::BackgroundColor(c) => self.bg(c), - _ => self.apply_cap(cap_for_attr(attr), &[]), - } - } - - fn supports_attr(&self, attr: Attr) -> bool { - match attr { - Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0, - _ => { - let cap = cap_for_attr(attr); - self.ti.strings.get(cap).is_some() - } - } - } - - fn reset(&mut self) -> io::Result { - // are there any terminals that have color/attrs and not sgr0? - // Try falling back to sgr, then op - let cmd = match ["sgr0", "sgr", "op"] - .iter() - .filter_map(|cap| self.ti.strings.get(*cap)) - .next() { - Some(op) => { - match expand(&op, &[], &mut Variables::new()) { - Ok(cmd) => cmd, - Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)), - } - } - None => return Ok(false), - }; - self.out.write_all(&cmd).and(Ok(true)) - } - - fn get_ref(&self) -> &T { - &self.out - } - - fn get_mut(&mut self) -> &mut T { - &mut self.out - } - - fn into_inner(self) -> T - where Self: Sized - { - self.out - } -} - -impl TerminfoTerminal { - /// Creates a new TerminfoTerminal with the given TermInfo and Write. - pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal { - let nc = if terminfo.strings.contains_key("setaf") && - terminfo.strings.contains_key("setab") { - terminfo.numbers.get("colors").map_or(0, |&n| n) - } else { - 0 - }; - - TerminfoTerminal { - out, - ti: terminfo, - num_colors: nc, - } - } - - /// Creates a new TerminfoTerminal for the current environment with the given Write. - /// - /// Returns `None` when the terminfo cannot be found or parsed. - pub fn new(out: T) -> Option> { - TermInfo::from_env().map(move |ti| TerminfoTerminal::new_with_terminfo(out, ti)).ok() - } - - fn dim_if_necessary(&self, color: color::Color) -> color::Color { - if color >= self.num_colors && color >= 8 && color < 16 { - color - 8 - } else { - color - } - } - - fn apply_cap(&mut self, cmd: &str, params: &[Param]) -> io::Result { - match self.ti.strings.get(cmd) { - Some(cmd) => { - match expand(&cmd, params, &mut Variables::new()) { - Ok(s) => self.out.write_all(&s).and(Ok(true)), - Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), - } - } - None => Ok(false), - } - } -} - - -impl Write for TerminfoTerminal { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.out.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.out.flush() - } -} diff --git a/src/libterm/terminfo/parm.rs b/src/libterm/terminfo/parm.rs deleted file mode 100644 index 28229bd87b0..00000000000 --- a/src/libterm/terminfo/parm.rs +++ /dev/null @@ -1,669 +0,0 @@ -//! Parameterized string expansion - -use self::Param::*; -use self::States::*; - -use std::iter::repeat; - -#[derive(Clone, Copy, PartialEq)] -enum States { - Nothing, - Percent, - SetVar, - GetVar, - PushParam, - CharConstant, - CharClose, - IntConstant(i32), - FormatPattern(Flags, FormatState), - SeekIfElse(usize), - SeekIfElsePercent(usize), - SeekIfEnd(usize), - SeekIfEndPercent(usize), -} - -#[derive(Copy, PartialEq, Clone)] -enum FormatState { - Flags, - Width, - Precision, -} - -/// Types of parameters a capability can use -#[allow(missing_docs)] -#[derive(Clone)] -pub enum Param { - Words(String), - Number(i32), -} - -/// Container for static and dynamic variable arrays -pub struct Variables { - /// Static variables A-Z - sta_va: [Param; 26], - /// Dynamic variables a-z - dyn_va: [Param; 26], -} - -impl Variables { - /// Returns a new zero-initialized Variables - pub fn new() -> Variables { - Variables { - sta_va: [ - Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0), Number(0), Number(0) - ], - dyn_va: [ - Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0), Number(0), Number(0) - ], - } - } -} - -/// Expand a parameterized capability -/// -/// # Arguments -/// * `cap` - string to expand -/// * `params` - vector of params for %p1 etc -/// * `vars` - Variables struct for %Pa etc -/// -/// To be compatible with ncurses, `vars` should be the same between calls to `expand` for -/// multiple capabilities for the same terminal. -pub fn expand(cap: &[u8], params: &[Param], vars: &mut Variables) -> Result, String> { - let mut state = Nothing; - - // expanded cap will only rarely be larger than the cap itself - let mut output = Vec::with_capacity(cap.len()); - - let mut stack: Vec = Vec::new(); - - // Copy parameters into a local vector for mutability - let mut mparams = [Number(0), Number(0), Number(0), Number(0), Number(0), Number(0), - Number(0), Number(0), Number(0)]; - for (dst, src) in mparams.iter_mut().zip(params.iter()) { - *dst = (*src).clone(); - } - - for &c in cap.iter() { - let cur = c as char; - let mut old_state = state; - match state { - Nothing => { - if cur == '%' { - state = Percent; - } else { - output.push(c); - } - } - Percent => { - match cur { - '%' => { - output.push(c); - state = Nothing - } - 'c' => { - match stack.pop() { - // if c is 0, use 0200 (128) for ncurses compatibility - Some(Number(0)) => output.push(128u8), - // Don't check bounds. ncurses just casts and truncates. - Some(Number(c)) => output.push(c as u8), - Some(_) => return Err("a non-char was used with %c".to_string()), - None => return Err("stack is empty".to_string()), - } - } - 'p' => state = PushParam, - 'P' => state = SetVar, - 'g' => state = GetVar, - '\'' => state = CharConstant, - '{' => state = IntConstant(0), - 'l' => { - match stack.pop() { - Some(Words(s)) => stack.push(Number(s.len() as i32)), - Some(_) => return Err("a non-str was used with %l".to_string()), - None => return Err("stack is empty".to_string()), - } - } - '+' | '-' | '/' | '*' | '^' | '&' | '|' | 'm' => { - match (stack.pop(), stack.pop()) { - (Some(Number(y)), Some(Number(x))) => { - stack.push(Number(match cur { - '+' => x + y, - '-' => x - y, - '*' => x * y, - '/' => x / y, - '|' => x | y, - '&' => x & y, - '^' => x ^ y, - 'm' => x % y, - _ => unreachable!("All cases handled"), - })) - } - (Some(_), Some(_)) => { - return Err(format!("non-numbers on stack with {}", cur)) - } - _ => return Err("stack is empty".to_string()), - } - } - '=' | '>' | '<' | 'A' | 'O' => { - match (stack.pop(), stack.pop()) { - (Some(Number(y)), Some(Number(x))) => { - stack.push(Number(if match cur { - '=' => x == y, - '<' => x < y, - '>' => x > y, - 'A' => x > 0 && y > 0, - 'O' => x > 0 || y > 0, - _ => unreachable!(), - } { - 1 - } else { - 0 - })) - } - (Some(_), Some(_)) => { - return Err(format!("non-numbers on stack with {}", cur)) - } - _ => return Err("stack is empty".to_string()), - } - } - '!' | '~' => { - match stack.pop() { - Some(Number(x)) => { - stack.push(Number(match cur { - '!' if x > 0 => 0, - '!' => 1, - '~' => !x, - _ => unreachable!(), - })) - } - Some(_) => return Err(format!("non-numbers on stack with {}", cur)), - None => return Err("stack is empty".to_string()), - } - } - 'i' => { - match (&mparams[0], &mparams[1]) { - (&Number(x), &Number(y)) => { - mparams[0] = Number(x + 1); - mparams[1] = Number(y + 1); - } - _ => { - return Err("first two params not numbers with %i".to_string()) - } - } - } - - // printf-style support for %doxXs - 'd' | 'o' | 'x' | 'X' | 's' => { - if let Some(arg) = stack.pop() { - let flags = Flags::new(); - let res = format(arg, FormatOp::from_char(cur), flags)?; - output.extend(res.iter().cloned()); - } else { - return Err("stack is empty".to_string()); - } - } - ':' | '#' | ' ' | '.' | '0'..='9' => { - let mut flags = Flags::new(); - let mut fstate = FormatState::Flags; - match cur { - ':' => (), - '#' => flags.alternate = true, - ' ' => flags.space = true, - '.' => fstate = FormatState::Precision, - '0'..='9' => { - flags.width = cur as usize - '0' as usize; - fstate = FormatState::Width; - } - _ => unreachable!(), - } - state = FormatPattern(flags, fstate); - } - - // conditionals - '?' => (), - 't' => { - match stack.pop() { - Some(Number(0)) => state = SeekIfElse(0), - Some(Number(_)) => (), - Some(_) => { - return Err("non-number on stack with conditional".to_string()) - } - None => return Err("stack is empty".to_string()), - } - } - 'e' => state = SeekIfEnd(0), - ';' => (), - _ => return Err(format!("unrecognized format option {}", cur)), - } - } - PushParam => { - // params are 1-indexed - stack.push(mparams[match cur.to_digit(10) { - Some(d) => d as usize - 1, - None => return Err("bad param number".to_string()), - }] - .clone()); - } - SetVar => { - if cur >= 'A' && cur <= 'Z' { - if let Some(arg) = stack.pop() { - let idx = (cur as u8) - b'A'; - vars.sta_va[idx as usize] = arg; - } else { - return Err("stack is empty".to_string()); - } - } else if cur >= 'a' && cur <= 'z' { - if let Some(arg) = stack.pop() { - let idx = (cur as u8) - b'a'; - vars.dyn_va[idx as usize] = arg; - } else { - return Err("stack is empty".to_string()); - } - } else { - return Err("bad variable name in %P".to_string()); - } - } - GetVar => { - if cur >= 'A' && cur <= 'Z' { - let idx = (cur as u8) - b'A'; - stack.push(vars.sta_va[idx as usize].clone()); - } else if cur >= 'a' && cur <= 'z' { - let idx = (cur as u8) - b'a'; - stack.push(vars.dyn_va[idx as usize].clone()); - } else { - return Err("bad variable name in %g".to_string()); - } - } - CharConstant => { - stack.push(Number(c as i32)); - state = CharClose; - } - CharClose => { - if cur != '\'' { - return Err("malformed character constant".to_string()); - } - } - IntConstant(i) => { - if cur == '}' { - stack.push(Number(i)); - state = Nothing; - } else if let Some(digit) = cur.to_digit(10) { - match i.checked_mul(10).and_then(|i_ten| i_ten.checked_add(digit as i32)) { - Some(i) => { - state = IntConstant(i); - old_state = Nothing; - } - None => return Err("int constant too large".to_string()), - } - } else { - return Err("bad int constant".to_string()); - } - } - FormatPattern(ref mut flags, ref mut fstate) => { - old_state = Nothing; - match (*fstate, cur) { - (_, 'd') | (_, 'o') | (_, 'x') | (_, 'X') | (_, 's') => { - if let Some(arg) = stack.pop() { - let res = format(arg, FormatOp::from_char(cur), *flags)?; - output.extend(res.iter().cloned()); - // will cause state to go to Nothing - old_state = FormatPattern(*flags, *fstate); - } else { - return Err("stack is empty".to_string()); - } - } - (FormatState::Flags, '#') => { - flags.alternate = true; - } - (FormatState::Flags, '-') => { - flags.left = true; - } - (FormatState::Flags, '+') => { - flags.sign = true; - } - (FormatState::Flags, ' ') => { - flags.space = true; - } - (FormatState::Flags, '0'..='9') => { - flags.width = cur as usize - '0' as usize; - *fstate = FormatState::Width; - } - (FormatState::Flags, '.') => { - *fstate = FormatState::Precision; - } - (FormatState::Width, '0'..='9') => { - let old = flags.width; - flags.width = flags.width * 10 + (cur as usize - '0' as usize); - if flags.width < old { - return Err("format width overflow".to_string()); - } - } - (FormatState::Width, '.') => { - *fstate = FormatState::Precision; - } - (FormatState::Precision, '0'..='9') => { - let old = flags.precision; - flags.precision = flags.precision * 10 + (cur as usize - '0' as usize); - if flags.precision < old { - return Err("format precision overflow".to_string()); - } - } - _ => return Err("invalid format specifier".to_string()), - } - } - SeekIfElse(level) => { - if cur == '%' { - state = SeekIfElsePercent(level); - } - old_state = Nothing; - } - SeekIfElsePercent(level) => { - if cur == ';' { - if level == 0 { - state = Nothing; - } else { - state = SeekIfElse(level - 1); - } - } else if cur == 'e' && level == 0 { - state = Nothing; - } else if cur == '?' { - state = SeekIfElse(level + 1); - } else { - state = SeekIfElse(level); - } - } - SeekIfEnd(level) => { - if cur == '%' { - state = SeekIfEndPercent(level); - } - old_state = Nothing; - } - SeekIfEndPercent(level) => { - if cur == ';' { - if level == 0 { - state = Nothing; - } else { - state = SeekIfEnd(level - 1); - } - } else if cur == '?' { - state = SeekIfEnd(level + 1); - } else { - state = SeekIfEnd(level); - } - } - } - if state == old_state { - state = Nothing; - } - } - Ok(output) -} - -#[derive(Copy, PartialEq, Clone)] -struct Flags { - width: usize, - precision: usize, - alternate: bool, - left: bool, - sign: bool, - space: bool, -} - -impl Flags { - fn new() -> Flags { - Flags { - width: 0, - precision: 0, - alternate: false, - left: false, - sign: false, - space: false, - } - } -} - -#[derive(Copy, Clone)] -enum FormatOp { - Digit, - Octal, - LowerHex, - UpperHex, - String, -} - -impl FormatOp { - fn from_char(c: char) -> FormatOp { - match c { - 'd' => FormatOp::Digit, - 'o' => FormatOp::Octal, - 'x' => FormatOp::LowerHex, - 'X' => FormatOp::UpperHex, - 's' => FormatOp::String, - _ => panic!("bad FormatOp char"), - } - } - fn to_char(self) -> char { - match self { - FormatOp::Digit => 'd', - FormatOp::Octal => 'o', - FormatOp::LowerHex => 'x', - FormatOp::UpperHex => 'X', - FormatOp::String => 's', - } - } -} - -fn format(val: Param, op: FormatOp, flags: Flags) -> Result, String> { - let mut s = match val { - Number(d) => { - match op { - FormatOp::Digit => { - if flags.sign { - format!("{:+01$}", d, flags.precision) - } else if d < 0 { - // C doesn't take sign into account in precision calculation. - format!("{:01$}", d, flags.precision + 1) - } else if flags.space { - format!(" {:01$}", d, flags.precision) - } else { - format!("{:01$}", d, flags.precision) - } - } - FormatOp::Octal => { - if flags.alternate { - // Leading octal zero counts against precision. - format!("0{:01$o}", d, flags.precision.saturating_sub(1)) - } else { - format!("{:01$o}", d, flags.precision) - } - } - FormatOp::LowerHex => { - if flags.alternate && d != 0 { - format!("0x{:01$x}", d, flags.precision) - } else { - format!("{:01$x}", d, flags.precision) - } - } - FormatOp::UpperHex => { - if flags.alternate && d != 0 { - format!("0X{:01$X}", d, flags.precision) - } else { - format!("{:01$X}", d, flags.precision) - } - } - FormatOp::String => return Err("non-number on stack with %s".to_string()), - } - .into_bytes() - } - Words(s) => { - match op { - FormatOp::String => { - let mut s = s.into_bytes(); - if flags.precision > 0 && flags.precision < s.len() { - s.truncate(flags.precision); - } - s - } - _ => return Err(format!("non-string on stack with %{}", op.to_char())), - } - } - }; - if flags.width > s.len() { - let n = flags.width - s.len(); - if flags.left { - s.extend(repeat(b' ').take(n)); - } else { - let mut s_ = Vec::with_capacity(flags.width); - s_.extend(repeat(b' ').take(n)); - s_.extend(s.into_iter()); - s = s_; - } - } - Ok(s) -} - -#[cfg(test)] -mod test { - use super::{expand, Variables}; - use super::Param::{self, Words, Number}; - use std::result::Result::Ok; - - #[test] - fn test_basic_setabf() { - let s = b"\\E[48;5;%p1%dm"; - assert_eq!(expand(s, &[Number(1)], &mut Variables::new()).unwrap(), - "\\E[48;5;1m".bytes().collect::>()); - } - - #[test] - fn test_multiple_int_constants() { - assert_eq!(expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(), - "21".bytes().collect::>()); - } - - #[test] - fn test_op_i() { - let mut vars = Variables::new(); - assert_eq!(expand(b"%p1%d%p2%d%p3%d%i%p1%d%p2%d%p3%d", - &[Number(1), Number(2), Number(3)], - &mut vars), - Ok("123233".bytes().collect::>())); - assert_eq!(expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars), - Ok("0011".bytes().collect::>())); - } - - #[test] - fn test_param_stack_failure_conditions() { - let mut varstruct = Variables::new(); - let vars = &mut varstruct; - fn get_res(fmt: &str, - cap: &str, - params: &[Param], - vars: &mut Variables) - -> Result, String> { - let mut u8v: Vec<_> = fmt.bytes().collect(); - u8v.extend(cap.as_bytes().iter().map(|&b| b)); - expand(&u8v, params, vars) - } - - let caps = ["%d", "%c", "%s", "%Pa", "%l", "%!", "%~"]; - for &cap in caps.iter() { - let res = get_res("", cap, &[], vars); - assert!(res.is_err(), - "Op {} succeeded incorrectly with 0 stack entries", - cap); - let p = if cap == "%s" || cap == "%l" { - Words("foo".to_string()) - } else { - Number(97) - }; - let res = get_res("%p1", cap, &[p], vars); - assert!(res.is_ok(), - "Op {} failed with 1 stack entry: {}", - cap, - res.unwrap_err()); - } - let caps = ["%+", "%-", "%*", "%/", "%m", "%&", "%|", "%A", "%O"]; - for &cap in caps.iter() { - let res = expand(cap.as_bytes(), &[], vars); - assert!(res.is_err(), - "Binop {} succeeded incorrectly with 0 stack entries", - cap); - let res = get_res("%{1}", cap, &[], vars); - assert!(res.is_err(), - "Binop {} succeeded incorrectly with 1 stack entry", - cap); - let res = get_res("%{1}%{2}", cap, &[], vars); - assert!(res.is_ok(), - "Binop {} failed with 2 stack entries: {}", - cap, - res.unwrap_err()); - } - } - - #[test] - fn test_push_bad_param() { - assert!(expand(b"%pa", &[], &mut Variables::new()).is_err()); - } - - #[test] - fn test_comparison_ops() { - let v = [('<', [1u8, 0u8, 0u8]), ('=', [0u8, 1u8, 0u8]), ('>', [0u8, 0u8, 1u8])]; - for &(op, bs) in v.iter() { - let s = format!("%{{1}}%{{2}}%{}%d", op); - let res = expand(s.as_bytes(), &[], &mut Variables::new()); - assert!(res.is_ok(), res.unwrap_err()); - assert_eq!(res.unwrap(), vec![b'0' + bs[0]]); - let s = format!("%{{1}}%{{1}}%{}%d", op); - let res = expand(s.as_bytes(), &[], &mut Variables::new()); - assert!(res.is_ok(), res.unwrap_err()); - assert_eq!(res.unwrap(), vec![b'0' + bs[1]]); - let s = format!("%{{2}}%{{1}}%{}%d", op); - let res = expand(s.as_bytes(), &[], &mut Variables::new()); - assert!(res.is_ok(), res.unwrap_err()); - assert_eq!(res.unwrap(), vec![b'0' + bs[2]]); - } - } - - #[test] - fn test_conditionals() { - let mut vars = Variables::new(); - let s = b"\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"; - let res = expand(s, &[Number(1)], &mut vars); - assert!(res.is_ok(), res.unwrap_err()); - assert_eq!(res.unwrap(), "\\E[31m".bytes().collect::>()); - let res = expand(s, &[Number(8)], &mut vars); - assert!(res.is_ok(), res.unwrap_err()); - assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::>()); - let res = expand(s, &[Number(42)], &mut vars); - assert!(res.is_ok(), res.unwrap_err()); - assert_eq!(res.unwrap(), "\\E[38;5;42m".bytes().collect::>()); - } - - #[test] - fn test_format() { - let mut varstruct = Variables::new(); - let vars = &mut varstruct; - assert_eq!(expand(b"%p1%s%p2%2s%p3%2s%p4%.2s", - &[Words("foo".to_string()), - Words("foo".to_string()), - Words("f".to_string()), - Words("foo".to_string())], - vars), - Ok("foofoo ffo".bytes().collect::>())); - assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars), - Ok("fo ".bytes().collect::>())); - - assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars), - Ok("1001 1+1".bytes().collect::>())); - assert_eq!(expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X", - &[Number(15), Number(27)], - vars), - Ok("17017 001b0X001B".bytes().collect::>())); - } -} diff --git a/src/libterm/terminfo/parser/compiled.rs b/src/libterm/terminfo/parser/compiled.rs deleted file mode 100644 index 05a8c9420ed..00000000000 --- a/src/libterm/terminfo/parser/compiled.rs +++ /dev/null @@ -1,346 +0,0 @@ -#![allow(non_upper_case_globals, missing_docs)] - -//! ncurses-compatible compiled terminfo format parsing (term(5)) - -use std::collections::HashMap; -use std::io; -use std::io::prelude::*; -use super::super::TermInfo; - -// These are the orders ncurses uses in its compiled format (as of 5.9). Not sure if portable. - -#[rustfmt::skip] -pub static boolfnames: &[&str] = &["auto_left_margin", "auto_right_margin", - "no_esc_ctlc", "ceol_standout_glitch", "eat_newline_glitch", "erase_overstrike", "generic_type", - "hard_copy", "has_meta_key", "has_status_line", "insert_null_glitch", "memory_above", - "memory_below", "move_insert_mode", "move_standout_mode", "over_strike", "status_line_esc_ok", - "dest_tabs_magic_smso", "tilde_glitch", "transparent_underline", "xon_xoff", "needs_xon_xoff", - "prtr_silent", "hard_cursor", "non_rev_rmcup", "no_pad_char", "non_dest_scroll_region", - "can_change", "back_color_erase", "hue_lightness_saturation", "col_addr_glitch", - "cr_cancels_micro_mode", "has_print_wheel", "row_addr_glitch", "semi_auto_right_margin", - "cpi_changes_res", "lpi_changes_res", "backspaces_with_bs", "crt_no_scrolling", - "no_correctly_working_cr", "gnu_has_meta_key", "linefeed_is_newline", "has_hardware_tabs", - "return_does_clr_eol"]; - -#[rustfmt::skip] -pub static boolnames: &[&str] = &["bw", "am", "xsb", "xhp", "xenl", "eo", - "gn", "hc", "km", "hs", "in", "db", "da", "mir", "msgr", "os", "eslok", "xt", "hz", "ul", "xon", - "nxon", "mc5i", "chts", "nrrmc", "npc", "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy", - "xvpa", "sam", "cpix", "lpix", "OTbs", "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr"]; - -#[rustfmt::skip] -pub static numfnames: &[&str] = &[ "columns", "init_tabs", "lines", - "lines_of_memory", "magic_cookie_glitch", "padding_baud_rate", "virtual_terminal", - "width_status_line", "num_labels", "label_height", "label_width", "max_attributes", - "maximum_windows", "max_colors", "max_pairs", "no_color_video", "buffer_capacity", - "dot_vert_spacing", "dot_horz_spacing", "max_micro_address", "max_micro_jump", "micro_col_size", - "micro_line_size", "number_of_pins", "output_res_char", "output_res_line", - "output_res_horz_inch", "output_res_vert_inch", "print_rate", "wide_char_size", "buttons", - "bit_image_entwining", "bit_image_type", "magic_cookie_glitch_ul", "carriage_return_delay", - "new_line_delay", "backspace_delay", "horizontal_tab_delay", "number_of_function_keys"]; - -#[rustfmt::skip] -pub static numnames: &[&str] = &[ "cols", "it", "lines", "lm", "xmc", "pb", - "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum", "colors", "pairs", "ncv", "bufsz", "spinv", - "spinh", "maddr", "mjump", "mcs", "mls", "npins", "orc", "orl", "orhi", "orvi", "cps", "widcs", - "btns", "bitwin", "bitype", "UTug", "OTdC", "OTdN", "OTdB", "OTdT", "OTkn"]; - -#[rustfmt::skip] -pub static stringfnames: &[&str] = &[ "back_tab", "bell", "carriage_return", - "change_scroll_region", "clear_all_tabs", "clear_screen", "clr_eol", "clr_eos", - "column_address", "command_character", "cursor_address", "cursor_down", "cursor_home", - "cursor_invisible", "cursor_left", "cursor_mem_address", "cursor_normal", "cursor_right", - "cursor_to_ll", "cursor_up", "cursor_visible", "delete_character", "delete_line", - "dis_status_line", "down_half_line", "enter_alt_charset_mode", "enter_blink_mode", - "enter_bold_mode", "enter_ca_mode", "enter_delete_mode", "enter_dim_mode", "enter_insert_mode", - "enter_secure_mode", "enter_protected_mode", "enter_reverse_mode", "enter_standout_mode", - "enter_underline_mode", "erase_chars", "exit_alt_charset_mode", "exit_attribute_mode", - "exit_ca_mode", "exit_delete_mode", "exit_insert_mode", "exit_standout_mode", - "exit_underline_mode", "flash_screen", "form_feed", "from_status_line", "init_1string", - "init_2string", "init_3string", "init_file", "insert_character", "insert_line", - "insert_padding", "key_backspace", "key_catab", "key_clear", "key_ctab", "key_dc", "key_dl", - "key_down", "key_eic", "key_eol", "key_eos", "key_f0", "key_f1", "key_f10", "key_f2", "key_f3", - "key_f4", "key_f5", "key_f6", "key_f7", "key_f8", "key_f9", "key_home", "key_ic", "key_il", - "key_left", "key_ll", "key_npage", "key_ppage", "key_right", "key_sf", "key_sr", "key_stab", - "key_up", "keypad_local", "keypad_xmit", "lab_f0", "lab_f1", "lab_f10", "lab_f2", "lab_f3", - "lab_f4", "lab_f5", "lab_f6", "lab_f7", "lab_f8", "lab_f9", "meta_off", "meta_on", "newline", - "pad_char", "parm_dch", "parm_delete_line", "parm_down_cursor", "parm_ich", "parm_index", - "parm_insert_line", "parm_left_cursor", "parm_right_cursor", "parm_rindex", "parm_up_cursor", - "pkey_key", "pkey_local", "pkey_xmit", "print_screen", "prtr_off", "prtr_on", "repeat_char", - "reset_1string", "reset_2string", "reset_3string", "reset_file", "restore_cursor", - "row_address", "save_cursor", "scroll_forward", "scroll_reverse", "set_attributes", "set_tab", - "set_window", "tab", "to_status_line", "underline_char", "up_half_line", "init_prog", "key_a1", - "key_a3", "key_b2", "key_c1", "key_c3", "prtr_non", "char_padding", "acs_chars", "plab_norm", - "key_btab", "enter_xon_mode", "exit_xon_mode", "enter_am_mode", "exit_am_mode", "xon_character", - "xoff_character", "ena_acs", "label_on", "label_off", "key_beg", "key_cancel", "key_close", - "key_command", "key_copy", "key_create", "key_end", "key_enter", "key_exit", "key_find", - "key_help", "key_mark", "key_message", "key_move", "key_next", "key_open", "key_options", - "key_previous", "key_print", "key_redo", "key_reference", "key_refresh", "key_replace", - "key_restart", "key_resume", "key_save", "key_suspend", "key_undo", "key_sbeg", "key_scancel", - "key_scommand", "key_scopy", "key_screate", "key_sdc", "key_sdl", "key_select", "key_send", - "key_seol", "key_sexit", "key_sfind", "key_shelp", "key_shome", "key_sic", "key_sleft", - "key_smessage", "key_smove", "key_snext", "key_soptions", "key_sprevious", "key_sprint", - "key_sredo", "key_sreplace", "key_sright", "key_srsume", "key_ssave", "key_ssuspend", - "key_sundo", "req_for_input", "key_f11", "key_f12", "key_f13", "key_f14", "key_f15", "key_f16", - "key_f17", "key_f18", "key_f19", "key_f20", "key_f21", "key_f22", "key_f23", "key_f24", - "key_f25", "key_f26", "key_f27", "key_f28", "key_f29", "key_f30", "key_f31", "key_f32", - "key_f33", "key_f34", "key_f35", "key_f36", "key_f37", "key_f38", "key_f39", "key_f40", - "key_f41", "key_f42", "key_f43", "key_f44", "key_f45", "key_f46", "key_f47", "key_f48", - "key_f49", "key_f50", "key_f51", "key_f52", "key_f53", "key_f54", "key_f55", "key_f56", - "key_f57", "key_f58", "key_f59", "key_f60", "key_f61", "key_f62", "key_f63", "clr_bol", - "clear_margins", "set_left_margin", "set_right_margin", "label_format", "set_clock", - "display_clock", "remove_clock", "create_window", "goto_window", "hangup", "dial_phone", - "quick_dial", "tone", "pulse", "flash_hook", "fixed_pause", "wait_tone", "user0", "user1", - "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "orig_pair", - "orig_colors", "initialize_color", "initialize_pair", "set_color_pair", "set_foreground", - "set_background", "change_char_pitch", "change_line_pitch", "change_res_horz", - "change_res_vert", "define_char", "enter_doublewide_mode", "enter_draft_quality", - "enter_italics_mode", "enter_leftward_mode", "enter_micro_mode", "enter_near_letter_quality", - "enter_normal_quality", "enter_shadow_mode", "enter_subscript_mode", "enter_superscript_mode", - "enter_upward_mode", "exit_doublewide_mode", "exit_italics_mode", "exit_leftward_mode", - "exit_micro_mode", "exit_shadow_mode", "exit_subscript_mode", "exit_superscript_mode", - "exit_upward_mode", "micro_column_address", "micro_down", "micro_left", "micro_right", - "micro_row_address", "micro_up", "order_of_pins", "parm_down_micro", "parm_left_micro", - "parm_right_micro", "parm_up_micro", "select_char_set", "set_bottom_margin", - "set_bottom_margin_parm", "set_left_margin_parm", "set_right_margin_parm", "set_top_margin", - "set_top_margin_parm", "start_bit_image", "start_char_set_def", "stop_bit_image", - "stop_char_set_def", "subscript_characters", "superscript_characters", "these_cause_cr", - "zero_motion", "char_set_names", "key_mouse", "mouse_info", "req_mouse_pos", "get_mouse", - "set_a_foreground", "set_a_background", "pkey_plab", "device_type", "code_set_init", - "set0_des_seq", "set1_des_seq", "set2_des_seq", "set3_des_seq", "set_lr_margin", - "set_tb_margin", "bit_image_repeat", "bit_image_newline", "bit_image_carriage_return", - "color_names", "define_bit_image_region", "end_bit_image_region", "set_color_band", - "set_page_length", "display_pc_char", "enter_pc_charset_mode", "exit_pc_charset_mode", - "enter_scancode_mode", "exit_scancode_mode", "pc_term_options", "scancode_escape", - "alt_scancode_esc", "enter_horizontal_hl_mode", "enter_left_hl_mode", "enter_low_hl_mode", - "enter_right_hl_mode", "enter_top_hl_mode", "enter_vertical_hl_mode", "set_a_attributes", - "set_pglen_inch", "termcap_init2", "termcap_reset", "linefeed_if_not_lf", "backspace_if_not_bs", - "other_non_function_keys", "arrow_key_map", "acs_ulcorner", "acs_llcorner", "acs_urcorner", - "acs_lrcorner", "acs_ltee", "acs_rtee", "acs_btee", "acs_ttee", "acs_hline", "acs_vline", - "acs_plus", "memory_lock", "memory_unlock", "box_chars_1"]; - -#[rustfmt::skip] -pub static stringnames: &[&str] = &[ "cbt", "_", "cr", "csr", "tbc", "clear", - "_", "_", "hpa", "cmdch", "cup", "cud1", "home", "civis", "cub1", "mrcup", "cnorm", "cuf1", - "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd", "smacs", "blink", "bold", "smcup", "smdc", - "dim", "smir", "invis", "prot", "rev", "smso", "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc", - "rmir", "rmso", "rmul", "flash", "ff", "fsl", "is1", "is2", "is3", "if", "ich1", "il1", "ip", - "kbs", "ktbc", "kclr", "kctab", "_", "_", "kcud1", "_", "_", "_", "_", "_", "_", "_", "_", "_", - "_", "_", "_", "_", "_", "khome", "_", "_", "kcub1", "_", "knp", "kpp", "kcuf1", "_", "_", - "khts", "_", "rmkx", "smkx", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "rmm", "_", - "_", "pad", "dch", "dl", "cud", "ich", "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey", - "pfloc", "pfx", "mc0", "mc4", "_", "rep", "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind", - "ri", "sgr", "_", "wind", "_", "tsl", "uc", "hu", "iprog", "_", "_", "_", "_", "_", "mc5p", - "rmp", "acsc", "pln", "kcbt", "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "_", "smln", - "rmln", "_", "kcan", "kclo", "kcmd", "kcpy", "kcrt", "_", "kent", "kext", "kfnd", "khlp", - "kmrk", "kmsg", "kmov", "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl", - "krst", "kres", "ksav", "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "_", "_", - "kslt", "kEND", "kEOL", "kEXT", "kFND", "kHLP", "kHOM", "_", "kLFT", "kMSG", "kMOV", "kNXT", - "kOPT", "kPRV", "kPRT", "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "_", "_", - "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", - "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", - "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", - "dclk", "rmclk", "cwin", "wingo", "_", "dial", "qdial", "_", "_", "hook", "pause", "wait", "_", - "_", "_", "_", "_", "_", "_", "_", "_", "_", "op", "oc", "initc", "initp", "scp", "setf", - "setb", "cpi", "lpi", "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq", - "snrmq", "sshm", "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm", - "rsupm", "rum", "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub", - "mcuf", "mcuu", "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd", - "rbim", "rcsd", "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm", - "setaf", "setab", "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb", - "birep", "binel", "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch", - "rmpch", "smsc", "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm", - "ethlm", "evhlm", "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2", - "OTG3", "OTG1", "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu", - "box1"]; - -fn read_le_u16(r: &mut dyn io::Read) -> io::Result { - let mut b = [0; 2]; - let mut amt = 0; - while amt < b.len() { - match r.read(&mut b[amt..])? { - 0 => return Err(io::Error::new(io::ErrorKind::Other, "end of file")), - n => amt += n, - } - } - Ok((b[0] as u16) | ((b[1] as u16) << 8)) -} - -fn read_byte(r: &mut dyn io::Read) -> io::Result { - match r.bytes().next() { - Some(s) => s, - None => Err(io::Error::new(io::ErrorKind::Other, "end of file")), - } -} - -/// Parse a compiled terminfo entry, using long capability names if `longnames` -/// is true -pub fn parse(file: &mut dyn io::Read, longnames: bool) -> Result { - macro_rules! t( ($e:expr) => ( - match $e { - Ok(e) => e, - Err(e) => return Err(e.to_string()) - } - ) ); - - let (bnames, snames, nnames) = if longnames { - (boolfnames, stringfnames, numfnames) - } else { - (boolnames, stringnames, numnames) - }; - - // Check magic number - let magic = t!(read_le_u16(file)); - if magic != 0x011A { - return Err(format!("invalid magic number: expected {:x}, found {:x}", - 0x011A, - magic)); - } - - // According to the spec, these fields must be >= -1 where -1 means that the feature is not - // supported. Using 0 instead of -1 works because we skip sections with length 0. - macro_rules! read_nonneg { - () => {{ - match t!(read_le_u16(file)) as i16 { - n if n >= 0 => n as usize, - -1 => 0, - _ => return Err("incompatible file: length fields must be >= -1".to_string()), - } - }} - } - - let names_bytes = read_nonneg!(); - let bools_bytes = read_nonneg!(); - let numbers_count = read_nonneg!(); - let string_offsets_count = read_nonneg!(); - let string_table_bytes = read_nonneg!(); - - if names_bytes == 0 { - return Err("incompatible file: names field must be at least 1 byte wide".to_string()); - } - - if bools_bytes > boolnames.len() { - return Err("incompatible file: more booleans than expected".to_string()); - } - - if numbers_count > numnames.len() { - return Err("incompatible file: more numbers than expected".to_string()); - } - - if string_offsets_count > stringnames.len() { - return Err("incompatible file: more string offsets than expected".to_string()); - } - - // don't read NUL - let mut bytes = Vec::new(); - t!(file.take((names_bytes - 1) as u64).read_to_end(&mut bytes)); - let names_str = match String::from_utf8(bytes) { - Ok(s) => s, - Err(_) => return Err("input not utf-8".to_string()), - }; - - let term_names: Vec = names_str.split('|') - .map(|s| s.to_string()) - .collect(); - // consume NUL - if t!(read_byte(file)) != b'\0' { - return Err("incompatible file: missing null terminator for names section".to_string()); - } - - let bools_map: HashMap = t! { - (0..bools_bytes).filter_map(|i| match read_byte(file) { - Err(e) => Some(Err(e)), - Ok(1) => Some(Ok((bnames[i].to_string(), true))), - Ok(_) => None - }).collect() - }; - - if (bools_bytes + names_bytes) % 2 == 1 { - t!(read_byte(file)); // compensate for padding - } - - let numbers_map: HashMap = t! { - (0..numbers_count).filter_map(|i| match read_le_u16(file) { - Ok(0xFFFF) => None, - Ok(n) => Some(Ok((nnames[i].to_string(), n))), - Err(e) => Some(Err(e)) - }).collect() - }; - - let string_map: HashMap> = if string_offsets_count > 0 { - let string_offsets: Vec = t!((0..string_offsets_count) - .map(|_| read_le_u16(file)) - .collect()); - - let mut string_table = Vec::new(); - t!(file.take(string_table_bytes as u64).read_to_end(&mut string_table)); - - t!(string_offsets.into_iter().enumerate().filter(|&(_, offset)| { - // non-entry - offset != 0xFFFF - }).map(|(i, offset)| { - let offset = offset as usize; - - let name = if snames[i] == "_" { - stringfnames[i] - } else { - snames[i] - }; - - if offset == 0xFFFE { - // undocumented: FFFE indicates cap@, which means the capability is not present - // unsure if the handling for this is correct - return Ok((name.to_string(), Vec::new())); - } - - // Find the offset of the NUL we want to go to - let nulpos = string_table[offset..string_table_bytes].iter().position(|&b| b == 0); - match nulpos { - Some(len) => Ok((name.to_string(), string_table[offset..offset + len].to_vec())), - None => Err("invalid file: missing NUL in string_table".to_string()), - } - }).collect()) - } else { - HashMap::new() - }; - - // And that's all there is to it - Ok(TermInfo { - names: term_names, - bools: bools_map, - numbers: numbers_map, - strings: string_map, - }) -} - -/// Creates a dummy TermInfo struct for msys terminals -pub fn msys_terminfo() -> TermInfo { - let mut strings = HashMap::new(); - strings.insert("sgr0".to_string(), b"\x1B[0m".to_vec()); - strings.insert("bold".to_string(), b"\x1B[1m".to_vec()); - strings.insert("setaf".to_string(), b"\x1B[3%p1%dm".to_vec()); - strings.insert("setab".to_string(), b"\x1B[4%p1%dm".to_vec()); - - let mut numbers = HashMap::new(); - numbers.insert("colors".to_string(), 8u16); - - TermInfo { - names: vec!["cygwin".to_string()], // msys is a fork of an older cygwin version - bools: HashMap::new(), - numbers, - strings, - } -} - -#[cfg(test)] -mod test { - - use super::{boolnames, boolfnames, numnames, numfnames, stringnames, stringfnames}; - - #[test] - fn test_veclens() { - assert_eq!(boolfnames.len(), boolnames.len()); - assert_eq!(numfnames.len(), numnames.len()); - assert_eq!(stringfnames.len(), stringnames.len()); - } -} diff --git a/src/libterm/terminfo/searcher.rs b/src/libterm/terminfo/searcher.rs deleted file mode 100644 index 0b17ed36fc8..00000000000 --- a/src/libterm/terminfo/searcher.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! ncurses-compatible database discovery. -//! -//! Does not support hashed database, only filesystem! - -use std::env; -use std::fs; -use std::path::PathBuf; - -/// Return path to database entry for `term` -#[allow(deprecated)] -pub fn get_dbpath_for_term(term: &str) -> Option { - let mut dirs_to_search = Vec::new(); - let first_char = term.chars().next()?; - - // Find search directory - if let Some(dir) = env::var_os("TERMINFO") { - dirs_to_search.push(PathBuf::from(dir)); - } - - if let Ok(dirs) = env::var("TERMINFO_DIRS") { - for i in dirs.split(':') { - if i == "" { - dirs_to_search.push(PathBuf::from("/usr/share/terminfo")); - } else { - dirs_to_search.push(PathBuf::from(i)); - } - } - } else { - // Found nothing in TERMINFO_DIRS, use the default paths: - // According to /etc/terminfo/README, after looking at - // ~/.terminfo, ncurses will search /etc/terminfo, then - // /lib/terminfo, and eventually /usr/share/terminfo. - // On Haiku the database can be found at /boot/system/data/terminfo - if let Some(mut homedir) = env::home_dir() { - homedir.push(".terminfo"); - dirs_to_search.push(homedir) - } - - dirs_to_search.push(PathBuf::from("/etc/terminfo")); - dirs_to_search.push(PathBuf::from("/lib/terminfo")); - dirs_to_search.push(PathBuf::from("/usr/share/terminfo")); - dirs_to_search.push(PathBuf::from("/boot/system/data/terminfo")); - } - - // Look for the terminal in all of the search directories - for mut p in dirs_to_search { - if fs::metadata(&p).is_ok() { - p.push(&first_char.to_string()); - p.push(&term); - if fs::metadata(&p).is_ok() { - return Some(p); - } - p.pop(); - p.pop(); - - // on some installations the dir is named after the hex of the char - // (e.g., macOS) - p.push(&format!("{:x}", first_char as usize)); - p.push(term); - if fs::metadata(&p).is_ok() { - return Some(p); - } - } - } - None -} - -#[test] -#[ignore = "buildbots don't have ncurses installed and I can't mock everything I need"] -fn test_get_dbpath_for_term() { - // woefully inadequate test coverage - // note: current tests won't work with non-standard terminfo hierarchies (e.g., macOS's) - use std::env; - // FIXME (#9639): This needs to handle non-utf8 paths - fn x(t: &str) -> String { - let p = get_dbpath_for_term(t).expect("no terminfo entry found"); - p.to_str().unwrap().to_string() - } - assert!(x("screen") == "/usr/share/terminfo/s/screen"); - assert!(get_dbpath_for_term("") == None); - env::set_var("TERMINFO_DIRS", ":"); - assert!(x("screen") == "/usr/share/terminfo/s/screen"); - env::remove_var("TERMINFO_DIRS"); -} diff --git a/src/libterm/win.rs b/src/libterm/win.rs deleted file mode 100644 index 6d42b01337e..00000000000 --- a/src/libterm/win.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Windows console handling - -// FIXME (#13400): this is only a tiny fraction of the Windows console api - -extern crate libc; - -use std::io; -use std::io::prelude::*; - -use crate::Attr; -use crate::color; -use crate::Terminal; - -/// A Terminal implementation that uses the Win32 Console API. -pub struct WinConsole { - buf: T, - def_foreground: color::Color, - def_background: color::Color, - foreground: color::Color, - background: color::Color, -} - -type WORD = u16; -type DWORD = u32; -type BOOL = i32; -type HANDLE = *mut u8; - -#[allow(non_snake_case)] -#[repr(C)] -struct CONSOLE_SCREEN_BUFFER_INFO { - dwSize: [libc::c_short; 2], - dwCursorPosition: [libc::c_short; 2], - wAttributes: WORD, - srWindow: [libc::c_short; 4], - dwMaximumWindowSize: [libc::c_short; 2], -} - -#[allow(non_snake_case)] -#[link(name = "kernel32")] -extern "system" { - fn SetConsoleTextAttribute(handle: HANDLE, attr: WORD) -> BOOL; - fn GetStdHandle(which: DWORD) -> HANDLE; - fn GetConsoleScreenBufferInfo(handle: HANDLE, info: *mut CONSOLE_SCREEN_BUFFER_INFO) -> BOOL; -} - -fn color_to_bits(color: color::Color) -> u16 { - // magic numbers from mingw-w64's wincon.h - - let bits = match color % 8 { - color::BLACK => 0, - color::BLUE => 0x1, - color::GREEN => 0x2, - color::RED => 0x4, - color::YELLOW => 0x2 | 0x4, - color::MAGENTA => 0x1 | 0x4, - color::CYAN => 0x1 | 0x2, - color::WHITE => 0x1 | 0x2 | 0x4, - _ => unreachable!(), - }; - - if color >= 8 { - bits | 0x8 - } else { - bits - } -} - -fn bits_to_color(bits: u16) -> color::Color { - let color = match bits & 0x7 { - 0 => color::BLACK, - 0x1 => color::BLUE, - 0x2 => color::GREEN, - 0x4 => color::RED, - 0x6 => color::YELLOW, - 0x5 => color::MAGENTA, - 0x3 => color::CYAN, - 0x7 => color::WHITE, - _ => unreachable!(), - }; - - color | (bits & 0x8) // copy the hi-intensity bit -} - -impl WinConsole { - fn apply(&mut self) { - let _unused = self.buf.flush(); - let mut accum: WORD = 0; - accum |= color_to_bits(self.foreground); - accum |= color_to_bits(self.background) << 4; - - unsafe { - // Magic -11 means stdout, from - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683231%28v=vs.85%29.aspx - // - // You may be wondering, "but what about stderr?", and the answer - // to that is that setting terminal attributes on the stdout - // handle also sets them for stderr, since they go to the same - // terminal! Admittedly, this is fragile, since stderr could be - // redirected to a different console. This is good enough for - // rustc though. See #13400. - let out = GetStdHandle(-11i32 as DWORD); - SetConsoleTextAttribute(out, accum); - } - } - - /// Returns `None` whenever the terminal cannot be created for some reason. - pub fn new(out: T) -> io::Result> { - let fg; - let bg; - unsafe { - let mut buffer_info = ::std::mem::uninitialized(); - if GetConsoleScreenBufferInfo(GetStdHandle(-11i32 as DWORD), &mut buffer_info) != 0 { - fg = bits_to_color(buffer_info.wAttributes); - bg = bits_to_color(buffer_info.wAttributes >> 4); - } else { - fg = color::WHITE; - bg = color::BLACK; - } - } - Ok(WinConsole { - buf: out, - def_foreground: fg, - def_background: bg, - foreground: fg, - background: bg, - }) - } -} - -impl Write for WinConsole { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.buf.flush() - } -} - -impl Terminal for WinConsole { - type Output = T; - - fn fg(&mut self, color: color::Color) -> io::Result { - self.foreground = color; - self.apply(); - - Ok(true) - } - - fn bg(&mut self, color: color::Color) -> io::Result { - self.background = color; - self.apply(); - - Ok(true) - } - - fn attr(&mut self, attr: Attr) -> io::Result { - match attr { - Attr::ForegroundColor(f) => { - self.foreground = f; - self.apply(); - Ok(true) - } - Attr::BackgroundColor(b) => { - self.background = b; - self.apply(); - Ok(true) - } - _ => Ok(false), - } - } - - fn supports_attr(&self, attr: Attr) -> bool { - // it claims support for underscore and reverse video, but I can't get - // it to do anything -cmr - match attr { - Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => true, - _ => false, - } - } - - fn reset(&mut self) -> io::Result { - self.foreground = self.def_foreground; - self.background = self.def_background; - self.apply(); - - Ok(true) - } - - fn get_ref(&self) -> &T { - &self.buf - } - - fn get_mut(&mut self) -> &mut T { - &mut self.buf - } - - fn into_inner(self) -> T - where Self: Sized - { - self.buf - } -} diff --git a/src/libtest/Cargo.toml b/src/libtest/Cargo.toml index 10bdd6e877c..a9a145c081f 100644 --- a/src/libtest/Cargo.toml +++ b/src/libtest/Cargo.toml @@ -10,8 +10,7 @@ path = "lib.rs" crate-type = ["dylib", "rlib"] [dependencies] -getopts = "0.2" -term = { path = "../libterm" } +libtest = { version = "0.0.0", git = "https://github.com/gnzlbg/libtest" } # not actually used but needed to always have proc_macro in the sysroot proc_macro = { path = "../libproc_macro" } diff --git a/src/libtest/formatters/json.rs b/src/libtest/formatters/json.rs deleted file mode 100644 index a06497f9862..00000000000 --- a/src/libtest/formatters/json.rs +++ /dev/null @@ -1,208 +0,0 @@ -use super::*; - -pub(crate) struct JsonFormatter { - out: OutputLocation, -} - -impl JsonFormatter { - pub fn new(out: OutputLocation) -> Self { - Self { out } - } - - fn write_message(&mut self, s: &str) -> io::Result<()> { - assert!(!s.contains('\n')); - - self.out.write_all(s.as_ref())?; - self.out.write_all(b"\n") - } - - fn write_event( - &mut self, - ty: &str, - name: &str, - evt: &str, - extra: Option, - ) -> io::Result<()> { - if let Some(extras) = extra { - self.write_message(&*format!( - r#"{{ "type": "{}", "name": "{}", "event": "{}", {} }}"#, - ty, name, evt, extras - )) - } else { - self.write_message(&*format!( - r#"{{ "type": "{}", "name": "{}", "event": "{}" }}"#, - ty, name, evt - )) - } - } -} - -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": {} }}"#, - test_count - )) - } - - fn write_test_start(&mut self, desc: &TestDesc) -> io::Result<()> { - self.write_message(&*format!( - r#"{{ "type": "test", "event": "started", "name": "{}" }}"#, - desc.name - )) - } - - fn write_result( - &mut self, - desc: &TestDesc, - result: &TestResult, - stdout: &[u8], - ) -> io::Result<()> { - match *result { - TrOk => self.write_event("test", desc.name.as_slice(), "ok", None), - - TrFailed => { - let extra_data = if stdout.len() > 0 { - Some(format!( - r#""stdout": "{}""#, - EscapedString(String::from_utf8_lossy(stdout)) - )) - } else { - None - }; - - self.write_event("test", desc.name.as_slice(), "failed", extra_data) - } - - TrFailedMsg(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), - - TrAllowedFail => { - self.write_event("test", desc.name.as_slice(), "allowed_failure", None) - } - - TrBench(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; - - let mbps = if bs.mb_s == 0 { - String::new() - } else { - format!(r#", "mib_per_second": {}"#, bs.mb_s) - }; - - let line = format!( - "{{ \"type\": \"bench\", \ - \"name\": \"{}\", \ - \"median\": {}, \ - \"deviation\": {}{} }}", - desc.name, median, deviation, mbps - ); - - self.write_message(&*line) - } - } - } - - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { - self.write_message(&*format!( - r#"{{ "type": "test", "event": "timeout", "name": "{}" }}"#, - desc.name - )) - } - - fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result { - self.write_message(&*format!( - "{{ \"type\": \"suite\", \ - \"event\": \"{}\", \ - \"passed\": {}, \ - \"failed\": {}, \ - \"allowed_fail\": {}, \ - \"ignored\": {}, \ - \"measured\": {}, \ - \"filtered_out\": {} }}", - if state.failed == 0 { "ok" } else { "failed" }, - state.passed, - state.failed + state.allowed_fail, - state.allowed_fail, - state.ignored, - state.measured, - state.filtered_out - ))?; - - Ok(state.failed == 0) - } -} - -/// A formatting utility used to print strings with characters in need of escaping. -/// Base code taken form `libserialize::json::escape_str` -struct EscapedString>(S); - -impl> ::std::fmt::Display for EscapedString { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - let mut start = 0; - - for (i, byte) in self.0.as_ref().bytes().enumerate() { - let escaped = match byte { - b'"' => "\\\"", - b'\\' => "\\\\", - b'\x00' => "\\u0000", - b'\x01' => "\\u0001", - b'\x02' => "\\u0002", - b'\x03' => "\\u0003", - b'\x04' => "\\u0004", - b'\x05' => "\\u0005", - b'\x06' => "\\u0006", - b'\x07' => "\\u0007", - b'\x08' => "\\b", - b'\t' => "\\t", - b'\n' => "\\n", - b'\x0b' => "\\u000b", - b'\x0c' => "\\f", - b'\r' => "\\r", - b'\x0e' => "\\u000e", - b'\x0f' => "\\u000f", - b'\x10' => "\\u0010", - b'\x11' => "\\u0011", - b'\x12' => "\\u0012", - b'\x13' => "\\u0013", - b'\x14' => "\\u0014", - b'\x15' => "\\u0015", - b'\x16' => "\\u0016", - b'\x17' => "\\u0017", - b'\x18' => "\\u0018", - b'\x19' => "\\u0019", - b'\x1a' => "\\u001a", - b'\x1b' => "\\u001b", - b'\x1c' => "\\u001c", - b'\x1d' => "\\u001d", - b'\x1e' => "\\u001e", - b'\x1f' => "\\u001f", - b'\x7f' => "\\u007f", - _ => { - continue; - } - }; - - if start < i { - f.write_str(&self.0.as_ref()[start..i])?; - } - - f.write_str(escaped)?; - - start = i + 1; - } - - if start != self.0.as_ref().len() { - f.write_str(&self.0.as_ref()[start..])?; - } - - Ok(()) - } -} diff --git a/src/libtest/formatters/mod.rs b/src/libtest/formatters/mod.rs deleted file mode 100644 index be5f6a65039..00000000000 --- a/src/libtest/formatters/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -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/formatters/pretty.rs b/src/libtest/formatters/pretty.rs deleted file mode 100644 index 4af00428ca8..00000000000 --- a/src/libtest/formatters/pretty.rs +++ /dev/null @@ -1,232 +0,0 @@ -use super::*; - -pub(crate) struct PrettyFormatter { - out: OutputLocation, - use_color: bool, - - /// Number of columns to fill when aligning names - max_name_len: usize, - - is_multithreaded: bool, -} - -impl PrettyFormatter { - pub fn new( - out: OutputLocation, - use_color: bool, - max_name_len: usize, - is_multithreaded: bool, - ) -> Self { - PrettyFormatter { - out, - use_color, - max_name_len, - is_multithreaded, - } - } - - #[cfg(test)] - pub fn output_location(&self) -> &OutputLocation { - &self.out - } - - pub fn write_ok(&mut self) -> io::Result<()> { - self.write_short_result("ok", term::color::GREEN) - } - - pub fn write_failed(&mut self) -> io::Result<()> { - self.write_short_result("FAILED", term::color::RED) - } - - pub fn write_ignored(&mut self) -> io::Result<()> { - self.write_short_result("ignored", term::color::YELLOW) - } - - pub fn write_allowed_fail(&mut self) -> io::Result<()> { - self.write_short_result("FAILED (allowed)", term::color::YELLOW) - } - - pub fn write_bench(&mut self) -> io::Result<()> { - self.write_pretty("bench", term::color::CYAN) - } - - pub fn write_short_result( - &mut self, - result: &str, - color: term::color::Color, - ) -> io::Result<()> { - self.write_pretty(result, color)?; - self.write_plain("\n") - } - - pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { - match self.out { - Pretty(ref mut term) => { - if self.use_color { - term.fg(color)?; - } - term.write_all(word.as_bytes())?; - if self.use_color { - term.reset()?; - } - term.flush() - } - Raw(ref mut stdout) => { - stdout.write_all(word.as_bytes())?; - stdout.flush() - } - } - } - - pub fn write_plain>(&mut self, s: S) -> io::Result<()> { - let s = s.as_ref(); - self.out.write_all(s.as_bytes())?; - self.out.flush() - } - - pub fn write_successes(&mut self, state: &ConsoleTestState) -> io::Result<()> { - self.write_plain("\nsuccesses:\n")?; - let mut successes = Vec::new(); - let mut stdouts = String::new(); - for &(ref f, ref stdout) in &state.not_failures { - successes.push(f.name.to_string()); - if !stdout.is_empty() { - stdouts.push_str(&format!("---- {} stdout ----\n", f.name)); - let output = String::from_utf8_lossy(stdout); - stdouts.push_str(&output); - stdouts.push_str("\n"); - } - } - if !stdouts.is_empty() { - self.write_plain("\n")?; - self.write_plain(&stdouts)?; - } - - self.write_plain("\nsuccesses:\n")?; - successes.sort(); - for name in &successes { - self.write_plain(&format!(" {}\n", name))?; - } - Ok(()) - } - - pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> { - self.write_plain("\nfailures:\n")?; - let mut failures = Vec::new(); - let mut fail_out = String::new(); - for &(ref f, ref stdout) in &state.failures { - failures.push(f.name.to_string()); - if !stdout.is_empty() { - fail_out.push_str(&format!("---- {} stdout ----\n", f.name)); - let output = String::from_utf8_lossy(stdout); - fail_out.push_str(&output); - fail_out.push_str("\n"); - } - } - if !fail_out.is_empty() { - self.write_plain("\n")?; - self.write_plain(&fail_out)?; - } - - self.write_plain("\nfailures:\n")?; - failures.sort(); - for name in &failures { - self.write_plain(&format!(" {}\n", name))?; - } - Ok(()) - } - - fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> { - let name = desc.padded_name(self.max_name_len, desc.name.padding()); - self.write_plain(&format!("test {} ... ", name))?; - - Ok(()) - } -} - -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<()> { - // 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 - // that the user can see which test hangs. - if !self.is_multithreaded { - self.write_test_name(desc)?; - } - - Ok(()) - } - - fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[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) => { - self.write_bench()?; - self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) - } - } - } - - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { - if self.is_multithreaded { - self.write_test_name(desc)?; - } - - self.write_plain(&format!( - "test {} has been running for over {} seconds\n", - desc.name, TEST_WARN_TIMEOUT_S - )) - } - - fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result { - if state.options.display_output { - self.write_successes(state)?; - } - let success = state.failed == 0; - if !success { - self.write_failures(state)?; - } - - self.write_plain("\ntest result: ")?; - - if success { - // There's no parallelism at this point so it's safe to use color - self.write_pretty("ok", term::color::GREEN)?; - } else { - self.write_pretty("FAILED", term::color::RED)?; - } - - let s = if state.allowed_fail > 0 { - format!( - ". {} passed; {} failed ({} allowed); {} ignored; {} measured; {} filtered out\n\n", - state.passed, - state.failed + state.allowed_fail, - state.allowed_fail, - state.ignored, - state.measured, - state.filtered_out - ) - } else { - format!( - ". {} passed; {} failed; {} ignored; {} measured; {} filtered out\n\n", - state.passed, state.failed, state.ignored, state.measured, state.filtered_out - ) - }; - - self.write_plain(&s)?; - - Ok(success) - } -} diff --git a/src/libtest/formatters/terse.rs b/src/libtest/formatters/terse.rs deleted file mode 100644 index 1400fba5d60..00000000000 --- a/src/libtest/formatters/terse.rs +++ /dev/null @@ -1,235 +0,0 @@ -use super::*; - -pub(crate) struct TerseFormatter { - out: OutputLocation, - use_color: bool, - is_multithreaded: bool, - /// Number of columns to fill when aligning names - max_name_len: usize, - - test_count: usize, - total_test_count: usize, -} - -impl TerseFormatter { - pub fn new( - out: OutputLocation, - use_color: bool, - max_name_len: usize, - is_multithreaded: bool, - ) -> Self { - TerseFormatter { - out, - use_color, - max_name_len, - is_multithreaded, - test_count: 0, - total_test_count: 0, // initialized later, when write_run_start is called - } - } - - pub fn write_ok(&mut self) -> io::Result<()> { - self.write_short_result(".", term::color::GREEN) - } - - pub fn write_failed(&mut self) -> io::Result<()> { - self.write_short_result("F", term::color::RED) - } - - pub fn write_ignored(&mut self) -> io::Result<()> { - self.write_short_result("i", term::color::YELLOW) - } - - pub fn write_allowed_fail(&mut self) -> io::Result<()> { - self.write_short_result("a", term::color::YELLOW) - } - - pub fn write_bench(&mut self) -> io::Result<()> { - self.write_pretty("bench", term::color::CYAN) - } - - pub fn write_short_result( - &mut self, - result: &str, - color: term::color::Color, - ) -> io::Result<()> { - self.write_pretty(result, color)?; - if self.test_count % QUIET_MODE_MAX_COLUMN == QUIET_MODE_MAX_COLUMN - 1 { - // 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); - self.write_plain(&out)?; - } - - self.test_count += 1; - Ok(()) - } - - pub fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> { - match self.out { - Pretty(ref mut term) => { - if self.use_color { - term.fg(color)?; - } - term.write_all(word.as_bytes())?; - if self.use_color { - term.reset()?; - } - term.flush() - } - Raw(ref mut stdout) => { - stdout.write_all(word.as_bytes())?; - stdout.flush() - } - } - } - - pub fn write_plain>(&mut self, s: S) -> io::Result<()> { - let s = s.as_ref(); - self.out.write_all(s.as_bytes())?; - self.out.flush() - } - - pub fn write_outputs(&mut self, state: &ConsoleTestState) -> io::Result<()> { - self.write_plain("\nsuccesses:\n")?; - let mut successes = Vec::new(); - let mut stdouts = String::new(); - for &(ref f, ref stdout) in &state.not_failures { - successes.push(f.name.to_string()); - if !stdout.is_empty() { - stdouts.push_str(&format!("---- {} stdout ----\n", f.name)); - let output = String::from_utf8_lossy(stdout); - stdouts.push_str(&output); - stdouts.push_str("\n"); - } - } - if !stdouts.is_empty() { - self.write_plain("\n")?; - self.write_plain(&stdouts)?; - } - - self.write_plain("\nsuccesses:\n")?; - successes.sort(); - for name in &successes { - self.write_plain(&format!(" {}\n", name))?; - } - Ok(()) - } - - pub fn write_failures(&mut self, state: &ConsoleTestState) -> io::Result<()> { - self.write_plain("\nfailures:\n")?; - let mut failures = Vec::new(); - let mut fail_out = String::new(); - for &(ref f, ref stdout) in &state.failures { - failures.push(f.name.to_string()); - if !stdout.is_empty() { - fail_out.push_str(&format!("---- {} stdout ----\n", f.name)); - let output = String::from_utf8_lossy(stdout); - fail_out.push_str(&output); - fail_out.push_str("\n"); - } - } - if !fail_out.is_empty() { - self.write_plain("\n")?; - self.write_plain(&fail_out)?; - } - - self.write_plain("\nfailures:\n")?; - failures.sort(); - for name in &failures { - self.write_plain(&format!(" {}\n", name))?; - } - Ok(()) - } - - fn write_test_name(&mut self, desc: &TestDesc) -> io::Result<()> { - let name = desc.padded_name(self.max_name_len, desc.name.padding()); - self.write_plain(&format!("test {} ... ", name))?; - - Ok(()) - } -} - -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<()> { - // 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 { - self.write_test_name(desc)?; - } - - Ok(()) - } - - fn write_result(&mut self, desc: &TestDesc, result: &TestResult, _: &[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) => { - if self.is_multithreaded { - self.write_test_name(desc)?; - } - self.write_bench()?; - self.write_plain(&format!(": {}\n", fmt_bench_samples(bs))) - } - } - } - - fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> { - self.write_plain(&format!( - "test {} has been running for over {} seconds\n", - desc.name, TEST_WARN_TIMEOUT_S - )) - } - - fn write_run_finish(&mut self, state: &ConsoleTestState) -> io::Result { - if state.options.display_output { - self.write_outputs(state)?; - } - let success = state.failed == 0; - if !success { - self.write_failures(state)?; - } - - self.write_plain("\ntest result: ")?; - - if success { - // There's no parallelism at this point so it's safe to use color - self.write_pretty("ok", term::color::GREEN)?; - } else { - self.write_pretty("FAILED", term::color::RED)?; - } - - let s = if state.allowed_fail > 0 { - format!( - ". {} passed; {} failed ({} allowed); {} ignored; {} measured; {} filtered out\n\n", - state.passed, - state.failed + state.allowed_fail, - state.allowed_fail, - state.ignored, - state.measured, - state.filtered_out - ) - } else { - format!( - ". {} passed; {} failed; {} ignored; {} measured; {} filtered out\n\n", - state.passed, state.failed, state.ignored, state.measured, state.filtered_out - ) - }; - - self.write_plain(&s)?; - - Ok(success) - } -} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs deleted file mode 100644 index ea821a1d939..00000000000 --- a/src/libtest/lib.rs +++ /dev/null @@ -1,2247 +0,0 @@ -//! Support code for rustc's built in unit-test and micro-benchmarking -//! framework. -//! -//! Almost all user code will only be interested in `Bencher` and -//! `black_box`. All other interactions (such as writing tests and -//! benchmarks themselves) should be done via the `#[test]` and -//! `#[bench]` attributes. -//! -//! See the [Testing Chapter](../book/ch11-00-testing.html) of the book for more details. - -// Currently, not much of this is meant for users. It is intended to -// support the simplest interface possible for representing and -// 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 -// this crate, which relies on this attribute (rather than the value of `--crate-name` passed by -// cargo) to detect this crate. - -#![deny(rust_2018_idioms)] -#![crate_name = "test"] -#![unstable(feature = "test", issue = "27812")] -#![doc(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, rustc_private))] -#![feature(nll)] -#![feature(set_stdio)] -#![feature(panic_unwind)] -#![feature(staged_api)] -#![feature(termination_trait_lib)] -#![feature(test)] - -use getopts; -#[cfg(any(unix, target_os = "cloudabi"))] -extern crate libc; -use term; - -// FIXME(#54291): rustc and/or LLVM don't yet support building with panic-unwind -// on aarch64-pc-windows-msvc, so we don't link libtest against -// libunwind (for the time being), even though it means that -// libtest won't be fully functional on this platform. -// -// See also: https://github.com/rust-lang/rust/issues/54190#issuecomment-422904437 -#[cfg(not(all(windows, target_arch = "aarch64")))] -extern crate panic_unwind; - -pub use self::ColorConfig::*; -use self::NamePadding::*; -use self::OutputLocation::*; -use self::TestEvent::*; -pub use self::TestFn::*; -pub use self::TestName::*; -pub use self::TestResult::*; - -use std::any::Any; -use std::borrow::Cow; -use std::boxed::FnBox; -use std::cmp; -use std::collections::BTreeMap; -use std::env; -use std::fmt; -use std::fs::File; -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}; - -const TEST_WARN_TIMEOUT_S: u64 = 60; -const QUIET_MODE_MAX_COLUMN: usize = 100; // insert a '\n' after 100 tests in quiet mode - -// to be used by rustc to compile tests in libtest -pub mod test { - pub use crate::{ - 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, - }; -} - -mod formatters; -pub mod stats; - -use crate::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 - } - } - } -} - -/// Represents a benchmark function. -pub trait TDynBenchFn: Send { - fn run(&self, harness: &mut Bencher); -} - -// A function that runs a test. If the function returns successfully, -// the test succeeds; if the function panics then the test fails. We -// may need to come up with a more clever definition of test in order -// to support isolation of tests into threads. -pub enum TestFn { - StaticTestFn(fn()), - StaticBenchFn(fn(&mut Bencher)), - DynTestFn(Box), - DynBenchFn(Box), -} - -impl TestFn { - fn padding(&self) -> NamePadding { - match *self { - StaticTestFn(..) => PadNone, - StaticBenchFn(..) => PadOnRight, - DynTestFn(..) => PadNone, - DynBenchFn(..) => PadOnRight, - } - } -} - -impl fmt::Debug for TestFn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - StaticTestFn(..) => "StaticTestFn(..)", - StaticBenchFn(..) => "StaticBenchFn(..)", - DynTestFn(..) => "DynTestFn(..)", - DynBenchFn(..) => "DynBenchFn(..)", - }) - } -} - -/// Manager of the benchmarking runs. -/// -/// This is fed into functions marked with `#[bench]` to allow for -/// set-up & tear-down before running a piece of code repeatedly via a -/// call to `iter`. -#[derive(Clone)] -pub struct Bencher { - mode: BenchMode, - summary: Option, - pub bytes: u64, -} - -#[derive(Clone, PartialEq, Eq)] -pub enum BenchMode { - Auto, - 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 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 { - display_output: bool, -} - -impl Options { - pub fn new() -> Options { - Options { - display_output: false, - } - } - - pub fn display_output(mut self, display_output: bool) -> Options { - self.display_output = display_output; - self - } -} - -// 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) { - let mut opts = match parse_opts(args) { - Some(Ok(o)) => o, - Some(Err(msg)) => { - eprintln!("error: {}", msg); - process::exit(101); - } - None => return, - }; - - opts.options = options; - if opts.list { - if let Err(e) = list_tests_console(&opts, tests) { - eprintln!("error: io error when listing tests: {:?}", e); - process::exit(101); - } - } else { - match run_tests_console(&opts, tests) { - Ok(true) => {} - Ok(false) => process::exit(101), - Err(e) => { - eprintln!("error: io error when listing tests: {:?}", e); - process::exit(101); - } - } - } -} - -// A variant optimized for invocation with a static test vector. -// 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 -// semantics into parallel test runners, which in turn requires a Vec<> -// rather than a &[]. -pub fn test_main_static(tests: &[&TestDescAndFn]) { - let args = env::args().collect::>(); - let owned_tests = tests - .iter() - .map(|t| match t.testfn { - StaticTestFn(f) => TestDescAndFn { - testfn: StaticTestFn(f), - desc: t.desc.clone(), - }, - StaticBenchFn(f) => TestDescAndFn { - testfn: StaticBenchFn(f), - desc: t.desc.clone(), - }, - _ => panic!("non-static tests passed to test::test_main_static"), - }) - .collect(); - test_main(&args, owned_tests, Options::new()) -} - -/// 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(result: T) { - let code = result.report(); - assert_eq!( - 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, - No, - Only, -} - -#[derive(Debug)] -pub struct TestOpts { - pub list: bool, - pub filter: Option, - pub filter_exact: bool, - pub exclude_should_panic: bool, - pub run_ignored: RunIgnored, - pub run_tests: bool, - pub bench_benchmarks: bool, - pub logfile: Option, - pub nocapture: bool, - pub color: ColorConfig, - pub format: OutputFormat, - pub test_threads: Option, - pub skip: Vec, - pub options: Options, -} - -impl TestOpts { - #[cfg(test)] - fn new() -> TestOpts { - TestOpts { - list: false, - filter: None, - filter_exact: false, - exclude_should_panic: false, - run_ignored: RunIgnored::No, - run_tests: false, - bench_benchmarks: false, - logfile: None, - nocapture: false, - color: AutoColor, - format: OutputFormat::Pretty, - test_threads: None, - skip: vec![], - options: Options::new(), - } - } -} - -/// Result of parsing the options. -pub type OptRes = Result; - -fn optgroups() -> getopts::Options { - let mut opts = getopts::Options::new(); - opts.optflag("", "include-ignored", "Run ignored and not ignored tests") - .optflag("", "ignored", "Run only ignored tests") - .optflag("", "exclude-should-panic", "Excludes tests marked as should_panic") - .optflag("", "test", "Run tests and not benchmarks") - .optflag("", "bench", "Run benchmarks instead of tests") - .optflag("", "list", "List all tests and benchmarks") - .optflag("h", "help", "Display this message (longer with --help)") - .optopt( - "", - "logfile", - "Write logs to the specified file instead \ - of stdout", - "PATH", - ) - .optflag( - "", - "nocapture", - "don't capture stdout/stderr of each \ - task, allow printing directly", - ) - .optopt( - "", - "test-threads", - "Number of threads used for running tests \ - in parallel", - "n_threads", - ) - .optmulti( - "", - "skip", - "Skip tests whose names contain FILTER (this flag can \ - be used multiple times)", - "FILTER", - ) - .optflag( - "q", - "quiet", - "Display one character per test instead of one line. \ - Alias to --format=terse", - ) - .optflag( - "", - "exact", - "Exactly match filters rather than by substring", - ) - .optopt( - "", - "color", - "Configure coloring of output: - auto = colorize if stdout is a tty and tests are run on serially (default); - always = always colorize output; - never = never colorize output;", - "auto|always|never", - ) - .optopt( - "", - "format", - "Configure formatting of output: - pretty = Print verbose output; - terse = Display one character per test; - json = Output a json document", - "pretty|terse|json", - ) - .optopt( - "Z", - "", - "Enable nightly-only flags: - unstable-options = Allow use of experimental features", - "unstable-options", - ); - return opts; -} - -fn usage(binary: &str, options: &getopts::Options) { - let message = format!("Usage: {} [OPTIONS] [FILTER]", binary); - println!( - r#"{usage} - -The FILTER string is tested against the name of all tests, and only those -tests whose names contain the filter are run. - -By default, all tests are run in parallel. This can be altered with the ---test-threads flag or the RUST_TEST_THREADS environment variable when running -tests (set it to 1). - -All tests have their standard output and standard error captured by default. -This can be overridden with the --nocapture flag or setting RUST_TEST_NOCAPTURE -environment variable to a value other than "0". Logging is not captured by default. - -Test Attributes: - - #[test] - Indicates a function is a test to be run. This function - takes no arguments. - #[bench] - Indicates a function is a benchmark to be run. This - function takes one argument (test::Bencher). - #[should_panic] - This function (also labeled with #[test]) will only pass if - the code causes a panic (an assertion failure or panic!) - A message may be provided, which the failure string must - contain: #[should_panic(expected = "foo")]. - #[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 or --include-ignored will run - these tests."#, - usage = options.usage(&message) - ); -} - -// FIXME: Copied from libsyntax until linkage errors are resolved. Issue #47566 -fn is_nightly() -> bool { - // Whether this is a feature-staged build, i.e., on the beta or stable channel - let disable_unstable_features = option_env!("CFG_DISABLE_UNSTABLE_FEATURES").is_some(); - // Whether we should enable unstable features for bootstrapping - let bootstrap = env::var("RUSTC_BOOTSTRAP").is_ok(); - - bootstrap || !disable_unstable_features -} - -// Parses command line arguments into test options -pub fn parse_opts(args: &[String]) -> Option { - let mut allow_unstable = false; - let opts = optgroups(); - let args = args.get(1..).unwrap_or(args); - let matches = match opts.parse(args) { - Ok(m) => m, - Err(f) => return Some(Err(f.to_string())), - }; - - if let Some(opt) = matches.opt_str("Z") { - if !is_nightly() { - return Some(Err( - "the option `Z` is only accepted on the nightly compiler".into(), - )); - } - - match &*opt { - "unstable-options" => { - allow_unstable = true; - } - _ => { - return Some(Err("Unrecognized option to `Z`".into())); - } - } - }; - - if matches.opt_present("h") { - usage(&args[0], &opts); - return None; - } - - let filter = if !matches.free.is_empty() { - Some(matches.free[0].clone()) - } else { - None - }; - - let exclude_should_panic = matches.opt_present("exclude-should-panic"); - if !allow_unstable && exclude_should_panic { - return Some(Err( - "The \"exclude-should-panic\" flag is only accepted on the nightly compiler".into(), - )); - } - - 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(), - )); - } - - 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, false) => RunIgnored::Yes, - (false, true) => RunIgnored::Only, - (false, false) => RunIgnored::No, - }; - let quiet = matches.opt_present("quiet"); - let exact = matches.opt_present("exact"); - let list = matches.opt_present("list"); - - let logfile = matches.opt_str("logfile"); - let logfile = logfile.map(|s| PathBuf::from(&s)); - - let bench_benchmarks = matches.opt_present("bench"); - let run_tests = !bench_benchmarks || matches.opt_present("test"); - - let mut nocapture = matches.opt_present("nocapture"); - if !nocapture { - nocapture = match env::var("RUST_TEST_NOCAPTURE") { - Ok(val) => &val != "0", - Err(_) => false, - }; - } - - let test_threads = match matches.opt_str("test-threads") { - Some(n_str) => match n_str.parse::() { - Ok(0) => return Some(Err("argument for --test-threads must not be 0".to_string())), - Ok(n) => Some(n), - Err(e) => { - return Some(Err(format!( - "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(v) => { - return Some(Err(format!( - "argument for --color must be auto, always, or never (was \ - {})", - v - ))); - } - }; - - let format = match matches.opt_str("format").as_ref().map(|s| &**s) { - None if quiet => OutputFormat::Terse, - Some("pretty") | None => OutputFormat::Pretty, - Some("terse") => OutputFormat::Terse, - Some("json") => { - if !allow_unstable { - return Some(Err( - "The \"json\" format is only accepted on the nightly compiler".into(), - )); - } - OutputFormat::Json - } - - Some(v) => { - return Some(Err(format!( - "argument for --format must be pretty, terse, or json (was \ - {})", - v - ))); - } - }; - - let test_opts = TestOpts { - list, - filter, - filter_exact: exact, - exclude_should_panic, - run_ignored, - run_tests, - bench_benchmarks, - logfile, - nocapture, - color, - format, - test_threads, - skip: matches.opt_strs("skip"), - options: Options::new(), - }; - - 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<()> { - let mut output = match term::stdout() { - None => Raw(io::stdout()), - Some(t) => Pretty(t), - }; - - let quiet = opts.format == OutputFormat::Terse; - let mut st = ConsoleTestState::new(opts)?; - - let mut ntest = 0; - let mut nbench = 0; - - for test in filter_tests(&opts, tests) { - use crate::TestFn::*; - - let TestDescAndFn { - desc: TestDesc { name, .. }, - testfn, - } = test; - - let fntype = match testfn { - StaticTestFn(..) | DynTestFn(..) => { - ntest += 1; - "test" - } - StaticBenchFn(..) | DynBenchFn(..) => { - nbench += 1; - "benchmark" - } - }; - - writeln!(output, "{}: {}", name, fntype)?; - st.write_log(format!("{} {}\n", fntype, name))?; - } - - fn plural(count: u32, s: &str) -> String { - match count { - 1 => format!("{} {}", 1, s), - n => format!("{} {}s", n, s), - } - } - - if !quiet { - if ntest != 0 || nbench != 0 { - writeln!(output, "")?; - } - - writeln!( - output, - "{}, {}", - plural(ntest, "test"), - plural(nbench, "benchmark") - )?; - } - - Ok(()) -} - -// A simple console test runner -pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Result { - fn callback( - event: &TestEvent, - st: &mut ConsoleTestState, - out: &mut dyn OutputFormatter, - ) -> io::Result<()> { - match (*event).clone() { - TeFiltered(ref filtered_tests) => { - st.total = filtered_tests.len(); - out.write_run_start(filtered_tests.len()) - } - 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)?; - match result { - TrOk => { - 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 => { - st.failed += 1; - st.failures.push((test, stdout)); - } - TrFailedMsg(msg) => { - st.failed += 1; - let mut stdout = stdout; - stdout.extend_from_slice(format!("note: {}", msg).as_bytes()); - st.failures.push((test, stdout)); - } - } - Ok(()) - } - } - } - - let output = match term::stdout() { - None => Raw(io::stdout()), - Some(t) => Pretty(t), - }; - - let max_name_len = tests - .iter() - .max_by_key(|t| len_if_padded(*t)) - .map(|t| t.desc.name.as_slice().len()) - .unwrap_or(0); - - let is_multithreaded = opts.test_threads.unwrap_or_else(get_concurrency) > 1; - - let mut out: Box = match opts.format { - OutputFormat::Pretty => Box::new(PrettyFormatter::new( - output, - use_color(opts), - max_name_len, - is_multithreaded, - )), - OutputFormat::Terse => Box::new(TerseFormatter::new( - output, - use_color(opts), - max_name_len, - is_multithreaded, - )), - OutputFormat::Json => Box::new(JsonFormatter::new(output)), - }; - let mut st = ConsoleTestState::new(opts)?; - fn len_if_padded(t: &TestDescAndFn) -> usize { - match t.testfn.padding() { - PadNone => 0, - PadOnRight => t.desc.name.as_slice().len(), - } - } - - run_tests(opts, tests, |x| callback(&x, &mut st, &mut *out))?; - - assert!(st.current_test_count() == st.total); - - 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")), - all(target_vendor = "fortanix", 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 - } -} - -#[derive(Clone)] -pub enum TestEvent { - TeFiltered(Vec), - TeWait(TestDesc), - TeResult(TestDesc, TestResult, Vec), - TeTimeout(TestDesc), - TeFilteredOut(usize), -} - -pub type MonitorMsg = (TestDesc, TestResult, Vec); - -struct Sink(Arc>>); -impl Write for Sink { - fn write(&mut self, data: &[u8]) -> io::Result { - Write::write(&mut *self.0.lock().unwrap(), data) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub fn run_tests(opts: &TestOpts, tests: Vec, mut callback: F) -> io::Result<()> -where - F: FnMut(TestEvent) -> io::Result<()>, -{ - use std::collections::{self, HashMap}; - use std::hash::BuildHasherDefault; - use std::sync::mpsc::RecvTimeoutError; - // Use a deterministic hasher - type TestMap = - HashMap>; - - let tests_len = tests.len(); - - let mut filtered_tests = filter_tests(opts, tests); - if !opts.bench_benchmarks { - filtered_tests = convert_benchmarks_to_tests(filtered_tests); - } - - let filtered_tests = { - let mut filtered_tests = filtered_tests; - for test in filtered_tests.iter_mut() { - test.desc.name = test.desc.name.with_padding(test.testfn.padding()); - } - - filtered_tests - }; - - let filtered_out = tests_len - filtered_tests.len(); - callback(TeFilteredOut(filtered_out))?; - - let filtered_descs = filtered_tests.iter().map(|t| t.desc.clone()).collect(); - - callback(TeFiltered(filtered_descs))?; - - let (filtered_tests, filtered_benchs): (Vec<_>, _) = - filtered_tests.into_iter().partition(|e| match e.testfn { - StaticTestFn(_) | DynTestFn(_) => true, - _ => false, - }); - - let concurrency = opts.test_threads.unwrap_or_else(get_concurrency); - - let mut remaining = filtered_tests; - remaining.reverse(); - let mut pending = 0; - - let (tx, rx) = channel::(); - - let mut running_tests: TestMap = HashMap::default(); - - fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec { - let now = Instant::now(); - let timed_out = running_tests - .iter() - .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 { - running_tests.values().min().map(|next_timeout| { - let now = Instant::now(); - if *next_timeout >= now { - *next_timeout - now - } else { - Duration::new(0, 0) - } - }) - }; - - if concurrency == 1 { - while !remaining.is_empty() { - 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))?; - } - } else { - while pending > 0 || !remaining.is_empty() { - while pending < concurrency && !remaining.is_empty() { - let test = remaining.pop().unwrap(); - let timeout = Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S); - running_tests.insert(test.desc.clone(), timeout); - callback(TeWait(test.desc.clone()))?; //here no pad - run_test(opts, !opts.run_tests, test, tx.clone(), Concurrent::Yes); - pending += 1; - } - - 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))?; - pending -= 1; - } - } - - if opts.bench_benchmarks { - // All benchmarks run at the end, in serial. - 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")), - all(target_vendor = "fortanix", 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 - } -} - -pub fn filter_tests(opts: &TestOpts, tests: Vec) -> Vec { - let mut filtered = tests; - let matches_filter = |test: &TestDescAndFn, filter: &str| { - let test_name = test.desc.name.as_slice(); - - match opts.filter_exact { - true => test_name == filter, - false => test_name.contains(filter), - } - }; - - // Remove tests that don't match the test filter - if let Some(ref filter) = opts.filter { - filtered.retain(|test| matches_filter(test, filter)); - } - - // Skip tests that match any of the skip filters - filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf))); - - // Excludes #[should_panic] tests - if opts.exclude_should_panic { - filtered.retain(|test| test.desc.should_panic == ShouldPanic::No); - } - - // maybe unignore tests - match opts.run_ignored { - RunIgnored::Yes => { - 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); - } - RunIgnored::No => {} - } - - // Sort the tests alphabetically - filtered.sort_by(|t1, t2| t1.desc.name.as_slice().cmp(t2.desc.name.as_slice())); - - filtered -} - -pub fn convert_benchmarks_to_tests(tests: Vec) -> Vec { - // convert benchmarks to tests, if we're not benchmarking them - tests - .into_iter() - .map(|x| { - let testfn = match x.testfn { - DynBenchFn(bench) => DynTestFn(Box::new(move || { - bench::run_once(|b| __rust_begin_short_backtrace(|| bench.run(b))) - })), - StaticBenchFn(benchfn) => DynTestFn(Box::new(move || { - bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b))) - })), - f => f, - }; - TestDescAndFn { - desc: x.desc, - testfn, - } - }) - .collect() -} - -pub fn run_test( - opts: &TestOpts, - force_ignore: bool, - test: TestDescAndFn, - monitor_ch: Sender, - concurrency: Concurrent, -) { - let TestDescAndFn { desc, testfn } = test; - - 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(); - return; - } - - fn run_test_inner( - desc: TestDesc, - monitor_ch: Sender, - nocapture: bool, - testfn: Box, - concurrency: Concurrent, - ) { - // Buffer for capturing standard I/O - let data = Arc::new(Mutex::new(Vec::new())); - let data2 = data.clone(); - - let name = desc.name.clone(); - let runtest = move || { - let oldio = if !nocapture { - Some(( - io::set_print(Some(Box::new(Sink(data2.clone())))), - io::set_panic(Some(Box::new(Sink(data2)))), - )) - } else { - None - }; - - let result = catch_unwind(AssertUnwindSafe(testfn)); - - if let Some((printio, panicio)) = oldio { - io::set_print(printio); - io::set_panic(panicio); - }; - - let test_result = calc_result(&desc, result); - let stdout = data.lock().unwrap().to_vec(); - monitor_ch - .send((desc.clone(), test_result, stdout)) - .unwrap(); - }; - - // If the platform is single-threaded we're just going to run - // the test synchronously, regardless of the concurrency - // level. - let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_arch = "wasm32"); - if concurrency == Concurrent::Yes && supports_threads { - let cfg = thread::Builder::new().name(name.as_slice().to_owned()); - cfg.spawn(runtest).unwrap(); - } else { - runtest(); - } - } - - match testfn { - DynBenchFn(bencher) => { - crate::bench::benchmark(desc, monitor_ch, opts.nocapture, |harness| { - bencher.run(harness) - }); - } - StaticBenchFn(benchfn) => { - crate::bench::benchmark(desc, monitor_ch, opts.nocapture, |harness| { - (benchfn.clone())(harness) - }); - } - DynTestFn(f) => { - let cb = move || __rust_begin_short_backtrace(f); - run_test_inner(desc, monitor_ch, opts.nocapture, Box::new(cb), concurrency) - } - StaticTestFn(f) => run_test_inner( - desc, - monitor_ch, - opts.nocapture, - Box::new(move || __rust_begin_short_backtrace(f)), - concurrency, - ), - } -} - -/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. -#[inline(never)] -fn __rust_begin_short_backtrace(f: F) { - f() -} - -fn calc_result(desc: &TestDesc, task_result: Result<(), Box>) -> TestResult { - match (&desc.should_panic, task_result) { - (&ShouldPanic::No, Ok(())) | (&ShouldPanic::Yes, Err(_)) => TrOk, - (&ShouldPanic::YesWithMessage(msg), Err(ref err)) => { - 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 - } else { - if desc.allow_fail { - TrAllowedFail - } else { - TrFailedMsg(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(", ") - } -} - -// Benchmarking - -/// A function that is opaque to the optimizer, to allow benchmarks to -/// pretend to use outputs to assist in avoiding dead-code -/// elimination. -/// -/// This function is a no-op, and does not even read from `dummy`. -#[cfg(not(any(target_arch = "asmjs", target_arch = "wasm32")))] -pub fn black_box(dummy: T) -> T { - // we need to "use" the argument in some way LLVM can't - // introspect. - unsafe { asm!("" : : "r"(&dummy)) } - dummy -} -#[cfg(any(target_arch = "asmjs", target_arch = "wasm32"))] -#[inline(never)] -pub fn black_box(dummy: T) -> T { - dummy -} - -impl Bencher { - /// Callback for benchmark functions to run in their body. - pub fn iter(&mut self, mut inner: F) - where - F: FnMut() -> T, - { - if self.mode == BenchMode::Single { - ns_iter_inner(&mut inner, 1); - return; - } - - self.summary = Some(iter(&mut inner)); - } - - pub fn bench(&mut self, mut f: F) -> Option - where - F: FnMut(&mut Bencher), - { - f(self); - return self.summary; - } -} - -fn ns_from_dur(dur: Duration) -> u64 { - dur.as_secs() * 1_000_000_000 + (dur.subsec_nanos() as u64) -} - -fn ns_iter_inner(inner: &mut F, k: u64) -> u64 -where - F: FnMut() -> T, -{ - let start = Instant::now(); - for _ in 0..k { - black_box(inner()); - } - return ns_from_dur(start.elapsed()); -} - -pub fn iter(inner: &mut F) -> stats::Summary -where - F: FnMut() -> T, -{ - // Initial bench run to get ballpark figure. - let ns_single = ns_iter_inner(inner, 1); - - // Try to estimate iter count for 1ms falling back to 1m - // iterations if first run took < 1ns. - let ns_target_total = 1_000_000; // 1ms - let mut n = ns_target_total / cmp::max(1, ns_single); - - // if the first run took more than 1ms we don't want to just - // be left doing 0 iterations on every loop. The unfortunate - // side effect of not being able to do as many runs is - // automatically handled by the statistical analysis below - // (i.e., larger error bars). - n = cmp::max(1, n); - - let mut total_run = Duration::new(0, 0); - let samples: &mut [f64] = &mut [0.0_f64; 50]; - loop { - let loop_start = Instant::now(); - - for p in &mut *samples { - *p = ns_iter_inner(inner, n) as f64 / n as f64; - } - - stats::winsorize(samples, 5.0); - let summ = stats::Summary::new(samples); - - for p in &mut *samples { - let ns = ns_iter_inner(inner, 5 * n); - *p = ns as f64 / (5 * n) as f64; - } - - stats::winsorize(samples, 5.0); - let summ5 = stats::Summary::new(samples); - - let loop_run = loop_start.elapsed(); - - // 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 - && summ.median - summ5.median < summ5.median_abs_dev - { - return summ5; - } - - total_run = total_run + loop_run; - // Longest we ever run for is 3s. - if total_run > Duration::from_secs(3) { - return summ5; - } - - // If we overflow here just return the results so far. We check a - // multiplier of 10 because we're about to multiply by 2 and the - // next iteration of the loop will also multiply by 5 (to calculate - // the summ5 result) - n = match n.checked_mul(10) { - Some(_) => n * 2, - None => { - return summ5; - } - }; - } -} - -pub mod bench { - use super::{BenchMode, BenchSamples, Bencher, MonitorMsg, Sender, Sink, TestDesc, TestResult}; - use crate::stats; - use std::cmp; - use std::io; - use std::panic::{catch_unwind, AssertUnwindSafe}; - use std::sync::{Arc, Mutex}; - - pub fn benchmark(desc: TestDesc, monitor_ch: Sender, nocapture: bool, f: F) - where - F: FnMut(&mut Bencher), - { - let mut bs = Bencher { - mode: BenchMode::Auto, - summary: None, - bytes: 0, - }; - - let data = Arc::new(Mutex::new(Vec::new())); - let data2 = data.clone(); - - let oldio = if !nocapture { - Some(( - io::set_print(Some(Box::new(Sink(data2.clone())))), - io::set_panic(Some(Box::new(Sink(data2)))), - )) - } else { - None - }; - - let result = catch_unwind(AssertUnwindSafe(|| bs.bench(f))); - - if let Some((printio, panicio)) = oldio { - io::set_print(printio); - io::set_panic(panicio); - }; - - let test_result = match result { - //bs.bench(f) { - Ok(Some(ns_iter_summ)) => { - let ns_iter = cmp::max(ns_iter_summ.median as u64, 1); - let mb_s = bs.bytes * 1000 / ns_iter; - - let bs = BenchSamples { - ns_iter_summ, - mb_s: mb_s as usize, - }; - TestResult::TrBench(bs) - } - Ok(None) => { - // iter not called, so no data. - // FIXME: error in this case? - let samples: &mut [f64] = &mut [0.0_f64; 1]; - let bs = BenchSamples { - ns_iter_summ: stats::Summary::new(samples), - mb_s: 0, - }; - TestResult::TrBench(bs) - } - Err(_) => TestResult::TrFailed, - }; - - let stdout = data.lock().unwrap().to_vec(); - monitor_ch.send((desc, test_result, stdout)).unwrap(); - } - - pub fn run_once(f: F) - where - F: FnMut(&mut Bencher), - { - let mut bs = Bencher { - mode: BenchMode::Single, - summary: None, - bytes: 0, - }; - bs.bench(f); - } -} - -#[cfg(test)] -mod tests { - use crate::bench; - use crate::test::{ - filter_tests, parse_opts, run_test, DynTestFn, DynTestName, MetricMap, RunIgnored, - ShouldPanic, StaticTestName, TestDesc, TestDescAndFn, TestOpts, TrFailed, TrFailedMsg, - TrIgnored, TrOk, - }; - use crate::Bencher; - use crate::Concurrent; - use std::sync::mpsc::channel; - - fn one_ignored_one_unignored_test() -> Vec { - vec![ - TestDescAndFn { - desc: TestDesc { - name: StaticTestName("1"), - ignore: true, - should_panic: ShouldPanic::No, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(move || {})), - }, - TestDescAndFn { - desc: TestDesc { - name: StaticTestName("2"), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(move || {})), - }, - ] - } - - #[test] - pub fn do_not_run_ignored_tests() { - fn f() { - panic!(); - } - let desc = TestDescAndFn { - desc: TestDesc { - name: StaticTestName("whatever"), - ignore: true, - should_panic: ShouldPanic::No, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(f)), - }; - let (tx, rx) = channel(); - run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res != TrOk); - } - - #[test] - pub fn ignored_tests_result_in_ignored() { - fn f() {} - let desc = TestDescAndFn { - desc: TestDesc { - name: StaticTestName("whatever"), - ignore: true, - should_panic: ShouldPanic::No, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(f)), - }; - let (tx, rx) = channel(); - run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrIgnored); - } - - #[test] - fn test_should_panic() { - fn f() { - panic!(); - } - let desc = TestDescAndFn { - desc: TestDesc { - name: StaticTestName("whatever"), - ignore: false, - should_panic: ShouldPanic::Yes, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(f)), - }; - let (tx, rx) = channel(); - run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrOk); - } - - #[test] - fn test_should_panic_good_message() { - fn f() { - panic!("an error message"); - } - let desc = TestDescAndFn { - desc: TestDesc { - name: StaticTestName("whatever"), - ignore: false, - should_panic: ShouldPanic::YesWithMessage("error message"), - allow_fail: false, - }, - testfn: DynTestFn(Box::new(f)), - }; - let (tx, rx) = channel(); - run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrOk); - } - - #[test] - fn test_should_panic_bad_message() { - fn f() { - panic!("an error message"); - } - let expected = "foobar"; - let failed_msg = "Panic did not include expected string"; - let desc = TestDescAndFn { - desc: TestDesc { - name: StaticTestName("whatever"), - ignore: false, - should_panic: ShouldPanic::YesWithMessage(expected), - allow_fail: false, - }, - testfn: DynTestFn(Box::new(f)), - }; - 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))); - } - - #[test] - fn test_should_panic_but_succeeds() { - fn f() {} - let desc = TestDescAndFn { - desc: TestDesc { - name: StaticTestName("whatever"), - ignore: false, - should_panic: ShouldPanic::Yes, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(f)), - }; - let (tx, rx) = channel(); - run_test(&TestOpts::new(), false, desc, tx, Concurrent::No); - let (_, res, _) = rx.recv().unwrap(); - assert!(res == TrFailed); - } - - #[test] - fn parse_ignored_flag() { - let args = vec![ - "progname".to_string(), - "filter".to_string(), - "--ignored".to_string(), - ]; - let opts = parse_opts(&args).unwrap().unwrap(); - assert_eq!(opts.run_ignored, RunIgnored::Only); - } - - #[test] - fn parse_include_ignored_flag() { - let args = vec![ - "progname".to_string(), - "filter".to_string(), - "-Zunstable-options".to_string(), - "--include-ignored".to_string(), - ]; - let opts = parse_opts(&args).unwrap().unwrap(); - assert_eq!(opts.run_ignored, RunIgnored::Yes); - } - - #[test] - pub fn filter_for_ignored_option() { - // When we run ignored tests the test filter should filter out all the - // unignored tests and flip the ignore flag on the rest to false - - let mut opts = TestOpts::new(); - opts.run_tests = true; - opts.run_ignored = RunIgnored::Only; - - let tests = one_ignored_one_unignored_test(); - let filtered = filter_tests(&opts, tests); - - assert_eq!(filtered.len(), 1); - assert_eq!(filtered[0].desc.name.to_string(), "1"); - assert!(!filtered[0].desc.ignore); - } - - #[test] - pub fn run_include_ignored_option() { - // When we "--include-ignored" tests, the ignore flag should be set to false on - // all tests and no test filtered out - - let mut opts = TestOpts::new(); - opts.run_tests = true; - opts.run_ignored = RunIgnored::Yes; - - let tests = one_ignored_one_unignored_test(); - let filtered = filter_tests(&opts, tests); - - assert_eq!(filtered.len(), 2); - assert!(!filtered[0].desc.ignore); - assert!(!filtered[1].desc.ignore); - } - - #[test] - pub fn exclude_should_panic_option() { - let mut opts = TestOpts::new(); - opts.run_tests = true; - opts.exclude_should_panic = true; - - let mut tests = one_ignored_one_unignored_test(); - tests.push(TestDescAndFn { - desc: TestDesc { - name: StaticTestName("3"), - ignore: false, - should_panic: ShouldPanic::Yes, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(move || {})), - }); - - let filtered = filter_tests(&opts, tests); - - assert_eq!(filtered.len(), 2); - assert!(filtered.iter().all(|test| test.desc.should_panic == ShouldPanic::No)); - } - - #[test] - pub fn exact_filter_match() { - fn tests() -> Vec { - vec!["base", "base::test", "base::test1", "base::test2"] - .into_iter() - .map(|name| TestDescAndFn { - desc: TestDesc { - name: StaticTestName(name), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(move || {})), - }) - .collect() - } - - let substr = filter_tests( - &TestOpts { - filter: Some("base".into()), - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(substr.len(), 4); - - let substr = filter_tests( - &TestOpts { - filter: Some("bas".into()), - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(substr.len(), 4); - - let substr = filter_tests( - &TestOpts { - filter: Some("::test".into()), - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(substr.len(), 3); - - let substr = filter_tests( - &TestOpts { - filter: Some("base::test".into()), - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(substr.len(), 3); - - let exact = filter_tests( - &TestOpts { - filter: Some("base".into()), - filter_exact: true, - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(exact.len(), 1); - - let exact = filter_tests( - &TestOpts { - filter: Some("bas".into()), - filter_exact: true, - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(exact.len(), 0); - - let exact = filter_tests( - &TestOpts { - filter: Some("::test".into()), - filter_exact: true, - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(exact.len(), 0); - - let exact = filter_tests( - &TestOpts { - filter: Some("base::test".into()), - filter_exact: true, - ..TestOpts::new() - }, - tests(), - ); - assert_eq!(exact.len(), 1); - } - - #[test] - pub fn sort_tests() { - let mut opts = TestOpts::new(); - opts.run_tests = true; - - let names = vec![ - "sha1::test".to_string(), - "isize::test_to_str".to_string(), - "isize::test_pow".to_string(), - "test::do_not_run_ignored_tests".to_string(), - "test::ignored_tests_result_in_ignored".to_string(), - "test::first_free_arg_should_be_a_filter".to_string(), - "test::parse_ignored_flag".to_string(), - "test::parse_include_ignored_flag".to_string(), - "test::filter_for_ignored_option".to_string(), - "test::run_include_ignored_option".to_string(), - "test::sort_tests".to_string(), - ]; - let tests = { - fn testfn() {} - let mut tests = Vec::new(); - for name in &names { - let test = TestDescAndFn { - desc: TestDesc { - name: DynTestName((*name).clone()), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }, - testfn: DynTestFn(Box::new(testfn)), - }; - tests.push(test); - } - tests - }; - let filtered = filter_tests(&opts, tests); - - let expected = vec![ - "isize::test_pow".to_string(), - "isize::test_to_str".to_string(), - "sha1::test".to_string(), - "test::do_not_run_ignored_tests".to_string(), - "test::filter_for_ignored_option".to_string(), - "test::first_free_arg_should_be_a_filter".to_string(), - "test::ignored_tests_result_in_ignored".to_string(), - "test::parse_ignored_flag".to_string(), - "test::parse_include_ignored_flag".to_string(), - "test::run_include_ignored_option".to_string(), - "test::sort_tests".to_string(), - ]; - - for (a, b) in expected.iter().zip(filtered) { - assert!(*a == b.desc.name.to_string()); - } - } - - #[test] - pub fn test_metricmap_compare() { - let mut m1 = MetricMap::new(); - let mut m2 = MetricMap::new(); - m1.insert_metric("in-both-noise", 1000.0, 200.0); - m2.insert_metric("in-both-noise", 1100.0, 200.0); - - m1.insert_metric("in-first-noise", 1000.0, 2.0); - m2.insert_metric("in-second-noise", 1000.0, 2.0); - - m1.insert_metric("in-both-want-downwards-but-regressed", 1000.0, 10.0); - m2.insert_metric("in-both-want-downwards-but-regressed", 2000.0, 10.0); - - m1.insert_metric("in-both-want-downwards-and-improved", 2000.0, 10.0); - m2.insert_metric("in-both-want-downwards-and-improved", 1000.0, 10.0); - - m1.insert_metric("in-both-want-upwards-but-regressed", 2000.0, -10.0); - m2.insert_metric("in-both-want-upwards-but-regressed", 1000.0, -10.0); - - m1.insert_metric("in-both-want-upwards-and-improved", 1000.0, -10.0); - m2.insert_metric("in-both-want-upwards-and-improved", 2000.0, -10.0); - } - - #[test] - pub fn test_bench_once_no_iter() { - fn f(_: &mut Bencher) {} - bench::run_once(f); - } - - #[test] - pub fn test_bench_once_iter() { - fn f(b: &mut Bencher) { - b.iter(|| {}) - } - bench::run_once(f); - } - - #[test] - pub fn test_bench_no_iter() { - fn f(_: &mut Bencher) {} - - let (tx, rx) = channel(); - - let desc = TestDesc { - name: StaticTestName("f"), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }; - - crate::bench::benchmark(desc, tx, true, f); - rx.recv().unwrap(); - } - - #[test] - pub fn test_bench_iter() { - fn f(b: &mut Bencher) { - b.iter(|| {}) - } - - let (tx, rx) = channel(); - - let desc = TestDesc { - name: StaticTestName("f"), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - }; - - crate::bench::benchmark(desc, tx, true, f); - rx.recv().unwrap(); - } -} diff --git a/src/libtest/stats.rs b/src/libtest/stats.rs deleted file mode 100644 index 5c9421d5ea4..00000000000 --- a/src/libtest/stats.rs +++ /dev/null @@ -1,922 +0,0 @@ -#![allow(missing_docs)] -#![allow(deprecated)] // Float - -use std::cmp::Ordering::{self, Equal, Greater, Less}; -use std::mem; - -fn local_cmp(x: f64, y: f64) -> Ordering { - // arbitrarily decide that NaNs are larger than everything. - if y.is_nan() { - Less - } else if x.is_nan() { - Greater - } else if x < y { - Less - } else if x == y { - Equal - } else { - Greater - } -} - -fn local_sort(v: &mut [f64]) { - v.sort_by(|x: &f64, y: &f64| local_cmp(*x, *y)); -} - -/// Trait that provides simple descriptive statistics on a univariate set of numeric samples. -pub trait Stats { - /// Sum of the samples. - /// - /// Note: this method sacrifices performance at the altar of accuracy - /// Depends on IEEE-754 arithmetic guarantees. See proof of correctness at: - /// ["Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric - /// Predicates"][paper] - /// - /// [paper]: http://www.cs.cmu.edu/~quake-papers/robust-arithmetic.ps - fn sum(&self) -> f64; - - /// Minimum value of the samples. - fn min(&self) -> f64; - - /// Maximum value of the samples. - fn max(&self) -> f64; - - /// Arithmetic mean (average) of the samples: sum divided by sample-count. - /// - /// See: - fn mean(&self) -> f64; - - /// Median of the samples: value separating the lower half of the samples from the higher half. - /// Equal to `self.percentile(50.0)`. - /// - /// See: - fn median(&self) -> f64; - - /// Variance of the samples: bias-corrected mean of the squares of the differences of each - /// sample from the sample mean. Note that this calculates the _sample variance_ rather than the - /// population variance, which is assumed to be unknown. It therefore corrects the `(n-1)/n` - /// bias that would appear if we calculated a population variance, by dividing by `(n-1)` rather - /// than `n`. - /// - /// See: - fn var(&self) -> f64; - - /// Standard deviation: the square root of the sample variance. - /// - /// Note: this is not a robust statistic for non-normal distributions. Prefer the - /// `median_abs_dev` for unknown distributions. - /// - /// See: - fn std_dev(&self) -> f64; - - /// Standard deviation as a percent of the mean value. See `std_dev` and `mean`. - /// - /// Note: this is not a robust statistic for non-normal distributions. Prefer the - /// `median_abs_dev_pct` for unknown distributions. - fn std_dev_pct(&self) -> f64; - - /// Scaled median of the absolute deviations of each sample from the sample median. This is a - /// robust (distribution-agnostic) estimator of sample variability. Use this in preference to - /// `std_dev` if you cannot assume your sample is normally distributed. Note that this is scaled - /// by the constant `1.4826` to allow its use as a consistent estimator for the standard - /// deviation. - /// - /// See: - fn median_abs_dev(&self) -> f64; - - /// Median absolute deviation as a percent of the median. See `median_abs_dev` and `median`. - fn median_abs_dev_pct(&self) -> f64; - - /// Percentile: the value below which `pct` percent of the values in `self` fall. For example, - /// percentile(95.0) will return the value `v` such that 95% of the samples `s` in `self` - /// satisfy `s <= v`. - /// - /// Calculated by linear interpolation between closest ranks. - /// - /// See: - fn percentile(&self, pct: f64) -> f64; - - /// Quartiles of the sample: three values that divide the sample into four equal groups, each - /// with 1/4 of the data. The middle value is the median. See `median` and `percentile`. This - /// function may calculate the 3 quartiles more efficiently than 3 calls to `percentile`, but - /// is otherwise equivalent. - /// - /// See also: - fn quartiles(&self) -> (f64, f64, f64); - - /// Inter-quartile range: the difference between the 25th percentile (1st quartile) and the 75th - /// percentile (3rd quartile). See `quartiles`. - /// - /// See also: - fn iqr(&self) -> f64; -} - -/// Extracted collection of all the summary statistics of a sample set. -#[derive(Clone, PartialEq, Copy)] -#[allow(missing_docs)] -pub struct Summary { - pub sum: f64, - pub min: f64, - pub max: f64, - pub mean: f64, - pub median: f64, - pub var: f64, - pub std_dev: f64, - pub std_dev_pct: f64, - pub median_abs_dev: f64, - pub median_abs_dev_pct: f64, - pub quartiles: (f64, f64, f64), - pub iqr: f64, -} - -impl Summary { - /// Construct a new summary of a sample set. - pub fn new(samples: &[f64]) -> Summary { - Summary { - sum: samples.sum(), - min: samples.min(), - max: samples.max(), - mean: samples.mean(), - median: samples.median(), - var: samples.var(), - std_dev: samples.std_dev(), - std_dev_pct: samples.std_dev_pct(), - median_abs_dev: samples.median_abs_dev(), - median_abs_dev_pct: samples.median_abs_dev_pct(), - quartiles: samples.quartiles(), - iqr: samples.iqr(), - } - } -} - -impl Stats for [f64] { - // FIXME #11059 handle NaN, inf and overflow - fn sum(&self) -> f64 { - let mut partials = vec![]; - - for &x in self { - let mut x = x; - let mut j = 0; - // This inner loop applies `hi`/`lo` summation to each - // partial so that the list of partial sums remains exact. - for i in 0..partials.len() { - let mut y: f64 = partials[i]; - if x.abs() < y.abs() { - mem::swap(&mut x, &mut y); - } - // Rounded `x+y` is stored in `hi` with round-off stored in - // `lo`. Together `hi+lo` are exactly equal to `x+y`. - let hi = x + y; - let lo = y - (hi - x); - if lo != 0.0 { - partials[j] = lo; - j += 1; - } - x = hi; - } - if j >= partials.len() { - partials.push(x); - } else { - partials[j] = x; - partials.truncate(j + 1); - } - } - let zero: f64 = 0.0; - partials.iter().fold(zero, |p, q| p + *q) - } - - fn min(&self) -> f64 { - assert!(!self.is_empty()); - self.iter().fold(self[0], |p, q| p.min(*q)) - } - - fn max(&self) -> f64 { - assert!(!self.is_empty()); - self.iter().fold(self[0], |p, q| p.max(*q)) - } - - fn mean(&self) -> f64 { - assert!(!self.is_empty()); - self.sum() / (self.len() as f64) - } - - fn median(&self) -> f64 { - self.percentile(50 as f64) - } - - fn var(&self) -> f64 { - if self.len() < 2 { - 0.0 - } else { - let mean = self.mean(); - let mut v: f64 = 0.0; - for s in self { - let x = *s - mean; - v = v + x * x; - } - // N.B., this is _supposed to be_ len-1, not len. If you - // change it back to len, you will be calculating a - // population variance, not a sample variance. - let denom = (self.len() - 1) as f64; - v / denom - } - } - - fn std_dev(&self) -> f64 { - self.var().sqrt() - } - - fn std_dev_pct(&self) -> f64 { - let hundred = 100 as f64; - (self.std_dev() / self.mean()) * hundred - } - - fn median_abs_dev(&self) -> f64 { - let med = self.median(); - let abs_devs: Vec = self.iter().map(|&v| (med - v).abs()).collect(); - // This constant is derived by smarter statistics brains than me, but it is - // consistent with how R and other packages treat the MAD. - let number = 1.4826; - abs_devs.median() * number - } - - fn median_abs_dev_pct(&self) -> f64 { - let hundred = 100 as f64; - (self.median_abs_dev() / self.median()) * hundred - } - - fn percentile(&self, pct: f64) -> f64 { - let mut tmp = self.to_vec(); - local_sort(&mut tmp); - percentile_of_sorted(&tmp, pct) - } - - fn quartiles(&self) -> (f64, f64, f64) { - let mut tmp = self.to_vec(); - local_sort(&mut tmp); - let first = 25f64; - let a = percentile_of_sorted(&tmp, first); - let second = 50f64; - let b = percentile_of_sorted(&tmp, second); - let third = 75f64; - let c = percentile_of_sorted(&tmp, third); - (a, b, c) - } - - fn iqr(&self) -> f64 { - let (a, _, c) = self.quartiles(); - c - a - } -} - -// Helper function: extract a value representing the `pct` percentile of a sorted sample-set, using -// linear interpolation. If samples are not sorted, return nonsensical value. -fn percentile_of_sorted(sorted_samples: &[f64], pct: f64) -> f64 { - assert!(!sorted_samples.is_empty()); - if sorted_samples.len() == 1 { - return sorted_samples[0]; - } - let zero: f64 = 0.0; - assert!(zero <= pct); - let hundred = 100f64; - assert!(pct <= hundred); - if pct == hundred { - return sorted_samples[sorted_samples.len() - 1]; - } - let length = (sorted_samples.len() - 1) as f64; - let rank = (pct / hundred) * length; - let lrank = rank.floor(); - let d = rank - lrank; - let n = lrank as usize; - let lo = sorted_samples[n]; - let hi = sorted_samples[n + 1]; - lo + (hi - lo) * d -} - -/// Winsorize a set of samples, replacing values above the `100-pct` percentile -/// and below the `pct` percentile with those percentiles themselves. This is a -/// way of minimizing the effect of outliers, at the cost of biasing the sample. -/// It differs from trimming in that it does not change the number of samples, -/// just changes the values of those that are outliers. -/// -/// See: -pub fn winsorize(samples: &mut [f64], pct: f64) { - let mut tmp = samples.to_vec(); - local_sort(&mut tmp); - let lo = percentile_of_sorted(&tmp, pct); - let hundred = 100 as f64; - let hi = percentile_of_sorted(&tmp, hundred - pct); - for samp in samples { - if *samp > hi { - *samp = hi - } else if *samp < lo { - *samp = lo - } - } -} - -// Test vectors generated from R, using the script src/etc/stat-test-vectors.r. - -#[cfg(test)] -mod tests { - use crate::stats::Stats; - use crate::stats::Summary; - use std::f64; - use std::io::prelude::*; - use std::io; - - macro_rules! assert_approx_eq { - ($a: expr, $b: expr) => {{ - let (a, b) = (&$a, &$b); - assert!( - (*a - *b).abs() < 1.0e-6, - "{} is not approximately equal to {}", - *a, - *b - ); - }}; - } - - fn check(samples: &[f64], summ: &Summary) { - let summ2 = Summary::new(samples); - - let mut w = io::sink(); - let w = &mut w; - (write!(w, "\n")).unwrap(); - - assert_eq!(summ.sum, summ2.sum); - assert_eq!(summ.min, summ2.min); - assert_eq!(summ.max, summ2.max); - assert_eq!(summ.mean, summ2.mean); - assert_eq!(summ.median, summ2.median); - - // We needed a few more digits to get exact equality on these - // but they're within float epsilon, which is 1.0e-6. - assert_approx_eq!(summ.var, summ2.var); - assert_approx_eq!(summ.std_dev, summ2.std_dev); - assert_approx_eq!(summ.std_dev_pct, summ2.std_dev_pct); - assert_approx_eq!(summ.median_abs_dev, summ2.median_abs_dev); - assert_approx_eq!(summ.median_abs_dev_pct, summ2.median_abs_dev_pct); - - assert_eq!(summ.quartiles, summ2.quartiles); - assert_eq!(summ.iqr, summ2.iqr); - } - - #[test] - fn test_min_max_nan() { - let xs = &[1.0, 2.0, f64::NAN, 3.0, 4.0]; - let summary = Summary::new(xs); - assert_eq!(summary.min, 1.0); - assert_eq!(summary.max, 4.0); - } - - #[test] - fn test_norm2() { - let val = &[958.0000000000, 924.0000000000]; - let summ = &Summary { - sum: 1882.0000000000, - min: 924.0000000000, - max: 958.0000000000, - mean: 941.0000000000, - median: 941.0000000000, - var: 578.0000000000, - std_dev: 24.0416305603, - std_dev_pct: 2.5549022912, - median_abs_dev: 25.2042000000, - median_abs_dev_pct: 2.6784484591, - quartiles: (932.5000000000, 941.0000000000, 949.5000000000), - iqr: 17.0000000000, - }; - check(val, summ); - } - #[test] - fn test_norm10narrow() { - let val = &[ - 966.0000000000, - 985.0000000000, - 1110.0000000000, - 848.0000000000, - 821.0000000000, - 975.0000000000, - 962.0000000000, - 1157.0000000000, - 1217.0000000000, - 955.0000000000, - ]; - let summ = &Summary { - sum: 9996.0000000000, - min: 821.0000000000, - max: 1217.0000000000, - mean: 999.6000000000, - median: 970.5000000000, - var: 16050.7111111111, - std_dev: 126.6914010938, - std_dev_pct: 12.6742097933, - median_abs_dev: 102.2994000000, - median_abs_dev_pct: 10.5408964451, - quartiles: (956.7500000000, 970.5000000000, 1078.7500000000), - iqr: 122.0000000000, - }; - check(val, summ); - } - #[test] - fn test_norm10medium() { - let val = &[ - 954.0000000000, - 1064.0000000000, - 855.0000000000, - 1000.0000000000, - 743.0000000000, - 1084.0000000000, - 704.0000000000, - 1023.0000000000, - 357.0000000000, - 869.0000000000, - ]; - let summ = &Summary { - sum: 8653.0000000000, - min: 357.0000000000, - max: 1084.0000000000, - mean: 865.3000000000, - median: 911.5000000000, - var: 48628.4555555556, - std_dev: 220.5186059170, - std_dev_pct: 25.4846418487, - median_abs_dev: 195.7032000000, - median_abs_dev_pct: 21.4704552935, - quartiles: (771.0000000000, 911.5000000000, 1017.2500000000), - iqr: 246.2500000000, - }; - check(val, summ); - } - #[test] - fn test_norm10wide() { - let val = &[ - 505.0000000000, - 497.0000000000, - 1591.0000000000, - 887.0000000000, - 1026.0000000000, - 136.0000000000, - 1580.0000000000, - 940.0000000000, - 754.0000000000, - 1433.0000000000, - ]; - let summ = &Summary { - sum: 9349.0000000000, - min: 136.0000000000, - max: 1591.0000000000, - mean: 934.9000000000, - median: 913.5000000000, - var: 239208.9888888889, - std_dev: 489.0899599142, - std_dev_pct: 52.3146817750, - median_abs_dev: 611.5725000000, - median_abs_dev_pct: 66.9482758621, - quartiles: (567.2500000000, 913.5000000000, 1331.2500000000), - iqr: 764.0000000000, - }; - check(val, summ); - } - #[test] - fn test_norm25verynarrow() { - let val = &[ - 991.0000000000, - 1018.0000000000, - 998.0000000000, - 1013.0000000000, - 974.0000000000, - 1007.0000000000, - 1014.0000000000, - 999.0000000000, - 1011.0000000000, - 978.0000000000, - 985.0000000000, - 999.0000000000, - 983.0000000000, - 982.0000000000, - 1015.0000000000, - 1002.0000000000, - 977.0000000000, - 948.0000000000, - 1040.0000000000, - 974.0000000000, - 996.0000000000, - 989.0000000000, - 1015.0000000000, - 994.0000000000, - 1024.0000000000, - ]; - let summ = &Summary { - sum: 24926.0000000000, - min: 948.0000000000, - max: 1040.0000000000, - mean: 997.0400000000, - median: 998.0000000000, - var: 393.2066666667, - std_dev: 19.8294393937, - std_dev_pct: 1.9888308788, - median_abs_dev: 22.2390000000, - median_abs_dev_pct: 2.2283567134, - quartiles: (983.0000000000, 998.0000000000, 1013.0000000000), - iqr: 30.0000000000, - }; - check(val, summ); - } - #[test] - fn test_exp10a() { - let val = &[ - 23.0000000000, - 11.0000000000, - 2.0000000000, - 57.0000000000, - 4.0000000000, - 12.0000000000, - 5.0000000000, - 29.0000000000, - 3.0000000000, - 21.0000000000, - ]; - let summ = &Summary { - sum: 167.0000000000, - min: 2.0000000000, - max: 57.0000000000, - mean: 16.7000000000, - median: 11.5000000000, - var: 287.7888888889, - std_dev: 16.9643416875, - std_dev_pct: 101.5828843560, - median_abs_dev: 13.3434000000, - median_abs_dev_pct: 116.0295652174, - quartiles: (4.2500000000, 11.5000000000, 22.5000000000), - iqr: 18.2500000000, - }; - check(val, summ); - } - #[test] - fn test_exp10b() { - let val = &[ - 24.0000000000, - 17.0000000000, - 6.0000000000, - 38.0000000000, - 25.0000000000, - 7.0000000000, - 51.0000000000, - 2.0000000000, - 61.0000000000, - 32.0000000000, - ]; - let summ = &Summary { - sum: 263.0000000000, - min: 2.0000000000, - max: 61.0000000000, - mean: 26.3000000000, - median: 24.5000000000, - var: 383.5666666667, - std_dev: 19.5848580967, - std_dev_pct: 74.4671410520, - median_abs_dev: 22.9803000000, - median_abs_dev_pct: 93.7971428571, - quartiles: (9.5000000000, 24.5000000000, 36.5000000000), - iqr: 27.0000000000, - }; - check(val, summ); - } - #[test] - fn test_exp10c() { - let val = &[ - 71.0000000000, - 2.0000000000, - 32.0000000000, - 1.0000000000, - 6.0000000000, - 28.0000000000, - 13.0000000000, - 37.0000000000, - 16.0000000000, - 36.0000000000, - ]; - let summ = &Summary { - sum: 242.0000000000, - min: 1.0000000000, - max: 71.0000000000, - mean: 24.2000000000, - median: 22.0000000000, - var: 458.1777777778, - std_dev: 21.4050876611, - std_dev_pct: 88.4507754589, - median_abs_dev: 21.4977000000, - median_abs_dev_pct: 97.7168181818, - quartiles: (7.7500000000, 22.0000000000, 35.0000000000), - iqr: 27.2500000000, - }; - check(val, summ); - } - #[test] - fn test_exp25() { - let val = &[ - 3.0000000000, - 24.0000000000, - 1.0000000000, - 19.0000000000, - 7.0000000000, - 5.0000000000, - 30.0000000000, - 39.0000000000, - 31.0000000000, - 13.0000000000, - 25.0000000000, - 48.0000000000, - 1.0000000000, - 6.0000000000, - 42.0000000000, - 63.0000000000, - 2.0000000000, - 12.0000000000, - 108.0000000000, - 26.0000000000, - 1.0000000000, - 7.0000000000, - 44.0000000000, - 25.0000000000, - 11.0000000000, - ]; - let summ = &Summary { - sum: 593.0000000000, - min: 1.0000000000, - max: 108.0000000000, - mean: 23.7200000000, - median: 19.0000000000, - var: 601.0433333333, - std_dev: 24.5161851301, - std_dev_pct: 103.3565983562, - median_abs_dev: 19.2738000000, - median_abs_dev_pct: 101.4410526316, - quartiles: (6.0000000000, 19.0000000000, 31.0000000000), - iqr: 25.0000000000, - }; - check(val, summ); - } - #[test] - fn test_binom25() { - let val = &[ - 18.0000000000, - 17.0000000000, - 27.0000000000, - 15.0000000000, - 21.0000000000, - 25.0000000000, - 17.0000000000, - 24.0000000000, - 25.0000000000, - 24.0000000000, - 26.0000000000, - 26.0000000000, - 23.0000000000, - 15.0000000000, - 23.0000000000, - 17.0000000000, - 18.0000000000, - 18.0000000000, - 21.0000000000, - 16.0000000000, - 15.0000000000, - 31.0000000000, - 20.0000000000, - 17.0000000000, - 15.0000000000, - ]; - let summ = &Summary { - sum: 514.0000000000, - min: 15.0000000000, - max: 31.0000000000, - mean: 20.5600000000, - median: 20.0000000000, - var: 20.8400000000, - std_dev: 4.5650848842, - std_dev_pct: 22.2037202539, - median_abs_dev: 5.9304000000, - median_abs_dev_pct: 29.6520000000, - quartiles: (17.0000000000, 20.0000000000, 24.0000000000), - iqr: 7.0000000000, - }; - check(val, summ); - } - #[test] - fn test_pois25lambda30() { - let val = &[ - 27.0000000000, - 33.0000000000, - 34.0000000000, - 34.0000000000, - 24.0000000000, - 39.0000000000, - 28.0000000000, - 27.0000000000, - 31.0000000000, - 28.0000000000, - 38.0000000000, - 21.0000000000, - 33.0000000000, - 36.0000000000, - 29.0000000000, - 37.0000000000, - 32.0000000000, - 34.0000000000, - 31.0000000000, - 39.0000000000, - 25.0000000000, - 31.0000000000, - 32.0000000000, - 40.0000000000, - 24.0000000000, - ]; - let summ = &Summary { - sum: 787.0000000000, - min: 21.0000000000, - max: 40.0000000000, - mean: 31.4800000000, - median: 32.0000000000, - var: 26.5933333333, - std_dev: 5.1568724372, - std_dev_pct: 16.3814245145, - median_abs_dev: 5.9304000000, - median_abs_dev_pct: 18.5325000000, - quartiles: (28.0000000000, 32.0000000000, 34.0000000000), - iqr: 6.0000000000, - }; - check(val, summ); - } - #[test] - fn test_pois25lambda40() { - let val = &[ - 42.0000000000, - 50.0000000000, - 42.0000000000, - 46.0000000000, - 34.0000000000, - 45.0000000000, - 34.0000000000, - 49.0000000000, - 39.0000000000, - 28.0000000000, - 40.0000000000, - 35.0000000000, - 37.0000000000, - 39.0000000000, - 46.0000000000, - 44.0000000000, - 32.0000000000, - 45.0000000000, - 42.0000000000, - 37.0000000000, - 48.0000000000, - 42.0000000000, - 33.0000000000, - 42.0000000000, - 48.0000000000, - ]; - let summ = &Summary { - sum: 1019.0000000000, - min: 28.0000000000, - max: 50.0000000000, - mean: 40.7600000000, - median: 42.0000000000, - var: 34.4400000000, - std_dev: 5.8685603004, - std_dev_pct: 14.3978417577, - median_abs_dev: 5.9304000000, - median_abs_dev_pct: 14.1200000000, - quartiles: (37.0000000000, 42.0000000000, 45.0000000000), - iqr: 8.0000000000, - }; - check(val, summ); - } - #[test] - fn test_pois25lambda50() { - let val = &[ - 45.0000000000, - 43.0000000000, - 44.0000000000, - 61.0000000000, - 51.0000000000, - 53.0000000000, - 59.0000000000, - 52.0000000000, - 49.0000000000, - 51.0000000000, - 51.0000000000, - 50.0000000000, - 49.0000000000, - 56.0000000000, - 42.0000000000, - 52.0000000000, - 51.0000000000, - 43.0000000000, - 48.0000000000, - 48.0000000000, - 50.0000000000, - 42.0000000000, - 43.0000000000, - 42.0000000000, - 60.0000000000, - ]; - let summ = &Summary { - sum: 1235.0000000000, - min: 42.0000000000, - max: 61.0000000000, - mean: 49.4000000000, - median: 50.0000000000, - var: 31.6666666667, - std_dev: 5.6273143387, - std_dev_pct: 11.3913245723, - median_abs_dev: 4.4478000000, - median_abs_dev_pct: 8.8956000000, - quartiles: (44.0000000000, 50.0000000000, 52.0000000000), - iqr: 8.0000000000, - }; - check(val, summ); - } - #[test] - fn test_unif25() { - let val = &[ - 99.0000000000, - 55.0000000000, - 92.0000000000, - 79.0000000000, - 14.0000000000, - 2.0000000000, - 33.0000000000, - 49.0000000000, - 3.0000000000, - 32.0000000000, - 84.0000000000, - 59.0000000000, - 22.0000000000, - 86.0000000000, - 76.0000000000, - 31.0000000000, - 29.0000000000, - 11.0000000000, - 41.0000000000, - 53.0000000000, - 45.0000000000, - 44.0000000000, - 98.0000000000, - 98.0000000000, - 7.0000000000, - ]; - let summ = &Summary { - sum: 1242.0000000000, - min: 2.0000000000, - max: 99.0000000000, - mean: 49.6800000000, - median: 45.0000000000, - var: 1015.6433333333, - std_dev: 31.8691595957, - std_dev_pct: 64.1488719719, - median_abs_dev: 45.9606000000, - median_abs_dev_pct: 102.1346666667, - quartiles: (29.0000000000, 45.0000000000, 79.0000000000), - iqr: 50.0000000000, - }; - check(val, summ); - } - - #[test] - fn test_sum_f64s() { - assert_eq!([0.5f64, 3.2321f64, 1.5678f64].sum(), 5.2999); - } - #[test] - fn test_sum_f64_between_ints_that_sum_to_0() { - assert_eq!([1e30f64, 1.2f64, -1e30f64].sum(), 1.2); - } -} - -#[cfg(test)] -mod bench { - extern crate test; - use self::test::Bencher; - use crate::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) {} -} From 2957c4c42eb282dd0c81f5231ed76235e08b4882 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Sat, 2 Mar 2019 19:40:15 +0100 Subject: [PATCH 02/12] Re-export libtest --- src/libtest/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/libtest/lib.rs diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs new file mode 100644 index 00000000000..17150046108 --- /dev/null +++ b/src/libtest/lib.rs @@ -0,0 +1,17 @@ +//! Support code for rustc's built in unit-test and micro-benchmarking +//! framework. +//! +//! Almost all user code will only be interested in `Bencher` and +//! `black_box`. All other interactions (such as writing tests and +//! benchmarks themselves) should be done via the `#[test]` and +//! `#[bench]` attributes. +//! +//! See the [Testing Chapter](../book/ch11-00-testing.html) of the book for more details. + +#![crate_name = "test"] +#![unstable(feature = "test", issue = "27812")] +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/", + test(attr(deny(warnings))))] + +extern crate libtest; +pub use libtest::*; From 3a9e30f6c9741f6fc3fa4eae320d56730c21e15b Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Sun, 3 Mar 2019 14:58:13 +0100 Subject: [PATCH 03/12] Use libtest from crates.io --- src/libtest/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtest/Cargo.toml b/src/libtest/Cargo.toml index a9a145c081f..26ac7888184 100644 --- a/src/libtest/Cargo.toml +++ b/src/libtest/Cargo.toml @@ -10,7 +10,7 @@ path = "lib.rs" crate-type = ["dylib", "rlib"] [dependencies] -libtest = { version = "0.0.0", git = "https://github.com/gnzlbg/libtest" } +libtest = { version = "0.0.1" } # not actually used but needed to always have proc_macro in the sysroot proc_macro = { path = "../libproc_macro" } From f2915a6f620ec110508bc01f63eeb9473ba6ae32 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Sun, 3 Mar 2019 15:50:52 +0100 Subject: [PATCH 04/12] Use feature(test) --- src/libtest/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 17150046108..2fdc85d41ca 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -12,6 +12,7 @@ #![unstable(feature = "test", issue = "27812")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))] +#![feature(test)] extern crate libtest; pub use libtest::*; From 6dbb5f7104ac5ab0d8ac44becfa4c1169687ba1a Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Sun, 3 Mar 2019 16:32:27 +0100 Subject: [PATCH 05/12] Update Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index f13245736f6..7fa0ee17508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4294,4 +4294,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" "checksum xz2 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "df8bf41d3030c3577c9458fd6640a05afbf43b150d0b531b16bd77d3f794f27a" -"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" +"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" \ No newline at end of file From 4c38f1928ebb8b03e1fa4fb336025bfe0e77cde4 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Sun, 3 Mar 2019 17:39:57 +0100 Subject: [PATCH 06/12] Allow the staged_api --- src/libtest/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 2fdc85d41ca..82e44ba4873 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -12,6 +12,7 @@ #![unstable(feature = "test", issue = "27812")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))] +#![feature(staged_api)] #![feature(test)] extern crate libtest; From 008ce9902828eb3cad0b2b1019ab1960fbb9300a Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Mon, 4 Mar 2019 17:45:28 +0100 Subject: [PATCH 07/12] Add a README to libtest with a roadmap --- src/libtest/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/libtest/README.md diff --git a/src/libtest/README.md b/src/libtest/README.md new file mode 100644 index 00000000000..d5e7ad0613b --- /dev/null +++ b/src/libtest/README.md @@ -0,0 +1,30 @@ +WIP - stable libtest +=== + +The migration of libtest to stable Rust is currently in progress. + +You can find libtest at: https://github.com/rust-lang/libtest . If you need to +make a change: + +* perform the change there, +* do a new crates.io release, and +* send a PR to rust-lang/rust bumping the libtest version. + +## Roadmap + +Right now all the contests of libtest live in the external repo. + +The next steps are: + +* make `#[test]` and `#[ignore]` procedural macros in the prelude by default, + routed to the same procedural macro, so that it doesn't matter which one runs + first. +* move the unstable APIs back into rust-lang/rust to help maintainability + (replacing `pub use libtest::*` with explicit imports) +* migrate libtest to the real `term` crate +* provide `libtest` a real `custom_test_framework` runner (in parallel with the + runner in rust-lang/rust) +* set up `libtest` as a normal `custom_test_framework` inside rust-lang/rust +* refactor the internal structure of `libtest` to make it re-usable by + third-party custom test frameworks (think test formatting, benchmark + formatting, argument parsing, json format serialization, etc.) From 25c8f61a9f84ecd546dd53b6b891c53d7bcd246a Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Mon, 4 Mar 2019 18:04:08 +0100 Subject: [PATCH 08/12] Move black_box back to rust-lang/libtest and use explicit imports --- src/libtest/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 82e44ba4873..c15ed308ff0 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -12,8 +12,27 @@ #![unstable(feature = "test", issue = "27812")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))] +#![feature(asm)] #![feature(staged_api)] #![feature(test)] extern crate libtest; -pub use libtest::*; +pub use libtest::{test_main_static, TestDescAndFn, StaticTestFn, StaticBenchFn, Options}; + +/// A function that is opaque to the optimizer, to allow benchmarks to +/// pretend to use outputs to assist in avoiding dead-code +/// elimination. +/// +/// This function is a no-op, and does not even read from `dummy`. +#[cfg(not(any(target_arch = "asmjs", target_arch = "wasm32")))] +pub fn black_box(dummy: T) -> T { + // we need to "use" the argument in some way LLVM can't + // introspect. + unsafe { asm!("" : : "r"(&dummy)) } + dummy +} +#[cfg(any(target_arch = "asmjs", target_arch = "wasm32"))] +#[inline(never)] +pub fn black_box(dummy: T) -> T { + dummy +} From a5e7f0c75b5db5c0ded0b46437ba17673cb98aa9 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Mon, 4 Mar 2019 18:06:07 +0100 Subject: [PATCH 09/12] Add missing explicit imports --- src/libtest/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index c15ed308ff0..bd7f89c3f66 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -17,7 +17,12 @@ #![feature(test)] extern crate libtest; -pub use libtest::{test_main_static, TestDescAndFn, StaticTestFn, StaticBenchFn, Options}; +pub use libtest::{ + 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, +}; /// A function that is opaque to the optimizer, to allow benchmarks to /// pretend to use outputs to assist in avoiding dead-code From cf5c3600648cb2acf42aaa4af5822b4489c86e01 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Mon, 4 Mar 2019 18:17:41 +0100 Subject: [PATCH 10/12] Export stats::Summary from libtest --- src/libtest/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index bd7f89c3f66..3fcba0f5791 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -17,11 +17,14 @@ #![feature(test)] extern crate libtest; + +// FIXME: we should be more explicit about the exact APIs that we +// export to users. pub use libtest::{ 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, + TestResult, TrFailed, TrFailedMsg, TrIgnored, TrOk, stats::Summary }; /// A function that is opaque to the optimizer, to allow benchmarks to From 144bdc68308598968d732272687679afc288961c Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Mon, 4 Mar 2019 18:29:23 +0100 Subject: [PATCH 11/12] Directly reference the roadmap upstream --- Cargo.lock | 25 ++++++++++++++++++------- src/libtest/README.md | 19 +------------------ 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fa0ee17508..d32d07569e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1298,6 +1298,15 @@ dependencies = [ "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "libtest" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_term 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libz-sys" version = "1.0.25" @@ -2939,6 +2948,11 @@ dependencies = [ "serialize 0.0.0", ] +[[package]] +name = "rustc_term" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc_tools_util" version = "0.1.1" @@ -3421,10 +3435,6 @@ dependencies = [ "utf-8 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "term" -version = "0.0.0" - [[package]] name = "term" version = "0.4.6" @@ -3465,9 +3475,8 @@ dependencies = [ name = "test" version = "0.0.0" dependencies = [ - "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "libtest 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "proc_macro 0.0.0", - "term 0.0.0", ] [[package]] @@ -4089,6 +4098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum libgit2-sys 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "48441cb35dc255da8ae72825689a95368bf510659ae1ad55dc4aa88cb1789bf1" "checksum libnghttp2-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d75d7966bda4730b722d1eab8e668df445368a24394bae9fc1e8dc0ab3dbe4f4" "checksum libssh2-sys 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "126a1f4078368b163bfdee65fbab072af08a1b374a5551b21e87ade27b1fbf9d" +"checksum libtest 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a51ac59582b915cdfc426dada72c6d9eba95818a6b481ca340f5c7152166837" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum lock_api 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "949826a5ccf18c1b3a7c3d57692778d21768b79e46eb9dd07bfc4c2160036c54" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" @@ -4199,6 +4209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rustc-rayon 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d98c51d9cbbe810c8b6693236d3412d8cd60513ff27a3e1b6af483dca0af544" "checksum rustc-rayon-core 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "526e7b6d2707a5b9bec3927d424ad70fa3cfc68e0ac1b75e46cdbbc95adc5108" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +"checksum rustc_term 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c69abe7f181d2ea8d2f7b44a4aa86f4b4a567444bcfcf51ed45ede957fbf064" "checksum rustc_tools_util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c5a95edfa0c893236ae4778bb7c4752760e4c0d245e19b5eff33c5aa5eb9dc" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustfix 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "af7c21531a91512a4a51b490be6ba1c8eff34fdda0dc5bf87dc28d86748aac56" @@ -4294,4 +4305,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" "checksum xz2 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "df8bf41d3030c3577c9458fd6640a05afbf43b150d0b531b16bd77d3f794f27a" -"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" \ No newline at end of file +"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" diff --git a/src/libtest/README.md b/src/libtest/README.md index d5e7ad0613b..6d9fe30dcad 100644 --- a/src/libtest/README.md +++ b/src/libtest/README.md @@ -10,21 +10,4 @@ make a change: * do a new crates.io release, and * send a PR to rust-lang/rust bumping the libtest version. -## Roadmap - -Right now all the contests of libtest live in the external repo. - -The next steps are: - -* make `#[test]` and `#[ignore]` procedural macros in the prelude by default, - routed to the same procedural macro, so that it doesn't matter which one runs - first. -* move the unstable APIs back into rust-lang/rust to help maintainability - (replacing `pub use libtest::*` with explicit imports) -* migrate libtest to the real `term` crate -* provide `libtest` a real `custom_test_framework` runner (in parallel with the - runner in rust-lang/rust) -* set up `libtest` as a normal `custom_test_framework` inside rust-lang/rust -* refactor the internal structure of `libtest` to make it re-usable by - third-party custom test frameworks (think test formatting, benchmark - formatting, argument parsing, json format serialization, etc.) +The roadmap of the migration is being tracked here: https://github.com/rust-lang/libtest/issues/2 From 1446b242cec21f896e10360c8f80ee047dbe24c8 Mon Sep 17 00:00:00 2001 From: gnzlbg Date: Tue, 19 Mar 2019 19:25:30 +0100 Subject: [PATCH 12/12] Remove libterm from bootstrap --- src/bootstrap/dist.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 2c40dd6d296..5dc326519c2 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -899,7 +899,6 @@ impl Step for Src { "src/libstd", "src/libunwind", "src/libtest", - "src/libterm", "src/libprofiler_builtins", "src/stdsimd", "src/libproc_macro",