From 01fef7014394ebc4462951f855c8069b0b9c58d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jord=C3=A3o?= Date: Sat, 17 Apr 2021 20:23:48 +0100 Subject: [PATCH 01/16] Changes parameter parsing to clap - Uses clap to parse parameters - Removes of "allow" directive where they are not necessary - Removes of unused variables --- Cargo.lock | 2 -- src/uu/printf/src/cli.rs | 11 ++++------ src/uu/printf/src/printf.rs | 2 -- .../num_format/formatters/base_conv/mod.rs | 10 +++------ .../formatters/cninetyninehexfloatf.rs | 22 +++++++++---------- .../tokenize/num_format/formatters/decf.rs | 7 +++--- .../tokenize/num_format/formatters/floatf.rs | 6 ++--- .../tokenize/num_format/formatters/intf.rs | 4 ++-- .../tokenize/num_format/formatters/scif.rs | 7 +++--- src/uu/printf/src/tokenize/unescaped_text.rs | 6 ++--- 10 files changed, 29 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 430abf921..461716b1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - [[package]] name = "advapi32-sys" version = "0.2.0" diff --git a/src/uu/printf/src/cli.rs b/src/uu/printf/src/cli.rs index 12e80a925..a5e9c9775 100644 --- a/src/uu/printf/src/cli.rs +++ b/src/uu/printf/src/cli.rs @@ -18,18 +18,15 @@ pub fn err_msg(msg: &str) { // by default stdout only flushes // to console when a newline is passed. -#[allow(unused_must_use)] pub fn flush_char(c: char) { print!("{}", c); - stdout().flush(); + let _ = stdout().flush(); } -#[allow(unused_must_use)] pub fn flush_str(s: &str) { print!("{}", s); - stdout().flush(); + let _ = stdout().flush(); } -#[allow(unused_must_use)] pub fn flush_bytes(bslice: &[u8]) { - stdout().write(bslice); - stdout().flush(); + let _ = stdout().write(bslice); + let _ = stdout().flush(); } diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index c2952e5a9..d947a7d83 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -1,5 +1,4 @@ #![allow(dead_code)] - // spell-checker:ignore (change!) each's // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr @@ -9,7 +8,6 @@ mod tokenize; static NAME: &str = "printf"; static VERSION: &str = env!("CARGO_PKG_VERSION"); -static SHORT_USAGE: &str = "printf: usage: printf [-v var] format [arguments]"; static LONGHELP_LEAD: &str = "printf USAGE: printf FORMATSTRING [ARGUMENT]... diff --git a/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs b/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs index 79af9abd5..701fb5dfc 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs @@ -28,8 +28,7 @@ pub fn arrnum_int_mult(arr_num: &[u8], basenum: u8, base_ten_int_fact: u8) -> Ve } } } - #[allow(clippy::map_clone)] - let ret: Vec = ret_rev.iter().rev().map(|x| *x).collect(); + let ret: Vec = ret_rev.into_iter().rev().collect(); ret } @@ -193,8 +192,7 @@ pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec< } } } - #[allow(clippy::map_clone)] - let ret: Vec = ret_rev.iter().rev().map(|x| *x).collect(); + let ret: Vec = ret_rev.into_iter().rev().collect(); ret } @@ -220,8 +218,7 @@ pub fn unsigned_to_arrnum(src: u16) -> Vec { } // temporary needs-improvement-function -#[allow(unused_variables)] -pub fn base_conv_float(src: &[u8], radix_src: u8, radix_dest: u8) -> f64 { +pub fn base_conv_float(src: &[u8], radix_src: u8, _radix_dest: u8) -> f64 { // it would require a lot of addl code // to implement this for arbitrary string input. // until then, the below operates as an outline @@ -269,7 +266,6 @@ pub fn arrnum_to_str(src: &[u8], radix_def_dest: &dyn RadixDef) -> String { str_out } -#[allow(unused_variables)] pub fn base_conv_str( src: &str, radix_def_src: &dyn RadixDef, diff --git a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs index 10e58cc32..f28121d3e 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs @@ -43,13 +43,11 @@ impl Formatter for CninetyNineHexFloatf { // c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around) // on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden. -#[allow(unused_variables)] -#[allow(unused_assignments)] fn get_primitive_hex( inprefix: &InPrefix, - str_in: &str, - analysis: &FloatAnalysis, - last_dec_place: usize, + _str_in: &str, + _analysis: &FloatAnalysis, + _last_dec_place: usize, capitalized: bool, ) -> FormatPrimitive { let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" })); @@ -57,13 +55,13 @@ fn get_primitive_hex( // assign the digits before and after the decimal points // to separate slices. If no digits after decimal point, // assign 0 - let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { - Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), - None => (str_in, "0"), - }; - if first_segment_raw.is_empty() { - first_segment_raw = "0"; - } + //let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { + //Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), + //None => (str_in, "0"), + //}; + //if first_segment_raw.is_empty() { + //first_segment_raw = "0"; + //} // convert to string, hexifying if input is in dec. // let (first_segment, second_segment) = // match inprefix.radix_in { diff --git a/src/uu/printf/src/tokenize/num_format/formatters/decf.rs b/src/uu/printf/src/tokenize/num_format/formatters/decf.rs index 6b2baa890..448771f22 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/decf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/decf.rs @@ -22,12 +22,11 @@ fn get_len_fprim(fprim: &FormatPrimitive) -> usize { len } -pub struct Decf { - as_num: f64, -} +pub struct Decf; + impl Decf { pub fn new() -> Decf { - Decf { as_num: 0.0 } + Decf } } impl Formatter for Decf { diff --git a/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs b/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs index 97ceafe8d..b3de2f98a 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/floatf.rs @@ -5,12 +5,10 @@ use super::super::format_field::FormatField; use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; -pub struct Floatf { - as_num: f64, -} +pub struct Floatf; impl Floatf { pub fn new() -> Floatf { - Floatf { as_num: 0.0 } + Floatf } } impl Formatter for Floatf { diff --git a/src/uu/printf/src/tokenize/num_format/formatters/intf.rs b/src/uu/printf/src/tokenize/num_format/formatters/intf.rs index 9231bd027..2e4e67047 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/intf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/intf.rs @@ -11,7 +11,7 @@ use std::i64; use std::u64; pub struct Intf { - a: u32, + _a: u32, } // see the Intf::analyze() function below @@ -24,7 +24,7 @@ struct IntAnalysis { impl Intf { pub fn new() -> Intf { - Intf { a: 0 } + Intf { _a: 0 } } // take a ref to argument string, and basic information // about prefix (offset, radix, sign), and analyze string diff --git a/src/uu/printf/src/tokenize/num_format/formatters/scif.rs b/src/uu/printf/src/tokenize/num_format/formatters/scif.rs index 69a703042..ebac1565e 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/scif.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/scif.rs @@ -5,12 +5,11 @@ use super::super::format_field::FormatField; use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; -pub struct Scif { - as_num: f64, -} +pub struct Scif; + impl Scif { pub fn new() -> Scif { - Scif { as_num: 0.0 } + Scif } } impl Formatter for Scif { diff --git a/src/uu/printf/src/tokenize/unescaped_text.rs b/src/uu/printf/src/tokenize/unescaped_text.rs index 3b9f0123e..084014ae9 100644 --- a/src/uu/printf/src/tokenize/unescaped_text.rs +++ b/src/uu/printf/src/tokenize/unescaped_text.rs @@ -242,18 +242,16 @@ impl UnescapedText { } } } -#[allow(unused_variables)] impl token::Tokenizer for UnescapedText { fn from_it( it: &mut PutBackN, - args: &mut Peekable>, + _: &mut Peekable>, ) -> Option> { UnescapedText::from_it_core(it, false) } } -#[allow(unused_variables)] impl token::Token for UnescapedText { - fn print(&self, pf_args_it: &mut Peekable>) { + fn print(&self, _: &mut Peekable>) { cli::flush_bytes(&self.0[..]); } } From 158ae35da5d6ef828d7945546b2276137cdff985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jord=C3=A3o?= Date: Mon, 19 Apr 2021 14:21:49 +0100 Subject: [PATCH 02/16] Commented out code removal --- .../num_format/formatters/base_conv/mod.rs | 64 ------------------- .../formatters/cninetyninehexfloatf.rs | 28 -------- 2 files changed, 92 deletions(-) diff --git a/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs b/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs index 701fb5dfc..e3c34df6b 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/base_conv/mod.rs @@ -101,70 +101,6 @@ pub fn arrnum_int_div_step( remainder: rem_out, } } -// pub struct ArrFloat { -// pub leading_zeros: u8, -// pub values: Vec, -// pub basenum: u8 -// } -// -// pub struct ArrFloatDivOut { -// pub quotient: u8, -// pub remainder: ArrFloat -// } -// -// pub fn arrfloat_int_div( -// arrfloat_in : &ArrFloat, -// base_ten_int_divisor : u8, -// precision : u16 -// ) -> DivOut { -// -// let mut remainder = ArrFloat { -// basenum: arrfloat_in.basenum, -// leading_zeros: arrfloat_in.leading_zeroes, -// values: Vec::new() -// } -// let mut quotient = 0; -// -// let mut bufferval : u16 = 0; -// let base : u16 = arrfloat_in.basenum as u16; -// let divisor : u16 = base_ten_int_divisor as u16; -// -// let mut it_f = arrfloat_in.values.iter(); -// let mut position = 0 + arrfloat_in.leading_zeroes as u16; -// let mut at_end = false; -// while position< precision { -// let next_digit = match it_f.next() { -// Some(c) => {} -// None => { 0 } -// } -// match u_cur { -// Some(u) => { -// bufferval += u.clone() as u16; -// if bufferval > divisor { -// while bufferval >= divisor { -// quotient+=1; -// bufferval -= divisor; -// } -// if bufferval == 0 { -// rem_out.position +=1; -// } else { -// rem_out.replace = Some(bufferval as u8); -// } -// break; -// } else { -// bufferval *= base; -// } -// }, -// None => { -// break; -// } -// } -// u_cur = it_f.next().clone(); -// rem_out.position+=1; -// } -// ArrFloatDivOut { quotient: quotient, remainder: remainder } -// } -// pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec { let mut carry: u16 = u16::from(base_ten_int_term); let mut rem: u16; diff --git a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs index f28121d3e..870e64712 100644 --- a/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs +++ b/src/uu/printf/src/tokenize/num_format/formatters/cninetyninehexfloatf.rs @@ -52,34 +52,6 @@ fn get_primitive_hex( ) -> FormatPrimitive { let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" })); - // assign the digits before and after the decimal points - // to separate slices. If no digits after decimal point, - // assign 0 - //let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { - //Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), - //None => (str_in, "0"), - //}; - //if first_segment_raw.is_empty() { - //first_segment_raw = "0"; - //} - // convert to string, hexifying if input is in dec. - // let (first_segment, second_segment) = - // match inprefix.radix_in { - // Base::Ten => { - // (to_hex(first_segment_raw, true), - // to_hex(second_segment_raw, false)) - // } - // _ => { - // (String::from(first_segment_raw), - // String::from(second_segment_raw)) - // } - // }; - // - // - // f.pre_decimal = Some(first_segment); - // f.post_decimal = Some(second_segment); - // - // TODO actual conversion, make sure to get back mantissa. // for hex to hex, it's really just a matter of moving the // decimal point and calculating the mantissa by its initial From 34a824af71705279e6f3a213668c495e0e6bd396 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 16:58:37 +0200 Subject: [PATCH 03/16] ls: use lscolors crate --- src/uu/ls/Cargo.toml | 5 +- src/uu/ls/src/ls.rs | 253 ++++++++++++++++----------------------- tests/by-util/test_ls.rs | 27 ++--- 3 files changed, 116 insertions(+), 169 deletions(-) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index dacdc7cd9..addca6fbb 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -16,17 +16,14 @@ path = "src/ls.rs" [dependencies] clap = "2.33" -lazy_static = "1.0.1" number_prefix = "0.4" term_grid = "0.1.5" termsize = "0.1.6" time = "0.1.40" -unicode-width = "0.1.5" globset = "0.4.6" +lscolors = { version="0.7.1", features=["ansi_term"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } - -[target.'cfg(unix)'.dependencies] atty = "0.2" [[bin]] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 514539809..c08e604f9 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -7,9 +7,6 @@ // spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf -#[cfg(unix)] -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate uucore; @@ -18,10 +15,9 @@ mod version_cmp; use clap::{App, Arg}; use globset::{self, Glob, GlobSet, GlobSetBuilder}; +use lscolors::LsColors; use number_prefix::NumberPrefix; use quoting_style::{escape_name, QuotingStyle}; -#[cfg(unix)] -use std::collections::HashMap; use std::fs; use std::fs::{DirEntry, FileType, Metadata}; #[cfg(unix)] @@ -41,7 +37,7 @@ use time::{strftime, Timespec}; #[cfg(unix)] use unicode_width::UnicodeWidthStr; #[cfg(unix)] -use uucore::libc::{mode_t, S_ISGID, S_ISUID, S_ISVTX, S_IWOTH, S_IXGRP, S_IXOTH, S_IXUSR}; +use uucore::libc::{mode_t, S_IXGRP, S_IXOTH, S_IXUSR}; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = " @@ -54,30 +50,6 @@ fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } -#[cfg(unix)] -static DEFAULT_COLORS: &str = "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"; - -#[cfg(unix)] -lazy_static! { - static ref LS_COLORS: String = - std::env::var("LS_COLORS").unwrap_or_else(|_| DEFAULT_COLORS.to_string()); - static ref COLOR_MAP: HashMap<&'static str, &'static str> = { - let codes = LS_COLORS.split(':'); - let mut map = HashMap::new(); - for c in codes { - let p: Vec<_> = c.splitn(2, '=').collect(); - if p.len() == 2 { - map.insert(p[0], p[1]); - } - } - map - }; - static ref RESET_CODE: &'static str = COLOR_MAP.get("rs").unwrap_or(&"0"); - static ref LEFT_CODE: &'static str = COLOR_MAP.get("lc").unwrap_or(&"\x1b["); - static ref RIGHT_CODE: &'static str = COLOR_MAP.get("rc").unwrap_or(&"m"); - static ref END_CODE: &'static str = COLOR_MAP.get("ec").unwrap_or(&""); -} - pub mod options { pub mod format { pub static ONELINE: &str = "1"; @@ -212,8 +184,7 @@ struct Config { time: Time, #[cfg(unix)] inode: bool, - #[cfg(unix)] - color: bool, + color: Option, long: LongFormat, width: Option, quoting_style: QuotingStyle, @@ -337,8 +308,7 @@ impl Config { Time::Modification }; - #[cfg(unix)] - let color = match options.value_of(options::COLOR) { + let needs_color = match options.value_of(options::COLOR) { None => options.is_present(options::COLOR), Some(val) => match val { "" | "always" | "yes" | "force" => true, @@ -347,6 +317,12 @@ impl Config { }, }; + let color = if needs_color { + Some(LsColors::from_env().unwrap_or_default()) + } else { + None + }; + let size_format = if options.is_present(options::size::HUMAN_READABLE) { SizeFormat::Binary } else if options.is_present(options::size::SI) { @@ -520,7 +496,6 @@ impl Config { size_format, directory: options.is_present(options::DIRECTORY), time, - #[cfg(unix)] color, #[cfg(unix)] inode: options.is_present(options::INODE), @@ -1470,64 +1445,44 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String { name.to_string_lossy().into_owned() } -#[cfg(not(unix))] -fn display_file_name( - path: &Path, - strip: Option<&Path>, - metadata: &Metadata, - config: &Config, -) -> Cell { - let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); - let file_type = metadata.file_type(); +// #[cfg(not(unix))] +// fn display_file_name( +// path: &Path, +// strip: Option<&Path>, +// metadata: &Metadata, +// config: &Config, +// ) -> Cell { +// let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); +// let file_type = metadata.file_type(); - match config.indicator_style { - IndicatorStyle::Classify | IndicatorStyle::FileType => { - if file_type.is_dir() { - name.push('/'); - } - if file_type.is_symlink() { - name.push('@'); - } - } - IndicatorStyle::Slash => { - if file_type.is_dir() { - name.push('/'); - } - } - _ => (), - }; +// match config.indicator_style { +// IndicatorStyle::Classify | IndicatorStyle::FileType => { +// if file_type.is_dir() { +// name.push('/'); +// } +// if file_type.is_symlink() { +// name.push('@'); +// } +// } +// IndicatorStyle::Slash => { +// if file_type.is_dir() { +// name.push('/'); +// } +// } +// _ => (), +// }; - if config.format == Format::Long && metadata.file_type().is_symlink() { - if let Ok(target) = path.read_link() { - // We don't bother updating width here because it's not used for long listings - let target_name = target.to_string_lossy().to_string(); - name.push_str(" -> "); - name.push_str(&target_name); - } - } +// if config.format == Format::Long && metadata.file_type().is_symlink() { +// if let Ok(target) = path.read_link() { +// // We don't bother updating width here because it's not used for long listings +// let target_name = target.to_string_lossy().to_string(); +// name.push_str(" -> "); +// name.push_str(&target_name); +// } +// } - name.into() -} - -#[cfg(unix)] -fn color_name(name: String, typ: &str) -> String { - let mut typ = typ; - if !COLOR_MAP.contains_key(typ) { - if typ == "or" { - typ = "ln"; - } else if typ == "mi" { - typ = "fi"; - } - }; - if let Some(code) = COLOR_MAP.get(typ) { - format!( - "{}{}{}{}{}{}{}{}", - *LEFT_CODE, code, *RIGHT_CODE, name, *END_CODE, *LEFT_CODE, *RESET_CODE, *RIGHT_CODE, - ) - } else { - name - } -} +// name.into() +// } #[cfg(unix)] macro_rules! has { @@ -1537,6 +1492,40 @@ macro_rules! has { } #[cfg(unix)] +fn classify_file(md: &Metadata) -> Option { + let file_type = md.file_type(); + if file_type.is_dir() { + Some('/') + } else if file_type.is_symlink() { + Some('@') + } else if file_type.is_socket() { + Some('=') + } else if file_type.is_fifo() { + Some('|') + } else if file_type.is_file() { + let mode = md.mode() as mode_t; + if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) { + Some('*') + } else { + None + } + } else { + None + } +} + +#[cfg(not(unix))] +fn classify_file(md: &Metadata) -> Option { + let file_type = md.file_type(); + if file_type.is_dir() { + Some('/') + } else if file_type.is_symlink() { + Some('@') + } else { + None + } +} + #[allow(clippy::cognitive_complexity)] fn display_file_name( path: &Path, @@ -1545,65 +1534,18 @@ fn display_file_name( config: &Config, ) -> Cell { let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); + + #[cfg(unix)] if config.format != Format::Long && config.inode { name = get_inode(metadata) + " " + &name; } - let mut width = UnicodeWidthStr::width(&*name); - let ext; - if config.color || config.indicator_style != IndicatorStyle::None { - let file_type = metadata.file_type(); + if let Some(ls_colors) = &config.color { + name = color_name(&ls_colors, path, name, metadata).to_string(); + } - let (code, sym) = if file_type.is_dir() { - ("di", Some('/')) - } else if file_type.is_symlink() { - if path.exists() { - ("ln", Some('@')) - } else { - ("or", Some('@')) - } - } else if file_type.is_socket() { - ("so", Some('=')) - } else if file_type.is_fifo() { - ("pi", Some('|')) - } else if file_type.is_block_device() { - ("bd", None) - } else if file_type.is_char_device() { - ("cd", None) - } else if file_type.is_file() { - let mode = metadata.mode() as mode_t; - let sym = if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) { - Some('*') - } else { - None - }; - if has!(mode, S_ISUID) { - ("su", sym) - } else if has!(mode, S_ISGID) { - ("sg", sym) - } else if has!(mode, S_ISVTX) && has!(mode, S_IWOTH) { - ("tw", sym) - } else if has!(mode, S_ISVTX) { - ("st", sym) - } else if has!(mode, S_IWOTH) { - ("ow", sym) - } else if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) { - ("ex", sym) - } else if metadata.nlink() > 1 { - ("mh", sym) - } else if let Some(e) = path.extension() { - ext = format!("*.{}", e.to_string_lossy()); - (ext.as_str(), None) - } else { - ("fi", None) - } - } else { - ("", None) - }; - - if config.color { - name = color_name(name, code); - } + if config.indicator_style != IndicatorStyle::None { + let sym = classify_file(metadata); let char_opt = match config.indicator_style { IndicatorStyle::Classify => sym, @@ -1626,23 +1568,32 @@ fn display_file_name( if let Some(c) = char_opt { name.push(c); - width += 1; } } if config.format == Format::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { - // We don't bother updating width here because it's not used for long listings - let code = if target.exists() { "fi" } else { "mi" }; - let target_name = color_name(target.to_string_lossy().to_string(), code); + // We don't bother updating width here because it's not used for long + let mut target_name = target.to_string_lossy().to_string(); + if let Some(ls_colors) = &config.color { + target_name = color_name(&ls_colors, &target, target_name, metadata); + } name.push_str(" -> "); + name.push_str(&target_name); } } - Cell { - contents: name, - width, + name.into() +} + +fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { + match ls_colors.style_for_path_with_metadata(path, Some(&md)) { + Some(style) => { + dbg!(style); + style.to_ansi_term_style().paint(name).to_string() + } + None => dbg!(name), } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d810cdc29..ed95c3034 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -621,20 +621,27 @@ fn test_ls_recursive() { result.stdout_contains(&"a\\b:\nb"); } -#[cfg(unix)] #[test] fn test_ls_ls_color() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("a"); - at.mkdir("a/nested_dir"); + let nested_dir = Path::new("a") + .join("nested_dir") + .to_string_lossy() + .to_string(); + at.mkdir(&nested_dir); at.mkdir("z"); - at.touch(&at.plus_as_string("a/nested_file")); + let nested_file = Path::new("a") + .join("nested_file") + .to_string_lossy() + .to_string(); + at.touch(&nested_file); at.touch("test-color"); - let a_with_colors = "\x1b[01;34ma\x1b[0m"; - let z_with_colors = "\x1b[01;34mz\x1b[0m"; - let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m"; + let a_with_colors = "\x1b[1;34ma\x1b[0m"; + let z_with_colors = "\x1b[1;34mz\x1b[0m"; + let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m"; // Color is disabled by default let result = scene.ucmd().succeeds(); @@ -670,14 +677,6 @@ fn test_ls_ls_color() { .succeeds() .stdout_contains(nested_dir_with_colors); - // Color has no effect - scene - .ucmd() - .arg("--color=always") - .arg("a/nested_file") - .succeeds() - .stdout_contains("a/nested_file\n"); - // No output scene .ucmd() From e382f7fa830a59e1b6832987db2f0f8a9835ef9a Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 17:43:57 +0200 Subject: [PATCH 04/16] ls: fix test warnings on Windows --- tests/by-util/test_ls.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index ed95c3034..c53091c94 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -816,7 +816,7 @@ fn test_ls_indicator_style() { let options = vec!["classify", "file-type", "slash"]; for opt in options { // Verify that classify and file-type both contain indicators for symlinks. - let result = scene + scene .ucmd() .arg(format!("--indicator-style={}", opt)) .succeeds() @@ -826,7 +826,7 @@ fn test_ls_indicator_style() { // Same test as above, but with the alternate flags. let options = vec!["--classify", "--file-type", "-p"]; for opt in options { - let result = scene + scene .ucmd() .arg(format!("{}", opt)) .succeeds() @@ -837,7 +837,7 @@ fn test_ls_indicator_style() { let options = vec!["classify", "file-type"]; for opt in options { // Verify that classify and file-type both contain indicators for symlinks. - let result = scene + scene .ucmd() .arg(format!("--indicator-style={}", opt)) .succeeds() @@ -961,7 +961,7 @@ fn test_ls_hidden_windows() { let result = scene.ucmd().succeeds(); assert!(!result.stdout_str().contains(file)); - let result = scene.ucmd().arg("-a").succeeds().stdout_contains(file); + scene.ucmd().arg("-a").succeeds().stdout_contains(file); } #[test] From ff3953837515257a32426e22d9f3bab75c984d5b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 17:57:17 +0200 Subject: [PATCH 05/16] ls: further refactor --color and classification --- src/uu/ls/src/ls.rs | 90 ++++++++-------------------------------- tests/by-util/test_ls.rs | 2 +- 2 files changed, 19 insertions(+), 73 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index c08e604f9..9f0c56245 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -35,8 +35,6 @@ use std::{cmp::Reverse, process::exit}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; #[cfg(unix)] -use unicode_width::UnicodeWidthStr; -#[cfg(unix)] use uucore::libc::{mode_t, S_IXGRP, S_IXOTH, S_IXUSR}; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -1445,45 +1443,6 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String { name.to_string_lossy().into_owned() } -// #[cfg(not(unix))] -// fn display_file_name( -// path: &Path, -// strip: Option<&Path>, -// metadata: &Metadata, -// config: &Config, -// ) -> Cell { -// let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); -// let file_type = metadata.file_type(); - -// match config.indicator_style { -// IndicatorStyle::Classify | IndicatorStyle::FileType => { -// if file_type.is_dir() { -// name.push('/'); -// } -// if file_type.is_symlink() { -// name.push('@'); -// } -// } -// IndicatorStyle::Slash => { -// if file_type.is_dir() { -// name.push('/'); -// } -// } -// _ => (), -// }; - -// if config.format == Format::Long && metadata.file_type().is_symlink() { -// if let Ok(target) = path.read_link() { -// // We don't bother updating width here because it's not used for long listings -// let target_name = target.to_string_lossy().to_string(); -// name.push_str(" -> "); -// name.push_str(&target_name); -// } -// } - -// name.into() -// } - #[cfg(unix)] macro_rules! has { ($mode:expr, $perm:expr) => { @@ -1491,42 +1450,32 @@ macro_rules! has { }; } -#[cfg(unix)] fn classify_file(md: &Metadata) -> Option { let file_type = md.file_type(); + + #[allow(clippy::clippy::collapsible_else_if)] if file_type.is_dir() { Some('/') } else if file_type.is_symlink() { Some('@') - } else if file_type.is_socket() { - Some('=') - } else if file_type.is_fifo() { - Some('|') - } else if file_type.is_file() { - let mode = md.mode() as mode_t; - if has!(mode, S_IXUSR | S_IXGRP | S_IXOTH) { - Some('*') - } else { - None + } else { + #[cfg(unix)] + { + if file_type.is_socket() { + Some('=') + } else if file_type.is_fifo() { + Some('|') + } else if file_type.is_file() || has!(md.mode(), S_IXUSR | S_IXGRP | S_IXOTH) { + Some('*') + } else { + None + } } - } else { + #[cfg(not(unix))] None } } -#[cfg(not(unix))] -fn classify_file(md: &Metadata) -> Option { - let file_type = md.file_type(); - if file_type.is_dir() { - Some('/') - } else if file_type.is_symlink() { - Some('@') - } else { - None - } -} - -#[allow(clippy::cognitive_complexity)] fn display_file_name( path: &Path, strip: Option<&Path>, @@ -1541,7 +1490,7 @@ fn display_file_name( } if let Some(ls_colors) = &config.color { - name = color_name(&ls_colors, path, name, metadata).to_string(); + name = color_name(&ls_colors, path, name, metadata); } if config.indicator_style != IndicatorStyle::None { @@ -1589,11 +1538,8 @@ fn display_file_name( fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { match ls_colors.style_for_path_with_metadata(path, Some(&md)) { - Some(style) => { - dbg!(style); - style.to_ansi_term_style().paint(name).to_string() - } - None => dbg!(name), + Some(style) => style.to_ansi_term_style().paint(name).to_string(), + None => name, } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index c53091c94..5538864ca 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -622,7 +622,7 @@ fn test_ls_recursive() { } #[test] -fn test_ls_ls_color() { +fn test_ls_color() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir("a"); From 3fc8d2e42270d9ef08521346ca2e754f63f6c9b8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 18:05:02 +0200 Subject: [PATCH 06/16] ls: make compatible with Rust 1.40 again --- Cargo.lock | 41 +++++++++++++++++++++++++++++------------ src/uu/ls/src/ls.rs | 8 +++++--- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 461716b1b..5370c50e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "arrayvec" version = "0.4.12" @@ -127,9 +136,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75" dependencies = [ "rustc_version", ] @@ -169,7 +178,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags", "strsim", @@ -452,9 +461,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -580,7 +589,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "winapi 0.3.9", ] @@ -783,6 +792,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "lscolors" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b894c45c9da468621cdd615a5a79ee5e5523dd4f75c76ebc03d458940c16e" +dependencies = [ + "ansi_term 0.12.1", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -1176,9 +1194,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" dependencies = [ "bitflags", ] @@ -1189,7 +1207,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", ] [[package]] @@ -1454,7 +1472,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", - "redox_syscall 0.2.5", + "redox_syscall 0.2.6", "redox_termios", ] @@ -1989,12 +2007,11 @@ dependencies = [ "atty", "clap", "globset", - "lazy_static", + "lscolors", "number_prefix", "term_grid", "termsize", "time", - "unicode-width", "uucore", "uucore_procs", ] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 9f0c56245..07147ae4b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1450,10 +1450,10 @@ macro_rules! has { }; } +#[allow(clippy::clippy::collapsible_else_if)] fn classify_file(md: &Metadata) -> Option { let file_type = md.file_type(); - #[allow(clippy::clippy::collapsible_else_if)] if file_type.is_dir() { Some('/') } else if file_type.is_symlink() { @@ -1485,8 +1485,10 @@ fn display_file_name( let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); #[cfg(unix)] - if config.format != Format::Long && config.inode { - name = get_inode(metadata) + " " + &name; + { + if config.format != Format::Long && config.inode { + name = get_inode(metadata) + " " + &name; + } } if let Some(ls_colors) = &config.color { From 8a05148d7be9949d2da93126c21ad2606d53cb2d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 21 Apr 2021 17:56:59 +0200 Subject: [PATCH 07/16] sort: fix tokenization for trailing separators Trailing separators were included at the end of the last token, but they should not be. This changes tokenize_with_separator as suggested by @cbjadwani. --- src/uu/sort/src/sort.rs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 07b852921..7515ca1c9 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -351,20 +351,18 @@ fn tokenize_default(line: &str) -> Vec { /// Split between separators. These separators are not included in fields. fn tokenize_with_separator(line: &str, separator: char) -> Vec { - let mut tokens = vec![0..0]; - let mut previous_was_separator = false; - for (idx, char) in line.char_indices() { - if previous_was_separator { - tokens.push(idx..0); - } - if char == separator { - tokens.last_mut().unwrap().end = idx; - previous_was_separator = true; - } else { - previous_was_separator = false; - } + let mut tokens = vec![]; + let separator_indices = + line.char_indices() + .filter_map(|(i, c)| if c == separator { Some(i) } else { None }); + let mut start = 0; + for sep_idx in separator_indices { + tokens.push(start..sep_idx); + start = sep_idx + 1; + } + if start < line.len() { + tokens.push(start..line.len()); } - tokens.last_mut().unwrap().end = line.len(); tokens } @@ -1383,4 +1381,14 @@ mod tests { vec![0..0, 1..1, 2..2, 3..9, 10..18,] ); } + + #[test] + fn test_tokenize_fields_trailing_custom_separator() { + let line = "a"; + assert_eq!(tokenize(line, Some('a')), vec![0..0]); + let line = "aa"; + assert_eq!(tokenize(line, Some('a')), vec![0..0, 1..1]); + let line = "..a..a"; + assert_eq!(tokenize(line, Some('a')), vec![0..2, 3..5]); + } } From 8914fd0a579a0e0da51ea94319fdca96f13dcc18 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 21 Apr 2021 19:26:17 +0200 Subject: [PATCH 08/16] add an integration test --- tests/by-util/test_sort.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index a4a9a383c..72d4f67fc 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -581,3 +581,12 @@ fn test_check_silent() { .fails() .stdout_is(""); } + +#[test] +fn test_trailing_separator() { + new_ucmd!() + .args(&["-t", "x", "-k", "1,1"]) + .pipe_in("aax\naaa\n") + .succeeds() + .stdout_is("aax\naaa\n"); +} \ No newline at end of file From 1d7e206d722b884bd65d59f267f2337351bdd5eb Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 20:04:52 +0200 Subject: [PATCH 09/16] ls: fix mac build --- src/uu/ls/src/ls.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 07147ae4b..8c8033744 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -35,7 +35,7 @@ use std::{cmp::Reverse, process::exit}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; #[cfg(unix)] -use uucore::libc::{mode_t, S_IXGRP, S_IXOTH, S_IXUSR}; +use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = " @@ -1444,10 +1444,13 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String { } #[cfg(unix)] -macro_rules! has { - ($mode:expr, $perm:expr) => { - $mode & ($perm as mode_t) != 0 - }; +fn file_is_executable(md: &Metadata) -> bool { + // Mode always returns u32, but the flags might not be, based on the platform + // e.g. linux has u32, mac has u16. + // S_IXUSR -> user has execute permission + // S_IXGRP -> group has execute persmission + // S_IXOTH -> other users have execute permission + md.mode() & ((S_IXUSR | S_IXGRP | S_IXOTH) as u32) != 0 } #[allow(clippy::clippy::collapsible_else_if)] @@ -1465,7 +1468,7 @@ fn classify_file(md: &Metadata) -> Option { Some('=') } else if file_type.is_fifo() { Some('|') - } else if file_type.is_file() || has!(md.mode(), S_IXUSR | S_IXGRP | S_IXOTH) { + } else if file_type.is_file() && file_is_executable(&md) { Some('*') } else { None From b756b987a43070d133e449a4a8d3f5a59deb09e8 Mon Sep 17 00:00:00 2001 From: rethab Date: Thu, 22 Apr 2021 08:42:56 +0200 Subject: [PATCH 10/16] cat: make tests stable (#2100) --- tests/by-util/test_cat.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index d7c3eec6b..9f7ebdd37 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -400,22 +400,29 @@ fn test_domain_socket() { use std::thread; use tempdir::TempDir; use unix_socket::UnixListener; + use std::sync::{Barrier, Arc}; let dir = TempDir::new("unix_socket").expect("failed to create dir"); let socket_path = dir.path().join("sock"); let listener = UnixListener::bind(&socket_path).expect("failed to create socket"); + // use a barrier to ensure we don't run cat before the listener is setup + let barrier = Arc::new(Barrier::new(2)); + let barrier2 = Arc::clone(&barrier); + let thread = thread::spawn(move || { let mut stream = listener.accept().expect("failed to accept connection").0; + barrier2.wait(); stream .write_all(b"a\tb") .expect("failed to write test data"); }); - new_ucmd!() - .args(&[socket_path]) - .succeeds() - .stdout_only("a\tb"); + let child = new_ucmd!().args(&[socket_path]).run_no_wait(); + barrier.wait(); + let stdout = &child.wait_with_output().unwrap().stdout.clone(); + let output = String::from_utf8_lossy(&stdout); + assert_eq!("a\tb", output); thread.join().unwrap(); } From 8554cdf35be3cc4e8b7998d4db0d4b7b34abd9a8 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Thu, 22 Apr 2021 12:49:17 +0530 Subject: [PATCH 11/16] Optimize recursive ls (#2083) * ls: Remove allocations by eliminating collect/clones * ls: Introduce PathData structure - PathData will hold Path related metadata / strings that are required frequently in subsequent functions - All data is precomputed and cached and subsequent functions just use cached data * ls: Cache more data related to paths - Cache filename and sort by filename instead of full path - Cache uid->usr and gid->grp mappings https://github.com/uutils/coreutils/pull/2099/files * ls: Add BENCHMARKING.md * ls: Document PathData structure * tests/ls: Add testcase for error paths with width option * ls: Fix unused import warning cached will be only used for unix currently as current use of caching gid/uid mappings is only relevant on unix * ls: Suggest checking syscall count in BENCHMARKING.md * ls: Remove mentions of sort in BENCHMARKING.md * ls: Remove dependency on cached Implement caching using HashMap and lazy_static * ls: Fix MSRV error related to map_or Rust 1.40 did not support map_or for result types --- src/uu/ls/BENCHMARKING.md | 34 ++++++ src/uu/ls/src/ls.rs | 212 +++++++++++++++++++++++++------------- tests/by-util/test_ls.rs | 8 ++ 3 files changed, 181 insertions(+), 73 deletions(-) create mode 100644 src/uu/ls/BENCHMARKING.md diff --git a/src/uu/ls/BENCHMARKING.md b/src/uu/ls/BENCHMARKING.md new file mode 100644 index 000000000..84a0c3d84 --- /dev/null +++ b/src/uu/ls/BENCHMARKING.md @@ -0,0 +1,34 @@ +# Benchmarking ls + +ls majorly involves fetching a lot of details (depending upon what details are requested, eg. time/date, inode details, etc) for each path using system calls. Ideally, any system call should be done only once for each of the paths - not adhering to this principle leads to a lot of system call overhead multiplying and bubbling up, especially for recursive ls, therefore it is important to always benchmark multiple scenarios. +This is an overwiew over what was benchmarked, and if you make changes to `ls`, you are encouraged to check +how performance was affected for the workloads listed below. Feel free to add other workloads to the +list that we should improve / make sure not to regress. + +Run `cargo build --release` before benchmarking after you make a change! + +## Simple recursive ls + +- Get a large tree, for example linux kernel source tree. +- Benchmark simple recursive ls with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -R tree > /dev/null"`. + +## Recursive ls with all and long options + +- Same tree as above +- Benchmark recursive ls with -al -R options with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"`. + +## Comparing with GNU ls + +Hyperfine accepts multiple commands to run and will compare them. To compare performance with GNU ls +duplicate the string you passed to hyperfine but remove the `target/release/coreutils` bit from it. + +Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"` becomes +`hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null" "ls -al -R tree > /dev/null"` +(This assumes GNU ls is installed as `ls`) + +This can also be used to compare with version of ls built before your changes to ensure your change does not regress this + +## Checking system call count + +- Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems. +- Example: `strace -c target/release/coreutils ls -al -R tree` diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 514539809..e0aa3ec4b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1038,12 +1038,43 @@ pub fn uumain(args: impl uucore::Args) -> i32 { list(locs, Config::from(matches)) } +/// Represents a Path along with it's associated data +/// Any data that will be reused several times makes sense to be added to this structure +/// Caching data here helps eliminate redundant syscalls to fetch same information +struct PathData { + // Result got from symlink_metadata() or metadata() based on config + md: std::io::Result, + // String formed from get_lossy_string() for the path + lossy_string: String, + // Name of the file - will be empty for . or .. + file_name: String, + // PathBuf that all above data corresponds to + p_buf: PathBuf, +} + +impl PathData { + fn new(p_buf: PathBuf, config: &Config, command_line: bool) -> Self { + let md = get_metadata(&p_buf, config, command_line); + let lossy_string = p_buf.to_string_lossy().into_owned(); + let name = p_buf + .file_name() + .map_or(String::new(), |s| s.to_string_lossy().into_owned()); + Self { + md, + lossy_string, + file_name: name, + p_buf, + } + } +} + fn list(locs: Vec, config: Config) -> i32 { let number_of_locs = locs.len(); - let mut files = Vec::::new(); - let mut dirs = Vec::::new(); + let mut files = Vec::::new(); + let mut dirs = Vec::::new(); let mut has_failed = false; + for loc in locs { let p = PathBuf::from(&loc); if !p.exists() { @@ -1054,36 +1085,28 @@ fn list(locs: Vec, config: Config) -> i32 { continue; } - let show_dir_contents = if !config.directory { - match config.dereference { - Dereference::None => { - if let Ok(md) = p.symlink_metadata() { - md.is_dir() - } else { - show_error!("'{}': {}", &loc, "No such file or directory"); - has_failed = true; - continue; - } - } - _ => p.is_dir(), - } + let path_data = PathData::new(p, &config, true); + + let show_dir_contents = if let Ok(md) = path_data.md.as_ref() { + !config.directory && md.is_dir() } else { + has_failed = true; false }; if show_dir_contents { - dirs.push(p); + dirs.push(path_data); } else { - files.push(p); + files.push(path_data); } } sort_entries(&mut files, &config); - display_items(&files, None, &config, true); + display_items(&files, None, &config); sort_entries(&mut dirs, &config); for dir in dirs { if number_of_locs > 1 { - println!("\n{}:", dir.to_string_lossy()); + println!("\n{}:", dir.lossy_string); } enter_directory(&dir, &config); } @@ -1094,22 +1117,22 @@ fn list(locs: Vec, config: Config) -> i32 { } } -fn sort_entries(entries: &mut Vec, config: &Config) { +fn sort_entries(entries: &mut Vec, config: &Config) { match config.sort { Sort::Time => entries.sort_by_key(|k| { Reverse( - get_metadata(k, false) + k.md.as_ref() .ok() .and_then(|md| get_system_time(&md, config)) .unwrap_or(UNIX_EPOCH), ) }), Sort::Size => { - entries.sort_by_key(|k| Reverse(get_metadata(k, false).map(|md| md.len()).unwrap_or(0))) + entries.sort_by_key(|k| Reverse(k.md.as_ref().map(|md| md.len()).unwrap_or(0))) } // The default sort in GNU ls is case insensitive - Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()), - Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)), + Sort::Name => entries.sort_by_key(|k| k.file_name.to_lowercase()), + Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)), Sort::None => {} } @@ -1143,32 +1166,57 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { true } -fn enter_directory(dir: &Path, config: &Config) { - let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect)); - - entries.retain(|e| should_display(e, config)); - - let mut entries: Vec<_> = entries.iter().map(DirEntry::path).collect(); - sort_entries(&mut entries, config); - - if config.files == Files::All { - let mut display_entries = entries.clone(); - display_entries.insert(0, dir.join("..")); - display_entries.insert(0, dir.join(".")); - display_items(&display_entries, Some(dir), config, false); +fn enter_directory(dir: &PathData, config: &Config) { + let mut entries: Vec<_> = if config.files == Files::All { + vec![ + PathData::new(dir.p_buf.join("."), config, false), + PathData::new(dir.p_buf.join(".."), config, false), + ] } else { - display_items(&entries, Some(dir), config, false); - } + vec![] + }; + + let mut temp: Vec<_> = safe_unwrap!(fs::read_dir(&dir.p_buf)) + .map(|res| safe_unwrap!(res)) + .filter(|e| should_display(e, config)) + .map(|e| PathData::new(DirEntry::path(&e), config, false)) + .collect(); + + sort_entries(&mut temp, config); + + entries.append(&mut temp); + + display_items(&entries, Some(&dir.p_buf), config); if config.recursive { - for e in entries.iter().filter(|p| p.is_dir()) { - println!("\n{}:", e.to_string_lossy()); + for e in entries + .iter() + .skip(if config.files == Files::All { 2 } else { 0 }) + .filter(|p| p.md.as_ref().map(|md| md.is_dir()).unwrap_or(false)) + { + println!("\n{}:", e.lossy_string); enter_directory(&e, config); } } } -fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { +fn get_metadata(entry: &Path, config: &Config, command_line: bool) -> std::io::Result { + let dereference = match &config.dereference { + Dereference::All => true, + Dereference::Args => command_line, + Dereference::DirArgs => { + if command_line { + if let Ok(md) = entry.metadata() { + md.is_dir() + } else { + false + } + } else { + false + } + } + Dereference::None => false, + }; if dereference { entry.metadata().or_else(|_| entry.symlink_metadata()) } else { @@ -1176,8 +1224,8 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { } } -fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) { - if let Ok(md) = get_metadata(entry, false) { +fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { + if let Ok(md) = entry.md.as_ref() { ( display_symlink_count(&md).len(), display_file_size(&md, config).len(), @@ -1191,7 +1239,7 @@ fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } -fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, command_line: bool) { +fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) { if config.format == Format::Long { let (mut max_links, mut max_size) = (1, 1); for item in items { @@ -1200,18 +1248,18 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, comma max_size = size.max(max_size); } for item in items { - display_item_long(item, strip, max_links, max_size, config, command_line); + display_item_long(item, strip, max_links, max_size, config); } } else { let names = items.iter().filter_map(|i| { - let md = get_metadata(i, false); + let md = i.md.as_ref(); match md { Err(e) => { - let filename = get_file_name(i, strip); + let filename = get_file_name(&i.p_buf, strip); show_error!("'{}': {}", filename, e); None } - Ok(md) => Some(display_file_name(&i, strip, &md, config)), + Ok(md) => Some(display_file_name(&i.p_buf, strip, &md, config)), } }); @@ -1271,33 +1319,15 @@ fn display_grid(names: impl Iterator, width: u16, direction: Direct use uucore::fs::display_permissions; fn display_item_long( - item: &Path, + item: &PathData, strip: Option<&Path>, max_links: usize, max_size: usize, config: &Config, - command_line: bool, ) { - let dereference = match &config.dereference { - Dereference::All => true, - Dereference::Args => command_line, - Dereference::DirArgs => { - if command_line { - if let Ok(md) = item.metadata() { - md.is_dir() - } else { - false - } - } else { - false - } - } - Dereference::None => false, - }; - - let md = match get_metadata(item, dereference) { + let md = match &item.md { Err(e) => { - let filename = get_file_name(&item, strip); + let filename = get_file_name(&item.p_buf, strip); show_error!("{}: {}", filename, e); return; } @@ -1336,7 +1366,7 @@ fn display_item_long( " {} {} {}", pad_left(display_file_size(&md, config), max_size), display_date(&md, config), - display_file_name(&item, strip, &md, config).contents, + display_file_name(&item.p_buf, strip, &md, config).contents, ); } @@ -1348,14 +1378,50 @@ fn get_inode(metadata: &Metadata) -> String { // Currently getpwuid is `linux` target only. If it's broken out into // a posix-compliant attribute this can be updated... #[cfg(unix)] +use std::sync::Mutex; +#[cfg(unix)] use uucore::entries; +#[cfg(unix)] +fn cached_uid2usr(uid: u32) -> String { + lazy_static! { + static ref UID_CACHE: Mutex> = Mutex::new(HashMap::new()); + } + + let mut uid_cache = UID_CACHE.lock().unwrap(); + match uid_cache.get(&uid) { + Some(usr) => usr.clone(), + None => { + let usr = entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()); + uid_cache.insert(uid, usr.clone()); + usr + } + } +} + #[cfg(unix)] fn display_uname(metadata: &Metadata, config: &Config) -> String { if config.long.numeric_uid_gid { metadata.uid().to_string() } else { - entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string()) + cached_uid2usr(metadata.uid()) + } +} + +#[cfg(unix)] +fn cached_gid2grp(gid: u32) -> String { + lazy_static! { + static ref GID_CACHE: Mutex> = Mutex::new(HashMap::new()); + } + + let mut gid_cache = GID_CACHE.lock().unwrap(); + match gid_cache.get(&gid) { + Some(grp) => grp.clone(), + None => { + let grp = entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()); + gid_cache.insert(gid, grp.clone()); + grp + } } } @@ -1364,7 +1430,7 @@ fn display_group(metadata: &Metadata, config: &Config) -> String { if config.long.numeric_uid_gid { metadata.gid().to_string() } else { - entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string()) + cached_gid2grp(metadata.gid()) } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d810cdc29..5583dbaca 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -103,6 +103,14 @@ fn test_ls_width() { .succeeds() .stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); } + + for option in &["-w 1a", "-w=1a", "--width=1a", "--width 1a"] { + scene + .ucmd() + .args(&option.split(" ").collect::>()) + .fails() + .stderr_only("ls: error: invalid line width: ‘1a’"); + } } #[test] From 4e4c3aba0066d3fbc27f44acab668cffe4959ee5 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 22 Apr 2021 11:16:33 +0200 Subject: [PATCH 12/16] ls: don't color symlink target --- src/uu/ls/src/ls.rs | 7 +------ tests/by-util/test_ls.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 8c8033744..88f26e604 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1528,13 +1528,8 @@ fn display_file_name( if config.format == Format::Long && metadata.file_type().is_symlink() { if let Ok(target) = path.read_link() { // We don't bother updating width here because it's not used for long - let mut target_name = target.to_string_lossy().to_string(); - if let Some(ls_colors) = &config.color { - target_name = color_name(&ls_colors, &target, target_name, metadata); - } name.push_str(" -> "); - - name.push_str(&target_name); + name.push_str(&target.to_string_lossy()); } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 5538864ca..f74443877 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -639,14 +639,18 @@ fn test_ls_color() { at.touch(&nested_file); at.touch("test-color"); + at.symlink_file(&nested_file, "link"); + let a_with_colors = "\x1b[1;34ma\x1b[0m"; let z_with_colors = "\x1b[1;34mz\x1b[0m"; let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m"; + let link_with_color = "\x1b[1;36mlink\x1b[0m"; // Color is disabled by default let result = scene.ucmd().succeeds(); assert!(!result.stdout_str().contains(a_with_colors)); assert!(!result.stdout_str().contains(z_with_colors)); + assert!(!result.stdout_str().contains(link_with_color)); // Color should be enabled scene @@ -654,7 +658,8 @@ fn test_ls_color() { .arg("--color") .succeeds() .stdout_contains(a_with_colors) - .stdout_contains(z_with_colors); + .stdout_contains(z_with_colors) + .stdout_contains(link_with_color); // Color should be enabled scene @@ -662,12 +667,14 @@ fn test_ls_color() { .arg("--color=always") .succeeds() .stdout_contains(a_with_colors) - .stdout_contains(z_with_colors); + .stdout_contains(z_with_colors) + .stdout_contains(link_with_color); // Color should be disabled let result = scene.ucmd().arg("--color=never").succeeds(); assert!(!result.stdout_str().contains(a_with_colors)); assert!(!result.stdout_str().contains(z_with_colors)); + assert!(!result.stdout_str().contains(link_with_color)); // Nested dir should be shown and colored scene From b9f4964a96a45cfbff4ff7c6277c5357892093a6 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 22 Apr 2021 11:39:08 +0200 Subject: [PATCH 13/16] ls: bring up to date with recent changes --- Cargo.lock | 5 +++-- src/uu/ls/Cargo.toml | 3 +++ src/uu/ls/src/ls.rs | 10 ++++++---- tests/by-util/test_ls.rs | 11 ++--------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5370c50e6..7ae0d078f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1412,9 +1412,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" +checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -2007,6 +2007,7 @@ dependencies = [ "atty", "clap", "globset", + "lazy_static", "lscolors", "number_prefix", "term_grid", diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index addca6fbb..9fa06c6a6 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -26,6 +26,9 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=[" uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } atty = "0.2" +[target.'cfg(unix)'.dependencies] +lazy_static = "1.4.0" + [[bin]] name = "ls" path = "src/main.rs" diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 99c41bdb8..3feee0f07 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -9,6 +9,9 @@ #[macro_use] extern crate uucore; +#[cfg(unix)] +#[macro_use] +extern crate lazy_static; mod quoting_style; mod version_cmp; @@ -18,12 +21,11 @@ use globset::{self, Glob, GlobSet, GlobSetBuilder}; use lscolors::LsColors; use number_prefix::NumberPrefix; use quoting_style::{escape_name, QuotingStyle}; -use std::fs; -use std::fs::{DirEntry, FileType, Metadata}; #[cfg(unix)] -use std::os::unix::fs::FileTypeExt; +use std::collections::HashMap; +use std::fs::{self, DirEntry, FileType, Metadata}; #[cfg(any(unix, target_os = "redox"))] -use std::os::unix::fs::MetadataExt; +use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::path::{Path, PathBuf}; diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 4282bcfc0..f87e64688 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -647,18 +647,14 @@ fn test_ls_color() { at.touch(&nested_file); at.touch("test-color"); - at.symlink_file(&nested_file, "link"); - let a_with_colors = "\x1b[1;34ma\x1b[0m"; let z_with_colors = "\x1b[1;34mz\x1b[0m"; let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m"; - let link_with_color = "\x1b[1;36mlink\x1b[0m"; // Color is disabled by default let result = scene.ucmd().succeeds(); assert!(!result.stdout_str().contains(a_with_colors)); assert!(!result.stdout_str().contains(z_with_colors)); - assert!(!result.stdout_str().contains(link_with_color)); // Color should be enabled scene @@ -666,8 +662,7 @@ fn test_ls_color() { .arg("--color") .succeeds() .stdout_contains(a_with_colors) - .stdout_contains(z_with_colors) - .stdout_contains(link_with_color); + .stdout_contains(z_with_colors); // Color should be enabled scene @@ -675,14 +670,12 @@ fn test_ls_color() { .arg("--color=always") .succeeds() .stdout_contains(a_with_colors) - .stdout_contains(z_with_colors) - .stdout_contains(link_with_color); + .stdout_contains(z_with_colors); // Color should be disabled let result = scene.ucmd().arg("--color=never").succeeds(); assert!(!result.stdout_str().contains(a_with_colors)); assert!(!result.stdout_str().contains(z_with_colors)); - assert!(!result.stdout_str().contains(link_with_color)); // Nested dir should be shown and colored scene From 3678777539b0f99a4545446fab119262ae7493e0 Mon Sep 17 00:00:00 2001 From: James Robson Date: Thu, 22 Apr 2021 16:10:08 +0100 Subject: [PATCH 14/16] tail --sleep-interval takes a value --- src/uu/tail/src/tail.rs | 1 + tests/by-util/test_tail.rs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index ffe27e26c..fec88e841 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -117,6 +117,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::SLEEP_INT) .short("s") + .takes_value(true) .long(options::SLEEP_INT) .help("Number or seconds to sleep between polling the file when running with -f"), ) diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 6e9eb4a17..1f74a3a98 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -343,3 +343,12 @@ fn test_negative_indexing() { assert_eq!(positive_lines_index.stdout(), negative_lines_index.stdout()); assert_eq!(positive_bytes_index.stdout(), negative_bytes_index.stdout()); } + +#[test] +fn test_sleep_interval() { + new_ucmd!() + .arg("-s") + .arg("10") + .arg(FOOBAR_TXT) + .succeeds(); +} From 646c6cacbc546d6f15435b4bb45458541c2d04d2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 22 Apr 2021 22:37:44 +0200 Subject: [PATCH 15/16] refactor tests (#1982) --- src/uu/tac/src/tac.rs | 13 +- tests/by-util/test_chmod.rs | 2 +- tests/by-util/test_chroot.rs | 35 ++--- tests/by-util/test_cp.rs | 277 +++++++++++----------------------- tests/by-util/test_date.rs | 150 ++++++++---------- tests/by-util/test_df.rs | 16 +- tests/by-util/test_du.rs | 30 ++-- tests/by-util/test_env.rs | 7 +- tests/by-util/test_fmt.rs | 2 +- tests/by-util/test_groups.rs | 10 +- tests/by-util/test_head.rs | 12 +- tests/by-util/test_install.rs | 42 ++---- tests/by-util/test_ln.rs | 18 ++- tests/by-util/test_ls.rs | 8 + tests/by-util/test_mkfifo.rs | 3 +- tests/by-util/test_more.rs | 13 +- tests/by-util/test_mv.rs | 27 ++-- tests/by-util/test_nice.rs | 2 +- tests/by-util/test_shuf.rs | 107 ++++++------- tests/by-util/test_stat.rs | 2 +- tests/by-util/test_tac.rs | 23 +-- tests/by-util/test_tr.rs | 12 +- tests/by-util/test_tsort.rs | 30 ++-- tests/by-util/test_who.rs | 4 +- tests/common/util.rs | 49 ++++-- 25 files changed, 373 insertions(+), 521 deletions(-) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 666ba3384..1fb6489da 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -91,10 +91,15 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { } else { let path = Path::new(filename); if path.is_dir() || path.metadata().is_err() { - show_error!( - "failed to open '{}' for reading: No such file or directory", - filename - ); + if path.is_dir() { + show_error!("dir: read error: Invalid argument"); + } else { + show_error!( + "failed to open '{}' for reading: No such file or directory", + filename + ); + } + exit_code = 1; continue; } match File::open(path) { diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 9eda769f1..3958c0a36 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -47,7 +47,7 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) { ucmd.arg(arg); } let r = ucmd.run(); - if !r.success { + if !r.succeeded() { println!("{}", r.stderr_str()); panic!("{:?}: failed", ucmd.raw); } diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 05efd23ae..e2e355e14 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -4,14 +4,11 @@ use crate::common::util::*; fn test_missing_operand() { let result = new_ucmd!().run(); - assert_eq!( - true, - result - .stderr - .starts_with("error: The following required arguments were not provided") - ); + assert!(result + .stderr_str() + .starts_with("error: The following required arguments were not provided")); - assert_eq!(true, result.stderr.contains("")); + assert!(result.stderr_str().contains("")); } #[test] @@ -20,14 +17,11 @@ fn test_enter_chroot_fails() { at.mkdir("jail"); - let result = ucmd.arg("jail").run(); + let result = ucmd.arg("jail").fails(); - assert_eq!( - true, - result.stderr.starts_with( - "chroot: error: cannot chroot to jail: Operation not permitted (os error 1)" - ) - ) + assert!(result + .stderr_str() + .starts_with("chroot: error: cannot chroot to jail: Operation not permitted (os error 1)")); } #[test] @@ -47,19 +41,18 @@ fn test_invalid_user_spec() { at.mkdir("a"); - let result = ucmd.arg("a").arg("--userspec=ARABA:").run(); + let result = ucmd.arg("a").arg("--userspec=ARABA:").fails(); - assert_eq!( - true, - result.stderr.starts_with("chroot: error: invalid userspec") - ); + assert!(result + .stderr_str() + .starts_with("chroot: error: invalid userspec")); } #[test] fn test_preference_of_userspec() { let scene = TestScenario::new(util_name!()); let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { + if is_ci() && result.stderr_str().contains("No such user/group") { // In the CI, some server are failing to return whoami. // As seems to be a configuration issue, ignoring it return; @@ -73,7 +66,7 @@ fn test_preference_of_userspec() { println!("result.stdout = {}", result.stdout_str()); println!("result.stderr = {}", result.stderr_str()); - if is_ci() && result.stderr.contains("cannot find name for user ID") { + if is_ci() && result.stderr_str().contains("cannot find name for user ID") { // In the CI, some server are failing to return id. // As seems to be a configuration issue, ignoring it return; diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index f4aabff3e..2d8a54c18 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -42,13 +42,9 @@ static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt"; fn test_cp_cp() { let (at, mut ucmd) = at_and_ucmd!(); // Invoke our binary to make the copy. - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_DEST) - .run(); - - // Check that the exit code represents a successful copy. - assert!(result.success); + .succeeds(); // Check the content of the destination file that was copied. assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); @@ -57,12 +53,9 @@ fn test_cp_cp() { #[test] fn test_cp_existing_target() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_EXISTING_FILE) - .run(); - - assert!(result.success); + .succeeds(); // Check the content of the destination file assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); @@ -74,52 +67,41 @@ fn test_cp_existing_target() { #[test] fn test_cp_duplicate_files() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_COPY_TO_FOLDER) - .run(); - - assert!(result.success); - assert!(result.stderr.contains("specified more than once")); + .succeeds() + .stderr_contains("specified more than once"); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); } #[test] fn test_cp_multiple_files_target_is_file() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_EXISTING_FILE) - .run(); - - assert!(!result.success); - assert!(result.stderr.contains("not a directory")); + .fails() + .stderr_contains("not a directory"); } #[test] fn test_cp_directory_not_recursive() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg(TEST_COPY_TO_FOLDER) .arg(TEST_HELLO_WORLD_DEST) - .run(); - - assert!(!result.success); - assert!(result.stderr.contains("omitting directory")); + .fails() + .stderr_contains("omitting directory"); } #[test] fn test_cp_multiple_files() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_COPY_TO_FOLDER) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n"); } @@ -129,14 +111,11 @@ fn test_cp_multiple_files() { #[cfg(not(macos))] fn test_cp_recurse() { let (at, mut ucmd) = at_and_ucmd!(); - - let result = ucmd - .arg("-r") + ucmd.arg("-r") .arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) - .run(); + .succeeds(); - assert!(result.success); // Check the content of the destination file that was copied. assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n"); } @@ -144,14 +123,10 @@ fn test_cp_recurse() { #[test] fn test_cp_with_dirs_t() { let (at, mut ucmd) = at_and_ucmd!(); - - //using -t option - let result_to_dir_t = ucmd - .arg("-t") + ucmd.arg("-t") .arg(TEST_COPY_TO_FOLDER) .arg(TEST_HELLO_WORLD_SOURCE) - .run(); - assert!(result_to_dir_t.success); + .succeeds(); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); } @@ -162,63 +137,52 @@ fn test_cp_with_dirs() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - //using -t option - let result_to_dir = scene + scene .ucmd() .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_COPY_TO_FOLDER) - .run(); - assert!(result_to_dir.success); + .succeeds(); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); - let result_from_dir = scene + scene .ucmd() .arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_HELLO_WORLD_DEST) - .run(); - assert!(result_from_dir.success); + .succeeds(); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); } #[test] fn test_cp_arg_target_directory() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("-t") .arg(TEST_COPY_TO_FOLDER) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); } #[test] fn test_cp_arg_no_target_directory() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg(TEST_HELLO_WORLD_SOURCE) .arg("-v") .arg("-T") .arg(TEST_COPY_TO_FOLDER) - .run(); - - assert!(!result.success); - assert!(result.stderr.contains("cannot overwrite directory")); + .fails() + .stderr_contains("cannot overwrite directory"); } #[test] fn test_cp_arg_interactive() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) .arg("-i") .pipe_in("N\n") - .run(); - - assert!(result.success); - assert!(result.stderr.contains("Not overwriting")); + .succeeds() + .stderr_contains("Not overwriting"); } #[test] @@ -227,39 +191,33 @@ fn test_cp_arg_link() { use std::os::linux::fs::MetadataExt; let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--link") .arg(TEST_HELLO_WORLD_DEST) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2); } #[test] fn test_cp_arg_symlink() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--symbolic-link") .arg(TEST_HELLO_WORLD_DEST) - .run(); + .succeeds(); - assert!(result.success); assert!(at.is_symlink(TEST_HELLO_WORLD_DEST)); } #[test] fn test_cp_arg_no_clobber() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--no-clobber") .arg(TEST_HOW_ARE_YOU_SOURCE) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); } @@ -267,34 +225,31 @@ fn test_cp_arg_no_clobber() { fn test_cp_arg_no_clobber_twice() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; + at.touch("source.txt"); - let result = scene + scene .ucmd() .arg("--no-clobber") .arg("source.txt") .arg("dest.txt") - .run(); + .succeeds() + .no_stderr(); - println!("stderr = {:?}", result.stderr_str()); - println!("stdout = {:?}", result.stdout_str()); - assert!(result.success); - assert!(result.stderr.is_empty()); assert_eq!(at.read("source.txt"), ""); at.append("source.txt", "some-content"); - let result = scene + scene .ucmd() .arg("--no-clobber") .arg("source.txt") .arg("dest.txt") - .run(); + .succeeds() + .stdout_does_not_contain("Not overwriting"); - assert!(result.success); assert_eq!(at.read("source.txt"), "some-content"); // Should be empty as the "no-clobber" should keep // the previous version assert_eq!(at.read("dest.txt"), ""); - assert!(!result.stderr.contains("Not overwriting")); } #[test] @@ -311,16 +266,11 @@ fn test_cp_arg_force() { permissions.set_readonly(true); set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--force") .arg(TEST_HELLO_WORLD_DEST) - .run(); + .succeeds(); - println!("{:?}", result.stderr_str()); - println!("{:?}", result.stdout_str()); - - assert!(result.success); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); } @@ -342,13 +292,11 @@ fn test_cp_arg_remove_destination() { permissions.set_readonly(true); set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--remove-destination") .arg(TEST_HELLO_WORLD_DEST) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); } @@ -356,13 +304,11 @@ fn test_cp_arg_remove_destination() { fn test_cp_arg_backup() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--backup") .arg(TEST_HOW_ARE_YOU_SOURCE) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); assert_eq!( at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), @@ -374,14 +320,12 @@ fn test_cp_arg_backup() { fn test_cp_arg_suffix() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--suffix") .arg(".bak") .arg(TEST_HOW_ARE_YOU_SOURCE) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); assert_eq!( at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)), @@ -391,9 +335,8 @@ fn test_cp_arg_suffix() { #[test] fn test_cp_deref_conflicting_options() { - let (_at, mut ucmd) = at_and_ucmd!(); - - ucmd.arg("-LP") + new_ucmd!() + .arg("-LP") .arg(TEST_COPY_TO_FOLDER) .arg(TEST_HELLO_WORLD_SOURCE) .fails(); @@ -401,8 +344,7 @@ fn test_cp_deref_conflicting_options() { #[test] fn test_cp_deref() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let (at, mut ucmd) = at_and_ucmd!(); #[cfg(not(windows))] let _r = fs::symlink( @@ -415,16 +357,12 @@ fn test_cp_deref() { at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), ); //using -L option - let result = scene - .ucmd() - .arg("-L") + ucmd.arg("-L") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE_SYMLINK) .arg(TEST_COPY_TO_FOLDER) - .run(); + .succeeds(); - // Check that the exit code represents a successful copy. - assert!(result.success); let path_to_new_symlink = at .subdir .join(TEST_COPY_TO_FOLDER) @@ -444,8 +382,7 @@ fn test_cp_deref() { } #[test] fn test_cp_no_deref() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let (at, mut ucmd) = at_and_ucmd!(); #[cfg(not(windows))] let _r = fs::symlink( @@ -458,16 +395,12 @@ fn test_cp_no_deref() { at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), ); //using -P option - let result = scene - .ucmd() - .arg("-P") + ucmd.arg("-P") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE_SYMLINK) .arg(TEST_COPY_TO_FOLDER) - .run(); + .succeeds(); - // Check that the exit code represents a successful copy. - assert!(result.success); let path_to_new_symlink = at .subdir .join(TEST_COPY_TO_FOLDER) @@ -490,14 +423,10 @@ fn test_cp_strip_trailing_slashes() { let (at, mut ucmd) = at_and_ucmd!(); //using --strip-trailing-slashes option - let result = ucmd - .arg("--strip-trailing-slashes") + ucmd.arg("--strip-trailing-slashes") .arg(format!("{}/", TEST_HELLO_WORLD_SOURCE)) .arg(TEST_HELLO_WORLD_DEST) - .run(); - - // Check that the exit code represents a successful copy. - assert!(result.success); + .succeeds(); // Check the content of the destination file that was copied. assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); @@ -507,14 +436,11 @@ fn test_cp_strip_trailing_slashes() { fn test_cp_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg("--parents") + ucmd.arg("--parents") .arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_COPY_TO_FOLDER) - .run(); + .succeeds(); - assert!(result.success); - // Check the content of the destination file that was copied. assert_eq!( at.read(&format!( "{}/{}", @@ -528,14 +454,12 @@ fn test_cp_parents() { fn test_cp_parents_multiple_files() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg("--parents") + ucmd.arg("--parents") .arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_COPY_TO_FOLDER) - .run(); + .succeeds(); - assert!(result.success); assert_eq!( at.read(&format!( "{}/{}", @@ -554,20 +478,12 @@ fn test_cp_parents_multiple_files() { #[test] fn test_cp_parents_dest_not_directory() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd + new_ucmd!() .arg("--parents") .arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_HELLO_WORLD_DEST) - .run(); - println!("{:?}", result); - - // Check that we did not succeed in copying. - assert!(!result.success); - assert!(result - .stderr - .contains("with --parents, the destination must be a directory")); + .fails() + .stderr_contains("with --parents, the destination must be a directory"); } #[test] @@ -594,18 +510,14 @@ fn test_cp_deref_folder_to_folder() { assert!(env::set_current_dir(&cwd).is_ok()); //using -P -R option - let result = scene + scene .ucmd() .arg("-L") .arg("-R") .arg("-v") .arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) - .run(); - println!("cp output {}", result.stdout_str()); - - // Check that the exit code represents a successful copy. - assert!(result.success); + .succeeds(); #[cfg(not(windows))] { @@ -698,18 +610,14 @@ fn test_cp_no_deref_folder_to_folder() { assert!(env::set_current_dir(&cwd).is_ok()); //using -P -R option - let result = scene + scene .ucmd() .arg("-P") .arg("-R") .arg("-v") .arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) - .run(); - println!("cp output {}", result.stdout_str()); - - // Check that the exit code represents a successful copy. - assert!(result.success); + .succeeds(); #[cfg(not(windows))] { @@ -791,13 +699,11 @@ fn test_cp_archive() { previous, ) .unwrap(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--archive") .arg(TEST_HOW_ARE_YOU_SOURCE) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap(); @@ -807,11 +713,10 @@ fn test_cp_archive() { let creation2 = metadata2.modified().unwrap(); let scene2 = TestScenario::new("ls"); - let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run(); + let result = scene2.cmd("ls").arg("-al").arg(at.subdir).succeeds(); println!("ls dest {}", result.stdout_str()); assert_eq!(creation, creation2); - assert!(result.success); } #[test] @@ -850,11 +755,10 @@ fn test_cp_archive_recursive() { // Back to the initial cwd (breaks the other tests) assert!(env::set_current_dir(&cwd).is_ok()); - let resultg = ucmd - .arg("--archive") + ucmd.arg("--archive") .arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) - .run(); + .fails(); // fails for now let scene2 = TestScenario::new("ls"); let result = scene2 @@ -865,7 +769,6 @@ fn test_cp_archive_recursive() { println!("ls dest {}", result.stdout_str()); - let scene2 = TestScenario::new("ls"); let result = scene2 .cmd("ls") .arg("-al") @@ -910,9 +813,6 @@ fn test_cp_archive_recursive() { .join("2.link") .to_string_lossy() )); - - // fails for now - assert!(resultg.success); } #[test] @@ -928,13 +828,11 @@ fn test_cp_preserve_timestamps() { previous, ) .unwrap(); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--preserve=timestamps") .arg(TEST_HOW_ARE_YOU_SOURCE) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap(); @@ -948,7 +846,6 @@ fn test_cp_preserve_timestamps() { println!("ls dest {}", result.stdout_str()); assert_eq!(creation, creation2); - assert!(result.success); } #[test] @@ -966,13 +863,11 @@ fn test_cp_dont_preserve_timestamps() { .unwrap(); sleep(Duration::from_secs(3)); - let result = ucmd - .arg(TEST_HELLO_WORLD_SOURCE) + ucmd.arg(TEST_HELLO_WORLD_SOURCE) .arg("--no-preserve=timestamps") .arg(TEST_HOW_ARE_YOU_SOURCE) - .run(); + .succeeds(); - assert!(result.success); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap(); @@ -992,7 +887,6 @@ fn test_cp_dont_preserve_timestamps() { // Some margins with time check assert!(res.as_secs() > 3595); assert!(res.as_secs() < 3605); - assert!(result.success); } #[test] @@ -1017,7 +911,7 @@ fn test_cp_one_file_system() { let scene = TestScenario::new(util_name!()); // Test must be run as root (or with `sudo -E`) - if scene.cmd("whoami").run().stdout != "root\n" { + if scene.cmd("whoami").run().stdout_str() != "root\n" { return; } @@ -1042,17 +936,16 @@ fn test_cp_one_file_system() { at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE); // Begin testing -x flag - let result = scene + scene .ucmd() .arg("-rx") .arg(TEST_MOUNT_COPY_FROM_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) - .run(); + .succeeds(); // Ditch the mount before the asserts scene.cmd("umount").arg(mountpoint_path).succeeds(); - assert!(result.success); assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE)); // Check if the other files were copied from the source folder hirerarchy for entry in WalkDir::new(at_src.as_string()) { diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 1933fdba3..0c66c563e 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -7,174 +7,147 @@ use rust_users::*; #[test] fn test_date_email() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--rfc-email").run(); - assert!(result.success); + new_ucmd!().arg("--rfc-email").succeeds(); } #[test] fn test_date_email2() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-R").run(); - assert!(result.success); + new_ucmd!().arg("-R").succeeds(); } #[test] fn test_date_rfc_3339() { let scene = TestScenario::new(util_name!()); - let mut result = scene.ucmd().arg("--rfc-3339=ns").succeeds(); + let rfc_regexp = concat!( + r#"(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])\s([01]\d|2[0-3]):"#, + r#"([0-5]\d):([0-5]\d|60)(\.\d+)?(([Zz])|([\+|\-]([01]\d|2[0-3])))"# + ); + let re = Regex::new(rfc_regexp).unwrap(); // Check that the output matches the regexp - let rfc_regexp = r"(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(\.\d+)?(([Zz])|([\+|\-]([01]\d|2[0-3])))"; - let re = Regex::new(rfc_regexp).unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene + .ucmd() + .arg("--rfc-3339=ns") + .succeeds() + .stdout_matches(&re); - result = scene.ucmd().arg("--rfc-3339=seconds").succeeds(); - - // Check that the output matches the regexp - let re = Regex::new(rfc_regexp).unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene + .ucmd() + .arg("--rfc-3339=seconds") + .succeeds() + .stdout_matches(&re); } #[test] fn test_date_rfc_8601() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--iso-8601=ns").run(); - assert!(result.success); + new_ucmd!().arg("--iso-8601=ns").succeeds(); } #[test] fn test_date_rfc_8601_second() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--iso-8601=second").run(); - assert!(result.success); + new_ucmd!().arg("--iso-8601=second").succeeds(); } #[test] fn test_date_utc() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--utc").run(); - assert!(result.success); + new_ucmd!().arg("--utc").succeeds(); } #[test] fn test_date_universal() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--universal").run(); - assert!(result.success); + new_ucmd!().arg("--universal").succeeds(); } #[test] fn test_date_format_y() { let scene = TestScenario::new(util_name!()); - let mut result = scene.ucmd().arg("+%Y").succeeds(); - - assert!(result.success); let mut re = Regex::new(r"^\d{4}$").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene.ucmd().arg("+%Y").succeeds().stdout_matches(&re); - result = scene.ucmd().arg("+%y").succeeds(); - - assert!(result.success); re = Regex::new(r"^\d{2}$").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene.ucmd().arg("+%y").succeeds().stdout_matches(&re); } #[test] fn test_date_format_m() { let scene = TestScenario::new(util_name!()); - let mut result = scene.ucmd().arg("+%b").succeeds(); - - assert!(result.success); let mut re = Regex::new(r"\S+").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene.ucmd().arg("+%b").succeeds().stdout_matches(&re); - result = scene.ucmd().arg("+%m").succeeds(); - - assert!(result.success); re = Regex::new(r"^\d{2}$").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene.ucmd().arg("+%m").succeeds().stdout_matches(&re); } #[test] fn test_date_format_day() { let scene = TestScenario::new(util_name!()); - let mut result = scene.ucmd().arg("+%a").succeeds(); - - assert!(result.success); let mut re = Regex::new(r"\S+").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); - - result = scene.ucmd().arg("+%A").succeeds(); - - assert!(result.success); + scene.ucmd().arg("+%a").succeeds().stdout_matches(&re); re = Regex::new(r"\S+").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene.ucmd().arg("+%A").succeeds().stdout_matches(&re); - result = scene.ucmd().arg("+%u").succeeds(); - - assert!(result.success); re = Regex::new(r"^\d{1}$").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + scene.ucmd().arg("+%u").succeeds().stdout_matches(&re); } #[test] fn test_date_format_full_day() { - let scene = TestScenario::new(util_name!()); - - let result = scene.ucmd().arg("+'%a %Y-%m-%d'").succeeds(); - - assert!(result.success); let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap(); - assert!(re.is_match(&result.stdout_str().trim())); + new_ucmd!() + .arg("+'%a %Y-%m-%d'") + .succeeds() + .stdout_matches(&re); } #[test] #[cfg(all(unix, not(target_os = "macos")))] fn test_date_set_valid() { if get_effective_uid() == 0 { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg("--set") .arg("2020-03-12 13:30:00+08:00") - .succeeds(); - result.no_stdout().no_stderr(); + .succeeds() + .no_stdout() + .no_stderr(); } } #[test] #[cfg(any(windows, all(unix, not(target_os = "macos"))))] fn test_date_set_invalid() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--set").arg("123abcd").fails(); - let result = result.no_stdout(); - assert!(result.stderr.starts_with("date: invalid date ")); + let result = new_ucmd!().arg("--set").arg("123abcd").fails(); + result.no_stdout(); + assert!(result.stderr_str().starts_with("date: invalid date ")); } #[test] #[cfg(all(unix, not(target_os = "macos")))] fn test_date_set_permissions_error() { if !(get_effective_uid() == 0 || is_wsl()) { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); - let result = result.no_stdout(); - assert!(result.stderr.starts_with("date: cannot set date: ")); + let result = new_ucmd!() + .arg("--set") + .arg("2020-03-11 21:45:00+08:00") + .fails(); + result.no_stdout(); + assert!(result.stderr_str().starts_with("date: cannot set date: ")); } } #[test] #[cfg(target_os = "macos")] fn test_date_set_mac_unavailable() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); - let result = result.no_stdout(); + let result = new_ucmd!() + .arg("--set") + .arg("2020-03-11 21:45:00+08:00") + .fails(); + result.no_stdout(); assert!(result - .stderr + .stderr_str() .starts_with("date: setting the date is not supported by macOS")); } @@ -183,13 +156,12 @@ fn test_date_set_mac_unavailable() { /// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_2() { if get_effective_uid() == 0 { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + let result = new_ucmd!() .arg("--set") .arg("Sat 20 Mar 2021 14:53:01 AWST") .fails(); - let result = result.no_stdout(); - assert!(result.stderr.starts_with("date: invalid date ")); + result.no_stdout(); + assert!(result.stderr_str().starts_with("date: invalid date ")); } } @@ -198,13 +170,12 @@ fn test_date_set_valid_2() { /// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_3() { if get_effective_uid() == 0 { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + let result = new_ucmd!() .arg("--set") .arg("Sat 20 Mar 2021 14:53:01") // Local timezone .fails(); - let result = result.no_stdout(); - assert!(result.stderr.starts_with("date: invalid date ")); + result.no_stdout(); + assert!(result.stderr_str().starts_with("date: invalid date ")); } } @@ -213,12 +184,11 @@ fn test_date_set_valid_3() { /// TODO: expected to fail currently; change to succeeds() when required. fn test_date_set_valid_4() { if get_effective_uid() == 0 { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + let result = new_ucmd!() .arg("--set") .arg("2020-03-11 21:45:00") // Local timezone .fails(); - let result = result.no_stdout(); - assert!(result.stderr.starts_with("date: invalid date ")); + result.no_stdout(); + assert!(result.stderr_str().starts_with("date: invalid date ")); } } diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index f79d1beb5..0ae8d2339 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -2,30 +2,22 @@ use crate::common::util::*; #[test] fn test_df_compatible_no_size_arg() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-a").run(); - assert!(result.success); + new_ucmd!().arg("-a").succeeds(); } #[test] fn test_df_compatible() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-ah").run(); - assert!(result.success); + new_ucmd!().arg("-ah").succeeds(); } #[test] fn test_df_compatible_type() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-aT").run(); - assert!(result.success); + new_ucmd!().arg("-aT").succeeds(); } #[test] fn test_df_compatible_si() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-aH").run(); - assert!(result.success); + new_ucmd!().arg("-aH").succeeds(); } // ToDO: more tests... diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 16adcb974..45704eb41 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -220,26 +220,36 @@ fn test_du_time() { .arg("date_test") .succeeds() .stdout_only("0\t2015-05-15 00:00\tdate_test\n"); - - // cleanup by removing test file - scene.cmd("rm").arg("date_test").succeeds(); // TODO: is this necessary? } #[cfg(not(target_os = "windows"))] #[cfg(feature = "chmod")] #[test] fn test_du_no_permission() { - let ts = TestScenario::new(util_name!()); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; - let _chmod = ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds(); - let result = ts.ucmd().arg(SUB_DIR_LINKS).succeeds(); + at.mkdir_all(SUB_DIR_LINKS); - ts.ccmd("chmod").arg("+r").arg(SUB_DIR_LINKS).run(); + scene.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds(); - assert_eq!( - result.stderr_str(), - "du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)\n" + let result = scene.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed + result.stderr_contains( + "du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)", ); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).fails(); + if result_reference + .stderr_str() + .contains("du: cannot read directory 'subdir/links': Permission denied") + { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } + _du_no_permission(result.stdout_str()); } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 39baf473b..e86a41783 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -140,8 +140,11 @@ fn test_unset_variable() { #[test] fn test_fail_null_with_program() { - let out = new_ucmd!().arg("--null").arg("cd").fails().stderr; - assert!(out.contains("cannot specify --null (-0) with command")); + new_ucmd!() + .arg("--null") + .arg("cd") + .fails() + .stderr_contains("cannot specify --null (-0) with command"); } #[cfg(not(windows))] diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index f962a9137..21a5f3396 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -29,7 +29,7 @@ fn test_fmt_w_too_big() { .run(); //.stdout_is_fixture("call_graph.expected"); assert_eq!( - result.stderr.trim(), + result.stderr_str().trim(), "fmt: error: invalid width: '2501': Numerical result out of range" ); } diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 32a16cc1a..cee13bdc3 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -10,7 +10,7 @@ fn test_groups() { // As seems to be a configuration issue, ignoring it return; } - assert!(result.success); + result.success(); assert!(!result.stdout_str().trim().is_empty()); } @@ -30,16 +30,12 @@ fn test_groups_arg() { println!("result.stdout = {}", result.stdout_str()); println!("result.stderr = {}", result.stderr_str()); - assert!(result.success); + result.success(); assert!(!result.stdout_str().is_empty()); let username = result.stdout_str().trim(); // call groups with the user name to check that we // are getting something - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg(username).run(); - println!("result.stdout = {}", result.stdout_str()); - println!("result.stderr = {}", result.stderr_str()); - assert!(result.success); + new_ucmd!().arg(username).succeeds(); assert!(!result.stdout_str().is_empty()); } diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index d91cc1289..4f009c800 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -156,14 +156,10 @@ fn test_negative_zero_bytes() { } #[test] fn test_no_such_file_or_directory() { - let result = new_ucmd!().arg("no_such_file.toml").run(); - - assert_eq!( - true, - result - .stderr - .contains("cannot open 'no_such_file.toml' for reading: No such file or directory") - ) + new_ucmd!() + .arg("no_such_file.toml") + .fails() + .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory"); } // there was a bug not caught by previous tests diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index fa23de745..fc4459072 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -11,12 +11,10 @@ use std::thread::sleep; fn test_install_help() { let (_, mut ucmd) = at_and_ucmd!(); - assert!(ucmd - .arg("--help") + ucmd.arg("--help") .succeeds() .no_stderr() - .stdout - .contains("FLAGS:")); + .stdout_contains("FLAGS:"); } #[test] @@ -59,13 +57,11 @@ fn test_install_failing_not_dir() { at.touch(file1); at.touch(file2); at.touch(file3); - assert!(ucmd - .arg(file1) + ucmd.arg(file1) .arg(file2) .arg(file3) .fails() - .stderr - .contains("not a directory")); + .stderr_contains("not a directory"); } #[test] @@ -77,13 +73,11 @@ fn test_install_unimplemented_arg() { at.touch(file); at.mkdir(dir); - assert!(ucmd - .arg(context_arg) + ucmd.arg(context_arg) .arg(file) .arg(dir) .fails() - .stderr - .contains("Unimplemented")); + .stderr_contains("Unimplemented"); assert!(!at.file_exists(&format!("{}/{}", dir, file))); } @@ -231,13 +225,11 @@ fn test_install_mode_failing() { at.touch(file); at.mkdir(dir); - assert!(ucmd - .arg(file) + ucmd.arg(file) .arg(dir) .arg(mode_arg) .fails() - .stderr - .contains("Invalid mode string: invalid digit found in string")); + .stderr_contains("Invalid mode string: invalid digit found in string"); let dest_file = &format!("{}/{}", dir, file); assert!(at.file_exists(file)); @@ -336,7 +328,7 @@ fn test_install_target_new_file_with_owner() { .arg(format!("{}/{}", dir, file)) .run(); - if is_ci() && result.stderr.contains("error: no such user:") { + if is_ci() && result.stderr_str().contains("error: no such user:") { // In the CI, some server are failing to return the user id. // As seems to be a configuration issue, ignoring it return; @@ -619,35 +611,27 @@ fn test_install_and_strip_with_program() { #[test] #[cfg(not(windows))] fn test_install_and_strip_with_invalid_program() { - let scene = TestScenario::new(util_name!()); - - let stderr = scene - .ucmd() + new_ucmd!() .arg("-s") .arg("--strip-program") .arg("/bin/date") .arg(strip_source_file()) .arg(STRIP_TARGET_FILE) .fails() - .stderr; - assert!(stderr.contains("strip program failed")); + .stderr_contains("strip program failed"); } #[test] #[cfg(not(windows))] fn test_install_and_strip_with_non_existent_program() { - let scene = TestScenario::new(util_name!()); - - let stderr = scene - .ucmd() + new_ucmd!() .arg("-s") .arg("--strip-program") .arg("/usr/bin/non_existent_program") .arg(strip_source_file()) .arg(STRIP_TARGET_FILE) .fails() - .stderr; - assert!(stderr.contains("No such file or directory")); + .stderr_contains("No such file or directory"); } #[test] diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index d7a13b0d4..646091b09 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -299,13 +299,11 @@ fn test_symlink_overwrite_dir_fail() { at.touch(path_a); at.mkdir(path_b); - assert!( - ucmd.args(&["-s", "-T", path_a, path_b]) - .fails() - .stderr - .len() - > 0 - ); + assert!(!ucmd + .args(&["-s", "-T", path_a, path_b]) + .fails() + .stderr_str() + .is_empty()); } #[test] @@ -358,7 +356,11 @@ fn test_symlink_target_only() { at.mkdir(dir); - assert!(ucmd.args(&["-s", "-t", dir]).fails().stderr.len() > 0); + assert!(!ucmd + .args(&["-s", "-t", dir]) + .fails() + .stderr_str() + .is_empty()); } #[test] diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d810cdc29..5583dbaca 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -103,6 +103,14 @@ fn test_ls_width() { .succeeds() .stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); } + + for option in &["-w 1a", "-w=1a", "--width=1a", "--width 1a"] { + scene + .ucmd() + .args(&option.split(" ").collect::>()) + .fails() + .stderr_only("ls: error: invalid line width: ‘1a’"); + } } #[test] diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index f60c0a4b8..23108d976 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -19,8 +19,7 @@ fn test_create_one_fifo_with_invalid_mode() { .arg("-m") .arg("invalid") .fails() - .stderr - .contains("invalid mode"); + .stderr_contains("invalid mode"); } #[test] diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 736fb6956..9245733ca 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -2,18 +2,15 @@ use crate::common::util::*; #[test] fn test_more_no_arg() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - assert!(!result.success); + // stderr = more: Reading from stdin isn't supported yet. + new_ucmd!().fails(); } #[test] fn test_more_dir_arg() { - let (_, mut ucmd) = at_and_ucmd!(); - ucmd.arg("."); - let result = ucmd.run(); - assert!(!result.success); + let result = new_ucmd!().arg(".").run(); + result.failure(); const EXPECTED_ERROR_MESSAGE: &str = "more: '.' is a directory.\nTry 'more --help' for more information."; - assert_eq!(result.stderr.trim(), EXPECTED_ERROR_MESSAGE); + assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE); } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 0caeb1ef1..e8ba43282 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -476,16 +476,9 @@ fn test_mv_overwrite_nonempty_dir() { // GNU: "mv: cannot move ‘a’ to ‘b’: Directory not empty" // Verbose output for the move should not be shown on failure - assert!( - ucmd.arg("-vT") - .arg(dir_a) - .arg(dir_b) - .fails() - .no_stdout() - .stderr - .len() - > 0 - ); + let result = ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails(); + result.no_stdout(); + assert!(!result.stderr_str().is_empty()); assert!(at.dir_exists(dir_a)); assert!(at.dir_exists(dir_b)); @@ -526,15 +519,15 @@ fn test_mv_errors() { // $ mv -T -t a b // mv: cannot combine --target-directory (-t) and --no-target-directory (-T) - let result = scene + scene .ucmd() .arg("-T") .arg("-t") .arg(dir) .arg(file_a) .arg(file_b) - .fails(); - assert!(result.stderr.contains("cannot be used with")); + .fails() + .stderr_contains("cannot be used with"); // $ at.touch file && at.mkdir dir // $ mv -T file dir @@ -553,7 +546,13 @@ fn test_mv_errors() { // $ at.mkdir dir && at.touch file // $ mv dir file // err == mv: cannot overwrite non-directory ‘file’ with directory ‘dir’ - assert!(scene.ucmd().arg(dir).arg(file_a).fails().stderr.len() > 0); + assert!(!scene + .ucmd() + .arg(dir) + .arg(file_a) + .fails() + .stderr_str() + .is_empty()); } #[test] diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 7e704fc00..d3457c686 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -16,7 +16,7 @@ fn test_negative_adjustment() { let res = new_ucmd!().args(&["-n", "-1", "true"]).run(); assert!(res - .stderr + .stderr_str() .starts_with("nice: warning: setpriority: Permission denied")); } diff --git a/tests/by-util/test_shuf.rs b/tests/by-util/test_shuf.rs index 717971bd4..f925f8357 100644 --- a/tests/by-util/test_shuf.rs +++ b/tests/by-util/test_shuf.rs @@ -9,35 +9,28 @@ fn test_output_is_random_permutation() { .collect::>() .join("\n"); - let result = new_ucmd!() - .pipe_in(input.as_bytes()) - .succeeds() - .no_stderr() - .stdout - .clone(); + let result = new_ucmd!().pipe_in(input.as_bytes()).succeeds(); + result.no_stderr(); let mut result_seq: Vec = result + .stdout_str() .split("\n") .filter(|x| !x.is_empty()) .map(|x| x.parse().unwrap()) .collect(); result_seq.sort(); - assert_ne!(result, input, "Output is not randomised"); + assert_ne!(result.stdout_str(), input, "Output is not randomised"); assert_eq!(result_seq, input_seq, "Output is not a permutation"); } #[test] fn test_zero_termination() { let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let result = new_ucmd!() - .arg("-z") - .arg("-i1-10") - .succeeds() - .no_stderr() - .stdout - .clone(); + let result = new_ucmd!().arg("-z").arg("-i1-10").succeeds(); + result.no_stderr(); let mut result_seq: Vec = result + .stdout_str() .split("\0") .filter(|x| !x.is_empty()) .map(|x| x.parse().unwrap()) @@ -57,12 +50,11 @@ fn test_echo() { .map(|x| x.to_string()) .collect::>(), ) - .succeeds() - .no_stderr() - .stdout - .clone(); + .succeeds(); + result.no_stderr(); let mut result_seq: Vec = result + .stdout_str() .split("\n") .filter(|x| !x.is_empty()) .map(|x| x.parse().unwrap()) @@ -84,12 +76,11 @@ fn test_head_count() { let result = new_ucmd!() .args(&["-n", &repeat_limit.to_string()]) .pipe_in(input.as_bytes()) - .succeeds() - .no_stderr() - .stdout - .clone(); + .succeeds(); + result.no_stderr(); let mut result_seq: Vec = result + .stdout_str() .split("\n") .filter(|x| !x.is_empty()) .map(|x| x.parse().unwrap()) @@ -99,7 +90,7 @@ fn test_head_count() { assert!( result_seq.iter().all(|x| input_seq.contains(x)), "Output includes element not from input: {}", - result + result.stdout_str() ) } @@ -117,12 +108,11 @@ fn test_repeat() { .arg("-r") .args(&["-n", &repeat_limit.to_string()]) .pipe_in(input.as_bytes()) - .succeeds() - .no_stderr() - .stdout - .clone(); + .succeeds(); + result.no_stderr(); let result_seq: Vec = result + .stdout_str() .split("\n") .filter(|x| !x.is_empty()) .map(|x| x.parse().unwrap()) @@ -146,14 +136,11 @@ fn test_repeat() { fn test_file_input() { let expected_seq = vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; - let result = new_ucmd!() - .arg("file_input.txt") - .succeeds() - .no_stderr() - .stdout - .clone(); + let result = new_ucmd!().arg("file_input.txt").succeeds(); + result.no_stderr(); let mut result_seq: Vec = result + .stdout_str() .split("\n") .filter(|x| !x.is_empty()) .map(|x| x.parse().unwrap()) @@ -164,52 +151,50 @@ fn test_file_input() { #[test] fn test_shuf_echo_and_input_range_not_allowed() { - let result = new_ucmd!().args(&["-e", "0", "-i", "0-2"]).run(); - - assert!(!result.success); - assert!(result - .stderr - .contains("The argument '--input-range ' cannot be used with '--echo ...'")); + new_ucmd!() + .args(&["-e", "0", "-i", "0-2"]) + .fails() + .stderr_contains( + "The argument '--input-range ' cannot be used with '--echo ...'", + ); } #[test] fn test_shuf_input_range_and_file_not_allowed() { - let result = new_ucmd!().args(&["-i", "0-9", "file"]).run(); - - assert!(!result.success); - assert!(result - .stderr - .contains("The argument '' cannot be used with '--input-range '")); + new_ucmd!() + .args(&["-i", "0-9", "file"]) + .fails() + .stderr_contains("The argument '' cannot be used with '--input-range '"); } #[test] fn test_shuf_invalid_input_range_one() { - let result = new_ucmd!().args(&["-i", "0"]).run(); - - assert!(!result.success); - assert!(result.stderr.contains("invalid input range")); + new_ucmd!() + .args(&["-i", "0"]) + .fails() + .stderr_contains("invalid input range"); } #[test] fn test_shuf_invalid_input_range_two() { - let result = new_ucmd!().args(&["-i", "a-9"]).run(); - - assert!(!result.success); - assert!(result.stderr.contains("invalid input range: 'a'")); + new_ucmd!() + .args(&["-i", "a-9"]) + .fails() + .stderr_contains("invalid input range: 'a'"); } #[test] fn test_shuf_invalid_input_range_three() { - let result = new_ucmd!().args(&["-i", "0-b"]).run(); - - assert!(!result.success); - assert!(result.stderr.contains("invalid input range: 'b'")); + new_ucmd!() + .args(&["-i", "0-b"]) + .fails() + .stderr_contains("invalid input range: 'b'"); } #[test] fn test_shuf_invalid_input_line_count() { - let result = new_ucmd!().args(&["-n", "a"]).run(); - - assert!(!result.success); - assert!(result.stderr.contains("invalid line count: 'a'")); + new_ucmd!() + .args(&["-n", "a"]) + .fails() + .stderr_contains("invalid line count: 'a'"); } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 376b3db51..60d735c51 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -337,5 +337,5 @@ fn expected_result(args: &[&str]) -> String { .env("LANGUAGE", "C") .args(args) .run() - .stdout + .stdout_move_str() } diff --git a/tests/by-util/test_tac.rs b/tests/by-util/test_tac.rs index 3733adbec..a8adbb28e 100644 --- a/tests/by-util/test_tac.rs +++ b/tests/by-util/test_tac.rs @@ -52,18 +52,19 @@ fn test_single_non_newline_separator_before() { #[test] fn test_invalid_input() { - let (_, mut ucmd) = at_and_ucmd!(); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; - ucmd.arg("b") - .run() - .stderr - .contains("tac: error: failed to open 'b' for reading"); - - let (at, mut ucmd) = at_and_ucmd!(); + scene + .ucmd() + .arg("b") + .fails() + .stderr_contains("failed to open 'b' for reading: No such file or directory"); at.mkdir("a"); - ucmd.arg("a") - .run() - .stderr - .contains("tac: error: failed to read 'a'"); + scene + .ucmd() + .arg("a") + .fails() + .stderr_contains("dir: read error: Invalid argument"); } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index a1500bcf6..630c305c6 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -120,19 +120,15 @@ fn test_truncate_with_set1_shorter_than_set2() { #[test] fn missing_args_fails() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - - assert!(!result.success); - assert!(result.stderr.contains("missing operand")); + ucmd.fails().stderr_contains("missing operand"); } #[test] fn missing_required_second_arg_fails() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.args(&["foo"]).run(); - - assert!(!result.success); - assert!(result.stderr.contains("missing operand after")); + ucmd.args(&["foo"]) + .fails() + .stderr_contains("missing operand after"); } #[test] diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 0ea6de281..0da6f44e4 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -18,33 +18,35 @@ fn test_sort_self_loop() { #[test] fn test_no_such_file() { - let result = new_ucmd!().arg("invalid_file_txt").run(); - - assert_eq!(true, result.stderr.contains("No such file or directory")); + new_ucmd!() + .arg("invalid_file_txt") + .fails() + .stderr_contains("No such file or directory"); } #[test] fn test_version_flag() { - let version_short = new_ucmd!().arg("-V").run(); - let version_long = new_ucmd!().arg("--version").run(); + let version_short = new_ucmd!().arg("-V").succeeds(); + let version_long = new_ucmd!().arg("--version").succeeds(); - assert_eq!(version_short.stdout(), version_long.stdout()); + assert_eq!(version_short.stdout_str(), version_long.stdout_str()); } #[test] fn test_help_flag() { - let help_short = new_ucmd!().arg("-h").run(); - let help_long = new_ucmd!().arg("--help").run(); + let help_short = new_ucmd!().arg("-h").succeeds(); + let help_long = new_ucmd!().arg("--help").succeeds(); - assert_eq!(help_short.stdout(), help_long.stdout()); + assert_eq!(help_short.stdout_str(), help_long.stdout_str()); } #[test] fn test_multiple_arguments() { - let result = new_ucmd!() + new_ucmd!() .arg("call_graph.txt") - .arg("invalid_file.txt") - .run(); - - assert_eq!(true, result.stderr.contains("error: Found argument 'invalid_file.txt' which wasn't expected, or isn't valid in this context")) + .arg("invalid_file") + .fails() + .stderr_contains( + "Found argument 'invalid_file' which wasn't expected, or isn't valid in this context", + ); } diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 89b7cec93..32d2427e0 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -23,7 +23,7 @@ fn test_heading() { for opt in vec!["-H"] { // allow whitespace variation // * minor whitespace differences occur between platform built-in outputs; specifically number of TABs between "TIME" and "COMMENT" may be variant - let actual = new_ucmd!().arg(opt).run().stdout; + let actual = new_ucmd!().arg(opt).run().stdout_move_str(); let expect = expected_result(opt); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -80,5 +80,5 @@ fn expected_result(arg: &str) -> String { .env("LANGUAGE", "C") .args(&[arg]) .run() - .stdout + .stdout_move_str() } diff --git a/tests/common/util.rs b/tests/common/util.rs index 55e121737..af3b6f1eb 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -74,11 +74,11 @@ pub struct CmdResult { code: Option, /// zero-exit from running the Command? /// see [`success`] - pub success: bool, + success: bool, /// captured standard output after running the Command - pub stdout: String, + stdout: String, /// captured standard error after running the Command - pub stderr: String, + stderr: String, } impl CmdResult { @@ -329,14 +329,14 @@ impl CmdResult { } pub fn stdout_matches(&self, regex: ®ex::Regex) -> &CmdResult { - if !regex.is_match(self.stdout_str()) { + if !regex.is_match(self.stdout_str().trim()) { panic!("Stdout does not match regex:\n{}", self.stdout_str()) } self } pub fn stdout_does_not_match(&self, regex: ®ex::Regex) -> &CmdResult { - if regex.is_match(self.stdout_str()) { + if regex.is_match(self.stdout_str().trim()) { panic!("Stdout matches regex:\n{}", self.stdout_str()) } self @@ -696,8 +696,11 @@ pub struct UCommand { comm_string: String, tmpd: Option>, has_run: bool, - stdin: Option>, ignore_stdin_write_error: bool, + stdin: Option, + stdout: Option, + stderr: Option, + bytes_into_stdin: Option>, } impl UCommand { @@ -726,8 +729,11 @@ impl UCommand { cmd }, comm_string: String::from(arg.as_ref().to_str().unwrap()), - stdin: None, ignore_stdin_write_error: false, + bytes_into_stdin: None, + stdin: None, + stdout: None, + stderr: None, } } @@ -738,6 +744,21 @@ impl UCommand { ucmd } + pub fn set_stdin>(&mut self, stdin: T) -> &mut UCommand { + self.stdin = Some(stdin.into()); + self + } + + pub fn set_stdout>(&mut self, stdout: T) -> &mut UCommand { + self.stdout = Some(stdout.into()); + self + } + + pub fn set_stderr>(&mut self, stderr: T) -> &mut UCommand { + self.stderr = Some(stderr.into()); + self + } + /// Add a parameter to the invocation. Path arguments are treated relative /// to the test environment directory. pub fn arg>(&mut self, arg: S) -> &mut UCommand { @@ -767,10 +788,10 @@ impl UCommand { /// provides stdinput to feed in to the command when spawned pub fn pipe_in>>(&mut self, input: T) -> &mut UCommand { - if self.stdin.is_some() { + if self.bytes_into_stdin.is_some() { panic!("{}", MULTIPLE_STDIN_MEANINGLESS); } - self.stdin = Some(input.into()); + self.bytes_into_stdin = Some(input.into()); self } @@ -784,7 +805,7 @@ impl UCommand { /// This is typically useful to test non-standard workflows /// like feeding something to a command that does not read it pub fn ignore_stdin_write_error(&mut self) -> &mut UCommand { - if self.stdin.is_none() { + if self.bytes_into_stdin.is_none() { panic!("{}", NO_STDIN_MEANINGLESS); } self.ignore_stdin_write_error = true; @@ -813,13 +834,13 @@ impl UCommand { log_info("run", &self.comm_string); let mut child = self .raw - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) + .stdin(self.stdin.take().unwrap_or_else(|| Stdio::piped())) + .stdout(self.stdout.take().unwrap_or_else(|| Stdio::piped())) + .stderr(self.stderr.take().unwrap_or_else(|| Stdio::piped())) .spawn() .unwrap(); - if let Some(ref input) = self.stdin { + if let Some(ref input) = self.bytes_into_stdin { let write_result = child .stdin .take() From b68ecf1269d9dcf11e5ed526d042bdb18a9722b1 Mon Sep 17 00:00:00 2001 From: James Robson Date: Fri, 23 Apr 2021 16:36:46 +0100 Subject: [PATCH 16/16] Allow space in truncate --size --- src/uu/truncate/src/truncate.rs | 15 ++++++++------- tests/by-util/test_truncate.rs | 11 +++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 9cd5865b7..2c232a3d1 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -192,7 +192,8 @@ fn truncate( } fn parse_size(size: &str) -> (u64, TruncateMode) { - let mode = match size.chars().next().unwrap() { + let clean_size = size.replace(" ", ""); + let mode = match clean_size.chars().next().unwrap() { '+' => TruncateMode::Extend, '-' => TruncateMode::Reduce, '<' => TruncateMode::AtMost, @@ -203,9 +204,9 @@ fn parse_size(size: &str) -> (u64, TruncateMode) { }; let bytes = { let mut slice = if mode == TruncateMode::Reference { - size + &clean_size } else { - &size[1..] + &clean_size[1..] }; if slice.chars().last().unwrap().is_alphabetic() { slice = &slice[..slice.len() - 1]; @@ -220,11 +221,11 @@ fn parse_size(size: &str) -> (u64, TruncateMode) { Ok(num) => num, Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e), }; - if size.chars().last().unwrap().is_alphabetic() { - number *= match size.chars().last().unwrap().to_ascii_uppercase() { - 'B' => match size + if clean_size.chars().last().unwrap().is_alphabetic() { + number *= match clean_size.chars().last().unwrap().to_ascii_uppercase() { + 'B' => match clean_size .chars() - .nth(size.len() - 2) + .nth(clean_size.len() - 2) .unwrap() .to_ascii_uppercase() { diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index ce7964d57..2a1f4429b 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -53,6 +53,16 @@ fn test_decrease_file_size() { assert!(file.seek(SeekFrom::Current(0)).unwrap() == 6); } +#[test] +fn test_space_in_size() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut file = at.make_file(TFILE2); + file.write_all(b"1234567890").unwrap(); + ucmd.args(&["--size", " 4", TFILE2]).succeeds(); + file.seek(SeekFrom::End(0)).unwrap(); + assert!(file.seek(SeekFrom::Current(0)).unwrap() == 4); +} + #[test] fn test_failed() { new_ucmd!().fails(); @@ -69,3 +79,4 @@ fn test_failed_incorrect_arg() { let (_at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-s", "+5A", TFILE1]).fails(); } +