]> git.lizzy.rs Git - rust.git/commitdiff
Auto merge of #59766 - xales:revertlibtest, r=Manishearth
authorbors <bors@rust-lang.org>
Sun, 7 Apr 2019 15:23:49 +0000 (15:23 +0000)
committerbors <bors@rust-lang.org>
Sun, 7 Apr 2019 15:23:49 +0000 (15:23 +0000)
Revert "Auto merge of #57842 - gnzlbg:extract_libtest, r=gnzlbg"

This reverts commit 3eb4890dfe6db0279fdd3cda19f9643873ae3db9, reversing
changes made to 7a4df3b53da369110984a2b57419c05a53e33b38.

This is, as best I can tell, a clean revert of #57842. It retains some interim changes, like moving `black_box`, and otherwise brings libtest back in as it was before removal.

17 files changed:
Cargo.lock
src/bootstrap/dist.rs
src/libterm/Cargo.toml [new file with mode: 0644]
src/libterm/lib.rs [new file with mode: 0644]
src/libterm/terminfo/mod.rs [new file with mode: 0644]
src/libterm/terminfo/parm.rs [new file with mode: 0644]
src/libterm/terminfo/parser/compiled.rs [new file with mode: 0644]
src/libterm/terminfo/searcher.rs [new file with mode: 0644]
src/libterm/win.rs [new file with mode: 0644]
src/libtest/Cargo.toml
src/libtest/README.md [deleted file]
src/libtest/formatters/json.rs [new file with mode: 0644]
src/libtest/formatters/mod.rs [new file with mode: 0644]
src/libtest/formatters/pretty.rs [new file with mode: 0644]
src/libtest/formatters/terse.rs [new file with mode: 0644]
src/libtest/lib.rs
src/libtest/stats.rs [new file with mode: 0644]

