mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
Revert "Auto merge of #57842 - gnzlbg:extract_libtest, r=gnzlbg"
This reverts commit3eb4890dfe
, reversing changes made to7a4df3b53d
.
This commit is contained in:
parent
dec0a98c4b
commit
28ea249ab5
23
Cargo.lock
23
Cargo.lock
@ -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"
|
||||
|
@ -901,6 +901,7 @@ impl Step for Src {
|
||||
"src/libstd",
|
||||
"src/libunwind",
|
||||
"src/libtest",
|
||||
"src/libterm",
|
||||
"src/libprofiler_builtins",
|
||||
"src/stdsimd",
|
||||
"src/libproc_macro",
|
||||
|
10
src/libterm/Cargo.toml
Normal file
10
src/libterm/Cargo.toml
Normal file
@ -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"]
|
201
src/libterm/lib.rs
Normal file
201
src/libterm/lib.rs
Normal file
@ -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;
|
||||
}
|
265
src/libterm/terminfo/mod.rs
Normal file
265
src/libterm/terminfo/mod.rs
Normal file
@ -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()
|
||||
}
|
||||
}
|
669
src/libterm/terminfo/parm.rs
Normal file
669
src/libterm/terminfo/parm.rs
Normal file
@ -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<_>>()));
|
||||
}
|
||||
}
|
346
src/libterm/terminfo/parser/compiled.rs
Normal file
346
src/libterm/terminfo/parser/compiled.rs
Normal file
@ -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());
|
||||
}
|
||||
}
|
84
src/libterm/terminfo/searcher.rs
Normal file
84
src/libterm/terminfo/searcher.rs
Normal file
@ -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");
|
||||
}
|
203
src/libterm/win.rs
Normal file
203
src/libterm/win.rs
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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" }
|
||||
|
@ -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
|
208
src/libtest/formatters/json.rs
Normal file
208
src/libtest/formatters/json.rs
Normal file
@ -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(())
|
||||
}
|
||||
}
|
22
src/libtest/formatters/mod.rs
Normal file
22
src/libtest/formatters/mod.rs
Normal file
@ -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>;
|
||||
}
|
232
src/libtest/formatters/pretty.rs
Normal file
232
src/libtest/formatters/pretty.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
235
src/libtest/formatters/terse.rs
Normal file
235
src/libtest/formatters/terse.rs
Normal file
@ -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)
|
||||
}
|
||||
}
|
2228
src/libtest/lib.rs
2228
src/libtest/lib.rs
File diff suppressed because it is too large
Load Diff
922
src/libtest/stats.rs
Normal file
922
src/libtest/stats.rs
Normal file
@ -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) {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user