index 3cc0026bf4589d1a315110ca8d46bdf05825e867..0256132be7db746d8f952c5a6b1674e2b4d6c304 100644 (file)
@@ -1319,15 +1319,6 @@ 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"
@@ -2952,11 +2943,6 @@ 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"
@@ -3439,6 +3425,10 @@ 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"
@@ -3479,8 +3469,9 @@ dependencies = [
 name = "test"
 version = "0.0.0"
 dependencies = [
- "libtest 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc_macro 0.0.0",
+ "term 0.0.0",
 ]
 
 [[package]]
@@ -4113,7 +4104,6 @@ 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"
@@ -4222,7 +4212,6 @@ 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"
index a923f2d2ca37e102018ce94f5361259cd893acdf..61a7705bd6cc6796cd8716ed6cc3b6c22b66c53c 100644 (file)
@@ -901,6 +901,7 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
             "src/libstd",
             "src/libunwind",
             "src/libtest",
+            "src/libterm",
             "src/libprofiler_builtins",
             "src/stdsimd",
             "src/libproc_macro",
diff --git a/src/libterm/Cargo.toml b/src/libterm/Cargo.toml
new file mode 100644 (file)
index 0000000..4eba9a9
--- /dev/null
@@ -0,0 +1,10 @@
+[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
new file mode 100644 (file)
index 0000000..711716d
--- /dev/null
@@ -0,0 +1,201 @@
+//! 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<Output = Stdout> + Send;
+/// Alias for stderr terminals.
+pub type StderrTerminal = dyn Terminal<Output = Stderr> + Send;
+
+#[cfg(not(windows))]
+/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be
+/// opened.
+pub fn stdout() -> Option<Box<StdoutTerminal>> {
+    TerminfoTerminal::new(io::stdout()).map(|t| Box::new(t) as Box<StdoutTerminal>)
+}
+
+#[cfg(windows)]
+/// Returns a Terminal wrapping stdout, or None if a terminal couldn't be
+/// opened.
+pub fn stdout() -> Option<Box<StdoutTerminal>> {
+    TerminfoTerminal::new(io::stdout())
+        .map(|t| Box::new(t) as Box<StdoutTerminal>)
+        .or_else(|| WinConsole::new(io::stdout()).ok().map(|t| Box::new(t) as Box<StdoutTerminal>))
+}
+
+#[cfg(not(windows))]
+/// Returns a Terminal wrapping stderr, or None if a terminal couldn't be
+/// opened.
+pub fn stderr() -> Option<Box<StderrTerminal>> {
+    TerminfoTerminal::new(io::stderr()).map(|t| Box::new(t) as Box<StderrTerminal>)
+}
+
+#[cfg(windows)]
+/// Returns a Terminal wrapping stderr, or None if a terminal couldn't be
+/// opened.
+pub fn stderr() -> Option<Box<StderrTerminal>> {
+    TerminfoTerminal::new(io::stderr())
+        .map(|t| Box::new(t) as Box<StderrTerminal>)
+        .or_else(|| WinConsole::new(io::stderr()).ok().map(|t| Box::new(t) as Box<StderrTerminal>))
+}
+
+
+/// 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<bool>;
+
+    /// 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<bool>;
+
+    /// 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<bool>;
+
+    /// 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<bool>;
+
+    /// 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
new file mode 100644 (file)
index 0000000..be90195
--- /dev/null
@@ -0,0 +1,265 @@
+//! 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<String>,
+    /// Map of capability name to boolean value
+    pub bools: HashMap<String, bool>,
+    /// Map of capability name to numeric value
+    pub numbers: HashMap<String, u16>,
+    /// Map of capability name to raw (unexpanded) string
+    pub strings: HashMap<String, Vec<u8>>,
+}
+
+/// 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<TermInfo, Error> {
+        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<TermInfo, Error> {
+        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<P: AsRef<Path>>(path: P) -> Result<TermInfo, Error> {
+        Self::_from_path(path.as_ref())
+    }
+    // Keep the metadata small
+    fn _from_path(path: &Path) -> Result<TermInfo, Error> {
+        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<T> {
+    num_colors: u16,
+    out: T,
+    ti: TermInfo,
+}
+
+impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
+    type Output = T;
+    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+        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<bool> {
+        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<bool> {
+        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<bool> {
+        // 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<T: Write + Send> TerminfoTerminal<T> {
+    /// Creates a new TerminfoTerminal with the given TermInfo and Write.
+    pub fn new_with_terminfo(out: T, terminfo: TermInfo) -> TerminfoTerminal<T> {
+        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<TerminfoTerminal<T>> {
+        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<bool> {
+        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<T: Write> Write for TerminfoTerminal<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        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
new file mode 100644 (file)
index 0000000..28229bd
--- /dev/null
@@ -0,0 +1,669 @@
+//! 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<Vec<u8>, 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<Param> = 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<Vec<u8>, 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::<Vec<_>>());
+    }
+
+    #[test]
+    fn test_multiple_int_constants() {
+        assert_eq!(expand(b"%{1}%{2}%d%d", &[], &mut Variables::new()).unwrap(),
+                   "21".bytes().collect::<Vec<_>>());
+    }
+
+    #[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::<Vec<_>>()));
+        assert_eq!(expand(b"%p1%d%p2%d%i%p1%d%p2%d", &[], &mut vars),
+                   Ok("0011".bytes().collect::<Vec<_>>()));
+    }
+
+    #[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<Vec<u8>, 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::<Vec<_>>());
+        let res = expand(s, &[Number(8)], &mut vars);
+        assert!(res.is_ok(), res.unwrap_err());
+        assert_eq!(res.unwrap(), "\\E[90m".bytes().collect::<Vec<_>>());
+        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::<Vec<_>>());
+    }
+
+    #[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::<Vec<_>>()));
+        assert_eq!(expand(b"%p1%:-4.2s", &[Words("foo".to_string())], vars),
+                   Ok("fo  ".bytes().collect::<Vec<_>>()));
+
+        assert_eq!(expand(b"%p1%d%p1%.3d%p1%5d%p1%:+d", &[Number(1)], vars),
+                   Ok("1001    1+1".bytes().collect::<Vec<_>>()));
+        assert_eq!(expand(b"%p1%o%p1%#o%p2%6.4x%p2%#6.4X",
+                          &[Number(15), Number(27)],
+                          vars),
+                   Ok("17017  001b0X001B".bytes().collect::<Vec<_>>()));
+    }
+}
diff --git a/src/libterm/terminfo/parser/compiled.rs b/src/libterm/terminfo/parser/compiled.rs
new file mode 100644 (file)
index 0000000..05a8c94
--- /dev/null
@@ -0,0 +1,346 @@
+#![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<u16> {
+    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<u8> {
+    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<TermInfo, String> {
+    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<String> = 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<String, bool> = 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<String, u16> = 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<String, Vec<u8>> = if string_offsets_count > 0 {
+        let string_offsets: Vec<u16> = 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
new file mode 100644 (file)
index 0000000..0b17ed3
--- /dev/null
@@ -0,0 +1,84 @@
+//! 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<PathBuf> {
+    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
new file mode 100644 (file)
index 0000000..6d42b01
--- /dev/null
@@ -0,0 +1,203 @@
+//! 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<T> {
+    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<T: Write + Send + 'static> WinConsole<T> {
+    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<WinConsole<T>> {
+        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<T: Write> Write for WinConsole<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.buf.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.buf.flush()
+    }
+}
+
+impl<T: Write + Send + 'static> Terminal for WinConsole<T> {
+    type Output = T;
+
+    fn fg(&mut self, color: color::Color) -> io::Result<bool> {
+        self.foreground = color;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn bg(&mut self, color: color::Color) -> io::Result<bool> {
+        self.background = color;
+        self.apply();
+
+        Ok(true)
+    }
+
+    fn attr(&mut self, attr: Attr) -> io::Result<bool> {
+        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<bool> {
+        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
+    }
+}
index 26ac788818410c09205e803098454141d9df2b18..10bdd6e877c4f2da1c17c9044cbae0d0da46dfd2 100644 (file)
@@ -10,7 +10,8 @@ path = "lib.rs"
 crate-type = ["dylib", "rlib"]
 
 [dependencies]
-libtest = { version = "0.0.1" }
+getopts = "0.2"
+term = { path = "../libterm" }
 
 # not actually used but needed to always have proc_macro in the sysroot
 proc_macro = { path = "../libproc_macro" }
diff --git a/src/libtest/README.md b/src/libtest/README.md
deleted file mode 100644 (file)
index 6d9fe30..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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.
-
-The roadmap of the migration is being tracked here: https://github.com/rust-lang/libtest/issues/2
diff --git a/src/libtest/formatters/json.rs b/src/libtest/formatters/json.rs
new file mode 100644 (file)
index 0000000..a06497f
--- /dev/null
@@ -0,0 +1,208 @@
+use super::*;
+
+pub(crate) struct JsonFormatter<T> {
+    out: OutputLocation<T>,
+}
+
+impl<T: Write> JsonFormatter<T> {
+    pub fn new(out: OutputLocation<T>) -> 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<String>,
+    ) -> 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<T: Write> OutputFormatter for JsonFormatter<T> {
+    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<bool> {
+        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: AsRef<str>>(S);
+
+impl<S: AsRef<str>> ::std::fmt::Display for EscapedString<S> {
+    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
new file mode 100644 (file)
index 0000000..be5f6a6
--- /dev/null
@@ -0,0 +1,22 @@
+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<bool>;
+}
diff --git a/src/libtest/formatters/pretty.rs b/src/libtest/formatters/pretty.rs
new file mode 100644 (file)
index 0000000..4af0042
--- /dev/null
@@ -0,0 +1,232 @@
+use super::*;
+
+pub(crate) struct PrettyFormatter<T> {
+    out: OutputLocation<T>,
+    use_color: bool,
+
+    /// Number of columns to fill when aligning names
+    max_name_len: usize,
+
+    is_multithreaded: bool,
+}
+
+impl<T: Write> PrettyFormatter<T> {
+    pub fn new(
+        out: OutputLocation<T>,
+        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<T> {
+        &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<S: AsRef<str>>(&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<T: Write> OutputFormatter for PrettyFormatter<T> {
+    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<bool> {
+        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
new file mode 100644 (file)
index 0000000..1400fba
--- /dev/null
@@ -0,0 +1,235 @@
+use super::*;
+
+pub(crate) struct TerseFormatter<T> {
+    out: OutputLocation<T>,
+    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<T: Write> TerseFormatter<T> {
+    pub fn new(
+        out: OutputLocation<T>,
+        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<S: AsRef<str>>(&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<T: Write> OutputFormatter for TerseFormatter<T> {
+    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<bool> {
+        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)
+    }
+}
index 5c91c0ec43b19f812483c65e90aef7108e24f0c0..26612964c308fa8cd50d1cb4d25b4626ad02f5a1 100644 (file)
 //!
 //! 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))))]
+#![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)]
 
-extern crate libtest;
+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<dyn FnBox() + Send>),
+    DynBenchFn(Box<dyn TDynBenchFn + 'static>),
+}
+
+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<stats::Summary>,
+    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<TestDescAndFn>, 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<TestDescAndFn> 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::<Vec<_>>();
+    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<T: Termination>(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<String>,
+    pub filter_exact: bool,
+    pub exclude_should_panic: bool,
+    pub run_ignored: RunIgnored,
+    pub run_tests: bool,
+    pub bench_benchmarks: bool,
+    pub logfile: Option<PathBuf>,
+    pub nocapture: bool,
+    pub color: ColorConfig,
+    pub format: OutputFormat,
+    pub test_threads: Option<usize>,
+    pub skip: Vec<String>,
+    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<TestOpts, String>;
+
+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<OptRes> {
+    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::<usize>() {
+            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<T> {
+    Pretty(Box<term::StdoutTerminal>),
+    Raw(T),
+}
+
+impl<T: Write> Write for OutputLocation<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        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<File>,
+    total: usize,
+    passed: usize,
+    failed: usize,
+    ignored: usize,
+    allowed_fail: usize,
+    filtered_out: usize,
+    measured: usize,
+    metrics: MetricMap,
+    failures: Vec<(TestDesc, Vec<u8>)>,
+    not_failures: Vec<(TestDesc, Vec<u8>)>,
+    options: Options,
+}
+
+impl ConsoleTestState {
+    pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestState> {
+        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<S: AsRef<str>>(&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<TestDescAndFn>) -> 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<TestDescAndFn>) -> io::Result<bool> {
+    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<dyn OutputFormatter> = 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<TestDesc>),
+    TeWait(TestDesc),
+    TeResult(TestDesc, TestResult, Vec<u8>),
+    TeTimeout(TestDesc),
+    TeFilteredOut(usize),
+}
+
+pub type MonitorMsg = (TestDesc, TestResult, Vec<u8>);
 
-// 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, stats::Summary
-};
+struct Sink(Arc<Mutex<Vec<u8>>>);
+impl Write for Sink {
+    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
+        Write::write(&mut *self.0.lock().unwrap(), data)
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+pub fn run_tests<F>(opts: &TestOpts, tests: Vec<TestDescAndFn>, 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<TestDesc, Instant, BuildHasherDefault<collections::hash_map::DefaultHasher>>;
+
+    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::<MonitorMsg>();
+
+    let mut running_tests: TestMap = HashMap::default();
+
+    fn get_timed_out_tests(running_tests: &mut TestMap) -> Vec<TestDesc> {
+        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<Duration> {
+        running_tests.values().min().map(|next_timeout| {
+            let now = Instant::now();
+            if *next_timeout >= now {
+                *next_timeout - now
+            } else {
+                Duration::new(0, 0)
+            }
+        })
+    };
+
+    if 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<usize> = 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<TestDescAndFn>) -> Vec<TestDescAndFn> {
+    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<TestDescAndFn>) -> Vec<TestDescAndFn> {
+    // 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<MonitorMsg>,
+    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<MonitorMsg>,
+        nocapture: bool,
+        testfn: Box<dyn FnBox() + Send>,
+        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: FnOnce()>(f: F) {
+    f()
+}
+
+fn calc_result(desc: &TestDesc, task_result: Result<(), Box<dyn Any + Send>>) -> TestResult {
+    match (&desc.should_panic, task_result) {
+        (&ShouldPanic::No, Ok(())) | (&ShouldPanic::Yes, Err(_)) => TrOk,
+        (&ShouldPanic::YesWithMessage(msg), Err(ref err)) => {
+            if err
+                .downcast_ref::<String>()
+                .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<String, Metric>);
+
+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::<Vec<_>>();
+        v.join(", ")
+    }
+}
+
+// Benchmarking
 
 pub use std::hint::black_box;
 
+impl Bencher {
+    /// Callback for benchmark functions to run in their body.
+    pub fn iter<T, F>(&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<F>(&mut self, mut f: F) -> Option<stats::Summary>
+    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<T, F>(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<T, F>(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<F>(desc: TestDesc, monitor_ch: Sender<MonitorMsg>, 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: 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 libtest::stats::Stats;
+    use crate::Concurrent;
+    use std::sync::mpsc::channel;
 
-    #[bench]
-    pub fn sum_three_items(b: &mut Bencher) {
-        b.iter(|| {
-            [1e20f64, 1.5f64, -1e20f64].sum();
-        })
+    fn one_ignored_one_unignored_test() -> Vec<TestDescAndFn> {
+        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 || {})),
+            },
+        ]
     }
 
-    #[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::<Vec<_>>();
-        b.iter(|| {
-            v.sum();
-        })
+    #[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);
     }
 
-    #[bench]
-    pub fn no_iter(_: &mut Bencher) {}
+    #[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<TestDescAndFn> {
+            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
new file mode 100644 (file)
index 0000000..5c9421d
--- /dev/null
@@ -0,0 +1,922 @@
+#![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: <https://en.wikipedia.org/wiki/Arithmetic_mean>
+    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: <https://en.wikipedia.org/wiki/Median>
+    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: <https://en.wikipedia.org/wiki/Variance>
+    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: <https://en.wikipedia.org/wiki/Standard_deviation>
+    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: <http://en.wikipedia.org/wiki/Median_absolute_deviation>
+    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: <http://en.wikipedia.org/wiki/Percentile>
+    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: <https://en.wikipedia.org/wiki/Quartile>
+    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: <https://en.wikipedia.org/wiki/Interquartile_range>
+    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<f64> = 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: <http://en.wikipedia.org/wiki/Winsorising>
+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::<Vec<_>>();
+
+        b.iter(|| {
+            v.sum();
+        })
+    }
+
+    #[bench]
+    pub fn no_iter(_: &mut Bencher) {}
+}