From b1fcb621a88e85830c074a9ac5c52def1ef73244 Mon Sep 17 00:00:00 2001 From: ReggaeMuffin <644950+reggaemuffin@users.noreply.github.com> Date: Tue, 6 Apr 2021 09:44:57 +0100 Subject: [PATCH 001/399] wsl2: wsl no longer differs in output refactor `is_wsl` to `is_wsl_1` and `is_wsl_2` On my tests with wsl2 ubuntu2004 all tests pass without special cases I moved wsl detection into uucore so that it can be shared instead of duplicated I moved `parse_mode` into uucode as it seemed to fit there better and anyway requires libc feature --- src/uu/mknod/src/mknod.rs | 4 +- src/uu/mknod/src/parsemode.rs | 58 ----------------------------- src/uucore/src/lib/features/mode.rs | 40 ++++++++++++++++++++ src/uucore/src/lib/lib.rs | 1 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/os.rs | 30 +++++++++++++++ tests/by-util/test_chgrp.rs | 3 +- tests/by-util/test_date.rs | 3 +- tests/by-util/test_du.rs | 9 +++-- tests/by-util/test_logname.rs | 3 +- tests/common/util.rs | 16 -------- 11 files changed, 84 insertions(+), 84 deletions(-) delete mode 100644 src/uu/mknod/src/parsemode.rs create mode 100644 src/uucore/src/lib/mods/os.rs diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 1343501bb..98404f89f 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -7,8 +7,6 @@ // spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO -mod parsemode; - #[macro_use] extern crate uucore; @@ -98,7 +96,7 @@ for details about the options it supports.", let mut last_umask: mode_t = 0; let mut newmode: mode_t = MODE_RW_UGO; if matches.opt_present("mode") { - match parsemode::parse_mode(matches.opt_str("mode")) { + match uucore::mode::parse_mode(matches.opt_str("mode")) { Ok(parsed) => { if parsed > 0o777 { show_info!("mode must specify only file permission bits"); diff --git a/src/uu/mknod/src/parsemode.rs b/src/uu/mknod/src/parsemode.rs deleted file mode 100644 index 8f8f9af61..000000000 --- a/src/uu/mknod/src/parsemode.rs +++ /dev/null @@ -1,58 +0,0 @@ -// spell-checker:ignore (ToDO) fperm - -use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; - -use uucore::mode; - -pub fn parse_mode(mode: Option) -> Result { - let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - if let Some(mode) = mode { - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { - mode::parse_numeric(fperm as u32, mode.as_str()) - } else { - mode::parse_symbolic(fperm as u32, mode.as_str(), true) - }; - result.map(|mode| mode as mode_t) - } else { - Ok(fperm) - } -} - -#[cfg(test)] -mod test { - /// Test if the program is running under WSL - // ref: @@ - // ToDO: test on WSL2 which likely doesn't need special handling; plan change to `is_wsl_1()` if WSL2 is less needy - pub fn is_wsl() -> bool { - #[cfg(target_os = "linux")] - { - if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") { - if let Ok(s) = std::str::from_utf8(&b) { - let a = s.to_ascii_lowercase(); - return a.contains("microsoft") || a.contains("wsl"); - } - } - } - false - } - - #[test] - fn symbolic_modes() { - assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); - assert_eq!( - super::parse_mode(Some("+x".to_owned())).unwrap(), - if !is_wsl() { 0o777 } else { 0o776 } - ); - assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); - assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); - } - - #[test] - fn numeric_modes() { - assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644); - assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766); - assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662); - assert_eq!(super::parse_mode(None).unwrap(), 0o666); - } -} diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 8b5e71799..1bb79ac03 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -7,6 +7,8 @@ // spell-checker:ignore (vars) fperm srwx +use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; + pub fn parse_numeric(fperm: u32, mut mode: &str) -> Result { let (op, pos) = parse_op(mode, Some('='))?; mode = mode[pos..].trim().trim_start_matches('0'); @@ -129,3 +131,41 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) { } (srwx, pos) } + +pub fn parse_mode(mode: Option) -> Result { + let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + if let Some(mode) = mode { + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + parse_numeric(fperm as u32, mode.as_str()) + } else { + parse_symbolic(fperm as u32, mode.as_str(), true) + }; + result.map(|mode| mode as mode_t) + } else { + Ok(fperm) + } +} + +#[cfg(test)] +mod test { + + #[test] + fn symbolic_modes() { + assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); + assert_eq!( + super::parse_mode(Some("+x".to_owned())).unwrap(), + if !crate::os::is_wsl_1() { 0o777 } else { 0o776 } + ); + assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); + assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); + } + + #[test] + fn numeric_modes() { + assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644); + assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766); + assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662); + assert_eq!(super::parse_mode(None).unwrap(), 0o666); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 324095b6a..208e9536c 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -26,6 +26,7 @@ mod mods; // core cross-platform modules // * cross-platform modules pub use crate::mods::coreopts; +pub use crate::mods::os; pub use crate::mods::panic; pub use crate::mods::ranges; diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index c73909dcc..74725e141 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -1,5 +1,6 @@ // mods ~ cross-platforms modules (core/bundler file) pub mod coreopts; +pub mod os; pub mod panic; pub mod ranges; diff --git a/src/uucore/src/lib/mods/os.rs b/src/uucore/src/lib/mods/os.rs new file mode 100644 index 000000000..da2002161 --- /dev/null +++ b/src/uucore/src/lib/mods/os.rs @@ -0,0 +1,30 @@ +/// Test if the program is running under WSL +// ref: @@ +pub fn is_wsl_1() -> bool { + #[cfg(target_os = "linux")] + { + if is_wsl_2() { + return false; + } + if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") { + if let Ok(s) = std::str::from_utf8(&b) { + let a = s.to_ascii_lowercase(); + return a.contains("microsoft") || a.contains("wsl"); + } + } + } + false +} + +pub fn is_wsl_2() -> bool { + #[cfg(target_os = "linux")] + { + if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") { + if let Ok(s) = std::str::from_utf8(&b) { + let a = s.to_ascii_lowercase(); + return a.contains("wsl2"); + } + } + } + false +} diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 613f52fd2..ffb078137 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -1,5 +1,6 @@ use crate::common::util::*; use rust_users::*; +use uucore; #[test] fn test_invalid_option() { @@ -104,7 +105,7 @@ fn test_reference() { // skip for root or MS-WSL // * MS-WSL is bugged (as of 2019-12-25), allowing non-root accounts su-level privileges for `chgrp` // * for MS-WSL, succeeds and stdout == 'group of /etc retained as root' - if !(get_effective_gid() == 0 || is_wsl()) { + if !(get_effective_gid() == 0 || uucore::os::is_wsl_1()) { new_ucmd!() .arg("-v") .arg("--reference=/etc/passwd") diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 5619aed94..b4e49e775 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -4,6 +4,7 @@ use self::regex::Regex; use crate::common::util::*; #[cfg(all(unix, not(target_os = "macos")))] use rust_users::*; +use uucore; #[test] fn test_date_email() { @@ -159,7 +160,7 @@ fn test_date_set_invalid() { #[test] #[cfg(all(unix, not(target_os = "macos")))] fn test_date_set_permissions_error() { - if !(get_effective_uid() == 0 || is_wsl()) { + if !(get_effective_uid() == 0 || uucore::os::is_wsl_1()) { 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(); diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index b3b1b3465..a0d698de0 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1,4 +1,5 @@ use crate::common::util::*; +use uucore; const SUB_DIR: &str = "subdir/deeper"; const SUB_DIR_LINKS: &str = "subdir/links"; @@ -52,7 +53,7 @@ fn _du_basics_subdir(s: String) { #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_basics_subdir(s: String) { // MS-WSL linux has altered expected output - if !is_wsl() { + if !uucore::os::is_wsl_1() { assert_eq!(s, "8\tsubdir/deeper\n"); } else { assert_eq!(s, "0\tsubdir/deeper\n"); @@ -96,7 +97,7 @@ fn _du_soft_link(s: String) { #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_soft_link(s: String) { // MS-WSL linux has altered expected output - if !is_wsl() { + if !uucore::os::is_wsl_1() { assert_eq!(s, "16\tsubdir/links\n"); } else { assert_eq!(s, "8\tsubdir/links\n"); @@ -128,7 +129,7 @@ fn _du_hard_link(s: String) { #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_hard_link(s: String) { // MS-WSL linux has altered expected output - if !is_wsl() { + if !uucore::os::is_wsl_1() { assert_eq!(s, "16\tsubdir/links\n"); } else { assert_eq!(s, "8\tsubdir/links\n"); @@ -156,7 +157,7 @@ fn _du_d_flag(s: String) { #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] fn _du_d_flag(s: String) { // MS-WSL linux has altered expected output - if !is_wsl() { + if !uucore::os::is_wsl_1() { assert_eq!(s, "28\t./subdir\n36\t./\n"); } else { assert_eq!(s, "8\t./subdir\n8\t./\n"); diff --git a/tests/by-util/test_logname.rs b/tests/by-util/test_logname.rs index b15941c06..baaad63cc 100644 --- a/tests/by-util/test_logname.rs +++ b/tests/by-util/test_logname.rs @@ -1,5 +1,6 @@ use crate::common::util::*; use std::env; +use uucore; #[test] fn test_normal() { @@ -13,7 +14,7 @@ fn test_normal() { for (key, value) in env::vars() { println!("{}: {}", key, value); } - if (is_ci() || is_wsl()) && result.stderr.contains("error: no login name") { + if (is_ci() || uucore::os::is_wsl_1()) && result.stderr.contains("error: no login name") { // ToDO: investigate WSL failure // In the CI, some server are failing to return logname. // As seems to be a configuration issue, ignoring it diff --git a/tests/common/util.rs b/tests/common/util.rs index 708b8dbba..38f5a90cc 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -40,22 +40,6 @@ pub fn is_ci() -> bool { .eq_ignore_ascii_case("true") } -/// Test if the program is running under WSL -// ref: @@ -// ToDO: test on WSL2 which likely doesn't need special handling; plan change to `is_wsl_1()` if WSL2 is less needy -pub fn is_wsl() -> bool { - #[cfg(target_os = "linux")] - { - if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") { - if let Ok(s) = std::str::from_utf8(&b) { - let a = s.to_ascii_lowercase(); - return a.contains("microsoft") || a.contains("wsl"); - } - } - } - false -} - /// Read a test scenario fixture, returning its bytes fn read_scenario_fixture>(tmpd: &Option>, file_rel_path: S) -> Vec { let tmpdir_path = tmpd.as_ref().unwrap().as_ref().path(); From d51ca4098678b4109c5c608da3ee65ba81682543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Fri, 9 Apr 2021 11:08:31 +0200 Subject: [PATCH 002/399] allow ignoring stdin write errors in tests * if we want to test an irregular scenario, ignoring errors caused by writing to stdin of the command can be uselful. * for example, when writing some text to stdin of cksum in a scenario where it doesn't consume this input, the child process might have exited before the text was written. therefore, this test sometimes fails with a 'Broken pipe'. --- tests/by-util/test_cksum.rs | 9 +++++++-- tests/common/util.rs | 29 ++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 8b41c782c..1a0915cd5 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -35,14 +35,19 @@ fn test_empty() { } #[test] -#[ignore] fn test_arg_overrides_stdin() { let (at, mut ucmd) = at_and_ucmd!(); let input = "foobarfoobar"; at.touch("a"); - let result = ucmd.arg("a").pipe_in(input.as_bytes()).run(); + let result = ucmd + .arg("a") + .pipe_in(input.as_bytes()) + // the command might have exited before all bytes have been pipe in. + // in that case, we don't care about the error (broken pipe) + .ignore_stdin_write_error() + .run(); println!("{}, {}", result.stdout, result.stderr); diff --git a/tests/common/util.rs b/tests/common/util.rs index 8a09b71c1..d0f8083fd 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -33,6 +33,8 @@ static ALREADY_RUN: &str = " you have already run this UCommand, if you want to testing();"; static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly."; +static NO_STDIN_MEANINGLESS: &str = "Setting this flag has no effect if there is no stdin"; + /// Test if the program is running under CI pub fn is_ci() -> bool { std::env::var("CI") @@ -624,6 +626,7 @@ pub struct UCommand { tmpd: Option>, has_run: bool, stdin: Option>, + ignore_stdin_write_error: bool, } impl UCommand { @@ -653,6 +656,7 @@ impl UCommand { }, comm_string: String::from(arg.as_ref().to_str().unwrap()), stdin: None, + ignore_stdin_write_error: false, } } @@ -705,6 +709,17 @@ impl UCommand { self.pipe_in(contents) } + /// Ignores error caused by feeding stdin to the command. + /// 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() { + panic!("{}", NO_STDIN_MEANINGLESS); + } + self.ignore_stdin_write_error = true; + self + } + pub fn env(&mut self, key: K, val: V) -> &mut UCommand where K: AsRef, @@ -725,7 +740,7 @@ impl UCommand { } self.has_run = true; log_info("run", &self.comm_string); - let mut result = self + let mut child = self .raw .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -734,15 +749,19 @@ impl UCommand { .unwrap(); if let Some(ref input) = self.stdin { - result + let write_result = child .stdin .take() .unwrap_or_else(|| panic!("Could not take child process stdin")) - .write_all(input) - .unwrap_or_else(|e| panic!("{}", e)); + .write_all(input); + if !self.ignore_stdin_write_error { + if let Err(e) = write_result { + panic!("failed to write to stdin of child: {}", e) + } + } } - result + child } /// Spawns the command, feeds the stdin if any, waits for the result From 1253323027e0b0f1d6adf204d5591f2fda1da9e4 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Fri, 9 Apr 2021 14:28:46 -0500 Subject: [PATCH 003/399] Various fixes and performance improvements --- src/uu/sort/src/sort.rs | 114 ++++++++++++------ tests/.DS_Store | Bin 0 -> 6148 bytes tests/by-util/test_sort.rs | 49 ++++++-- tests/fixtures/.DS_Store | Bin 0 -> 6148 bytes ...ars_numeric_unique_reverse_stable.expected | 20 +++ .../fixtures/sort/multiple_decimals.expected | 33 +++++ .../sort/multiple_decimals_general.txt | 35 ++++++ .../sort/multiple_decimals_numeric.txt | 35 ++++++ 8 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 tests/.DS_Store create mode 100644 tests/fixtures/.DS_Store create mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected create mode 100644 tests/fixtures/sort/multiple_decimals.expected create mode 100644 tests/fixtures/sort/multiple_decimals_general.txt create mode 100644 tests/fixtures/sort/multiple_decimals_numeric.txt diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4e0e25d65..211f87672 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -22,6 +22,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; +use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; @@ -262,7 +263,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_CHECK_SILENT) .short("C") .long(OPT_CHECK_SILENT) - .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise. "), + .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), ) .arg( Arg::with_name(OPT_IGNORE_CASE) @@ -353,7 +354,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if let Ok(n) = line { files.push( std::str::from_utf8(&n) - .expect("Could not parse zero terminated string from input.") + .expect("Could not parse string from zero terminated input.") .to_string(), ); } @@ -488,6 +489,8 @@ fn exec(files: Vec, settings: &mut Settings) -> i32 { } else { print_sorted(file_merger, &settings) } + } else if settings.mode == SortMode::Default && settings.unique { + print_sorted(lines.iter().dedup(), &settings) } else if settings.mode == SortMode::Month && settings.unique { print_sorted( lines @@ -499,7 +502,7 @@ fn exec(files: Vec, settings: &mut Settings) -> i32 { print_sorted( lines .iter() - .dedup_by(|a, b| get_nums_dedup(a) == get_nums_dedup(b)), + .dedup_by(|a, b| get_num_dedup(a, &settings) == get_num_dedup(b, &settings)), &settings, ) } else { @@ -603,12 +606,13 @@ fn default_compare(a: &str, b: &str) -> Ordering { #[inline(always)] fn leading_num_common(a: &str) -> &str { let mut s = ""; + + // check whether char is numeric, whitespace or decimal point or thousand seperator for (idx, c) in a.char_indices() { - // check whether char is numeric, whitespace or decimal point or thousand seperator if !c.is_numeric() && !c.is_whitespace() - && !c.eq(&DECIMAL_PT) && !c.eq(&THOUSANDS_SEP) + && !c.eq(&DECIMAL_PT) // check for e notation && !c.eq(&'e') && !c.eq(&'E') @@ -621,7 +625,7 @@ fn leading_num_common(a: &str) -> &str { break; } // If line is not a number line, return the line as is - s = a; + s = &a; } s } @@ -633,16 +637,17 @@ fn leading_num_common(a: &str) -> &str { // not recognize a positive sign or scientific/E notation so we strip those elements here. fn get_leading_num(a: &str) -> &str { let mut s = ""; - let b = leading_num_common(a); - // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip - for (idx, c) in b.char_indices() { - if c.eq(&'e') || c.eq(&'E') || b.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) { - s = &b[..idx]; + let a = leading_num_common(a); + + // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip trailing chars + for (idx, c) in a.char_indices() { + if c.eq(&'e') || c.eq(&'E') || a.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) { + s = &a[..idx]; break; } // If no further processing needed to be done, return the line as-is to be sorted - s = b; + s = &a; } // And empty number or non-number lines are to be treated as ‘0’ but only for numeric sort @@ -657,30 +662,32 @@ fn get_leading_num(a: &str) -> &str { // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. // For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000. -fn get_leading_gen(a: &str) -> String { +fn get_leading_gen(a: &str) -> &str { // Make this iter peekable to see if next char is numeric - let mut p_iter = leading_num_common(a).chars().peekable(); - let mut r = String::new(); + let raw_leading_num = leading_num_common(a); + let mut p_iter = raw_leading_num.chars().peekable(); + let mut result = ""; // Cleanup raw stripped strings for c in p_iter.to_owned() { let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric(); - // Only general numeric recognizes e notation and, see block below, the '+' sign - if (c.eq(&'e') && !next_char_numeric) || (c.eq(&'E') && !next_char_numeric) { - r = a.split(c).next().unwrap_or("").to_owned(); + // Only general numeric recognizes e notation and the '+' sign + if (c.eq(&'e') && !next_char_numeric) + || (c.eq(&'E') && !next_char_numeric) + // Only GNU (non-general) numeric recognize thousands seperators, takes only leading # + || c.eq(&THOUSANDS_SEP) + { + result = a.split(c).next().unwrap_or(""); break; // If positive sign and next char is not numeric, split at postive sign at keep trailing numbers // There is a more elegant way to do this in Rust 1.45, std::str::strip_prefix } else if c.eq(&POSITIVE) && !next_char_numeric { - let mut v: Vec<&str> = a.split(c).collect(); - let x = v.split_off(1); - r = x.join(""); + result = a.trim().trim_start_matches('+'); break; - // If no further processing needed to be done, return the line as-is to be sorted - } else { - r = a.to_owned(); } + // If no further processing needed to be done, return the line as-is to be sorted + result = a; } - r + result } fn get_months_dedup(a: &str) -> String { @@ -714,10 +721,10 @@ fn get_months_dedup(a: &str) -> String { } } -// *For all dedups/uniques we must compare leading numbers* +// *For all dedups/uniques expect default we must compare leading numbers* // Also note numeric compare and unique output is specifically *not* the same as a "sort | uniq" // See: https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html -fn get_nums_dedup(a: &str) -> &str { +fn get_num_dedup<'a>(a: &'a str, settings: &&mut Settings) -> &'a str { // Trim and remove any leading zeros let s = a.trim().trim_start_matches('0'); @@ -731,20 +738,50 @@ fn get_nums_dedup(a: &str) -> &str { "" // Prepare lines for comparison of only the numerical leading numbers } else { - get_leading_num(s) + let result = match settings.mode { + SortMode::Numeric => get_leading_num(s), + SortMode::GeneralNumeric => get_leading_gen(s), + SortMode::HumanNumeric => get_leading_num(s), + SortMode::Version => get_leading_num(s), + _ => s, + }; + result + } +} + +#[inline(always)] +fn remove_thousands_sep<'a, S: Into>>(input: S) -> Cow<'a, str> { + let input = input.into(); + if input.contains(THOUSANDS_SEP) { + let output = input.replace(THOUSANDS_SEP, ""); + Cow::Owned(output) + } else { + input + } +} + +#[inline(always)] +fn remove_trailing_dec<'a, S: Into>>(input: S) -> Cow<'a, str> { + let input = input.into(); + if let Some(s) = input.find(DECIMAL_PT) { + let (leading, trailing) = input.split_at(s); + let output = [leading, ".", trailing.replace(DECIMAL_PT, "").as_str()].concat(); + Cow::Owned(output) + } else { + input } } /// Parse the beginning string into an f64, returning -inf instead of NaN on errors. #[inline(always)] fn permissive_f64_parse(a: &str) -> f64 { - // Remove thousands seperators - let a = a.replace(THOUSANDS_SEP, ""); - // GNU sort treats "NaN" as non-number in numeric, so it needs special care. // *Keep this trim before parse* despite what POSIX may say about -b and -n // because GNU and BSD both seem to require it to match their behavior - match a.trim().parse::() { + // + // Remove any trailing decimals, ie 4568..890... becomes 4568.890 + // Then, we trim whitespace and parse + match remove_trailing_dec(a).trim().parse::() { Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, Ok(a) => a, Err(_) => std::f64::NEG_INFINITY, @@ -757,8 +794,13 @@ fn numeric_compare(a: &str, b: &str) -> Ordering { let sa = get_leading_num(a); let sb = get_leading_num(b); - let fa = permissive_f64_parse(sa); - let fb = permissive_f64_parse(sb); + // Avoids a string alloc for every line to remove thousands seperators here + // instead of inside the get_leading_num function, which is a HUGE performance benefit + let ta = remove_thousands_sep(sa); + let tb = remove_thousands_sep(sb); + + let fa = permissive_f64_parse(&ta); + let fb = permissive_f64_parse(&tb); // f64::cmp isn't implemented (due to NaN issues); implement directly instead if fa > fb { @@ -799,8 +841,8 @@ fn general_numeric_compare(a: &str, b: &str) -> Ordering { // these types of numbers, we rarely care about pure performance. fn human_numeric_convert(a: &str) -> f64 { let num_str = get_leading_num(a); - let suffix = a.trim_start_matches(num_str); - let num_part = permissive_f64_parse(num_str); + let suffix = a.trim_start_matches(&num_str); + let num_part = permissive_f64_parse(&num_str); let suffix: f64 = match suffix.parse().unwrap_or('\0') { // SI Units 'K' => 1E3, diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8z45|!R0Z1N%F(jFgL>QrFAPJ2!M?+vV1V%$(Gz3ON zU^D~25V%SxcdJP zRior+2#kinunYl47MEZbCs3t{!+W4QHvuXKVuPw;Mo^s$(F3lEVT}ML$bg~*R5_@+ b2Uo?6kTwK}57Iu`5P${HC_Nei0}uiLNUI8I literal 0 HcmV?d00001 diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 43aaf1da1..6455d837b 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1,5 +1,31 @@ use crate::common::util::*; +fn test_helper(file_name: &str, args: &str) { + new_ucmd!() + .arg(args) + .arg(format!("{}.txt", file_name)) + .succeeds() + .stdout_is_fixture(format!("{}.expected", file_name)); +} + +#[test] +fn test_multiple_decimals_general() { + new_ucmd!() + .arg("-g") + .arg("multiple_decimals_general.txt") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); +} + +#[test] +fn test_multiple_decimals_numeric() { + new_ucmd!() + .arg("-n") + .arg("multiple_decimals_numeric.txt") + .succeeds() + .stdout_is("-2028789030\n-896689\n-8.90880\n-1\n-.05\n\n\n\n\n\n\n\n\n000\nCARAvan\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n576,446.88800000\n576,446.890\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); +} + #[test] fn test_check_zero_terminated_failure() { new_ucmd!() @@ -44,6 +70,21 @@ fn test_random_shuffle_contains_all_lines() { assert_eq!(result_sorted, expected); } +#[test] +fn test_random_shuffle_two_runs_not_the_same() { + // check to verify that two random shuffles are not equal; this has the + // potential to fail in the very unlikely event that the random order is the same + // as the starting order, or if both random sorts end up having the same order. + const FILE: &'static str = "default_unsorted_ints.expected"; + let (at, _ucmd) = at_and_ucmd!(); + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let expected = at.read(FILE); + let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout; + + assert_ne!(result, expected); + assert_ne!(result, unexpected); +} + #[test] fn test_random_shuffle_contains_two_runs_not_the_same() { // check to verify that two random shuffles are not equal; this has the @@ -355,11 +396,3 @@ fn test_check_silent() { .fails() .stdout_is(""); } - -fn test_helper(file_name: &str, args: &str) { - new_ucmd!() - .arg(args) - .arg(format!("{}{}", file_name, ".txt")) - .succeeds() - .stdout_is_fixture(format!("{}{}", file_name, ".expected")); -} diff --git a/tests/fixtures/.DS_Store b/tests/fixtures/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..607a7386a75b94e7df52bcee971a48217dc92331 GIT binary patch literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8zOq>i@0Z1N%F(jFwA|RR(WJXeXaY0f}ei8!%!%3*z zC^fi402FsD48;uj3`Gnj$nlp{kds+lVqkEck%^gwm5rSP1b8`OgER8WgG&-iN{gKm zi=siifW(rFBq%#1KR*Y~PD~2ROf8QW5OL1WD@n}EODzH^56(kXDc!NGpg2X=Pvp zvB2_RtqhC|5Uq^hZU_SdBe+WfqQTl37#YCY85kMB+8JQ&JVuCi21bZ>21aNPg%Q-F z0htfc&cF!K4s+fpJsJX|Api{lW(X|+s{dUX7;yFfA*x2n(GVC7fngZ}j4Up}E>56I z6NmRebuFkqO@PXSYJX65%m}Kd5n|w~mEQChs K(GZ}22mk;)qfplX literal 0 HcmV?d00001 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected new file mode 100644 index 000000000..bbce16934 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected @@ -0,0 +1,20 @@ +4798908.8909800 +4798908.45 +4798908.340000000000 +576,446.890 +576,446.88800000 + 37800 + 4567. +46.89 +45 +8.013 +1.58590 +1.444 +1.040000000 +1 + +-.05 +-1 +-8.90880 +-896689 +-2028789030 diff --git a/tests/fixtures/sort/multiple_decimals.expected b/tests/fixtures/sort/multiple_decimals.expected new file mode 100644 index 000000000..6afbdcaa0 --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals.expected @@ -0,0 +1,33 @@ +-2028789030 +-896689 +-8.90880 +-1 +-.05 + + + + + + + + +000 +CARAvan +00000001 +1 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 + 4567..457 + 4567. +4567.1 +4567.34 + 37800 +576,446.88800000 +576,446.890 +4798908.340000000000 +4798908.45 +4798908.8909800 diff --git a/tests/fixtures/sort/multiple_decimals_general.txt b/tests/fixtures/sort/multiple_decimals_general.txt new file mode 100644 index 000000000..4e65ecfda --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_general.txt @@ -0,0 +1,35 @@ +576,446.890 +576,446.88800000 + +4567.1 + 4567..457 + 45670.89079.1 + 45670.89079.098 +4567.34 + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 + diff --git a/tests/fixtures/sort/multiple_decimals_numeric.txt b/tests/fixtures/sort/multiple_decimals_numeric.txt new file mode 100644 index 000000000..4e65ecfda --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_numeric.txt @@ -0,0 +1,35 @@ +576,446.890 +576,446.88800000 + +4567.1 + 4567..457 + 45670.89079.1 + 45670.89079.098 +4567.34 + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 + From a9209049bf586ce40008d6a1151d2f21adef3c10 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 9 Apr 2021 22:18:52 +0200 Subject: [PATCH 004/399] fix a typo Co-authored-by: Michael Debertol --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 211f87672..4aa3fbed2 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -607,7 +607,7 @@ fn default_compare(a: &str, b: &str) -> Ordering { fn leading_num_common(a: &str) -> &str { let mut s = ""; - // check whether char is numeric, whitespace or decimal point or thousand seperator + // check whether char is numeric, whitespace or decimal point or thousand separator for (idx, c) in a.char_indices() { if !c.is_numeric() && !c.is_whitespace() From c54b5f2d8220317323f5ab2d6b3643380629f758 Mon Sep 17 00:00:00 2001 From: joppich Date: Thu, 1 Apr 2021 09:44:24 +0200 Subject: [PATCH 005/399] stdbuf: move from getopts to clap --- Cargo.lock | 2 +- src/uu/stdbuf/Cargo.toml | 2 +- src/uu/stdbuf/src/stdbuf.rs | 253 +++++++++++++++-------------------- tests/by-util/test_stdbuf.rs | 74 ++++++++-- 4 files changed, 175 insertions(+), 156 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e094f1f48..9dfeba336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2316,7 +2316,7 @@ dependencies = [ name = "uu_stdbuf" version = "0.0.6" dependencies = [ - "getopts", + "clap", "tempfile", "uu_stdbuf_libstdbuf", "uucore", diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 22ce4de6a..884a98785 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/stdbuf.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" tempfile = "3.1" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 67ed9a838..a6c9f9dc5 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -10,7 +10,8 @@ #[macro_use] extern crate uucore; -use getopts::{Matches, Options}; +use clap::{App, AppSettings, Arg, ArgMatches}; +use std::convert::TryFrom; use std::fs::File; use std::io::{self, Write}; use std::os::unix::process::ExitStatusExt; @@ -19,8 +20,35 @@ use std::process::Command; use tempfile::tempdir; use tempfile::TempDir; -static NAME: &str = "stdbuf"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = + "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ + Mandatory arguments to long options are mandatory for short options too."; +static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ + This option is invalid with standard input.\n\n\ + If MODE is '0' the corresponding stream will be unbuffered.\n\n\ + Otherwise MODE is a number which may be followed by one of the following:\n\n\ + KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ + In this case the corresponding stream will be fully buffered with the buffer size set to \ + MODE bytes.\n\n\ + NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ + that will override corresponding settings changed by 'stdbuf'.\n\ + Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ + and are thus unaffected by 'stdbuf' settings.\n"; + +mod options { + pub const INPUT: &str = "input"; + pub const INPUT_SHORT: &str = "i"; + pub const OUTPUT: &str = "output"; + pub const OUTPUT_SHORT: &str = "o"; + pub const ERROR: &str = "error"; + pub const ERROR_SHORT: &str = "e"; + pub const COMMAND: &str = "command"; +} + +fn get_usage() -> String { + format!("{0} OPTION... COMMAND", executable!()) +} const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); @@ -36,16 +64,19 @@ struct ProgramOptions { stderr: BufferType, } -enum ErrMsg { - Retry, - Fatal, +impl<'a> TryFrom<&ArgMatches<'a>> for ProgramOptions { + type Error = ProgramOptionsError; + + fn try_from(matches: &ArgMatches) -> Result { + Ok(ProgramOptions { + stdin: check_option(&matches, options::INPUT)?, + stdout: check_option(&matches, options::OUTPUT)?, + stderr: check_option(&matches, options::ERROR)?, + }) + } } -enum OkMsg { - Buffering, - Help, - Version, -} +struct ProgramOptionsError(String); #[cfg(any( target_os = "linux", @@ -73,31 +104,6 @@ fn preload_strings() -> (&'static str, &'static str) { crash!(1, "Command not supported for this operating system!") } -fn print_version() { - println!("{} {}", NAME, VERSION); -} - -fn print_usage(opts: &Options) { - let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \ - Mandatory arguments to long options are mandatory for short options too."; - let explanation = "If MODE is 'L' the corresponding stream will be line buffered.\n \ - This option is invalid with standard input.\n\n \ - If MODE is '0' the corresponding stream will be unbuffered.\n\n \ - Otherwise MODE is a number which may be followed by one of the following:\n\n \ - KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n \ - In this case the corresponding stream will be fully buffered with the buffer size set to \ - MODE bytes.\n\n \ - NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ - that will override corresponding settings changed by 'stdbuf'.\n \ - Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ - and are thus unaffected by 'stdbuf' settings.\n"; - println!("{} {}", NAME, VERSION); - println!(); - println!("Usage: stdbuf OPTION... COMMAND"); - println!(); - println!("{}\n{}", opts.usage(brief), explanation); -} - fn parse_size(size: &str) -> Option { let ext = size.trim_start_matches(|c: char| c.is_digit(10)); let num = size.trim_end_matches(char::is_alphabetic); @@ -133,65 +139,30 @@ fn parse_size(size: &str) -> Option { Some(buf_size * base.pow(power)) } -fn check_option(matches: &Matches, name: &str, modified: &mut bool) -> Option { - match matches.opt_str(name) { - Some(value) => { - *modified = true; - match &value[..] { - "L" => { - if name == "input" { - show_info!("line buffering stdin is meaningless"); - None - } else { - Some(BufferType::Line) - } - } - x => { - let size = match parse_size(x) { - Some(m) => m, - None => { - show_error!("Invalid mode {}", x); - return None; - } - }; - Some(BufferType::Size(size)) +fn check_option(matches: &ArgMatches, name: &str) -> Result { + match matches.value_of(name) { + Some(value) => match &value[..] { + "L" => { + if name == options::INPUT { + Err(ProgramOptionsError(format!( + "line buffering stdin is meaningless" + ))) + } else { + Ok(BufferType::Line) } } - } - None => Some(BufferType::Default), + x => { + let size = match parse_size(x) { + Some(m) => m, + None => return Err(ProgramOptionsError(format!("invalid mode {}", x))), + }; + Ok(BufferType::Size(size)) + } + }, + None => Ok(BufferType::Default), } } -fn parse_options( - args: &[String], - options: &mut ProgramOptions, - optgrps: &Options, -) -> Result { - let matches = match optgrps.parse(args) { - Ok(m) => m, - Err(_) => return Err(ErrMsg::Retry), - }; - if matches.opt_present("help") { - return Ok(OkMsg::Help); - } - if matches.opt_present("version") { - return Ok(OkMsg::Version); - } - let mut modified = false; - options.stdin = check_option(&matches, "input", &mut modified).ok_or(ErrMsg::Fatal)?; - options.stdout = check_option(&matches, "output", &mut modified).ok_or(ErrMsg::Fatal)?; - options.stderr = check_option(&matches, "error", &mut modified).ok_or(ErrMsg::Fatal)?; - - if matches.free.len() != 1 { - return Err(ErrMsg::Retry); - } - if !modified { - show_error!("you must specify a buffering mode option"); - return Err(ErrMsg::Fatal); - } - Ok(OkMsg::Buffering) -} - fn set_command_env(command: &mut Command, buffer_name: &str, buffer_type: BufferType) { match buffer_type { BufferType::Size(m) => { @@ -215,72 +186,62 @@ fn get_preload_env(tmp_dir: &mut TempDir) -> io::Result<(String, PathBuf)> { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let usage = get_usage(); - let mut opts = Options::new(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(LONG_HELP) + .setting(AppSettings::TrailingVarArg) + .arg( + Arg::with_name(options::INPUT) + .long(options::INPUT) + .short(options::INPUT_SHORT) + .help("adjust standard input stream buffering") + .value_name("MODE") + .required_unless_one(&[options::OUTPUT, options::ERROR]), + ) + .arg( + Arg::with_name(options::OUTPUT) + .long(options::OUTPUT) + .short(options::OUTPUT_SHORT) + .help("adjust standard output stream buffering") + .value_name("MODE") + .required_unless_one(&[options::INPUT, options::ERROR]), + ) + .arg( + Arg::with_name(options::ERROR) + .long(options::ERROR) + .short(options::ERROR_SHORT) + .help("adjust standard error stream buffering") + .value_name("MODE") + .required_unless_one(&[options::INPUT, options::OUTPUT]), + ) + .arg( + Arg::with_name(options::COMMAND) + .multiple(true) + .takes_value(true) + .hidden(true) + .required(true), + ) + .get_matches_from(args); - opts.optopt( - "i", - "input", - "adjust standard input stream buffering", - "MODE", - ); - opts.optopt( - "o", - "output", - "adjust standard output stream buffering", - "MODE", - ); - opts.optopt( - "e", - "error", - "adjust standard error stream buffering", - "MODE", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let options = ProgramOptions::try_from(&matches) + .unwrap_or_else(|e| crash!(125, "{}\nTry 'stdbuf --help' for more information.", e.0)); - let mut options = ProgramOptions { - stdin: BufferType::Default, - stdout: BufferType::Default, - stderr: BufferType::Default, - }; - let mut command_idx: i32 = -1; - for i in 1..=args.len() { - match parse_options(&args[1..i], &mut options, &opts) { - Ok(OkMsg::Buffering) => { - command_idx = (i as i32) - 1; - break; - } - Ok(OkMsg::Help) => { - print_usage(&opts); - return 0; - } - Ok(OkMsg::Version) => { - print_version(); - return 0; - } - Err(ErrMsg::Fatal) => break, - Err(ErrMsg::Retry) => continue, - } - } - if command_idx == -1 { - crash!( - 125, - "Invalid options\nTry 'stdbuf --help' for more information." - ); - } - let command_name = &args[command_idx as usize]; - let mut command = Command::new(command_name); + let mut command_values = matches.values_of::<&str>(options::COMMAND).unwrap(); + let mut command = Command::new(command_values.next().unwrap()); + let command_params: Vec<&str> = command_values.collect(); let mut tmp_dir = tempdir().unwrap(); let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir)); - command - .args(&args[(command_idx as usize) + 1..]) - .env(preload_env, libstdbuf); + command.env(preload_env, libstdbuf); set_command_env(&mut command, "_STDBUF_I", options.stdin); set_command_env(&mut command, "_STDBUF_O", options.stdout); set_command_env(&mut command, "_STDBUF_E", options.stderr); + command.args(command_params); + let mut process = match command.spawn() { Ok(p) => p, Err(e) => crash!(1, "failed to execute process: {}", e), diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 9adb0cfda..61fa36977 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -1,13 +1,71 @@ +#[cfg(not(target_os = "windows"))] use crate::common::util::*; +#[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_unbuffered_stdout() { - if cfg!(target_os = "linux") { - // This is a basic smoke test - new_ucmd!() - .args(&["-o0", "head"]) - .pipe_in("The quick brown fox jumps over the lazy dog.") - .run() - .stdout_is("The quick brown fox jumps over the lazy dog."); - } + // This is a basic smoke test + new_ucmd!() + .args(&["-o0", "head"]) + .pipe_in("The quick brown fox jumps over the lazy dog.") + .run() + .stdout_is("The quick brown fox jumps over the lazy dog."); +} + +#[cfg(not(target_os = "windows"))] +#[test] +fn test_stdbuf_line_buffered_stdout() { + new_ucmd!() + .args(&["-oL", "head"]) + .pipe_in("The quick brown fox jumps over the lazy dog.") + .run() + .stdout_is("The quick brown fox jumps over the lazy dog."); +} + +#[cfg(not(target_os = "windows"))] +#[test] +fn test_stdbuf_no_buffer_option_fails() { + new_ucmd!() + .args(&["head"]) + .pipe_in("The quick brown fox jumps over the lazy dog.") + .fails() + .stderr_is( + "error: The following required arguments were not provided:\n \ + --error \n \ + --input \n \ + --output \n\n\ + USAGE:\n \ + stdbuf OPTION... COMMAND\n\n\ + For more information try --help", + ); +} + +#[cfg(not(target_os = "windows"))] +#[test] +fn test_stdbuf_trailing_var_arg() { + new_ucmd!() + .args(&["-i", "1024", "tail", "-1"]) + .pipe_in("The quick brown fox\njumps over the lazy dog.") + .run() + .stdout_is("jumps over the lazy dog."); +} + +#[cfg(not(target_os = "windows"))] +#[test] +fn test_stdbuf_line_buffering_stdin_fails() { + new_ucmd!() + .args(&["-i", "L", "head"]) + .pipe_in("The quick brown fox jumps over the lazy dog.") + .fails() + .stderr_is("stdbuf: error: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information."); +} + +#[cfg(not(target_os = "windows"))] +#[test] +fn test_stdbuf_invalid_mode_fails() { + new_ucmd!() + .args(&["-i", "1024R", "head"]) + .pipe_in("The quick brown fox jumps over the lazy dog.") + .fails() + .stderr_is("stdbuf: error: invalid mode 1024R\nTry 'stdbuf --help' for more information."); } From 18191f9212bfa61b333a4ace437a02c493c1d67f Mon Sep 17 00:00:00 2001 From: Aleksandar Janicijevic Date: Sat, 10 Apr 2021 04:41:59 -0400 Subject: [PATCH 006/399] shred: Implemented --force option (#2012) --- src/uu/shred/src/shred.rs | 67 ++++++++++++++++++++++++++++++------- tests/by-util/test_shred.rs | 52 +++++++++++++++++++++++++++- tests/common/util.rs | 7 ++++ 3 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index d5f910297..7e0e77184 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -259,6 +259,7 @@ static AFTER_HELP: &str = "; pub mod options { + pub const FORCE: &str = "force"; pub const FILE: &str = "file"; pub const ITERATIONS: &str = "iterations"; pub const SIZE: &str = "size"; @@ -278,6 +279,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .after_help(AFTER_HELP) .usage(&usage[..]) + .arg( + Arg::with_name(options::FORCE) + .long(options::FORCE) + .short("f") + .help("change permissions to allow writing if necessary"), + ) .arg( Arg::with_name(options::ITERATIONS) .long(options::ITERATIONS) @@ -354,8 +361,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // TODO: implement --random-source - // TODO: implement --force - + let force = matches.is_present(options::FORCE); let remove = matches.is_present(options::REMOVE); let size_arg = match matches.value_of(options::SIZE) { Some(s) => Some(s.to_string()), @@ -375,7 +381,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } for path_str in matches.values_of(options::FILE).unwrap() { - wipe_file(&path_str, iterations, remove, size, exact, zero, verbose); + wipe_file( + &path_str, iterations, remove, size, exact, zero, verbose, force, + ); } 0 @@ -439,18 +447,40 @@ fn wipe_file( exact: bool, zero: bool, verbose: bool, + force: bool, ) { // Get these potential errors out of the way first let path: &Path = Path::new(path_str); if !path.exists() { - println!("{}: {}: No such file or directory", NAME, path.display()); + show_error!("{}: No such file or directory", path.display()); return; } if !path.is_file() { - println!("{}: {}: Not a file", NAME, path.display()); + show_error!("{}: Not a file", path.display()); return; } + // If force is true, set file permissions to not-readonly. + if force { + let metadata = match fs::metadata(path) { + Ok(m) => m, + Err(e) => { + show_error!("{}", e); + return; + } + }; + + let mut perms = metadata.permissions(); + perms.set_readonly(false); + match fs::set_permissions(path, perms) { + Err(e) => { + show_error!("{}", e); + return; + } + _ => {} + } + } + // Fill up our pass sequence let mut pass_sequence: Vec = Vec::new(); @@ -489,11 +519,13 @@ fn wipe_file( { let total_passes: usize = pass_sequence.len(); - let mut file: File = OpenOptions::new() - .write(true) - .truncate(false) - .open(path) - .expect("Failed to open file for writing"); + let mut file: File = match OpenOptions::new().write(true).truncate(false).open(path) { + Ok(f) => f, + Err(e) => { + show_error!("{}: failed to open for writing: {}", path.display(), e); + return; + } + }; // NOTE: it does not really matter what we set for total_bytes and gen_type here, so just // use bogus values @@ -523,14 +555,23 @@ fn wipe_file( } } // size is an optional argument for exactly how many bytes we want to shred - do_pass(&mut file, path, &mut generator, *pass_type, size) - .expect("File write pass failed"); + match do_pass(&mut file, path, &mut generator, *pass_type, size) { + Ok(_) => {} + Err(e) => { + show_error!("{}: File write pass failed: {}", path.display(), e); + } + } // Ignore failed writes; just keep trying } } if remove { - do_remove(path, path_str, verbose).expect("Failed to remove file"); + match do_remove(path, path_str, verbose) { + Ok(_) => {} + Err(e) => { + show_error!("{}: failed to remove file: {}", path.display(), e); + } + } } } diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index 651491045..de54fae5b 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -1 +1,51 @@ -// ToDO: add tests +use crate::common::util::*; + +#[test] +fn test_shred_remove() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_a = "test_shred_remove_a"; + let file_b = "test_shred_remove_b"; + + // Create file_a and file_b. + at.touch(file_a); + at.touch(file_b); + + // Shred file_a. + scene.ucmd().arg("-u").arg(file_a).run(); + + // file_a was deleted, file_b exists. + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); +} + +#[cfg(not(target_os = "freebsd"))] +#[test] +fn test_shred_force() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_shred_force"; + + // Create file_a. + at.touch(file); + assert!(at.file_exists(file)); + + // Make file_a readonly. + at.set_readonly(file); + + // Try shred -u. + let result = scene.ucmd().arg("-u").arg(file).run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + + // file_a was not deleted because it is readonly. + assert!(at.file_exists(file)); + + // Try shred -u -f. + scene.ucmd().arg("-u").arg("-f").arg(file).run(); + + // file_a was deleted. + assert!(!at.file_exists(file)); +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 8a09b71c1..13c58747d 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -351,6 +351,13 @@ impl AtPath { String::from(self.minus(name).to_str().unwrap()) } + pub fn set_readonly(&self, name: &str) { + let metadata = fs::metadata(self.plus(name)).unwrap(); + let mut permissions = metadata.permissions(); + permissions.set_readonly(true); + fs::set_permissions(self.plus(name), permissions).unwrap(); + } + pub fn open(&self, name: &str) -> File { log_info("open", self.plus_as_string(name)); File::open(self.plus(name)).unwrap() From 698924a20a66568b13b360cb7d0f1677c9be659f Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 10 Apr 2021 11:50:21 +0200 Subject: [PATCH 007/399] unlink: move from getopts to clap (#2052) (#2058) --- Cargo.lock | 2 +- src/uu/unlink/Cargo.toml | 2 +- src/uu/unlink/src/unlink.rs | 63 +++++++++++++++---------------------- 3 files changed, 28 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e094f1f48..0af3a8222 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2500,7 +2500,7 @@ dependencies = [ name = "uu_unlink" version = "0.0.6" dependencies = [ - "getopts", + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index b193bd1b5..08da2624e 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" path = "src/unlink.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index b85b6ea94..26460e59c 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -12,59 +12,53 @@ #[macro_use] extern crate uucore; -use getopts::Options; +use clap::{App, Arg}; use libc::{lstat, stat, unlink}; use libc::{S_IFLNK, S_IFMT, S_IFREG}; use std::ffi::CString; use std::io::{Error, ErrorKind}; -static NAME: &str = "unlink"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Unlink the file at [FILE]."; +static OPT_PATH: &str = "FILE"; + +fn get_usage() -> String { + format!("{} [OPTION]... FILE", executable!()) +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); - let mut opts = Options::new(); + let usage = get_usage(); - opts.optflag("h", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) + .get_matches_from(args); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "invalid options\n{}", f), - }; + let paths: Vec = matches + .values_of(OPT_PATH) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); - if matches.opt_present("help") { - println!("{} {}", NAME, VERSION); - println!(); - println!("Usage:"); - println!(" {} [FILE]... [OPTION]...", NAME); - println!(); - println!("{}", opts.usage("Unlink the file at [FILE].")); - return 0; - } - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } - - if matches.free.is_empty() { + if paths.is_empty() { crash!( 1, "missing operand\nTry '{0} --help' for more information.", - NAME + executable!() ); - } else if matches.free.len() > 1 { + } else if paths.len() > 1 { crash!( 1, "extra operand: '{1}'\nTry '{0} --help' for more information.", - NAME, - matches.free[1] + executable!(), + paths[1] ); } - let c_string = CString::new(matches.free[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0. + let c_string = CString::new(paths[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0. let st_mode = { #[allow(deprecated)] @@ -72,12 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let result = unsafe { lstat(c_string.as_ptr(), &mut buf as *mut stat) }; if result < 0 { - crash!( - 1, - "Cannot stat '{}': {}", - matches.free[0], - Error::last_os_error() - ); + crash!(1, "Cannot stat '{}': {}", paths[0], Error::last_os_error()); } buf.st_mode & S_IFMT @@ -101,7 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match result { Ok(_) => (), Err(e) => { - crash!(1, "cannot unlink '{0}': {1}", matches.free[0], e); + crash!(1, "cannot unlink '{0}': {1}", paths[0], e); } } From ee070028e411bec7f0fa8f129abd72b88c5d077e Mon Sep 17 00:00:00 2001 From: Sivachandran Date: Sat, 10 Apr 2021 15:23:29 +0530 Subject: [PATCH 008/399] install: implement stripping symbol table (#2047) --- src/uu/install/src/install.rs | 35 +++++++-- tests/by-util/test_install.rs | 96 ++++++++++++++++++++++++ tests/fixtures/install/helloworld.rs | 3 + tests/fixtures/install/helloworld_linux | Bin 0 -> 3343560 bytes tests/fixtures/install/helloworld_macos | Bin 0 -> 424240 bytes 5 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/install/helloworld.rs create mode 100755 tests/fixtures/install/helloworld_linux create mode 100755 tests/fixtures/install/helloworld_macos diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index a4f1ca6e6..e902862a8 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -23,9 +23,11 @@ use std::fs; use std::fs::File; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; +use std::process::Command; use std::result::Result; const DEFAULT_MODE: u32 = 0o755; +const DEFAULT_STRIP_PROGRAM: &str = "strip"; #[allow(dead_code)] pub struct Behavior { @@ -37,6 +39,8 @@ pub struct Behavior { verbose: bool, preserve_timestamps: bool, compare: bool, + strip: bool, + strip_program: String, } #[derive(Clone, Eq, PartialEq)] @@ -164,17 +168,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("apply access/modification times of SOURCE files to corresponding destination files") ) .arg( - // TODO implement flag Arg::with_name(OPT_STRIP) .short("s") .long(OPT_STRIP) - .help("(unimplemented) strip symbol tables") + .help("strip symbol tables (no action Windows)") ) .arg( - // TODO implement flag Arg::with_name(OPT_STRIP_PROGRAM) .long(OPT_STRIP_PROGRAM) - .help("(unimplemented) program used to strip binaries") + .help("program used to strip binaries (no action Windows)") .value_name("PROGRAM") ) .arg( @@ -266,10 +268,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("-b") } else if matches.is_present(OPT_CREATED) { Err("-D") - } else if matches.is_present(OPT_STRIP) { - Err("--strip, -s") - } else if matches.is_present(OPT_STRIP_PROGRAM) { - Err("--strip-program") } else if matches.is_present(OPT_SUFFIX) { Err("--suffix, -S") } else if matches.is_present(OPT_TARGET_DIRECTORY) { @@ -339,6 +337,12 @@ fn behavior(matches: &ArgMatches) -> Result { verbose: matches.is_present(OPT_VERBOSE), preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS), compare: matches.is_present(OPT_COMPARE), + strip: matches.is_present(OPT_STRIP), + strip_program: String::from( + matches + .value_of(OPT_STRIP_PROGRAM) + .unwrap_or(DEFAULT_STRIP_PROGRAM), + ), }) } @@ -521,6 +525,21 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { return Err(()); } + if b.strip && cfg!(not(windows)) { + match Command::new(&b.strip_program).arg(to).output() { + Ok(o) => { + if !o.status.success() { + crash!( + 1, + "strip program failed: {}", + String::from_utf8(o.stderr).unwrap_or_default() + ); + } + } + Err(e) => crash!(1, "strip program execution failed: {}", e), + } + } + if mode::chmod(&to, b.mode()).is_err() { return Err(()); } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 8ac6396fd..840b2f6c7 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -2,6 +2,8 @@ use crate::common::util::*; use filetime::FileTime; use rust_users::*; use std::os::unix::fs::PermissionsExt; +#[cfg(not(windows))] +use std::process::Command; #[cfg(target_os = "linux")] use std::thread::sleep; @@ -566,3 +568,97 @@ fn test_install_copy_then_compare_file_with_extra_mode() { assert!(after_install_sticky != after_install_sticky_again); } + +const STRIP_TARGET_FILE: &str = "helloworld_installed"; +const SYMBOL_DUMP_PROGRAM: &str = "objdump"; +const STRIP_SOURCE_FILE_SYMBOL: &str = "main"; + +fn strip_source_file() -> &'static str { + if cfg!(target_os = "macos") { + "helloworld_macos" + } else { + "helloworld_linux" + } +} + +#[test] +#[cfg(not(windows))] +fn test_install_and_strip() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + scene + .ucmd() + .arg("-s") + .arg(strip_source_file()) + .arg(STRIP_TARGET_FILE) + .succeeds() + .no_stderr(); + + let output = Command::new(SYMBOL_DUMP_PROGRAM) + .arg("-t") + .arg(at.plus(STRIP_TARGET_FILE)) + .output() + .unwrap(); + + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(!stdout.contains(STRIP_SOURCE_FILE_SYMBOL)); +} + +#[test] +#[cfg(not(windows))] +fn test_install_and_strip_with_program() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + scene + .ucmd() + .arg("-s") + .arg("--strip-program") + .arg("/usr/bin/strip") + .arg(strip_source_file()) + .arg(STRIP_TARGET_FILE) + .succeeds() + .no_stderr(); + + let output = Command::new(SYMBOL_DUMP_PROGRAM) + .arg("-t") + .arg(at.plus(STRIP_TARGET_FILE)) + .output() + .unwrap(); + + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(!stdout.contains(STRIP_SOURCE_FILE_SYMBOL)); +} + +#[test] +#[cfg(not(windows))] +fn test_install_and_strip_with_invalid_program() { + let scene = TestScenario::new(util_name!()); + + let stderr = scene + .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")); +} + +#[test] +#[cfg(not(windows))] +fn test_install_and_strip_with_non_existent_program() { + let scene = TestScenario::new(util_name!()); + + let stderr = scene + .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")); +} diff --git a/tests/fixtures/install/helloworld.rs b/tests/fixtures/install/helloworld.rs new file mode 100644 index 000000000..47ad8c634 --- /dev/null +++ b/tests/fixtures/install/helloworld.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello World!"); +} diff --git a/tests/fixtures/install/helloworld_linux b/tests/fixtures/install/helloworld_linux new file mode 100755 index 0000000000000000000000000000000000000000..c1c6b9b37226b201e0ecdd470f59022353f602c2 GIT binary patch literal 3343560 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�JMVc~{wq5CG6mFv=w$(|Bttk7a$>48*pZ;K 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 * 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%0AWd+-T3<+3JZa{8o=z!N< zz#GnX3@dS85`f&`&;gCQz_c0KF(g|;mV1Kdi5ZT|vohqB|eJ9HuOnwDv0g8W-)Wbb2wr6B`521g=qI)g9JE-uM0N=#3+V|e|KkpVVi2-zRR z)dV`|IT2Q*qgD3=1B2GF2+BQ!;3i}{hSUYv$62rk7FlfDH|oVp&Dh8ybu96qKBmK&C@wHF1r8cC({ST!HsAL#kBlZ50iqwbICy ziSk6~5^%DbTR8H=TO|gDxbb#v&61&T=t^nE=b(FD` z|H(`YDMk4O41PsS44_r$rFr1R;HQN^6()Gf2y8X>5W+2G#}Jwbz1%IeA~CrH(pmte zJ47=9a(Dwnu{87)mAuq)_^qZ8_1JBN`3rOZ9#-{sb_@za>s5Q^7Dk-Yz#&D<7#7b%&4^%~;IKv-i2^mnpp`O|f^`8I-Iy4X%Su2;BqgSpnK1kS9ix+4 z0%`-~7nI<<4K=eMKDQ{f(2gNs4R~i+N@^LyWz=~?+yRW?30!{b;|2A7(n>%(zM$?V z++ZTm|H17naG;^L`rvD82wM!RZR{8xa+AD#pXys?U_}{WcY|A)47YDVmYAR{kz;7X zHH-u*j=+;GkjZB(OEhqFlkFI++)$S4K_UcgG6|fDk@_!4Ytr+QGjnnhiz=i2`y;q*%&YMMuPHf+N;t^}T|$jp!k+9Huz zVw08!y8Ma-xqpe|8IVb^vmmjX1`$P3mzD=Kkoi9zu70noAuzH#1;VG<*FOc`lr7@{0w8UflEvtwBE8&tWLOt~!)gV~=wym0#p|~WmID;X4E@B@lI7G0PWH=)6 zc^~Ks@1oQ+aIFXmVd(5Rp`o?jb!?!Gs*oi}@j3a$#qgy=I7iu1`jB>XL7Nf9R#u=T ze0B_83)n!%R;J{{gVPyORLh1w=Sid9g@555`(YA%$5 z1>2>~NQ)}n@{4j4OBf!cFf(}OmE=Q~34_Z-hPqAY?!+2`P}7kd4?SK1DgmXSw%ge; zEI=y);fs{PWk5)B0rai`a8gIl{E$)*tP-|d7pfeoFvMXZqCCW+-;Tkf6y3jI7eEI_ z5O<{KmzLNuwBoq?13b^RZ!-&nC;a-%B8C=7BPtJceKW)R2`Cv7GERgPdTx1S?JLhZ z3O@Obfd@2c0qx6x4|)YPuD~9lR<1b1fqgL}jx|82^N4l~Qft^4+)@)uN{dn%Y~X{M zNDJHGC!ryFE8rl{vw>d8Q=D3mSOmJu0#d?&BQvwW%F45#%mg%ui_%~G3|RmGTEmuK zWP`eS7gR10yIR=7i3wC3msBtqwt|kw$Ve^9EMZ88j{c)LaQw5!8f~+y3?yWEky=I_h1|-6fnmeGS2v$}h znYpQ;`zRoV15Rl>hHPeLh7gY+S4Wq4Pd_(*h7Z!Duf$n;@V^H@-TJHkRTAzG06JSXK0iN~q2vc*3<1=#2Nh%3(~}*8 z3ffWiVB^6x0a3#TtJX7u8iAmLfS^m9K&z=B8}cFTI`GyENGk$I3pC_EXvbrId1hW( zKEpdJP(Cg}zX2KANWk5hrA2v(J8ysk2y>_awom}KGm&S3kdBf8ox}%jeGGGd+A+AS zV`C`J&MdHjCKsGtg_Q=-+B~xWa-j3sBMjitP*~jwnJgjph}2_ppi&~SC^-Xs9u3S8 z!Z9^q!%3*yo*?-bGu_!Ss6lQxhOJ!CfDX0mD3CP}|8Ool17wL1WHmS>51@_CfJ%I5 zFA-MZz~U2<{UNbR&WVUv@8m>ou+3Qqx<()|CAEklh#PWnbP@Ozmdmcp3@Mq#1v!b8 z4AVccGe9mOVJHMm2!QV80rzjIIx7IJ<-kQIX~X|^49gZVGk`-Fa`Gc+UmAl~J7`?L zIJKxO6@5I7K$p!Ob{l14Nq#QqaHO0{&?aP9&2M1GU?;)Akd&F1SX3EboC?kcNtNi9 zfF=ZCS`6(N!XJWXebb8)bK^6MK@0OU7)0){F@Tm_F%-QA?b^<)VAw{DlWFoxOF-lF zc6JO~_`v&o<8$*tl{ym^Q zO?fGxbAsbR_bDZ(GHkP81D})ynmjAAV>pEON@j}JJlQd@PGx2Qt(ahFt_OAGz>WpA zj5pkYtc(OF7jQ;~*07*CAMk;H;h7~Fex*4`Exl___-aGQ&D= zf&7+g$Kd*p36z~v7^Xgl9SjBUA%i*%;5|g7p9|&d$HV|WM=3tBgy9A!=q3Wts^P>O z(7n6GB}JvlCHX}RCORnNYTy8dI2)X-sSu^sl1vOPnMF>enK__>UM`;ryr?|Cw1nY5 zXh%KhWLof0GitSi?SQ6Y)PpL(nahqL9=gH-sU|}n8M9+p1RdK3?IdNGBLvBYrLgWW0!~$=)gw6PC0yVKE+N*0f;T3Pv*mK4Aa!v|$baEi6Ef+oTDuFMSJ8i0Y_ zje#Mx5OnOUMXViz0Y)DkoG{>~I)je>0QD^M>=>5APsK}3VaTwDwwNJ7nV6GPX;W%! zgdEUrlVGbOz-D64u8{sMRykbDHx9wC+Ac1EUX}>*Fo9ACsSAx5%7ctDG$%1JK+3@Q z;*!Lo5{77sH`8l?#zGWI%}g{wYtGBRurg#8$AdN|#24q}mqVI2kD&X!X_xrz7|vLL zrng{gM@kLN;8h;lS`hegk#-EBc`OXci3N$tnI)Bwk`@%ah(OCPEdd=H#-OCi#*m&` z0v$|%477nyWPvo}(MA`bB><@Iw6dbsf?Yd12HT$?A0j63AeQ5}i5t`m!9K|zg1Q0N zj^Qoz6a>#aNL7@XpI6M_NBG7t&|(HqjK-Jd*)eeNfLdk6$*FnpV-g5g)>Q5U+A(m+ zf>wnW6{RMZ#KX?=0X4BfH?M)#`ezqIq8V#0#MKuBt@40`HSWpP|7#)LCrIB6R((SH zYgm@W8(lS)l$BYQ@-pN7&e2>fKAP2khTYnNyX$^UL6sV;a};aGz^ewi1fx8&Bm*-SfS1`Jj_@`BugSaY8$WLVoa=H@?lQC5hRo@#PtrIjL4w zA&J?k;UFe7xFP#P5Ep3`!H&Cwq)f~J0_9BTU;=!~+0G8JkHpYU2f1RWc4I^hv~CgB z>ou}tsM*BCkd_i(k{_R&T9OFOh44A*Js%ku^7B&Tb5e_o<4ZCU^B|Yb6@boZj?Yhv zFDcJwsI_8eNY2kIE=kNQDP|A@1t%$+U%^AZ19~0u&$pSLm%jn z-b^z4WCZ&=kf?*@O=9lsvHS-angN|_Xal-gwn#^z)X)q%4350Q4qOr-u2ZvP*xkkm zzS<`qR^fqeP=$^bLPnQB0}!;nX#^V1Pzu&awkly_0FO*8*#NyC1JsC5OJR`Rf?h~L zb%NJOfzLogG{j+JW}vx9@Pq-7kK%0K}ivVv?rnk42cbJ&5#^lmY0oEl zy_H~R0PW5!233X(8^KpeC4!4r(ABh{Y8tf6E48Q$e1j1rP_Xt3GV_p&!(X>q7}D~K zl2bvY1bC^yq&1M0${0o@=jWxAB^H6E%fJjfhTn+pCS+C+yxQE3Vc}Is84GQA_+p%C zf?AgovkmvSBXo}xXmSqgjgX)zK4hP*nGRiihd9j(=K?(CdPat@#N@=h67Vb=!vxqE z5~PSgOEM6_0qPXkG5lf!mrnGTI|Ch1Gb9CcIX&o-OVI7qR#u=bz2Fg5O*;nlHRx41 zB*{QB44%_XL7QHnaRsGdHMku^%R&}V#S9r!ElULrpQffjiYQRgkL^fIl25Rg#VDu1 zsTJNwLqEsEj^P|9BLny%Qic=wYgBma1*acz%<12=fGzY)%PnCDxyQs154qeN#C;}6 zlN}$hR>HY;pmFQs)S{C3jMT&w(y4M4=ZmKLX$Bsd09e9C9 zKIp7Z@TfPhAhg^DJJyatKN37)XM=mT8Ip~_%_B&A6?$SWy>X;(71 zUc^~z!gIA9gYXA-hJySQ2FpNDw8Jhz1qUM7PK@Rn#64)0BX0Ge?(;TghJyS8hRxTQ z7{C=1Xe(4c!?su!)SikB*c4FIA%^Cl8>!kBFftT_&w~YZEz>efY=-%9CrTJQAJNlo zrUlOFi>fK01zVtN`4}Q*urVYSr5DHN7c&I4q8HInGqDe078EC2StWs{Ff&2-{DCjc zwXgI$PcBmyx4WdILNc$o{<3?8}wmA=TUA91E>-3^d+0iYWvrp*Um@&!sg zpsQ3EM8AX5RtoZY43Nc!&{gHwy$cbAPYk(MWR`#$Go>hpw&z+hFhE9Z7_O+Wf!ER~Nw?HClj(A%@%jw#07 zFLn&vK9I9*a!YecGLymM7y0=G@nvQVx}Ml&Oc+$*ZON4K{P@fi)MQ0LGrWEs+72Sb z=nam>E>ex;VamV&?u{_Sl6$)_!67+t0>m2Tuof9K8-ONBtgKKD=fklGkwF=@iYTu% zHz~CUT-`eoXk6l0)DJ!AE;uzkH#M)sjzM7|_K_Q~H{dN*9EPFp5P6s-Acc)>T%7!zj3_pWTl}Js_ zPECo=EzM#0Tgu8%o|sv}AnuIZ+(Etq1k^19ZDg@yShfz`^I%`5=9O7lg(Vh2hcJ0} zf)Z{z!=AaQ8v?;tjSoptkQov1we5Bc-w-WSc+@}-G_+%w?#93Xnt$u$WCYy>0$H)0 zomyE8Z9Qis7H3#l6@ykX1ZNf)8VBW<=A{(dF%-XHg`BP)4=Rkn(;Uh9IiNufw(8=#U*fAVi4GTlEb{yC-RQa-kk9-2v4AAaSnF)h|CnG~%Vp(Q7XhFHAAEpvB zh9uCrpwOja$QcjW)1`UIpyk!D`DbVrgi^4QLe!O&AvC1`y0U)KPSAA$`30#(C7G$k z4FBG0C9x-jG;&08QU0JJj6jzNDm#_Sf!4%;>rQuxIemlQF4lLXC? zleU_R>TA$pWf`fJj~xRud^QEL&zo@GhuMV|Tl?H0edMH~{KOPc@0no%+WiU0i)hgL zu+-`{<8a$O=)7tv=-}sQ!t3?0%_Gb9Ku34Mll;(TCCN!=`%GpA(0pDoxFBcf5e4-t zU`sKfAxbz3>=-`DGcthA-vW*0g2ywU)d5M4*KISY7(2kA2pl z@sjwW)HKwDNZS!oI|fk`=!x9m)BqXA!rmxGYJ$~*wuond=6O9q`&ETK5DRm_(G3}J zfJ`rjCxS*F?HKlTVOi1yRt-)ZXuT3>GJsOB;#fZr)B;aT&W;CNqnehJU(S#Q-HHuW ziIi^K@<212pxw?mlh1@}EDWi6iAg!BDGbemXsroISddoz+A&l}urq*W+)|6cr}d*o zCfT(qq=jzBAS4OBTMZm=pawLwl?GZc$52*?BR7GwGiVuFJSZ-#tU#xjfGT_&vRCxr znq1pX+R5c;sT;aQ8kgbOu;neF{SeU|ph_>k0Jhd19#QC1y*LWwt+>X@2;O7u4!%SR z(iVq|3}c+*fM`v^F0-~{ScuszMj7@QxD|b;F#6Ha;PizvNMr376c@twZi4q2f=&Qp zc!y=l7LFC~(Cmu4u>jf{2HMXK$%Wt|0@81>vhpv6oWP@_0Eq(cGK*-7%!P1(qyv2LY}j>P$O$RROUSk_f@m7Z3ro5P=3Zq*SKj9)}?4r3v6r z0To8jPN{QgaY=rz9Yah#D?=`16+Gx5iujz&BnDS01_scOV|*!Sad1*jYJ5^sca$2HtW%ff?`H;IvIrg$1#R*M zHPTY7tT6MD9fPk6WXuRls}^)06>5xL)5$4_*5K zpHYS8PgpDG2N$%NpIwHp^n=7ST2==~EM~xF=B2}JBfY@kWJcYZ2py`(w_^jxYe8yJ zZe}qkVv8BvL7nr|iW0=23`6h??4w}#0vg&7fCdbdf`!kT73hf`>}yZ~gjh?KRGJ3K zN#GGK(2X*w&}}y$b%^>IvNXkx;T8|-)=#h>AUIuhH!j7Rk1$2N`Nj_wlj^P_o132K3 z9BQ4$3p#V>C<6m%cVKD?%9c}b-!wiaGdmT0$W3Vl=#qru;_?(bhC*?)Ry24_4jezE zmhGSrw_~W5hAy>2NiKvY89#}zF@W~I`@;8RlD{(?aV|pw#)W}M#T00*3+S@Xs!F7a znDDd})nW_lp&@p53`^Ugr3ttohYcD)Mq1DgPk`n#CK zUVwtu_MlSh7T(ZAbFdA@;s%(fLGv(HR*(gHkQOi2GSrUYv?cVE8_>i#Lp^-{3>rt^ ziBYUKEtW_@rY=E;^+0UET0p@Q1g49ys<*RaI6M`y^JzxKYQ5j}N6OHo7>%CLeLbZ#4XJ_wY_sC=L@a!=jn9W(g4 zV(`2Zcndf~ybP%Nt|)by+%CEyy;M zD$`Q;R`sh^W;-VVJUp88T`EIk_S^A9{u`xWr)4 zkz`;1otsz^pI8DOz5q=(Eq=`kN+G$4CGn6$HRHik9#q$U|_JR(q z1GOL+mc3KaEWDgmMVrYY%;EsHP73hGZoc!X_qEuZ2JBF!< zb6i39%@bekK^A=wl}JFBZ`v`uw1*7GLYmRgBC9AhEwchz1Aw;AfHM`c9P$8}KQm~B zBzU$xKD8n_BQY;MmEjsIXqQk?QGPi%e8C2QawVb+fKHn+=x{@`1mf5^uxhMzCZr{W zRmzUx_d1jTb$GNOHN}G~bCdFO>=@QCf)=GF78NrXBliozZ8lIF1-VSIW7w*SGQ;R@%%I6oASXl*?=D`{69Ka8VQb7FuM2RvAJ92vkawbD|+= z4AhQ6lfXtQq#%Hm#R=P>n>9hJiEK1b?`Vi*(*$W2ISZHSb$JYcs=Vjg&asO6wlzP<`8Lt1W0d~s&R(?n~9&)tIOD|Z2K^B@5Xp*Mqm0DRPnVDEwRaltCgXrRt#1bN zccS(0snw*zKKsF-Py$-upU?0%0=(V1C^0W3KNoafNe*;gZT2sedoWPi&XCDp(7-!V zSBFXth7TK}*a#i5n?@X9Aaw#k z6A+dt3;S?XLswU#cdy`nh4rVctddI8z$dK~fhWGfWi{4`Z)h*uj=^UWWHunaG%qnH zGd(Xg#Rh!f^JhVJhEmW#D^;(gg_M+b3>Tp%i$MYqt)KvV3#m&FI+ziD@gwADUQh%> zV+s-{(6L)=CK0C{(Z5s&LMF6;IhB`nCD?d_1*dUiZfsgHAi0Vdb z&w~!kBr9TZG`p=}r*?oxEFd94V5=lV(O_EuYsav9186=Qyw#w%q{xop&Ob(mv=oL_ zoQw=5r3E>u3`@nJTSADL$(i{X)aHb)@&w)f_6<*y3emg>%1igjEG_|$oT9XW$Zr;) zHZLHx0>js2=zKgl8G#Pn>zc*H5So`+0l5;B!4&(+Gy_*2e=K5RNGoP&J_;(H3UU&Y zVW*U!Wjsjog0y|WOO7-^1AFkp-CH;r89Wk;Gg6BfR+83QDJinDg0yIyGZKr6k>T^zisHF!<%?`IYA6Fj%RvF(j5HX6Ar~ ztw7goB$pNyrRF79#%Jb1?%rUK;sdR`h);qp6ow2p!jIZ1hByR66sbdIwHACydoHN& z5}#L`n#@qv2EGk6J}sq~L6R3VObI!Q9JDhiuLOJoImG?oVhDW-1!5ysDLV$XS@^d2 zfw~m%BNm{QCzOH>7R4O|byygty=P|t&m}M<2}AQFxcoutNg*3%$8c~h8v{5yG1Ow5 zq%eFcs?!+96hea!+==6AF~H^*ptEvV;}hDngc=UX8sLHpQAwev z4m&%Bkog#CfWmvpf?;DJpo6P4koPjeJ%wmq8QC%LSwRLaV8>X2GIm~mUOeay5i2Wb z<|90w=LyY&paDR983NKU#~Olm@I@iG2ktz;4MD=r0Xc)DaY)G27#`O_+wE9g4sXWd zb~tRG1}vQHNYLY&lAemP!s0hKBWRu}nS|@425d0{*zpW|XQQ60fmAl3Eii&EZpP8V z>ciNDgj~ZRojnQ>Mym~B1wbWeR*{N(T!I@wQ&ko5X+^22HprJQpsi4e#o;RlWzf3L zqLNI|gdk|G7Wgc_VunLF`hk!^Cpw>q=XZ{Yp`f%l1F|}p;VH(>UOWXI!(VX*$c8FV zse}~ypo%Fdzub;t1#D{t*c5nI0b{TiM+mwh-*5;W@xf6PgBlvpm7Ayo{?HPfz)C;x z2$_z8h9+nli-Cb5q&z=7u`(EZRbXln!@ec3GYr8VK^va{pR5SlXW)`qWXEv%9Avx{ zU1ff;m6cb1W*+Erykck*^*|Cc1L!m!hP&@sL93}CWh`XXF7lyW5OI(nQAT?}%TH4w zqdg$&VGFQ9>d+fqc6JP`l-wair5Q~-I|h@ltPClspmjFIsSNKnfSS6A@VjV`X2u9k zjqOwx&lIDkMW z6_0~r?HHVQGBd;%rRQVP*(V6y9*9)1z#BWDH45;F8bdpV=tL%l%oK(x(197`1`=9L zggWR=ewWjZp>7S-=cq=~q5EjZkZBKFs>X2m1SkvP=`A9KAN*uEI|el)2GDNx0?>|3 zNUXt&XB;Cr;1#3+iABY!DZ!Pw7)$OagC<<^8GbXeGGrDoT=7F6LP8`raDNBOti;F9*tU^oDEPOyrJBDxAs}kbe3T+}lDOgYIjwlmDW--GuX+#H* z^vkFqjwT_m!!)3+_qG97L3VZwI<-uovzBa-I~I8K>vc%M4fZlpjSiX_12ss&?cpwrDK%&@jMUbPE;Tlah4-DI z^3Y=1j^Q|bbu;KbAh2avBiN3CaXQBNlh~6U)|0kz`0|(zE6Rm&rJ!V=i`+Q`I|-@4 zg`9$b!#L9wObn?NsmTnFWuVLR65-b#LMmdco`aO?Sf%V3uDLKVloVCQgARLV*yIdO ziz%R6-(h1aM2EF4{4Avu(AM6P48+NekQIMO-h_5yY9NQ9xkh+~xVkW;Vhlb)N@mDt zHKD#dL@ip29jO5WtEi}!uoY0!2gFC9^AK|Xfd*Ym@@=9^4b7|-EMg(y53Xr53#_a> z3(CwugBo@WzoD0=q8LP%M&J@ME~Y|R^HTs>uOFXSQc@IOV#g57$;ObBnx2^#51v|! zPs=S~c(VpPc%BFima1~%5p3cl(T#b|658qUJ36MB;+WWWV-Em_o728J}~Dsl$@ z70e6;;EE%@EVU>J6z~iZAK5|o=oFV0fDQ&tO+l?mKto25wO|DA)MVJW2DTIxaZDk+ zS7pbbPTKKwcvF)YGb4jzNq%l-awup+Kf@16P)DUKwWt{H7Ey-xp71mCQG%H+dFtjw zXax%z>!-(++Bj;7g_u(a=xGq=`KUOIT*1r0kdq2OP92<3d^Atez*tsEm{KTBpFXxB0{6%g(nLHcH(322nuinUNA+ECCq7t&@)@X?vq zBbXRK%QzVd=U_ip1{^W))J5zdvvnkBp(t#e5Bo(<;4wqQ+06=IKIn3J1@N8(Viwzi z*4I{mx5t2&*j+n_yttXbUK-flZIF}-=~ZajF=#=z-ho#q+c6yT0F|$Zu~ghqN3(u9 zz2@6M$%Bj@c{5u32C@LHh(Sq$j4K>SNcVOOHY=DxISn#b%;5f+9kP!VJo;<{K2ySu zVM`!+B|bU70J`o1)TsltG$4g0jAzH-b`0900H47D+O%rNAl`&N1OQo~La>C; zv}1^41P#YwHVGjW0b1D#^&Dizglcnt3=7j)8PY*L`}l(V%;Nk!NLWE^NlGj#$^_54 zKp3FGfK)q%AFZH~h;r1HF z?lKc)Fp#0^2XrTTX5Ktd> zf&&9$OTPxz`~&IJV>V_$Yt-|LQmw4e@*7U|nmV|oklP{;S>VUXffnK?7Nx>>hJspb z44acc%TO8I)1fI1lJ785Sx{oR9mA$naLWm_7R`>~Z8{@(IS;I?!gfbDwBW)fWykP@ z6E+xA2|7oTVaIJYhQxw`oXU96q3@7wmWe54iFwIXJ0_nY8-DsZqAvy;-~f#+fG$Ue zW@sn{EyNl2Edg!P$xDfcZ83tJPk@*URjhh z&Ss=(YuApU{xbsu?3im%=cBkZi9vH3>fi>>Td{vhKo(_yGt}&Q$VxN@2TOK_`~n7> zHEay|#SE`FKx2@ZB@C+!*%%;)fO@7dTs#V?wV^95(6T-x>>=giPRCE|kQHtxtqKg`S(t$N)M;5;FS^y)qfzwZSnS zhI;E9G;e{@vy~MI8R@NnKuSWaNdp>E#3v<)i!sfzW4OGOiJ=s{ZIT0Wsy|Wl zrX;Ux98~+O?HFFIW@AXoEMYi^abXd7G9TQ_flTH@2G|*j+!5CXz^e#52D`Q3Lx@un zL0b{?(qWSq@WKSV&=>M2SIUEy&c>I34!q3+oe%^WHK9_A-j3lq=-wLe()W`1+|=CsqDnl|diWa$&)0zt z9ZF1zFEuoyn)_T2!xp=Mvm0^?l&o8k5bM}LeI(enMbN@Q@WLUy`xfo&7(UNq10O&P zE^gw%2`oM%KR=rx6klrwJZu9`Z#q;M#u1mr+LC~zQOp7&GcO%i*#WAe7$%#8hG-$W zA%i{O-X%m2l7JnM%j^VNdbV3R=L;}s@phJKz!FK6_>M!U{cBFZeO(-XaAgLj)Jfx)H;)buZjFUU+`C{zOVLO}<9#iwKz#V6+EFxakzwBbPO zjUj8oz*)q~3Oa-gKKTX5VV$7d2iu%tXUAY8#m?YTo>-K|V7Zo&p(vTbTa}Fgw1O-L zbiVQo0vjXK?@U53h_D)8h(X@Iam%F@Zfot zu+(H&ZH6*fN8p|sobxJG>)>l78LrM{XGqEiAE{KHSpqx6jPM*f)lTTadIAG-x1Sw! z_y=_N34=Y-wdt7gqXXWTIBgLdLs1Ds)Gtz7*hF>zia_V;6vyXg=9L!5=jWv|c*`*` z6yz6|fDW>ZFD^+ef*xl_WP{s|VFv7oB5;4fj$w}^J3|3zrDS|bVmj68VE)hS49OXg z%Z1>_dgi5MCgxdL`JkNOtY^-~P?}eeSd`6hlnr#gT@JKN!3=Ovl>})4VfMly6AOr# zL_0}G@(N`6Pjsn;wSq~k9Rou=8$)7pa%ypLY6`=I70jUN#`vVvwEUvf_@w-lN`{2Z zph41#5(X{_P-elH3q~n{7>r$@2Nfcfr=ao&w8YPj;ow_VhT;N-pBT%Fz&Rgw6Kt`6 zu^qz%N9ayaaNdiDj>3!ip%;~qaDX%yoxnLNC^ZdyzY^O=b_USkH)NWfVH>vY8P+}u zv~Pj47Y24La%&1~icfxic4>h}esKx%7EBx!qXwP#dO<>o_}4r=n)*)gc&D8xXcr_h}-;H1273n=S>rU?Ix0QUG4%Yho;cuScw0 zfq0+VVPwZ(bO>4{g4=WP;6YP{B}CqS1TJ2wc>~f-EoO$4)TGjM&{8qTxk})fCx&Me zp=V=*VhGZoAr#VR+Y2B82-1#c^a@=%EiNn^*Lpu)(I zRGO0=UtE%!m=m9qng(r_V6Vi%$r-DZ9mDPkpb74x)HLuGa)e6MoftUk7hB9lwO|uS zj8JHE7)rqg2ty|LmEI~`&%Zm~VY`}Bg1l>K5>n$Mpi1>0C z-sK=14P>suh&N?s2ro&qa4Jn>n2+r+c{T9)Ebz(!+7bmj4WnZY z+PB6axtWEbxTGjGF*iOZHIJbi;|v|JUP74$d@hb1!-+P?bR=lK9?BE~9Tq|22!l;H z=B?2?nvhLGs7F6RH(bIR<&aTeJBI(xpqaJ!{308)!yQT58UZV)A-hW;W-~N0kg$pb z*20L8gC9y&nwOGVlvbLP6Q7r#k_uW_pO}+F_U!Ue*p*&osmTntPcSfmZsCc~gkA6e zUTtk=k|oPTUk|y`P$s znhP88K(&YAqA(MLWnTxRprYxSN#HQSUPXdt7^Oj-)AD%G<}65)1*=ks*Re|3 zG0cFC10s*!2c;H+#<{394uB(nYDF?Jgr_EEGq8KW*H1%39^CCE@}?_01_Q_z5U5_< z)d0mYM#N5TlIQvcRcP2TO#colvtd~qt(XUAFw|0> z;XgZQb3<_|!)`L}od6Aa+c6}xfsZYN?pB1`jeW`k z1*yf*Dv%gW;M?QCnlvGW4Q4sZ*AH#bqZTTlxim;2?V49oREc+!i`q#>KPJ@8%g49OB^;#PFAsks%3m=KzDZH!DMKX--Kdc-3J^Nltv33B%qd z@G7GC^vo0nRZxuQ=cGVZ@j(0am>n!|VgY##X>=>jM;I;_McYz123!SHYcWN~bKUVa{EMNba&w7mR0=mY{ZnZm1W zD4&!P8)tnk1e(AB_0EG!iu}{kic?F984NHkvV_#TXdMKUHYX%idFFwZ?19eC1q~wv z6y>KECFY_gKgw6M+A(Y`1ZBM3(D^rzJ7Duwuqc9tA1vE%MVmo@ zN~0B9D87X@AZKC@r$IHN#0_XEE56m%6_TL&#JrNk%)DZ7U%zJy?i7JFDv&*#pA24! zXlKW8*&fn~Vpx3^G6)Y&z3|=z#^wh*249SgnbE^##yHlF;r>j>Lbv$D-29@F%&OEB z8iL)~@#8;%JmL!!iRL_TW=n6`TY>1t7#F3`$Pl6WD zfKII|vSXOL9=#&~P6(i470bfxU+JJ_Z=ktYavQjFotPQ?i-Svw7}k)oP9N+jXys^S z1+L(NOHv@U`#OI%hRi(h2Ap`vvJmhT7lYD9tX_kx1_F(fSy_cAf<^%C7_KZp?dOB7 zgJ%XD%jM7p9HF@zO2G<{M}+T=0bki*$G|rk+(-e}bNE6RY5|mj*>h|BFr9(%`{v7(>Ns63b59_So4m^d&MuHhC}{PDW2@C^Z74{sWaE&{_{N3rMZf z0@AXvvtuyDb zG@<<*Pz8iCG-SshS_{#Mu{5bDu^hCH3v`TGIcT8_IEvw=H%3ziXFN|Y1}_W)&7Q=k zq?ToZ?!-d9i*?{TZt7pb3!*_w7UBI^@RS*-uUG_Hi~;sN)&dYxHllQ|LH#4#8o<3J zur!J$I|iL~(DMvIr!41#+ofWbpdH6Hr4~A%ED|Eb&QO+^Q%b$fE`8nL>Jilb0lNqu zzc^}Vw8iYuVgO3PYR!M&SQ$zZvr{2!Kd-NbAKsUinpjd=lv>Pi@*~Jw6(#Wn`Q@oa zpi}Eg%JUg`p!fP=-U3L3WFRE|FunhM3<5c^KLaqw?^k9m8cpH+h3P^58Z$(){bEc4+Ho-r8Lke7R1Y-js~SG z5x{)|ltl*MBnGN0p<`f3iv^64*d~aBqD?WnJGN*)g2UW@bpuD~pF* zl9Cz7#83>rYBe6V{r&SDNCgje3{n9B+T-n#S>#ljnUj)QWXB*$-nbsZ?FLY{JN=?JP9}VUwc;N;qGqK-+y%y9XNG$;^0Zj%iyGSf5VX&AC>M}#` zHwVw`{;NW|+BLqYn4y=8i6Or@J`L0}Whh1;D~31~&Hcy+l)@TZRO(OIfzl0hd>gb< z5`GvWQjrZ`MX8DP=8RoP6LUq-vI|F+hh}q>Obs3;wz2|EoLX6dMnpl)WJt!vn%|%m zD^@vB`IGX6?&Xi69fL92(BS}d39&{FPZPKy1)B_kG-0rY9lmhIHBeB%1KN0)lL@_v z8g#c6zL>+BxWRc2tBZ)3AA?P%!Iw@l2)ltdddC-lcYK(cFf`_ZPe=vb3(c@?6AOb= zX&Pwj7sCt(W>7vXE-7NjBR3C^ra1uf@FEXglo*zXKZNH%KFREso{i|+(N z9W{t7QsuM+(H?^Y0lX-JW*4;l4h|CJWxhCzA!zjpF%Pl`8C*5mF+BSW-3y(R3R!B$ z(7PDDM+I>VT3vvzQ?@#VnIW$tYfy&O}Eh4 zfl{!x{){<{48<8mnR(fuT{3Bzprv!LHU*keD^muB!qUv-Y_bnGMvCeSa#(H-CDZ^x z9b2?x5W;eWZ+vztbU_JerlFjDZD7a1p~?pC1VK|CB<0|)5usrPs;sQ6sJBC#!TclS zfGKb;g&*SDa)=pnML>QrsLz{U#NdT-Q#?3xKszDO!C^aw_w}p{MXAO4Ic2E~JvHES zsz4jyz*ny?#%#@jjuTk+>)v3bFmCdV_%a4jTI;bOY@JUnL+3Nfld$x zoo0~2uyQ`Aiwi#ED;}&C5~z^=Z@4(>?e&O02&4!nEt%Od%w>S~opTdQk~0`$_#vBW zA(O>_CV<*SDXA6U{tehINarJ#THqL^fwlml>Y)_OyVBS%=reGsk0bLhS?Dx7r>nfs`(XUC8Z9n1iE3A7IfT7!U3h_bQ@DXMfU%Fhi3EtUXRBs$RTI>_Cl!(TyD zN}#j^o>qdIfMfIa<|B|%cu1ncUe7c9Scf^Xh}7HOCJgD^fl@Z)@FdCehRYt%yb5c{;O?T(Y$4kqKMrp*#)0S1Bnh8v!C8#kF|3@- zh;k}6IFk~pVbBJ3Az6;V$O>k1rRoVKjED*Y;v8~2y08ulg`Hi0*bSVJdKIxp3pPvu z-JFG-Bf-0XK+`9A>8X(MVLLkp571HZ#qp`R1tml_Goi5qs}-S%0aiZki-7JMg9H;= z2M4NnG?Q7uIuj;~E&yZ3^kaLR{Z0sLtiyq`k zugn#Y)<}FYsG0&h1JeJ2Ug!Y6$^lkU5K;QsFW)o**k;k3adxzyMBz{Py5hU><03BDg=5 z?EyMyH!mK1Xat_-9=t39jUd3s8^OVhy?JQIAnFM_&Z{UDbbtlC?LfQ{sS*qfNttJ#)@4LnU*o{^c8YGoCY zn4KC9V&YJnn^<6F<%@HdE5trkG zSx^~|eAJ7fnH|IWbXJDkg5qR`dDoC8Mo`-}aGsSFcs9_EAs2BZ2N7KnScn_gF$ju5 z_QQaRSB6XV&>@5P%-n(;23JXT1`s1NIkN|8Xb2B=Ni^NZZ0r~c&O@RX*U6>eBbj_*_l}_? zw1FE_Qd0np02P-MPM?6DYQXU8EE7X;WpPPr zZb)V>r2b>@ac5x22k(4=mRAF*Ffem=@Dlpi2(%xevE-Sp8#X#~^^^3~kU#Z36p(#5h1rp^6e{<%!-{2L~vk0S+Drho}iI z$jL0R0d<$`bQB=hb`*g#G{os>Gq2E050T3&&9$r@Q zpPEyg3JD-^g$f#&MP62j+{~JI8k|umqaNfsUa5VLL81Qxu8zh1(&2`=GoaX z)QLdrVestmtparOz$p!Fg%Kowvz=gK@XRYNNz5x@cvr{-x*h{GN6g^E!o&a$vv^3p zV>rGaaz=N2Vp<8j^8<++#39E;sbyAHL8)bspoAnv_^m(1uv>#j)MdvIq{Pk;lvo~= zm+q5UTmqhqVt79Tbs&I_S(ByT4>ra zl;B(DLyu-YN`iru2MoK&yo(96FczH0u*Mtd@eGYhY;_J~br*6|60vC>sWwEZg6tSh zXfZQ@t__D~<_jp3T)6K80M)hdf$g8*^bI~}lkB}ju&hI>&+Hhah1eNDJ@w3_(vnn$ zDP-(v2bYxJWkD5CIV4vi57ICkX$56pj44fOk7Plk7D~a&jw2CF43O!tB8GH($Z9EY zf&y1Z(2FlkKvNKqI7Si%3!rhCkn{~ zsOtjB?hn{8d^BZXNX{()Uve62$8f;`DaMhiX?SH1WyELZ<$;b_bFIiM0Uc0XS`0}I zP-!bGBneoGAjvpHYQbWp9fK)Gbm4BO5MeJ;CnrB1bTu!i?NFRrVrRz?Wy!#hS_m4% z0ohfy4a{4` z!T??h8Tp=_0X!}V>Jcym%YX`-3h421NTm(D2>QE5_fYI1yTW^z$}F#{Lq^60#j_>zp&JR8u! z1qdVcU4OYF68Lq-S49`&?klcW+fekO;z?-gM{iqv=<;CE(D%d%&bzj(adl_1@Go*p~kBP|{ z&_hA5%|)Cz0M-w#t3knn(xZe1G?aq11J3_vVE|n<%&8CPSUZNbAK4jFc56XfL2kVo4(CUqC5X?t3*AdWv3YNty-2 z^P5ZzpjK99GI*H+!_qmJM@8VtdU(#h#Mtk-@g6j1qYu8I4Y{F9W1aIRN)cuo1a@E~*s;M>JK49B2{Z$Od2wl}v5_4^ z1JYI08juzq_Ci!a4N|gUt1PhUu!F}Uw$c?Gte{39o|c^sIm5yalt8OSAm@5ymgPg+ zmV{cZ;2;MNex?Lh<{}qGrF_f`xrvoYsqvsYKtMxw46|_T1%ym>(tn3TFzgIDa774- zCh{vFXg&b1mPKB`hja*C`$i^)g8Y)yypqhsocQ9@ycF>9EyekzMaiiQF}FbXVV5(! zUIJQ)pO*@~Gl{~5O?Q#zLBMT4aP5UWb`4!o4ja9Obp1e`3h+2B^e9l|sAWQI8wd5G zj1Z@+7H1Td=4C^USS?CTE-fm~EK9|I%qk?nFOK*bWba4-vTldi^H827$?U>Bg{eXUc}llL`s87>!PC6 z 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

t3hI*Q3R0HAk82Q69YK`#0O!h7 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%0A&b_(Fl+rs5~FM(^;V67o2S!!97mUEHgOUJ^+OV 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

)f43n58FU;3D0hH5@yKGJ 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 z

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 z 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 g52`q{OsTBO}n^fVkU=#^7{-bcR4?Fwxk+ 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/install/helloworld_macos b/tests/fixtures/install/helloworld_macos new file mode 100755 index 0000000000000000000000000000000000000000..40e6ee515245014a961cf6fd0ab1176289cf9fce GIT binary patch literal 424240 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 Date: Sat, 10 Apr 2021 04:56:20 -0500 Subject: [PATCH 009/399] Sort: Various fixes and performance improvements (#2057) * Various fixes and performance improvements * fix a typo Co-authored-by: Michael Debertol Co-authored-by: Sylvestre Ledru Co-authored-by: Michael Debertol --- src/uu/sort/src/sort.rs | 114 ++++++++++++------ tests/.DS_Store | Bin 0 -> 6148 bytes tests/by-util/test_sort.rs | 49 ++++++-- tests/fixtures/.DS_Store | Bin 0 -> 6148 bytes ...ars_numeric_unique_reverse_stable.expected | 20 +++ .../fixtures/sort/multiple_decimals.expected | 33 +++++ .../sort/multiple_decimals_general.txt | 35 ++++++ .../sort/multiple_decimals_numeric.txt | 35 ++++++ 8 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 tests/.DS_Store create mode 100644 tests/fixtures/.DS_Store create mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected create mode 100644 tests/fixtures/sort/multiple_decimals.expected create mode 100644 tests/fixtures/sort/multiple_decimals_general.txt create mode 100644 tests/fixtures/sort/multiple_decimals_numeric.txt diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4e0e25d65..4aa3fbed2 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -22,6 +22,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; +use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; @@ -262,7 +263,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_CHECK_SILENT) .short("C") .long(OPT_CHECK_SILENT) - .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise. "), + .help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."), ) .arg( Arg::with_name(OPT_IGNORE_CASE) @@ -353,7 +354,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if let Ok(n) = line { files.push( std::str::from_utf8(&n) - .expect("Could not parse zero terminated string from input.") + .expect("Could not parse string from zero terminated input.") .to_string(), ); } @@ -488,6 +489,8 @@ fn exec(files: Vec, settings: &mut Settings) -> i32 { } else { print_sorted(file_merger, &settings) } + } else if settings.mode == SortMode::Default && settings.unique { + print_sorted(lines.iter().dedup(), &settings) } else if settings.mode == SortMode::Month && settings.unique { print_sorted( lines @@ -499,7 +502,7 @@ fn exec(files: Vec, settings: &mut Settings) -> i32 { print_sorted( lines .iter() - .dedup_by(|a, b| get_nums_dedup(a) == get_nums_dedup(b)), + .dedup_by(|a, b| get_num_dedup(a, &settings) == get_num_dedup(b, &settings)), &settings, ) } else { @@ -603,12 +606,13 @@ fn default_compare(a: &str, b: &str) -> Ordering { #[inline(always)] fn leading_num_common(a: &str) -> &str { let mut s = ""; + + // check whether char is numeric, whitespace or decimal point or thousand separator for (idx, c) in a.char_indices() { - // check whether char is numeric, whitespace or decimal point or thousand seperator if !c.is_numeric() && !c.is_whitespace() - && !c.eq(&DECIMAL_PT) && !c.eq(&THOUSANDS_SEP) + && !c.eq(&DECIMAL_PT) // check for e notation && !c.eq(&'e') && !c.eq(&'E') @@ -621,7 +625,7 @@ fn leading_num_common(a: &str) -> &str { break; } // If line is not a number line, return the line as is - s = a; + s = &a; } s } @@ -633,16 +637,17 @@ fn leading_num_common(a: &str) -> &str { // not recognize a positive sign or scientific/E notation so we strip those elements here. fn get_leading_num(a: &str) -> &str { let mut s = ""; - let b = leading_num_common(a); - // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip - for (idx, c) in b.char_indices() { - if c.eq(&'e') || c.eq(&'E') || b.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) { - s = &b[..idx]; + let a = leading_num_common(a); + + // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip trailing chars + for (idx, c) in a.char_indices() { + if c.eq(&'e') || c.eq(&'E') || a.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) { + s = &a[..idx]; break; } // If no further processing needed to be done, return the line as-is to be sorted - s = b; + s = &a; } // And empty number or non-number lines are to be treated as ‘0’ but only for numeric sort @@ -657,30 +662,32 @@ fn get_leading_num(a: &str) -> &str { // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. // For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000. -fn get_leading_gen(a: &str) -> String { +fn get_leading_gen(a: &str) -> &str { // Make this iter peekable to see if next char is numeric - let mut p_iter = leading_num_common(a).chars().peekable(); - let mut r = String::new(); + let raw_leading_num = leading_num_common(a); + let mut p_iter = raw_leading_num.chars().peekable(); + let mut result = ""; // Cleanup raw stripped strings for c in p_iter.to_owned() { let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric(); - // Only general numeric recognizes e notation and, see block below, the '+' sign - if (c.eq(&'e') && !next_char_numeric) || (c.eq(&'E') && !next_char_numeric) { - r = a.split(c).next().unwrap_or("").to_owned(); + // Only general numeric recognizes e notation and the '+' sign + if (c.eq(&'e') && !next_char_numeric) + || (c.eq(&'E') && !next_char_numeric) + // Only GNU (non-general) numeric recognize thousands seperators, takes only leading # + || c.eq(&THOUSANDS_SEP) + { + result = a.split(c).next().unwrap_or(""); break; // If positive sign and next char is not numeric, split at postive sign at keep trailing numbers // There is a more elegant way to do this in Rust 1.45, std::str::strip_prefix } else if c.eq(&POSITIVE) && !next_char_numeric { - let mut v: Vec<&str> = a.split(c).collect(); - let x = v.split_off(1); - r = x.join(""); + result = a.trim().trim_start_matches('+'); break; - // If no further processing needed to be done, return the line as-is to be sorted - } else { - r = a.to_owned(); } + // If no further processing needed to be done, return the line as-is to be sorted + result = a; } - r + result } fn get_months_dedup(a: &str) -> String { @@ -714,10 +721,10 @@ fn get_months_dedup(a: &str) -> String { } } -// *For all dedups/uniques we must compare leading numbers* +// *For all dedups/uniques expect default we must compare leading numbers* // Also note numeric compare and unique output is specifically *not* the same as a "sort | uniq" // See: https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html -fn get_nums_dedup(a: &str) -> &str { +fn get_num_dedup<'a>(a: &'a str, settings: &&mut Settings) -> &'a str { // Trim and remove any leading zeros let s = a.trim().trim_start_matches('0'); @@ -731,20 +738,50 @@ fn get_nums_dedup(a: &str) -> &str { "" // Prepare lines for comparison of only the numerical leading numbers } else { - get_leading_num(s) + let result = match settings.mode { + SortMode::Numeric => get_leading_num(s), + SortMode::GeneralNumeric => get_leading_gen(s), + SortMode::HumanNumeric => get_leading_num(s), + SortMode::Version => get_leading_num(s), + _ => s, + }; + result + } +} + +#[inline(always)] +fn remove_thousands_sep<'a, S: Into>>(input: S) -> Cow<'a, str> { + let input = input.into(); + if input.contains(THOUSANDS_SEP) { + let output = input.replace(THOUSANDS_SEP, ""); + Cow::Owned(output) + } else { + input + } +} + +#[inline(always)] +fn remove_trailing_dec<'a, S: Into>>(input: S) -> Cow<'a, str> { + let input = input.into(); + if let Some(s) = input.find(DECIMAL_PT) { + let (leading, trailing) = input.split_at(s); + let output = [leading, ".", trailing.replace(DECIMAL_PT, "").as_str()].concat(); + Cow::Owned(output) + } else { + input } } /// Parse the beginning string into an f64, returning -inf instead of NaN on errors. #[inline(always)] fn permissive_f64_parse(a: &str) -> f64 { - // Remove thousands seperators - let a = a.replace(THOUSANDS_SEP, ""); - // GNU sort treats "NaN" as non-number in numeric, so it needs special care. // *Keep this trim before parse* despite what POSIX may say about -b and -n // because GNU and BSD both seem to require it to match their behavior - match a.trim().parse::() { + // + // Remove any trailing decimals, ie 4568..890... becomes 4568.890 + // Then, we trim whitespace and parse + match remove_trailing_dec(a).trim().parse::() { Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, Ok(a) => a, Err(_) => std::f64::NEG_INFINITY, @@ -757,8 +794,13 @@ fn numeric_compare(a: &str, b: &str) -> Ordering { let sa = get_leading_num(a); let sb = get_leading_num(b); - let fa = permissive_f64_parse(sa); - let fb = permissive_f64_parse(sb); + // Avoids a string alloc for every line to remove thousands seperators here + // instead of inside the get_leading_num function, which is a HUGE performance benefit + let ta = remove_thousands_sep(sa); + let tb = remove_thousands_sep(sb); + + let fa = permissive_f64_parse(&ta); + let fb = permissive_f64_parse(&tb); // f64::cmp isn't implemented (due to NaN issues); implement directly instead if fa > fb { @@ -799,8 +841,8 @@ fn general_numeric_compare(a: &str, b: &str) -> Ordering { // these types of numbers, we rarely care about pure performance. fn human_numeric_convert(a: &str) -> f64 { let num_str = get_leading_num(a); - let suffix = a.trim_start_matches(num_str); - let num_part = permissive_f64_parse(num_str); + let suffix = a.trim_start_matches(&num_str); + let num_part = permissive_f64_parse(&num_str); let suffix: f64 = match suffix.parse().unwrap_or('\0') { // SI Units 'K' => 1E3, diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8z45|!R0Z1N%F(jFgL>QrFAPJ2!M?+vV1V%$(Gz3ON zU^D~25V%SxcdJP zRior+2#kinunYl47MEZbCs3t{!+W4QHvuXKVuPw;Mo^s$(F3lEVT}ML$bg~*R5_@+ b2Uo?6kTwK}57Iu`5P${HC_Nei0}uiLNUI8I literal 0 HcmV?d00001 diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 43aaf1da1..6455d837b 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1,5 +1,31 @@ use crate::common::util::*; +fn test_helper(file_name: &str, args: &str) { + new_ucmd!() + .arg(args) + .arg(format!("{}.txt", file_name)) + .succeeds() + .stdout_is_fixture(format!("{}.expected", file_name)); +} + +#[test] +fn test_multiple_decimals_general() { + new_ucmd!() + .arg("-g") + .arg("multiple_decimals_general.txt") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); +} + +#[test] +fn test_multiple_decimals_numeric() { + new_ucmd!() + .arg("-n") + .arg("multiple_decimals_numeric.txt") + .succeeds() + .stdout_is("-2028789030\n-896689\n-8.90880\n-1\n-.05\n\n\n\n\n\n\n\n\n000\nCARAvan\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n576,446.88800000\n576,446.890\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); +} + #[test] fn test_check_zero_terminated_failure() { new_ucmd!() @@ -44,6 +70,21 @@ fn test_random_shuffle_contains_all_lines() { assert_eq!(result_sorted, expected); } +#[test] +fn test_random_shuffle_two_runs_not_the_same() { + // check to verify that two random shuffles are not equal; this has the + // potential to fail in the very unlikely event that the random order is the same + // as the starting order, or if both random sorts end up having the same order. + const FILE: &'static str = "default_unsorted_ints.expected"; + let (at, _ucmd) = at_and_ucmd!(); + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let expected = at.read(FILE); + let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout; + + assert_ne!(result, expected); + assert_ne!(result, unexpected); +} + #[test] fn test_random_shuffle_contains_two_runs_not_the_same() { // check to verify that two random shuffles are not equal; this has the @@ -355,11 +396,3 @@ fn test_check_silent() { .fails() .stdout_is(""); } - -fn test_helper(file_name: &str, args: &str) { - new_ucmd!() - .arg(args) - .arg(format!("{}{}", file_name, ".txt")) - .succeeds() - .stdout_is_fixture(format!("{}{}", file_name, ".expected")); -} diff --git a/tests/fixtures/.DS_Store b/tests/fixtures/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..607a7386a75b94e7df52bcee971a48217dc92331 GIT binary patch literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8zOq>i@0Z1N%F(jFwA|RR(WJXeXaY0f}ei8!%!%3*z zC^fi402FsD48;uj3`Gnj$nlp{kds+lVqkEck%^gwm5rSP1b8`OgER8WgG&-iN{gKm zi=siifW(rFBq%#1KR*Y~PD~2ROf8QW5OL1WD@n}EODzH^56(kXDc!NGpg2X=Pvp zvB2_RtqhC|5Uq^hZU_SdBe+WfqQTl37#YCY85kMB+8JQ&JVuCi21bZ>21aNPg%Q-F z0htfc&cF!K4s+fpJsJX|Api{lW(X|+s{dUX7;yFfA*x2n(GVC7fngZ}j4Up}E>56I z6NmRebuFkqO@PXSYJX65%m}Kd5n|w~mEQChs K(GZ}22mk;)qfplX literal 0 HcmV?d00001 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected new file mode 100644 index 000000000..bbce16934 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected @@ -0,0 +1,20 @@ +4798908.8909800 +4798908.45 +4798908.340000000000 +576,446.890 +576,446.88800000 + 37800 + 4567. +46.89 +45 +8.013 +1.58590 +1.444 +1.040000000 +1 + +-.05 +-1 +-8.90880 +-896689 +-2028789030 diff --git a/tests/fixtures/sort/multiple_decimals.expected b/tests/fixtures/sort/multiple_decimals.expected new file mode 100644 index 000000000..6afbdcaa0 --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals.expected @@ -0,0 +1,33 @@ +-2028789030 +-896689 +-8.90880 +-1 +-.05 + + + + + + + + +000 +CARAvan +00000001 +1 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 + 4567..457 + 4567. +4567.1 +4567.34 + 37800 +576,446.88800000 +576,446.890 +4798908.340000000000 +4798908.45 +4798908.8909800 diff --git a/tests/fixtures/sort/multiple_decimals_general.txt b/tests/fixtures/sort/multiple_decimals_general.txt new file mode 100644 index 000000000..4e65ecfda --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_general.txt @@ -0,0 +1,35 @@ +576,446.890 +576,446.88800000 + +4567.1 + 4567..457 + 45670.89079.1 + 45670.89079.098 +4567.34 + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 + diff --git a/tests/fixtures/sort/multiple_decimals_numeric.txt b/tests/fixtures/sort/multiple_decimals_numeric.txt new file mode 100644 index 000000000..4e65ecfda --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_numeric.txt @@ -0,0 +1,35 @@ +576,446.890 +576,446.88800000 + +4567.1 + 4567..457 + 45670.89079.1 + 45670.89079.098 +4567.34 + 4567. +45 +46.89 +-1 +1 +00000001 +4798908.340000000000 +4798908.45 +4798908.8909800 + + + 37800 + +-2028789030 +-896689 +CARAvan + +-8.90880 +-.05 +1.444 +1.58590 +1.040000000 + +8.013 + +000 + From 49c9d8c9018eeeb2ae68a43f3763e087c44b1653 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 10 Apr 2021 14:54:58 +0200 Subject: [PATCH 010/399] sort: implement -k and -t support (#1996) * sort: implement basic -k and -t support This allows to specify keys after the -k flag and a custom field separator using -t. Support for options for specific keys is still missing, and the -b flag is not passed down correctly. * sort: implement support for key options * remove unstable feature use * don't pipe in input when we expect a failure * only tokenize when needed, remove a clone() * improve comments * fix clippy lints * re-add test * buffer writes to stdout * fix ignore_non_printing and make the test fail in case it is broken :) * move attribute to the right position * add more tests * add my name to the copyright section * disallow dead code * move a comment * re-add a loc * use smallvec for a perf improvement in the common case * add BENCHMARKING.md * add ignore_case to benchmarks --- Cargo.lock | 11 +- src/uu/sort/BENCHMARKING.md | 33 ++ src/uu/sort/Cargo.toml | 1 + src/uu/sort/src/sort.rs | 674 ++++++++++++++++++++++++++++-------- tests/by-util/test_sort.rs | 164 ++++++++- 5 files changed, 742 insertions(+), 141 deletions(-) create mode 100644 src/uu/sort/BENCHMARKING.md diff --git a/Cargo.lock b/Cargo.lock index 615c1a961..d2b2fa32e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "advapi32-sys" version = "0.2.0" @@ -1362,6 +1364,12 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + [[package]] name = "strsim" version = "0.8.0" @@ -1816,7 +1824,7 @@ dependencies = [ "quickcheck", "rand 0.7.3", "rand_chacha", - "smallvec", + "smallvec 0.6.14", "uucore", "uucore_procs", ] @@ -2289,6 +2297,7 @@ dependencies = [ "rand 0.7.3", "rayon", "semver", + "smallvec 1.6.1", "uucore", "uucore_procs", ] diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md new file mode 100644 index 000000000..b20db014d --- /dev/null +++ b/src/uu/sort/BENCHMARKING.md @@ -0,0 +1,33 @@ +# Benchmarking sort + +Most of the time when sorting is spent comparing lines. The comparison functions however differ based +on which arguments are passed to `sort`, therefore it is important to always benchmark multiple scenarios. +This is an overwiew over what was benchmarked, and if you make changes to `sort`, 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! + +## Sorting a wordlist +- Get a wordlist, for example with [words](https://en.wikipedia.org/wiki/Words_(Unix)) on Linux. The exact wordlist + doesn't matter for performance comparisons. In this example I'm using `/usr/share/dict/american-english` as the wordlist. +- Shuffle the wordlist by running `sort -R /usr/share/dict/american-english > shuffled_wordlist.txt`. +- Benchmark sorting the wordlist with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -o output.txt"`. + +## Sorting a wordlist with ignore_case +- Same wordlist as above +- Benchmark sorting the wordlist ignoring the case with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -f -o output.txt"`. + +## Sorting numbers +- Generate a list of numbers: `seq 0 100000 | sort -R > shuffled_numbers.txt`. +- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"`. + +## Stdout and stdin performance +Try to run the above benchmarks by piping the input through stdin (standard input) and redirect the +output through stdout (standard output): +- Remove the input file from the arguments and add `cat [inputfile] | ` at the beginning. +- Remove `-o output.txt` and add `> output.txt` at the end. + +Example: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"` becomes +`hyperfine "cat shuffled_numbers.txt | target/release/coreutils sort -n > output.txt` +- Check that performance is similar to the original benchmark. \ No newline at end of file diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 814e4bbba..96e88ebc9 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -21,6 +21,7 @@ clap = "2.33" fnv = "1.0.7" itertools = "0.8.0" semver = "0.9.0" +smallvec = "1.6.1" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4aa3fbed2..4f669f578 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2,10 +2,10 @@ // * // * (c) Michael Yin // * (c) Robert Swinford +// * (c) Michael Debertol // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -#![allow(dead_code)] // Although these links don't always seem to describe reality, check out the POSIX and GNU specs: // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html @@ -22,6 +22,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; +use smallvec::SmallVec; use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BinaryHeap; @@ -30,6 +31,7 @@ use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; use std::mem::replace; +use std::ops::{Range, RangeInclusive}; use std::path::Path; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() @@ -37,6 +39,16 @@ static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; static VERSION: &str = env!("CARGO_PKG_VERSION"); +const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. + +Fields by default are separated by the first whitespace after a non-whitespace character. Use -t to specify a custom separator. +In the default case, whitespace is appended at the beginning of each field. Custom separators however are not included in fields. + +FIELD and CHAR both start at 1 (i.e. they are 1-indexed). If there is no end specified after a comma, the end will be the end of the line. +If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the start position and to 0 for the end position. + +Valid options are: MbdfhnRrV. They override the global options for this key."; + static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort"; static OPT_MONTH_SORT: &str = "month-sort"; static OPT_NUMERIC_SORT: &str = "numeric-sort"; @@ -54,6 +66,8 @@ static OPT_OUTPUT: &str = "output"; static OPT_REVERSE: &str = "reverse"; static OPT_STABLE: &str = "stable"; static OPT_UNIQUE: &str = "unique"; +static OPT_KEY: &str = "key"; +static OPT_SEPARATOR: &str = "field-separator"; static OPT_RANDOM: &str = "random-sort"; static OPT_ZERO_TERMINATED: &str = "zero-terminated"; static OPT_PARALLEL: &str = "parallel"; @@ -63,10 +77,11 @@ static ARG_FILES: &str = "files"; static DECIMAL_PT: char = '.'; static THOUSANDS_SEP: char = ','; + static NEGATIVE: char = '-'; static POSITIVE: char = '+'; -#[derive(Eq, Ord, PartialEq, PartialOrd)] +#[derive(Eq, Ord, PartialEq, PartialOrd, Clone)] enum SortMode { Numeric, HumanNumeric, @@ -76,8 +91,12 @@ enum SortMode { Default, } -struct Settings { +struct GlobalSettings { mode: SortMode, + ignore_blanks: bool, + ignore_case: bool, + dictionary_order: bool, + ignore_non_printing: bool, merge: bool, reverse: bool, outfile: Option, @@ -86,17 +105,21 @@ struct Settings { check: bool, check_silent: bool, random: bool, - compare_fn: fn(&str, &str) -> Ordering, - transform_fns: Vec String>, - threads: String, salt: String, + selectors: Vec, + separator: Option, + threads: String, zero_terminated: bool, } -impl Default for Settings { - fn default() -> Settings { - Settings { +impl Default for GlobalSettings { + fn default() -> GlobalSettings { + GlobalSettings { mode: SortMode::Default, + ignore_blanks: false, + ignore_case: false, + dictionary_order: false, + ignore_non_printing: false, merge: false, reverse: false, outfile: None, @@ -105,19 +128,330 @@ impl Default for Settings { check: false, check_silent: false, random: false, - compare_fn: default_compare, - transform_fns: Vec::new(), - threads: String::new(), salt: String::new(), + selectors: vec![], + separator: None, + threads: String::new(), zero_terminated: false, } } } +struct KeySettings { + mode: SortMode, + ignore_blanks: bool, + ignore_case: bool, + dictionary_order: bool, + ignore_non_printing: bool, + random: bool, + reverse: bool, +} + +impl From<&GlobalSettings> for KeySettings { + fn from(settings: &GlobalSettings) -> Self { + Self { + mode: settings.mode.clone(), + ignore_blanks: settings.ignore_blanks, + ignore_case: settings.ignore_case, + ignore_non_printing: settings.ignore_non_printing, + random: settings.random, + reverse: settings.reverse, + dictionary_order: settings.dictionary_order, + } + } +} + +/// Represents the string selected by a FieldSelector. +enum Selection { + /// If we had to transform this selection, we have to store a new string. + String(String), + /// If there was no transformation, we can store an index into the line. + ByIndex(Range), +} + +impl Selection { + /// Gets the actual string slice represented by this Selection. + fn get_str<'a>(&'a self, line: &'a Line) -> &'a str { + match self { + Selection::String(string) => string.as_str(), + Selection::ByIndex(range) => &line.line[range.to_owned()], + } + } +} + +type Field = Range; + +struct Line { + line: String, + // The common case is not to specify fields. Let's make this fast. + selections: SmallVec<[Selection; 1]>, +} + +impl Line { + fn new(line: String, settings: &GlobalSettings) -> Self { + let fields = if settings + .selectors + .iter() + .any(|selector| selector.needs_tokens()) + { + // Only tokenize if we will need tokens. + Some(tokenize(&line, settings.separator)) + } else { + None + }; + + let selections = settings + .selectors + .iter() + .map(|selector| { + if let Some(range) = selector.get_selection(&line, fields.as_deref()) { + if let Some(transformed) = + transform(&line[range.to_owned()], &selector.settings) + { + Selection::String(transformed) + } else { + Selection::ByIndex(range.start().to_owned()..range.end() + 1) + } + } else { + // If there is no match, match the empty string. + Selection::ByIndex(0..0) + } + }) + .collect(); + Self { line, selections } + } +} + +/// Transform this line. Returns None if there's no need to transform. +fn transform(line: &str, settings: &KeySettings) -> Option { + let mut transformed = None; + if settings.ignore_case { + transformed = Some(line.to_uppercase()); + } + if settings.ignore_blanks { + transformed = Some( + transformed + .as_deref() + .unwrap_or(line) + .trim_start() + .to_string(), + ); + } + if settings.dictionary_order { + transformed = Some(remove_nondictionary_chars( + transformed.as_deref().unwrap_or(line), + )); + } + if settings.ignore_non_printing { + transformed = Some(remove_nonprinting_chars( + transformed.as_deref().unwrap_or(line), + )); + } + transformed +} + +/// Tokenize a line into fields. +fn tokenize(line: &str, separator: Option) -> Vec { + if let Some(separator) = separator { + tokenize_with_separator(line, separator) + } else { + tokenize_default(line) + } +} + +/// By default fields are separated by the first whitespace after non-whitespace. +/// Whitespace is included in fields at the start. +fn tokenize_default(line: &str) -> Vec { + let mut tokens = vec![0..0]; + // pretend that there was whitespace in front of the line + let mut previous_was_whitespace = true; + for (idx, char) in line.char_indices() { + if char.is_whitespace() { + if !previous_was_whitespace { + tokens.last_mut().unwrap().end = idx; + tokens.push(idx..0); + } + previous_was_whitespace = true; + } else { + previous_was_whitespace = false; + } + } + tokens.last_mut().unwrap().end = line.len(); + tokens +} + +/// 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; + } + } + tokens.last_mut().unwrap().end = line.len(); + tokens +} + +struct KeyPosition { + /// 1-indexed, 0 is invalid. + field: usize, + /// 1-indexed, 0 is end of field. + char: usize, + ignore_blanks: bool, +} + +impl KeyPosition { + fn parse(key: &str, default_char_index: usize, settings: &mut KeySettings) -> Self { + let mut field_and_char = key.split('.'); + let mut field = field_and_char + .next() + .unwrap_or_else(|| crash!(1, "invalid key `{}`", key)); + let mut char = field_and_char.next(); + + // If there is a char index, we expect options to appear after it. Otherwise we expect them after the field index. + let value_with_options = char.as_mut().unwrap_or(&mut field); + + let mut ignore_blanks = settings.ignore_blanks; + if let Some(options_start) = value_with_options.chars().position(char::is_alphabetic) { + for option in value_with_options[options_start..].chars() { + // valid options: MbdfghinRrV + match option { + 'M' => settings.mode = SortMode::Month, + 'b' => ignore_blanks = true, + 'd' => settings.dictionary_order = true, + 'f' => settings.ignore_case = true, + 'g' => settings.mode = SortMode::GeneralNumeric, + 'h' => settings.mode = SortMode::HumanNumeric, + 'i' => settings.ignore_non_printing = true, + 'n' => settings.mode = SortMode::Numeric, + 'R' => settings.random = true, + 'r' => settings.reverse = true, + 'V' => settings.mode = SortMode::Version, + c => { + crash!(1, "invalid option for key: `{}`", c) + } + } + } + // Strip away option characters from the original value so we can parse it later + *value_with_options = &value_with_options[..options_start]; + } + + let field = field + .parse() + .unwrap_or_else(|e| crash!(1, "failed to parse field index for key `{}`: {}", key, e)); + if field == 0 { + crash!(1, "field index was 0"); + } + let char = char.map_or(default_char_index, |char| { + char.parse().unwrap_or_else(|e| { + crash!( + 1, + "failed to parse character index for key `{}`: {}", + key, + e + ) + }) + }); + Self { + field, + char, + ignore_blanks, + } + } +} + +struct FieldSelector { + from: KeyPosition, + to: Option, + settings: KeySettings, +} + +impl FieldSelector { + fn needs_tokens(&self) -> bool { + self.from.field != 1 || self.from.char == 0 || self.to.is_some() + } + + /// Look up the slice that corresponds to this selector for the given line. + /// If needs_fields returned false, fields may be None. + fn get_selection<'a>( + &self, + line: &'a str, + tokens: Option<&[Field]>, + ) -> Option> { + enum ResolutionErr { + TooLow, + TooHigh, + } + + // Get the index for this line given the KeyPosition + fn resolve_index( + line: &str, + tokens: Option<&[Field]>, + position: &KeyPosition, + ) -> Result { + if tokens.map_or(false, |fields| fields.len() < position.field) { + Err(ResolutionErr::TooHigh) + } else if position.char == 0 { + let end = tokens.unwrap()[position.field - 1].end; + if end == 0 { + Err(ResolutionErr::TooLow) + } else { + Ok(end - 1) + } + } else { + let mut idx = if position.field == 1 { + // The first field always starts at 0. + // We don't need tokens for this case. + 0 + } else { + tokens.unwrap()[position.field - 1].start + } + position.char + - 1; + if idx >= line.len() { + Err(ResolutionErr::TooHigh) + } else { + if position.ignore_blanks { + if let Some(not_whitespace) = + line[idx..].chars().position(|c| !c.is_whitespace()) + { + idx += not_whitespace; + } else { + return Err(ResolutionErr::TooHigh); + } + } + Ok(idx) + } + } + } + + if let Ok(from) = resolve_index(line, tokens, &self.from) { + let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to)); + match to { + Some(Ok(to)) => Some(from..=to), + // If `to` was not given or the match would be after the end of the line, + // match everything until the end of the line. + None | Some(Err(ResolutionErr::TooHigh)) => Some(from..=line.len() - 1), + // If `to` is before the start of the line, report no match. + // This can happen if the line starts with a separator. + Some(Err(ResolutionErr::TooLow)) => None, + } + } else { + None + } + } +} + struct MergeableFile<'a> { lines: Lines>>, - current_line: String, - settings: &'a Settings, + current_line: Line, + settings: &'a GlobalSettings, } // BinaryHeap depends on `Ord`. Note that we want to pop smallest items @@ -125,7 +459,7 @@ struct MergeableFile<'a> { // trick it into the right order by calling reverse() here. impl<'a> Ord for MergeableFile<'a> { fn cmp(&self, other: &MergeableFile) -> Ordering { - compare_by(&self.current_line, &other.current_line, &self.settings).reverse() + compare_by(&self.current_line, &other.current_line, self.settings).reverse() } } @@ -137,7 +471,7 @@ impl<'a> PartialOrd for MergeableFile<'a> { impl<'a> PartialEq for MergeableFile<'a> { fn eq(&self, other: &MergeableFile) -> bool { - Ordering::Equal == compare_by(&self.current_line, &other.current_line, &self.settings) + Ordering::Equal == compare_by(&self.current_line, &other.current_line, self.settings) } } @@ -145,11 +479,11 @@ impl<'a> Eq for MergeableFile<'a> {} struct FileMerger<'a> { heap: BinaryHeap>, - settings: &'a Settings, + settings: &'a GlobalSettings, } impl<'a> FileMerger<'a> { - fn new(settings: &'a Settings) -> FileMerger<'a> { + fn new(settings: &'a GlobalSettings) -> FileMerger<'a> { FileMerger { heap: BinaryHeap::new(), settings, @@ -159,7 +493,7 @@ impl<'a> FileMerger<'a> { if let Some(Ok(next_line)) = lines.next() { let mergeable_file = MergeableFile { lines, - current_line: next_line, + current_line: Line::new(next_line, &self.settings), settings: &self.settings, }; self.heap.push(mergeable_file); @@ -174,14 +508,17 @@ impl<'a> Iterator for FileMerger<'a> { Some(mut current) => { match current.lines.next() { Some(Ok(next_line)) => { - let ret = replace(&mut current.current_line, next_line); + let ret = replace( + &mut current.current_line, + Line::new(next_line, &self.settings), + ); self.heap.push(current); - Some(ret) + Some(ret.line) } _ => { // Don't put it back in the heap (it's empty/erroring) // but its first line is still valid. - Some(current.current_line) + Some(current.current_line.line) } } } @@ -205,7 +542,7 @@ With no FILE, or when FILE is -, read standard input.", pub fn uumain(args: impl uucore::Args) -> i32 { let args = args.collect_str(); let usage = get_usage(); - let mut settings: Settings = Default::default(); + let mut settings: GlobalSettings = Default::default(); let matches = App::new(executable!()) .version(VERSION) @@ -316,7 +653,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("output only the first of an equal run"), ) .arg( - Arg::with_name(OPT_ZERO_TERMINATED) + Arg::with_name(OPT_KEY) + .short("k") + .long(OPT_KEY) + .help("sort by a key") + .long_help(LONG_HELP_KEYS) + .multiple(true) + .takes_value(true), + ) + .arg( + Arg::with_name(OPT_SEPARATOR) + .short("t") + .long(OPT_SEPARATOR) + .help("custom separator for -k") + .takes_value(true)) + .arg(Arg::with_name(OPT_ZERO_TERMINATED) .short("z") .long(OPT_ZERO_TERMINATED) .help("line delimiter is NUL, not newline"), @@ -350,14 +701,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for path in &files0_from { let (reader, _) = open(path.as_str()).expect("Could not read from file specified."); let buf_reader = BufReader::new(reader); - for line in buf_reader.split(b'\0') { - if let Ok(n) = line { - files.push( - std::str::from_utf8(&n) - .expect("Could not parse string from zero terminated input.") - .to_string(), - ); - } + for line in buf_reader.split(b'\0').flatten() { + files.push( + std::str::from_utf8(&line) + .expect("Could not parse string from zero terminated input.") + .to_string(), + ); } } files @@ -382,21 +731,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { SortMode::Default }; + settings.dictionary_order = matches.is_present(OPT_DICTIONARY_ORDER); + settings.ignore_non_printing = matches.is_present(OPT_IGNORE_NONPRINTING); if matches.is_present(OPT_PARALLEL) { // "0" is default - threads = num of cores settings.threads = matches .value_of(OPT_PARALLEL) .map(String::from) - .unwrap_or("0".to_string()); + .unwrap_or_else(|| "0".to_string()); env::set_var("RAYON_NUM_THREADS", &settings.threads); } - if matches.is_present(OPT_DICTIONARY_ORDER) { - settings.transform_fns.push(remove_nondictionary_chars); - } else if matches.is_present(OPT_IGNORE_NONPRINTING) { - settings.transform_fns.push(remove_nonprinting_chars); - } - settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); settings.merge = matches.is_present(OPT_MERGE); @@ -406,13 +751,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.check = true; }; - if matches.is_present(OPT_IGNORE_CASE) { - settings.transform_fns.push(|s| s.to_uppercase()); - } + settings.ignore_case = matches.is_present(OPT_IGNORE_CASE); - if matches.is_present(OPT_IGNORE_BLANKS) { - settings.transform_fns.push(|s| s.trim_start().to_string()); - } + settings.ignore_blanks = matches.is_present(OPT_IGNORE_BLANKS); settings.outfile = matches.value_of(OPT_OUTPUT).map(String::from); settings.reverse = matches.is_present(OPT_REVERSE); @@ -424,27 +765,64 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.salt = get_rand_string(); } - //let mut files = matches.free; if files.is_empty() { /* if no file, default to stdin */ files.push("-".to_owned()); } else if settings.check && files.len() != 1 { - crash!(1, "sort: extra operand `{}' not allowed with -c", files[1]) + crash!(1, "extra operand `{}' not allowed with -c", files[1]) } - settings.compare_fn = match settings.mode { - SortMode::Numeric => numeric_compare, - SortMode::GeneralNumeric => general_numeric_compare, - SortMode::HumanNumeric => human_numeric_size_compare, - SortMode::Month => month_compare, - SortMode::Version => version_compare, - SortMode::Default => default_compare, - }; + if let Some(arg) = matches.args.get(OPT_SEPARATOR) { + let separator = arg.vals[0].to_string_lossy(); + let separator = separator; + if separator.len() != 1 { + crash!(1, "separator must be exactly one character long"); + } + settings.separator = Some(separator.chars().next().unwrap()) + } - exec(files, &mut settings) + if matches.is_present(OPT_KEY) { + for key in &matches.args[OPT_KEY].vals { + let key = key.to_string_lossy(); + let mut from_to = key.split(','); + let mut key_settings = KeySettings::from(&settings); + let from = KeyPosition::parse( + from_to + .next() + .unwrap_or_else(|| crash!(1, "invalid key `{}`", key)), + 1, + &mut key_settings, + ); + let to = from_to + .next() + .map(|to| KeyPosition::parse(to, 0, &mut key_settings)); + let field_selector = FieldSelector { + from, + to, + settings: key_settings, + }; + settings.selectors.push(field_selector); + } + } + + if !settings.stable || !matches.is_present(OPT_KEY) { + // add a default selector matching the whole line + let key_settings = KeySettings::from(&settings); + settings.selectors.push(FieldSelector { + from: KeyPosition { + field: 1, + char: 1, + ignore_blanks: key_settings.ignore_blanks, + }, + to: None, + settings: key_settings, + }); + } + + exec(files, &settings) } -fn exec(files: Vec, settings: &mut Settings) -> i32 { +fn exec(files: Vec, settings: &GlobalSettings) -> i32 { let mut lines = Vec::new(); let mut file_merger = FileMerger::new(&settings); @@ -459,26 +837,27 @@ fn exec(files: Vec, settings: &mut Settings) -> i32 { if settings.merge { file_merger.push_file(buf_reader.lines()); } else if settings.zero_terminated { - for line in buf_reader.split(b'\0') { - if let Ok(n) = line { - lines.push( - std::str::from_utf8(&n) - .expect("Could not parse string from zero terminated input.") - .to_string(), - ); - } + for line in buf_reader.split(b'\0').flatten() { + lines.push(Line::new( + std::str::from_utf8(&line) + .expect("Could not parse string from zero terminated input.") + .to_string(), + &settings, + )); } } else { for line in buf_reader.lines() { if let Ok(n) = line { - lines.push(n); + lines.push(Line::new(n, &settings)); + } else { + break; } } } } if settings.check { - return exec_check_file(lines, &settings); + return exec_check_file(&lines, &settings); } else { sort_by(&mut lines, &settings); } @@ -490,29 +869,31 @@ fn exec(files: Vec, settings: &mut Settings) -> i32 { print_sorted(file_merger, &settings) } } else if settings.mode == SortMode::Default && settings.unique { - print_sorted(lines.iter().dedup(), &settings) + print_sorted(lines.into_iter().map(|line| line.line).dedup(), &settings) } else if settings.mode == SortMode::Month && settings.unique { print_sorted( lines - .iter() + .into_iter() + .map(|line| line.line) .dedup_by(|a, b| get_months_dedup(a) == get_months_dedup(b)), &settings, ) } else if settings.unique { print_sorted( lines - .iter() - .dedup_by(|a, b| get_num_dedup(a, &settings) == get_num_dedup(b, &settings)), + .into_iter() + .map(|line| line.line) + .dedup_by(|a, b| get_num_dedup(a, settings) == get_num_dedup(b, settings)), &settings, ) } else { - print_sorted(lines.iter(), &settings) + print_sorted(lines.into_iter().map(|line| line.line), &settings) } 0 } -fn exec_check_file(unwrapped_lines: Vec, settings: &Settings) -> i32 { +fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { // errors yields the line before each disorder, // plus the last line (quirk of .coalesce()) let mut errors = @@ -544,51 +925,45 @@ fn exec_check_file(unwrapped_lines: Vec, settings: &Settings) -> i32 { } } -#[inline(always)] -fn transform(line: &str, settings: &Settings) -> String { - let mut transformed = line.to_owned(); - for transform_fn in &settings.transform_fns { - transformed = transform_fn(&transformed); - } - - transformed -} - -#[inline(always)] -fn sort_by(lines: &mut Vec, settings: &Settings) { +fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { lines.par_sort_by(|a, b| compare_by(a, b, &settings)) } -fn compare_by(a: &str, b: &str, settings: &Settings) -> Ordering { - let (a_transformed, b_transformed): (String, String); - let (a, b) = if !settings.transform_fns.is_empty() { - a_transformed = transform(&a, &settings); - b_transformed = transform(&b, &settings); - (a_transformed.as_str(), b_transformed.as_str()) - } else { - (a, b) - }; +fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { + for (idx, selector) in global_settings.selectors.iter().enumerate() { + let a = a.selections[idx].get_str(a); + let b = b.selections[idx].get_str(b); + let settings = &selector.settings; - // 1st Compare - let mut cmp: Ordering = if settings.random { - random_shuffle(a, b, settings.salt.clone()) - } else { - (settings.compare_fn)(a, b) - }; - - // Call "last resort compare" on any equal - if cmp == Ordering::Equal { - if settings.random || settings.stable || settings.unique { - cmp = Ordering::Equal + let cmp: Ordering = if settings.random { + random_shuffle(a, b, global_settings.salt.clone()) } else { - cmp = default_compare(a, b) + (match settings.mode { + SortMode::Numeric => numeric_compare, + SortMode::GeneralNumeric => general_numeric_compare, + SortMode::HumanNumeric => human_numeric_size_compare, + SortMode::Month => month_compare, + SortMode::Version => version_compare, + SortMode::Default => default_compare, + })(a, b) }; + if cmp != Ordering::Equal { + return if settings.reverse { cmp.reverse() } else { cmp }; + } + } + + // Call "last resort compare" if all selectors returned Equal + + let cmp = if global_settings.random || global_settings.stable || global_settings.unique { + Ordering::Equal + } else { + default_compare(&a.line, &b.line) }; - if settings.reverse { - return cmp.reverse(); + if global_settings.reverse { + cmp.reverse() } else { - return cmp; + cmp } } @@ -617,8 +992,8 @@ fn leading_num_common(a: &str) -> &str { && !c.eq(&'e') && !c.eq(&'E') // check whether first char is + or - - && !a.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) - && !a.chars().nth(0).unwrap_or('\0').eq(&NEGATIVE) + && !a.chars().next().unwrap_or('\0').eq(&POSITIVE) + && !a.chars().next().unwrap_or('\0').eq(&NEGATIVE) { // Strip string of non-numeric trailing chars s = &a[..idx]; @@ -640,9 +1015,9 @@ fn get_leading_num(a: &str) -> &str { let a = leading_num_common(a); - // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip trailing chars + // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip for (idx, c) in a.char_indices() { - if c.eq(&'e') || c.eq(&'E') || a.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) { + if c.eq(&'e') || c.eq(&'E') || a.chars().next().unwrap_or('\0').eq(&POSITIVE) { s = &a[..idx]; break; } @@ -670,12 +1045,9 @@ fn get_leading_gen(a: &str) -> &str { // Cleanup raw stripped strings for c in p_iter.to_owned() { let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric(); - // Only general numeric recognizes e notation and the '+' sign - if (c.eq(&'e') && !next_char_numeric) - || (c.eq(&'E') && !next_char_numeric) - // Only GNU (non-general) numeric recognize thousands seperators, takes only leading # - || c.eq(&THOUSANDS_SEP) - { + // Only general numeric recognizes e notation and, see block below, the '+' sign + // Only GNU (non-general) numeric recognize thousands seperators, takes only leading # + if (c.eq(&'e') || c.eq(&'E')) && !next_char_numeric || c.eq(&THOUSANDS_SEP) { result = a.split(c).next().unwrap_or(""); break; // If positive sign and next char is not numeric, split at postive sign at keep trailing numbers @@ -724,19 +1096,17 @@ fn get_months_dedup(a: &str) -> String { // *For all dedups/uniques expect default we must compare leading numbers* // Also note numeric compare and unique output is specifically *not* the same as a "sort | uniq" // See: https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html -fn get_num_dedup<'a>(a: &'a str, settings: &&mut Settings) -> &'a str { +fn get_num_dedup<'a>(a: &'a str, settings: &GlobalSettings) -> &'a str { // Trim and remove any leading zeros let s = a.trim().trim_start_matches('0'); // Get first char - let c = s.chars().nth(0).unwrap_or('\0'); + let c = s.chars().next().unwrap_or('\0'); // Empty lines and non-number lines are treated as the same for dedup - if s.is_empty() { - "" - } else if !c.eq(&NEGATIVE) && !c.is_numeric() { - "" // Prepare lines for comparison of only the numerical leading numbers + if s.is_empty() || (!c.eq(&NEGATIVE) && !c.is_numeric()) { + "" } else { let result = match settings.mode { SortMode::Numeric => get_leading_num(s), @@ -794,7 +1164,7 @@ fn numeric_compare(a: &str, b: &str) -> Ordering { let sa = get_leading_num(a); let sb = get_leading_num(b); - // Avoids a string alloc for every line to remove thousands seperators here + // Avoids a string alloc for every line to remove thousands seperators here // instead of inside the get_leading_num function, which is a HUGE performance benefit let ta = remove_thousands_sep(sa); let tb = remove_thousands_sep(sb); @@ -944,6 +1314,7 @@ fn month_parse(line: &str) -> Month { } fn month_compare(a: &str, b: &str) -> Ordering { + #![allow(clippy::comparison_chain)] let ma = month_parse(a); let mb = month_parse(b); @@ -986,32 +1357,29 @@ fn remove_nonprinting_chars(s: &str) -> String { .collect::() } -fn print_sorted>(iter: T, settings: &Settings) -where - S: std::fmt::Display, -{ +fn print_sorted>(iter: T, settings: &GlobalSettings) { let mut file: Box = match settings.outfile { Some(ref filename) => match File::create(Path::new(&filename)) { Ok(f) => Box::new(BufWriter::new(f)) as Box, Err(e) => { - show_error!("sort: {0}: {1}", filename, e.to_string()); + show_error!("{0}: {1}", filename, e.to_string()); panic!("Could not open output file"); } }, - None => Box::new(stdout()) as Box, + None => Box::new(BufWriter::new(stdout())) as Box, }; - if settings.zero_terminated { for line in iter { - let str = format!("{}\0", line); - crash_if_err!(1, file.write_all(str.as_bytes())); + crash_if_err!(1, file.write_all(line.as_bytes())); + crash_if_err!(1, file.write_all("\0".as_bytes())); } } else { for line in iter { - let str = format!("{}\n", line); - crash_if_err!(1, file.write_all(str.as_bytes())); + crash_if_err!(1, file.write_all(line.as_bytes())); + crash_if_err!(1, file.write_all("\n".as_bytes())); } } + crash_if_err!(1, file.flush()); } // from cat.rs @@ -1024,7 +1392,7 @@ fn open(path: &str) -> Option<(Box, bool)> { match File::open(Path::new(path)) { Ok(f) => Some((Box::new(f) as Box, false)), Err(e) => { - show_error!("sort: {0}: {1}", path, e.to_string()); + show_error!("{0}: {1}", path, e.to_string()); None } } @@ -1097,4 +1465,34 @@ mod tests { assert_eq!(Ordering::Less, version_compare(a, b)); } + + #[test] + fn test_random_compare() { + let a = "9"; + let b = "9"; + let c = get_rand_string(); + + assert_eq!(Ordering::Equal, random_shuffle(a, b, c)); + } + + #[test] + fn test_tokenize_fields() { + let line = "foo bar b x"; + assert_eq!(tokenize(line, None), vec![0..3, 3..7, 7..9, 9..14,],); + } + + #[test] + fn test_tokenize_fields_leading_whitespace() { + let line = " foo bar b x"; + assert_eq!(tokenize(line, None), vec![0..7, 7..11, 11..13, 13..18,]); + } + + #[test] + fn test_tokenize_fields_custom_separator() { + let line = "aaa foo bar b x"; + assert_eq!( + tokenize(line, Some('a')), + vec![0..0, 1..1, 2..2, 3..9, 10..18,] + ); + } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 6455d837b..668e783ae 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -185,10 +185,10 @@ fn test_dictionary_order2() { fn test_non_printing_chars() { for non_printing_chars_param in vec!["-i"] { new_ucmd!() - .pipe_in("a👦🏻aa b\naaaa b") + .pipe_in("a👦🏻aa\naaaa") .arg(non_printing_chars_param) .succeeds() - .stdout_only("aaaa b\na👦🏻aa b\n"); + .stdout_only("a👦🏻aa\naaaa\n"); } } @@ -307,6 +307,166 @@ fn test_numeric_unique_ints2() { } } +#[test] +fn test_keys_open_ended() { + let input = "aa bb cc\ndd aa ff\ngg aa cc\n"; + new_ucmd!() + .args(&["-k", "2.2"]) + .pipe_in(input) + .succeeds() + .stdout_only("gg aa cc\ndd aa ff\naa bb cc\n"); +} + +#[test] +fn test_keys_closed_range() { + let input = "aa bb cc\ndd aa ff\ngg aa cc\n"; + new_ucmd!() + .args(&["-k", "2.2,2.2"]) + .pipe_in(input) + .succeeds() + .stdout_only("dd aa ff\ngg aa cc\naa bb cc\n"); +} + +#[test] +fn test_keys_multiple_ranges() { + let input = "aa bb cc\ndd aa ff\ngg aa cc\n"; + new_ucmd!() + .args(&["-k", "2,2", "-k", "3,3"]) + .pipe_in(input) + .succeeds() + .stdout_only("gg aa cc\ndd aa ff\naa bb cc\n"); +} + +#[test] +fn test_keys_no_field_match() { + let input = "aa aa aa aa\naa bb cc\ndd aa ff\n"; + new_ucmd!() + .args(&["-k", "4,4"]) + .pipe_in(input) + .succeeds() + .stdout_only("aa bb cc\ndd aa ff\naa aa aa aa\n"); +} + +#[test] +fn test_keys_no_char_match() { + let input = "aaa\nba\nc\n"; + new_ucmd!() + .args(&["-k", "1.2"]) + .pipe_in(input) + .succeeds() + .stdout_only("c\nba\naaa\n"); +} + +#[test] +fn test_keys_custom_separator() { + let input = "aaxbbxcc\nddxaaxff\nggxaaxcc\n"; + new_ucmd!() + .args(&["-k", "2.2,2.2", "-t", "x"]) + .pipe_in(input) + .succeeds() + .stdout_only("ddxaaxff\nggxaaxcc\naaxbbxcc\n"); +} + +#[test] +fn test_keys_invalid_field() { + new_ucmd!() + .args(&["-k", "1."]) + .fails() + .stderr_only("sort: error: failed to parse character index for key `1.`: cannot parse integer from empty string"); +} + +#[test] +fn test_keys_invalid_field_option() { + new_ucmd!() + .args(&["-k", "1.1x"]) + .fails() + .stderr_only("sort: error: invalid option for key: `x`"); +} + +#[test] +fn test_keys_invalid_field_zero() { + new_ucmd!() + .args(&["-k", "0.1"]) + .fails() + .stderr_only("sort: error: field index was 0"); +} + +#[test] +fn test_keys_with_options() { + let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n"; + for param in &[ + &["-k", "2,2n"][..], + &["-k", "2n,2"][..], + &["-k", "2,2", "-n"][..], + ] { + new_ucmd!() + .args(param) + .pipe_in(input) + .succeeds() + .stdout_only("dd 1 ff\ngg 2 cc\naa 3 cc\n"); + } +} + +#[test] +fn test_keys_with_options_blanks_start() { + let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n"; + for param in &[&["-k", "2b,2"][..], &["-k", "2,2", "-b"][..]] { + new_ucmd!() + .args(param) + .pipe_in(input) + .succeeds() + .stdout_only("dd 1 ff\ngg 2 cc\naa 3 cc\n"); + } +} + +#[test] +fn test_keys_with_options_blanks_end() { + let input = "a b +a b +a b +"; + new_ucmd!() + .args(&["-k", "1,2.1b", "-s"]) + .pipe_in(input) + .succeeds() + .stdout_only( + "a b +a b +a b +", + ); +} + +#[test] +fn test_keys_stable() { + let input = "a b +a b +a b +"; + new_ucmd!() + .args(&["-k", "1,2.1", "-s"]) + .pipe_in(input) + .succeeds() + .stdout_only( + "a b +a b +a b +", + ); +} + +#[test] +fn test_keys_empty_match() { + let input = "a a a a +aaaa +"; + new_ucmd!() + .args(&["-k", "1,1", "-t", "a"]) + .pipe_in(input) + .succeeds() + .stdout_only(input); +} + #[test] fn test_zero_terminated() { test_helper("zero-terminated", "-z"); From 2d9f15d12cca22d2a397a7de851b6b768ebc88fe Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 10 Apr 2021 12:02:02 -0500 Subject: [PATCH 011/399] Fix month parse for months with leading whitespace --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4f669f578..f60302622 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1291,7 +1291,7 @@ fn month_parse(line: &str) -> Month { // GNU splits at any 3 letter match "JUNNNN" is JUN let pattern = if line.trim().len().ge(&3) { // Split a 3 and get first element of tuple ".0" - line.split_at(3).0 + line.trim().split_at(3).0 } else { "" }; From 69f4410a8a7460e9fd50c570e569770305ce1b49 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 10 Apr 2021 19:49:10 +0200 Subject: [PATCH 012/399] sort: dedup using compare_by (#2064) compare_by is the function used for sorting, we should use it for dedup as well. --- Cargo.lock | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/sort/src/sort.rs | 73 ++------------------- tests/by-util/test_sort.rs | 10 +++ tests/fixtures/sort/numeric_unique.expected | 2 + tests/fixtures/sort/numeric_unique.txt | 3 + tests/fixtures/sort/words_unique.expected | 3 + tests/fixtures/sort/words_unique.txt | 4 ++ 8 files changed, 28 insertions(+), 71 deletions(-) create mode 100644 tests/fixtures/sort/numeric_unique.expected create mode 100644 tests/fixtures/sort/numeric_unique.txt create mode 100644 tests/fixtures/sort/words_unique.expected create mode 100644 tests/fixtures/sort/words_unique.txt diff --git a/Cargo.lock b/Cargo.lock index d2b2fa32e..a6ddf7105 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2293,7 +2293,7 @@ version = "0.0.6" dependencies = [ "clap", "fnv", - "itertools 0.8.2", + "itertools 0.10.0", "rand 0.7.3", "rayon", "semver", diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 96e88ebc9..6a9976278 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -19,7 +19,7 @@ rayon = "1.5" rand = "0.7" clap = "2.33" fnv = "1.0.7" -itertools = "0.8.0" +itertools = "0.10.0" semver = "0.9.0" smallvec = "1.6.1" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4f669f578..35ab71ba2 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -162,6 +162,7 @@ impl From<&GlobalSettings> for KeySettings { } /// Represents the string selected by a FieldSelector. +#[derive(Debug)] enum Selection { /// If we had to transform this selection, we have to store a new string. String(String), @@ -181,6 +182,7 @@ impl Selection { type Field = Range; +#[derive(Debug)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. @@ -868,22 +870,12 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { } else { print_sorted(file_merger, &settings) } - } else if settings.mode == SortMode::Default && settings.unique { - print_sorted(lines.into_iter().map(|line| line.line).dedup(), &settings) - } else if settings.mode == SortMode::Month && settings.unique { - print_sorted( - lines - .into_iter() - .map(|line| line.line) - .dedup_by(|a, b| get_months_dedup(a) == get_months_dedup(b)), - &settings, - ) } else if settings.unique { print_sorted( lines .into_iter() - .map(|line| line.line) - .dedup_by(|a, b| get_num_dedup(a, settings) == get_num_dedup(b, settings)), + .dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal) + .map(|line| line.line), &settings, ) } else { @@ -1062,63 +1054,6 @@ fn get_leading_gen(a: &str) -> &str { result } -fn get_months_dedup(a: &str) -> String { - let pattern = if a.trim().len().ge(&3) { - // Split at 3rd char and get first element of tuple ".0" - a.split_at(3).0 - } else { - "" - }; - - let month = match pattern.to_uppercase().as_ref() { - "JAN" => Month::January, - "FEB" => Month::February, - "MAR" => Month::March, - "APR" => Month::April, - "MAY" => Month::May, - "JUN" => Month::June, - "JUL" => Month::July, - "AUG" => Month::August, - "SEP" => Month::September, - "OCT" => Month::October, - "NOV" => Month::November, - "DEC" => Month::December, - _ => Month::Unknown, - }; - - if month == Month::Unknown { - "".to_owned() - } else { - pattern.to_uppercase() - } -} - -// *For all dedups/uniques expect default we must compare leading numbers* -// Also note numeric compare and unique output is specifically *not* the same as a "sort | uniq" -// See: https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html -fn get_num_dedup<'a>(a: &'a str, settings: &GlobalSettings) -> &'a str { - // Trim and remove any leading zeros - let s = a.trim().trim_start_matches('0'); - - // Get first char - let c = s.chars().next().unwrap_or('\0'); - - // Empty lines and non-number lines are treated as the same for dedup - // Prepare lines for comparison of only the numerical leading numbers - if s.is_empty() || (!c.eq(&NEGATIVE) && !c.is_numeric()) { - "" - } else { - let result = match settings.mode { - SortMode::Numeric => get_leading_num(s), - SortMode::GeneralNumeric => get_leading_gen(s), - SortMode::HumanNumeric => get_leading_num(s), - SortMode::Version => get_leading_num(s), - _ => s, - }; - result - } -} - #[inline(always)] fn remove_thousands_sep<'a, S: Into>>(input: S) -> Cow<'a, str> { let input = input.into(); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 668e783ae..866beefff 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -223,6 +223,16 @@ fn test_mixed_floats_ints_chars_numeric_unique() { test_helper("mixed_floats_ints_chars_numeric_unique", "-nu"); } +#[test] +fn test_words_unique() { + test_helper("words_unique", "-u"); +} + +#[test] +fn test_numeric_unique() { + test_helper("numeric_unique", "-nu"); +} + #[test] fn test_mixed_floats_ints_chars_numeric_reverse() { test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur"); diff --git a/tests/fixtures/sort/numeric_unique.expected b/tests/fixtures/sort/numeric_unique.expected new file mode 100644 index 000000000..8a31187f6 --- /dev/null +++ b/tests/fixtures/sort/numeric_unique.expected @@ -0,0 +1,2 @@ +-10 bb +aa diff --git a/tests/fixtures/sort/numeric_unique.txt b/tests/fixtures/sort/numeric_unique.txt new file mode 100644 index 000000000..15cc08022 --- /dev/null +++ b/tests/fixtures/sort/numeric_unique.txt @@ -0,0 +1,3 @@ +aa +-10 bb +-10 aa diff --git a/tests/fixtures/sort/words_unique.expected b/tests/fixtures/sort/words_unique.expected new file mode 100644 index 000000000..2444ce1c6 --- /dev/null +++ b/tests/fixtures/sort/words_unique.expected @@ -0,0 +1,3 @@ +aaa +bbb +zzz diff --git a/tests/fixtures/sort/words_unique.txt b/tests/fixtures/sort/words_unique.txt new file mode 100644 index 000000000..9c6666029 --- /dev/null +++ b/tests/fixtures/sort/words_unique.txt @@ -0,0 +1,4 @@ +zzz +aaa +bbb +bbb From 77411f3fb5b95b893894958497fbef7dafb6a14c Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:36:57 -0500 Subject: [PATCH 013/399] Implement test for months whitespace fix --- Cargo.lock | 2 -- tests/by-util/test_sort.rs | 5 +++++ tests/fixtures/sort/months-whitespace.expected | 8 ++++++++ tests/fixtures/sort/months-whitespace.txt | 8 ++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sort/months-whitespace.expected create mode 100644 tests/fixtures/sort/months-whitespace.txt diff --git a/Cargo.lock b/Cargo.lock index a6ddf7105..d45e41c16 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/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 866beefff..c09ebd256 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -8,6 +8,11 @@ fn test_helper(file_name: &str, args: &str) { .stdout_is_fixture(format!("{}.expected", file_name)); } +#[test] +fn test_months_whitespace() { + test_helper("months-whitespace", "-M"); +} + #[test] fn test_multiple_decimals_general() { new_ucmd!() diff --git a/tests/fixtures/sort/months-whitespace.expected b/tests/fixtures/sort/months-whitespace.expected new file mode 100644 index 000000000..84a44d564 --- /dev/null +++ b/tests/fixtures/sort/months-whitespace.expected @@ -0,0 +1,8 @@ + + +JAN + FEb + apr + apr + JUNNNN +AUG diff --git a/tests/fixtures/sort/months-whitespace.txt b/tests/fixtures/sort/months-whitespace.txt new file mode 100644 index 000000000..45c477477 --- /dev/null +++ b/tests/fixtures/sort/months-whitespace.txt @@ -0,0 +1,8 @@ +JAN + JUNNNN +AUG + + apr + apr + + FEb From 9bcf752b0c3feb820a87a31968dfac2f121301fc Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 10 Apr 2021 14:06:24 -0500 Subject: [PATCH 014/399] Confirm human numeric works as expected with whitespace with a test --- tests/by-util/test_sort.rs | 5 +++++ tests/fixtures/sort/human-numeric-whitespace.expected | 11 +++++++++++ tests/fixtures/sort/human-numeric-whitespace.txt | 11 +++++++++++ 3 files changed, 27 insertions(+) create mode 100644 tests/fixtures/sort/human-numeric-whitespace.expected create mode 100644 tests/fixtures/sort/human-numeric-whitespace.txt diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index c09ebd256..23ce3258d 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -13,6 +13,11 @@ fn test_months_whitespace() { test_helper("months-whitespace", "-M"); } +#[test] +fn test_human_numeric_whitespace() { + test_helper("human-numeric-whitespace", "-h"); +} + #[test] fn test_multiple_decimals_general() { new_ucmd!() diff --git a/tests/fixtures/sort/human-numeric-whitespace.expected b/tests/fixtures/sort/human-numeric-whitespace.expected new file mode 100644 index 000000000..6fb9291ff --- /dev/null +++ b/tests/fixtures/sort/human-numeric-whitespace.expected @@ -0,0 +1,11 @@ + + + + + + + +456K +4568K + 456M + 6.2G diff --git a/tests/fixtures/sort/human-numeric-whitespace.txt b/tests/fixtures/sort/human-numeric-whitespace.txt new file mode 100644 index 000000000..19db648b1 --- /dev/null +++ b/tests/fixtures/sort/human-numeric-whitespace.txt @@ -0,0 +1,11 @@ + + +456K + + 456M + + +4568K + + 6.2G + From 713327372529c335551eccf36ff17ab55266e651 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 10 Apr 2021 14:13:49 -0500 Subject: [PATCH 015/399] Correct arg help value name for --parallel --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 10e4229d4..6611a70e4 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -677,7 +677,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(OPT_PARALLEL) .long(OPT_PARALLEL) - .help("change the number of threads running concurrently to N") + .help("change the number of threads running concurrently to NUM_THREADS") .takes_value(true) .value_name("NUM_THREADS"), ) From bf1944271ccfa062fce566388615faa18c7eebc0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 10 Apr 2021 21:57:03 +0200 Subject: [PATCH 016/399] remove .DS_Store --- tests/.DS_Store | Bin 6148 -> 0 bytes tests/fixtures/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/.DS_Store delete mode 100644 tests/fixtures/.DS_Store diff --git a/tests/.DS_Store b/tests/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8z45|!R0Z1N%F(jFgL>QrFAPJ2!M?+vV1V%$(Gz3ON zU^D~25V%SxcdJP zRior+2#kinunYl47MEZbCs3t{!+W4QHvuXKVuPw;Mo^s$(F3lEVT}ML$bg~*R5_@+ b2Uo?6kTwK}57Iu`5P${HC_Nei0}uiLNUI8I diff --git a/tests/fixtures/.DS_Store b/tests/fixtures/.DS_Store deleted file mode 100644 index 607a7386a75b94e7df52bcee971a48217dc92331..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmZQzU|@7AO)+F(5MW?n;9!8zOq>i@0Z1N%F(jFwA|RR(WJXeXaY0f}ei8!%!%3*z zC^fi402FsD48;uj3`Gnj$nlp{kds+lVqkEck%^gwm5rSP1b8`OgER8WgG&-iN{gKm zi=siifW(rFBq%#1KR*Y~PD~2ROf8QW5OL1WD@n}EODzH^56(kXDc!NGpg2X=Pvp zvB2_RtqhC|5Uq^hZU_SdBe+WfqQTl37#YCY85kMB+8JQ&JVuCi21bZ>21aNPg%Q-F z0htfc&cF!K4s+fpJsJX|Api{lW(X|+s{dUX7;yFfA*x2n(GVC7fngZ}j4Up}E>56I z6NmRebuFkqO@PXSYJX65%m}Kd5n|w~mEQChs K(GZ}22mk;)qfplX From eb4971e6f4050e749127260c89a7a6eaf164faad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81rni=20Dagur?= Date: Sat, 10 Apr 2021 20:19:53 +0000 Subject: [PATCH 017/399] cat: Unrevert splice patch (#2020) * cat: Unrevert splice patch * cat: Add fifo test * cat: Add tests for error cases * cat: Add tests for character devices * wc: Make sure we handle short splice writes * cat: Fix tests for 1.40.0 compiler * cat: Run rustfmt on test_cat.rs * Run 'cargo +1.40.0 update' --- Cargo.lock | 7 +- src/uu/cat/Cargo.toml | 5 +- src/uu/cat/src/cat.rs | 418 +++++++++++------- src/uu/wc/src/count_bytes.rs | 17 +- tests/by-util/test_cat.rs | 184 +++++++- tests/by-util/test_wc.rs | 28 ++ tests/common/util.rs | 15 + tests/fixtures/cat/empty.txt | 0 ...ctories_and_file_and_stdin.stderr.expected | 5 + 9 files changed, 496 insertions(+), 183 deletions(-) create mode 100644 tests/fixtures/cat/empty.txt create mode 100644 tests/fixtures/cat/three_directories_and_file_and_stdin.stderr.expected diff --git a/Cargo.lock b/Cargo.lock index a6ddf7105..430abf921 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,9 +1396,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" +checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -1618,7 +1618,8 @@ name = "uu_cat" version = "0.0.6" dependencies = [ "clap", - "quick-error", + "nix 0.20.0", + "thiserror", "unix_socket", "uucore", "uucore_procs", diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index e44a874c1..09b289253 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -16,13 +16,16 @@ path = "src/cat.rs" [dependencies] clap = "2.33" -quick-error = "1.2.3" +thiserror = "1.0" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] unix_socket = "0.5.0" +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +nix = "0.20" + [[bin]] name = "cat" path = "src/main.rs" diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index cf5a384a4..7d56a7485 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -3,14 +3,13 @@ // (c) Jordi Boggiano // (c) Evgeniy Klyuchikov // (c) Joshua S. Miller +// (c) Árni Dagur // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (ToDO) nonprint nonblank nonprinting -#[macro_use] -extern crate quick_error; #[cfg(unix)] extern crate unix_socket; #[macro_use] @@ -18,9 +17,9 @@ extern crate uucore; // last synced with: cat (GNU coreutils) 8.13 use clap::{App, Arg}; -use quick_error::ResultExt; use std::fs::{metadata, File}; -use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write}; +use std::io::{self, Read, Write}; +use thiserror::Error; use uucore::fs::is_stdin_interactive; /// Unix domain socket support @@ -31,12 +30,41 @@ use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use unix_socket::UnixStream; +/// Linux splice support +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::fcntl::{splice, SpliceFFlags}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::unistd::pipe; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::unix::io::{AsRawFd, RawFd}; + static NAME: &str = "cat"; static VERSION: &str = env!("CARGO_PKG_VERSION"); static SYNTAX: &str = "[OPTION]... [FILE]..."; static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output With no FILE, or when FILE is -, read standard input."; +#[derive(Error, Debug)] +enum CatError { + /// Wrapper around `io::Error` + #[error("{0}")] + Io(#[from] io::Error), + /// Wrapper around `nix::Error` + #[cfg(any(target_os = "linux", target_os = "android"))] + #[error("{0}")] + Nix(#[from] nix::Error), + /// Unknown file type; it's not a regular file, socket, etc. + #[error("unknown filetype: {}", ft_debug)] + UnknownFiletype { + /// A debug print of the file type + ft_debug: String, + }, + #[error("Is a directory")] + IsDirectory, +} + +type CatResult = Result; + #[derive(PartialEq)] enum NumberingMode { None, @@ -44,39 +72,6 @@ enum NumberingMode { All, } -quick_error! { - #[derive(Debug)] - enum CatError { - /// Wrapper for io::Error with path context - Input(err: io::Error, path: String) { - display("cat: {0}: {1}", path, err) - context(path: &'a str, err: io::Error) -> (err, path.to_owned()) - cause(err) - } - - /// Wrapper for io::Error with no context - Output(err: io::Error) { - display("cat: {0}", err) from() - cause(err) - } - - /// Unknown Filetype classification - UnknownFiletype(path: String) { - display("cat: {0}: unknown filetype", path) - } - - /// At least one error was encountered in reading or writing - EncounteredErrors(count: usize) { - display("cat: encountered {0} errors", count) - } - - /// Denotes an error caused by trying to `cat` a directory - IsDirectory(path: String) { - display("cat: {0}: Is a directory", path) - } - } -} - struct OutputOptions { /// Line numbering mode number: NumberingMode, @@ -87,21 +82,56 @@ struct OutputOptions { /// display TAB characters as `tab` show_tabs: bool, - /// If `show_tabs == true`, this string will be printed in the - /// place of tabs - tab: String, - - /// Can be set to show characters other than '\n' a the end of - /// each line, e.g. $ - end_of_line: String, + /// Show end of lines + show_ends: bool, /// use ^ and M- notation, except for LF (\\n) and TAB (\\t) show_nonprint: bool, } +impl OutputOptions { + fn tab(&self) -> &'static str { + if self.show_tabs { + "^I" + } else { + "\t" + } + } + + fn end_of_line(&self) -> &'static str { + if self.show_ends { + "$\n" + } else { + "\n" + } + } + + /// We can write fast if we can simply copy the contents of the file to + /// stdout, without augmenting the output with e.g. line numbers. + fn can_write_fast(&self) -> bool { + !(self.show_tabs + || self.show_nonprint + || self.show_ends + || self.squeeze_blank + || self.number != NumberingMode::None) + } +} + +/// State that persists between output of each file. This struct is only used +/// when we can't write fast. +struct OutputState { + /// The current line number + line_number: usize, + + /// Whether the output cursor is at the beginning of a new line + at_line_start: bool, +} + /// Represents an open file handle, stream, or other device -struct InputHandle { - reader: Box, +struct InputHandle { + #[cfg(any(target_os = "linux", target_os = "android"))] + file_descriptor: RawFd, + reader: R, is_interactive: bool, } @@ -124,8 +154,6 @@ enum InputType { Socket, } -type CatResult = Result; - mod options { pub static FILE: &str = "file"; pub static SHOW_ALL: &str = "show-all"; @@ -243,30 +271,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => vec!["-".to_owned()], }; - let can_write_fast = !(show_tabs - || show_nonprint - || show_ends - || squeeze_blank - || number_mode != NumberingMode::None); - - let success = if can_write_fast { - write_fast(files).is_ok() - } else { - let tab = if show_tabs { "^I" } else { "\t" }.to_owned(); - - let end_of_line = if show_ends { "$\n" } else { "\n" }.to_owned(); - - let options = OutputOptions { - end_of_line, - number: number_mode, - show_nonprint, - show_tabs, - squeeze_blank, - tab, - }; - - write_lines(files, &options).is_ok() + let options = OutputOptions { + show_ends, + number: number_mode, + show_nonprint, + show_tabs, + squeeze_blank, }; + let success = cat_files(files, &options).is_ok(); if success { 0 @@ -275,6 +287,76 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } +fn cat_handle( + handle: &mut InputHandle, + options: &OutputOptions, + state: &mut OutputState, +) -> CatResult<()> { + if options.can_write_fast() { + write_fast(handle) + } else { + write_lines(handle, &options, state) + } +} + +fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { + if path == "-" { + let stdin = io::stdin(); + let mut handle = InputHandle { + #[cfg(any(target_os = "linux", target_os = "android"))] + file_descriptor: stdin.as_raw_fd(), + reader: stdin, + is_interactive: is_stdin_interactive(), + }; + return cat_handle(&mut handle, &options, state); + } + match get_input_type(path)? { + InputType::Directory => Err(CatError::IsDirectory), + #[cfg(unix)] + InputType::Socket => { + let socket = UnixStream::connect(path)?; + socket.shutdown(Shutdown::Write)?; + let mut handle = InputHandle { + #[cfg(any(target_os = "linux", target_os = "android"))] + file_descriptor: socket.as_raw_fd(), + reader: socket, + is_interactive: false, + }; + cat_handle(&mut handle, &options, state) + } + _ => { + let file = File::open(path)?; + let mut handle = InputHandle { + #[cfg(any(target_os = "linux", target_os = "android"))] + file_descriptor: file.as_raw_fd(), + reader: file, + is_interactive: false, + }; + cat_handle(&mut handle, &options, state) + } + } +} + +fn cat_files(files: Vec, options: &OutputOptions) -> Result<(), u32> { + let mut error_count = 0; + let mut state = OutputState { + line_number: 1, + at_line_start: true, + }; + + for path in &files { + if let Err(err) = cat_path(path, &options, &mut state) { + show_info!("{}: {}", path, err); + error_count += 1; + } + } + if error_count == 0 { + Ok(()) + } else { + Err(error_count) + } +} + /// Classifies the `InputType` of file at `path` if possible /// /// # Arguments @@ -285,7 +367,8 @@ fn get_input_type(path: &str) -> CatResult { return Ok(InputType::StdIn); } - match metadata(path).context(path)?.file_type() { + let ft = metadata(path)?.file_type(); + match ft { #[cfg(unix)] ft if ft.is_block_device() => Ok(InputType::BlockDevice), #[cfg(unix)] @@ -297,125 +380,116 @@ fn get_input_type(path: &str) -> CatResult { ft if ft.is_dir() => Ok(InputType::Directory), ft if ft.is_file() => Ok(InputType::File), ft if ft.is_symlink() => Ok(InputType::SymLink), - _ => Err(CatError::UnknownFiletype(path.to_owned())), + _ => Err(CatError::UnknownFiletype { + ft_debug: format!("{:?}", ft), + }), } } -/// Returns an InputHandle from which a Reader can be accessed or an -/// error -/// -/// # Arguments -/// -/// * `path` - `InputHandler` will wrap a reader from this file path -fn open(path: &str) -> CatResult { - if path == "-" { - let stdin = stdin(); - return Ok(InputHandle { - reader: Box::new(stdin) as Box, - is_interactive: is_stdin_interactive(), - }); - } - - match get_input_type(path)? { - InputType::Directory => Err(CatError::IsDirectory(path.to_owned())), - #[cfg(unix)] - InputType::Socket => { - let socket = UnixStream::connect(path).context(path)?; - socket.shutdown(Shutdown::Write).context(path)?; - Ok(InputHandle { - reader: Box::new(socket) as Box, - is_interactive: false, - }) - } - _ => { - let file = File::open(path).context(path)?; - Ok(InputHandle { - reader: Box::new(file) as Box, - is_interactive: false, - }) +/// Writes handle to stdout with no configuration. This allows a +/// simple memory copy. +fn write_fast(handle: &mut InputHandle) -> CatResult<()> { + let stdout = io::stdout(); + let mut stdout_lock = stdout.lock(); + #[cfg(any(target_os = "linux", target_os = "android"))] + { + // If we're on Linux or Android, try to use the splice() system call + // for faster writing. If it works, we're done. + if !write_fast_using_splice(handle, stdout_lock.as_raw_fd())? { + return Ok(()); } } + // If we're not on Linux or Android, or the splice() call failed, + // fall back on slower writing. + let mut buf = [0; 1024 * 64]; + while let Ok(n) = handle.reader.read(&mut buf) { + if n == 0 { + break; + } + stdout_lock.write_all(&buf[..n])?; + } + Ok(()) } -/// Writes files to stdout with no configuration. This allows a -/// simple memory copy. Returns `Ok(())` if no errors were -/// encountered, or an error with the number of errors encountered. +/// This function is called from `write_fast()` on Linux and Android. The +/// function `splice()` is used to move data between two file descriptors +/// without copying between kernel- and userspace. This results in a large +/// speedup. /// -/// # Arguments -/// -/// * `files` - There is no short circuit when encountering an error -/// reading a file in this vector -fn write_fast(files: Vec) -> CatResult<()> { - let mut writer = stdout(); - let mut in_buf = [0; 1024 * 64]; - let mut error_count = 0; +/// The `bool` in the result value indicates if we need to fall back to normal +/// copying or not. False means we don't have to. +#[cfg(any(target_os = "linux", target_os = "android"))] +#[inline] +fn write_fast_using_splice(handle: &mut InputHandle, writer: RawFd) -> CatResult { + const BUF_SIZE: usize = 1024 * 16; - for file in files { - match open(&file[..]) { - Ok(mut handle) => { - while let Ok(n) = handle.reader.read(&mut in_buf) { - if n == 0 { - break; - } - writer.write_all(&in_buf[..n]).context(&file[..])?; - } - } - Err(error) => { - writeln!(&mut stderr(), "{}", error)?; - error_count += 1; + let (pipe_rd, pipe_wr) = pipe()?; + + // We only fall back if splice fails on the first call. + match splice( + handle.file_descriptor, + None, + pipe_wr, + None, + BUF_SIZE, + SpliceFFlags::empty(), + ) { + Ok(n) => { + if n == 0 { + return Ok(false); } + splice_exact(pipe_rd, writer, n)?; + } + Err(_) => { + return Ok(true); } } - match error_count { - 0 => Ok(()), - _ => Err(CatError::EncounteredErrors(error_count)), + loop { + let n = splice( + handle.file_descriptor, + None, + pipe_wr, + None, + BUF_SIZE, + SpliceFFlags::empty(), + )?; + if n == 0 { + // We read 0 bytes from the input, + // which means we're done copying. + break; + } + splice_exact(pipe_rd, writer, n)?; } + + Ok(false) } -/// State that persists between output of each file -struct OutputState { - /// The current line number - line_number: usize, - - /// Whether the output cursor is at the beginning of a new line - at_line_start: bool, -} - -/// Writes files to stdout with `options` as configuration. Returns -/// `Ok(())` if no errors were encountered, or an error with the -/// number of errors encountered. -/// -/// # Arguments -/// -/// * `files` - There is no short circuit when encountering an error -/// reading a file in this vector -fn write_lines(files: Vec, options: &OutputOptions) -> CatResult<()> { - let mut error_count = 0; - let mut state = OutputState { - line_number: 1, - at_line_start: true, - }; - - for file in files { - if let Err(error) = write_file_lines(&file, options, &mut state) { - writeln!(&mut stderr(), "{}", error).context(&file[..])?; - error_count += 1; +/// Splice wrapper which handles short writes +#[cfg(any(target_os = "linux", target_os = "android"))] +#[inline] +fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { + let mut left = num_bytes; + loop { + let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?; + left -= written; + if left == 0 { + break; } } - - match error_count { - 0 => Ok(()), - _ => Err(CatError::EncounteredErrors(error_count)), - } + Ok(()) } /// Outputs file contents to stdout in a line-by-line fashion, /// propagating any errors that might occur. -fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { - let mut handle = open(file)?; +fn write_lines( + handle: &mut InputHandle, + options: &OutputOptions, + state: &mut OutputState, +) -> CatResult<()> { let mut in_buf = [0; 1024 * 31]; - let mut writer = BufWriter::with_capacity(1024 * 64, stdout()); + let stdout = io::stdout(); + let mut writer = stdout.lock(); let mut one_blank_kept = false; while let Ok(n) = handle.reader.read(&mut in_buf) { @@ -433,9 +507,9 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState write!(&mut writer, "{0:6}\t", state.line_number)?; state.line_number += 1; } - writer.write_all(options.end_of_line.as_bytes())?; + writer.write_all(options.end_of_line().as_bytes())?; if handle.is_interactive { - writer.flush().context(file)?; + writer.flush()?; } } state.at_line_start = true; @@ -450,7 +524,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState // print to end of line or end of buffer let offset = if options.show_nonprint { - write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab.as_bytes()) + write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab().as_bytes()) } else if options.show_tabs { write_tab_to_end(&in_buf[pos..], &mut writer) } else { @@ -462,7 +536,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState break; } // print suitable end of line - writer.write_all(options.end_of_line.as_bytes())?; + writer.write_all(options.end_of_line().as_bytes())?; if handle.is_interactive { writer.flush()?; } diff --git a/src/uu/wc/src/count_bytes.rs b/src/uu/wc/src/count_bytes.rs index dc90f67cc..0c3b5edb7 100644 --- a/src/uu/wc/src/count_bytes.rs +++ b/src/uu/wc/src/count_bytes.rs @@ -20,6 +20,21 @@ use nix::unistd::pipe; const BUF_SIZE: usize = 16384; +/// Splice wrapper which handles short writes +#[cfg(any(target_os = "linux", target_os = "android"))] +#[inline] +fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { + let mut left = num_bytes; + loop { + let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?; + left -= written; + if left == 0 { + break; + } + } + Ok(()) +} + /// This is a Linux-specific function to count the number of bytes using the /// `splice` system call, which is faster than using `read`. #[inline] @@ -39,7 +54,7 @@ fn count_bytes_using_splice(fd: RawFd) -> nix::Result { break; } byte_count += res; - splice(pipe_rd, None, null, None, res, SpliceFFlags::empty())?; + splice_exact(pipe_rd, null, res)?; } Ok(byte_count) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 481b1683d..7b4c9924e 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -1,7 +1,5 @@ -#[cfg(unix)] -extern crate unix_socket; - use crate::common::util::*; +use std::io::Read; #[test] fn test_output_simple() { @@ -11,6 +9,129 @@ fn test_output_simple() { .stdout_only("abcde\nfghij\nklmno\npqrst\nuvwxyz\n"); } +#[test] +fn test_no_options() { + for fixture in &["empty.txt", "alpha.txt", "nonewline.txt"] { + // Give fixture through command line file argument + new_ucmd!() + .args(&[fixture]) + .succeeds() + .stdout_is_fixture(fixture); + // Give fixture through stdin + new_ucmd!() + .pipe_in_fixture(fixture) + .succeeds() + .stdout_is_fixture(fixture); + } +} + +#[test] +fn test_no_options_big_input() { + for &n in &[ + 0, + 1, + 42, + 16 * 1024 - 7, + 16 * 1024 - 1, + 16 * 1024, + 16 * 1024 + 1, + 16 * 1024 + 3, + 32 * 1024, + 64 * 1024, + 80 * 1024, + 96 * 1024, + 112 * 1024, + 128 * 1024, + ] { + let data = vec_of_size(n); + let data2 = data.clone(); + assert_eq!(data.len(), data2.len()); + new_ucmd!().pipe_in(data).succeeds().stdout_is_bytes(&data2); + } +} + +#[test] +#[cfg(unix)] +fn test_fifo_symlink() { + use std::fs::OpenOptions; + use std::io::Write; + use std::thread; + + let s = TestScenario::new(util_name!()); + s.fixtures.mkdir("dir"); + s.fixtures.mkfifo("dir/pipe"); + assert!(s.fixtures.is_fifo("dir/pipe")); + + // Make cat read the pipe through a symlink + s.fixtures.symlink_file("dir/pipe", "sympipe"); + let proc = s.ucmd().args(&["sympipe"]).run_no_wait(); + + let data = vec_of_size(128 * 1024); + let data2 = data.clone(); + + let pipe_path = s.fixtures.plus("dir/pipe"); + let thread = thread::spawn(move || { + let mut pipe = OpenOptions::new() + .write(true) + .create(false) + .open(pipe_path) + .unwrap(); + pipe.write_all(&data).unwrap(); + }); + + let output = proc.wait_with_output().unwrap(); + assert_eq!(&output.stdout, &data2); + thread.join().unwrap(); +} + +#[test] +fn test_directory() { + let s = TestScenario::new(util_name!()); + s.fixtures.mkdir("test_directory"); + s.ucmd() + .args(&["test_directory"]) + .fails() + .stderr_is("cat: test_directory: Is a directory"); +} + +#[test] +fn test_directory_and_file() { + let s = TestScenario::new(util_name!()); + s.fixtures.mkdir("test_directory2"); + for fixture in &["empty.txt", "alpha.txt", "nonewline.txt"] { + s.ucmd() + .args(&["test_directory2", fixture]) + .fails() + .stderr_is("cat: test_directory2: Is a directory") + .stdout_is_fixture(fixture); + } +} + +#[test] +fn test_three_directories_and_file_and_stdin() { + let s = TestScenario::new(util_name!()); + s.fixtures.mkdir("test_directory3"); + s.fixtures.mkdir("test_directory3/test_directory4"); + s.fixtures.mkdir("test_directory3/test_directory5"); + s.ucmd() + .args(&[ + "test_directory3/test_directory4", + "alpha.txt", + "-", + "filewhichdoesnotexist.txt", + "nonewline.txt", + "test_directory3/test_directory5", + "test_directory3/../test_directory3/test_directory5", + "test_directory3", + ]) + .pipe_in("stdout bytes") + .fails() + .stderr_is_fixture("three_directories_and_file_and_stdin.stderr.expected") + .stdout_is( + "abcde\nfghij\nklmno\npqrst\nuvwxyz\nstdout bytestext without a trailing newline", + ); +} + #[test] fn test_output_multi_files_print_all_chars() { new_ucmd!() @@ -149,13 +270,64 @@ fn test_squeeze_blank_before_numbering() { } } +/// This tests reading from Unix character devices #[test] -#[cfg(foo)] +#[cfg(unix)] +fn test_dev_random() { + let mut buf = [0; 2048]; + let mut proc = new_ucmd!().args(&["/dev/random"]).run_no_wait(); + let mut proc_stdout = proc.stdout.take().unwrap(); + proc_stdout.read_exact(&mut buf).unwrap(); + + let num_zeroes = buf.iter().fold(0, |mut acc, &n| { + if n == 0 { + acc += 1; + } + acc + }); + // The probability of more than 512 zero bytes is essentially zero if the + // output is truly random. + assert!(num_zeroes < 512); + proc.kill().unwrap(); +} + +/// Reading from /dev/full should return an infinite amount of zero bytes. +/// Wikipedia says there is support on Linux, FreeBSD, and NetBSD. +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_dev_full() { + let mut buf = [0; 2048]; + let mut proc = new_ucmd!().args(&["/dev/full"]).run_no_wait(); + let mut proc_stdout = proc.stdout.take().unwrap(); + let expected = [0; 2048]; + proc_stdout.read_exact(&mut buf).unwrap(); + assert_eq!(&buf[..], &expected[..]); + proc.kill().unwrap(); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_dev_full_show_all() { + let mut buf = [0; 2048]; + let mut proc = new_ucmd!().args(&["-A", "/dev/full"]).run_no_wait(); + let mut proc_stdout = proc.stdout.take().unwrap(); + proc_stdout.read_exact(&mut buf).unwrap(); + + let expected: Vec = (0..buf.len()) + .map(|n| if n & 1 == 0 { b'^' } else { b'@' }) + .collect(); + + assert_eq!(&buf[..], &expected[..]); + proc.kill().unwrap(); +} + +#[test] +#[cfg(unix)] fn test_domain_socket() { - use self::tempdir::TempDir; - use self::unix_socket::UnixListener; use std::io::prelude::*; use std::thread; + use tempdir::TempDir; + use unix_socket::UnixListener; let dir = TempDir::new("unix_socket").expect("failed to create dir"); let socket_path = dir.path().join("sock"); diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index fc1665efc..075878470 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -1,5 +1,33 @@ use crate::common::util::*; +#[test] +fn test_count_bytes_large_stdin() { + for &n in &[ + 0, + 1, + 42, + 16 * 1024 - 7, + 16 * 1024 - 1, + 16 * 1024, + 16 * 1024 + 1, + 16 * 1024 + 3, + 32 * 1024, + 64 * 1024, + 80 * 1024, + 96 * 1024, + 112 * 1024, + 128 * 1024, + ] { + let data = vec_of_size(n); + let expected = format!("{}\n", n); + new_ucmd!() + .args(&["-c"]) + .pipe_in(data) + .succeeds() + .stdout_is_bytes(&expected.as_bytes()); + } +} + #[test] fn test_stdin_default() { new_ucmd!() diff --git a/tests/common/util.rs b/tests/common/util.rs index 13c58747d..d5663e9ed 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -222,6 +222,12 @@ impl CmdResult { self } + /// Like stdout_is_fixture, but for stderr + pub fn stderr_is_fixture>(&self, file_rel_path: T) -> &CmdResult { + let contents = read_scenario_fixture(&self.tmpd, file_rel_path); + self.stderr_is_bytes(contents) + } + /// asserts that /// 1. the command resulted in stdout stream output that equals the /// passed in value @@ -804,3 +810,12 @@ pub fn read_size(child: &mut Child, size: usize) -> String { .unwrap(); String::from_utf8(output).unwrap() } + +pub fn vec_of_size(n: usize) -> Vec { + let mut result = Vec::new(); + for _ in 0..n { + result.push('a' as u8); + } + assert_eq!(result.len(), n); + result +} diff --git a/tests/fixtures/cat/empty.txt b/tests/fixtures/cat/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/cat/three_directories_and_file_and_stdin.stderr.expected b/tests/fixtures/cat/three_directories_and_file_and_stdin.stderr.expected new file mode 100644 index 000000000..1a8a33d77 --- /dev/null +++ b/tests/fixtures/cat/three_directories_and_file_and_stdin.stderr.expected @@ -0,0 +1,5 @@ +cat: test_directory3/test_directory4: Is a directory +cat: filewhichdoesnotexist.txt: No such file or directory (os error 2) +cat: test_directory3/test_directory5: Is a directory +cat: test_directory3/../test_directory3/test_directory5: Is a directory +cat: test_directory3: Is a directory From c6021e10c2fa46d906cb8d3b804d8348b68d0c92 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 10 Apr 2021 15:27:16 -0500 Subject: [PATCH 018/399] Fix SemVer non version lines/empty line sorting with a test --- src/uu/sort/src/sort.rs | 15 +++++++++++++-- tests/by-util/test_sort.rs | 9 +++++++++ tests/fixtures/sort/version-empty-lines.expected | 11 +++++++++++ tests/fixtures/sort/version-empty-lines.txt | 11 +++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/sort/version-empty-lines.expected create mode 100644 tests/fixtures/sort/version-empty-lines.txt diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 6611a70e4..8bf6eb1e8 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1262,10 +1262,21 @@ fn month_compare(a: &str, b: &str) -> Ordering { } } +fn version_parse(a: &str) -> Version { + let result = Version::parse(a); + + match result { + Ok(vers_a) => vers_a, + // Non-version lines parse to 0.0.0 + Err(_e) => Version::parse("0.0.0").unwrap(), + } +} + fn version_compare(a: &str, b: &str) -> Ordering { #![allow(clippy::comparison_chain)] - let ver_a = Version::parse(a); - let ver_b = Version::parse(b); + let ver_a = version_parse(a); + let ver_b = version_parse(b); + // Version::cmp is not implemented; implement comparison directly if ver_a > ver_b { Ordering::Greater diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 23ce3258d..0f8020688 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -13,6 +13,15 @@ fn test_months_whitespace() { test_helper("months-whitespace", "-M"); } +#[test] +fn test_version_empty_lines() { + new_ucmd!() + .arg("-V") + .arg("version-empty-lines.txt") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n"); +} + #[test] fn test_human_numeric_whitespace() { test_helper("human-numeric-whitespace", "-h"); diff --git a/tests/fixtures/sort/version-empty-lines.expected b/tests/fixtures/sort/version-empty-lines.expected new file mode 100644 index 000000000..c496c0ff5 --- /dev/null +++ b/tests/fixtures/sort/version-empty-lines.expected @@ -0,0 +1,11 @@ + + + + + + + +1.2.3-alpha +1.2.3-alpha2 +11.2.3 + 1.12.4 diff --git a/tests/fixtures/sort/version-empty-lines.txt b/tests/fixtures/sort/version-empty-lines.txt new file mode 100644 index 000000000..9b6b89788 --- /dev/null +++ b/tests/fixtures/sort/version-empty-lines.txt @@ -0,0 +1,11 @@ +11.2.3 + + + +1.2.3-alpha2 + + +1.2.3-alpha + + + 1.12.4 From 81d42aa2b36186bb6b1d58e92968a8d0f06373b3 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Mon, 5 Apr 2021 23:03:43 +0300 Subject: [PATCH 019/399] Fix some tests to not use CmdResult fields --- tests/by-util/test_arch.rs | 14 ++- tests/by-util/test_basename.rs | 2 +- tests/by-util/test_chgrp.rs | 2 +- tests/by-util/test_chmod.rs | 100 ++++++++------------- tests/by-util/test_chown.rs | 157 +++++++++++++++++---------------- tests/by-util/test_chroot.rs | 16 ++-- tests/by-util/test_cp.rs | 30 +++---- tests/by-util/test_date.rs | 20 ++--- tests/by-util/test_du.rs | 52 +++++------ tests/by-util/test_echo.rs | 57 ++++++------ tests/by-util/test_env.rs | 71 ++++++--------- tests/by-util/test_expand.rs | 63 +++++++------ tests/by-util/test_factor.rs | 7 +- tests/by-util/test_fmt.rs | 6 +- tests/by-util/test_groups.rs | 31 ++++--- tests/by-util/test_hashsum.rs | 4 +- tests/by-util/test_hostid.rs | 2 +- tests/by-util/test_hostname.rs | 14 +-- tests/by-util/test_id.rs | 126 +++++++++----------------- tests/by-util/test_install.rs | 35 ++------ tests/by-util/test_ln.rs | 10 +-- tests/by-util/test_logname.rs | 12 +-- 22 files changed, 353 insertions(+), 478 deletions(-) diff --git a/tests/by-util/test_arch.rs b/tests/by-util/test_arch.rs index d2ec138d9..909e0ee80 100644 --- a/tests/by-util/test_arch.rs +++ b/tests/by-util/test_arch.rs @@ -2,17 +2,13 @@ use crate::common::util::*; #[test] fn test_arch() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.run(); - assert!(result.success); + new_ucmd!().succeeds(); } #[test] fn test_arch_help() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("--help").run(); - assert!(result.success); - assert!(result.stdout.contains("architecture name")); + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("architecture name"); } diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index fa599644d..3483e800c 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -66,7 +66,7 @@ fn test_zero_param() { } fn expect_error(input: Vec<&str>) { - assert!(new_ucmd!().args(&input).fails().no_stdout().stderr.len() > 0); + assert!(new_ucmd!().args(&input).fails().no_stdout().stderr().len() > 0); } #[test] diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 613f52fd2..343b336a6 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -149,7 +149,7 @@ fn test_big_h() { .arg("bin") .arg("/proc/self/fd") .fails() - .stderr + .stderr_str() .lines() .fold(0, |acc, _| acc + 1) > 1 diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index b85567166..d60b8a50b 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -48,7 +48,7 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) { } let r = ucmd.run(); if !r.success { - println!("{}", r.stderr); + println!("{}", r.stderr_str()); panic!("{:?}: failed", ucmd.raw); } @@ -297,13 +297,14 @@ fn test_chmod_recursive() { mkfile(&at.plus_as_string("a/b/c/c"), 0o100444); mkfile(&at.plus_as_string("z/y"), 0o100444); - let result = ucmd - .arg("-R") + ucmd.arg("-R") .arg("--verbose") .arg("-r,a+w") .arg("a") .arg("z") - .succeeds(); + .succeeds() + .stderr_contains(&"to 333 (-wx-wx-wx)") + .stderr_contains(&"to 222 (-w--w--w-)"); assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222); assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222); @@ -312,8 +313,6 @@ fn test_chmod_recursive() { println!("mode {:o}", at.metadata("a").permissions().mode()); assert_eq!(at.metadata("a").permissions().mode(), 0o40333); assert_eq!(at.metadata("z").permissions().mode(), 0o40333); - assert!(result.stderr.contains("to 333 (-wx-wx-wx)")); - assert!(result.stderr.contains("to 222 (-w--w--w-)")); unsafe { umask(original_umask); @@ -322,30 +321,24 @@ fn test_chmod_recursive() { #[test] fn test_chmod_non_existing_file() { - let (_at, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg("-R") .arg("--verbose") .arg("-r,a+w") .arg("dont-exist") - .fails(); - assert!(result - .stderr - .contains("cannot access 'dont-exist': No such file or directory")); + .fails() + .stderr_contains(&"cannot access 'dont-exist': No such file or directory"); } #[test] fn test_chmod_preserve_root() { - let (_at, mut ucmd) = at_and_ucmd!(); - let result = ucmd + new_ucmd!() .arg("-R") .arg("--preserve-root") .arg("755") .arg("/") - .fails(); - assert!(result - .stderr - .contains("chmod: error: it is dangerous to operate recursively on '/'")); + .fails() + .stderr_contains(&"chmod: error: it is dangerous to operate recursively on '/'"); } #[test] @@ -362,33 +355,27 @@ fn test_chmod_symlink_non_existing_file() { let expected_stderr = &format!("cannot operate on dangling symlink '{}'", test_symlink); at.symlink_file(non_existing, test_symlink); - let mut result; // this cannot succeed since the symbolic link dangles - result = scene.ucmd().arg("755").arg("-v").arg(test_symlink).fails(); - - println!("stdout = {:?}", result.stdout); - println!("stderr = {:?}", result.stderr); - - assert!(result.stdout.contains(expected_stdout)); - assert!(result.stderr.contains(expected_stderr)); - assert_eq!(result.code, Some(1)); + scene.ucmd() + .arg("755") + .arg("-v") + .arg(test_symlink) + .fails() + .code_is(1) + .stdout_contains(expected_stdout) + .stderr_contains(expected_stderr); // this should be the same than with just '-v' but without stderr - result = scene - .ucmd() + scene.ucmd() .arg("755") .arg("-v") .arg("-f") .arg(test_symlink) - .fails(); - - println!("stdout = {:?}", result.stdout); - println!("stderr = {:?}", result.stderr); - - assert!(result.stdout.contains(expected_stdout)); - assert!(result.stderr.is_empty()); - assert_eq!(result.code, Some(1)); + .run() + .code_is(1) + .no_stderr() + .stdout_contains(expected_stdout); } #[test] @@ -405,18 +392,15 @@ fn test_chmod_symlink_non_existing_file_recursive() { non_existing, &format!("{}/{}", test_directory, test_symlink), ); - let mut result; // this should succeed - result = scene - .ucmd() + scene.ucmd() .arg("-R") .arg("755") .arg(test_directory) - .succeeds(); - assert_eq!(result.code, Some(0)); - assert!(result.stdout.is_empty()); - assert!(result.stderr.is_empty()); + .succeeds() + .no_stderr() + .no_stdout(); let expected_stdout = &format!( "mode of '{}' retained as 0755 (rwxr-xr-x)\nneither symbolic link '{}/{}' nor referent has been changed", @@ -424,37 +408,25 @@ fn test_chmod_symlink_non_existing_file_recursive() { ); // '-v': this should succeed without stderr - result = scene - .ucmd() + scene.ucmd() .arg("-R") .arg("-v") .arg("755") .arg(test_directory) - .succeeds(); - - println!("stdout = {:?}", result.stdout); - println!("stderr = {:?}", result.stderr); - - assert!(result.stdout.contains(expected_stdout)); - assert!(result.stderr.is_empty()); - assert_eq!(result.code, Some(0)); + .succeeds() + .stdout_contains(expected_stdout) + .no_stderr(); // '-vf': this should be the same than with just '-v' - result = scene - .ucmd() + scene.ucmd() .arg("-R") .arg("-v") .arg("-f") .arg("755") .arg(test_directory) - .succeeds(); - - println!("stdout = {:?}", result.stdout); - println!("stderr = {:?}", result.stderr); - - assert!(result.stdout.contains(expected_stdout)); - assert!(result.stderr.is_empty()); - assert_eq!(result.code, Some(0)); + .succeeds() + .stdout_contains(expected_stdout) + .no_stderr(); } #[test] diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 7b663e9c9..e27fba3d4 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -53,22 +53,22 @@ fn test_chown_myself() { // test chown username file.txt 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; } - println!("results {}", result.stdout); - let username = result.stdout.trim_end(); + println!("results {}", result.stdout_str()); + let username = result.stdout_str().trim_end(); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; at.touch(file1); let result = ucmd.arg(username).arg(file1).run(); - println!("results stdout {}", result.stdout); - println!("results stderr {}", result.stderr); - if is_ci() && result.stderr.contains("invalid user") { + println!("results stdout {}", result.stdout_str()); + println!("results stderr {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains("invalid user") { // In the CI, some server are failing to return id. // As seems to be a configuration issue, ignoring it return; @@ -81,24 +81,24 @@ fn test_chown_myself_second() { // test chown username: file.txt 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; } - println!("results {}", result.stdout); + println!("results {}", result.stdout_str()); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; at.touch(file1); let result = ucmd - .arg(result.stdout.trim_end().to_owned() + ":") + .arg(result.stdout_str().trim_end().to_owned() + ":") .arg(file1) .run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); assert!(result.success); } @@ -107,31 +107,31 @@ fn test_chown_myself_group() { // test chown username:group file.txt 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; } - println!("user name = {}", result.stdout); - let username = result.stdout.trim_end(); + println!("user name = {}", result.stdout_str()); + let username = result.stdout_str().trim_end(); let result = scene.cmd("id").arg("-gn").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; } - println!("group name = {}", result.stdout); - let group = result.stdout.trim_end(); + println!("group name = {}", result.stdout_str()); + let group = result.stdout_str().trim_end(); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; let perm = username.to_owned() + ":" + group; at.touch(file1); let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("chown: invalid group:") { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains("chown: invalid group:") { // With some Ubuntu into the CI, we can get this answer return; } @@ -143,27 +143,27 @@ fn test_chown_only_group() { // test chown :group file.txt 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; } - println!("results {}", result.stdout); + println!("results {}", result.stdout_str()); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; - let perm = ":".to_owned() + result.stdout.trim_end(); + let perm = ":".to_owned() + result.stdout_str().trim_end(); at.touch(file1); let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); - if is_ci() && result.stderr.contains("Operation not permitted") { + if is_ci() && result.stderr_str().contains("Operation not permitted") { // With ubuntu with old Rust in the CI, we can get an error return; } - if is_ci() && result.stderr.contains("chown: invalid group:") { + if is_ci() && result.stderr_str().contains("chown: invalid group:") { // With mac into the CI, we can get this answer return; } @@ -174,14 +174,14 @@ fn test_chown_only_group() { fn test_chown_only_id() { // test chown 1111 file.txt let result = TestScenario::new("id").ucmd_keepenv().arg("-u").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; } - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let id = String::from(result.stdout_str().trim()); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; @@ -189,9 +189,9 @@ fn test_chown_only_id() { at.touch(file1); let result = ucmd.arg(id).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("chown: invalid user:") { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains("chown: invalid user:") { // With some Ubuntu into the CI, we can get this answer return; } @@ -202,14 +202,14 @@ fn test_chown_only_id() { fn test_chown_only_group_id() { // test chown :1111 file.txt let result = TestScenario::new("id").ucmd_keepenv().arg("-g").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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let id = String::from(result.stdout_str().trim()); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; @@ -219,9 +219,9 @@ fn test_chown_only_group_id() { let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("chown: invalid group:") { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains("chown: invalid group:") { // With mac into the CI, we can get this answer return; } @@ -232,24 +232,24 @@ fn test_chown_only_group_id() { fn test_chown_both_id() { // test chown 1111:1111 file.txt let result = TestScenario::new("id").ucmd_keepenv().arg("-u").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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id_user = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let id_user = String::from(result.stdout_str().trim()); let result = TestScenario::new("id").ucmd_keepenv().arg("-g").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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id_group = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let id_group = String::from(result.stdout_str().trim()); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; @@ -258,10 +258,10 @@ fn test_chown_both_id() { let perm = id_user + &":".to_owned() + &id_group; let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); - if is_ci() && result.stderr.contains("invalid user") { + if is_ci() && result.stderr_str().contains("invalid user") { // In the CI, some server are failing to return id. // As seems to be a configuration issue, ignoring it return; @@ -274,24 +274,24 @@ fn test_chown_both_id() { fn test_chown_both_mix() { // test chown 1111:1111 file.txt let result = TestScenario::new("id").ucmd_keepenv().arg("-u").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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id_user = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let id_user = String::from(result.stdout_str().trim()); let result = TestScenario::new("id").ucmd_keepenv().arg("-gn").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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let group_name = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let group_name = String::from(result.stdout_str().trim()); let (at, mut ucmd) = at_and_ucmd!(); let file1 = "test_install_target_dir_file_a1"; @@ -301,7 +301,7 @@ fn test_chown_both_mix() { let result = ucmd.arg(perm).arg(file1).run(); - if is_ci() && result.stderr.contains("invalid user") { + if is_ci() && result.stderr_str().contains("invalid user") { // In the CI, some server are failing to return id. // As seems to be a configuration issue, ignoring it return; @@ -313,14 +313,14 @@ fn test_chown_both_mix() { fn test_chown_recursive() { 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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let username = result.stdout.trim_end(); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let username = result.stdout_str().trim_end(); let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("a"); @@ -339,31 +339,32 @@ fn test_chown_recursive() { .arg("a") .arg("z") .run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("invalid user") { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains("invalid user") { // In the CI, some server are failing to return id. // As seems to be a configuration issue, ignoring it return; } - assert!(result.stderr.contains("ownership of 'a/a' retained as")); - assert!(result.stderr.contains("ownership of 'z/y' retained as")); - assert!(result.success); + result + .stderr_contains(&"ownership of 'a/a' retained as") + .stderr_contains(&"ownership of 'z/y' retained as") + .success(); } #[test] fn test_root_preserve() { 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; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let username = result.stdout.trim_end(); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let username = result.stdout_str().trim_end(); let result = new_ucmd!() .arg("--preserve-root") @@ -371,9 +372,9 @@ fn test_root_preserve() { .arg(username) .arg("/") .fails(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("invalid user") { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains("invalid user") { // 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_chroot.rs b/tests/by-util/test_chroot.rs index 9a8fb71dd..05efd23ae 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -64,14 +64,14 @@ fn test_preference_of_userspec() { // As seems to be a configuration issue, ignoring it return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let username = result.stdout.trim_end(); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let username = result.stdout_str().trim_end(); let ts = TestScenario::new("id"); let result = ts.cmd("id").arg("-g").arg("-n").run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); if is_ci() && result.stderr.contains("cannot find name for user ID") { // In the CI, some server are failing to return id. @@ -79,7 +79,7 @@ fn test_preference_of_userspec() { return; } - let group_name = result.stdout.trim_end(); + let group_name = result.stdout_str().trim_end(); let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("a"); @@ -93,6 +93,6 @@ fn test_preference_of_userspec() { .arg(format!("--userspec={}:{}", username, group_name)) .run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1fa8212ca..07880d5c0 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -275,8 +275,8 @@ fn test_cp_arg_no_clobber_twice() { .arg("dest.txt") .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); + println!("stderr = {:?}", result.stderr_str()); + println!("stdout = {:?}", result.stdout_str()); assert!(result.success); assert!(result.stderr.is_empty()); assert_eq!(at.read("source.txt"), ""); @@ -317,8 +317,8 @@ fn test_cp_arg_force() { .arg(TEST_HELLO_WORLD_DEST) .run(); - println!("{:?}", result.stderr); - println!("{:?}", result.stdout); + println!("{:?}", result.stderr_str()); + println!("{:?}", result.stdout_str()); assert!(result.success); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); @@ -602,7 +602,7 @@ fn test_cp_deref_folder_to_folder() { .arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) .run(); - println!("cp output {}", result.stdout); + println!("cp output {}", result.stdout_str()); // Check that the exit code represents a successful copy. assert!(result.success); @@ -611,12 +611,12 @@ fn test_cp_deref_folder_to_folder() { { let scene2 = TestScenario::new("ls"); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); - println!("ls source {}", result.stdout); + println!("ls source {}", result.stdout_str()); let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); } #[cfg(windows)] @@ -706,7 +706,7 @@ fn test_cp_no_deref_folder_to_folder() { .arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_TO_FOLDER_NEW) .run(); - println!("cp output {}", result.stdout); + println!("cp output {}", result.stdout_str()); // Check that the exit code represents a successful copy. assert!(result.success); @@ -715,12 +715,12 @@ fn test_cp_no_deref_folder_to_folder() { { let scene2 = TestScenario::new("ls"); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); - println!("ls source {}", result.stdout); + println!("ls source {}", result.stdout_str()); let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); } #[cfg(windows)] @@ -809,7 +809,7 @@ fn test_cp_archive() { let scene2 = TestScenario::new("ls"); let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); assert_eq!(creation, creation2); assert!(result.success); } @@ -863,7 +863,7 @@ fn test_cp_archive_recursive() { .arg(&at.subdir.join(TEST_COPY_TO_FOLDER)) .run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); let scene2 = TestScenario::new("ls"); let result = scene2 @@ -872,7 +872,7 @@ fn test_cp_archive_recursive() { .arg(&at.subdir.join(TEST_COPY_TO_FOLDER_NEW)) .run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); assert!(at.file_exists( &at.subdir .join(TEST_COPY_TO_FOLDER_NEW) @@ -946,7 +946,7 @@ fn test_cp_preserve_timestamps() { let scene2 = TestScenario::new("ls"); let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); assert_eq!(creation, creation2); assert!(result.success); } @@ -984,7 +984,7 @@ fn test_cp_dont_preserve_timestamps() { let scene2 = TestScenario::new("ls"); let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run(); - println!("ls dest {}", result.stdout); + println!("ls dest {}", result.stdout_str()); println!("creation {:?} / {:?}", creation, creation2); assert_ne!(creation, creation2); diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 5619aed94..1933fdba3 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -28,13 +28,13 @@ fn test_date_rfc_3339() { // 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.trim())); + assert!(re.is_match(&result.stdout_str().trim())); 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.trim())); + assert!(re.is_match(&result.stdout_str().trim())); } #[test] @@ -73,13 +73,13 @@ fn test_date_format_y() { assert!(result.success); let mut re = Regex::new(r"^\d{4}$").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); result = scene.ucmd().arg("+%y").succeeds(); assert!(result.success); re = Regex::new(r"^\d{2}$").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); } #[test] @@ -90,13 +90,13 @@ fn test_date_format_m() { assert!(result.success); let mut re = Regex::new(r"\S+").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); result = scene.ucmd().arg("+%m").succeeds(); assert!(result.success); re = Regex::new(r"^\d{2}$").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); } #[test] @@ -107,20 +107,20 @@ fn test_date_format_day() { assert!(result.success); let mut re = Regex::new(r"\S+").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); result = scene.ucmd().arg("+%A").succeeds(); assert!(result.success); re = Regex::new(r"\S+").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); result = scene.ucmd().arg("+%u").succeeds(); assert!(result.success); re = Regex::new(r"^\d{1}$").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); } #[test] @@ -131,7 +131,7 @@ fn test_date_format_full_day() { assert!(result.success); let re = Regex::new(r"\S+ \d{4}-\d{2}-\d{2}").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str().trim())); } #[test] diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 30dcd9bb3..8f2cff65d 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -7,10 +7,9 @@ const SUB_LINK: &str = "subdir/links/sublink.txt"; #[test] fn test_du_basics() { - let (_at, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - assert!(result.success); - assert_eq!(result.stderr, ""); + new_ucmd!() + .succeeds() + .no_stderr(); } #[cfg(target_vendor = "apple")] fn _du_basics(s: String) { @@ -22,7 +21,7 @@ fn _du_basics(s: String) { assert_eq!(s, answer); } #[cfg(not(target_vendor = "apple"))] -fn _du_basics(s: String) { +fn _du_basics(s: &str) { let answer = "28\t./subdir 8\t./subdir/deeper 16\t./subdir/links @@ -38,19 +37,19 @@ fn test_du_basics_subdir() { let result = ucmd.arg(SUB_DIR).run(); assert!(result.success); assert_eq!(result.stderr, ""); - _du_basics_subdir(result.stdout); + _du_basics_subdir(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_basics_subdir(s: String) { +fn _du_basics_subdir(s: &str) { assert_eq!(s, "4\tsubdir/deeper\n"); } #[cfg(target_os = "windows")] -fn _du_basics_subdir(s: String) { +fn _du_basics_subdir(s: &str) { assert_eq!(s, "0\tsubdir/deeper\n"); } #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] -fn _du_basics_subdir(s: String) { +fn _du_basics_subdir(s: &str) { // MS-WSL linux has altered expected output if !is_wsl() { assert_eq!(s, "8\tsubdir/deeper\n"); @@ -64,7 +63,7 @@ fn test_du_basics_bad_name() { let (_at, mut ucmd) = at_and_ucmd!(); let result = ucmd.arg("bad_name").run(); - assert_eq!(result.stdout, ""); + assert_eq!(result.stdout_str(), ""); assert_eq!( result.stderr, "du: error: bad_name: No such file or directory\n" @@ -81,20 +80,20 @@ fn test_du_soft_link() { let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); assert!(result.success); assert_eq!(result.stderr, ""); - _du_soft_link(result.stdout); + _du_soft_link(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_soft_link(s: String) { +fn _du_soft_link(s: &str) { // 'macos' host variants may have `du` output variation for soft links assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n")); } #[cfg(target_os = "windows")] -fn _du_soft_link(s: String) { +fn _du_soft_link(s: &str) { assert_eq!(s, "8\tsubdir/links\n"); } #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] -fn _du_soft_link(s: String) { +fn _du_soft_link(s: &str) { // MS-WSL linux has altered expected output if !is_wsl() { assert_eq!(s, "16\tsubdir/links\n"); @@ -114,19 +113,19 @@ fn test_du_hard_link() { assert!(result.success); assert_eq!(result.stderr, ""); // We do not double count hard links as the inodes are identical - _du_hard_link(result.stdout); + _du_hard_link(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_hard_link(s: String) { +fn _du_hard_link(s: &str) { assert_eq!(s, "12\tsubdir/links\n") } #[cfg(target_os = "windows")] -fn _du_hard_link(s: String) { +fn _du_hard_link(s: &str) { assert_eq!(s, "8\tsubdir/links\n") } #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] -fn _du_hard_link(s: String) { +fn _du_hard_link(s: &str) { // MS-WSL linux has altered expected output if !is_wsl() { assert_eq!(s, "16\tsubdir/links\n"); @@ -142,19 +141,19 @@ fn test_du_d_flag() { let result = ts.ucmd().arg("-d").arg("1").run(); assert!(result.success); assert_eq!(result.stderr, ""); - _du_d_flag(result.stdout); + _du_d_flag(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_d_flag(s: String) { +fn _du_d_flag(s: &str) { assert_eq!(s, "16\t./subdir\n20\t./\n"); } #[cfg(target_os = "windows")] -fn _du_d_flag(s: String) { +fn _du_d_flag(s: &str) { assert_eq!(s, "8\t./subdir\n8\t./\n"); } #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] -fn _du_d_flag(s: String) { +fn _du_d_flag(s: &str) { // MS-WSL linux has altered expected output if !is_wsl() { assert_eq!(s, "28\t./subdir\n36\t./\n"); @@ -167,10 +166,11 @@ fn _du_d_flag(s: String) { fn test_du_h_flag_empty_file() { let ts = TestScenario::new("du"); - let result = ts.ucmd().arg("-h").arg("empty.txt").run(); - assert!(result.success); - assert_eq!(result.stderr, ""); - assert_eq!(result.stdout, "0\tempty.txt\n"); + ts.ucmd() + .arg("-h") + .arg("empty.txt") + .succeeds() + .stdout_only("0\tempty.txt\n"); } #[cfg(feature = "touch")] diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 7394ffc1e..99c8f3a1e 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -2,22 +2,20 @@ use crate::common::util::*; #[test] fn test_default() { - //CmdResult.stdout_only(...) trims trailing newlines - assert_eq!("hi\n", new_ucmd!().arg("hi").succeeds().no_stderr().stdout); + new_ucmd!() + .arg("hi") + .succeeds() + .stdout_only("hi\n"); } #[test] fn test_no_trailing_newline() { - //CmdResult.stdout_only(...) trims trailing newlines - assert_eq!( - "hi", - new_ucmd!() - .arg("-n") - .arg("hi") - .succeeds() - .no_stderr() - .stdout - ); + new_ucmd!() + .arg("-n") + .arg("hi") + .succeeds() + .no_stderr() + .stdout_only("hi"); } #[test] @@ -192,39 +190,38 @@ fn test_hyphen_values_inside_string() { new_ucmd!() .arg("'\"\n'CXXFLAGS=-g -O2'\n\"'") .succeeds() - .stdout - .contains("CXXFLAGS"); + .stdout_contains("CXXFLAGS"); } #[test] fn test_hyphen_values_at_start() { - let result = new_ucmd!() + new_ucmd!() .arg("-E") .arg("-test") .arg("araba") .arg("-merci") - .run(); - - assert!(result.success); - assert_eq!(false, result.stdout.contains("-E")); - assert_eq!(result.stdout, "-test araba -merci\n"); + .run() + .success() + .stdout_does_not_contain("-E") + .stdout_is("-test araba -merci\n"); } #[test] fn test_hyphen_values_between() { - let result = new_ucmd!().arg("test").arg("-E").arg("araba").run(); + new_ucmd!() + .arg("test") + .arg("-E") + .arg("araba") + .run() + .success() + .stdout_is("test -E araba\n"); - assert!(result.success); - assert_eq!(result.stdout, "test -E araba\n"); - - let result = new_ucmd!() + new_ucmd!() .arg("dumdum ") .arg("dum dum dum") .arg("-e") .arg("dum") - .run(); - - assert!(result.success); - assert_eq!(result.stdout, "dumdum dum dum dum -e dum\n"); - assert_eq!(true, result.stdout.contains("-e")); + .run() + .success() + .stdout_is("dumdum dum dum dum -e dum\n"); } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 2ffb2bc48..19ecd7afb 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -8,45 +8,35 @@ use tempfile::tempdir; #[test] fn test_env_help() { - assert!(new_ucmd!() + new_ucmd!() .arg("--help") .succeeds() .no_stderr() - .stdout - .contains("OPTIONS:")); + .stdout_contains("OPTIONS:"); } #[test] fn test_env_version() { - assert!(new_ucmd!() + new_ucmd!() .arg("--version") .succeeds() .no_stderr() - .stdout - .contains(util_name!())); + .stdout_contains(util_name!()); } #[test] fn test_echo() { - // assert!(new_ucmd!().arg("printf").arg("FOO-bar").succeeds().no_stderr().stdout.contains("FOO-bar")); - let mut cmd = new_ucmd!(); - cmd.arg("echo").arg("FOO-bar"); - println!("cmd={:?}", cmd); + let result = new_ucmd!() + .arg("echo") + .arg("FOO-bar") + .succeeds(); - let result = cmd.run(); - println!("success={:?}", result.success); - println!("stdout={:?}", result.stdout); - println!("stderr={:?}", result.stderr); - assert!(result.success); - - let out = result.stdout.trim_end(); - - assert_eq!(out, "FOO-bar"); + assert_eq!(result.stdout_str().trim(), "FOO-bar"); } #[test] fn test_file_option() { - let out = new_ucmd!().arg("-f").arg("vars.conf.txt").run().stdout; + let out = new_ucmd!().arg("-f").arg("vars.conf.txt").run().stdout_move_str(); assert_eq!( out.lines() @@ -63,7 +53,7 @@ fn test_combined_file_set() { .arg("vars.conf.txt") .arg("FOO=bar.alt") .run() - .stdout; + .stdout_move_str(); assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt").count(), 1); } @@ -76,8 +66,8 @@ fn test_combined_file_set_unset() { .arg("-f") .arg("vars.conf.txt") .arg("FOO=bar.alt") - .run() - .stdout; + .succeeds() + .stdout_move_str(); assert_eq!( out.lines() @@ -89,17 +79,17 @@ fn test_combined_file_set_unset() { #[test] fn test_single_name_value_pair() { - let out = new_ucmd!().arg("FOO=bar").run().stdout; + let out = new_ucmd!().arg("FOO=bar").run(); - assert!(out.lines().any(|line| line == "FOO=bar")); + assert!(out.stdout_str().lines().any(|line| line == "FOO=bar")); } #[test] fn test_multiple_name_value_pairs() { - let out = new_ucmd!().arg("FOO=bar").arg("ABC=xyz").run().stdout; + let out = new_ucmd!().arg("FOO=bar").arg("ABC=xyz").run(); assert_eq!( - out.lines() + out.stdout_str().lines() .filter(|&line| line == "FOO=bar" || line == "ABC=xyz") .count(), 2 @@ -110,13 +100,8 @@ fn test_multiple_name_value_pairs() { fn test_ignore_environment() { let scene = TestScenario::new(util_name!()); - let out = scene.ucmd().arg("-i").run().stdout; - - assert_eq!(out, ""); - - let out = scene.ucmd().arg("-").run().stdout; - - assert_eq!(out, ""); + scene.ucmd().arg("-i").run().no_stdout(); + scene.ucmd().arg("-").run().no_stdout(); } #[test] @@ -126,8 +111,8 @@ fn test_null_delimiter() { .arg("--null") .arg("FOO=bar") .arg("ABC=xyz") - .run() - .stdout; + .succeeds() + .stdout_move_str(); let mut vars: Vec<_> = out.split('\0').collect(); assert_eq!(vars.len(), 3); @@ -145,8 +130,8 @@ fn test_unset_variable() { .ucmd_keepenv() .arg("-u") .arg("HOME") - .run() - .stdout; + .succeeds() + .stdout_move_str(); assert_eq!(out.lines().any(|line| line.starts_with("HOME=")), false); } @@ -173,8 +158,8 @@ fn test_change_directory() { .arg("--chdir") .arg(&temporary_path) .arg(pwd) - .run() - .stdout; + .succeeds() + .stdout_move_str(); assert_eq!(out.trim(), temporary_path.as_os_str()) } @@ -193,8 +178,8 @@ fn test_change_directory() { .ucmd() .arg("--chdir") .arg(&temporary_path) - .run() - .stdout; + .succeeds() + .stdout_move_str(); assert_eq!( out.lines() .any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())), @@ -214,6 +199,6 @@ fn test_fail_change_directory() { .arg(some_non_existing_path) .arg("pwd") .fails() - .stderr; + .stderr_move_str(); assert!(out.contains("env: cannot change directory to ")); } diff --git a/tests/by-util/test_expand.rs b/tests/by-util/test_expand.rs index 801bf9d98..834a09736 100644 --- a/tests/by-util/test_expand.rs +++ b/tests/by-util/test_expand.rs @@ -2,57 +2,54 @@ use crate::common::util::*; #[test] fn test_with_tab() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("with-tab.txt").run(); - assert!(result.success); - assert!(result.stdout.contains(" ")); - assert!(!result.stdout.contains("\t")); + new_ucmd!() + .arg("with-tab.txt") + .succeeds() + .stdout_contains(" ") + .stdout_does_not_contain("\t"); } #[test] fn test_with_trailing_tab() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("with-trailing-tab.txt").run(); - assert!(result.success); - assert!(result.stdout.contains("with tabs=> ")); - assert!(!result.stdout.contains("\t")); + new_ucmd!() + .arg("with-trailing-tab.txt") + .succeeds() + .stdout_contains("with tabs=> ") + .stdout_does_not_contain("\t"); } #[test] fn test_with_trailing_tab_i() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("with-trailing-tab.txt").arg("-i").run(); - assert!(result.success); - assert!(result.stdout.contains(" // with tabs=>\t")); + new_ucmd!() + .arg("with-trailing-tab.txt") + .arg("-i") + .succeeds() + .stdout_contains(" // with tabs=>\t"); } #[test] fn test_with_tab_size() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("with-tab.txt").arg("--tabs=10").run(); - assert!(result.success); - assert!(result.stdout.contains(" ")); + new_ucmd!() + .arg("with-tab.txt") + .arg("--tabs=10") + .succeeds() + .stdout_contains(" "); } #[test] fn test_with_space() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("with-spaces.txt").run(); - assert!(result.success); - assert!(result.stdout.contains(" return")); + new_ucmd!() + .arg("with-spaces.txt") + .succeeds() + .stdout_contains(" return"); } #[test] fn test_with_multiple_files() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("with-spaces.txt").arg("with-tab.txt").run(); - assert!(result.success); - assert!(result.stdout.contains(" return")); - assert!(result.stdout.contains(" ")); + new_ucmd!() + .arg("with-spaces.txt") + .arg("with-tab.txt") + .succeeds() + .stdout_contains(" return") + .stdout_contains(" "); } diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 5bde17cdb..af2ff4ddb 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -32,13 +32,10 @@ fn test_first_100000_integers() { } println!("STDIN='{}'", instring); - let result = new_ucmd!().pipe_in(instring.as_bytes()).run(); - let stdout = result.stdout; - - assert!(result.success); + let result = new_ucmd!().pipe_in(instring.as_bytes()).succeeds(); // `seq 0 100000 | factor | sha1sum` => "4ed2d8403934fa1c76fe4b84c5d4b8850299c359" - let hash_check = sha1::Sha1::from(stdout.as_bytes()).hexdigest(); + let hash_check = sha1::Sha1::from(result.stdout()).hexdigest(); assert_eq!(hash_check, "4ed2d8403934fa1c76fe4b84c5d4b8850299c359"); } diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 4533cdf24..f962a9137 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -5,7 +5,7 @@ fn test_fmt() { let result = new_ucmd!().arg("one-word-per-line.txt").run(); //.stdout_is_fixture("call_graph.expected"); assert_eq!( - result.stdout.trim(), + result.stdout_str().trim(), "this is a file with one word per line" ); } @@ -15,7 +15,7 @@ fn test_fmt_q() { let result = new_ucmd!().arg("-q").arg("one-word-per-line.txt").run(); //.stdout_is_fixture("call_graph.expected"); assert_eq!( - result.stdout.trim(), + result.stdout_str().trim(), "this is a file with one word per line" ); } @@ -42,7 +42,7 @@ fn test_fmt_w() { .arg("one-word-per-line.txt") .run(); //.stdout_is_fixture("call_graph.expected"); - assert_eq!(result.stdout.trim(), "this is a file with one word per line"); + assert_eq!(result.stdout_str().trim(), "this is a file with one word per line"); } diff --git a/tests/by-util/test_groups.rs b/tests/by-util/test_groups.rs index 5c326fe2d..32a16cc1a 100644 --- a/tests/by-util/test_groups.rs +++ b/tests/by-util/test_groups.rs @@ -2,26 +2,25 @@ use crate::common::util::*; #[test] fn test_groups() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stdout.trim().is_empty() { + let result = new_ucmd!().run(); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stdout_str().trim().is_empty() { // In the CI, some server are failing to return the group. // As seems to be a configuration issue, ignoring it return; } assert!(result.success); - assert!(!result.stdout.trim().is_empty()); + assert!(!result.stdout_str().trim().is_empty()); } #[test] fn test_groups_arg() { // get the username with the "id -un" command let result = TestScenario::new("id").ucmd_keepenv().arg("-un").run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let s1 = String::from(result.stdout.trim()); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + let s1 = String::from(result.stdout_str().trim()); if is_ci() && s1.parse::().is_ok() { // In the CI, some server are failing to return id -un. // So, if we are getting a uid, just skip this test @@ -29,18 +28,18 @@ fn test_groups_arg() { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); assert!(result.success); - assert!(!result.stdout.is_empty()); - let username = result.stdout.trim(); + 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); - println!("result.stderr = {}", result.stderr); + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); assert!(result.success); - assert!(!result.stdout.is_empty()); + assert!(!result.stdout_str().is_empty()); } diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 6e7d59107..f059e53f3 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -17,14 +17,14 @@ macro_rules! test_digest { fn test_single_file() { let ts = TestScenario::new("hashsum"); assert_eq!(ts.fixtures.read(EXPECTED_FILE), - get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg("input.txt").succeeds().no_stderr().stdout)); + get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg("input.txt").succeeds().no_stderr().stdout_str())); } #[test] fn test_stdin() { let ts = TestScenario::new("hashsum"); assert_eq!(ts.fixtures.read(EXPECTED_FILE), - get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout)); + get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout_str())); } } )*) diff --git a/tests/by-util/test_hostid.rs b/tests/by-util/test_hostid.rs index 17aad4aff..b5b668901 100644 --- a/tests/by-util/test_hostid.rs +++ b/tests/by-util/test_hostid.rs @@ -9,5 +9,5 @@ fn test_normal() { assert!(result.success); let re = Regex::new(r"^[0-9a-f]{8}").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + assert!(re.is_match(&result.stdout_str())); } diff --git a/tests/by-util/test_hostname.rs b/tests/by-util/test_hostname.rs index 804d47642..9fa63241f 100644 --- a/tests/by-util/test_hostname.rs +++ b/tests/by-util/test_hostname.rs @@ -6,8 +6,8 @@ fn test_hostname() { let ls_short_res = new_ucmd!().arg("-s").succeeds(); let ls_domain_res = new_ucmd!().arg("-d").succeeds(); - assert!(ls_default_res.stdout.len() >= ls_short_res.stdout.len()); - assert!(ls_default_res.stdout.len() >= ls_domain_res.stdout.len()); + assert!(ls_default_res.stdout().len() >= ls_short_res.stdout().len()); + assert!(ls_default_res.stdout().len() >= ls_domain_res.stdout().len()); } // FixME: fails for "MacOS" @@ -17,14 +17,14 @@ fn test_hostname_ip() { let result = new_ucmd!().arg("-i").run(); println!("{:#?}", result); assert!(result.success); - assert!(!result.stdout.trim().is_empty()); + assert!(!result.stdout_str().trim().is_empty()); } #[test] fn test_hostname_full() { - let result = new_ucmd!().arg("-f").succeeds(); - assert!(!result.stdout.trim().is_empty()); - let ls_short_res = new_ucmd!().arg("-s").succeeds(); - assert!(result.stdout.trim().contains(ls_short_res.stdout.trim())); + assert!(!ls_short_res.stdout_str().trim().is_empty()); + + new_ucmd!().arg("-f").succeeds() + .stdout_contains(ls_short_res.stdout_str().trim()); } diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 116c73995..7e2791467 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -9,33 +9,29 @@ fn return_whoami_username() -> String { return String::from(""); } - result.stdout.trim().to_string() + result.stdout_str().trim().to_string() } #[test] fn test_id() { - let scene = TestScenario::new(util_name!()); - - let mut result = scene.ucmd().arg("-u").run(); + let result = new_ucmd!().arg("-u").run(); if result.stderr.contains("cannot find name for user ID") { // In the CI, some server are failing to return whoami. // As seems to be a configuration issue, ignoring it return; } - assert!(result.success); - let uid = String::from(result.stdout.trim()); - result = scene.ucmd().run(); + let uid = result.success().stdout_str().trim(); + let result = new_ucmd!().run(); if is_ci() && result.stderr.contains("cannot find name for user ID") { // In the CI, some server are failing to return whoami. // As seems to be a configuration issue, ignoring it return; } - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if !result.stderr.contains("Could not find uid") { + + if !result.stderr_str().contains("Could not find uid") { // Verify that the id found by --user/-u exists in the list - assert!(result.stdout.contains(&uid)); + result.success().stdout_contains(&uid); } } @@ -47,88 +43,64 @@ fn test_id_from_name() { return; } - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg(&username).succeeds(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - let uid = String::from(result.stdout.trim()); - let result = scene.ucmd().succeeds(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - // Verify that the id found by --user/-u exists in the list - assert!(result.stdout.contains(&uid)); - // Verify that the username found by whoami exists in the list - assert!(result.stdout.contains(&username)); + let result = new_ucmd!().arg(&username).succeeds(); + let uid = result.stdout_str().trim(); + + new_ucmd!().succeeds() + // Verify that the id found by --user/-u exists in the list + .stdout_contains(uid) + // Verify that the username found by whoami exists in the list + .stdout_contains(username); } #[test] fn test_id_name_from_id() { - let mut scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-u").run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - let uid = String::from(result.stdout.trim()); + let result = new_ucmd!().arg("-u").succeeds(); + let uid = result.stdout_str().trim(); - scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-nu").arg(uid).run(); + let result = new_ucmd!().arg("-nu").arg(uid).run(); if is_ci() && result.stderr.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; } - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - let username_id = String::from(result.stdout.trim()); + let username_id = result + .success() + .stdout_str() + .trim(); - scene = TestScenario::new("whoami"); - let result = scene.cmd("whoami").run(); + let scene = TestScenario::new("whoami"); + let result = scene.cmd("whoami").succeeds(); - let username_whoami = result.stdout.trim(); + let username_whoami = result.stdout_str().trim(); assert_eq!(username_id, username_whoami); } #[test] fn test_id_group() { - let scene = TestScenario::new(util_name!()); - - let mut result = scene.ucmd().arg("-g").succeeds(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - let s1 = String::from(result.stdout.trim()); + let mut result = new_ucmd!().arg("-g").succeeds(); + let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); - result = scene.ucmd().arg("--group").succeeds(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - let s1 = String::from(result.stdout.trim()); + result = new_ucmd!().arg("--group").succeeds(); + let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); } #[test] fn test_id_groups() { - let scene = TestScenario::new(util_name!()); - - let result = scene.ucmd().arg("-G").succeeds(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); + let result = new_ucmd!().arg("-G").succeeds(); assert!(result.success); - let groups = result.stdout.trim().split_whitespace(); + let groups = result.stdout_str().trim().split_whitespace(); for s in groups { assert!(s.parse::().is_ok()); } - let result = scene.ucmd().arg("--groups").succeeds(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); + let result = new_ucmd!().arg("--groups").succeeds(); assert!(result.success); - let groups = result.stdout.trim().split_whitespace(); + let groups = result.stdout_str().trim().split_whitespace(); for s in groups { assert!(s.parse::().is_ok()); } @@ -136,15 +108,12 @@ fn test_id_groups() { #[test] fn test_id_user() { - let scene = TestScenario::new(util_name!()); - - let mut result = scene.ucmd().arg("-u").succeeds(); - assert!(result.success); - let s1 = String::from(result.stdout.trim()); + let mut result = new_ucmd!().arg("-u").succeeds(); + let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); - result = scene.ucmd().arg("--user").succeeds(); - assert!(result.success); - let s1 = String::from(result.stdout.trim()); + + result = new_ucmd!().arg("--user").succeeds(); + let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); } @@ -156,17 +125,13 @@ fn test_id_pretty_print() { return; } - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-p").run(); - if result.stdout.trim() == "" { + let result = new_ucmd!().arg("-p").run(); + if result.stdout_str().trim() == "" { // Sometimes, the CI is failing here with // old rust versions on Linux return; } - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - assert!(result.stdout.contains(&username)); + result.success().stdout_contains(username); } #[test] @@ -176,12 +141,7 @@ fn test_id_password_style() { // Sometimes, the CI is failing here return; } - let scene = TestScenario::new(util_name!()); - let result = scene.ucmd().arg("-P").succeeds(); - - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); - assert!(result.stdout.starts_with(&username)); + let result = new_ucmd!().arg("-P").succeeds(); + assert!(result.stdout_str().starts_with(&username)); } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 840b2f6c7..32df8d460 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -195,12 +195,8 @@ fn test_install_mode_numeric() { let mode_arg = "-m 0333"; at.mkdir(dir2); - let result = scene.ucmd().arg(mode_arg).arg(file).arg(dir2).run(); + scene.ucmd().arg(mode_arg).arg(file).arg(dir2).succeeds(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - - assert!(result.success); let dest_file = &format!("{}/{}", dir2, file); assert!(at.file_exists(file)); assert!(at.file_exists(dest_file)); @@ -313,16 +309,13 @@ fn test_install_target_new_file_with_group() { .arg(format!("{}/{}", dir, file)) .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - - if is_ci() && result.stderr.contains("error: no such group:") { + if is_ci() && result.stderr_str().contains("error: no such group:") { // In the CI, some server are failing to return the group. // As seems to be a configuration issue, ignoring it return; } - assert!(result.success); + result.success(); assert!(at.file_exists(file)); assert!(at.file_exists(&format!("{}/{}", dir, file))); } @@ -343,16 +336,13 @@ fn test_install_target_new_file_with_owner() { .arg(format!("{}/{}", dir, file)) .run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - if is_ci() && result.stderr.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; } - assert!(result.success); + result.success(); assert!(at.file_exists(file)); assert!(at.file_exists(&format!("{}/{}", dir, file))); } @@ -366,13 +356,10 @@ fn test_install_target_new_file_failing_nonexistent_parent() { at.touch(file1); - let err = ucmd - .arg(file1) + ucmd.arg(file1) .arg(format!("{}/{}", dir, file2)) .fails() - .stderr; - - assert!(err.contains("not a directory")) + .stderr_contains(&"not a directory"); } #[test] @@ -417,18 +404,12 @@ fn test_install_copy_file() { #[test] #[cfg(target_os = "linux")] fn test_install_target_file_dev_null() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; + let (at, mut ucmd) = at_and_ucmd!(); let file1 = "/dev/null"; let file2 = "target_file"; - let result = scene.ucmd().arg(file1).arg(file2).run(); - - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - - assert!(result.success); + ucmd.arg(file1).arg(file2).succeeds(); assert!(at.file_exists(file2)); } diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 89261036d..d7a13b0d4 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -520,10 +520,7 @@ fn test_symlink_no_deref_dir() { scene.ucmd().args(&["-sn", dir1, link]).fails(); // Try with the no-deref - let result = scene.ucmd().args(&["-sfn", dir1, link]).run(); - println!("stdout {}", result.stdout); - println!("stderr {}", result.stderr); - assert!(result.success); + scene.ucmd().args(&["-sfn", dir1, link]).succeeds(); assert!(at.dir_exists(dir1)); assert!(at.dir_exists(dir2)); assert!(at.is_symlink(link)); @@ -566,10 +563,7 @@ fn test_symlink_no_deref_file() { scene.ucmd().args(&["-sn", file1, link]).fails(); // Try with the no-deref - let result = scene.ucmd().args(&["-sfn", file1, link]).run(); - println!("stdout {}", result.stdout); - println!("stderr {}", result.stderr); - assert!(result.success); + scene.ucmd().args(&["-sfn", file1, link]).succeeds(); assert!(at.file_exists(file1)); assert!(at.file_exists(file2)); assert!(at.is_symlink(link)); diff --git a/tests/by-util/test_logname.rs b/tests/by-util/test_logname.rs index b15941c06..8d1996e63 100644 --- a/tests/by-util/test_logname.rs +++ b/tests/by-util/test_logname.rs @@ -3,23 +3,19 @@ use std::env; #[test] fn test_normal() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); + let result = new_ucmd!().run(); println!("env::var(CI).is_ok() = {}", env::var("CI").is_ok()); for (key, value) in env::vars() { println!("{}: {}", key, value); } - if (is_ci() || is_wsl()) && result.stderr.contains("error: no login name") { + if (is_ci() || is_wsl()) && result.stderr_str().contains("error: no login name") { // ToDO: investigate WSL failure // In the CI, some server are failing to return logname. // As seems to be a configuration issue, ignoring it return; } - assert!(result.success); - assert!(!result.stdout.trim().is_empty()); + result.success(); + assert!(!result.stdout_str().trim().is_empty()); } From cd3dba24816a6b01a6af85716ecb31e7b2114fa4 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 10 Apr 2021 21:24:30 +0300 Subject: [PATCH 020/399] Added some tests utils for future refactoring --- tests/common/macros.rs | 37 -------------------------- tests/common/util.rs | 59 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/tests/common/macros.rs b/tests/common/macros.rs index e8b9c9d5d..81878bf1b 100644 --- a/tests/common/macros.rs +++ b/tests/common/macros.rs @@ -1,40 +1,3 @@ -/// Assertion helper macro for [`CmdResult`] types -/// -/// [`CmdResult`]: crate::tests::common::util::CmdResult -#[macro_export] -macro_rules! assert_empty_stderr( - ($cond:expr) => ( - if $cond.stderr.len() > 0 { - panic!("stderr: {}", $cond.stderr_str()) - } - ); -); - -/// Assertion helper macro for [`CmdResult`] types -/// -/// [`CmdResult`]: crate::tests::common::util::CmdResult -#[macro_export] -macro_rules! assert_empty_stdout( - ($cond:expr) => ( - if $cond.stdout.len() > 0 { - panic!("stdout: {}", $cond.stdout_str()) - } - ); -); - -/// Assertion helper macro for [`CmdResult`] types -/// -/// [`CmdResult`]: crate::tests::common::util::CmdResult -#[macro_export] -macro_rules! assert_no_error( - ($cond:expr) => ( - assert!($cond.success); - if $cond.stderr.len() > 0 { - panic!("stderr: {}", $cond.stderr_str()) - } - ); -); - /// Platform-independent helper for constructing a PathBuf from individual elements #[macro_export] macro_rules! path_concat { diff --git a/tests/common/util.rs b/tests/common/util.rs index d5663e9ed..18830919c 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -130,6 +130,11 @@ impl CmdResult { self.code.expect("Program must be run first") } + pub fn code_is(&self, expected_code: i32) -> &CmdResult { + assert_eq!(self.code(), expected_code); + self + } + /// Returns the program's TempDir /// Panics if not present pub fn tmpd(&self) -> Rc { @@ -146,13 +151,25 @@ impl CmdResult { /// asserts that the command resulted in a success (zero) status code pub fn success(&self) -> &CmdResult { - assert!(self.success); + if !self.success { + panic!( + "Command was expected to succeed.\nstdout = {}\n stderr = {}", + self.stdout_str(), + self.stderr_str() + ); + } self } /// asserts that the command resulted in a failure (non-zero) status code pub fn failure(&self) -> &CmdResult { - assert!(!self.success); + if self.success { + panic!( + "Command was expected to fail.\nstdout = {}\n stderr = {}", + self.stdout_str(), + self.stderr_str() + ); + } self } @@ -168,7 +185,12 @@ impl CmdResult { /// 1. you can not know exactly what stdout will be or /// 2. you know that stdout will also be empty pub fn no_stderr(&self) -> &CmdResult { - assert!(self.stderr.is_empty()); + if !self.stderr.is_empty() { + panic!( + "Expected stderr to be empty, but it's:\n{}", + self.stderr_str() + ); + } self } @@ -179,7 +201,12 @@ impl CmdResult { /// 1. you can not know exactly what stderr will be or /// 2. you know that stderr will also be empty pub fn no_stdout(&self) -> &CmdResult { - assert!(self.stdout.is_empty()); + if !self.stdout.is_empty() { + panic!( + "Expected stdout to be empty, but it's:\n{}", + self.stderr_str() + ); + } self } @@ -281,6 +308,30 @@ impl CmdResult { assert!(self.stderr_str().contains(cmp.as_ref())); self } + + pub fn stdout_does_not_contain>(&self, cmp: T) -> &CmdResult { + assert!(!self.stdout_str().contains(cmp.as_ref())); + self + } + + pub fn stderr_does_not_contain>(&self, cmp: &T) -> &CmdResult { + assert!(!self.stderr_str().contains(cmp.as_ref())); + self + } + + pub fn stdout_matches(&self, regex: ®ex::Regex) -> &CmdResult { + if !regex.is_match(self.stdout_str()) { + 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()) { + panic!("Stdout matches regex:\n{}", self.stdout_str()) + } + self + } } pub fn log_info, U: AsRef>(msg: T, par: U) { From 4695667c7ce7230f75fb39d0a232e0ad5564e7d4 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 10 Apr 2021 21:18:38 +0300 Subject: [PATCH 021/399] Added sanity checks for test utils --- tests/common/util.rs | 214 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 3 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 18830919c..aca8ef216 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -64,7 +64,7 @@ fn read_scenario_fixture>(tmpd: &Option>, file_rel_p /// A command result is the outputs of a command (streams and status code) /// within a struct which has convenience assertion functions about those outputs -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CmdResult { //tmpd is used for convenience functions for asserts against fixtures tmpd: Option>, @@ -304,7 +304,7 @@ impl CmdResult { self } - pub fn stderr_contains>(&self, cmp: &T) -> &CmdResult { + pub fn stderr_contains>(&self, cmp: T) -> &CmdResult { assert!(self.stderr_str().contains(cmp.as_ref())); self } @@ -314,7 +314,7 @@ impl CmdResult { self } - pub fn stderr_does_not_contain>(&self, cmp: &T) -> &CmdResult { + pub fn stderr_does_not_contain>(&self, cmp: T) -> &CmdResult { assert!(!self.stderr_str().contains(cmp.as_ref())); self } @@ -870,3 +870,211 @@ pub fn vec_of_size(n: usize) -> Vec { assert_eq!(result.len(), n); result } + +/// Sanity checks for test utils +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_code_is() { + let res = CmdResult { + tmpd: None, + code: Some(32), + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.code_is(32); + } + + #[test] + #[should_panic] + fn test_code_is_fail() { + let res = CmdResult { + tmpd: None, + code: Some(32), + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.code_is(1); + } + + #[test] + fn test_failure() { + let res = CmdResult { + tmpd: None, + code: None, + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.failure(); + } + + #[test] + #[should_panic] + fn test_failure_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "".into(), + }; + res.failure(); + } + + #[test] + fn test_success() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "".into(), + }; + res.success(); + } + + #[test] + #[should_panic] + fn test_success_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: false, + stdout: "".into(), + stderr: "".into(), + }; + res.success(); + } + + #[test] + fn test_no_std_errout() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "".into(), + }; + res.no_stderr(); + res.no_stdout(); + } + + #[test] + #[should_panic] + fn test_no_stderr_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "asdfsadfa".into(), + }; + + res.no_stderr(); + } + + #[test] + #[should_panic] + fn test_no_stdout_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "asdfsadfa".into(), + stderr: "".into(), + }; + + res.no_stdout(); + } + + #[test] + fn test_std_does_not_contain() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + res.stdout_does_not_contain("unlikely"); + res.stderr_does_not_contain("unlikely"); + } + + #[test] + #[should_panic] + fn test_stdout_does_not_contain_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "".into(), + }; + + res.stdout_does_not_contain("likely"); + } + + #[test] + #[should_panic] + fn test_stderr_does_not_contain_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "".into(), + stderr: "This is a likely error message\n".into(), + }; + + res.stderr_does_not_contain("likely"); + } + + #[test] + fn test_stdout_matches() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + let positive = regex::Regex::new(".*likely.*").unwrap(); + let negative = regex::Regex::new(".*unlikely.*").unwrap(); + res.stdout_matches(&positive); + res.stdout_does_not_match(&negative); + } + + #[test] + #[should_panic] + fn test_stdout_matches_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + let negative = regex::Regex::new(".*unlikely.*").unwrap(); + + res.stdout_matches(&negative); + } + + #[test] + #[should_panic] + fn test_stdout_not_matches_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "This is a likely error message\n".into(), + stderr: "This is a likely error message\n".into(), + }; + let positive = regex::Regex::new(".*likely.*").unwrap(); + + res.stdout_does_not_match(&positive); + } +} From 5f00a0f9079638b58f0099a3244e9af30c599593 Mon Sep 17 00:00:00 2001 From: ReggaeMuffin <644950+reggaemuffin@users.noreply.github.com> Date: Sun, 11 Apr 2021 10:49:52 +0100 Subject: [PATCH 022/399] remove the unused imports --- tests/by-util/test_chgrp.rs | 1 - tests/by-util/test_date.rs | 1 - tests/by-util/test_du.rs | 1 - tests/by-util/test_logname.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index ffb078137..fa5d71bc7 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -1,6 +1,5 @@ use crate::common::util::*; use rust_users::*; -use uucore; #[test] fn test_invalid_option() { diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index b4e49e775..b736a9401 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -4,7 +4,6 @@ use self::regex::Regex; use crate::common::util::*; #[cfg(all(unix, not(target_os = "macos")))] use rust_users::*; -use uucore; #[test] fn test_date_email() { diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index a0d698de0..4543b804a 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -1,5 +1,4 @@ use crate::common::util::*; -use uucore; const SUB_DIR: &str = "subdir/deeper"; const SUB_DIR_LINKS: &str = "subdir/links"; diff --git a/tests/by-util/test_logname.rs b/tests/by-util/test_logname.rs index baaad63cc..b95265a8f 100644 --- a/tests/by-util/test_logname.rs +++ b/tests/by-util/test_logname.rs @@ -1,6 +1,5 @@ use crate::common::util::*; use std::env; -use uucore; #[test] fn test_normal() { From 97d12d6e3c7b8afcde13555e34e35545b7f0a96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Sun, 11 Apr 2021 16:05:25 +0200 Subject: [PATCH 023/399] fix trivial warnings without features --- src/uu/basename/src/basename.rs | 10 +-- src/uu/cp/src/cp.rs | 16 ++--- src/uu/cut/src/cut.rs | 66 +++++++++---------- src/uu/du/src/du.rs | 2 +- src/uu/expr/src/expr.rs | 2 +- src/uu/expr/src/syntax_tree.rs | 36 +++++----- src/uu/fmt/src/parasplit.rs | 2 +- src/uu/fold/src/fold.rs | 2 +- src/uu/head/src/head.rs | 2 +- src/uu/ln/src/ln.rs | 14 ++-- src/uu/ls/src/ls.rs | 8 +-- src/uu/mv/src/mv.rs | 16 ++--- src/uu/od/src/od.rs | 2 +- src/uu/od/src/parse_inputs.rs | 6 +- .../num_format/formatters/base_conv/mod.rs | 6 +- src/uu/readlink/src/readlink.rs | 6 +- src/uu/realpath/src/realpath.rs | 4 +- src/uu/rm/src/rm.rs | 2 +- src/uu/seq/src/seq.rs | 8 +-- src/uu/shred/src/shred.rs | 10 ++- src/uu/sum/src/sum.rs | 2 +- src/uu/tac/src/tac.rs | 2 +- src/uu/test/src/test.rs | 24 +++---- src/uu/tr/src/expand.rs | 2 +- src/uu/tsort/src/tsort.rs | 17 +++-- src/uu/wc/src/count_bytes.rs | 31 ++++----- src/uu/wc/src/wc.rs | 19 +++--- 27 files changed, 151 insertions(+), 166 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 84521bdd1..b7f99af27 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -113,12 +113,8 @@ fn basename(fullname: &str, suffix: &str) -> String { fn strip_suffix(name: &str, suffix: &str) -> String { if name == suffix { - return name.to_owned(); + name.to_owned() + } else { + name.strip_suffix(suffix).unwrap_or(name).to_owned() } - - if name.ends_with(suffix) { - return name[..name.len() - suffix.len()].to_owned(); - } - - name.to_owned() } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 569ee78bc..60484a79a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -547,14 +547,13 @@ impl FromStr for Attribute { } fn add_all_attributes() -> Vec { - let mut attr = Vec::new(); + use Attribute::*; + + let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links]; + #[cfg(unix)] - attr.push(Attribute::Mode); - attr.push(Attribute::Ownership); - attr.push(Attribute::Timestamps); - attr.push(Attribute::Context); - attr.push(Attribute::Xattr); - attr.push(Attribute::Links); + attr.insert(0, Mode); + attr } @@ -714,7 +713,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec, - source: &std::path::PathBuf, + source: &std::path::Path, dest: std::path::PathBuf, found_hard_link: &mut bool, ) -> CopyResult<()> { @@ -1068,6 +1067,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu } #[cfg(not(windows))] +#[allow(clippy::unnecessary_wraps)] // needed for windows version fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { match std::os::unix::fs::symlink(source, dest).context(context) { Ok(_) => Ok(()), diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 6b09b91d9..5bf310daa 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -406,7 +406,7 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { continue; } - if !path.metadata().is_ok() { + if path.metadata().is_err() { show_error!("{}: No such file or directory", filename); continue; } @@ -487,7 +487,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("filter field columns from the input source") .takes_value(true) .allow_hyphen_values(true) - .value_name("LIST") + .value_name("LIST") .display_order(4), ) .arg( @@ -535,40 +535,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 { matches.value_of(options::CHARACTERS), matches.value_of(options::FIELDS), ) { - (Some(byte_ranges), None, None) => { - list_to_ranges(&byte_ranges[..], complement).map(|ranges| { - Mode::Bytes( - ranges, - Options { - out_delim: Some( - matches - .value_of(options::OUTPUT_DELIMITER) - .unwrap_or_default() - .to_owned(), - ), - zero_terminated: matches.is_present(options::ZERO_TERMINATED), - }, - ) - }) - } - (None, Some(char_ranges), None) => { - list_to_ranges(&char_ranges[..], complement).map(|ranges| { - Mode::Characters( - ranges, - Options { - out_delim: Some( - matches - .value_of(options::OUTPUT_DELIMITER) - .unwrap_or_default() - .to_owned(), - ), - zero_terminated: matches.is_present(options::ZERO_TERMINATED), - }, - ) - }) - } + (Some(byte_ranges), None, None) => list_to_ranges(byte_ranges, complement).map(|ranges| { + Mode::Bytes( + ranges, + Options { + out_delim: Some( + matches + .value_of(options::OUTPUT_DELIMITER) + .unwrap_or_default() + .to_owned(), + ), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), + }, + ) + }), + (None, Some(char_ranges), None) => list_to_ranges(char_ranges, complement).map(|ranges| { + Mode::Characters( + ranges, + Options { + out_delim: Some( + matches + .value_of(options::OUTPUT_DELIMITER) + .unwrap_or_default() + .to_owned(), + ), + zero_terminated: matches.is_present(options::ZERO_TERMINATED), + }, + ) + }), (None, None, Some(field_ranges)) => { - list_to_ranges(&field_ranges[..], complement).and_then(|ranges| { + list_to_ranges(field_ranges, complement).and_then(|ranges| { let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) { Some(s) => { if s.is_empty() { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 615b66a4e..07635881a 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -322,7 +322,7 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String { } } if size == 0 { - return format!("0"); + return "0".to_string(); } format!("{}B", size) } diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index fee85dfe1..4a13812d3 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -51,7 +51,7 @@ fn print_expr_error(expr_error: &str) -> ! { crash!(2, "{}", expr_error) } -fn evaluate_ast(maybe_ast: Result, String>) -> Result { +fn evaluate_ast(maybe_ast: Result, String>) -> Result { if maybe_ast.is_err() { Err(maybe_ast.err().unwrap()) } else { diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 3381c29bd..c81adf0c8 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -17,10 +17,10 @@ use onig::{Regex, RegexOptions, Syntax}; use crate::tokens::Token; type TokenStack = Vec<(usize, Token)>; -pub type OperandsList = Vec>; +pub type OperandsList = Vec>; #[derive(Debug)] -pub enum ASTNode { +pub enum AstNode { Leaf { token_idx: usize, value: String, @@ -31,7 +31,7 @@ pub enum ASTNode { operands: OperandsList, }, } -impl ASTNode { +impl AstNode { fn debug_dump(&self) { self.debug_dump_impl(1); } @@ -40,7 +40,7 @@ impl ASTNode { print!("\t",); } match *self { - ASTNode::Leaf { + AstNode::Leaf { ref token_idx, ref value, } => println!( @@ -49,7 +49,7 @@ impl ASTNode { token_idx, self.evaluate() ), - ASTNode::Node { + AstNode::Node { ref token_idx, ref op_type, ref operands, @@ -67,23 +67,23 @@ impl ASTNode { } } - fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box { - Box::new(ASTNode::Node { + fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box { + Box::new(AstNode::Node { token_idx, op_type: op_type.into(), operands, }) } - fn new_leaf(token_idx: usize, value: &str) -> Box { - Box::new(ASTNode::Leaf { + fn new_leaf(token_idx: usize, value: &str) -> Box { + Box::new(AstNode::Leaf { token_idx, value: value.into(), }) } pub fn evaluate(&self) -> Result { match *self { - ASTNode::Leaf { ref value, .. } => Ok(value.clone()), - ASTNode::Node { ref op_type, .. } => match self.operand_values() { + AstNode::Leaf { ref value, .. } => Ok(value.clone()), + AstNode::Node { ref op_type, .. } => match self.operand_values() { Err(reason) => Err(reason), Ok(operand_values) => match op_type.as_ref() { "+" => infix_operator_two_ints( @@ -161,7 +161,7 @@ impl ASTNode { } } pub fn operand_values(&self) -> Result, String> { - if let ASTNode::Node { ref operands, .. } = *self { + if let AstNode::Node { ref operands, .. } = *self { let mut out = Vec::with_capacity(operands.len()); for operand in operands { match operand.evaluate() { @@ -178,7 +178,7 @@ impl ASTNode { pub fn tokens_to_ast( maybe_tokens: Result, String>, -) -> Result, String> { +) -> Result, String> { if maybe_tokens.is_err() { Err(maybe_tokens.err().unwrap()) } else { @@ -212,7 +212,7 @@ pub fn tokens_to_ast( } } -fn maybe_dump_ast(result: &Result, String>) { +fn maybe_dump_ast(result: &Result, String>) { use std::env; if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") { if debug_var == "1" { @@ -238,11 +238,11 @@ fn maybe_dump_rpn(rpn: &TokenStack) { } } -fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { +fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { match rpn.pop() { None => Err("syntax error (premature end of expression)".to_owned()), - Some((token_idx, Token::Value { value })) => Ok(ASTNode::new_leaf(token_idx, &value)), + Some((token_idx, Token::Value { value })) => Ok(AstNode::new_leaf(token_idx, &value)), Some((token_idx, Token::InfixOp { value, .. })) => { maybe_ast_node(token_idx, &value, 2, rpn) @@ -262,7 +262,7 @@ fn maybe_ast_node( op_type: &str, arity: usize, rpn: &mut TokenStack, -) -> Result, String> { +) -> Result, String> { let mut operands = Vec::with_capacity(arity); for _ in 0..arity { match ast_from_rpn(rpn) { @@ -271,7 +271,7 @@ fn maybe_ast_node( } } operands.reverse(); - Ok(ASTNode::new_node(token_idx, op_type, operands)) + Ok(AstNode::new_node(token_idx, op_type, operands)) } fn move_rest_of_ops_to_out( diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index f74a25413..950b3f66d 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -267,7 +267,7 @@ impl<'a> ParagraphStream<'a> { #[allow(clippy::match_like_matches_macro)] // `matches!(...)` macro not stabilized until rust v1.42 l_slice[..colon_posn].chars().all(|x| match x as usize { - y if y < 33 || y > 126 => false, + y if !(33..=126).contains(&y) => false, _ => true, }) } diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index c35e996f2..fa703eade 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -66,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true), ) .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) - .get_matches_from(args.clone()); + .get_matches_from(args); let bytes = matches.is_present(options::BYTES); let spaces = matches.is_present(options::SPACES); diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 3500af544..807d04314 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -625,7 +625,7 @@ mod tests { assert_eq!(arg_outputs("head"), Ok("head".to_owned())); } #[test] - #[cfg(linux)] + #[cfg(target_os = "linux")] fn test_arg_iterate_bad_encoding() { let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") }; // this arises from a conversion from OsString to &str diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 96a0df813..04358a415 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -303,7 +303,7 @@ fn exec(files: &[PathBuf], settings: &Settings) -> i32 { } } -fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Settings) -> i32 { +fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> i32 { if !target_dir.is_dir() { show_error!("target '{}' is not a directory", target_dir.display()); return 1; @@ -329,7 +329,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting }; } } - target_dir.clone() + target_dir.to_path_buf() } else { match srcpath.as_os_str().to_str() { Some(name) => { @@ -370,7 +370,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting } } -fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result> { +fn relative_path<'a>(src: &Path, dst: &Path) -> Result> { let abssrc = canonicalize(src, CanonicalizeMode::Normal)?; let absdst = canonicalize(dst, CanonicalizeMode::Normal)?; let suffix_pos = abssrc @@ -390,7 +390,7 @@ fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result> { Ok(result.into()) } -fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> { +fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { let mut backup_path = None; let source: Cow<'_, Path> = if settings.relative { relative_path(&src, dst)? @@ -453,13 +453,13 @@ fn read_yes() -> bool { } } -fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { +fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { let mut p = path.as_os_str().to_str().unwrap().to_owned(); p.push_str(suffix); PathBuf::from(p) } -fn numbered_backup_path(path: &PathBuf) -> PathBuf { +fn numbered_backup_path(path: &Path) -> PathBuf { let mut i: u64 = 1; loop { let new_path = simple_backup_path(path, &format!(".~{}~", i)); @@ -470,7 +470,7 @@ fn numbered_backup_path(path: &PathBuf) -> PathBuf { } } -fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { +fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { let test_path = simple_backup_path(path, &".~1~".to_owned()); if test_path.exists() { return numbered_backup_path(path); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index fdc11144a..4024328b5 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1076,7 +1076,7 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { true } -fn enter_directory(dir: &PathBuf, config: &Config) { +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)); @@ -1101,7 +1101,7 @@ fn enter_directory(dir: &PathBuf, config: &Config) { } } -fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result { +fn get_metadata(entry: &Path, config: &Config) -> std::io::Result { if config.dereference { entry.metadata().or_else(|_| entry.symlink_metadata()) } else { @@ -1109,7 +1109,7 @@ fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result { } } -fn display_dir_entry_size(entry: &PathBuf, config: &Config) -> (usize, usize) { +fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) { if let Ok(md) = get_metadata(entry, config) { ( display_symlink_count(&md).len(), @@ -1204,7 +1204,7 @@ fn display_grid(names: impl Iterator, width: u16, direction: Direct use uucore::fs::display_permissions; fn display_item_long( - item: &PathBuf, + item: &Path, strip: Option<&Path>, max_links: usize, max_size: usize, diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index b481aeebc..f57178a09 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -335,7 +335,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { 0 } -fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 { +fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 { if !target_dir.is_dir() { show_error!("target ‘{}’ is not a directory", target_dir.display()); return 1; @@ -373,7 +373,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> } } -fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> { +fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { let mut backup_path = None; if to.exists() { @@ -429,7 +429,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> { /// A wrapper around `fs::rename`, so that if it fails, we try falling back on /// copying and removing. -fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { +fn rename_with_fallback(from: &Path, to: &Path) -> io::Result<()> { if fs::rename(from, to).is_err() { // Get metadata without following symlinks let metadata = from.symlink_metadata()?; @@ -464,7 +464,7 @@ fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { /// Move the given symlink to the given destination. On Windows, dangling /// symlinks return an error. #[inline] -fn rename_symlink_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { +fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> { let path_symlink_points_to = fs::read_link(from)?; #[cfg(unix)] { @@ -507,20 +507,20 @@ fn read_yes() -> bool { } } -fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { +fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { let mut p = path.to_string_lossy().into_owned(); p.push_str(suffix); PathBuf::from(p) } -fn numbered_backup_path(path: &PathBuf) -> PathBuf { +fn numbered_backup_path(path: &Path) -> PathBuf { (1_u64..) .map(|i| path.with_extension(format!("~{}~", i))) .find(|p| !p.exists()) .expect("cannot create backup") } -fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { +fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { let test_path = path.with_extension("~1~"); if test_path.exists() { numbered_backup_path(path) @@ -529,7 +529,7 @@ fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { } } -fn is_empty_dir(path: &PathBuf) -> bool { +fn is_empty_dir(path: &Path) -> bool { match fs::read_dir(path) { Ok(contents) => contents.peekable().peek().is_none(), Err(_e) => false, diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index c3b39fca1..36eae66ab 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -118,7 +118,7 @@ struct OdOptions { } impl OdOptions { - fn new<'a>(matches: ArgMatches<'a>, args: Vec) -> Result { + fn new(matches: ArgMatches, args: Vec) -> Result { let byte_order = match matches.value_of(options::ENDIAN) { None => ByteOrder::Native, Some("little") => ByteOrder::Little, diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 915aa1d92..533f4f106 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -63,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result) -> Result Ok(CommandLineInputs::FileAndOffset(( - input_strings[0].clone().to_owned(), + input_strings[0].to_string(), m, None, ))), @@ -118,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result Ok(CommandLineInputs::FileAndOffset(( - input_strings[0].clone().to_owned(), + input_strings[0].to_string(), n, Some(m), ))), 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..04d33b52c 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 @@ -199,8 +199,7 @@ pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec< } pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec { - let mut result: Vec = Vec::new(); - result.push(0); + let mut result = vec![0]; for i in src { result = arrnum_int_mult(&result, radix_dest, radix_src); result = arrnum_int_add(&result, radix_dest, *i); @@ -226,8 +225,7 @@ pub fn base_conv_float(src: &[u8], radix_src: u8, radix_dest: u8) -> f64 { // to implement this for arbitrary string input. // until then, the below operates as an outline // of how it would work. - let mut result: Vec = Vec::new(); - result.push(0); + let result: Vec = vec![0]; let mut factor: f64 = 1_f64; let radix_src_float: f64 = f64::from(radix_src); let mut r: f64 = 0_f64; diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 727c2cce5..43a4ca656 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -13,7 +13,7 @@ extern crate uucore; use clap::{App, Arg}; use std::fs; use std::io::{stdout, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; const NAME: &str = "readlink"; @@ -160,8 +160,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } -fn show(path: &PathBuf, no_newline: bool, use_zero: bool) { - let path = path.as_path().to_str().unwrap(); +fn show(path: &Path, no_newline: bool, use_zero: bool) { + let path = path.to_str().unwrap(); if use_zero { print!("{}\0", path); } else if no_newline { diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 5cc8f3d9a..37ff70fb2 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -12,7 +12,7 @@ extern crate uucore; use clap::{App, Arg}; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; static ABOUT: &str = "print the resolved path"; @@ -82,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { retcode } -fn resolve_path(p: &PathBuf, strip: bool, zero: bool, quiet: bool) -> bool { +fn resolve_path(p: &Path, strip: bool, zero: bool, quiet: bool) -> bool { let abs = canonicalize(p, CanonicalizeMode::Normal).unwrap(); if strip { diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 09671768b..94626b4e7 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -176,7 +176,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } else if matches.is_present(OPT_PROMPT_MORE) { InteractiveMode::Once } else if matches.is_present(OPT_INTERACTIVE) { - match &matches.value_of(OPT_INTERACTIVE).unwrap()[..] { + match matches.value_of(OPT_INTERACTIVE).unwrap() { "none" => InteractiveMode::None, "once" => InteractiveMode::Once, "always" => InteractiveMode::Always, diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 671dd7e1c..c3bba1c78 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut largest_dec = 0; let mut padding = 0; let first = if numbers.len() > 1 { - let slice = &numbers[0][..]; + let slice = numbers[0]; let len = slice.len(); let dec = slice.find('.').unwrap_or(len); largest_dec = len - dec; @@ -118,7 +118,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 1.0 }; let increment = if numbers.len() > 2 { - let slice = &numbers[1][..]; + let slice = numbers[1]; let len = slice.len(); let dec = slice.find('.').unwrap_or(len); largest_dec = cmp::max(largest_dec, len - dec); @@ -134,11 +134,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 1.0 }; if increment == 0.0 { - show_error!("increment value: '{}'", &numbers[1][..]); + show_error!("increment value: '{}'", numbers[1]); return 1; } let last = { - let slice = &numbers[numbers.len() - 1][..]; + let slice = numbers[numbers.len() - 1]; padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); match parse_float(slice) { Ok(n) => n, diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 7e0e77184..c56f14280 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -439,6 +439,7 @@ fn pass_name(pass_type: PassType) -> String { } } +#[allow(clippy::too_many_arguments)] fn wipe_file( path_str: &str, n_passes: usize, @@ -472,12 +473,9 @@ fn wipe_file( let mut perms = metadata.permissions(); perms.set_readonly(false); - match fs::set_permissions(path, perms) { - Err(e) => { - show_error!("{}", e); - return; - } - _ => {} + if let Err(e) = fs::set_permissions(path, perms) { + show_error!("{}", e); + return; } } diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index ed5655a3d..d0fbc7c0d 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -75,7 +75,7 @@ fn open(name: &str) -> Result> { "Is a directory", )); }; - if !path.metadata().is_ok() { + if path.metadata().is_err() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, "No such file or directory", diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 68dae94e2..666ba3384 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -90,7 +90,7 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { Box::new(stdin()) as Box } else { let path = Path::new(filename); - if path.is_dir() || !path.metadata().is_ok() { + if path.is_dir() || path.metadata().is_err() { show_error!( "failed to open '{}' for reading: No such file or directory", filename diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 4394e4a8e..f882ff5ae 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -55,16 +55,16 @@ fn two(args: &[&[u8]], error: &mut bool) -> bool { b"-d" => path(args[1], PathCondition::Directory), b"-e" => path(args[1], PathCondition::Exists), b"-f" => path(args[1], PathCondition::Regular), - b"-g" => path(args[1], PathCondition::GroupIDFlag), + b"-g" => path(args[1], PathCondition::GroupIdFlag), b"-h" => path(args[1], PathCondition::SymLink), b"-L" => path(args[1], PathCondition::SymLink), b"-n" => one(&args[1..]), - b"-p" => path(args[1], PathCondition::FIFO), + b"-p" => path(args[1], PathCondition::Fifo), b"-r" => path(args[1], PathCondition::Readable), b"-S" => path(args[1], PathCondition::Socket), b"-s" => path(args[1], PathCondition::NonEmpty), b"-t" => isatty(args[1]), - b"-u" => path(args[1], PathCondition::UserIDFlag), + b"-u" => path(args[1], PathCondition::UserIdFlag), b"-w" => path(args[1], PathCondition::Writable), b"-x" => path(args[1], PathCondition::Executable), b"-z" => !one(&args[1..]), @@ -322,13 +322,13 @@ enum PathCondition { Directory, Exists, Regular, - GroupIDFlag, + GroupIdFlag, SymLink, - FIFO, + Fifo, Readable, Socket, NonEmpty, - UserIDFlag, + UserIdFlag, Writable, Executable, } @@ -390,13 +390,13 @@ fn path(path: &[u8], cond: PathCondition) -> bool { PathCondition::Directory => file_type.is_dir(), PathCondition::Exists => true, PathCondition::Regular => file_type.is_file(), - PathCondition::GroupIDFlag => metadata.mode() & S_ISGID != 0, + PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0, PathCondition::SymLink => metadata.file_type().is_symlink(), - PathCondition::FIFO => file_type.is_fifo(), + PathCondition::Fifo => file_type.is_fifo(), PathCondition::Readable => perm(metadata, Permission::Read), PathCondition::Socket => file_type.is_socket(), PathCondition::NonEmpty => metadata.size() > 0, - PathCondition::UserIDFlag => metadata.mode() & S_ISUID != 0, + PathCondition::UserIdFlag => metadata.mode() & S_ISUID != 0, PathCondition::Writable => perm(metadata, Permission::Write), PathCondition::Executable => perm(metadata, Permission::Execute), } @@ -416,13 +416,13 @@ fn path(path: &[u8], cond: PathCondition) -> bool { PathCondition::Directory => stat.is_dir(), PathCondition::Exists => true, PathCondition::Regular => stat.is_file(), - PathCondition::GroupIDFlag => false, + PathCondition::GroupIdFlag => false, PathCondition::SymLink => false, - PathCondition::FIFO => false, + PathCondition::Fifo => false, PathCondition::Readable => false, // TODO PathCondition::Socket => false, PathCondition::NonEmpty => stat.len() > 0, - PathCondition::UserIDFlag => false, + PathCondition::UserIdFlag => false, PathCondition::Writable => false, // TODO PathCondition::Executable => false, // TODO } diff --git a/src/uu/tr/src/expand.rs b/src/uu/tr/src/expand.rs index e71cf262c..73612a065 100644 --- a/src/uu/tr/src/expand.rs +++ b/src/uu/tr/src/expand.rs @@ -24,7 +24,7 @@ use std::ops::RangeInclusive; fn parse_sequence(s: &str) -> (char, usize) { let c = s.chars().next().expect("invalid escape: empty string"); - if '0' <= c && c <= '7' { + if ('0'..='7').contains(&c) { let mut v = c.to_digit(8).unwrap(); let mut consumed = 1; let bits_per_digit = 3; diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 3440972a2..967a9514a 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -16,8 +16,8 @@ use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; static VERSION: &str = env!("CARGO_PKG_VERSION"); -static SUMMARY: &str = "Topological sort the strings in FILE. -Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). +static SUMMARY: &str = "Topological sort the strings in FILE. +Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead."; static USAGE: &str = "tsort [OPTIONS] FILE"; @@ -32,13 +32,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .version(VERSION) .usage(USAGE) .about(SUMMARY) - .arg(Arg::with_name(options::FILE).hidden(true)) + .arg( + Arg::with_name(options::FILE) + .default_value("-") + .hidden(true), + ) .get_matches_from(args); - let input = match matches.value_of(options::FILE) { - Some(v) => v, - None => "-", - }; + let input = matches + .value_of(options::FILE) + .expect("Value is required by clap"); let mut stdin_buf; let mut file_buf; diff --git a/src/uu/wc/src/count_bytes.rs b/src/uu/wc/src/count_bytes.rs index 0c3b5edb7..7f06f8171 100644 --- a/src/uu/wc/src/count_bytes.rs +++ b/src/uu/wc/src/count_bytes.rs @@ -72,30 +72,27 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult { - // If the file is regular, then the `st_size` should hold - // the file's size in bytes. - if (stat.st_mode & S_IFREG) != 0 { - return Ok(stat.st_size as usize); - } - #[cfg(any(target_os = "linux", target_os = "android"))] - { - // Else, if we're on Linux and our file is a FIFO pipe - // (or stdin), we use splice to count the number of bytes. - if (stat.st_mode & S_IFIFO) != 0 { - if let Ok(n) = count_bytes_using_splice(fd) { - return Ok(n); - } + if let Ok(stat) = fstat(fd) { + // If the file is regular, then the `st_size` should hold + // the file's size in bytes. + if (stat.st_mode & S_IFREG) != 0 { + return Ok(stat.st_size as usize); + } + #[cfg(any(target_os = "linux", target_os = "android"))] + { + // Else, if we're on Linux and our file is a FIFO pipe + // (or stdin), we use splice to count the number of bytes. + if (stat.st_mode & S_IFIFO) != 0 { + if let Ok(n) = count_bytes_using_splice(fd) { + return Ok(n); } } } - _ => {} } } // Fall back on `read`, but without the overhead of counting words and lines. - let mut buf = [0 as u8; BUF_SIZE]; + let mut buf = [0_u8; BUF_SIZE]; let mut byte_count = 0; loop { match handle.read(&mut buf) { diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 22463caa4..59ca10141 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -138,11 +138,8 @@ impl AddAssign for WordCount { } impl WordCount { - fn with_title<'a>(self, title: &'a str) -> TitledWordCount<'a> { - return TitledWordCount { - title: title, - count: self, - }; + fn with_title(self, title: &str) -> TitledWordCount { + TitledWordCount { title, count: self } } } @@ -251,7 +248,7 @@ fn is_word_separator(byte: u8) -> bool { fn word_count_from_reader( mut reader: T, settings: &Settings, - path: &String, + path: &str, ) -> WcResult { let only_count_bytes = settings.show_bytes && (!(settings.show_chars @@ -333,18 +330,18 @@ fn word_count_from_reader( }) } -fn word_count_from_path(path: &String, settings: &Settings) -> WcResult { +fn word_count_from_path(path: &str, settings: &Settings) -> WcResult { if path == "-" { let stdin = io::stdin(); let stdin_lock = stdin.lock(); - return Ok(word_count_from_reader(stdin_lock, settings, path)?); + word_count_from_reader(stdin_lock, settings, path) } else { let path_obj = Path::new(path); if path_obj.is_dir() { - return Err(WcError::IsDirectory(path.clone())); + Err(WcError::IsDirectory(path.to_owned())) } else { let file = File::open(path)?; - return Ok(word_count_from_reader(file, settings, path)?); + word_count_from_reader(file, settings, path) } } } @@ -425,7 +422,7 @@ fn print_stats( } if result.title == "-" { - writeln!(stdout_lock, "")?; + writeln!(stdout_lock)?; } else { writeln!(stdout_lock, " {}", result.title)?; } From 75a76613e43308c53477b41575a33a01f7fdaa05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Sun, 11 Apr 2021 16:09:18 +0200 Subject: [PATCH 024/399] fix clippy in cp --- src/uu/cp/src/cp.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 60484a79a..4e245b298 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -132,7 +132,9 @@ macro_rules! prompt_yes( pub type CopyResult = Result; pub type Source = PathBuf; +pub type SourceSlice = Path; pub type Target = PathBuf; +pub type TargetSlice = Path; /// Specifies whether when overwrite files #[derive(Clone, Eq, PartialEq)] @@ -664,7 +666,7 @@ impl TargetType { /// /// Treat target as a dir if we have multiple sources or the target /// exists and already is a directory - fn determine(sources: &[Source], target: &Target) -> TargetType { + fn determine(sources: &[Source], target: &TargetSlice) -> TargetType { if sources.len() > 1 || target.is_dir() { TargetType::Directory } else { @@ -787,7 +789,7 @@ fn preserve_hardlinks( /// Behavior depends on `options`, see [`Options`] for details. /// /// [`Options`]: ./struct.Options.html -fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()> { +fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> { let target_type = TargetType::determine(sources, target); verify_target_type(target, &target_type)?; @@ -839,7 +841,7 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<() fn construct_dest_path( source_path: &Path, - target: &Target, + target: &TargetSlice, target_type: &TargetType, options: &Options, ) -> CopyResult { @@ -869,8 +871,8 @@ fn construct_dest_path( } fn copy_source( - source: &Source, - target: &Target, + source: &SourceSlice, + target: &TargetSlice, target_type: &TargetType, options: &Options, ) -> CopyResult<()> { @@ -911,7 +913,7 @@ fn adjust_canonicalization(p: &Path) -> Cow { /// /// Any errors encountered copying files in the tree will be logged but /// will not cause a short-circuit. -fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult<()> { +fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> { if !options.recursive { return Err(format!("omitting directory '{}'", root.display()).into()); } From b465c34eeffc99ba6d5b390ed92209c08baa11e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Sun, 11 Apr 2021 16:16:38 +0200 Subject: [PATCH 025/399] fix ls --- src/uu/ls/src/ls.rs | 2 +- src/uu/ls/src/version_cmp.rs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 4024328b5..d198f1588 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1042,7 +1042,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { .sort_by_key(|k| Reverse(get_metadata(k, config).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(version_cmp::version_cmp), + Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)), Sort::None => {} } diff --git a/src/uu/ls/src/version_cmp.rs b/src/uu/ls/src/version_cmp.rs index 3cd5989f1..4cd39f916 100644 --- a/src/uu/ls/src/version_cmp.rs +++ b/src/uu/ls/src/version_cmp.rs @@ -1,8 +1,9 @@ -use std::{cmp::Ordering, path::PathBuf}; +use std::cmp::Ordering; +use std::path::Path; -/// Compare pathbufs in a way that matches the GNU version sort, meaning that +/// Compare paths in a way that matches the GNU version sort, meaning that /// numbers get sorted in a natural way. -pub(crate) fn version_cmp(a: &PathBuf, b: &PathBuf) -> Ordering { +pub(crate) fn version_cmp(a: &Path, b: &Path) -> Ordering { let a_string = a.to_string_lossy(); let b_string = b.to_string_lossy(); let mut a = a_string.chars().peekable(); From d67560c37ac6e2a4443920ced20b788249fba45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Sun, 11 Apr 2021 16:34:19 +0200 Subject: [PATCH 026/399] fix clippy for unix --- src/uu/chgrp/src/chgrp.rs | 2 +- src/uu/chmod/src/chmod.rs | 8 ++++---- src/uu/chown/src/chown.rs | 4 ++-- src/uu/chroot/src/chroot.rs | 14 +++++++------- src/uu/install/src/install.rs | 18 +++++++++--------- src/uu/ls/src/ls.rs | 1 + src/uu/pinky/src/pinky.rs | 12 +++--------- src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs | 4 ++-- src/uu/stdbuf/src/stdbuf.rs | 6 ++---- src/uu/touch/src/touch.rs | 2 +- src/uu/tty/src/tty.rs | 4 ++-- src/uucore/src/lib/features/perms.rs | 4 ++-- 12 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index b4c3360c5..592a0a905 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -286,7 +286,7 @@ impl Chgrper { ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) { Ok(n) => { - if n != "" { + if !n.is_empty() { show_info!("{}", n); } 0 diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d9d8c8cf2..dc11be7b8 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -171,13 +171,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // of a prefix '-' if it's associated with MODE // e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE" pub fn strip_minus_from_mode(args: &mut Vec) -> bool { - for i in 0..args.len() { - if args[i].starts_with("-") { - if let Some(second) = args[i].chars().nth(1) { + for arg in args { + if arg.starts_with('-') { + if let Some(second) = arg.chars().nth(1) { match second { 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { // TODO: use strip_prefix() once minimum rust version reaches 1.45.0 - args[i] = args[i][1..args[i].len()].to_string(); + *arg = arg[1..arg.len()].to_string(); return true; } _ => {} diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 42010de03..0e3273b3b 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -391,7 +391,7 @@ impl Chowner { self.verbosity.clone(), ) { Ok(n) => { - if n != "" { + if !n.is_empty() { show_info!("{}", n); } 0 @@ -446,7 +446,7 @@ impl Chowner { self.verbosity.clone(), ) { Ok(n) => { - if n != "" { + if !n.is_empty() { show_info!("{}", n); } 0 diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 44c5dfa02..7e672da1e 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -104,7 +104,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { _ => { let mut vector: Vec<&str> = Vec::new(); for (&k, v) in matches.args.iter() { - vector.push(k.clone()); + vector.push(k); vector.push(&v.vals[0].to_str().unwrap()); } vector @@ -133,7 +133,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { let userspec = match userspec_str { Some(ref u) => { let s: Vec<&str> = u.split(':').collect(); - if s.len() != 2 || s.iter().any(|&spec| spec == "") { + if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { crash!(1, "invalid userspec: `{}`", u) }; s @@ -142,16 +142,16 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { }; let (user, group) = if userspec.is_empty() { - (&user_str[..], &group_str[..]) + (user_str, group_str) } else { - (&userspec[0][..], &userspec[1][..]) + (userspec[0], userspec[1]) }; enter_chroot(root); - set_groups_from_str(&groups_str[..]); - set_main_group(&group[..]); - set_user(&user[..]); + set_groups_from_str(groups_str); + set_main_group(group); + set_user(user); } fn enter_chroot(root: &Path) { diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index e902862a8..a75ce45be 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -302,7 +302,7 @@ fn behavior(matches: &ArgMatches) -> Result { let specified_mode: Option = if matches.is_present(OPT_MODE) { match matches.value_of(OPT_MODE) { - Some(x) => match mode::parse(&x[..], considering_dir) { + Some(x) => match mode::parse(x, considering_dir) { Ok(y) => Some(y), Err(err) => { show_error!("Invalid mode string: {}", err); @@ -429,7 +429,7 @@ fn standard(paths: Vec, b: Behavior) -> i32 { /// _files_ must all exist as non-directories. /// _target_dir_ must be a directory. /// -fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 { +fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 { if !target_dir.is_dir() { show_error!("target '{}' is not a directory", target_dir.display()); return 1; @@ -453,7 +453,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> continue; } - let mut targetpath = target_dir.clone().to_path_buf(); + let mut targetpath = target_dir.to_path_buf(); let filename = sourcepath.components().last().unwrap(); targetpath.push(filename); @@ -478,7 +478,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> /// _file_ must exist as a non-directory. /// _target_ must be a non-directory /// -fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 { +fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 { if copy(file, &target, b).is_err() { 1 } else { @@ -497,7 +497,7 @@ fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 { /// /// If the copy system call fails, we print a verbose error and return an empty error value. /// -fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { +fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { if b.compare && !need_copy(from, to, b) { return Ok(()); } @@ -556,7 +556,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { }; let gid = meta.gid(); match wrap_chown( - to.as_path(), + to, &meta, Some(owner_id), Some(gid), @@ -582,7 +582,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { Ok(g) => g, _ => crash!(1, "no such group: {}", b.group), }; - match wrap_chgrp(to.as_path(), &meta, group_id, false, Verbosity::Normal) { + match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) { Ok(n) => { if !n.is_empty() { show_info!("{}", n); @@ -601,7 +601,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { let modified_time = FileTime::from_last_modification_time(&meta); let accessed_time = FileTime::from_last_access_time(&meta); - match set_file_times(to.as_path(), accessed_time, modified_time) { + match set_file_times(to, accessed_time, modified_time) { Ok(_) => {} Err(e) => show_info!("{}", e), } @@ -630,7 +630,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { /// /// Crashes the program if a nonexistent owner or group is specified in _b_. /// -fn need_copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> bool { +fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool { let from_meta = match fs::metadata(from) { Ok(meta) => meta, Err(_) => return true, diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d198f1588..aebaa6b44 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -370,6 +370,7 @@ impl Config { }) .or_else(|| termsize::get().map(|s| s.cols)); + #[allow(clippy::needless_bool)] let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { false } else if options.is_present(options::SHOW_CONTROL_CHARS) { diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 772e311d6..851a3cd42 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -15,7 +15,6 @@ use uucore::utmpx::{self, time, Utmpx}; use std::io::prelude::*; use std::io::BufReader; -use std::io::Result as IOResult; use std::fs::File; use std::os::unix::fs::MetadataExt; @@ -136,12 +135,8 @@ The utmp file will be {}", }; if do_short_format { - if let Err(e) = pk.short_pinky() { - show_usage_error!("{}", e); - 1 - } else { - 0 - } + pk.short_pinky(); + 0 } else { pk.long_pinky() } @@ -282,7 +277,7 @@ impl Pinky { println!(); } - fn short_pinky(&self) -> IOResult<()> { + fn short_pinky(&self) { if self.include_heading { self.print_heading(); } @@ -295,7 +290,6 @@ impl Pinky { } } } - Ok(()) } fn long_pinky(&self) -> i32 { diff --git a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs index fa36d4ab5..d08427d98 100644 --- a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs +++ b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs @@ -35,8 +35,8 @@ extern "C" { fn set_buffer(stream: *mut FILE, value: &str) { let (mode, size): (c_int, size_t) = match value { - "0" => (_IONBF, 0 as size_t), - "L" => (_IOLBF, 0 as size_t), + "0" => (_IONBF, 0_usize), + "L" => (_IOLBF, 0_usize), input => { let buff_size: usize = match input.parse() { Ok(num) => num, diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index a6c9f9dc5..8c65a5c7e 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -141,12 +141,10 @@ fn parse_size(size: &str) -> Option { fn check_option(matches: &ArgMatches, name: &str) -> Result { match matches.value_of(name) { - Some(value) => match &value[..] { + Some(value) => match value { "L" => { if name == options::INPUT { - Err(ProgramOptionsError(format!( - "line buffering stdin is meaningless" - ))) + Err(ProgramOptionsError("line buffering stdin is meaningless".to_string())) } else { Ok(BufferType::Line) } diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 39405900e..f0c3c12d2 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -137,7 +137,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) { stat( - &matches.value_of(options::sources::REFERENCE).unwrap()[..], + matches.value_of(options::sources::REFERENCE).unwrap(), !matches.is_present(options::NO_DEREF), ) } else if matches.is_present(options::sources::DATE) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 18d69db46..815c6f96b 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -65,9 +65,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } - return if is_stdin_interactive() { + if is_stdin_interactive() { libc::EXIT_SUCCESS } else { libc::EXIT_FAILURE - }; + } } diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 66db15451..36f56206d 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -31,9 +31,9 @@ fn chgrp>(path: P, dgid: gid_t, follow: bool) -> IOResult<()> { let s = CString::new(path.as_os_str().as_bytes()).unwrap(); let ret = unsafe { if follow { - libc::chown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) + libc::chown(s.as_ptr(), 0_u32.wrapping_sub(1), dgid) } else { - lchown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) + lchown(s.as_ptr(), 0_u32.wrapping_sub(1), dgid) } }; if ret == 0 { From d219b6e705b8f48a73f19481aad4fdd9b25beca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Mon, 12 Apr 2021 19:50:23 +0200 Subject: [PATCH 027/399] strip_suffix is not avaialble with rust 1.40 --- src/uu/basename/src/basename.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index b7f99af27..84521bdd1 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -113,8 +113,12 @@ fn basename(fullname: &str, suffix: &str) -> String { fn strip_suffix(name: &str, suffix: &str) -> String { if name == suffix { - name.to_owned() - } else { - name.strip_suffix(suffix).unwrap_or(name).to_owned() + return name.to_owned(); } + + if name.ends_with(suffix) { + return name[..name.len() - suffix.len()].to_owned(); + } + + name.to_owned() } From 07e9c5896c9dc72fa2c1ec5f20db2b7dc1e39327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Mon, 12 Apr 2021 19:53:47 +0200 Subject: [PATCH 028/399] ignore strip_suffix until minimum rust version is 1.45 --- src/uu/basename/src/basename.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 84521bdd1..7b02a7a83 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -111,6 +111,7 @@ fn basename(fullname: &str, suffix: &str) -> String { } } +#[allow(clippy::manual_strip)] // can be replaced with strip_suffix once the minimum rust version is 1.45 fn strip_suffix(name: &str, suffix: &str) -> String { if name == suffix { return name.to_owned(); From a4253d125497a98e15f4da27191053302a1870e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reto=20Habl=C3=BCtzel?= Date: Mon, 12 Apr 2021 20:07:10 +0200 Subject: [PATCH 029/399] apply more clippy suggestions from nightly --- src/uu/df/src/df.rs | 5 ----- src/uu/hashsum/src/hashsum.rs | 14 +++++++------- .../printf/src/tokenize/num_format/num_format.rs | 6 +----- src/uu/ptx/src/ptx.rs | 4 ++-- src/uu/shred/src/shred.rs | 5 +---- src/uu/unexpand/src/unexpand.rs | 6 ++---- src/uu/who/src/who.rs | 5 +---- 7 files changed, 14 insertions(+), 31 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 57caf7970..e898b187c 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -116,7 +116,6 @@ struct Options { show_listed_fs: bool, show_fs_type: bool, show_inode_instead: bool, - print_grand_total: bool, // block_size: usize, human_readable_base: i64, fs_selector: FsSelector, @@ -286,7 +285,6 @@ impl Options { show_listed_fs: false, show_fs_type: false, show_inode_instead: false, - print_grand_total: false, // block_size: match env::var("BLOCKSIZE") { // Ok(size) => size.parse().unwrap(), // Err(_) => 512, @@ -871,9 +869,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(OPT_ALL) { opt.show_all_fs = true; } - if matches.is_present(OPT_TOTAL) { - opt.print_grand_total = true; - } if matches.is_present(OPT_INODES) { opt.show_inode_instead = true; } diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index ee7d2a0f7..2e31ddd25 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -78,7 +78,7 @@ fn detect_algo<'a>( "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box, 512), "b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box, 512), "sha3sum" => match matches.value_of("bits") { - Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Some(bits_str) => match (&bits_str).parse::() { Ok(224) => ( "SHA3-224", Box::new(Sha3_224::new()) as Box, @@ -128,7 +128,7 @@ fn detect_algo<'a>( 512, ), "shake128sum" => match matches.value_of("bits") { - Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Some(bits_str) => match (&bits_str).parse::() { Ok(bits) => ( "SHAKE128", Box::new(Shake128::new()) as Box, @@ -139,7 +139,7 @@ fn detect_algo<'a>( None => crash!(1, "--bits required for SHAKE-128"), }, "shake256sum" => match matches.value_of("bits") { - Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Some(bits_str) => match (&bits_str).parse::() { Ok(bits) => ( "SHAKE256", Box::new(Shake256::new()) as Box, @@ -182,7 +182,7 @@ fn detect_algo<'a>( } if matches.is_present("sha3") { match matches.value_of("bits") { - Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Some(bits_str) => match (&bits_str).parse::() { Ok(224) => set_or_crash( "SHA3-224", Box::new(Sha3_224::new()) as Box, @@ -226,7 +226,7 @@ fn detect_algo<'a>( } if matches.is_present("shake128") { match matches.value_of("bits") { - Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Some(bits_str) => match (&bits_str).parse::() { Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), Err(err) => crash!(1, "{}", err), }, @@ -235,7 +235,7 @@ fn detect_algo<'a>( } if matches.is_present("shake256") { match matches.value_of("bits") { - Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { + Some(bits_str) => match (&bits_str).parse::() { Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), Err(err) => crash!(1, "{}", err), }, @@ -253,7 +253,7 @@ fn detect_algo<'a>( // TODO: return custom error type fn parse_bit_num(arg: &str) -> Result { - usize::from_str_radix(arg, 10) + arg.parse() } fn is_valid_bit_num(arg: String) -> Result<(), String> { diff --git a/src/uu/printf/src/tokenize/num_format/num_format.rs b/src/uu/printf/src/tokenize/num_format/num_format.rs index 9a519e95e..812f51b5a 100644 --- a/src/uu/printf/src/tokenize/num_format/num_format.rs +++ b/src/uu/printf/src/tokenize/num_format/num_format.rs @@ -263,9 +263,5 @@ pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option Config { } if matches.is_present(options::WIDTH) { let width_str = matches.value_of(options::WIDTH).expect(err_msg).to_string(); - config.line_width = crash_if_err!(1, usize::from_str_radix(&width_str, 10)); + config.line_width = crash_if_err!(1, (&width_str).parse::()); } if matches.is_present(options::GAP_SIZE) { let gap_str = matches .value_of(options::GAP_SIZE) .expect(err_msg) .to_string(); - config.gap_size = crash_if_err!(1, usize::from_str_radix(&gap_str, 10)); + config.gap_size = crash_if_err!(1, (&gap_str).parse::()); } if matches.is_present(options::FORMAT_ROFF) { config.format = OutFormat::Roff; diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index c56f14280..b89d48a10 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -363,10 +363,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let force = matches.is_present(options::FORCE); let remove = matches.is_present(options::REMOVE); - let size_arg = match matches.value_of(options::SIZE) { - Some(s) => Some(s.to_string()), - None => None, - }; + let size_arg = matches.value_of(options::SIZE).map(|s| s.to_string()); let size = get_size(size_arg); let exact = matches.is_present(options::EXACT) && size.is_none(); // if -s is given, ignore -x let zero = matches.is_present(options::ZERO); diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 5b08c33cf..3d80bd6e9 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -149,10 +149,8 @@ fn next_tabstop(tabstops: &[usize], col: usize) -> Option { Some(tabstops[0] - col % tabstops[0]) } else { // find next larger tab - match tabstops.iter().find(|&&t| t > col) { - Some(t) => Some(t - col), - None => None, // if there isn't one in the list, tab becomes a single space - } + // if there isn't one in the list, tab becomes a single space + tabstops.iter().find(|&&t| t > col).map(|t| t-col) } } diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 8c7ff3211..9444985dc 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -222,7 +222,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { need_runlevel, need_users, my_line_only, - has_records: false, args: matches.free, }; @@ -247,7 +246,6 @@ struct Who { need_runlevel: bool, need_users: bool, my_line_only: bool, - has_records: bool, args: Vec, } @@ -321,8 +319,7 @@ impl Who { println!("{}", users.join(" ")); println!("# users={}", users.len()); } else { - let mut records = Utmpx::iter_all_records().read_from(f).peekable(); - self.has_records = records.peek().is_some(); + let records = Utmpx::iter_all_records().read_from(f).peekable(); if self.include_heading { self.print_heading() From e6c195a675eb9043ad7ee1668e784de2387bf21b Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Mon, 12 Apr 2021 14:24:22 -0500 Subject: [PATCH 030/399] ExtSort --- Cargo.lock | 139 +++++++++++++++++++++++++++++++++++++++- src/uu/sort/Cargo.toml | 6 +- src/uu/sort/src/sort.rs | 96 +++++++++++++++++++++++---- 3 files changed, 223 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d45e41c16..052d6de40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,12 +119,40 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +[[package]] +name = "bytecount" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" + [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cargo-platform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714a157da7991e23d90686b9524b9e12e0407a108647f52e9328f4b3d51ac7f" +dependencies = [ + "cargo-platform", + "semver 0.11.0", + "semver-parser 0.10.2", + "serde", + "serde_json", +] + [[package]] name = "cast" version = "0.2.3" @@ -560,6 +588,26 @@ dependencies = [ "regex", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + +[[package]] +name = "extsort" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc5bb6fbca3c5ce6a51f6857eab8c35c898b2fbcb62ff1b728243dd19ec0c9f" +dependencies = [ + "rayon", + "skeptic", + "tempfile", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -944,6 +992,15 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "pkg-config" version = "0.3.19" @@ -1009,6 +1066,17 @@ dependencies = [ "unicode-xid 0.2.1", ] +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr 2.3.4", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -1245,7 +1313,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", ] [[package]] @@ -1275,7 +1343,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", + "serde", ] [[package]] @@ -1284,11 +1362,23 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_cbor" @@ -1353,6 +1443,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "skeptic" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "188b810342d98f23f0bb875045299f34187b559370b041eb11520c905370a888" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob 0.3.0", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -1367,6 +1472,9 @@ name = "smallvec" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +dependencies = [ + "serde", +] [[package]] name = "strsim" @@ -1528,6 +1636,21 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -2289,12 +2412,16 @@ dependencies = [ name = "uu_sort" version = "0.0.6" dependencies = [ + "byteorder", "clap", + "extsort", "fnv", "itertools 0.10.0", "rand 0.7.3", "rayon", - "semver", + "semver 0.9.0", + "serde", + "serde_json", "smallvec 1.6.1", "uucore", "uucore_procs", @@ -2604,6 +2731,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "void" version = "1.0.2" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 6a9976278..8ad0a681f 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -15,13 +15,17 @@ edition = "2018" path = "src/sort.rs" [dependencies] +byteorder = "1.4.3" +extsort = "0.4.2" +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +serde = { version = "1.0", features = ["derive"] } rayon = "1.5" rand = "0.7" clap = "2.33" fnv = "1.0.7" itertools = "0.10.0" semver = "0.9.0" -smallvec = "1.6.1" +smallvec = { version = "1.6.1", features = ["serde"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8bf6eb1e8..cb07f60b7 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -20,7 +20,6 @@ use fnv::FnvHasher; use itertools::Itertools; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use rayon::prelude::*; use semver::Version; use smallvec::SmallVec; use std::borrow::Cow; @@ -34,6 +33,14 @@ use std::mem::replace; use std::ops::{Range, RangeInclusive}; use std::path::Path; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() +use extsort::*; +use std::str; +use serde::{Serialize, Deserialize}; +use std::ffi::OsString; +use std::usize; +use std::path::PathBuf; +use std::string::*; +use serde_json::Result; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -72,6 +79,8 @@ static OPT_RANDOM: &str = "random-sort"; static OPT_ZERO_TERMINATED: &str = "zero-terminated"; static OPT_PARALLEL: &str = "parallel"; static OPT_FILES0_FROM: &str = "files0-from"; +static OPT_BUF_SIZE: &str = "buffer-size"; +static OPT_TMP_DIR: &str = "temporary-directory"; static ARG_FILES: &str = "files"; @@ -110,6 +119,8 @@ struct GlobalSettings { separator: Option, threads: String, zero_terminated: bool, + buffer_size: usize, + tmp_dir: PathBuf, } impl Default for GlobalSettings { @@ -133,6 +144,8 @@ impl Default for GlobalSettings { separator: None, threads: String::new(), zero_terminated: false, + buffer_size: 10000000usize, + tmp_dir: PathBuf::from(r"/tmp"), } } } @@ -162,7 +175,7 @@ impl From<&GlobalSettings> for KeySettings { } /// Represents the string selected by a FieldSelector. -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize, Clone)] enum Selection { /// If we had to transform this selection, we have to store a new string. String(String), @@ -182,13 +195,29 @@ impl Selection { type Field = Range; -#[derive(Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. selections: SmallVec<[Selection; 1]>, } +impl Sortable for Line { + fn encode(&self, write: &mut W) { + let line = Line { line: self.line.clone(), selections: self.selections.clone() } ; + let serialized = serde_json::to_string(&line).unwrap(); + write.write_all(serialized.as_bytes()).unwrap(); + } + + fn decode(read: &mut R) -> Option { + let mut buf = String::new(); + read.read_to_string(&mut buf).ok(); + let line: Option = buf; + println!("deserialized = {:?}", line); + line + } +} + impl Line { fn new(line: String, settings: &GlobalSettings) -> Self { let fields = if settings @@ -681,6 +710,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .value_name("NUM_THREADS"), ) + .arg( + Arg::with_name(OPT_BUF_SIZE) + .long(OPT_BUF_SIZE) + .help("sets the maximum SIZE of each segment in number of sorted items") + .takes_value(true) + .value_name("SIZE"), + ) + .arg( + Arg::with_name(OPT_TMP_DIR) + .long(OPT_TMP_DIR) + .help("use DIR for temporaries, not $TMPDIR or /tmp") + .takes_value(true) + .value_name("DIR"), + ) .arg( Arg::with_name(OPT_FILES0_FROM) .long(OPT_FILES0_FROM) @@ -744,6 +787,32 @@ pub fn uumain(args: impl uucore::Args) -> i32 { env::set_var("RAYON_NUM_THREADS", &settings.threads); } + if matches.is_present(OPT_BUF_SIZE) { + // 10000 is the default extsort buffer, but it's too small + settings.buffer_size = matches + .value_of(OPT_BUF_SIZE) + .map(String::from) + .unwrap_or( format! ( "{}", 10000000usize ) ) + .parse::() + .unwrap_or(10000000usize); + } + + if matches.is_present(OPT_TMP_DIR) { + let result = matches + .value_of(OPT_TMP_DIR) + .map(String::from) + .unwrap_or("/tmp".to_owned() ); + settings.tmp_dir = PathBuf::from(format!(r"{}", result)); + } else { + for (key, value) in env::vars_os() { + if key == OsString::from("TMPDIR") { + settings.tmp_dir = PathBuf::from(format!(r"{}", value.into_string().unwrap_or("/tmp".to_owned()))); + break + } + settings.tmp_dir = PathBuf::from(r"/tmp"); + } + } + settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); settings.merge = matches.is_present(OPT_MERGE); @@ -860,9 +929,9 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { if settings.check { return exec_check_file(&lines, &settings); - } else { - sort_by(&mut lines, &settings); } + + lines = sort_by(lines, &settings); if settings.merge { if settings.unique { @@ -917,8 +986,9 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { - lines.par_sort_by(|a, b| compare_by(a, b, &settings)) +fn sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { + let sorter = ExternalSorter::new().with_segment_size(settings.buffer_size).with_sort_dir(settings.tmp_dir.clone()).with_parallel_sort(); + sorter.sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)).unwrap().collect() } fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { @@ -1004,7 +1074,6 @@ fn leading_num_common(a: &str) -> &str { // not recognize a positive sign or scientific/E notation so we strip those elements here. fn get_leading_num(a: &str) -> &str { let mut s = ""; - let a = leading_num_common(a); // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip @@ -1019,9 +1088,7 @@ fn get_leading_num(a: &str) -> &str { // And empty number or non-number lines are to be treated as ‘0’ but only for numeric sort // All '0'-ed lines will be sorted later, but only amongst themselves, during the so-called 'last resort comparison.' - if s.is_empty() { - s = "0"; - }; + if s.is_empty() { s = "0"; }; s } @@ -1087,8 +1154,8 @@ fn permissive_f64_parse(a: &str) -> f64 { // Remove any trailing decimals, ie 4568..890... becomes 4568.890 // Then, we trim whitespace and parse match remove_trailing_dec(a).trim().parse::() { - Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, - Ok(a) => a, + Ok(val) if val.is_nan() => std::f64::NEG_INFINITY, + Ok(val) => val, Err(_) => std::f64::NEG_INFINITY, } } @@ -1107,7 +1174,6 @@ fn numeric_compare(a: &str, b: &str) -> Ordering { let fa = permissive_f64_parse(&ta); let fb = permissive_f64_parse(&tb); - // f64::cmp isn't implemented (due to NaN issues); implement directly instead if fa > fb { Ordering::Greater } else if fa < fb { @@ -1150,6 +1216,7 @@ fn human_numeric_convert(a: &str) -> f64 { let num_part = permissive_f64_parse(&num_str); let suffix: f64 = match suffix.parse().unwrap_or('\0') { // SI Units + 'b' => 1f64, 'K' => 1E3, 'M' => 1E6, 'G' => 1E9, @@ -1262,6 +1329,7 @@ fn month_compare(a: &str, b: &str) -> Ordering { } } +#[inline(always)] fn version_parse(a: &str) -> Version { let result = Version::parse(a); From fbb2125812657c5eb9b8b1395314033c8bb72b0d Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci Date: Mon, 12 Apr 2021 22:50:29 +0300 Subject: [PATCH 031/399] doc: update CONTRIBUTING.md --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c40e5dfd..d52f2a8af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,7 @@ search the issues to make sure no one else is working on it. 1. Make sure that the code coverage is covering all of the cases, including errors. 1. The code must be clippy-warning-free and rustfmt-compliant. 1. Don't hesitate to move common functions into uucore if they can be reused by other binaries. +1. Unsafe code should be documented with Safety comments ## Commit messages From 3aee0dfa88973beb23f4d6ded0c59aab1571303b Mon Sep 17 00:00:00 2001 From: Yagiz Degirmenci Date: Mon, 12 Apr 2021 22:52:17 +0300 Subject: [PATCH 032/399] add dot --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d52f2a8af..bcb1f8fff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ search the issues to make sure no one else is working on it. 1. Make sure that the code coverage is covering all of the cases, including errors. 1. The code must be clippy-warning-free and rustfmt-compliant. 1. Don't hesitate to move common functions into uucore if they can be reused by other binaries. -1. Unsafe code should be documented with Safety comments +1. Unsafe code should be documented with Safety comments. ## Commit messages From c49f93c9af2582c276b85c8e78841797fe7e938d Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Mon, 12 Apr 2021 18:05:37 -0500 Subject: [PATCH 033/399] Psuedo working extsort --- src/uu/sort/Cargo.toml | 2 +- src/uu/sort/src/sort.rs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 8ad0a681f..8a3d1ed25 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -17,7 +17,7 @@ path = "src/sort.rs" [dependencies] byteorder = "1.4.3" extsort = "0.4.2" -serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } serde = { version = "1.0", features = ["derive"] } rayon = "1.5" rand = "0.7" diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index cb07f60b7..986db59f8 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -40,7 +40,6 @@ use std::ffi::OsString; use std::usize; use std::path::PathBuf; use std::string::*; -use serde_json::Result; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -195,7 +194,7 @@ impl Selection { type Field = Range; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. @@ -203,18 +202,22 @@ struct Line { } impl Sortable for Line { + fn encode(&self, write: &mut W) { - let line = Line { line: self.line.clone(), selections: self.selections.clone() } ; - let serialized = serde_json::to_string(&line).unwrap(); - write.write_all(serialized.as_bytes()).unwrap(); + let line = Line {line: self.line.clone(), selections: self.selections.clone()}; + let serialized = serde_json::ser::to_string(&line).unwrap(); + write.write_all(format!("{}{}", serialized, "\n").as_bytes()).unwrap(); } fn decode(read: &mut R) -> Option { - let mut buf = String::new(); - read.read_to_string(&mut buf).ok(); - let line: Option = buf; - println!("deserialized = {:?}", line); - line + let buf_reader = BufReader::new(read); + + let mut result: Option = None; + for line in buf_reader.lines() { + let line_as_str: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); + result = Some( Line {line: line_as_str.line, selections: line_as_str.selections} ); + } + result } } @@ -235,7 +238,7 @@ impl Line { .selectors .iter() .map(|selector| { - if let Some(range) = selector.get_selection(&line, fields.as_deref()) { + if let Some(range) = selector.get_field_selection(&line, fields.as_deref()) { if let Some(transformed) = transform(&line[range.to_owned()], &selector.settings) { @@ -411,7 +414,7 @@ impl FieldSelector { /// Look up the slice that corresponds to this selector for the given line. /// If needs_fields returned false, fields may be None. - fn get_selection<'a>( + fn get_field_selection<'a>( &self, line: &'a str, tokens: Option<&[Field]>, From 5c28ac1b0de516d20da19971f199e9e43198842b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 14 Apr 2021 14:12:00 +0200 Subject: [PATCH 034/399] ls: dereference command line --- src/uu/ls/src/ls.rs | 151 ++++++++++++++++++++++++++++--------- tests/by-util/test_ls.rs | 156 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 34 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index fdc11144a..d0733357b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -120,6 +120,11 @@ pub mod options { pub static FILE_TYPE: &str = "file-type"; pub static CLASSIFY: &str = "classify"; } + pub mod dereference { + pub static ALL: &str = "dereference"; + pub static ARGS: &str = "dereference-command-line"; + pub static DIR_ARGS: &str = "dereference-command-line-symlink-to-dir"; + } pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; pub static WIDTH: &str = "width"; @@ -134,7 +139,6 @@ pub mod options { pub static FILE_TYPE: &str = "file-type"; pub static SLASH: &str = "p"; pub static INODE: &str = "inode"; - pub static DEREFERENCE: &str = "dereference"; pub static REVERSE: &str = "reverse"; pub static RECURSIVE: &str = "recursive"; pub static COLOR: &str = "color"; @@ -180,6 +184,13 @@ enum Time { Change, } +enum Dereference { + None, + DirArgs, + Args, + All, +} + #[derive(PartialEq, Eq)] enum IndicatorStyle { None, @@ -194,7 +205,7 @@ struct Config { sort: Sort, recursive: bool, reverse: bool, - dereference: bool, + dereference: Dereference, ignore_patterns: GlobSet, size_format: SizeFormat, directory: bool, @@ -482,13 +493,27 @@ impl Config { let ignore_patterns = ignore_patterns.build().unwrap(); + let dereference = if options.is_present(options::dereference::ALL) { + Dereference::All + } else if options.is_present(options::dereference::ARGS) { + Dereference::Args + } else if options.is_present(options::dereference::DIR_ARGS) { + Dereference::DirArgs + } else if options.is_present(options::DIRECTORY) + || indicator_style == IndicatorStyle::Classify + { + Dereference::None + } else { + Dereference::DirArgs + }; + Config { format, files, sort, recursive: options.is_present(options::RECURSIVE), reverse: options.is_present(options::REVERSE), - dereference: options.is_present(options::DEREFERENCE), + dereference, ignore_patterns, size_format, directory: options.is_present(options::DIRECTORY), @@ -819,6 +844,48 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ]) ) + // Dereferencing + .arg( + Arg::with_name(options::dereference::ALL) + .short("L") + .long(options::dereference::ALL) + .help( + "When showing file information for a symbolic link, show information for the \ + file the link references rather than the link itself.", + ) + .overrides_with_all(&[ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + ) + .arg( + Arg::with_name(options::dereference::DIR_ARGS) + .long(options::dereference::DIR_ARGS) + .help( + "Do not dereference symlinks except when they link to directories and are \ + given as command line arguments.", + ) + .overrides_with_all(&[ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + ) + .arg( + Arg::with_name(options::dereference::ARGS) + .short("H") + .long(options::dereference::ARGS) + .help( + "Do not dereference symlinks except when given as command line arguments.", + ) + .overrides_with_all(&[ + options::dereference::ALL, + options::dereference::DIR_ARGS, + options::dereference::ARGS, + ]) + ) + // Long format options .arg( Arg::with_name(options::NO_GROUP) @@ -877,15 +944,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::INODE) .help("print the index number of each file"), ) - .arg( - Arg::with_name(options::DEREFERENCE) - .short("L") - .long(options::DEREFERENCE) - .help( - "When showing file information for a symbolic link, show information for the \ - file the link references rather than the link itself.", - ), - ) .arg( Arg::with_name(options::REVERSE) .short("r") @@ -993,26 +1051,32 @@ fn list(locs: Vec, config: Config) -> i32 { has_failed = true; continue; } - let mut dir = false; - if p.is_dir() && !config.directory { - dir = true; - if config.format == Format::Long && !config.dereference { - if let Ok(md) = p.symlink_metadata() { - if md.file_type().is_symlink() && !p.ends_with("/") { - dir = false; + 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(), } - } - if dir { + } else { + false + }; + + if show_dir_contents { dirs.push(p); } else { files.push(p); } } sort_entries(&mut files, &config); - display_items(&files, None, &config); + display_items(&files, None, &config, true); sort_entries(&mut dirs, &config); for dir in dirs { @@ -1032,14 +1096,15 @@ fn sort_entries(entries: &mut Vec, config: &Config) { match config.sort { Sort::Time => entries.sort_by_key(|k| { Reverse( - get_metadata(k, config) + get_metadata(k, false) .ok() .and_then(|md| get_system_time(&md, config)) .unwrap_or(UNIX_EPOCH), ) }), - Sort::Size => entries - .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))), + Sort::Size => { + entries.sort_by_key(|k| Reverse(get_metadata(k, false).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(version_cmp::version_cmp), @@ -1088,9 +1153,9 @@ fn enter_directory(dir: &PathBuf, config: &Config) { 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); + display_items(&display_entries, Some(dir), config, false); } else { - display_items(&entries, Some(dir), config); + display_items(&entries, Some(dir), config, false); } if config.recursive { @@ -1101,8 +1166,8 @@ fn enter_directory(dir: &PathBuf, config: &Config) { } } -fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result { - if config.dereference { +fn get_metadata(entry: &PathBuf, dereference: bool) -> std::io::Result { + if dereference { entry.metadata().or_else(|_| entry.symlink_metadata()) } else { entry.symlink_metadata() @@ -1110,7 +1175,7 @@ fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result { } fn display_dir_entry_size(entry: &PathBuf, config: &Config) -> (usize, usize) { - if let Ok(md) = get_metadata(entry, config) { + if let Ok(md) = get_metadata(entry, false) { ( display_symlink_count(&md).len(), display_file_size(&md, config).len(), @@ -1124,7 +1189,7 @@ fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } -fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { +fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, command_line: bool) { if config.format == Format::Long { let (mut max_links, mut max_size) = (1, 1); for item in items { @@ -1133,11 +1198,11 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { max_size = size.max(max_size); } for item in items { - display_item_long(item, strip, max_links, max_size, config); + display_item_long(item, strip, max_links, max_size, config, command_line); } } else { let names = items.iter().filter_map(|i| { - let md = get_metadata(i, config); + let md = get_metadata(i, false); match md { Err(e) => { let filename = get_file_name(i, strip); @@ -1209,8 +1274,26 @@ fn display_item_long( max_links: usize, max_size: usize, config: &Config, + command_line: bool, ) { - let md = match get_metadata(item, config) { + 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) { Err(e) => { let filename = get_file_name(&item, strip); show_error!("{}: {}", filename, e); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f0db7ca9c..ef6c23b73 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1314,3 +1314,159 @@ fn test_ls_ignore_hide() { .stderr_contains(&"Invalid pattern") .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); } + +#[test] +fn test_ls_directory() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("some_dir"); + at.symlink_dir("some_dir", "sym_dir"); + + at.touch("some_dir/nested_file"); + + scene + .ucmd() + .arg("some_dir") + .succeeds() + .stdout_is("nested_file\n"); + + scene + .ucmd() + .arg("--directory") + .arg("some_dir") + .succeeds() + .stdout_is("some_dir\n"); + + scene + .ucmd() + .arg("sym_dir") + .succeeds() + .stdout_is("nested_file\n"); +} + +#[test] +fn test_ls_deref_command_line() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("some_file"); + at.symlink_file("some_file", "sym_file"); + + scene + .ucmd() + .arg("-l") + .arg("sym_file") + .succeeds() + .stdout_contains("sym_file ->"); + + scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line-symlink-to-dir") + .arg("sym_file") + .succeeds() + .stdout_contains("sym_file ->"); + + let result = scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line") + .arg("sym_file") + .succeeds(); + + assert!(!result.stdout_str().contains("->")); + + let result = scene.ucmd().arg("-lH").arg("sym_file").succeeds(); + + assert!(!result.stdout_str().contains("sym_file ->")); + + // If the symlink is not a command line argument, it must be shown normally + scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line") + .succeeds() + .stdout_contains("sym_file ->"); +} + +#[test] +fn test_ls_deref_command_line_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("some_dir"); + at.symlink_dir("some_dir", "sym_dir"); + + at.touch("some_dir/nested_file"); + + scene + .ucmd() + .arg("-l") + .arg("sym_dir") + .succeeds() + .stdout_contains("nested_file"); + + scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line-symlink-to-dir") + .arg("sym_dir") + .succeeds() + .stdout_contains("nested_file"); + + scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line") + .arg("sym_dir") + .succeeds() + .stdout_contains("nested_file"); + + scene + .ucmd() + .arg("-lH") + .arg("sym_dir") + .succeeds() + .stdout_contains("nested_file"); + + // If the symlink is not a command line argument, it must be shown normally + scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line") + .succeeds() + .stdout_contains("sym_dir ->"); + + scene + .ucmd() + .arg("-lH") + .succeeds() + .stdout_contains("sym_dir ->"); + + scene + .ucmd() + .arg("-l") + .arg("--dereference-command-line-symlink-to-dir") + .succeeds() + .stdout_contains("sym_dir ->"); + + // --directory does not dereference anything by default + scene + .ucmd() + .arg("-l") + .arg("--directory") + .arg("sym_dir") + .succeeds() + .stdout_contains("sym_dir ->"); + + let result = scene + .ucmd() + .arg("-l") + .arg("--directory") + .arg("--dereference-command-line-symlink-to-dir") + .arg("sym_dir") + .succeeds(); + + assert!(!result.stdout_str().ends_with("sym_dir")); +} From 2c130ae7c0194ad652d70c1d1bfe34a23f169545 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 14 Apr 2021 14:42:14 +0200 Subject: [PATCH 035/399] ls: take `-l` into account with dereference-command-line --- src/uu/ls/src/ls.rs | 1 + tests/by-util/test_ls.rs | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d0733357b..f22c83c48 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -501,6 +501,7 @@ impl Config { Dereference::DirArgs } else if options.is_present(options::DIRECTORY) || indicator_style == IndicatorStyle::Classify + || format == Format::Long { Dereference::None } else { diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index ef6c23b73..bdf4440e0 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1353,6 +1353,13 @@ fn test_ls_deref_command_line() { at.touch("some_file"); at.symlink_file("some_file", "sym_file"); + scene + .ucmd() + .arg("sym_file") + .succeeds() + .stdout_is("sym_file\n"); + + // -l changes the default to no dereferencing scene .ucmd() .arg("-l") @@ -1360,6 +1367,13 @@ fn test_ls_deref_command_line() { .succeeds() .stdout_contains("sym_file ->"); + scene + .ucmd() + .arg("--dereference-command-line-symlink-to-dir") + .arg("sym_file") + .succeeds() + .stdout_is("sym_file\n"); + scene .ucmd() .arg("-l") @@ -1368,6 +1382,13 @@ fn test_ls_deref_command_line() { .succeeds() .stdout_contains("sym_file ->"); + scene + .ucmd() + .arg("--dereference-command-line") + .arg("sym_file") + .succeeds() + .stdout_is("sym_file\n"); + let result = scene .ucmd() .arg("-l") @@ -1400,11 +1421,24 @@ fn test_ls_deref_command_line_dir() { at.touch("some_dir/nested_file"); + scene + .ucmd() + .arg("sym_dir") + .succeeds() + .stdout_contains("nested_file"); + scene .ucmd() .arg("-l") .arg("sym_dir") .succeeds() + .stdout_contains("sym_dir ->"); + + scene + .ucmd() + .arg("--dereference-command-line-symlink-to-dir") + .arg("sym_dir") + .succeeds() .stdout_contains("nested_file"); scene @@ -1415,6 +1449,13 @@ fn test_ls_deref_command_line_dir() { .succeeds() .stdout_contains("nested_file"); + scene + .ucmd() + .arg("--dereference-command-line") + .arg("sym_dir") + .succeeds() + .stdout_contains("nested_file"); + scene .ucmd() .arg("-l") @@ -1469,4 +1510,23 @@ fn test_ls_deref_command_line_dir() { .succeeds(); assert!(!result.stdout_str().ends_with("sym_dir")); + + // --classify does not dereference anything by default + scene + .ucmd() + .arg("-l") + .arg("--directory") + .arg("sym_dir") + .succeeds() + .stdout_contains("sym_dir ->"); + + let result = scene + .ucmd() + .arg("-l") + .arg("--directory") + .arg("--dereference-command-line-symlink-to-dir") + .arg("sym_dir") + .succeeds(); + + assert!(!result.stdout_str().ends_with("sym_dir")); } From c81cf9b6266e7dc8dccaec318c9a9a47290f8717 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 10 Apr 2021 23:03:07 +0200 Subject: [PATCH 036/399] chown: refactor tests --- tests/by-util/test_chown.rs | 503 ++++++++++++++++++++++-------------- 1 file changed, 308 insertions(+), 195 deletions(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 7b663e9c9..4ec9d60f8 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -4,6 +4,28 @@ use rust_users::get_effective_uid; extern crate chown; +// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. +// If we are running inside the CI and "needle" is in "stderr" skipping this test is +// considered okay. If we are not inside the CI this calls assert!(result.success). +// +// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" +// stderr: "whoami: cannot find name for user ID 1001" +// Maybe: "adduser --uid 1001 username" can put things right? +// stderr: "id: cannot find name for group ID 116" +fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { + if !result.succeeded() { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains(needle) { + println!("test skipped:"); + return true; + } else { + result.success(); + } + } + false +} + #[cfg(test)] mod test_passgrp { use super::chown::entries::{gid2grp, grp2gid, uid2usr, usr2uid}; @@ -49,338 +71,403 @@ fn test_invalid_option() { } #[test] -fn test_chown_myself() { +fn test_chown_only_owner() { // test chown username file.txt + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { return; } - println!("results {}", result.stdout); - let username = result.stdout.trim_end(); - - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); + let file1 = "test_chown_file1"; at.touch(file1); - let result = ucmd.arg(username).arg(file1).run(); - println!("results stdout {}", result.stdout); - println!("results stderr {}", result.stderr); - if is_ci() && result.stderr.contains("invalid user") { - // In the CI, some server are failing to return id. - // As seems to be a configuration issue, ignoring it - return; - } - assert!(result.success); + + // since only superuser can change owner, we have to change from ourself to ourself + let result = scene + .ucmd() + .arg(user_name) + .arg("--verbose") + .arg(file1) + .run(); + result.stderr_contains(&"retained as"); + + // try to change to another existing user, e.g. 'root' + scene + .ucmd() + .arg("root") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] -fn test_chown_myself_second() { +fn test_chown_only_owner_colon() { // test chown username: file.txt + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { return; } - println!("results {}", result.stdout); - - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); + let file1 = "test_chown_file1"; at.touch(file1); - let result = ucmd - .arg(result.stdout.trim_end().to_owned() + ":") + + scene + .ucmd() + .arg(format!("{}:", user_name)) + .arg("--verbose") .arg(file1) .run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - assert!(result.success); + // scene // TODO: uncomment once #2060 is fixed + // .ucmd() + // .arg("root:") + // .arg("--verbose") + // .arg(file1) + // .fails() + // .stderr_contains(&"failed to change"); } #[test] -fn test_chown_myself_group() { +fn test_chown_only_colon() { + // test chown : file.txt + + // TODO: implement once #2060 is fixed + // expected: + // $ chown -v : file.txt 2>out_err ; echo $? ; cat out_err + // ownership of 'file.txt' retained + // 0 +} + +#[test] +fn test_chown_failed_stdout() { + // test chown root file.txt + + // TODO: implement once output "failed to change" to stdout is fixed + // expected: + // $ chown -v root file.txt 2>out_err ; echo $? ; cat out_err + // failed to change ownership of 'file.txt' from jhs to root + // 1 + // chown: changing ownership of 'file.txt': Operation not permitted +} + +#[test] +fn test_chown_owner_group() { // test chown username:group file.txt + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { return; } - println!("user name = {}", result.stdout); - let username = result.stdout.trim_end(); + + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); + + let file1 = "test_chown_file1"; + at.touch(file1); let result = scene.cmd("id").arg("-gn").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("group name = {}", result.stdout); - let group = result.stdout.trim_end(); + let group_name = String::from(result.stdout_str().trim()); + assert!(!group_name.is_empty()); - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; - let perm = username.to_owned() + ":" + group; - at.touch(file1); - let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("chown: invalid group:") { - // With some Ubuntu into the CI, we can get this answer + let result = scene + .ucmd() + .arg(format!("{}:{}", user_name, group_name)) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "chown: invalid group:") { return; } - assert!(result.success); + result.stderr_contains(&"retained as"); + + // TODO: on macos group name is not recognized correctly: "chown: invalid group: 'root:root' + #[cfg(any(windows, all(unix, not(target_os = "macos"))))] + scene + .ucmd() + .arg("root:root") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] +// TODO: on macos group name is not recognized correctly: "chown: invalid group: ':groupname' +#[cfg(any(windows, all(unix, not(target_os = "macos"))))] fn test_chown_only_group() { // test chown :group file.txt + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { return; } - println!("results {}", result.stdout); + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; - let perm = ":".to_owned() + result.stdout.trim_end(); + let file1 = "test_chown_file1"; at.touch(file1); - let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - - if is_ci() && result.stderr.contains("Operation not permitted") { + let result = scene + .ucmd() + .arg(format!(":{}", user_name)) + .arg("--verbose") + .arg(file1) + .run(); + if is_ci() && result.stderr_str().contains("Operation not permitted") { // With ubuntu with old Rust in the CI, we can get an error return; } - if is_ci() && result.stderr.contains("chown: invalid group:") { + if is_ci() && result.stderr_str().contains("chown: invalid group:") { // With mac into the CI, we can get this answer return; } - assert!(result.success); + result.stderr_contains(&"retained as"); + result.success(); + + scene + .ucmd() + .arg(":root") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] -fn test_chown_only_id() { +fn test_chown_only_user_id() { // test chown 1111 file.txt - let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let result = scene.cmd_keepenv("id").arg("-u").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id = String::from(result.stdout.trim()); - - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; + let user_id = String::from(result.stdout_str().trim()); + assert!(!user_id.is_empty()); + let file1 = "test_chown_file1"; at.touch(file1); - let result = ucmd.arg(id).arg(file1).run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("chown: invalid user:") { - // With some Ubuntu into the CI, we can get this answer + let result = scene.ucmd().arg(user_id).arg("--verbose").arg(file1).run(); + if skipping_test_is_okay(&result, "invalid user") { + // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" + // stderr: "chown: invalid user: '1001' return; } - assert!(result.success); + result.stderr_contains(&"retained as"); + + scene + .ucmd() + .arg("0") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] fn test_chown_only_group_id() { // test chown :1111 file.txt - let result = TestScenario::new("id").ucmd_keepenv().arg("-g").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let result = scene.cmd_keepenv("id").arg("-g").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id = String::from(result.stdout.trim()); - - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; + let group_id = String::from(result.stdout_str().trim()); + assert!(!group_id.is_empty()); + let file1 = "test_chown_file1"; at.touch(file1); - let perm = ":".to_owned() + &id; - let result = ucmd.arg(perm).arg(file1).run(); - - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("chown: invalid group:") { + let result = scene + .ucmd() + .arg(format!(":{}", group_id)) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "chown: invalid group:") { // With mac into the CI, we can get this answer return; } - assert!(result.success); + result.stderr_contains(&"retained as"); + + // Apparently on CI "macos-latest, x86_64-apple-darwin, feat_os_macos" + // the process has the rights to change from runner:staff to runner:wheel + #[cfg(any(windows, all(unix, not(target_os = "macos"))))] + scene + .ucmd() + .arg(":0") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] -fn test_chown_both_id() { +fn test_chown_owner_group_id() { // test chown 1111:1111 file.txt - let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let result = scene.cmd_keepenv("id").arg("-u").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id_user = String::from(result.stdout.trim()); + let user_id = String::from(result.stdout_str().trim()); + assert!(!user_id.is_empty()); - let result = TestScenario::new("id").ucmd_keepenv().arg("-g").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + let result = scene.cmd_keepenv("id").arg("-g").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id_group = String::from(result.stdout.trim()); - - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; + let group_id = String::from(result.stdout_str().trim()); + assert!(!group_id.is_empty()); + let file1 = "test_chown_file1"; at.touch(file1); - let perm = id_user + &":".to_owned() + &id_group; - let result = ucmd.arg(perm).arg(file1).run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - - if is_ci() && result.stderr.contains("invalid user") { - // In the CI, some server are failing to return id. - // As seems to be a configuration issue, ignoring it + let result = scene + .ucmd() + .arg(format!("{}:{}", user_id, group_id)) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "invalid user") { + // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" + // stderr: "chown: invalid user: '1001:116' return; } + result.stderr_contains(&"retained as"); - assert!(result.success); + scene + .ucmd() + .arg("0:0") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] -fn test_chown_both_mix() { - // test chown 1111:1111 file.txt - let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it +fn test_chown_owner_group_mix() { + // test chown 1111:group file.txt + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let result = scene.cmd_keepenv("id").arg("-u").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let id_user = String::from(result.stdout.trim()); + let user_id = String::from(result.stdout_str().trim()); + assert!(!user_id.is_empty()); - let result = TestScenario::new("id").ucmd_keepenv().arg("-gn").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + let result = scene.cmd_keepenv("id").arg("-gn").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let group_name = String::from(result.stdout.trim()); - - let (at, mut ucmd) = at_and_ucmd!(); - let file1 = "test_install_target_dir_file_a1"; + let group_name = String::from(result.stdout_str().trim()); + assert!(!group_name.is_empty()); + let file1 = "test_chown_file1"; at.touch(file1); - let perm = id_user + &":".to_owned() + &group_name; - let result = ucmd.arg(perm).arg(file1).run(); + let result = scene + .ucmd() + .arg(format!("{}:{}", user_id, group_name)) + .arg("--verbose") + .arg(file1) + .run(); + result.stderr_contains(&"retained as"); - if is_ci() && result.stderr.contains("invalid user") { - // In the CI, some server are failing to return id. - // As seems to be a configuration issue, ignoring it - return; - } - assert!(result.success); + // TODO: on macos group name is not recognized correctly: "chown: invalid group: '0:root' + #[cfg(any(windows, all(unix, not(target_os = "macos"))))] + scene + .ucmd() + .arg("0:root") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] fn test_chown_recursive() { let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let username = result.stdout.trim_end(); + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); - let (at, mut ucmd) = at_and_ucmd!(); - at.mkdir("a"); - at.mkdir("a/b"); - at.mkdir("a/b/c"); + at.mkdir_all("a/b/c"); at.mkdir("z"); at.touch(&at.plus_as_string("a/a")); at.touch(&at.plus_as_string("a/b/b")); at.touch(&at.plus_as_string("a/b/c/c")); at.touch(&at.plus_as_string("z/y")); - let result = ucmd + let result = scene + .ucmd() .arg("-R") .arg("--verbose") - .arg(username) + .arg(user_name) .arg("a") .arg("z") .run(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("invalid user") { - // In the CI, some server are failing to return id. - // As seems to be a configuration issue, ignoring it - return; - } - - assert!(result.stderr.contains("ownership of 'a/a' retained as")); - assert!(result.stderr.contains("ownership of 'z/y' retained as")); - assert!(result.success); + result.stderr_contains(&"ownership of 'a/a' retained as"); + result.stderr_contains(&"ownership of 'z/y' retained as"); } #[test] fn test_root_preserve() { let scene = TestScenario::new(util_name!()); + let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { return; } - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - let username = result.stdout.trim_end(); + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); - let result = new_ucmd!() + let result = scene + .ucmd() .arg("--preserve-root") .arg("-R") - .arg(username) + .arg(user_name) .arg("/") .fails(); - println!("result.stdout {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("invalid user") { - // In the CI, some server are failing to return id. - // As seems to be a configuration issue, ignoring it - return; - } - assert!(result - .stderr - .contains("chown: it is dangerous to operate recursively")); + result.stderr_contains(&"chown: it is dangerous to operate recursively"); } #[cfg(target_os = "linux")] @@ -397,3 +484,29 @@ fn test_big_p() { ); } } + +#[test] +fn test_chown_file_notexisting() { + // test chown username not_existing + + let scene = TestScenario::new(util_name!()); + + let result = scene.cmd("whoami").run(); + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { + return; + } + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); + + let _result = scene + .ucmd() + .arg(user_name) + .arg("--verbose") + .arg("not_existing") + .fails(); + + // TODO: uncomment once "failed to change ownership of '{}' to {}" added to stdout + // result.stderr_contains(&"retained as"); + // TODO: uncomment once message changed from "cannot dereference" to "cannot access" + // result.stderr_contains(&"cannot access 'not_existing': No such file or directory"); +} From 3aff898acd9722f325c761d9faf6675a077853a7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 16 Apr 2021 18:58:46 +0200 Subject: [PATCH 037/399] Disable test_no_options_big_input on Windows --- tests/by-util/test_cat.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 7b4c9924e..f83d51328 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -26,6 +26,7 @@ fn test_no_options() { } #[test] +#[cfg(unix)] fn test_no_options_big_input() { for &n in &[ 0, From 58a2821dce70ed0d60704cbabb2924080f39f5f6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 16 Apr 2021 19:44:40 +0200 Subject: [PATCH 038/399] Also disable on test_three_directories_and_file_and_stdin --- tests/by-util/test_cat.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index f83d51328..eb6cc9148 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -109,6 +109,7 @@ fn test_directory_and_file() { } #[test] +#[cfg(unix)] fn test_three_directories_and_file_and_stdin() { let s = TestScenario::new(util_name!()); s.fixtures.mkdir("test_directory3"); From 685493f72bb07bcdd7c230f44d33952ac582e203 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 16 Apr 2021 18:27:36 +0200 Subject: [PATCH 039/399] ls: make path platform independent --- tests/by-util/test_ls.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index bdf4440e0..d810cdc29 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -5,6 +5,7 @@ use crate::common::util::*; extern crate regex; use self::regex::Regex; +use std::path::Path; use std::thread::sleep; use std::time::Duration; @@ -1323,7 +1324,7 @@ fn test_ls_directory() { at.mkdir("some_dir"); at.symlink_dir("some_dir", "sym_dir"); - at.touch("some_dir/nested_file"); + at.touch(Path::new("some_dir").join("nested_file").to_str().unwrap()); scene .ucmd() @@ -1419,7 +1420,7 @@ fn test_ls_deref_command_line_dir() { at.mkdir("some_dir"); at.symlink_dir("some_dir", "sym_dir"); - at.touch("some_dir/nested_file"); + at.touch(Path::new("some_dir").join("nested_file").to_str().unwrap()); scene .ucmd() From a76d452f75e1a503e1e1369143555d9153004edb Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 17 Apr 2021 03:06:19 -0500 Subject: [PATCH 040/399] Sort: More small fixes (#2065) * Various fixes and performance improvements * fix a typo Co-authored-by: Michael Debertol * Fix month parse for months with leading whitespace * Implement test for months whitespace fix * Confirm human numeric works as expected with whitespace with a test * Correct arg help value name for --parallel * Fix SemVer non version lines/empty line sorting with a test Co-authored-by: Sylvestre Ledru Co-authored-by: Michael Debertol --- Cargo.lock | 2 -- src/uu/sort/src/sort.rs | 19 +++++++++++++++---- tests/by-util/test_sort.rs | 19 +++++++++++++++++++ .../sort/human-numeric-whitespace.expected | 11 +++++++++++ .../sort/human-numeric-whitespace.txt | 11 +++++++++++ .../fixtures/sort/months-whitespace.expected | 8 ++++++++ tests/fixtures/sort/months-whitespace.txt | 8 ++++++++ .../sort/version-empty-lines.expected | 11 +++++++++++ tests/fixtures/sort/version-empty-lines.txt | 11 +++++++++++ 9 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/sort/human-numeric-whitespace.expected create mode 100644 tests/fixtures/sort/human-numeric-whitespace.txt create mode 100644 tests/fixtures/sort/months-whitespace.expected create mode 100644 tests/fixtures/sort/months-whitespace.txt create mode 100644 tests/fixtures/sort/version-empty-lines.expected create mode 100644 tests/fixtures/sort/version-empty-lines.txt 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/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 35ab71ba2..8bf6eb1e8 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -677,7 +677,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(OPT_PARALLEL) .long(OPT_PARALLEL) - .help("change the number of threads running concurrently to N") + .help("change the number of threads running concurrently to NUM_THREADS") .takes_value(true) .value_name("NUM_THREADS"), ) @@ -1226,7 +1226,7 @@ fn month_parse(line: &str) -> Month { // GNU splits at any 3 letter match "JUNNNN" is JUN let pattern = if line.trim().len().ge(&3) { // Split a 3 and get first element of tuple ".0" - line.split_at(3).0 + line.trim().split_at(3).0 } else { "" }; @@ -1262,10 +1262,21 @@ fn month_compare(a: &str, b: &str) -> Ordering { } } +fn version_parse(a: &str) -> Version { + let result = Version::parse(a); + + match result { + Ok(vers_a) => vers_a, + // Non-version lines parse to 0.0.0 + Err(_e) => Version::parse("0.0.0").unwrap(), + } +} + fn version_compare(a: &str, b: &str) -> Ordering { #![allow(clippy::comparison_chain)] - let ver_a = Version::parse(a); - let ver_b = Version::parse(b); + let ver_a = version_parse(a); + let ver_b = version_parse(b); + // Version::cmp is not implemented; implement comparison directly if ver_a > ver_b { Ordering::Greater diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 866beefff..0f8020688 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -8,6 +8,25 @@ fn test_helper(file_name: &str, args: &str) { .stdout_is_fixture(format!("{}.expected", file_name)); } +#[test] +fn test_months_whitespace() { + test_helper("months-whitespace", "-M"); +} + +#[test] +fn test_version_empty_lines() { + new_ucmd!() + .arg("-V") + .arg("version-empty-lines.txt") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n"); +} + +#[test] +fn test_human_numeric_whitespace() { + test_helper("human-numeric-whitespace", "-h"); +} + #[test] fn test_multiple_decimals_general() { new_ucmd!() diff --git a/tests/fixtures/sort/human-numeric-whitespace.expected b/tests/fixtures/sort/human-numeric-whitespace.expected new file mode 100644 index 000000000..6fb9291ff --- /dev/null +++ b/tests/fixtures/sort/human-numeric-whitespace.expected @@ -0,0 +1,11 @@ + + + + + + + +456K +4568K + 456M + 6.2G diff --git a/tests/fixtures/sort/human-numeric-whitespace.txt b/tests/fixtures/sort/human-numeric-whitespace.txt new file mode 100644 index 000000000..19db648b1 --- /dev/null +++ b/tests/fixtures/sort/human-numeric-whitespace.txt @@ -0,0 +1,11 @@ + + +456K + + 456M + + +4568K + + 6.2G + diff --git a/tests/fixtures/sort/months-whitespace.expected b/tests/fixtures/sort/months-whitespace.expected new file mode 100644 index 000000000..84a44d564 --- /dev/null +++ b/tests/fixtures/sort/months-whitespace.expected @@ -0,0 +1,8 @@ + + +JAN + FEb + apr + apr + JUNNNN +AUG diff --git a/tests/fixtures/sort/months-whitespace.txt b/tests/fixtures/sort/months-whitespace.txt new file mode 100644 index 000000000..45c477477 --- /dev/null +++ b/tests/fixtures/sort/months-whitespace.txt @@ -0,0 +1,8 @@ +JAN + JUNNNN +AUG + + apr + apr + + FEb diff --git a/tests/fixtures/sort/version-empty-lines.expected b/tests/fixtures/sort/version-empty-lines.expected new file mode 100644 index 000000000..c496c0ff5 --- /dev/null +++ b/tests/fixtures/sort/version-empty-lines.expected @@ -0,0 +1,11 @@ + + + + + + + +1.2.3-alpha +1.2.3-alpha2 +11.2.3 + 1.12.4 diff --git a/tests/fixtures/sort/version-empty-lines.txt b/tests/fixtures/sort/version-empty-lines.txt new file mode 100644 index 000000000..9b6b89788 --- /dev/null +++ b/tests/fixtures/sort/version-empty-lines.txt @@ -0,0 +1,11 @@ +11.2.3 + + + +1.2.3-alpha2 + + +1.2.3-alpha + + + 1.12.4 From f33320e58133c1e4182ddb1eac72ee0743e77ba0 Mon Sep 17 00:00:00 2001 From: joppich Date: Sat, 17 Apr 2021 10:07:45 +0200 Subject: [PATCH 041/399] do not pipe data into failure tests (#2072) Co-authored-by: joppich --- tests/by-util/test_stdbuf.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 61fa36977..808b7382a 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -25,19 +25,15 @@ fn test_stdbuf_line_buffered_stdout() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_no_buffer_option_fails() { - new_ucmd!() - .args(&["head"]) - .pipe_in("The quick brown fox jumps over the lazy dog.") - .fails() - .stderr_is( - "error: The following required arguments were not provided:\n \ + new_ucmd!().args(&["head"]).fails().stderr_is( + "error: The following required arguments were not provided:\n \ --error \n \ --input \n \ --output \n\n\ USAGE:\n \ stdbuf OPTION... COMMAND\n\n\ For more information try --help", - ); + ); } #[cfg(not(target_os = "windows"))] @@ -55,7 +51,6 @@ fn test_stdbuf_trailing_var_arg() { fn test_stdbuf_line_buffering_stdin_fails() { new_ucmd!() .args(&["-i", "L", "head"]) - .pipe_in("The quick brown fox jumps over the lazy dog.") .fails() .stderr_is("stdbuf: error: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information."); } @@ -65,7 +60,6 @@ fn test_stdbuf_line_buffering_stdin_fails() { fn test_stdbuf_invalid_mode_fails() { new_ucmd!() .args(&["-i", "1024R", "head"]) - .pipe_in("The quick brown fox jumps over the lazy dog.") .fails() .stderr_is("stdbuf: error: invalid mode 1024R\nTry 'stdbuf --help' for more information."); } From fe207640e24b25ec3cade4384da8b156265418b2 Mon Sep 17 00:00:00 2001 From: Aleksandar Janicijevic Date: Sat, 17 Apr 2021 04:08:10 -0400 Subject: [PATCH 042/399] touch: dealing with DST in touch -m -t (#2073) --- src/uu/touch/src/touch.rs | 23 ++++++++++++++++++++++- tests/by-util/test_touch.rs | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index f0c3c12d2..b158fdc0e 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -18,6 +18,7 @@ use filetime::*; use std::fs::{self, File}; use std::io::Error; use std::path::Path; +use std::process; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; @@ -261,7 +262,27 @@ fn parse_timestamp(s: &str) -> FileTime { }; match time::strptime(&ts, format) { - Ok(tm) => local_tm_to_filetime(to_local(tm)), + Ok(tm) => { + let mut local = to_local(tm); + local.tm_isdst = -1; + let ft = local_tm_to_filetime(local); + + // We have to check that ft is valid time. Due to daylight saving + // time switch, local time can jump from 1:59 AM to 3:00 AM, + // in which case any time between 2:00 AM and 2:59 AM is not valid. + // Convert back to local time and see if we got the same value back. + let ts = time::Timespec { + sec: ft.unix_seconds(), + nsec: 0, + }; + let tm2 = time::at(ts); + if tm.tm_hour != tm2.tm_hour { + show_error!("invalid date format {}", s); + process::exit(1); + } + + ft + } Err(e) => panic!("Unable to parse timestamp\n{}", e), } } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 9921c16b5..9f2c079b0 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -29,6 +29,7 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { fn str_to_filetime(format: &str, s: &str) -> FileTime { let mut tm = time::strptime(s, format).unwrap(); tm.tm_utcoff = time::now().tm_utcoff; + tm.tm_isdst = -1; // Unknown flag DST let ts = tm.to_timespec(); FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) } @@ -352,3 +353,21 @@ fn test_touch_set_date() { assert_eq!(atime, start_of_year); assert_eq!(mtime, start_of_year); } + +#[test] +fn test_touch_mtime_dst_succeeds() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_mtime_dst_succeeds"; + + ucmd.args(&["-m", "-t", "202103140300", file]) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file)); + + let target_time = str_to_filetime("%Y%m%d%H%M", "202103140300"); + let (_, mtime) = get_file_times(&at, file); + eprintln!("target_time: {:?}", target_time); + eprintln!("mtime: {:?}", mtime); + assert!(target_time == mtime); +} From d0c7e8c09e6101a10975640762006d6228ca0ed0 Mon Sep 17 00:00:00 2001 From: Andrew Rowson Date: Sat, 17 Apr 2021 09:26:52 +0100 Subject: [PATCH 043/399] du error output should match GNU (#1776) * du error output should match GNU * Created a new error macro which allows the customization of the "error:" string part * Match the du output based on the type of error encountered. Can extend to handling other errors I guess. * Rustfmt updates * Added non-windows test for du no permission output --- src/uu/du/src/du.rs | 18 ++++++++++++++++-- src/uucore/src/lib/macros.rs | 8 ++++++++ tests/by-util/test_du.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 07635881a..e01af5195 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -15,7 +15,7 @@ use chrono::Local; use std::collections::HashSet; use std::env; use std::fs; -use std::io::{stderr, Result, Write}; +use std::io::{stderr, ErrorKind, Result, Write}; use std::iter; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; @@ -296,7 +296,21 @@ fn du( } } } - Err(error) => show_error!("{}", error), + Err(error) => match error.kind() { + ErrorKind::PermissionDenied => { + let description = format!( + "cannot access '{}'", + entry + .path() + .as_os_str() + .to_str() + .unwrap_or("") + ); + let error_message = "Permission denied"; + show_error_custom_description!(description, "{}", error_message) + } + _ => show_error!("{}", error), + }, }, Err(error) => show_error!("{}", error), } diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 24b392ebd..637e91f8f 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -31,6 +31,14 @@ macro_rules! show_error( ); /// Show a warning to stderr in a silimar style to GNU coreutils. +#[macro_export] +macro_rules! show_error_custom_description ( + ($err:expr,$($args:tt)+) => ({ + eprint!("{}: {}: ", executable!(), $err); + eprintln!($($args)+); + }) +); + #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 8f2cff65d..f668edeef 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -190,3 +190,33 @@ fn test_du_time() { assert_eq!(result.stderr, ""); assert_eq!(result.stdout, "0\t2015-05-15 00:00\tdate_test\n"); } + +#[cfg(not(target_os = "windows"))] +#[cfg(feature = "chmod")] +#[test] +fn test_du_no_permission() { + let ts = TestScenario::new("du"); + + let chmod = ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).run(); + println!("chmod output: {:?}", chmod); + assert!(chmod.success); + let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); + + ts.ccmd("chmod").arg("+r").arg(SUB_DIR_LINKS).run(); + + assert!(result.success); + assert_eq!( + result.stderr, + "du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)\n" + ); + _du_no_permission(result.stdout); +} + +#[cfg(target_vendor = "apple")] +fn _du_no_permission(s: String) { + assert_eq!(s, "0\tsubdir/links\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] +fn _du_no_permission(s: String) { + assert_eq!(s, "4\tsubdir/links\n"); +} From c5b43c09943a719d81412f431241f5b412d64c4e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 17 Apr 2021 11:22:49 +0200 Subject: [PATCH 044/399] rustfmt the recent change --- src/uu/stdbuf/src/stdbuf.rs | 4 +++- src/uu/unexpand/src/unexpand.rs | 2 +- tests/by-util/test_chmod.rs | 15 ++++++++++----- tests/by-util/test_du.rs | 13 +++++++++---- tests/by-util/test_echo.rs | 5 +---- tests/by-util/test_env.rs | 14 ++++++++------ tests/by-util/test_fold.rs | 2 +- tests/by-util/test_hostname.rs | 4 +++- tests/by-util/test_id.rs | 8 +++----- tests/by-util/test_sort.rs | 8 ++++---- 10 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 8c65a5c7e..ddbd76133 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -144,7 +144,9 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result match value { "L" => { if name == options::INPUT { - Err(ProgramOptionsError("line buffering stdin is meaningless".to_string())) + Err(ProgramOptionsError( + "line buffering stdin is meaningless".to_string(), + )) } else { Ok(BufferType::Line) } diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 3d80bd6e9..a811d3b66 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -150,7 +150,7 @@ fn next_tabstop(tabstops: &[usize], col: usize) -> Option { } else { // find next larger tab // if there isn't one in the list, tab becomes a single space - tabstops.iter().find(|&&t| t > col).map(|t| t-col) + tabstops.iter().find(|&&t| t > col).map(|t| t - col) } } diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index d60b8a50b..9eda769f1 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -357,7 +357,8 @@ fn test_chmod_symlink_non_existing_file() { at.symlink_file(non_existing, test_symlink); // this cannot succeed since the symbolic link dangles - scene.ucmd() + scene + .ucmd() .arg("755") .arg("-v") .arg(test_symlink) @@ -367,7 +368,8 @@ fn test_chmod_symlink_non_existing_file() { .stderr_contains(expected_stderr); // this should be the same than with just '-v' but without stderr - scene.ucmd() + scene + .ucmd() .arg("755") .arg("-v") .arg("-f") @@ -394,7 +396,8 @@ fn test_chmod_symlink_non_existing_file_recursive() { ); // this should succeed - scene.ucmd() + scene + .ucmd() .arg("-R") .arg("755") .arg(test_directory) @@ -408,7 +411,8 @@ fn test_chmod_symlink_non_existing_file_recursive() { ); // '-v': this should succeed without stderr - scene.ucmd() + scene + .ucmd() .arg("-R") .arg("-v") .arg("755") @@ -418,7 +422,8 @@ fn test_chmod_symlink_non_existing_file_recursive() { .no_stderr(); // '-vf': this should be the same than with just '-v' - scene.ucmd() + scene + .ucmd() .arg("-R") .arg("-v") .arg("-f") diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index f668edeef..ea6b18937 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -7,9 +7,7 @@ const SUB_LINK: &str = "subdir/links/sublink.txt"; #[test] fn test_du_basics() { - new_ucmd!() - .succeeds() - .no_stderr(); + new_ucmd!().succeeds().no_stderr(); } #[cfg(target_vendor = "apple")] fn _du_basics(s: String) { @@ -178,7 +176,14 @@ fn test_du_h_flag_empty_file() { fn test_du_time() { let ts = TestScenario::new("du"); - let touch = ts.ccmd("touch").arg("-a").arg("-m").arg("-t").arg("201505150000").arg("date_test").run(); + let touch = ts + .ccmd("touch") + .arg("-a") + .arg("-m") + .arg("-t") + .arg("201505150000") + .arg("date_test") + .run(); assert!(touch.success); let result = ts.ucmd().arg("--time").arg("date_test").run(); diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 99c8f3a1e..5d1b68e6c 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -2,10 +2,7 @@ use crate::common::util::*; #[test] fn test_default() { - new_ucmd!() - .arg("hi") - .succeeds() - .stdout_only("hi\n"); + new_ucmd!().arg("hi").succeeds().stdout_only("hi\n"); } #[test] diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 19ecd7afb..39baf473b 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -26,17 +26,18 @@ fn test_env_version() { #[test] fn test_echo() { - let result = new_ucmd!() - .arg("echo") - .arg("FOO-bar") - .succeeds(); + let result = new_ucmd!().arg("echo").arg("FOO-bar").succeeds(); assert_eq!(result.stdout_str().trim(), "FOO-bar"); } #[test] fn test_file_option() { - let out = new_ucmd!().arg("-f").arg("vars.conf.txt").run().stdout_move_str(); + let out = new_ucmd!() + .arg("-f") + .arg("vars.conf.txt") + .run() + .stdout_move_str(); assert_eq!( out.lines() @@ -89,7 +90,8 @@ fn test_multiple_name_value_pairs() { let out = new_ucmd!().arg("FOO=bar").arg("ABC=xyz").run(); assert_eq!( - out.stdout_str().lines() + out.stdout_str() + .lines() .filter(|&line| line == "FOO=bar" || line == "ABC=xyz") .count(), 2 diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index ffcd65737..5224a50dc 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -542,4 +542,4 @@ fn test_obsolete_syntax() { .arg("space_separated_words.txt") .succeeds() .stdout_is("test1\n \ntest2\n \ntest3\n \ntest4\n \ntest5\n \ntest6\n "); -} \ No newline at end of file +} diff --git a/tests/by-util/test_hostname.rs b/tests/by-util/test_hostname.rs index 9fa63241f..c9dc99040 100644 --- a/tests/by-util/test_hostname.rs +++ b/tests/by-util/test_hostname.rs @@ -25,6 +25,8 @@ fn test_hostname_full() { let ls_short_res = new_ucmd!().arg("-s").succeeds(); assert!(!ls_short_res.stdout_str().trim().is_empty()); - new_ucmd!().arg("-f").succeeds() + new_ucmd!() + .arg("-f") + .succeeds() .stdout_contains(ls_short_res.stdout_str().trim()); } diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 7e2791467..719cfd876 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -46,7 +46,8 @@ fn test_id_from_name() { let result = new_ucmd!().arg(&username).succeeds(); let uid = result.stdout_str().trim(); - new_ucmd!().succeeds() + new_ucmd!() + .succeeds() // Verify that the id found by --user/-u exists in the list .stdout_contains(uid) // Verify that the username found by whoami exists in the list @@ -65,10 +66,7 @@ fn test_id_name_from_id() { return; } - let username_id = result - .success() - .stdout_str() - .trim(); + let username_id = result.success().stdout_str().trim(); let scene = TestScenario::new("whoami"); let result = scene.cmd("whoami").succeeds(); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0f8020688..c3b8870b9 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -16,10 +16,10 @@ fn test_months_whitespace() { #[test] fn test_version_empty_lines() { new_ucmd!() - .arg("-V") - .arg("version-empty-lines.txt") - .succeeds() - .stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n"); + .arg("-V") + .arg("version-empty-lines.txt") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n"); } #[test] From 4bbbe3a3f2f65673beee7f1edca416784a02a20c Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 17 Apr 2021 13:49:35 +0200 Subject: [PATCH 045/399] sort: implement numeric string comparison (#2070) * sort: implement numeric string comparison This implements -n and -h using a string comparison algorithm instead of parsing each number to a f64 and comparing those. This should result in a moderate performance increase and eliminate loss of precision. * cache parsed f64 numbers For general numeric comparisons we have to parse numbers as f64, as this behavior is explicitly documented by GNU coreutils. We can however cache the parsed value to speed up comparisons. * fix leading zeroes for negative numbers * use more appropriate name for exponent * improvements to the parse function * move checks into main loop and fix thousands separator condition * remove unneeded checks * rustfmt --- src/uu/sort/BENCHMARKING.md | 81 +++- src/uu/sort/src/numeric_str_cmp.rs | 455 ++++++++++++++++++ src/uu/sort/src/sort.rs | 274 ++++------- tests/by-util/test_sort.rs | 6 +- .../sort/multiple_decimals_numeric.expected | 35 ++ 5 files changed, 667 insertions(+), 184 deletions(-) create mode 100644 src/uu/sort/src/numeric_str_cmp.rs create mode 100644 tests/fixtures/sort/multiple_decimals_numeric.expected diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index b20db014d..78c2e2b2d 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -9,25 +9,84 @@ list that we should improve / make sure not to regress. Run `cargo build --release` before benchmarking after you make a change! ## Sorting a wordlist -- Get a wordlist, for example with [words](https://en.wikipedia.org/wiki/Words_(Unix)) on Linux. The exact wordlist - doesn't matter for performance comparisons. In this example I'm using `/usr/share/dict/american-english` as the wordlist. -- Shuffle the wordlist by running `sort -R /usr/share/dict/american-english > shuffled_wordlist.txt`. -- Benchmark sorting the wordlist with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -o output.txt"`. + +- Get a wordlist, for example with [words]() on Linux. The exact wordlist + doesn't matter for performance comparisons. In this example I'm using `/usr/share/dict/american-english` as the wordlist. +- Shuffle the wordlist by running `sort -R /usr/share/dict/american-english > shuffled_wordlist.txt`. +- Benchmark sorting the wordlist with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -o output.txt"`. ## Sorting a wordlist with ignore_case -- Same wordlist as above -- Benchmark sorting the wordlist ignoring the case with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -f -o output.txt"`. + +- Same wordlist as above +- Benchmark sorting the wordlist ignoring the case with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -f -o output.txt"`. ## Sorting numbers -- Generate a list of numbers: `seq 0 100000 | sort -R > shuffled_numbers.txt`. -- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"`. + +- Generate a list of numbers: `seq 0 100000 | sort -R > shuffled_numbers.txt`. +- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"`. + +## Sorting numbers with -g + +- Same list of numbers as above. +- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -g -o output.txt"`. + +## Sorting numbers with SI prefixes + +- Generate a list of numbers: +
+ Rust script + + ## Cargo.toml + + ```toml + [dependencies] + rand = "0.8.3" + ``` + + ## main.rs + + ```rust + use rand::prelude::*; + fn main() { + let suffixes = ['k', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + let mut rng = thread_rng(); + for _ in 0..100000 { + println!( + "{}{}", + rng.gen_range(0..1000000), + suffixes.choose(&mut rng).unwrap() + ) + } + } + + ``` + + ## running + + `cargo run > shuffled_numbers_si.txt` + +
+ +- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"`. ## Stdout and stdin performance + Try to run the above benchmarks by piping the input through stdin (standard input) and redirect the output through stdout (standard output): -- Remove the input file from the arguments and add `cat [inputfile] | ` at the beginning. -- Remove `-o output.txt` and add `> output.txt` at the end. + +- Remove the input file from the arguments and add `cat [inputfile] | ` at the beginning. +- Remove `-o output.txt` and add `> output.txt` at the end. Example: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"` becomes `hyperfine "cat shuffled_numbers.txt | target/release/coreutils sort -n > output.txt` -- Check that performance is similar to the original benchmark. \ No newline at end of file + +- Check that performance is similar to the original benchmark. + +## Comparing with GNU sort + +Hyperfine accepts multiple commands to run and will compare them. To compare performance with GNU sort +duplicate the string you passed to hyperfine but remove the `target/release/coreutils` bit from it. + +Example: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"` becomes +`hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt" "sort shuffled_numbers_si.txt -h -o output.txt"` +(This assumes GNU sort is installed as `sort`) diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs new file mode 100644 index 000000000..a50734ebd --- /dev/null +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -0,0 +1,455 @@ +// * This file is part of the uutils coreutils package. +// * +// * (c) Michael Debertol +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +//! Fast comparison for strings representing a base 10 number without precision loss. +//! +//! To be able to short-circuit when comparing, [NumInfo] must be passed along with each number +//! to [numeric_str_cmp]. [NumInfo] is generally obtained by calling [NumInfo::parse] and should be cached. +//! It is allowed to arbitrarily modify the exponent afterwards, which is equivalent to shifting the decimal point. +//! +//! More specifically, exponent can be understood so that the original number is in (1..10)*10^exponent. +//! From that follows the constraints of this algorithm: It is able to compare numbers in ±(1*10^[i64::MIN]..10*10^[i64::MAX]). + +use std::{cmp::Ordering, ops::Range}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +enum Sign { + Negative, + Positive, +} + +#[derive(Debug, PartialEq)] +pub struct NumInfo { + exponent: i64, + sign: Sign, +} + +pub struct NumInfoParseSettings { + pub accept_si_units: bool, + pub thousands_separator: Option, + pub decimal_pt: Option, +} + +impl Default for NumInfoParseSettings { + fn default() -> Self { + Self { + accept_si_units: false, + thousands_separator: None, + decimal_pt: Some('.'), + } + } +} + +impl NumInfo { + /// Parse NumInfo for this number. + /// Also returns the range of num that should be passed to numeric_str_cmp later + pub fn parse(num: &str, parse_settings: NumInfoParseSettings) -> (Self, Range) { + let mut exponent = -1; + let mut had_decimal_pt = false; + let mut had_digit = false; + let mut start = None; + let mut sign = Sign::Positive; + + let mut first_char = true; + + for (idx, char) in num.char_indices() { + if first_char && char.is_whitespace() { + continue; + } + + if first_char && char == '-' { + sign = Sign::Negative; + first_char = false; + continue; + } + first_char = false; + + if parse_settings + .thousands_separator + .map_or(false, |c| c == char) + { + continue; + } + + if Self::is_invalid_char(char, &mut had_decimal_pt, &parse_settings) { + let si_unit = if parse_settings.accept_si_units { + match char { + 'K' | 'k' => 3, + 'M' => 6, + 'G' => 9, + 'T' => 12, + 'P' => 15, + 'E' => 18, + 'Z' => 21, + 'Y' => 24, + _ => 0, + } + } else { + 0 + }; + return if let Some(start) = start { + ( + NumInfo { + exponent: exponent + si_unit, + sign, + }, + start..idx, + ) + } else { + ( + NumInfo { + sign: if had_digit { sign } else { Sign::Positive }, + exponent: 0, + }, + 0..0, + ) + }; + } + if Some(char) == parse_settings.decimal_pt { + continue; + } + had_digit = true; + if start.is_none() && char == '0' { + if had_decimal_pt { + // We're parsing a number whose first nonzero digit is after the decimal point. + exponent -= 1; + } else { + // Skip leading zeroes + continue; + } + } + if !had_decimal_pt { + exponent += 1; + } + if start.is_none() && char != '0' { + start = Some(idx); + } + } + if let Some(start) = start { + (NumInfo { exponent, sign }, start..num.len()) + } else { + ( + NumInfo { + sign: if had_digit { sign } else { Sign::Positive }, + exponent: 0, + }, + 0..0, + ) + } + } + + fn is_invalid_char( + c: char, + had_decimal_pt: &mut bool, + parse_settings: &NumInfoParseSettings, + ) -> bool { + if Some(c) == parse_settings.decimal_pt { + if *had_decimal_pt { + // this is a decimal pt but we already had one, so it is invalid + true + } else { + *had_decimal_pt = true; + false + } + } else { + !c.is_ascii_digit() + } + } +} + +/// compare two numbers as strings without parsing them as a number first. This should be more performant and can handle numbers more precisely. +/// NumInfo is needed to provide a fast path for most numbers. +pub fn numeric_str_cmp((a, a_info): (&str, &NumInfo), (b, b_info): (&str, &NumInfo)) -> Ordering { + // check for a difference in the sign + if a_info.sign != b_info.sign { + return a_info.sign.cmp(&b_info.sign); + } + + // check for a difference in the exponent + let ordering = if a_info.exponent != b_info.exponent && !a.is_empty() && !b.is_empty() { + a_info.exponent.cmp(&b_info.exponent) + } else { + // walk the characters from the front until we find a difference + let mut a_chars = a.chars().filter(|c| c.is_ascii_digit()); + let mut b_chars = b.chars().filter(|c| c.is_ascii_digit()); + loop { + let a_next = a_chars.next(); + let b_next = b_chars.next(); + match (a_next, b_next) { + (None, None) => break Ordering::Equal, + (Some(c), None) => { + break if c == '0' && a_chars.all(|c| c == '0') { + Ordering::Equal + } else { + Ordering::Greater + } + } + (None, Some(c)) => { + break if c == '0' && b_chars.all(|c| c == '0') { + Ordering::Equal + } else { + Ordering::Less + } + } + (Some(a_char), Some(b_char)) => { + let ord = a_char.cmp(&b_char); + if ord != Ordering::Equal { + break ord; + } + } + } + } + }; + + if a_info.sign == Sign::Negative { + ordering.reverse() + } else { + ordering + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_exp() { + let n = "1"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 0, + sign: Sign::Positive + }, + 0..1 + ) + ); + let n = "100"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 2, + sign: Sign::Positive + }, + 0..3 + ) + ); + let n = "1,000"; + assert_eq!( + NumInfo::parse( + n, + NumInfoParseSettings { + thousands_separator: Some(','), + ..Default::default() + } + ), + ( + NumInfo { + exponent: 3, + sign: Sign::Positive + }, + 0..5 + ) + ); + let n = "1,000"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 0, + sign: Sign::Positive + }, + 0..1 + ) + ); + let n = "1000.00"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 3, + sign: Sign::Positive + }, + 0..7 + ) + ); + } + #[test] + fn parses_negative_exp() { + let n = "0.00005"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: -5, + sign: Sign::Positive + }, + 6..7 + ) + ); + let n = "00000.00005"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: -5, + sign: Sign::Positive + }, + 10..11 + ) + ); + } + + #[test] + fn parses_sign() { + let n = "5"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 0, + sign: Sign::Positive + }, + 0..1 + ) + ); + let n = "-5"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 0, + sign: Sign::Negative + }, + 1..2 + ) + ); + let n = " -5"; + assert_eq!( + NumInfo::parse(n, Default::default()), + ( + NumInfo { + exponent: 0, + sign: Sign::Negative + }, + 5..6 + ) + ); + } + + fn test_helper(a: &str, b: &str, expected: Ordering) { + let (a_info, a_range) = NumInfo::parse(a, Default::default()); + let (b_info, b_range) = NumInfo::parse(b, Default::default()); + let ordering = numeric_str_cmp( + (&a[a_range.to_owned()], &a_info), + (&b[b_range.to_owned()], &b_info), + ); + assert_eq!(ordering, expected); + let ordering = numeric_str_cmp((&b[b_range], &b_info), (&a[a_range], &a_info)); + assert_eq!(ordering, expected.reverse()); + } + #[test] + fn test_single_digit() { + test_helper("1", "2", Ordering::Less); + test_helper("0", "0", Ordering::Equal); + } + #[test] + fn test_minus() { + test_helper("-1", "-2", Ordering::Greater); + test_helper("-0", "-0", Ordering::Equal); + } + #[test] + fn test_different_len() { + test_helper("-20", "-100", Ordering::Greater); + test_helper("10.0", "2.000000", Ordering::Greater); + } + #[test] + fn test_decimal_digits() { + test_helper("20.1", "20.2", Ordering::Less); + test_helper("20.1", "20.15", Ordering::Less); + test_helper("-20.1", "+20.15", Ordering::Less); + test_helper("-20.1", "-20", Ordering::Less); + } + #[test] + fn test_trailing_zeroes() { + test_helper("20.00000", "20.1", Ordering::Less); + test_helper("20.00000", "20.0", Ordering::Equal); + } + #[test] + fn test_invalid_digits() { + test_helper("foo", "bar", Ordering::Equal); + test_helper("20.1", "a", Ordering::Greater); + test_helper("-20.1", "a", Ordering::Less); + test_helper("a", "0.15", Ordering::Less); + } + #[test] + fn test_multiple_decimal_pts() { + test_helper("10.0.0", "50.0.0", Ordering::Less); + test_helper("0.1.", "0.2.0", Ordering::Less); + test_helper("1.1.", "0", Ordering::Greater); + test_helper("1.1.", "-0", Ordering::Greater); + } + #[test] + fn test_leading_decimal_pts() { + test_helper(".0", ".0", Ordering::Equal); + test_helper(".1", ".0", Ordering::Greater); + test_helper(".02", "0", Ordering::Greater); + } + #[test] + fn test_leading_zeroes() { + test_helper("000000.0", ".0", Ordering::Equal); + test_helper("0.1", "0000000000000.0", Ordering::Greater); + test_helper("-01", "-2", Ordering::Greater); + } + + #[test] + fn minus_zero() { + // This matches GNU sort behavior. + test_helper("-0", "0", Ordering::Less); + test_helper("-0x", "0", Ordering::Less); + } + #[test] + fn double_minus() { + test_helper("--1", "0", Ordering::Equal); + } + #[test] + fn single_minus() { + let info = NumInfo::parse("-", Default::default()); + assert_eq!( + info, + ( + NumInfo { + exponent: 0, + sign: Sign::Positive + }, + 0..0 + ) + ); + } + #[test] + fn invalid_with_unit() { + let info = NumInfo::parse( + "-K", + NumInfoParseSettings { + accept_si_units: true, + ..Default::default() + }, + ); + assert_eq!( + info, + ( + NumInfo { + exponent: 0, + sign: Sign::Positive + }, + 0..0 + ) + ); + } +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8bf6eb1e8..c097861fc 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,9 +15,12 @@ #[macro_use] extern crate uucore; +mod numeric_str_cmp; + use clap::{App, Arg}; use fnv::FnvHasher; use itertools::Itertools; +use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; @@ -162,27 +165,71 @@ impl From<&GlobalSettings> for KeySettings { } /// Represents the string selected by a FieldSelector. -#[derive(Debug)] -enum Selection { +enum SelectionRange { /// If we had to transform this selection, we have to store a new string. String(String), /// If there was no transformation, we can store an index into the line. ByIndex(Range), } +impl SelectionRange { + /// Gets the actual string slice represented by this Selection. + fn get_str<'a>(&'a self, line: &'a str) -> &'a str { + match self { + SelectionRange::String(string) => string.as_str(), + SelectionRange::ByIndex(range) => &line[range.to_owned()], + } + } + + fn shorten(&mut self, new_range: Range) { + match self { + SelectionRange::String(string) => { + string.drain(new_range.end..); + string.drain(..new_range.start); + } + SelectionRange::ByIndex(range) => { + range.end = range.start + new_range.end; + range.start += new_range.start; + } + } + } +} + +enum NumCache { + AsF64(f64), + WithInfo(NumInfo), + None, +} + +impl NumCache { + fn as_f64(&self) -> f64 { + match self { + NumCache::AsF64(n) => *n, + _ => unreachable!(), + } + } + fn as_num_info(&self) -> &NumInfo { + match self { + NumCache::WithInfo(n) => n, + _ => unreachable!(), + } + } +} + +struct Selection { + range: SelectionRange, + num_cache: NumCache, +} + impl Selection { /// Gets the actual string slice represented by this Selection. fn get_str<'a>(&'a self, line: &'a Line) -> &'a str { - match self { - Selection::String(string) => string.as_str(), - Selection::ByIndex(range) => &line.line[range.to_owned()], - } + self.range.get_str(&line.line) } } type Field = Range; -#[derive(Debug)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. @@ -206,18 +253,38 @@ impl Line { .selectors .iter() .map(|selector| { - if let Some(range) = selector.get_selection(&line, fields.as_deref()) { - if let Some(transformed) = - transform(&line[range.to_owned()], &selector.settings) - { - Selection::String(transformed) + let mut range = + if let Some(range) = selector.get_selection(&line, fields.as_deref()) { + if let Some(transformed) = + transform(&line[range.to_owned()], &selector.settings) + { + SelectionRange::String(transformed) + } else { + SelectionRange::ByIndex(range.start().to_owned()..range.end() + 1) + } } else { - Selection::ByIndex(range.start().to_owned()..range.end() + 1) - } + // If there is no match, match the empty string. + SelectionRange::ByIndex(0..0) + }; + let num_cache = if selector.settings.mode == SortMode::Numeric + || selector.settings.mode == SortMode::HumanNumeric + { + let (info, num_range) = NumInfo::parse( + range.get_str(&line), + NumInfoParseSettings { + accept_si_units: selector.settings.mode == SortMode::HumanNumeric, + thousands_separator: Some(THOUSANDS_SEP), + decimal_pt: Some(DECIMAL_PT), + }, + ); + range.shorten(num_range); + NumCache::WithInfo(info) + } else if selector.settings.mode == SortMode::GeneralNumeric { + NumCache::AsF64(permissive_f64_parse(get_leading_gen(range.get_str(&line)))) } else { - // If there is no match, match the empty string. - Selection::ByIndex(0..0) - } + NumCache::None + }; + Selection { range, num_cache } }) .collect(); Self { line, selections } @@ -923,21 +990,28 @@ fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { for (idx, selector) in global_settings.selectors.iter().enumerate() { - let a = a.selections[idx].get_str(a); - let b = b.selections[idx].get_str(b); + let a_selection = &a.selections[idx]; + let b_selection = &b.selections[idx]; + let a_str = a_selection.get_str(a); + let b_str = b_selection.get_str(b); let settings = &selector.settings; let cmp: Ordering = if settings.random { - random_shuffle(a, b, global_settings.salt.clone()) + random_shuffle(a_str, b_str, global_settings.salt.clone()) } else { - (match settings.mode { - SortMode::Numeric => numeric_compare, - SortMode::GeneralNumeric => general_numeric_compare, - SortMode::HumanNumeric => human_numeric_size_compare, - SortMode::Month => month_compare, - SortMode::Version => version_compare, - SortMode::Default => default_compare, - })(a, b) + match settings.mode { + SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( + (a_str, a_selection.num_cache.as_num_info()), + (b_str, b_selection.num_cache.as_num_info()), + ), + SortMode::GeneralNumeric => general_numeric_compare( + a_selection.num_cache.as_f64(), + b_selection.num_cache.as_f64(), + ), + SortMode::Month => month_compare(a_str, b_str), + SortMode::Version => version_compare(a_str, b_str), + SortMode::Default => default_compare(a_str, b_str), + } }; if cmp != Ordering::Equal { return if settings.reverse { cmp.reverse() } else { cmp }; @@ -945,7 +1019,6 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } // Call "last resort compare" if all selectors returned Equal - let cmp = if global_settings.random || global_settings.stable || global_settings.unique { Ordering::Equal } else { @@ -997,34 +1070,6 @@ fn leading_num_common(a: &str) -> &str { s } -// This function cleans up the initial comparison done by leading_num_common for a numeric compare. -// GNU sort does its numeric comparison through strnumcmp. However, we don't have or -// may not want to use libc. Instead we emulate the GNU sort numeric compare by ignoring -// those leading number lines GNU sort would not recognize. GNU numeric compare would -// not recognize a positive sign or scientific/E notation so we strip those elements here. -fn get_leading_num(a: &str) -> &str { - let mut s = ""; - - let a = leading_num_common(a); - - // GNU numeric sort doesn't recognize '+' or 'e' notation so we strip - for (idx, c) in a.char_indices() { - if c.eq(&'e') || c.eq(&'E') || a.chars().next().unwrap_or('\0').eq(&POSITIVE) { - s = &a[..idx]; - break; - } - // If no further processing needed to be done, return the line as-is to be sorted - s = &a; - } - - // And empty number or non-number lines are to be treated as ‘0’ but only for numeric sort - // All '0'-ed lines will be sorted later, but only amongst themselves, during the so-called 'last resort comparison.' - if s.is_empty() { - s = "0"; - }; - s -} - // This function cleans up the initial comparison done by leading_num_common for a general numeric compare. // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. @@ -1054,17 +1099,6 @@ fn get_leading_gen(a: &str) -> &str { result } -#[inline(always)] -fn remove_thousands_sep<'a, S: Into>>(input: S) -> Cow<'a, str> { - let input = input.into(); - if input.contains(THOUSANDS_SEP) { - let output = input.replace(THOUSANDS_SEP, ""); - Cow::Owned(output) - } else { - input - } -} - #[inline(always)] fn remove_trailing_dec<'a, S: Into>>(input: S) -> Cow<'a, str> { let input = input.into(); @@ -1093,87 +1127,15 @@ fn permissive_f64_parse(a: &str) -> f64 { } } -fn numeric_compare(a: &str, b: &str) -> Ordering { - #![allow(clippy::comparison_chain)] - - let sa = get_leading_num(a); - let sb = get_leading_num(b); - - // Avoids a string alloc for every line to remove thousands seperators here - // instead of inside the get_leading_num function, which is a HUGE performance benefit - let ta = remove_thousands_sep(sa); - let tb = remove_thousands_sep(sb); - - let fa = permissive_f64_parse(&ta); - let fb = permissive_f64_parse(&tb); - - // f64::cmp isn't implemented (due to NaN issues); implement directly instead - if fa > fb { - Ordering::Greater - } else if fa < fb { - Ordering::Less - } else { - Ordering::Equal - } -} - /// Compares two floats, with errors and non-numerics assumed to be -inf. /// Stops coercing at the first non-numeric char. -fn general_numeric_compare(a: &str, b: &str) -> Ordering { +/// We explicitly need to convert to f64 in this case. +fn general_numeric_compare(a: f64, b: f64) -> Ordering { #![allow(clippy::comparison_chain)] - - let sa = get_leading_gen(a); - let sb = get_leading_gen(b); - - let fa = permissive_f64_parse(&sa); - let fb = permissive_f64_parse(&sb); - // f64::cmp isn't implemented (due to NaN issues); implement directly instead - if fa > fb { + if a > b { Ordering::Greater - } else if fa < fb { - Ordering::Less - } else { - Ordering::Equal - } -} - -// GNU/BSD does not handle converting numbers to an equal scale -// properly. GNU/BSD simply recognize that there is a human scale and sorts -// those numbers ahead of other number inputs. There are perhaps limits -// to the type of behavior we should emulate, and this might be such a limit. -// Properly handling these units seems like a value add to me. And when sorting -// these types of numbers, we rarely care about pure performance. -fn human_numeric_convert(a: &str) -> f64 { - let num_str = get_leading_num(a); - let suffix = a.trim_start_matches(&num_str); - let num_part = permissive_f64_parse(&num_str); - let suffix: f64 = match suffix.parse().unwrap_or('\0') { - // SI Units - 'K' => 1E3, - 'M' => 1E6, - 'G' => 1E9, - 'T' => 1E12, - 'P' => 1E15, - 'E' => 1E18, - 'Z' => 1E21, - 'Y' => 1E24, - _ => 1f64, - }; - num_part * suffix -} - -/// Compare two strings as if they are human readable sizes. -/// AKA 1M > 100k -fn human_numeric_size_compare(a: &str, b: &str) -> Ordering { - #![allow(clippy::comparison_chain)] - let fa = human_numeric_convert(a); - let fb = human_numeric_convert(b); - - // f64::cmp isn't implemented (due to NaN issues); implement directly instead - if fa > fb { - Ordering::Greater - } else if fa < fb { + } else if a < b { Ordering::Less } else { Ordering::Equal @@ -1373,30 +1335,6 @@ mod tests { assert_eq!(Ordering::Less, default_compare(a, b)); } - #[test] - fn test_numeric_compare1() { - let a = "149:7"; - let b = "150:5"; - - assert_eq!(Ordering::Less, numeric_compare(a, b)); - } - - #[test] - fn test_numeric_compare2() { - let a = "-1.02"; - let b = "1"; - - assert_eq!(Ordering::Less, numeric_compare(a, b)); - } - - #[test] - fn test_human_numeric_compare() { - let a = "300K"; - let b = "1M"; - - assert_eq!(Ordering::Less, human_numeric_size_compare(a, b)); - } - #[test] fn test_month_compare() { let a = "JaN"; diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index c3b8870b9..aacc34eb0 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -38,11 +38,7 @@ fn test_multiple_decimals_general() { #[test] fn test_multiple_decimals_numeric() { - new_ucmd!() - .arg("-n") - .arg("multiple_decimals_numeric.txt") - .succeeds() - .stdout_is("-2028789030\n-896689\n-8.90880\n-1\n-.05\n\n\n\n\n\n\n\n\n000\nCARAvan\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n576,446.88800000\n576,446.890\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); + test_helper("multiple_decimals_numeric", "-n") } #[test] diff --git a/tests/fixtures/sort/multiple_decimals_numeric.expected b/tests/fixtures/sort/multiple_decimals_numeric.expected new file mode 100644 index 000000000..3ef4d22e8 --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_numeric.expected @@ -0,0 +1,35 @@ +-2028789030 +-896689 +-8.90880 +-1 +-.05 + + + + + + + + +000 +CARAvan +00000001 +1 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 + 4567..457 + 4567. +4567.1 +4567.34 + 37800 + 45670.89079.098 + 45670.89079.1 +576,446.88800000 +576,446.890 +4798908.340000000000 +4798908.45 +4798908.8909800 From 0d1946a5d27e4ff79196d18cc51835a1836da8f9 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 17 Apr 2021 15:01:52 +0300 Subject: [PATCH 046/399] cksum: Remove direct usage of CmdResult fields in tests --- tests/by-util/test_cksum.rs | 54 ++++++++++++++++++------------------- tests/common/util.rs | 36 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 1a0915cd5..c8e60f8a9 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -31,7 +31,10 @@ fn test_empty() { at.touch("a"); - ucmd.arg("a").succeeds().stdout.ends_with("0 a"); + ucmd.arg("a") + .succeeds() + .no_stderr() + .normalized_newlines_stdout_is("4294967295 0 a\n"); } #[test] @@ -41,36 +44,35 @@ fn test_arg_overrides_stdin() { at.touch("a"); - let result = ucmd - .arg("a") + ucmd.arg("a") .pipe_in(input.as_bytes()) // the command might have exited before all bytes have been pipe in. // in that case, we don't care about the error (broken pipe) .ignore_stdin_write_error() - .run(); - - println!("{}, {}", result.stdout, result.stderr); - - assert!(result.stdout.ends_with("0 a\n")) + .succeeds() + .no_stderr() + .normalized_newlines_stdout_is("4294967295 0 a\n"); } #[test] fn test_invalid_file() { - let (_, mut ucmd) = at_and_ucmd!(); + let ts = TestScenario::new(util_name!()); + let at = ts.fixtures.clone(); - let ls = TestScenario::new("ls"); - let files = ls.cmd("ls").arg("-l").run(); - println!("{:?}", files.stdout); - println!("{:?}", files.stderr); + let folder_name = "asdf"; - let folder_name = "asdf".to_string(); - - let result = ucmd.arg(&folder_name).run(); - - println!("stdout: {:?}", result.stdout); - println!("stderr: {:?}", result.stderr); - assert!(result.stderr.contains("cksum: error: 'asdf'")); - assert!(!result.success); + // First check when file doesn't exist + ts.ucmd().arg(folder_name) + .fails() + .no_stdout() + .stderr_contains("cksum: error: 'asdf' No such file or directory"); + + // Then check when the file is of an invalid type + at.mkdir(folder_name); + ts.ucmd().arg(folder_name) + .fails() + .no_stdout() + .stderr_contains("cksum: error: 'asdf' Is a directory"); } // Make sure crc is correct for files larger than 32 bytes @@ -79,14 +81,13 @@ fn test_invalid_file() { fn test_crc_for_bigger_than_32_bytes() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("chars.txt").run(); + let result = ucmd.arg("chars.txt").succeeds(); - let mut stdout_splitted = result.stdout.split(" "); + let mut stdout_splitted = result.stdout_str().split(" "); let cksum: i64 = stdout_splitted.next().unwrap().parse().unwrap(); let bytes_cnt: i64 = stdout_splitted.next().unwrap().parse().unwrap(); - assert!(result.success); assert_eq!(cksum, 586047089); assert_eq!(bytes_cnt, 16); } @@ -95,14 +96,13 @@ fn test_crc_for_bigger_than_32_bytes() { fn test_stdin_larger_than_128_bytes() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("larger_than_2056_bytes.txt").run(); + let result = ucmd.arg("larger_than_2056_bytes.txt").succeeds(); - let mut stdout_splitted = result.stdout.split(" "); + let mut stdout_splitted = result.stdout_str().split(" "); let cksum: i64 = stdout_splitted.next().unwrap().parse().unwrap(); let bytes_cnt: i64 = stdout_splitted.next().unwrap().parse().unwrap(); - assert!(result.success); assert_eq!(cksum, 945881979); assert_eq!(bytes_cnt, 2058); } diff --git a/tests/common/util.rs b/tests/common/util.rs index c7f46c2a9..b54c8ee39 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -220,6 +220,13 @@ impl CmdResult { self } + /// Like `stdout_is` but newlines are normalized to `\n`. + pub fn normalized_newlines_stdout_is>(&self, msg: T) -> &CmdResult { + let msg = msg.as_ref().replace("\r\n", "\n"); + assert_eq!(self.stdout.replace("\r\n", "\n"), msg); + self + } + /// asserts that the command resulted in stdout stream output, /// whose bytes equal those of the passed in slice pub fn stdout_is_bytes>(&self, msg: T) -> &CmdResult { @@ -1096,4 +1103,33 @@ mod tests { res.stdout_does_not_match(&positive); } + + #[test] + fn test_normalized_newlines_stdout_is() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "A\r\nB\nC".into(), + stderr: "".into(), + }; + + res.normalized_newlines_stdout_is("A\r\nB\nC"); + res.normalized_newlines_stdout_is("A\nB\nC"); + res.normalized_newlines_stdout_is("A\nB\r\nC"); + } + + #[test] + #[should_panic] + fn test_normalized_newlines_stdout_is_fail() { + let res = CmdResult { + tmpd: None, + code: None, + success: true, + stdout: "A\r\nB\nC".into(), + stderr: "".into(), + }; + + res.normalized_newlines_stdout_is("A\r\nB\nC\n"); + } } From 7c7e64e79c41cffce6ce00c02f97f53e911e11d0 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 17 Apr 2021 15:14:49 +0300 Subject: [PATCH 047/399] pinky, mktemp: Remove direct usage of CmdResult fields in test --- tests/by-util/test_mktemp.rs | 24 +++++++++--------------- tests/by-util/test_pinky.rs | 12 ++++-------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index 2639a2c2f..aa3ff5f1f 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -113,17 +113,14 @@ fn test_mktemp_mktemp_t() { .arg("-t") .arg(TEST_TEMPLATE7) .succeeds(); - let result = scene + scene .ucmd() .env(TMPDIR, &pathname) .arg("-t") .arg(TEST_TEMPLATE8) - .fails(); - println!("stdout {}", result.stdout); - println!("stderr {}", result.stderr); - assert!(result - .stderr - .contains("error: suffix cannot contain any path separators")); + .fails() + .no_stdout() + .stderr_contains("error: suffix cannot contain any path separators"); } #[test] @@ -391,10 +388,9 @@ fn test_mktemp_tmpdir_one_arg() { .arg("--tmpdir") .arg("apt-key-gpghome.XXXXXXXXXX") .succeeds(); - println!("stdout {}", result.stdout); - println!("stderr {}", result.stderr); - assert!(result.stdout.contains("apt-key-gpghome.")); - assert!(PathBuf::from(result.stdout.trim()).is_file()); + result.no_stderr() + .stdout_contains("apt-key-gpghome."); + assert!(PathBuf::from(result.stdout_str().trim()).is_file()); } #[test] @@ -407,8 +403,6 @@ fn test_mktemp_directory_tmpdir() { .arg("--tmpdir") .arg("apt-key-gpghome.XXXXXXXXXX") .succeeds(); - println!("stdout {}", result.stdout); - println!("stderr {}", result.stderr); - assert!(result.stdout.contains("apt-key-gpghome.")); - assert!(PathBuf::from(result.stdout.trim()).is_dir()); + result.no_stderr().stdout_contains("apt-key-gpghome."); + assert!(PathBuf::from(result.stdout_str().trim()).is_dir()); } diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index c8e8334ab..161054b2c 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -43,11 +43,9 @@ fn test_short_format_i() { let actual = TestScenario::new(util_name!()) .ucmd() .args(&args) - .run() - .stdout; + .succeeds() + .stdout_move_str(); let expect = expected_result(&args); - println!("actual: {:?}", actual); - println!("expect: {:?}", expect); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); @@ -62,11 +60,9 @@ fn test_short_format_q() { let actual = TestScenario::new(util_name!()) .ucmd() .args(&args) - .run() - .stdout; + .succeeds() + .stdout_move_str(); let expect = expected_result(&args); - println!("actual: {:?}", actual); - println!("expect: {:?}", expect); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); From 600bab52ffcfa90ec69c1ec45fc2e4ec7280a19c Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 17 Apr 2021 15:22:20 +0300 Subject: [PATCH 048/399] shred, stat, tail: Remove direct usage of CmdResult fields in test --- tests/by-util/test_shred.rs | 4 +--- tests/by-util/test_stat.rs | 6 +++--- tests/by-util/test_tail.rs | 8 ++++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index de54fae5b..b29b9bfec 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -36,9 +36,7 @@ fn test_shred_force() { at.set_readonly(file); // Try shred -u. - let result = scene.ucmd().arg("-u").arg(file).run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); + scene.ucmd().arg("-u").arg(file).run(); // file_a was not deleted because it is readonly. assert!(at.file_exists(file)); diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 225ea52cd..376b3db51 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -194,7 +194,7 @@ fn test_terse_normal_format() { // note: contains birth/creation date which increases test fragility // * results may vary due to built-in `stat` limitations as well as linux kernel and rust version capability variations let args = ["-t", "/"]; - let actual = new_ucmd!().args(&args).run().stdout; + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -216,7 +216,7 @@ fn test_terse_normal_format() { #[cfg(target_os = "linux")] fn test_format_created_time() { let args = ["-c", "%w", "/boot"]; - let actual = new_ucmd!().args(&args).run().stdout; + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -240,7 +240,7 @@ fn test_format_created_time() { #[cfg(target_os = "linux")] fn test_format_created_seconds() { let args = ["-c", "%W", "/boot"]; - let actual = new_ucmd!().args(&args).run().stdout; + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); println!("actual: {:?}", actual); println!("expect: {:?}", expect); diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 5edff4d55..6e9eb4a17 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -226,8 +226,8 @@ fn test_bytes_big() { .arg(FILE) .arg("-c") .arg(format!("{}", N_ARG)) - .run() - .stdout; + .succeeds() + .stdout_move_str(); let expected = at.read(EXPECTED_FILE); assert_eq!(result.len(), expected.len()); @@ -340,6 +340,6 @@ fn test_negative_indexing() { let negative_bytes_index = new_ucmd!().arg("-c").arg("-20").arg(FOOBAR_TXT).run(); - assert_eq!(positive_lines_index.stdout, negative_lines_index.stdout); - assert_eq!(positive_bytes_index.stdout, negative_bytes_index.stdout); + assert_eq!(positive_lines_index.stdout(), negative_lines_index.stdout()); + assert_eq!(positive_bytes_index.stdout(), negative_bytes_index.stdout()); } From c5d7f63b3ca9214f2aa5dc07f378eab8752d7cbf Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 17 Apr 2021 16:48:23 +0300 Subject: [PATCH 049/399] Make CmdResult::code private --- tests/by-util/test_cp.rs | 8 +++----- tests/by-util/test_install.rs | 18 ++++++++++++------ tests/common/util.rs | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 07880d5c0..f4aabff3e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1029,7 +1029,7 @@ fn test_cp_one_file_system() { at_src.mkdir(TEST_MOUNT_MOUNTPOINT); let mountpoint_path = &at_src.plus_as_string(TEST_MOUNT_MOUNTPOINT); - let _r = scene + scene .cmd("mount") .arg("-t") .arg("tmpfs") @@ -1037,8 +1037,7 @@ fn test_cp_one_file_system() { .arg("size=640k") // ought to be enough .arg("tmpfs") .arg(mountpoint_path) - .run(); - assert!(_r.code == Some(0), "{}", _r.stderr); + .succeeds(); at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE); @@ -1051,8 +1050,7 @@ fn test_cp_one_file_system() { .run(); // Ditch the mount before the asserts - let _r = scene.cmd("umount").arg(mountpoint_path).run(); - assert!(_r.code == Some(0), "{}", _r.stderr); + scene.cmd("umount").arg(mountpoint_path).succeeds(); assert!(result.success); assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE)); diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 32df8d460..dfaaabce6 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -443,9 +443,12 @@ fn test_install_failing_omitting_directory() { at.mkdir(dir2); at.touch(file1); - let r = ucmd.arg(dir1).arg(file1).arg(dir2).run(); - assert!(r.code == Some(1)); - assert!(r.stderr.contains("omitting directory")); + ucmd.arg(dir1) + .arg(file1) + .arg(dir2) + .fails() + .code_is(1) + .stderr_contains("omitting directory"); } #[test] @@ -458,9 +461,12 @@ fn test_install_failing_no_such_file() { at.mkdir(dir1); at.touch(file1); - let r = ucmd.arg(file1).arg(file2).arg(dir1).run(); - assert!(r.code == Some(1)); - assert!(r.stderr.contains("No such file or directory")); + ucmd.arg(file1) + .arg(file2) + .arg(dir1) + .fails() + .code_is(1) + .stderr_contains("No such file or directory"); } #[test] diff --git a/tests/common/util.rs b/tests/common/util.rs index b54c8ee39..55e121737 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -71,7 +71,7 @@ pub struct CmdResult { //tmpd is used for convenience functions for asserts against fixtures tmpd: Option>, /// exit status for command (if there is one) - pub code: Option, + code: Option, /// zero-exit from running the Command? /// see [`success`] pub success: bool, 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 050/399] 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 48121daf94217924942a1f3ec07f6413e2dce144 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 17 Apr 2021 22:29:07 +0200 Subject: [PATCH 051/399] whoami/id: refactor tests for #1982 --- tests/by-util/test_id.rs | 130 +++++++++++++++++++++-------------- tests/by-util/test_whoami.rs | 67 ++++++++++-------- 2 files changed, 117 insertions(+), 80 deletions(-) diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 719cfd876..534736a32 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -1,11 +1,32 @@ use crate::common::util::*; +// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. +// If we are running inside the CI and "needle" is in "stderr" skipping this test is +// considered okay. If we are not inside the CI this calls assert!(result.success). +// +// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" +// stderr: "whoami: cannot find name for user ID 1001" +// Maybe: "adduser --uid 1001 username" can put things right? +// stderr = id: error: Could not find uid 1001: No such id: 1001 +fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { + if !result.succeeded() { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains(needle) { + println!("test skipped:"); + return true; + } else { + result.success(); + } + } + false +} + fn return_whoami_username() -> String { let scene = TestScenario::new("whoami"); let result = scene.cmd("whoami").run(); - if is_ci() && result.stderr.contains("cannot find name for user ID") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { + println!("test skipped:"); return String::from(""); } @@ -14,40 +35,41 @@ fn return_whoami_username() -> String { #[test] fn test_id() { - let result = new_ucmd!().arg("-u").run(); - if result.stderr.contains("cannot find name for user ID") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + let scene = TestScenario::new(util_name!()); + + let result = scene.ucmd().arg("-u").succeeds(); + let uid = result.stdout_str().trim(); + + let result = scene.ucmd().run(); + if skipping_test_is_okay(&result, "Could not find uid") { return; } - let uid = result.success().stdout_str().trim(); - let result = new_ucmd!().run(); - if is_ci() && result.stderr.contains("cannot find name for user ID") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it - return; - } - - if !result.stderr_str().contains("Could not find uid") { - // Verify that the id found by --user/-u exists in the list - result.success().stdout_contains(&uid); - } + // Verify that the id found by --user/-u exists in the list + result.stdout_contains(uid); } #[test] fn test_id_from_name() { let username = return_whoami_username(); - if username == "" { - // Sometimes, the CI is failing here + if username.is_empty() { + return; + } + + let scene = TestScenario::new(util_name!()); + let result = scene.ucmd().arg(&username).run(); + if skipping_test_is_okay(&result, "Could not find uid") { return; } - let result = new_ucmd!().arg(&username).succeeds(); let uid = result.stdout_str().trim(); - new_ucmd!() - .succeeds() + let result = scene.ucmd().run(); + if skipping_test_is_okay(&result, "Could not find uid") { + return; + } + + result // Verify that the id found by --user/-u exists in the list .stdout_contains(uid) // Verify that the username found by whoami exists in the list @@ -56,48 +78,42 @@ fn test_id_from_name() { #[test] fn test_id_name_from_id() { - let result = new_ucmd!().arg("-u").succeeds(); - let uid = result.stdout_str().trim(); + let result = new_ucmd!().arg("-nu").run(); - let result = new_ucmd!().arg("-nu").arg(uid).run(); - if is_ci() && result.stderr.contains("No such user/group") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + let username_id = result.stdout_str().trim(); + + let username_whoami = return_whoami_username(); + if username_whoami.is_empty() { return; } - let username_id = result.success().stdout_str().trim(); - - let scene = TestScenario::new("whoami"); - let result = scene.cmd("whoami").succeeds(); - - let username_whoami = result.stdout_str().trim(); - assert_eq!(username_id, username_whoami); } #[test] fn test_id_group() { - let mut result = new_ucmd!().arg("-g").succeeds(); + let scene = TestScenario::new(util_name!()); + + let mut result = scene.ucmd().arg("-g").succeeds(); let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); - result = new_ucmd!().arg("--group").succeeds(); + result = scene.ucmd().arg("--group").succeeds(); let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); } #[test] fn test_id_groups() { - let result = new_ucmd!().arg("-G").succeeds(); - assert!(result.success); + let scene = TestScenario::new(util_name!()); + + let result = scene.ucmd().arg("-G").succeeds(); let groups = result.stdout_str().trim().split_whitespace(); for s in groups { assert!(s.parse::().is_ok()); } - let result = new_ucmd!().arg("--groups").succeeds(); - assert!(result.success); + let result = scene.ucmd().arg("--groups").succeeds(); let groups = result.stdout_str().trim().split_whitespace(); for s in groups { assert!(s.parse::().is_ok()); @@ -106,11 +122,13 @@ fn test_id_groups() { #[test] fn test_id_user() { - let mut result = new_ucmd!().arg("-u").succeeds(); + let scene = TestScenario::new(util_name!()); + + let result = scene.ucmd().arg("-u").succeeds(); let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); - result = new_ucmd!().arg("--user").succeeds(); + let result = scene.ucmd().arg("--user").succeeds(); let s1 = result.stdout_str().trim(); assert!(s1.parse::().is_ok()); } @@ -118,28 +136,34 @@ fn test_id_user() { #[test] fn test_id_pretty_print() { let username = return_whoami_username(); - if username == "" { - // Sometimes, the CI is failing here + if username.is_empty() { return; } - let result = new_ucmd!().arg("-p").run(); - if result.stdout_str().trim() == "" { - // Sometimes, the CI is failing here with - // old rust versions on Linux + let scene = TestScenario::new(util_name!()); + let result = scene.ucmd().arg("-p").run(); + if result.stdout_str().trim().is_empty() { + // this fails only on: "MinRustV (ubuntu-latest, feat_os_unix)" + // `rustc 1.40.0 (73528e339 2019-12-16)` + // run: /home/runner/work/coreutils/coreutils/target/debug/coreutils id -p + // thread 'test_id::test_id_pretty_print' panicked at 'Command was expected to succeed. + // stdout = + // stderr = ', tests/common/util.rs:157:13 + println!("test skipped:"); return; } + result.success().stdout_contains(username); } #[test] fn test_id_password_style() { let username = return_whoami_username(); - if username == "" { - // Sometimes, the CI is failing here + if username.is_empty() { return; } let result = new_ucmd!().arg("-P").succeeds(); + assert!(result.stdout_str().starts_with(&username)); } diff --git a/tests/by-util/test_whoami.rs b/tests/by-util/test_whoami.rs index c6ec6990f..dc6a1ceed 100644 --- a/tests/by-util/test_whoami.rs +++ b/tests/by-util/test_whoami.rs @@ -1,50 +1,63 @@ use crate::common::util::*; -use std::env; + +// Apparently some CI environments have configuration issues, e.g. with 'whoami' and 'id'. +// If we are running inside the CI and "needle" is in "stderr" skipping this test is +// considered okay. If we are not inside the CI this calls assert!(result.success). +// +// From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" +// stderr: "whoami: error: failed to get username" +// Maybe: "adduser --uid 1001 username" can put things right? +fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { + if !result.succeeded() { + println!("result.stdout = {}", result.stdout_str()); + println!("result.stderr = {}", result.stderr_str()); + if is_ci() && result.stderr_str().contains(needle) { + println!("test skipped:"); + return true; + } else { + result.success(); + } + } + false +} #[test] fn test_normal() { let (_, mut ucmd) = at_and_ucmd!(); let result = ucmd.run(); - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - println!("env::var(CI).is_ok() = {}", env::var("CI").is_ok()); - for (key, value) in env::vars() { - println!("{}: {}", key, value); - } - if is_ci() && result.stderr.contains("failed to get username") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + // use std::env; + // println!("env::var(CI).is_ok() = {}", env::var("CI").is_ok()); + // for (key, value) in env::vars() { + // println!("{}: {}", key, value); + // } + + if skipping_test_is_okay(&result, "failed to get username") { return; } - assert!(result.success); - assert!(!result.stdout.trim().is_empty()); + result.no_stderr(); + assert!(!result.stdout_str().trim().is_empty()); } #[test] #[cfg(not(windows))] fn test_normal_compare_id() { - let (_, mut ucmd) = at_and_ucmd!(); + let scene = TestScenario::new(util_name!()); - let result = ucmd.run(); - - println!("result.stdout = {}", result.stdout); - println!("result.stderr = {}", result.stderr); - if is_ci() && result.stderr.contains("failed to get username") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + let result_ucmd = scene.ucmd().run(); + if skipping_test_is_okay(&result_ucmd, "failed to get username") { return; } - assert!(result.success); - let ts = TestScenario::new("id"); - let id = ts.cmd("id").arg("-un").run(); - if is_ci() && id.stderr.contains("cannot find name for user ID") { - // In the CI, some server are failing to return whoami. - // As seems to be a configuration issue, ignoring it + let result_cmd = scene.cmd("id").arg("-un").run(); + if skipping_test_is_okay(&result_cmd, "cannot find name for user ID") { return; } - assert_eq!(result.stdout.trim(), id.stdout.trim()); + + assert_eq!( + result_ucmd.stdout_str().trim(), + result_cmd.stdout_str().trim() + ); } From 519b9d34a68ccd282c6f47235e060d5783783173 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 17 Apr 2021 22:40:13 +0200 Subject: [PATCH 052/399] sort: use unstable sort when possible (#2076) * sort: use unstable sort when possible This results in a very minor performance (speed) improvement. It does however result in a memory usage reduction, because unstable sort does not allocate auxiliary memory. There's also an improvement in overall CPU usage. * add benchmarking instructions * add user time * fix typo --- src/uu/sort/BENCHMARKING.md | 41 +++++++++++++++++++++++++++++++++++++ src/uu/sort/src/sort.rs | 6 +++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 78c2e2b2d..1caea0326 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -90,3 +90,44 @@ duplicate the string you passed to hyperfine but remove the `target/release/core Example: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"` becomes `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt" "sort shuffled_numbers_si.txt -h -o output.txt"` (This assumes GNU sort is installed as `sort`) + +## Memory and CPU usage + +The above benchmarks use hyperfine to measure the speed of sorting. There are however other useful metrics to determine overall +resource usage. One way to measure them is the `time` command. This is not to be confused with the `time` that is built in to the bash shell. +You may have to install `time` first, then you have to run it with `/bin/time -v` to give it precedence over the built in `time`. + +
+ Example output + + Command being timed: "target/release/coreutils sort shuffled_numbers.txt" + User time (seconds): 0.10 + System time (seconds): 0.00 + Percent of CPU this job got: 365% + Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.02 + Average shared text size (kbytes): 0 + Average unshared data size (kbytes): 0 + Average stack size (kbytes): 0 + Average total size (kbytes): 0 + Maximum resident set size (kbytes): 25360 + Average resident set size (kbytes): 0 + Major (requiring I/O) page faults: 0 + Minor (reclaiming a frame) page faults: 5802 + Voluntary context switches: 462 + Involuntary context switches: 73 + Swaps: 0 + File system inputs: 1184 + File system outputs: 0 + Socket messages sent: 0 + Socket messages received: 0 + Signals delivered: 0 + Page size (bytes): 4096 + Exit status: 0 + +
+ +Useful metrics to look at could be: + +- User time +- Percent of CPU this job got +- Maximum resident set size diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c097861fc..07b852921 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -985,7 +985,11 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { - lines.par_sort_by(|a, b| compare_by(a, b, &settings)) + if settings.stable || settings.unique { + lines.par_sort_by(|a, b| compare_by(a, b, &settings)) + } else { + lines.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) + } } fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { From b91fadd8f4f18ef861da155434f5a9f707c73234 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sun, 18 Apr 2021 02:28:06 +0300 Subject: [PATCH 053/399] Refactored tests for more utilities --- tests/by-util/test_hostid.rs | 6 +----- tests/by-util/test_hostname.rs | 4 +--- tests/by-util/test_nproc.rs | 34 ++++++++++++----------------- tests/by-util/test_pinky.rs | 2 +- tests/by-util/test_printenv.rs | 16 +++++++------- tests/by-util/test_readlink.rs | 15 ++++++++----- tests/by-util/test_sort.rs | 14 ++++++------ tests/by-util/test_sync.rs | 19 ++++++++++------- tests/by-util/test_tsort.rs | 4 ++-- tests/by-util/test_uname.rs | 39 +++++++++------------------------- tests/by-util/test_uptime.rs | 26 +++++++---------------- tests/by-util/test_users.rs | 13 +++++------- 12 files changed, 77 insertions(+), 115 deletions(-) diff --git a/tests/by-util/test_hostid.rs b/tests/by-util/test_hostid.rs index b5b668901..3ea818480 100644 --- a/tests/by-util/test_hostid.rs +++ b/tests/by-util/test_hostid.rs @@ -4,10 +4,6 @@ use self::regex::Regex; #[test] fn test_normal() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - - assert!(result.success); let re = Regex::new(r"^[0-9a-f]{8}").unwrap(); - assert!(re.is_match(&result.stdout_str())); + new_ucmd!().succeeds().stdout_matches(&re); } diff --git a/tests/by-util/test_hostname.rs b/tests/by-util/test_hostname.rs index c9dc99040..3fcb1ae8b 100644 --- a/tests/by-util/test_hostname.rs +++ b/tests/by-util/test_hostname.rs @@ -14,9 +14,7 @@ fn test_hostname() { #[cfg(not(target_vendor = "apple"))] #[test] fn test_hostname_ip() { - let result = new_ucmd!().arg("-i").run(); - println!("{:#?}", result); - assert!(result.success); + let result = new_ucmd!().arg("-i").succeeds(); assert!(!result.stdout_str().trim().is_empty()); } diff --git a/tests/by-util/test_nproc.rs b/tests/by-util/test_nproc.rs index 055b4890d..abf758829 100644 --- a/tests/by-util/test_nproc.rs +++ b/tests/by-util/test_nproc.rs @@ -2,54 +2,46 @@ use crate::common::util::*; #[test] fn test_nproc() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - assert!(result.success); - let nproc: u8 = result.stdout.trim().parse().unwrap(); + let nproc: u8 = new_ucmd!().succeeds().stdout_str().trim().parse().unwrap(); assert!(nproc > 0); } #[test] fn test_nproc_all_omp() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("--all").run(); - assert!(result.success); - let nproc: u8 = result.stdout.trim().parse().unwrap(); + let result = new_ucmd!().arg("--all").succeeds(); + + let nproc: u8 = result.stdout_str().trim().parse().unwrap(); assert!(nproc > 0); let result = TestScenario::new(util_name!()) .ucmd_keepenv() .env("OMP_NUM_THREADS", "1") - .run(); - assert!(result.success); - let nproc_omp: u8 = result.stdout.trim().parse().unwrap(); + .succeeds(); + + let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap(); assert!(nproc - 1 == nproc_omp); let result = TestScenario::new(util_name!()) .ucmd_keepenv() .env("OMP_NUM_THREADS", "1") // Has no effect .arg("--all") - .run(); - assert!(result.success); - let nproc_omp: u8 = result.stdout.trim().parse().unwrap(); + .succeeds(); + let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap(); assert!(nproc == nproc_omp); } #[test] fn test_nproc_ignore() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - assert!(result.success); - let nproc: u8 = result.stdout.trim().parse().unwrap(); + let result = new_ucmd!().succeeds(); + let nproc: u8 = result.stdout_str().trim().parse().unwrap(); if nproc > 1 { // Ignore all CPU but one let result = TestScenario::new(util_name!()) .ucmd_keepenv() .arg("--ignore") .arg((nproc - 1).to_string()) - .run(); - assert!(result.success); - let nproc: u8 = result.stdout.trim().parse().unwrap(); + .succeeds(); + let nproc: u8 = result.stdout_str().trim().parse().unwrap(); assert!(nproc == 1); } } diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 161054b2c..7a4a3f3df 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -75,5 +75,5 @@ fn expected_result(args: &[&str]) -> String { .env("LANGUAGE", "C") .args(args) .run() - .stdout + .stdout_move_str() } diff --git a/tests/by-util/test_printenv.rs b/tests/by-util/test_printenv.rs index 9d90051ea..bc0bc0f3c 100644 --- a/tests/by-util/test_printenv.rs +++ b/tests/by-util/test_printenv.rs @@ -7,10 +7,11 @@ fn test_get_all() { env::set_var(key, "VALUE"); assert_eq!(env::var(key), Ok("VALUE".to_string())); - let result = TestScenario::new(util_name!()).ucmd_keepenv().run(); - assert!(result.success); - assert!(result.stdout.contains("HOME=")); - assert!(result.stdout.contains("KEY=VALUE")); + TestScenario::new(util_name!()) + .ucmd_keepenv() + .succeeds() + .stdout_contains("HOME=") + .stdout_contains("KEY=VALUE"); } #[test] @@ -22,9 +23,8 @@ fn test_get_var() { let result = TestScenario::new(util_name!()) .ucmd_keepenv() .arg("KEY") - .run(); + .succeeds(); - assert!(result.success); - assert!(!result.stdout.is_empty()); - assert!(result.stdout.trim() == "VALUE"); + assert!(!result.stdout_str().is_empty()); + assert!(result.stdout_str().trim() == "VALUE"); } diff --git a/tests/by-util/test_readlink.rs b/tests/by-util/test_readlink.rs index 84747b24c..cae5eafee 100644 --- a/tests/by-util/test_readlink.rs +++ b/tests/by-util/test_readlink.rs @@ -5,7 +5,7 @@ static GIBBERISH: &'static str = "supercalifragilisticexpialidocious"; #[test] fn test_canonicalize() { let (at, mut ucmd) = at_and_ucmd!(); - let actual = ucmd.arg("-f").arg(".").run().stdout; + let actual = ucmd.arg("-f").arg(".").run().stdout_move_str(); let expect = at.root_dir_resolved() + "\n"; println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -15,7 +15,7 @@ fn test_canonicalize() { #[test] fn test_canonicalize_existing() { let (at, mut ucmd) = at_and_ucmd!(); - let actual = ucmd.arg("-e").arg(".").run().stdout; + let actual = ucmd.arg("-e").arg(".").run().stdout_move_str(); let expect = at.root_dir_resolved() + "\n"; println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -25,7 +25,7 @@ fn test_canonicalize_existing() { #[test] fn test_canonicalize_missing() { let (at, mut ucmd) = at_and_ucmd!(); - let actual = ucmd.arg("-m").arg(GIBBERISH).run().stdout; + let actual = ucmd.arg("-m").arg(GIBBERISH).run().stdout_move_str(); let expect = path_concat!(at.root_dir_resolved(), GIBBERISH) + "\n"; println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -37,7 +37,7 @@ fn test_long_redirection_to_current_dir() { let (at, mut ucmd) = at_and_ucmd!(); // Create a 256-character path to current directory let dir = path_concat!(".", ..128); - let actual = ucmd.arg("-n").arg("-m").arg(dir).run().stdout; + let actual = ucmd.arg("-n").arg("-m").arg(dir).run().stdout_move_str(); let expect = at.root_dir_resolved(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -48,7 +48,12 @@ fn test_long_redirection_to_current_dir() { fn test_long_redirection_to_root() { // Create a 255-character path to root let dir = path_concat!("..", ..85); - let actual = new_ucmd!().arg("-n").arg("-m").arg(dir).run().stdout; + let actual = new_ucmd!() + .arg("-n") + .arg("-m") + .arg(dir) + .run() + .stdout_move_str(); let expect = get_root_path(); println!("actual: {:?}", actual); println!("expect: {:?}", expect); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index aacc34eb0..a4a9a383c 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -65,7 +65,7 @@ fn test_random_shuffle_len() { // check whether output is the same length as the input const FILE: &'static str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); - let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); assert_ne!(result, expected); @@ -77,9 +77,9 @@ fn test_random_shuffle_contains_all_lines() { // check whether lines of input are all in output const FILE: &'static str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); - let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); - let result_sorted = new_ucmd!().pipe_in(result.clone()).run().stdout; + let result_sorted = new_ucmd!().pipe_in(result.clone()).run().stdout_move_str(); assert_ne!(result, expected); assert_eq!(result_sorted, expected); @@ -92,9 +92,9 @@ fn test_random_shuffle_two_runs_not_the_same() { // as the starting order, or if both random sorts end up having the same order. const FILE: &'static str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); - let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); - let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); assert_ne!(result, expected); assert_ne!(result, unexpected); @@ -107,9 +107,9 @@ fn test_random_shuffle_contains_two_runs_not_the_same() { // as the starting order, or if both random sorts end up having the same order. const FILE: &'static str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); - let result = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); - let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout; + let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); assert_ne!(result, expected); assert_ne!(result, unexpected); diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index ddd6969a3..436bfdef3 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -5,8 +5,7 @@ use tempfile::tempdir; #[test] fn test_sync_default() { - let result = new_ucmd!().run(); - assert!(result.success); + new_ucmd!().succeeds(); } #[test] @@ -18,8 +17,10 @@ fn test_sync_incorrect_arg() { fn test_sync_fs() { let temporary_directory = tempdir().unwrap(); let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap(); - let result = new_ucmd!().arg("--file-system").arg(&temporary_path).run(); - assert!(result.success); + new_ucmd!() + .arg("--file-system") + .arg(&temporary_path) + .succeeds(); } #[test] @@ -27,12 +28,14 @@ fn test_sync_data() { // Todo add a second arg let temporary_directory = tempdir().unwrap(); let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap(); - let result = new_ucmd!().arg("--data").arg(&temporary_path).run(); - assert!(result.success); + new_ucmd!().arg("--data").arg(&temporary_path).succeeds(); } #[test] fn test_sync_no_existing_files() { - let result = new_ucmd!().arg("--data").arg("do-no-exist").fails(); - assert!(result.stderr.contains("error: cannot stat")); + new_ucmd!() + .arg("--data") + .arg("do-no-exist") + .fails() + .stderr_contains("error: cannot stat"); } diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 159b80025..0ea6de281 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -28,7 +28,7 @@ fn test_version_flag() { let version_short = new_ucmd!().arg("-V").run(); let version_long = new_ucmd!().arg("--version").run(); - assert_eq!(version_short.stdout, version_long.stdout); + assert_eq!(version_short.stdout(), version_long.stdout()); } #[test] @@ -36,7 +36,7 @@ fn test_help_flag() { let help_short = new_ucmd!().arg("-h").run(); let help_long = new_ucmd!().arg("--help").run(); - assert_eq!(help_short.stdout, help_long.stdout); + assert_eq!(help_short.stdout(), help_long.stdout()); } #[test] diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index f0e32b430..6b8d2d59d 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -2,60 +2,41 @@ use crate::common::util::*; #[test] fn test_uname_compatible() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("-a").run(); - assert!(result.success); + new_ucmd!().arg("-a").succeeds(); } #[test] fn test_uname_name() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("-n").run(); - assert!(result.success); + new_ucmd!().arg("-n").succeeds(); } #[test] fn test_uname_processor() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("-p").run(); - assert!(result.success); - assert_eq!(result.stdout.trim_end(), "unknown"); + let result = new_ucmd!().arg("-p").succeeds(); + assert_eq!(result.stdout_str().trim_end(), "unknown"); } #[test] fn test_uname_hwplatform() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("-i").run(); - assert!(result.success); - assert_eq!(result.stdout.trim_end(), "unknown"); + let result = new_ucmd!().arg("-i").succeeds(); + assert_eq!(result.stdout_str().trim_end(), "unknown"); } #[test] fn test_uname_machine() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("-m").run(); - assert!(result.success); + new_ucmd!().arg("-m").succeeds(); } #[test] fn test_uname_kernel_version() { - let (_, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("-v").run(); - assert!(result.success); + new_ucmd!().arg("-v").succeeds(); } #[test] fn test_uname_kernel() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-o").run(); - assert!(result.success); + let result = ucmd.arg("-o").succeeds(); #[cfg(target_os = "linux")] - assert!(result.stdout.to_lowercase().contains("linux")); + assert!(result.stdout_str().to_lowercase().contains("linux")); } diff --git a/tests/by-util/test_uptime.rs b/tests/by-util/test_uptime.rs index c8f6a11d3..d20ad90c9 100644 --- a/tests/by-util/test_uptime.rs +++ b/tests/by-util/test_uptime.rs @@ -4,33 +4,23 @@ use crate::common::util::*; #[test] fn test_uptime() { - let result = TestScenario::new(util_name!()).ucmd_keepenv().run(); + TestScenario::new(util_name!()) + .ucmd_keepenv() + .succeeds() + .stdout_contains("load average:") + .stdout_contains(" up "); - println!("stdout = {}", result.stdout); - println!("stderr = {}", result.stderr); - - assert!(result.success); - assert!(result.stdout.contains("load average:")); - assert!(result.stdout.contains(" up ")); // Don't check for users as it doesn't show in some CI } #[test] fn test_uptime_since() { - let scene = TestScenario::new(util_name!()); - - let result = scene.ucmd().arg("--since").succeeds(); - - println!("stdout = {}", result.stdout); - println!("stderr = {}", result.stderr); - - assert!(result.success); let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}").unwrap(); - assert!(re.is_match(&result.stdout.trim())); + + new_ucmd!().arg("--since").succeeds().stdout_matches(&re); } #[test] fn test_failed() { - let (_at, mut ucmd) = at_and_ucmd!(); - ucmd.arg("willfail").fails(); + new_ucmd!().arg("willfail").fails(); } diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index cb444b8be..3c5789820 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -3,14 +3,11 @@ use std::env; #[test] fn test_users_noarg() { - let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.run(); - assert!(result.success); + new_ucmd!().succeeds(); } #[test] fn test_users_check_name() { - let result = TestScenario::new(util_name!()).ucmd_keepenv().run(); - assert!(result.success); + let result = TestScenario::new(util_name!()).ucmd_keepenv().succeeds(); // Expectation: USER is often set let key = "USER"; @@ -21,9 +18,9 @@ fn test_users_check_name() { // Check if "users" contains the name of the user { println!("username found {}", &username); - println!("result.stdout {}", &result.stdout); - if !&result.stdout.is_empty() { - assert!(result.stdout.contains(&username)) + // println!("result.stdout {}", &result.stdout); + if !result.stdout_str().is_empty() { + result.stdout_contains(&username); } } } From 93b03bf9a608ef0602ab29b6fa6796171a8e46c1 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sun, 18 Apr 2021 02:33:52 +0300 Subject: [PATCH 054/399] Ran cargo fmt --- tests/by-util/test_cksum.rs | 8 +++++--- tests/by-util/test_mktemp.rs | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index c8e60f8a9..592e45c58 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -62,14 +62,16 @@ fn test_invalid_file() { let folder_name = "asdf"; // First check when file doesn't exist - ts.ucmd().arg(folder_name) + ts.ucmd() + .arg(folder_name) .fails() .no_stdout() .stderr_contains("cksum: error: 'asdf' No such file or directory"); - + // Then check when the file is of an invalid type at.mkdir(folder_name); - ts.ucmd().arg(folder_name) + ts.ucmd() + .arg(folder_name) .fails() .no_stdout() .stderr_contains("cksum: error: 'asdf' Is a directory"); diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index aa3ff5f1f..c273c407c 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -388,8 +388,7 @@ fn test_mktemp_tmpdir_one_arg() { .arg("--tmpdir") .arg("apt-key-gpghome.XXXXXXXXXX") .succeeds(); - result.no_stderr() - .stdout_contains("apt-key-gpghome."); + result.no_stderr().stdout_contains("apt-key-gpghome."); assert!(PathBuf::from(result.stdout_str().trim()).is_file()); } From df2dcc5b998e321881ca4c83c0fb67053cf1afe8 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 17 Apr 2021 23:20:19 +0200 Subject: [PATCH 055/399] chown: fix parse_spec() for colon (#2060) --- src/uu/chown/src/chown.rs | 14 +++++----- tests/by-util/test_chown.rs | 54 ++++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 0e3273b3b..23fb030ba 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -272,16 +272,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } fn parse_spec(spec: &str) -> Result<(Option, Option), String> { - let args = spec.split(':').collect::>(); - let usr_only = args.len() == 1; - let grp_only = args.len() == 2 && args[0].is_empty() && !args[1].is_empty(); + let args = spec.split_terminator(':').collect::>(); + let usr_only = args.len() == 1 && !args[0].is_empty(); + let grp_only = args.len() == 2 && args[0].is_empty(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); if usr_only { Ok(( Some(match Passwd::locate(args[0]) { Ok(v) => v.uid(), - _ => return Err(format!("invalid user: '{}'", spec)), + _ => return Err(format!("invalid user: ‘{}’", spec)), }), None, )) @@ -290,18 +290,18 @@ fn parse_spec(spec: &str) -> Result<(Option, Option), String> { None, Some(match Group::locate(args[1]) { Ok(v) => v.gid(), - _ => return Err(format!("invalid group: '{}'", spec)), + _ => return Err(format!("invalid group: ‘{}’", spec)), }), )) } else if usr_grp { Ok(( Some(match Passwd::locate(args[0]) { Ok(v) => v.uid(), - _ => return Err(format!("invalid user: '{}'", spec)), + _ => return Err(format!("invalid user: ‘{}’", spec)), }), Some(match Group::locate(args[1]) { Ok(v) => v.gid(), - _ => return Err(format!("invalid group: '{}'", spec)), + _ => return Err(format!("invalid group: ‘{}’", spec)), }), )) } else { diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 4ec9d60f8..3d94632a6 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -9,9 +9,15 @@ extern crate chown; // considered okay. If we are not inside the CI this calls assert!(result.success). // // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" +// // stderr: "whoami: cannot find name for user ID 1001" -// Maybe: "adduser --uid 1001 username" can put things right? +// TODO: Maybe `adduser --uid 1001 username` can put things right? +// // stderr: "id: cannot find name for group ID 116" +// stderr: "thread 'main' panicked at 'called `Result::unwrap()` on an `Err` +// value: Custom { kind: NotFound, error: "No such id: 1001" }', +// /project/src/uucore/src/lib/features/perms.rs:176:44" +// fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { if !result.succeeded() { println!("result.stdout = {}", result.stdout_str()); @@ -128,26 +134,50 @@ fn test_chown_only_owner_colon() { .arg(format!("{}:", user_name)) .arg("--verbose") .arg(file1) - .run(); + .succeeds() + .stderr_contains(&"retained as"); - // scene // TODO: uncomment once #2060 is fixed - // .ucmd() - // .arg("root:") - // .arg("--verbose") - // .arg(file1) - // .fails() - // .stderr_contains(&"failed to change"); + scene + .ucmd() + .arg("root:") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"failed to change"); } #[test] fn test_chown_only_colon() { // test chown : file.txt - // TODO: implement once #2060 is fixed + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file1 = "test_chown_file1"; + at.touch(file1); + // expected: // $ chown -v : file.txt 2>out_err ; echo $? ; cat out_err // ownership of 'file.txt' retained // 0 + let result = scene.ucmd().arg(":").arg("--verbose").arg(file1).run(); + if skipping_test_is_okay(&result, "No such id") { + return; + } + result.stderr_contains(&"retained as"); // TODO: verbose is not printed to stderr in GNU chown + + // test chown : file.txt + // expected: + // $ chown -v :: file.txt 2>out_err ; echo $? ; cat out_err + // 1 + // chown: invalid group: ‘::’ + scene + .ucmd() + .arg("::") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"invalid group: ‘::’"); } #[test] @@ -479,8 +509,8 @@ fn test_big_p() { .arg("bin") .arg("/proc/self/cwd") .fails() - .stderr_is( - "chown: changing ownership of '/proc/self/cwd': Operation not permitted (os error 1)\n", + .stderr_contains( + "chown: changing ownership of '/proc/self/cwd': Operation not permitted (os error 1)", ); } } From 826b6004708f3fcfcb07cfb977c5e9933fb986cc Mon Sep 17 00:00:00 2001 From: Denis Isidoro Date: Sat, 17 Apr 2021 21:14:05 -0300 Subject: [PATCH 056/399] Fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76ea92ab5..12dfb5609 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ $ cargo build --features "base32 cat echo rm" --no-default-features If you don't want to build the multicall binary and would prefer to build the utilities as individual binaries, that is also possible. Each utility -is contained in it's own package within the main repository, named +is contained in its own package within the main repository, named "uu_UTILNAME". To build individual utilities, use cargo to build just the specific packages (using the `--package` [aka `-p`] option). For example: From 65e9c7b1b54cbeeed39c508c47992786d37b077d Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 17 Apr 2021 21:30:03 -0500 Subject: [PATCH 057/399] Sorta working ExtSort - concat struct elements --- src/uu/sort/src/numeric_str_cmp.rs | 7 ++++--- src/uu/sort/src/sort.rs | 27 +++++++++++++++++---------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index a50734ebd..ac615d1f7 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -15,19 +15,20 @@ //! From that follows the constraints of this algorithm: It is able to compare numbers in ±(1*10^[i64::MIN]..10*10^[i64::MAX]). use std::{cmp::Ordering, ops::Range}; +use serde::{Serialize, Deserialize}; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] enum Sign { Negative, Positive, } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct NumInfo { exponent: i64, sign: Sign, } - +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct NumInfoParseSettings { pub accept_si_units: bool, pub thousands_separator: Option, diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index b355c1e68..1e533cf65 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -176,6 +176,7 @@ impl From<&GlobalSettings> for KeySettings { } } +#[derive(Debug, Serialize, Deserialize, Clone)] /// Represents the string selected by a FieldSelector. enum SelectionRange { /// If we had to transform this selection, we have to store a new string. @@ -206,7 +207,7 @@ impl SelectionRange { } } } - +#[derive(Debug, Serialize, Deserialize, Clone)] enum NumCache { AsF64(f64), WithInfo(NumInfo), @@ -227,7 +228,7 @@ impl NumCache { } } } - +#[derive(Debug, Serialize, Deserialize, Clone)] struct Selection { range: SelectionRange, num_cache: NumCache, @@ -241,7 +242,7 @@ impl Selection { } type Field = Range; - +#[derive(Debug, Serialize, Deserialize, Clone)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. @@ -251,7 +252,7 @@ struct Line { impl Sortable for Line { fn encode(&self, write: &mut W) { - let line = Line {line: self.line.clone(), selections: self.selections.clone()}; + let line = Line {line: self.line.to_owned(), selections: self.selections.to_owned() }; let serialized = serde_json::ser::to_string(&line).unwrap(); write.write_all(format!("{}{}", serialized, "\n").as_bytes()).unwrap(); } @@ -259,11 +260,17 @@ impl Sortable for Line { fn decode(read: &mut R) -> Option { let buf_reader = BufReader::new(read); - let mut result: Option = None; - for line in buf_reader.lines() { - let line_as_str: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); - result = Some( Line {line: line_as_str.line, selections: line_as_str.selections} ); - } + let result = { + let mut line_joined= String::new(); + let mut selections_joined= SmallVec::new(); + for line in buf_reader.lines() { + let mut deserialized_line: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); + line_joined = format!("{}\n{}", line_joined, deserialized_line.line); + selections_joined.append(&mut deserialized_line.selections); + selections_joined.dedup(); + } + Some( Line {line: line_joined, selections: selections_joined} ) + }; result } } @@ -286,7 +293,7 @@ impl Line { .iter() .map(|selector| { let mut range = - if let Some(range) = selector.get_selection(&line, fields.as_deref()) { + if let Some(range) = selector.get_field_selection(&line, fields.as_deref()) { if let Some(transformed) = transform(&line[range.to_owned()], &selector.settings) { From 7a8767e359433044ae1e9428b85fa05e93a78caa Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 17 Apr 2021 22:34:03 -0500 Subject: [PATCH 058/399] Cleanup --- src/uu/sort/src/sort.rs | 46 +++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1e533cf65..ce6ba8fa7 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -43,6 +43,7 @@ use std::ffi::OsString; use std::usize; use std::path::PathBuf; use std::string::*; +use rayon::prelude::*; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -92,6 +93,9 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; +static DEFAULT_TMPDIR: &str = r"/tmp"; +static DEFAULT_BUF_SIZE: usize = 10000000usize; + #[derive(Eq, Ord, PartialEq, PartialOrd, Clone)] enum SortMode { Numeric, @@ -146,8 +150,8 @@ impl Default for GlobalSettings { separator: None, threads: String::new(), zero_terminated: false, - buffer_size: 10000000usize, - tmp_dir: PathBuf::from(r"/tmp"), + buffer_size: DEFAULT_BUF_SIZE, + tmp_dir: PathBuf::from(DEFAULT_TMPDIR), } } } @@ -242,7 +246,7 @@ impl Selection { } type Field = Range; -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. @@ -250,16 +254,20 @@ struct Line { } impl Sortable for Line { - fn encode(&self, write: &mut W) { let line = Line {line: self.line.to_owned(), selections: self.selections.to_owned() }; let serialized = serde_json::ser::to_string(&line).unwrap(); + // Valid JSON needs to be seperated by something, so here we use a newline write.write_all(format!("{}{}", serialized, "\n").as_bytes()).unwrap(); } + // This crate asks us to write one line at a time, but returns multiple lines(?). + // However, this crate also expects us to return a result of Option, + // so we concat the these lines into a single Option. So, this may be broken, + // and needs to be tested more thoroughly. Perhaps we need to rethink our struct or rewrite a + // ext sorter ourselves. fn decode(read: &mut R) -> Option { let buf_reader = BufReader::new(read); - let result = { let mut line_joined= String::new(); let mut selections_joined= SmallVec::new(); @@ -267,7 +275,6 @@ impl Sortable for Line { let mut deserialized_line: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); line_joined = format!("{}\n{}", line_joined, deserialized_line.line); selections_joined.append(&mut deserialized_line.selections); - selections_joined.dedup(); } Some( Line {line: line_joined, selections: selections_joined} ) }; @@ -869,16 +876,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.buffer_size = matches .value_of(OPT_BUF_SIZE) .map(String::from) - .unwrap_or( format! ( "{}", 10000000usize ) ) + .unwrap_or( format! ( "{}", DEFAULT_BUF_SIZE ) ) .parse::() - .unwrap_or(10000000usize); + .unwrap_or( DEFAULT_BUF_SIZE ); } if matches.is_present(OPT_TMP_DIR) { let result = matches .value_of(OPT_TMP_DIR) .map(String::from) - .unwrap_or("/tmp".to_owned() ); + .unwrap_or(DEFAULT_TMPDIR.to_owned() ); settings.tmp_dir = PathBuf::from(format!(r"{}", result)); } else { for (key, value) in env::vars_os() { @@ -886,7 +893,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.tmp_dir = PathBuf::from(format!(r"{}", value.into_string().unwrap_or("/tmp".to_owned()))); break } - settings.tmp_dir = PathBuf::from(r"/tmp"); + settings.tmp_dir = PathBuf::from(DEFAULT_TMPDIR); } } @@ -1008,7 +1015,11 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { return exec_check_file(&lines, &settings); } - lines = sort_by(lines, &settings); + if ( settings.buffer_size != DEFAULT_BUF_SIZE ) || ( settings.tmp_dir.as_os_str() != DEFAULT_TMPDIR ) { + lines = ext_sort_by(lines, &settings); + } else { + sort_by(&mut lines, &settings); + } if settings.merge { if settings.unique { @@ -1063,9 +1074,18 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { +fn ext_sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { let sorter = ExternalSorter::new().with_segment_size(settings.buffer_size).with_sort_dir(settings.tmp_dir.clone()).with_parallel_sort(); - sorter.sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)).unwrap().collect() + let result = sorter.sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)).unwrap().collect(); + result +} + +fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { + if settings.stable || settings.unique { + lines.par_sort_by(|a, b| compare_by(a, b, &settings)) + } else { + lines.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) + } } fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { From 3a1e92fdd286f7078957a18b0eda2b4b45c11bd2 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 17 Apr 2021 22:39:05 -0500 Subject: [PATCH 059/399] More cleanup --- src/uu/sort/src/sort.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index ce6ba8fa7..1c72e3427 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -180,7 +180,7 @@ impl From<&GlobalSettings> for KeySettings { } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] /// Represents the string selected by a FieldSelector. enum SelectionRange { /// If we had to transform this selection, we have to store a new string. @@ -211,7 +211,7 @@ impl SelectionRange { } } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] enum NumCache { AsF64(f64), WithInfo(NumInfo), @@ -232,7 +232,7 @@ impl NumCache { } } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] struct Selection { range: SelectionRange, num_cache: NumCache, @@ -275,6 +275,7 @@ impl Sortable for Line { let mut deserialized_line: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); line_joined = format!("{}\n{}", line_joined, deserialized_line.line); selections_joined.append(&mut deserialized_line.selections); + selections_joined.dedup(); } Some( Line {line: line_joined, selections: selections_joined} ) }; From 4c8d62c2be89dba9158106abf47c0b97392a5f5e Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sat, 17 Apr 2021 23:24:32 -0500 Subject: [PATCH 060/399] More cleanup --- src/uu/sort/src/sort.rs | 42 ++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1c72e3427..9052b2a5b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -257,14 +257,14 @@ impl Sortable for Line { fn encode(&self, write: &mut W) { let line = Line {line: self.line.to_owned(), selections: self.selections.to_owned() }; let serialized = serde_json::ser::to_string(&line).unwrap(); - // Valid JSON needs to be seperated by something, so here we use a newline + // Each instance of valid JSON needs to be seperated by something, so here we use a newline write.write_all(format!("{}{}", serialized, "\n").as_bytes()).unwrap(); } - // This crate asks us to write one line at a time, but returns multiple lines(?). + // This crate asks us to write one Line at a time, but returns multiple Lines to us(?). // However, this crate also expects us to return a result of Option, - // so we concat the these lines into a single Option. So, this may be broken, - // and needs to be tested more thoroughly. Perhaps we need to rethink our struct or rewrite a + // so we concat the these lines into a single Option. So, it's possible this is broken, + // and/or needs to be tested more thoroughly. Perhaps we need to rethink our Line struct or rewrite a // ext sorter ourselves. fn decode(read: &mut R) -> Option { let buf_reader = BufReader::new(read); @@ -274,6 +274,7 @@ impl Sortable for Line { for line in buf_reader.lines() { let mut deserialized_line: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); line_joined = format!("{}\n{}", line_joined, deserialized_line.line); + // I think we've done our sorting already and these are irrelevant? @miDeb what's your sense? selections_joined.append(&mut deserialized_line.selections); selections_joined.dedup(); } @@ -873,13 +874,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if matches.is_present(OPT_BUF_SIZE) { - // 10000 is the default extsort buffer, but it's too small - settings.buffer_size = matches + // 10K is the default extsort buffer, but that's too small, so we set at 10M + // Although the "default" is never used unless extsort options are given + settings.buffer_size = { + let input = matches .value_of(OPT_BUF_SIZE) .map(String::from) - .unwrap_or( format! ( "{}", DEFAULT_BUF_SIZE ) ) - .parse::() - .unwrap_or( DEFAULT_BUF_SIZE ); + .unwrap_or( format! ( "{}", DEFAULT_BUF_SIZE ) ); + + human_numeric_convert(&input) + } } if matches.is_present(OPT_TMP_DIR) { @@ -1133,6 +1137,26 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } } +// Brought back! Probably want to do through numstrcmp somehow now +fn human_numeric_convert(a: &str) -> usize { + let num_part = leading_num_common(a); + let (_, s) = a.split_at(num_part.len()); + let num_part = permissive_f64_parse(num_part); + let suffix = match s.parse().unwrap_or('\0') { + // SI Units + 'K' => 1E3, + 'M' => 1E6, + 'G' => 1E9, + 'T' => 1E12, + 'P' => 1E15, + 'E' => 1E18, + 'Z' => 1E21, + 'Y' => 1E24, + _ => 1f64, + }; + num_part as usize * suffix as usize +} + // Test output against BSDs and GNU with their locale // env var set to lc_ctype=utf-8 to enjoy the exact same output. #[inline(always)] From f36832c39264718f3b41d8e80e92e39f1726ae06 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sun, 18 Apr 2021 14:17:55 +0200 Subject: [PATCH 061/399] cp: add support for --reflink=never - Passing `never` to `--reflink` does not raise an error anymore. - Remove `Options::reflink` flag as it was redundant with `reflink_mode`. - Add basic tests for this option. Does not check that a copy-on-write rather than a regular copy was made. --- src/uu/cp/src/cp.rs | 5 ++-- tests/by-util/test_cp.rs | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 4e245b298..da8c9037e 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -210,7 +210,6 @@ pub struct Options { overwrite: OverwriteMode, parents: bool, strip_trailing_slashes: bool, - reflink: bool, reflink_mode: ReflinkMode, preserve_attributes: Vec, recursive: bool, @@ -633,12 +632,12 @@ impl Options { update: matches.is_present(OPT_UPDATE), verbose: matches.is_present(OPT_VERBOSE), strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), - reflink: matches.is_present(OPT_REFLINK), reflink_mode: { if let Some(reflink) = matches.value_of(OPT_REFLINK) { match reflink { "always" => ReflinkMode::Always, "auto" => ReflinkMode::Auto, + "never" => ReflinkMode::Never, value => { return Err(Error::InvalidArgument(format!( "invalid argument '{}' for \'reflink\'", @@ -1196,7 +1195,7 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { ///Copy the file from `source` to `dest` either using the normal `fs::copy` or the ///`FICLONE` ioctl if --reflink is specified and the filesystem supports it. fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { - if options.reflink { + if options.reflink_mode != ReflinkMode::Never { #[cfg(not(target_os = "linux"))] return Err("--reflink is only supported on linux".to_string().into()); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index f4aabff3e..c90dff061 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1072,3 +1072,67 @@ fn test_cp_one_file_system() { } } } + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_always() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--reflink=always") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + if result.success { + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); + } else { + // Older Linux versions do not support cloning. + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_auto() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--reflink=auto") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + assert!(result.success); + + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_never() { + let (at, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--reflink=never") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + assert!(result.success); + + // Check the content of the destination file + assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_cp_reflink_bad() { + let (_, mut ucmd) = at_and_ucmd!(); + let result = ucmd + .arg("--reflink=bad") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_EXISTING_FILE) + .run(); + + assert!(!result.success); + assert!(result.stderr.contains("invalid argument")); +} From d7b7ce52bc28904f8af8ae36a9e43e698cbdd295 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 11:54:18 -0500 Subject: [PATCH 062/399] Vendored ext_sorter, removed unstable, created a byte buffer sized vector instead of a numbered capacity vector --- Cargo.lock | 1 + src/uu/sort/Cargo.toml | 1 + src/uu/sort/src/ext_sorter.rs | 347 +++++++++++++++++++++++++++++ src/uu/sort/src/numeric_str_cmp.rs | 2 +- src/uu/sort/src/sort.rs | 112 +++++----- 5 files changed, 409 insertions(+), 54 deletions(-) create mode 100644 src/uu/sort/src/ext_sorter.rs diff --git a/Cargo.lock b/Cargo.lock index b7328009c..76f43d8b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2424,6 +2424,7 @@ dependencies = [ "serde", "serde_json", "smallvec 1.6.1", + "tempfile", "uucore", "uucore_procs", ] diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 8a3d1ed25..e1e0d1b87 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -28,6 +28,7 @@ semver = "0.9.0" smallvec = { version = "1.6.1", features = ["serde"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +tempfile = "3.1.0" [[bin]] name = "sort" diff --git a/src/uu/sort/src/ext_sorter.rs b/src/uu/sort/src/ext_sorter.rs new file mode 100644 index 000000000..c19f1262b --- /dev/null +++ b/src/uu/sort/src/ext_sorter.rs @@ -0,0 +1,347 @@ +// Copyright 2018 Andre-Philippe Paquet +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use rayon::prelude::*; +use std::{ + cmp::Ordering, + collections::VecDeque, + fs::{File, OpenOptions}, + io::{BufReader, BufWriter, Error, Read, Seek, SeekFrom, Write}, + path::{Path, PathBuf}, +}; + +/// Exposes external sorting (i.e. on disk sorting) capability on arbitrarily +/// sized iterator, even if the generated content of the iterator doesn't fit in +/// memory. +/// +/// It uses an in-memory buffer sorted and flushed to disk in segment files when +/// full. Once sorted, it returns a new sorted iterator with all items. In order +/// to remain efficient for all implementations, the crate doesn't handle +/// serialization, but leaves that to the user. +pub struct ExternalSorter { + segment_size: usize, + sort_dir: Option, + parallel: bool, +} + +impl ExternalSorter { + pub fn new() -> ExternalSorter { + ExternalSorter { + segment_size: 10000000, + sort_dir: None, + parallel: false, + } + } + + /// Sets the maximum size of each segment in number of sorted items. + /// + /// This number of items needs to fit in memory. While sorting, a + /// in-memory buffer is used to collect the items to be sorted. Once + /// it reaches the maximum size, it is sorted and then written to disk. + /// + /// Using a higher segment size makes sorting faster by leveraging + /// faster in-memory operations. + pub fn with_segment_size(mut self, size: usize) -> Self { + self.segment_size = size; + self + } + + /// Sets directory in which sorted segments will be written (if it doesn't + /// fit in memory). + pub fn with_sort_dir(mut self, path: PathBuf) -> Self { + self.sort_dir = Some(path); + self + } + + /// Uses Rayon to sort the in-memory buffer. + /// + /// This may not be needed if the buffer isn't big enough for parallelism to + /// be gainful over the overhead of multithreading. + pub fn with_parallel_sort(mut self) -> Self { + self.parallel = true; + self + } + + /// Sorts a given iterator, returning a new iterator with items + pub fn sort( + &self, + iterator: I, + ) -> Result Ordering + Send + Sync>, Error> + where + T: Sortable + Ord, + I: Iterator, + { + self.sort_by(iterator, |a, b| a.cmp(b)) + } + + /// Sorts a given iterator with a comparator function, returning a new iterator with items + pub fn sort_by(&self, iterator: I, cmp: F) -> Result, Error> + where + T: Sortable, + I: Iterator, + F: Fn(&T, &T) -> Ordering + Send + Sync, + { + let mut tempdir: Option = None; + let mut sort_dir: Option = None; + + let mut count = 0; + let mut segments_file: Vec = Vec::new(); + let size_of_items = std::mem::size_of::(); + let mut buffer: Vec = Vec::with_capacity(self.segment_size / size_of_items); + for next_item in iterator { + count += 1; + buffer.push(next_item); + if buffer.len() > self.segment_size { + let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; + self.sort_and_write_segment(sort_dir, &mut segments_file, &mut buffer, &cmp)?; + } + } + + // Write any items left in buffer, but only if we had at least 1 segment + // written. Otherwise we use the buffer itself to iterate from memory + let pass_through_queue = if !buffer.is_empty() && !segments_file.is_empty() { + let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; + self.sort_and_write_segment(sort_dir, &mut segments_file, &mut buffer, &cmp)?; + None + } else { + buffer.sort_by(&cmp); + Some(VecDeque::from(buffer)) + }; + + SortedIterator::new(tempdir, pass_through_queue, segments_file, count, cmp) + } + + /// Sorts a given iterator with a key extraction function, returning a new iterator with items + pub fn sort_by_key( + &self, + iterator: I, + f: F, + ) -> Result Ordering + Send + Sync>, Error> + where + T: Sortable, + I: Iterator, + F: Fn(&T) -> K + Send + Sync, + K: Ord, + { + self.sort_by(iterator, move |a, b| f(a).cmp(&f(b))) + } + + /// We only want to create directory if it's needed (i.e. if the dataset + /// doesn't fit in memory) to prevent filesystem latency + fn lazy_create_dir<'a>( + &self, + tempdir: &mut Option, + sort_dir: &'a mut Option, + ) -> Result<&'a Path, Error> { + if let Some(sort_dir) = sort_dir { + return Ok(sort_dir); + } + + *sort_dir = if let Some(ref sort_dir) = self.sort_dir { + Some(sort_dir.to_path_buf()) + } else { + *tempdir = Some(tempfile::TempDir::new()?); + Some(tempdir.as_ref().unwrap().path().to_path_buf()) + }; + + Ok(sort_dir.as_ref().unwrap()) + } + + fn sort_and_write_segment( + &self, + sort_dir: &Path, + segments: &mut Vec, + buffer: &mut Vec, + cmp: F, + ) -> Result<(), Error> + where + T: Sortable, + F: Fn(&T, &T) -> Ordering + Send + Sync, + { + if self.parallel { + buffer.par_sort_by(|a, b| cmp(a, b)); + } else { + buffer.sort_by(|a, b| cmp(a, b)); + } + + let segment_path = sort_dir.join(format!("{}", segments.len())); + let segment_file = OpenOptions::new() + .create(true) + .truncate(true) + .read(true) + .write(true) + .open(&segment_path)?; + let mut buf_writer = BufWriter::new(segment_file); + + for item in buffer.drain(0..) { + item.encode(&mut buf_writer); + } + + let file = buf_writer.into_inner()?; + segments.push(file); + + Ok(()) + } +} + +impl Default for ExternalSorter { + fn default() -> Self { + ExternalSorter::new() + } +} + +pub trait Sortable: Sized + Send { + fn encode(&self, writer: &mut W); + fn decode(reader: &mut R) -> Option; +} + +pub struct SortedIterator { + _tempdir: Option, + pass_through_queue: Option>, + segments_file: Vec>, + next_values: Vec>, + count: u64, + cmp: F, +} + +impl Ordering + Send + Sync> SortedIterator { + fn new( + tempdir: Option, + pass_through_queue: Option>, + mut segments_file: Vec, + count: u64, + cmp: F, + ) -> Result, Error> { + for segment in &mut segments_file { + segment.seek(SeekFrom::Start(0))?; + } + + let next_values = segments_file + .iter_mut() + .map(|file| T::decode(file)) + .collect(); + + let segments_file_buffered = segments_file.into_iter().map(BufReader::new).collect(); + + Ok(SortedIterator { + _tempdir: tempdir, + pass_through_queue, + segments_file: segments_file_buffered, + next_values, + count, + cmp, + }) + } + + pub fn sorted_count(&self) -> u64 { + self.count + } +} + +impl Ordering> Iterator for SortedIterator { + type Item = T; + + fn next(&mut self) -> Option { + // if we have a pass through, we dequeue from it directly + if let Some(ptb) = self.pass_through_queue.as_mut() { + return ptb.pop_front(); + } + + // otherwise, we iter from segments on disk + let mut smallest_idx: Option = None; + { + let mut smallest: Option<&T> = None; + for idx in 0..self.segments_file.len() { + let next_value = self.next_values[idx].as_ref(); + if next_value.is_none() { + continue; + } + + if smallest.is_none() + || (self.cmp)(next_value.unwrap(), smallest.unwrap()) == Ordering::Less + { + smallest = Some(next_value.unwrap()); + smallest_idx = Some(idx); + } + } + } + + smallest_idx.map(|idx| { + let file = &mut self.segments_file[idx]; + let value = self.next_values[idx].take().unwrap(); + self.next_values[idx] = T::decode(file); + value + }) + } +} + +#[cfg(test)] +pub mod test { + use super::*; + + use byteorder::{ReadBytesExt, WriteBytesExt}; + + #[test] + fn test_smaller_than_segment() { + let sorter = ExternalSorter::new(); + let data: Vec = (0..100u32).collect(); + let data_rev: Vec = data.iter().rev().cloned().collect(); + + let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); + + // should not have used any segments (all in memory) + assert_eq!(sorted_iter.segments_file.len(), 0); + let sorted_data: Vec = sorted_iter.collect(); + + assert_eq!(data, sorted_data); + } + + #[test] + fn test_multiple_segments() { + let sorter = ExternalSorter::new().with_segment_size(100); + let data: Vec = (0..1000u32).collect(); + + let data_rev: Vec = data.iter().rev().cloned().collect(); + let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); + assert_eq!(sorted_iter.segments_file.len(), 10); + + let sorted_data: Vec = sorted_iter.collect(); + assert_eq!(data, sorted_data); + } + + #[test] + fn test_parallel() { + let sorter = ExternalSorter::new() + .with_segment_size(100) + .with_parallel_sort(); + let data: Vec = (0..1000u32).collect(); + + let data_rev: Vec = data.iter().rev().cloned().collect(); + let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); + assert_eq!(sorted_iter.segments_file.len(), 10); + + let sorted_data: Vec = sorted_iter.collect(); + assert_eq!(data, sorted_data); + } + + impl Sortable for u32 { + fn encode(&self, writer: &mut W) { + writer.write_u32::(*self).unwrap(); + } + + fn decode(reader: &mut R) -> Option { + reader.read_u32::().ok() + } + } +} diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index ac615d1f7..b15eec988 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -14,8 +14,8 @@ //! More specifically, exponent can be understood so that the original number is in (1..10)*10^exponent. //! From that follows the constraints of this algorithm: It is able to compare numbers in ±(1*10^[i64::MIN]..10*10^[i64::MAX]). +use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, ops::Range}; -use serde::{Serialize, Deserialize}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] enum Sign { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 9052b2a5b..07a8879b7 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -16,6 +16,8 @@ extern crate uucore; mod numeric_str_cmp; +pub mod ext_sorter; +pub use ext_sorter::{ExternalSorter, Sortable, SortedIterator}; use clap::{App, Arg}; use fnv::FnvHasher; @@ -24,26 +26,20 @@ use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use semver::Version; +use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; +use std::ffi::OsString; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; use std::mem::replace; use std::ops::{Range, RangeInclusive}; -use std::path::Path; +use std::path::{Path, PathBuf}; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() -use extsort::*; -use std::str; -use serde::{Serialize, Deserialize}; -use std::ffi::OsString; -use std::usize; -use std::path::PathBuf; -use std::string::*; -use rayon::prelude::*; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -255,30 +251,39 @@ struct Line { impl Sortable for Line { fn encode(&self, write: &mut W) { - let line = Line {line: self.line.to_owned(), selections: self.selections.to_owned() }; + let line = Line { + line: self.line.to_owned(), + selections: self.selections.to_owned(), + }; let serialized = serde_json::ser::to_string(&line).unwrap(); // Each instance of valid JSON needs to be seperated by something, so here we use a newline - write.write_all(format!("{}{}", serialized, "\n").as_bytes()).unwrap(); + write + .write_all(format!("{}{}", serialized, "\n").as_bytes()) + .unwrap(); } // This crate asks us to write one Line at a time, but returns multiple Lines to us(?). - // However, this crate also expects us to return a result of Option, - // so we concat the these lines into a single Option. So, it's possible this is broken, + // However, this crate also expects us to return a result of Option, + // so we concat the these lines into a single Option. So, it's possible this is broken, // and/or needs to be tested more thoroughly. Perhaps we need to rethink our Line struct or rewrite a - // ext sorter ourselves. + // ext sorter ourselves. fn decode(read: &mut R) -> Option { let buf_reader = BufReader::new(read); let result = { - let mut line_joined= String::new(); - let mut selections_joined= SmallVec::new(); - for line in buf_reader.lines() { + let mut line_joined = String::new(); + let mut selections_joined = SmallVec::new(); + let p_iter = buf_reader.lines().peekable(); + for line in p_iter { let mut deserialized_line: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); - line_joined = format!("{}\n{}", line_joined, deserialized_line.line); - // I think we've done our sorting already and these are irrelevant? @miDeb what's your sense? + line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line); + // I think we've done our sorting already and these are irrelevant? + // @miDeb what's your sense? Could we just return an empty vec? selections_joined.append(&mut deserialized_line.selections); - selections_joined.dedup(); } - Some( Line {line: line_joined, selections: selections_joined} ) + Some(Line { + line: line_joined.strip_suffix("\n").unwrap().to_owned(), + selections: selections_joined, + }) }; result } @@ -798,6 +803,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .arg( Arg::with_name(OPT_BUF_SIZE) + .short("S") .long(OPT_BUF_SIZE) .help("sets the maximum SIZE of each segment in number of sorted items") .takes_value(true) @@ -805,6 +811,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .arg( Arg::with_name(OPT_TMP_DIR) + .short("T") .long(OPT_TMP_DIR) .help("use DIR for temporaries, not $TMPDIR or /tmp") .takes_value(true) @@ -875,13 +882,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(OPT_BUF_SIZE) { // 10K is the default extsort buffer, but that's too small, so we set at 10M - // Although the "default" is never used unless extsort options are given - settings.buffer_size = { + // Although the "default" is never used unless extsort options are given + settings.buffer_size = { let input = matches - .value_of(OPT_BUF_SIZE) - .map(String::from) - .unwrap_or( format! ( "{}", DEFAULT_BUF_SIZE ) ); - + .value_of(OPT_BUF_SIZE) + .map(String::from) + .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); + human_numeric_convert(&input) } } @@ -890,13 +897,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let result = matches .value_of(OPT_TMP_DIR) .map(String::from) - .unwrap_or(DEFAULT_TMPDIR.to_owned() ); + .unwrap_or(DEFAULT_TMPDIR.to_owned()); settings.tmp_dir = PathBuf::from(format!(r"{}", result)); } else { for (key, value) in env::vars_os() { if key == OsString::from("TMPDIR") { - settings.tmp_dir = PathBuf::from(format!(r"{}", value.into_string().unwrap_or("/tmp".to_owned()))); - break + settings.tmp_dir = PathBuf::from(format!( + r"{}", + value.into_string().unwrap_or("/tmp".to_owned()) + )); + break; } settings.tmp_dir = PathBuf::from(DEFAULT_TMPDIR); } @@ -1019,12 +1029,9 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { if settings.check { return exec_check_file(&lines, &settings); } - - if ( settings.buffer_size != DEFAULT_BUF_SIZE ) || ( settings.tmp_dir.as_os_str() != DEFAULT_TMPDIR ) { - lines = ext_sort_by(lines, &settings); - } else { - sort_by(&mut lines, &settings); - } + + + lines = sort_by(lines, &settings); if settings.merge { if settings.unique { @@ -1079,20 +1086,18 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn ext_sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { - let sorter = ExternalSorter::new().with_segment_size(settings.buffer_size).with_sort_dir(settings.tmp_dir.clone()).with_parallel_sort(); - let result = sorter.sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)).unwrap().collect(); +fn sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { + let sorter = ExternalSorter::new() + .with_segment_size(settings.buffer_size) + .with_sort_dir(settings.tmp_dir.clone()) + .with_parallel_sort(); + let result = sorter + .sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)) + .unwrap() + .collect(); result } -fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { - if settings.stable || settings.unique { - lines.par_sort_by(|a, b| compare_by(a, b, &settings)) - } else { - lines.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) - } -} - fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { for (idx, selector) in global_settings.selectors.iter().enumerate() { let a_selection = &a.selections[idx]; @@ -1137,14 +1142,14 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } } -// Brought back! Probably want to do through numstrcmp somehow now +// It's back to do conversions for command options! Probably want to do through numstrcmp somehow now fn human_numeric_convert(a: &str) -> usize { let num_part = leading_num_common(a); let (_, s) = a.split_at(num_part.len()); let num_part = permissive_f64_parse(num_part); let suffix = match s.parse().unwrap_or('\0') { // SI Units - 'K' => 1E3, + 'K' | 'k' => 1E3, 'M' => 1E6, 'G' => 1E9, 'T' => 1E12, @@ -1164,7 +1169,7 @@ fn default_compare(a: &str, b: &str) -> Ordering { a.cmp(b) } -// This function does the initial detection of numeric lines. +/// This function does the initial detection of numeric lines for FP compares. // Lines starting with a number or positive or negative sign. // It also strips the string of any thing that could never // be a number for the purposes of any type of numeric comparison. @@ -1195,7 +1200,7 @@ fn leading_num_common(a: &str) -> &str { s } -// This function cleans up the initial comparison done by leading_num_common for a general numeric compare. +/// This function cleans up the initial comparison done by leading_num_common for a general numeric compare. // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. // For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000. @@ -1318,7 +1323,7 @@ fn month_parse(line: &str) -> Month { "" }; - match pattern.to_uppercase().as_ref() { + let result = match pattern.to_uppercase().as_ref() { "JAN" => Month::January, "FEB" => Month::February, "MAR" => Month::March, @@ -1332,7 +1337,8 @@ fn month_parse(line: &str) -> Month { "NOV" => Month::November, "DEC" => Month::December, _ => Month::Unknown, - } + }; + result } fn month_compare(a: &str, b: &str) -> Ordering { From da94e350448239ca6ddd6442d76e4cd610dd6d98 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 13:02:50 -0500 Subject: [PATCH 063/399] Cleanup, removed unused code, add copyright --- src/uu/sort/src/ext_sorter.rs | 94 +---------------------------------- src/uu/sort/src/sort.rs | 20 +++++--- 2 files changed, 14 insertions(+), 100 deletions(-) diff --git a/src/uu/sort/src/ext_sorter.rs b/src/uu/sort/src/ext_sorter.rs index c19f1262b..d607cbd3e 100644 --- a/src/uu/sort/src/ext_sorter.rs +++ b/src/uu/sort/src/ext_sorter.rs @@ -1,4 +1,5 @@ // Copyright 2018 Andre-Philippe Paquet +// Copyright 2021 Robert Swinford // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -38,7 +39,7 @@ pub struct ExternalSorter { impl ExternalSorter { pub fn new() -> ExternalSorter { ExternalSorter { - segment_size: 10000000, + segment_size: 16000000000, sort_dir: None, parallel: false, } @@ -73,18 +74,6 @@ impl ExternalSorter { self } - /// Sorts a given iterator, returning a new iterator with items - pub fn sort( - &self, - iterator: I, - ) -> Result Ordering + Send + Sync>, Error> - where - T: Sortable + Ord, - I: Iterator, - { - self.sort_by(iterator, |a, b| a.cmp(b)) - } - /// Sorts a given iterator with a comparator function, returning a new iterator with items pub fn sort_by(&self, iterator: I, cmp: F) -> Result, Error> where @@ -122,21 +111,6 @@ impl ExternalSorter { SortedIterator::new(tempdir, pass_through_queue, segments_file, count, cmp) } - /// Sorts a given iterator with a key extraction function, returning a new iterator with items - pub fn sort_by_key( - &self, - iterator: I, - f: F, - ) -> Result Ordering + Send + Sync>, Error> - where - T: Sortable, - I: Iterator, - F: Fn(&T) -> K + Send + Sync, - K: Ord, - { - self.sort_by(iterator, move |a, b| f(a).cmp(&f(b))) - } - /// We only want to create directory if it's needed (i.e. if the dataset /// doesn't fit in memory) to prevent filesystem latency fn lazy_create_dir<'a>( @@ -243,10 +217,6 @@ impl Ordering + Send + Sync> SortedIterator cmp, }) } - - pub fn sorted_count(&self) -> u64 { - self.count - } } impl Ordering> Iterator for SortedIterator { @@ -285,63 +255,3 @@ impl Ordering> Iterator for SortedIterator { }) } } - -#[cfg(test)] -pub mod test { - use super::*; - - use byteorder::{ReadBytesExt, WriteBytesExt}; - - #[test] - fn test_smaller_than_segment() { - let sorter = ExternalSorter::new(); - let data: Vec = (0..100u32).collect(); - let data_rev: Vec = data.iter().rev().cloned().collect(); - - let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); - - // should not have used any segments (all in memory) - assert_eq!(sorted_iter.segments_file.len(), 0); - let sorted_data: Vec = sorted_iter.collect(); - - assert_eq!(data, sorted_data); - } - - #[test] - fn test_multiple_segments() { - let sorter = ExternalSorter::new().with_segment_size(100); - let data: Vec = (0..1000u32).collect(); - - let data_rev: Vec = data.iter().rev().cloned().collect(); - let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); - assert_eq!(sorted_iter.segments_file.len(), 10); - - let sorted_data: Vec = sorted_iter.collect(); - assert_eq!(data, sorted_data); - } - - #[test] - fn test_parallel() { - let sorter = ExternalSorter::new() - .with_segment_size(100) - .with_parallel_sort(); - let data: Vec = (0..1000u32).collect(); - - let data_rev: Vec = data.iter().rev().cloned().collect(); - let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); - assert_eq!(sorted_iter.segments_file.len(), 10); - - let sorted_data: Vec = sorted_iter.collect(); - assert_eq!(data, sorted_data); - } - - impl Sortable for u32 { - fn encode(&self, writer: &mut W) { - writer.write_u32::(*self).unwrap(); - } - - fn decode(reader: &mut R) -> Option { - reader.read_u32::().ok() - } - } -} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 07a8879b7..fab712978 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,14 +15,14 @@ #[macro_use] extern crate uucore; +mod ext_sorter; mod numeric_str_cmp; -pub mod ext_sorter; -pub use ext_sorter::{ExternalSorter, Sortable, SortedIterator}; use clap::{App, Arg}; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; +use ext_sorter::{ExternalSorter, Sortable}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use semver::Version; @@ -39,7 +39,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; use std::mem::replace; use std::ops::{Range, RangeInclusive}; use std::path::{Path, PathBuf}; -use uucore::fs::is_stdin_interactive; // for Iterator::dedup() +use uucore::fs::is_stdin_interactive; // for Iterator::dedup(); static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -90,7 +90,8 @@ static NEGATIVE: char = '-'; static POSITIVE: char = '+'; static DEFAULT_TMPDIR: &str = r"/tmp"; -static DEFAULT_BUF_SIZE: usize = 10000000usize; +// 16GB buffer for Vec before we dump to disk +static DEFAULT_BUF_SIZE: usize = 16000000000; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone)] enum SortMode { @@ -281,7 +282,7 @@ impl Sortable for Line { selections_joined.append(&mut deserialized_line.selections); } Some(Line { - line: line_joined.strip_suffix("\n").unwrap().to_owned(), + line: line_joined.strip_suffix("\n").unwrap_or("").to_owned(), selections: selections_joined, }) }; @@ -881,7 +882,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if matches.is_present(OPT_BUF_SIZE) { - // 10K is the default extsort buffer, but that's too small, so we set at 10M + // 10K is the default extsort buffer, but that's too small, so we set at 100M // Although the "default" is never used unless extsort options are given settings.buffer_size = { let input = matches @@ -889,7 +890,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(String::from) .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); - human_numeric_convert(&input) + if human_numeric_convert(&input) < 128000 { + panic!("sort will not operate with less than 128K of memory."); + } else { + human_numeric_convert(&input) + } } } @@ -1030,7 +1035,6 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { return exec_check_file(&lines, &settings); } - lines = sort_by(lines, &settings); if settings.merge { From dad7761be96e18bb66d3ff50642fab36fb242955 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 13:43:41 -0500 Subject: [PATCH 064/399] Add test --- src/uu/sort/src/ext_sorter.rs | 72 +++++++++++++++++++++++++++++++++++ src/uu/sort/src/sort.rs | 6 +-- tests/by-util/test_sort.rs | 25 +++++------- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src/uu/sort/src/ext_sorter.rs b/src/uu/sort/src/ext_sorter.rs index d607cbd3e..00fe9b401 100644 --- a/src/uu/sort/src/ext_sorter.rs +++ b/src/uu/sort/src/ext_sorter.rs @@ -74,6 +74,18 @@ impl ExternalSorter { self } + /// Sorts a given iterator, returning a new iterator with items + pub fn sort( + &self, + iterator: I, + ) -> Result Ordering + Send + Sync>, Error> + where + T: Sortable + Ord, + I: Iterator, + { + self.sort_by(iterator, |a, b| a.cmp(b)) + } + /// Sorts a given iterator with a comparator function, returning a new iterator with items pub fn sort_by(&self, iterator: I, cmp: F) -> Result, Error> where @@ -255,3 +267,63 @@ impl Ordering> Iterator for SortedIterator { }) } } + +#[cfg(test)] +pub mod test { + use super::*; + + use byteorder::{ReadBytesExt, WriteBytesExt}; + + #[test] + fn test_smaller_than_segment() { + let sorter = ExternalSorter::new(); + let data: Vec = (0..100u32).collect(); + let data_rev: Vec = data.iter().rev().cloned().collect(); + + let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); + + // should not have used any segments (all in memory) + assert_eq!(sorted_iter.segments_file.len(), 0); + let sorted_data: Vec = sorted_iter.collect(); + + assert_eq!(data, sorted_data); + } + + #[test] + fn test_multiple_segments() { + let sorter = ExternalSorter::new().with_segment_size(100); + let data: Vec = (0..1000u32).collect(); + + let data_rev: Vec = data.iter().rev().cloned().collect(); + let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); + assert_eq!(sorted_iter.segments_file.len(), 10); + + let sorted_data: Vec = sorted_iter.collect(); + assert_eq!(data, sorted_data); + } + + #[test] + fn test_parallel() { + let sorter = ExternalSorter::new() + .with_segment_size(100) + .with_parallel_sort(); + let data: Vec = (0..1000u32).collect(); + + let data_rev: Vec = data.iter().rev().cloned().collect(); + let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); + assert_eq!(sorted_iter.segments_file.len(), 10); + + let sorted_data: Vec = sorted_iter.collect(); + assert_eq!(data, sorted_data); + } + + impl Sortable for u32 { + fn encode(&self, writer: &mut W) { + writer.write_u32::(*self).unwrap(); + } + + fn decode(reader: &mut R) -> Option { + reader.read_u32::().ok() + } + } +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index fab712978..4854990e6 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -90,7 +90,7 @@ static NEGATIVE: char = '-'; static POSITIVE: char = '+'; static DEFAULT_TMPDIR: &str = r"/tmp"; -// 16GB buffer for Vec before we dump to disk +// 16GB buffer for Vec before we dump to disk static DEFAULT_BUF_SIZE: usize = 16000000000; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone)] @@ -890,11 +890,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(String::from) .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); - if human_numeric_convert(&input) < 128000 { - panic!("sort will not operate with less than 128K of memory."); - } else { human_numeric_convert(&input) - } } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index a4a9a383c..0ca917a86 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -8,6 +8,16 @@ fn test_helper(file_name: &str, args: &str) { .stdout_is_fixture(format!("{}.expected", file_name)); } +#[test] +fn test_larger_than_specified_segment() { + new_ucmd!() + .arg("-n") + .arg("-S 100") + .arg("numeric_unsorted_ints.txt") + .succeeds() + .stdout_is_fixture(format!("{}", "numeric_unsorted_ints.expected")); +} + #[test] fn test_months_whitespace() { test_helper("months-whitespace", "-M"); @@ -100,21 +110,6 @@ fn test_random_shuffle_two_runs_not_the_same() { assert_ne!(result, unexpected); } -#[test] -fn test_random_shuffle_contains_two_runs_not_the_same() { - // check to verify that two random shuffles are not equal; this has the - // potential to fail in the unlikely event that random order is the same - // as the starting order, or if both random sorts end up having the same order. - const FILE: &'static str = "default_unsorted_ints.expected"; - let (at, _ucmd) = at_and_ucmd!(); - let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); - let expected = at.read(FILE); - let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); - - assert_ne!(result, expected); - assert_ne!(result, unexpected); -} - #[test] fn test_numeric_floats_and_ints() { test_helper("numeric_floats_and_ints", "-n"); From 42da444f40df869406e1d0138341b308a148001e Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 13:49:11 -0500 Subject: [PATCH 065/399] Remove unused deps --- Cargo.lock | 131 +---------------------------------------- src/uu/sort/Cargo.toml | 2 - 2 files changed, 3 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76f43d8b4..eb99af34b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,40 +119,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -[[package]] -name = "bytecount" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" - [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "cargo-platform" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714a157da7991e23d90686b9524b9e12e0407a108647f52e9328f4b3d51ac7f" -dependencies = [ - "cargo-platform", - "semver 0.11.0", - "semver-parser 0.10.2", - "serde", - "serde_json", -] - [[package]] name = "cast" version = "0.2.3" @@ -588,26 +560,6 @@ dependencies = [ "regex", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - -[[package]] -name = "extsort" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc5bb6fbca3c5ce6a51f6857eab8c35c898b2fbcb62ff1b728243dd19ec0c9f" -dependencies = [ - "rayon", - "skeptic", - "tempfile", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -992,15 +944,6 @@ dependencies = [ "proc-macro-hack", ] -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - [[package]] name = "pkg-config" version = "0.3.19" @@ -1066,17 +1009,6 @@ dependencies = [ "unicode-xid 0.2.1", ] -[[package]] -name = "pulldown-cmark" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" -dependencies = [ - "bitflags", - "memchr 2.3.4", - "unicase", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -1313,7 +1245,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", + "semver", ] [[package]] @@ -1343,17 +1275,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser 0.10.2", - "serde", + "semver-parser", ] [[package]] @@ -1362,15 +1284,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.125" @@ -1443,21 +1356,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "skeptic" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "188b810342d98f23f0bb875045299f34187b559370b041eb11520c905370a888" -dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob 0.3.0", - "pulldown-cmark", - "tempfile", - "walkdir", -] - [[package]] name = "smallvec" version = "0.6.14" @@ -1636,21 +1534,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -2413,14 +2296,12 @@ dependencies = [ name = "uu_sort" version = "0.0.6" dependencies = [ - "byteorder", "clap", - "extsort", "fnv", "itertools 0.10.0", "rand 0.7.3", "rayon", - "semver 0.9.0", + "semver", "serde", "serde_json", "smallvec 1.6.1", @@ -2733,12 +2614,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - [[package]] name = "void" version = "1.0.2" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index e1e0d1b87..f29df6ab8 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -15,8 +15,6 @@ edition = "2018" path = "src/sort.rs" [dependencies] -byteorder = "1.4.3" -extsort = "0.4.2" serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } serde = { version = "1.0", features = ["derive"] } rayon = "1.5" From 0275a43c5bc414c52e40edbb1e2cd2d531255009 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 14:05:27 -0500 Subject: [PATCH 066/399] Make modifications clearer per Apache license --- src/uu/sort/src/ext_sorter.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/ext_sorter.rs b/src/uu/sort/src/ext_sorter.rs index 00fe9b401..782e80429 100644 --- a/src/uu/sort/src/ext_sorter.rs +++ b/src/uu/sort/src/ext_sorter.rs @@ -1,5 +1,5 @@ // Copyright 2018 Andre-Philippe Paquet -// Copyright 2021 Robert Swinford +// Modifications copyright 2021 Robert Swinford // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// This file has been modified for use in the uutils project. + use rayon::prelude::*; use std::{ cmp::Ordering, From e3e1ee30ebd378b8dfdf9502a271bcda71c80667 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 14:37:16 -0500 Subject: [PATCH 067/399] Add additional notices --- src/uu/sort/src/APACHE_LICENSE_EXT_SORTER | 202 ++++++++++++++++++++++ src/uu/sort/src/NOTICE | 8 + src/uu/sort/src/ext_sorter.rs | 2 +- 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/uu/sort/src/APACHE_LICENSE_EXT_SORTER create mode 100644 src/uu/sort/src/NOTICE diff --git a/src/uu/sort/src/APACHE_LICENSE_EXT_SORTER b/src/uu/sort/src/APACHE_LICENSE_EXT_SORTER new file mode 100644 index 000000000..7a4a3ea24 --- /dev/null +++ b/src/uu/sort/src/APACHE_LICENSE_EXT_SORTER @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/src/uu/sort/src/NOTICE b/src/uu/sort/src/NOTICE new file mode 100644 index 000000000..3a20ec6e7 --- /dev/null +++ b/src/uu/sort/src/NOTICE @@ -0,0 +1,8 @@ +extsort +Copyright 2016 Andre-Philippe Paquet + +This project includes software developed by Andre-Philippe Paquet. + +The ext_sorter.rs file was copied and modified for use in the uutils' coreutils subproject, sort. + +Except as otherwise specified, all other contributions to sort are licensed according to the terms of the LICENSE file. \ No newline at end of file diff --git a/src/uu/sort/src/ext_sorter.rs b/src/uu/sort/src/ext_sorter.rs index 782e80429..026c6d8da 100644 --- a/src/uu/sort/src/ext_sorter.rs +++ b/src/uu/sort/src/ext_sorter.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file has been modified for use in the uutils project. +// This file has been modified for use in the uutils' coreutils subproject, sort. use rayon::prelude::*; use std::{ From 0151f30c4ed420962a77365044ae6a6151aaa51e Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:04:25 -0500 Subject: [PATCH 068/399] Change directory structure --- src/uu/sort/src/APACHE_LICENSE_EXT_SORTER | 202 ------------------ src/uu/sort/src/ext_sorter/LICENSE | 202 ++++++++++++++++++ src/uu/sort/src/{ => ext_sorter}/NOTICE | 0 .../src/{ext_sorter.rs => ext_sorter/mod.rs} | 62 +----- src/uu/sort/src/sort.rs | 4 +- 5 files changed, 205 insertions(+), 265 deletions(-) delete mode 100644 src/uu/sort/src/APACHE_LICENSE_EXT_SORTER create mode 100644 src/uu/sort/src/ext_sorter/LICENSE rename src/uu/sort/src/{ => ext_sorter}/NOTICE (100%) rename src/uu/sort/src/{ext_sorter.rs => ext_sorter/mod.rs} (82%) diff --git a/src/uu/sort/src/APACHE_LICENSE_EXT_SORTER b/src/uu/sort/src/APACHE_LICENSE_EXT_SORTER deleted file mode 100644 index 7a4a3ea24..000000000 --- a/src/uu/sort/src/APACHE_LICENSE_EXT_SORTER +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/src/uu/sort/src/ext_sorter/LICENSE b/src/uu/sort/src/ext_sorter/LICENSE new file mode 100644 index 000000000..fe647bd7f --- /dev/null +++ b/src/uu/sort/src/ext_sorter/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/src/uu/sort/src/NOTICE b/src/uu/sort/src/ext_sorter/NOTICE similarity index 100% rename from src/uu/sort/src/NOTICE rename to src/uu/sort/src/ext_sorter/NOTICE diff --git a/src/uu/sort/src/ext_sorter.rs b/src/uu/sort/src/ext_sorter/mod.rs similarity index 82% rename from src/uu/sort/src/ext_sorter.rs rename to src/uu/sort/src/ext_sorter/mod.rs index 026c6d8da..c5d7e59e8 100644 --- a/src/uu/sort/src/ext_sorter.rs +++ b/src/uu/sort/src/ext_sorter/mod.rs @@ -12,7 +12,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - +// // This file has been modified for use in the uutils' coreutils subproject, sort. use rayon::prelude::*; @@ -269,63 +269,3 @@ impl Ordering> Iterator for SortedIterator { }) } } - -#[cfg(test)] -pub mod test { - use super::*; - - use byteorder::{ReadBytesExt, WriteBytesExt}; - - #[test] - fn test_smaller_than_segment() { - let sorter = ExternalSorter::new(); - let data: Vec = (0..100u32).collect(); - let data_rev: Vec = data.iter().rev().cloned().collect(); - - let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); - - // should not have used any segments (all in memory) - assert_eq!(sorted_iter.segments_file.len(), 0); - let sorted_data: Vec = sorted_iter.collect(); - - assert_eq!(data, sorted_data); - } - - #[test] - fn test_multiple_segments() { - let sorter = ExternalSorter::new().with_segment_size(100); - let data: Vec = (0..1000u32).collect(); - - let data_rev: Vec = data.iter().rev().cloned().collect(); - let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); - assert_eq!(sorted_iter.segments_file.len(), 10); - - let sorted_data: Vec = sorted_iter.collect(); - assert_eq!(data, sorted_data); - } - - #[test] - fn test_parallel() { - let sorter = ExternalSorter::new() - .with_segment_size(100) - .with_parallel_sort(); - let data: Vec = (0..1000u32).collect(); - - let data_rev: Vec = data.iter().rev().cloned().collect(); - let sorted_iter = sorter.sort(data_rev.into_iter()).unwrap(); - assert_eq!(sorted_iter.segments_file.len(), 10); - - let sorted_data: Vec = sorted_iter.collect(); - assert_eq!(data, sorted_data); - } - - impl Sortable for u32 { - fn encode(&self, writer: &mut W) { - writer.write_u32::(*self).unwrap(); - } - - fn decode(reader: &mut R) -> Option { - reader.read_u32::().ok() - } - } -} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4854990e6..7b19547ea 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -256,7 +256,7 @@ impl Sortable for Line { line: self.line.to_owned(), selections: self.selections.to_owned(), }; - let serialized = serde_json::ser::to_string(&line).unwrap(); + let serialized = serde_json::to_string(&line).unwrap(); // Each instance of valid JSON needs to be seperated by something, so here we use a newline write .write_all(format!("{}{}", serialized, "\n").as_bytes()) @@ -275,7 +275,7 @@ impl Sortable for Line { let mut selections_joined = SmallVec::new(); let p_iter = buf_reader.lines().peekable(); for line in p_iter { - let mut deserialized_line: Line = serde_json::de::from_str(&line.unwrap()).unwrap(); + let mut deserialized_line: Line = serde_json::from_str(&line.unwrap()).unwrap(); line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line); // I think we've done our sorting already and these are irrelevant? // @miDeb what's your sense? Could we just return an empty vec? From 298e269531b8c1ba0f29e6d31d476c0a824c078e Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:08:42 -0500 Subject: [PATCH 069/399] Remove unsed code --- src/uu/sort/src/ext_sorter/mod.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/uu/sort/src/ext_sorter/mod.rs b/src/uu/sort/src/ext_sorter/mod.rs index c5d7e59e8..07ae3bb09 100644 --- a/src/uu/sort/src/ext_sorter/mod.rs +++ b/src/uu/sort/src/ext_sorter/mod.rs @@ -75,18 +75,6 @@ impl ExternalSorter { self.parallel = true; self } - - /// Sorts a given iterator, returning a new iterator with items - pub fn sort( - &self, - iterator: I, - ) -> Result Ordering + Send + Sync>, Error> - where - T: Sortable + Ord, - I: Iterator, - { - self.sort_by(iterator, |a, b| a.cmp(b)) - } /// Sorts a given iterator with a comparator function, returning a new iterator with items pub fn sort_by(&self, iterator: I, cmp: F) -> Result, Error> @@ -98,12 +86,10 @@ impl ExternalSorter { let mut tempdir: Option = None; let mut sort_dir: Option = None; - let mut count = 0; let mut segments_file: Vec = Vec::new(); let size_of_items = std::mem::size_of::(); let mut buffer: Vec = Vec::with_capacity(self.segment_size / size_of_items); for next_item in iterator { - count += 1; buffer.push(next_item); if buffer.len() > self.segment_size { let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; @@ -122,7 +108,7 @@ impl ExternalSorter { Some(VecDeque::from(buffer)) }; - SortedIterator::new(tempdir, pass_through_queue, segments_file, count, cmp) + SortedIterator::new(tempdir, pass_through_queue, segments_file, cmp) } /// We only want to create directory if it's needed (i.e. if the dataset @@ -199,7 +185,6 @@ pub struct SortedIterator { pass_through_queue: Option>, segments_file: Vec>, next_values: Vec>, - count: u64, cmp: F, } @@ -208,7 +193,6 @@ impl Ordering + Send + Sync> SortedIterator tempdir: Option, pass_through_queue: Option>, mut segments_file: Vec, - count: u64, cmp: F, ) -> Result, Error> { for segment in &mut segments_file { @@ -227,7 +211,6 @@ impl Ordering + Send + Sync> SortedIterator pass_through_queue, segments_file: segments_file_buffered, next_values, - count, cmp, }) } From 9170e7a5112f6ca437e564bfd6b2fbaac13ea6d6 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:15:12 -0500 Subject: [PATCH 070/399] Modify NOTICE --- src/uu/sort/src/ext_sorter/NOTICE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/ext_sorter/NOTICE b/src/uu/sort/src/ext_sorter/NOTICE index 3a20ec6e7..168d9d3b5 100644 --- a/src/uu/sort/src/ext_sorter/NOTICE +++ b/src/uu/sort/src/ext_sorter/NOTICE @@ -1,8 +1,8 @@ extsort Copyright 2016 Andre-Philippe Paquet -This project includes software developed by Andre-Philippe Paquet. +This ext_sorter module includes software developed by Andre-Philippe Paquet. -The ext_sorter.rs file was copied and modified for use in the uutils' coreutils subproject, sort. +The sorter.rs file was copied and modified for use in the uutils' coreutils subproject, sort. Except as otherwise specified, all other contributions to sort are licensed according to the terms of the LICENSE file. \ No newline at end of file From e841bb6a2414114669bd2d75a23260f54d2da505 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:20:16 -0500 Subject: [PATCH 071/399] More license cleanup --- src/uu/sort/src/ext_sorter/{LICENSE => APACHE_LICENSE} | 0 src/uu/sort/src/ext_sorter/NOTICE | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/uu/sort/src/ext_sorter/{LICENSE => APACHE_LICENSE} (100%) diff --git a/src/uu/sort/src/ext_sorter/LICENSE b/src/uu/sort/src/ext_sorter/APACHE_LICENSE similarity index 100% rename from src/uu/sort/src/ext_sorter/LICENSE rename to src/uu/sort/src/ext_sorter/APACHE_LICENSE diff --git a/src/uu/sort/src/ext_sorter/NOTICE b/src/uu/sort/src/ext_sorter/NOTICE index 168d9d3b5..2964ac31d 100644 --- a/src/uu/sort/src/ext_sorter/NOTICE +++ b/src/uu/sort/src/ext_sorter/NOTICE @@ -1,8 +1,8 @@ extsort -Copyright 2016 Andre-Philippe Paquet +Copyright 2018 Andre-Philippe Paquet This ext_sorter module includes software developed by Andre-Philippe Paquet. The sorter.rs file was copied and modified for use in the uutils' coreutils subproject, sort. -Except as otherwise specified, all other contributions to sort are licensed according to the terms of the LICENSE file. \ No newline at end of file +Except as otherwise specified, all other contributions to sort are licensed according to the terms of the LICENSE file found in root directory of this project. \ No newline at end of file From df0304d8f436e674267d33377918b059ae0ad31e Mon Sep 17 00:00:00 2001 From: Aleksandar Janicijevic Date: Sun, 18 Apr 2021 16:36:43 -0400 Subject: [PATCH 072/399] touch: added unit test for test -m -t fail (#2089) --- tests/by-util/test_touch.rs | 55 +++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 9f2c079b0..40fbb8aa9 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -367,7 +367,58 @@ fn test_touch_mtime_dst_succeeds() { let target_time = str_to_filetime("%Y%m%d%H%M", "202103140300"); let (_, mtime) = get_file_times(&at, file); - eprintln!("target_time: {:?}", target_time); - eprintln!("mtime: {:?}", mtime); assert!(target_time == mtime); } + +// is_dst_switch_hour returns true if timespec ts is just before the switch +// to Daylight Saving Time. +// For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 } +// for March 8 2020 01:00:00 AM +// is just before the switch because on that day clock jumps by 1 hour, +// so 1 minute after 01:59:00 is 03:00:00. +fn is_dst_switch_hour(ts: time::Timespec) -> bool { + let ts_after = ts + time::Duration::hours(1); + let tm = time::at(ts); + let tm_after = time::at(ts_after); + tm_after.tm_hour == tm.tm_hour + 2 +} + +// get_dstswitch_hour returns date string for which touch -m -t fails. +// For example, in EST (UTC-5), that will be "202003080200" so +// touch -m -t 202003080200 somefile +// fails (that date/time does not exist). +// In other locales it will be a different date/time, and in some locales +// it doesn't exist at all, in which case this function will return None. +fn get_dstswitch_hour() -> Option { + let now = time::now(); + // Start from January 1, 2020, 00:00. + let mut tm = time::strptime("20200101-0000", "%Y%m%d-%H%M").unwrap(); + tm.tm_isdst = -1; + tm.tm_utcoff = now.tm_utcoff; + let mut ts = tm.to_timespec(); + // Loop through all hours in year 2020 until we find the hour just + // before the switch to DST. + for _i in 0..(366 * 24) { + if is_dst_switch_hour(ts) { + let mut tm = time::at(ts); + tm.tm_hour = tm.tm_hour + 1; + let s = time::strftime("%Y%m%d%H%M", &tm).unwrap().to_string(); + return Some(s); + } + ts = ts + time::Duration::hours(1); + } + None +} + +#[test] +fn test_touch_mtime_dst_fails() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_set_mtime_dst_fails"; + + match get_dstswitch_hour() { + Some(s) => { + ucmd.args(&["-m", "-t", &s, file]).fails(); + } + None => (), + } +} From fb19522ca05401ec95eed6f091c89f5fd0c94fbb Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:39:20 -0500 Subject: [PATCH 073/399] Bring back non-external sort as default --- src/uu/sort/src/sort.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7b19547ea..e04688e70 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -18,6 +18,7 @@ extern crate uucore; mod ext_sorter; mod numeric_str_cmp; +use rayon::prelude::*; use clap::{App, Arg}; use fnv::FnvHasher; use itertools::Itertools; @@ -1031,8 +1032,15 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { return exec_check_file(&lines, &settings); } - lines = sort_by(lines, &settings); - + // Only use ext_sorter when we need to. + // Probably faster that we don't create + // an owned value each run + if settings.buffer_size != DEFAULT_BUF_SIZE { + lines = ext_sort_by(lines, &settings); + } else { + sort_by(&mut lines, &settings); + } + if settings.merge { if settings.unique { print_sorted(file_merger.dedup(), &settings) @@ -1086,7 +1094,7 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { +fn ext_sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { let sorter = ExternalSorter::new() .with_segment_size(settings.buffer_size) .with_sort_dir(settings.tmp_dir.clone()) @@ -1098,6 +1106,10 @@ fn sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { result } +fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { + lines.par_sort_by(|a, b| compare_by(a, b, &settings)) +} + fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { for (idx, selector) in global_settings.selectors.iter().enumerate() { let a_selection = &a.selections[idx]; From 559f4e81f607749c4af505c97211459c78854287 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:47:05 -0500 Subject: [PATCH 074/399] More license cleanup --- src/uu/sort/src/ext_sorter/NOTICE | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/uu/sort/src/ext_sorter/NOTICE b/src/uu/sort/src/ext_sorter/NOTICE index 2964ac31d..d7c2199d1 100644 --- a/src/uu/sort/src/ext_sorter/NOTICE +++ b/src/uu/sort/src/ext_sorter/NOTICE @@ -1,8 +1,9 @@ -extsort +ext_sorter Copyright 2018 Andre-Philippe Paquet +Copyright 2021 Robert Swinford -This ext_sorter module includes software developed by Andre-Philippe Paquet. +This ext_sorter module includes software developed by Andre-Philippe Paquet as extsort. The sorter.rs file was copied and modified for use in the uutils' coreutils subproject, sort. -Except as otherwise specified, all other contributions to sort are licensed according to the terms of the LICENSE file found in root directory of this project. \ No newline at end of file +sort is licensed according to the term of the LICENSE file found in root directory of the uutils' coreutils project. \ No newline at end of file From deb94cef7aca462e0c1dc405ff1a13ec0fc23b0c Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 15:52:48 -0500 Subject: [PATCH 075/399] Cleanup --- src/uu/sort/src/sort.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index e04688e70..1df3b1bc9 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -264,11 +264,8 @@ impl Sortable for Line { .unwrap(); } - // This crate asks us to write one Line at a time, but returns multiple Lines to us(?). - // However, this crate also expects us to return a result of Option, - // so we concat the these lines into a single Option. So, it's possible this is broken, - // and/or needs to be tested more thoroughly. Perhaps we need to rethink our Line struct or rewrite a - // ext sorter ourselves. + // This crate asks us to write one Line struct at a time, but then returns multiple Lines to us at once. + // We concatanate them and return them as one big Line here. fn decode(read: &mut R) -> Option { let buf_reader = BufReader::new(read); let result = { @@ -278,7 +275,7 @@ impl Sortable for Line { for line in p_iter { let mut deserialized_line: Line = serde_json::from_str(&line.unwrap()).unwrap(); line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line); - // I think we've done our sorting already and these are irrelevant? + // I think we've done our sorting already and these selctions are irrelevant? // @miDeb what's your sense? Could we just return an empty vec? selections_joined.append(&mut deserialized_line.selections); } From 8072e2092af919eab71cfffe144851ff943edf2c Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 16:33:18 -0500 Subject: [PATCH 076/399] Cleanup loop, run rustfmt --- src/uu/sort/src/ext_sorter/mod.rs | 2 +- src/uu/sort/src/sort.rs | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/uu/sort/src/ext_sorter/mod.rs b/src/uu/sort/src/ext_sorter/mod.rs index 07ae3bb09..92b88637d 100644 --- a/src/uu/sort/src/ext_sorter/mod.rs +++ b/src/uu/sort/src/ext_sorter/mod.rs @@ -75,7 +75,7 @@ impl ExternalSorter { self.parallel = true; self } - + /// Sorts a given iterator with a comparator function, returning a new iterator with items pub fn sort_by(&self, iterator: I, cmp: F) -> Result, Error> where diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1df3b1bc9..ea41ce24f 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -18,14 +18,14 @@ extern crate uucore; mod ext_sorter; mod numeric_str_cmp; -use rayon::prelude::*; use clap::{App, Arg}; +use ext_sorter::{ExternalSorter, Sortable}; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; -use ext_sorter::{ExternalSorter, Sortable}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use rayon::prelude::*; use semver::Version; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -271,16 +271,21 @@ impl Sortable for Line { let result = { let mut line_joined = String::new(); let mut selections_joined = SmallVec::new(); - let p_iter = buf_reader.lines().peekable(); - for line in p_iter { - let mut deserialized_line: Line = serde_json::from_str(&line.unwrap()).unwrap(); - line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line); + let mut p_iter = buf_reader.lines().peekable(); + while let Some(line) = p_iter.next() { + let mut deserialized_line: Line = + serde_json::from_str(&line.as_ref().unwrap()).unwrap(); + if let Some(_next_line) = p_iter.peek() { + line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line) + } else { + line_joined = format!("{}\n{}", line_joined, deserialized_line.line) + } // I think we've done our sorting already and these selctions are irrelevant? // @miDeb what's your sense? Could we just return an empty vec? selections_joined.append(&mut deserialized_line.selections); } Some(Line { - line: line_joined.strip_suffix("\n").unwrap_or("").to_owned(), + line: line_joined, selections: selections_joined, }) }; @@ -888,7 +893,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(String::from) .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); - human_numeric_convert(&input) + human_numeric_convert(&input) } } @@ -1030,14 +1035,14 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { } // Only use ext_sorter when we need to. - // Probably faster that we don't create + // Probably faster that we don't create // an owned value each run if settings.buffer_size != DEFAULT_BUF_SIZE { lines = ext_sort_by(lines, &settings); } else { sort_by(&mut lines, &settings); } - + if settings.merge { if settings.unique { print_sorted(file_merger.dedup(), &settings) From 258325491f341db66341e8133f72fc586b281af4 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 17:39:42 -0500 Subject: [PATCH 077/399] Make human_numeric_convert a method --- src/uu/sort/src/sort.rs | 45 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index ea41ce24f..7da1c8cd7 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -127,6 +127,29 @@ struct GlobalSettings { tmp_dir: PathBuf, } +impl GlobalSettings { + // It's back to do conversions for command line opts! + // Probably want to do through numstrcmp somehow now? + fn human_numeric_convert(a: &str) -> usize { + let num_part = leading_num_common(a); + let (_, s) = a.split_at(num_part.len()); + let num_part = permissive_f64_parse(num_part); + let suffix = match s.parse().unwrap_or('\0') { + // SI Units + 'K' | 'k' => 1E3, + 'M' => 1E6, + 'G' => 1E9, + 'T' => 1E12, + 'P' => 1E15, + 'E' => 1E18, + 'Z' => 1E21, + 'Y' => 1E24, + _ => 1f64, + }; + num_part as usize * suffix as usize + } +} + impl Default for GlobalSettings { fn default() -> GlobalSettings { GlobalSettings { @@ -893,7 +916,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(String::from) .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); - human_numeric_convert(&input) + GlobalSettings::human_numeric_convert(&input) } } @@ -1156,26 +1179,6 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } } -// It's back to do conversions for command options! Probably want to do through numstrcmp somehow now -fn human_numeric_convert(a: &str) -> usize { - let num_part = leading_num_common(a); - let (_, s) = a.split_at(num_part.len()); - let num_part = permissive_f64_parse(num_part); - let suffix = match s.parse().unwrap_or('\0') { - // SI Units - 'K' | 'k' => 1E3, - 'M' => 1E6, - 'G' => 1E9, - 'T' => 1E12, - 'P' => 1E15, - 'E' => 1E18, - 'Z' => 1E21, - 'Y' => 1E24, - _ => 1f64, - }; - num_part as usize * suffix as usize -} - // Test output against BSDs and GNU with their locale // env var set to lc_ctype=utf-8 to enjoy the exact same output. #[inline(always)] From 72858dda423cd9df812425c53f1aa5ac29bdf951 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 17:40:59 -0500 Subject: [PATCH 078/399] Ran rustfmt --- src/uu/sort/src/sort.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7da1c8cd7..4938450f4 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -127,8 +127,8 @@ struct GlobalSettings { tmp_dir: PathBuf, } -impl GlobalSettings { - // It's back to do conversions for command line opts! +impl GlobalSettings { + // It's back to do conversions for command line opts! // Probably want to do through numstrcmp somehow now? fn human_numeric_convert(a: &str) -> usize { let num_part = leading_num_common(a); From 5efd67b5e2d969cb8a8e8d17e2cb4da216a1d016 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 17:44:45 -0500 Subject: [PATCH 079/399] License cleanup --- src/uu/sort/src/ext_sorter/NOTICE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/ext_sorter/NOTICE b/src/uu/sort/src/ext_sorter/NOTICE index d7c2199d1..fdfc6f04f 100644 --- a/src/uu/sort/src/ext_sorter/NOTICE +++ b/src/uu/sort/src/ext_sorter/NOTICE @@ -1,6 +1,6 @@ ext_sorter Copyright 2018 Andre-Philippe Paquet -Copyright 2021 Robert Swinford +Modifications copyright 2021 Robert Swinford This ext_sorter module includes software developed by Andre-Philippe Paquet as extsort. From fcebdbb7a737750b70b7bfd8883bee5b2acaaa04 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 17:51:44 -0500 Subject: [PATCH 080/399] Cleanup comment --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 4938450f4..3b13e5bbb 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -908,7 +908,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if matches.is_present(OPT_BUF_SIZE) { - // 10K is the default extsort buffer, but that's too small, so we set at 100M + // 16G is the default in memory buffer. // Although the "default" is never used unless extsort options are given settings.buffer_size = { let input = matches From e7bcd5955815461873e9a4ed304afe796e42b6ab Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 18 Apr 2021 18:22:30 -0500 Subject: [PATCH 081/399] Remove a clone --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 3b13e5bbb..d5a2ccbf4 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1227,7 +1227,7 @@ fn get_leading_gen(a: &str) -> &str { let mut p_iter = raw_leading_num.chars().peekable(); let mut result = ""; // Cleanup raw stripped strings - for c in p_iter.to_owned() { + while let Some(c) = p_iter.next() { let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric(); // Only general numeric recognizes e notation and, see block below, the '+' sign // Only GNU (non-general) numeric recognize thousands seperators, takes only leading # From 049f21a1990648647c73611a10082d9226604b22 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 19 Apr 2021 10:45:51 +0200 Subject: [PATCH 082/399] du: fix tests on linux (#2066) (#2090) --- src/uu/du/src/du.rs | 2 +- tests/by-util/test_du.rs | 130 +++++++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e01af5195..fa3b3c80a 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -500,7 +500,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let strs = if matches.free.is_empty() { - vec!["./".to_owned()] + vec!["./".to_owned()] // TODO: gnu `du` doesn't use trailing "/" here } else { matches.free.clone() }; diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index ea6b18937..16adcb974 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -10,7 +10,7 @@ fn test_du_basics() { new_ucmd!().succeeds().no_stderr(); } #[cfg(target_vendor = "apple")] -fn _du_basics(s: String) { +fn _du_basics(s: &str) { let answer = "32\t./subdir 8\t./subdir/deeper 24\t./subdir/links @@ -30,11 +30,18 @@ fn _du_basics(s: &str) { #[test] fn test_du_basics_subdir() { - let (_at, mut ucmd) = at_and_ucmd!(); + let scene = TestScenario::new(util_name!()); - let result = ucmd.arg(SUB_DIR).run(); - assert!(result.success); - assert_eq!(result.stderr, ""); + let result = scene.ucmd().arg(SUB_DIR).succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg(SUB_DIR).run(); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } _du_basics_subdir(result.stdout_str()); } @@ -58,26 +65,29 @@ fn _du_basics_subdir(s: &str) { #[test] fn test_du_basics_bad_name() { - let (_at, mut ucmd) = at_and_ucmd!(); - - let result = ucmd.arg("bad_name").run(); - assert_eq!(result.stdout_str(), ""); - assert_eq!( - result.stderr, - "du: error: bad_name: No such file or directory\n" - ); + new_ucmd!() + .arg("bad_name") + .succeeds() // TODO: replace with ".fails()" once `du` is fixed + .stderr_only("du: error: bad_name: No such file or directory\n"); } #[test] fn test_du_soft_link() { - let ts = TestScenario::new("du"); + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; - let link = ts.ccmd("ln").arg("-s").arg(SUB_FILE).arg(SUB_LINK).run(); - assert!(link.success); + at.symlink_file(SUB_FILE, SUB_LINK); - let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); - assert!(result.success); - assert_eq!(result.stderr, ""); + let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).run(); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } _du_soft_link(result.stdout_str()); } @@ -102,14 +112,23 @@ fn _du_soft_link(s: &str) { #[test] fn test_du_hard_link() { - let ts = TestScenario::new("du"); + let scene = TestScenario::new(util_name!()); - let link = ts.ccmd("ln").arg(SUB_FILE).arg(SUB_LINK).run(); - assert!(link.success); + let result_ln = scene.cmd("ln").arg(SUB_FILE).arg(SUB_LINK).run(); + if !result_ln.succeeded() { + scene.ccmd("ln").arg(SUB_FILE).arg(SUB_LINK).succeeds(); + } - let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); - assert!(result.success); - assert_eq!(result.stderr, ""); + let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg(SUB_DIR_LINKS).run(); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } // We do not double count hard links as the inodes are identical _du_hard_link(result.stdout_str()); } @@ -134,11 +153,23 @@ fn _du_hard_link(s: &str) { #[test] fn test_du_d_flag() { - let ts = TestScenario::new("du"); + let scene = TestScenario::new(util_name!()); - let result = ts.ucmd().arg("-d").arg("1").run(); - assert!(result.success); - assert_eq!(result.stderr, ""); + let result = scene.ucmd().arg("-d1").succeeds(); + + #[cfg(target_os = "linux")] + { + let result_reference = scene.cmd("du").arg("-d1").run(); + if result_reference.succeeded() { + assert_eq!( + // TODO: gnu `du` doesn't use trailing "/" here + // result.stdout_str(), result_reference.stdout_str() + result.stdout_str().trim_end_matches("/\n"), + result_reference.stdout_str().trim_end_matches("\n") + ); + return; + } + } _du_d_flag(result.stdout_str()); } @@ -162,9 +193,7 @@ fn _du_d_flag(s: &str) { #[test] fn test_du_h_flag_empty_file() { - let ts = TestScenario::new("du"); - - ts.ucmd() + new_ucmd!() .arg("-h") .arg("empty.txt") .succeeds() @@ -174,54 +203,51 @@ fn test_du_h_flag_empty_file() { #[cfg(feature = "touch")] #[test] fn test_du_time() { - let ts = TestScenario::new("du"); + let scene = TestScenario::new(util_name!()); - let touch = ts + scene .ccmd("touch") .arg("-a") .arg("-m") .arg("-t") .arg("201505150000") .arg("date_test") - .run(); - assert!(touch.success); + .succeeds(); - let result = ts.ucmd().arg("--time").arg("date_test").run(); + scene + .ucmd() + .arg("--time") + .arg("date_test") + .succeeds() + .stdout_only("0\t2015-05-15 00:00\tdate_test\n"); // cleanup by removing test file - ts.cmd("rm").arg("date_test").run(); - - assert!(result.success); - assert_eq!(result.stderr, ""); - assert_eq!(result.stdout, "0\t2015-05-15 00:00\tdate_test\n"); + 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("du"); + let ts = TestScenario::new(util_name!()); - let chmod = ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).run(); - println!("chmod output: {:?}", chmod); - assert!(chmod.success); - let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); + let _chmod = ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds(); + let result = ts.ucmd().arg(SUB_DIR_LINKS).succeeds(); ts.ccmd("chmod").arg("+r").arg(SUB_DIR_LINKS).run(); - assert!(result.success); assert_eq!( - result.stderr, + result.stderr_str(), "du: cannot read directory ‘subdir/links‘: Permission denied (os error 13)\n" ); - _du_no_permission(result.stdout); + _du_no_permission(result.stdout_str()); } #[cfg(target_vendor = "apple")] -fn _du_no_permission(s: String) { +fn _du_no_permission(s: &str) { assert_eq!(s, "0\tsubdir/links\n"); } #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] -fn _du_no_permission(s: String) { +fn _du_no_permission(s: &str) { assert_eq!(s, "4\tsubdir/links\n"); } From 879ab2ecb02a9f85cdbe2bcafbc5f3a5463f8810 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 19 Apr 2021 11:14:04 +0200 Subject: [PATCH 083/399] Disable test_no_options_big_input on freebsd too (#2093) --- tests/by-util/test_cat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index eb6cc9148..389269395 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -26,7 +26,7 @@ fn test_no_options() { } #[test] -#[cfg(unix)] +#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] fn test_no_options_big_input() { for &n in &[ 0, From 3bb99e70477e134c634da20c01644ca2e77c5677 Mon Sep 17 00:00:00 2001 From: Chirag Jadwani Date: Mon, 19 Apr 2021 17:02:24 +0530 Subject: [PATCH 084/399] uniq: avoid building list of duplicate lines This reduces memory usage by only storing two lines of the input file at a time. The current implementation first builds a list of all duplicate lines ('group') and then decides which lines of the group should be printed. --- src/uu/uniq/src/uniq.rs | 89 ++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index a61a78a61..7e9862e65 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -61,34 +61,43 @@ impl Uniq { reader: &mut BufReader, writer: &mut BufWriter, ) { - let mut lines: Vec = vec![]; let mut first_line_printed = false; - let delimiters = self.delimiters; + let mut group_count = 1; let line_terminator = self.get_line_terminator(); - // Don't print any delimiting lines before, after or between groups if delimiting method is 'none' - let no_delimiters = delimiters == Delimiters::None; - // The 'prepend' and 'both' delimit methods will cause output to start with delimiter line - let prepend_delimiter = delimiters == Delimiters::Prepend || delimiters == Delimiters::Both; - // The 'append' and 'both' delimit methods will cause output to end with delimiter line - let append_delimiter = delimiters == Delimiters::Append || delimiters == Delimiters::Both; + let mut lines = reader.split(line_terminator).map(get_line_string); + let mut line = match lines.next() { + Some(l) => l, + None => return, + }; - for line in reader.split(line_terminator).map(get_line_string) { - if !lines.is_empty() && self.cmp_keys(&lines[0], &line) { - // Print delimiter if delimit method is not 'none' and any line has been output - // before or if we need to start output with delimiter - let print_delimiter = !no_delimiters && (prepend_delimiter || first_line_printed); - first_line_printed |= self.print_lines(writer, &lines, print_delimiter); - lines.truncate(0); + // compare current `line` with consecutive lines (`next_line`) of the input + // and if needed, print `line` based on the command line options provided + for next_line in lines { + if self.cmp_keys(&line, &next_line) { + if (group_count == 1 && !self.repeats_only) + || (group_count > 1 && !self.uniques_only) + { + self.print_line(writer, &line, group_count, first_line_printed); + first_line_printed = true; + } + line = next_line; + group_count = 1; + } else { + if self.all_repeated { + self.print_line(writer, &line, group_count, first_line_printed); + first_line_printed = true; + line = next_line; + } + group_count += 1; } - lines.push(line); } - if !lines.is_empty() { - // Print delimiter if delimit method is not 'none' and any line has been output - // before or if we need to start output with delimiter - let print_delimiter = !no_delimiters && (prepend_delimiter || first_line_printed); - first_line_printed |= self.print_lines(writer, &lines, print_delimiter); + if (group_count == 1 && !self.repeats_only) || (group_count > 1 && !self.uniques_only) { + self.print_line(writer, &line, group_count, first_line_printed); + first_line_printed = true; } - if append_delimiter && first_line_printed { + if (self.delimiters == Delimiters::Append || self.delimiters == Delimiters::Both) + && first_line_printed + { crash_if_err!(1, writer.write_all(&[line_terminator])); } } @@ -163,27 +172,17 @@ impl Uniq { } } - fn print_lines( - &self, - writer: &mut BufWriter, - lines: &[String], - print_delimiter: bool, - ) -> bool { - let mut first_line_printed = false; - let mut count = if self.all_repeated { 1 } else { lines.len() }; - if lines.len() == 1 && !self.repeats_only || lines.len() > 1 && !self.uniques_only { - self.print_line(writer, &lines[0], count, print_delimiter); - first_line_printed = true; - count += 1; - } - if self.all_repeated { - for line in lines[1..].iter() { - self.print_line(writer, line, count, print_delimiter && !first_line_printed); - first_line_printed = true; - count += 1; - } - } - first_line_printed + fn should_print_delimiter(&self, group_count: usize, first_line_printed: bool) -> bool { + // if no delimiter option is selected then no other checks needed + self.delimiters != Delimiters::None + // print delimiter only before the first line of a group, not between lines of a group + && group_count == 1 + // if at least one line has been output before current group then print delimiter + && (first_line_printed + // or if we need to prepend delimiter then print it even at the start of the output + || self.delimiters == Delimiters::Prepend + // the 'both' delimit mode should prepend and append delimiters + || self.delimiters == Delimiters::Both) } fn print_line( @@ -191,11 +190,11 @@ impl Uniq { writer: &mut BufWriter, line: &str, count: usize, - print_delimiter: bool, + first_line_printed: bool, ) { let line_terminator = self.get_line_terminator(); - if print_delimiter { + if self.should_print_delimiter(count, first_line_printed) { crash_if_err!(1, writer.write_all(&[line_terminator])); } 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 085/399] 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 b8d667c38393e17baf7fd10ef61eeeb717fb2cec Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Mon, 19 Apr 2021 10:57:53 -0500 Subject: [PATCH 086/399] Clippy lints, more work on ext_sorter leads to 2 failing tests --- .../ext_sorter/{APACHE_LICENSE => LICENSE} | 0 src/uu/sort/src/ext_sorter/mod.rs | 28 ++++++++++++++++--- src/uu/sort/src/sort.rs | 14 ++++------ 3 files changed, 29 insertions(+), 13 deletions(-) rename src/uu/sort/src/ext_sorter/{APACHE_LICENSE => LICENSE} (100%) diff --git a/src/uu/sort/src/ext_sorter/APACHE_LICENSE b/src/uu/sort/src/ext_sorter/LICENSE similarity index 100% rename from src/uu/sort/src/ext_sorter/APACHE_LICENSE rename to src/uu/sort/src/ext_sorter/LICENSE diff --git a/src/uu/sort/src/ext_sorter/mod.rs b/src/uu/sort/src/ext_sorter/mod.rs index 92b88637d..eef7befe4 100644 --- a/src/uu/sort/src/ext_sorter/mod.rs +++ b/src/uu/sort/src/ext_sorter/mod.rs @@ -86,14 +86,24 @@ impl ExternalSorter { let mut tempdir: Option = None; let mut sort_dir: Option = None; + let mut count = 0; let mut segments_file: Vec = Vec::new(); + // FYI, the initialization size of struct Line is 96 bytes, but below works for all let size_of_items = std::mem::size_of::(); - let mut buffer: Vec = Vec::with_capacity(self.segment_size / size_of_items); + let initial_capacity = + if self.segment_size / size_of_items >= 2 { + self.segment_size / size_of_items + } else { 2 }; + let mut buffer: Vec = Vec::with_capacity(initial_capacity); for next_item in iterator { + count += 1; buffer.push(next_item); - if buffer.len() > self.segment_size { + // if after push, number of elements in vector > initial capacity + if buffer.len() > initial_capacity { let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; self.sort_and_write_segment(sort_dir, &mut segments_file, &mut buffer, &cmp)?; + // Resize buffer after write out + // buffer.shrink_to_fit(); } } @@ -108,7 +118,7 @@ impl ExternalSorter { Some(VecDeque::from(buffer)) }; - SortedIterator::new(tempdir, pass_through_queue, segments_file, cmp) + SortedIterator::new(tempdir, pass_through_queue, segments_file, count, cmp) } /// We only want to create directory if it's needed (i.e. if the dataset @@ -158,7 +168,10 @@ impl ExternalSorter { .open(&segment_path)?; let mut buf_writer = BufWriter::new(segment_file); - for item in buffer.drain(0..) { + // Possible panic here. + // Why use drain here, if we want to dump the entire buffer? + // Was "buffer.drain(0..)" + for item in buffer { item.encode(&mut buf_writer); } @@ -185,6 +198,7 @@ pub struct SortedIterator { pass_through_queue: Option>, segments_file: Vec>, next_values: Vec>, + count: u64, cmp: F, } @@ -193,6 +207,7 @@ impl Ordering + Send + Sync> SortedIterator tempdir: Option, pass_through_queue: Option>, mut segments_file: Vec, + count: u64, cmp: F, ) -> Result, Error> { for segment in &mut segments_file { @@ -211,9 +226,14 @@ impl Ordering + Send + Sync> SortedIterator pass_through_queue, segments_file: segments_file_buffered, next_values, + count, cmp, }) } + + pub fn sorted_count(&self) -> u64 { + self.count + } } impl Ordering> Iterator for SortedIterator { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index d5a2ccbf4..2bbc02e78 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -924,15 +924,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let result = matches .value_of(OPT_TMP_DIR) .map(String::from) - .unwrap_or(DEFAULT_TMPDIR.to_owned()); - settings.tmp_dir = PathBuf::from(format!(r"{}", result)); + .unwrap_or_else(|| DEFAULT_TMPDIR.to_owned()); + settings.tmp_dir = PathBuf::from(result); } else { for (key, value) in env::vars_os() { if key == OsString::from("TMPDIR") { - settings.tmp_dir = PathBuf::from(format!( - r"{}", - value.into_string().unwrap_or("/tmp".to_owned()) - )); + settings.tmp_dir = PathBuf::from(value); break; } settings.tmp_dir = PathBuf::from(DEFAULT_TMPDIR); @@ -1124,11 +1121,10 @@ fn ext_sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { .with_segment_size(settings.buffer_size) .with_sort_dir(settings.tmp_dir.clone()) .with_parallel_sort(); - let result = sorter + sorter .sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)) .unwrap() - .collect(); - result + .collect() } fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { From 0ea35f3fbcbb3596b379e2f6dd7eac2f676da298 Mon Sep 17 00:00:00 2001 From: Sivachandran Date: Tue, 20 Apr 2021 01:33:13 +0530 Subject: [PATCH 087/399] Implement install create leading components(-D) option (#2092) * Implement install's create leading components(-D) option * Format changes * Add install test to check fail on long dir name --- src/uu/install/src/install.rs | 39 ++++++++++++++++++++++++++------- tests/by-util/test_install.rs | 41 ++++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index a75ce45be..4ce665b80 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -41,6 +41,7 @@ pub struct Behavior { compare: bool, strip: bool, strip_program: String, + create_leading: bool, } #[derive(Clone, Eq, PartialEq)] @@ -70,7 +71,7 @@ static OPT_BACKUP: &str = "backup"; static OPT_BACKUP_2: &str = "backup2"; static OPT_DIRECTORY: &str = "directory"; static OPT_IGNORED: &str = "ignored"; -static OPT_CREATED: &str = "created"; +static OPT_CREATE_LEADING: &str = "create-leading"; static OPT_GROUP: &str = "group"; static OPT_MODE: &str = "mode"; static OPT_OWNER: &str = "owner"; @@ -133,9 +134,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( // TODO implement flag - Arg::with_name(OPT_CREATED) + Arg::with_name(OPT_CREATE_LEADING) .short("D") - .help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST") + .help("create all leading components of DEST except the last, then copy SOURCE to DEST") ) .arg( Arg::with_name(OPT_GROUP) @@ -266,8 +267,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> { Err("--backup") } else if matches.is_present(OPT_BACKUP_2) { Err("-b") - } else if matches.is_present(OPT_CREATED) { - Err("-D") } else if matches.is_present(OPT_SUFFIX) { Err("--suffix, -S") } else if matches.is_present(OPT_TARGET_DIRECTORY) { @@ -343,6 +342,7 @@ fn behavior(matches: &ArgMatches) -> Result { .value_of(OPT_STRIP_PROGRAM) .unwrap_or(DEFAULT_STRIP_PROGRAM), ), + create_leading: matches.is_present(OPT_CREATE_LEADING), }) } @@ -410,12 +410,35 @@ fn standard(paths: Vec, b: Behavior) -> i32 { .iter() .map(PathBuf::from) .collect::>(); + let target = Path::new(paths.last().unwrap()); - if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 { - copy_file_to_file(&sources[0], &target.to_path_buf(), &b) - } else { + if sources.len() > 1 || (target.exists() && target.is_dir()) { copy_files_into_dir(sources, &target.to_path_buf(), &b) + } else { + if let Some(parent) = target.parent() { + if !parent.exists() && b.create_leading { + if let Err(e) = fs::create_dir_all(parent) { + show_error!("failed to create {}: {}", parent.display(), e); + return 1; + } + + if mode::chmod(&parent, b.mode()).is_err() { + show_error!("failed to chmod {}", parent.display()); + return 1; + } + } + } + + if target.is_file() || is_new_file_path(target) { + copy_file_to_file(&sources[0], &target.to_path_buf(), &b) + } else { + show_error!( + "invalid target {}: No such file or directory", + target.display() + ); + 1 + } } } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index dfaaabce6..fa23de745 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -359,7 +359,7 @@ fn test_install_target_new_file_failing_nonexistent_parent() { ucmd.arg(file1) .arg(format!("{}/{}", dir, file2)) .fails() - .stderr_contains(&"not a directory"); + .stderr_contains(&"No such file or directory"); } #[test] @@ -649,3 +649,42 @@ fn test_install_and_strip_with_non_existent_program() { .stderr; assert!(stderr.contains("No such file or directory")); } + +#[test] +fn test_install_creating_leading_dirs() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let source = "create_leading_test_file"; + let target = "dir1/dir2/dir3/test_file"; + + at.touch(source); + + scene + .ucmd() + .arg("-D") + .arg(source) + .arg(at.plus(target)) + .succeeds() + .no_stderr(); +} + +#[test] +#[cfg(not(windows))] +fn test_install_creating_leading_dir_fails_on_long_name() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let source = "create_leading_test_file"; + let target = format!("{}/test_file", "d".repeat(libc::PATH_MAX as usize + 1)); + + at.touch(source); + + scene + .ucmd() + .arg("-D") + .arg(source) + .arg(at.plus(target.as_str())) + .fails() + .stderr_contains("failed to create"); +} From 25021f31ebd927e61ff2d2815b4d3cf169ce1c00 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:24:52 -0500 Subject: [PATCH 088/399] Incorporate overhead of Line struct --- src/uu/sort/src/ext_sorter/mod.rs | 17 +- src/uu/sort/src/sort.rs | 11 +- tests/by-util/test_sort.rs | 31 +- tests/fixtures/sort/ext_sort.expected | 20000 ++++++++++++++++++++++++ tests/fixtures/sort/ext_sort.txt | 20000 ++++++++++++++++++++++++ 5 files changed, 40033 insertions(+), 26 deletions(-) create mode 100644 tests/fixtures/sort/ext_sort.expected create mode 100644 tests/fixtures/sort/ext_sort.txt diff --git a/src/uu/sort/src/ext_sorter/mod.rs b/src/uu/sort/src/ext_sorter/mod.rs index eef7befe4..a90be6bb0 100644 --- a/src/uu/sort/src/ext_sorter/mod.rs +++ b/src/uu/sort/src/ext_sorter/mod.rs @@ -41,6 +41,8 @@ pub struct ExternalSorter { impl ExternalSorter { pub fn new() -> ExternalSorter { ExternalSorter { + // Default is 16G - But we never use it, + // because we always set or ignore segment_size: 16000000000, sort_dir: None, parallel: false, @@ -88,13 +90,14 @@ impl ExternalSorter { let mut count = 0; let mut segments_file: Vec = Vec::new(); - // FYI, the initialization size of struct Line is 96 bytes, but below works for all + let size_of_items = std::mem::size_of::(); - let initial_capacity = - if self.segment_size / size_of_items >= 2 { - self.segment_size / size_of_items - } else { 2 }; + // Get size of iterator + let (_, upper_bound) = iterator.size_hint(); + // Buffer size specified + minimum overhead of struct / size of items + let initial_capacity = (self.segment_size + (upper_bound.unwrap() * size_of_items)) / size_of_items; let mut buffer: Vec = Vec::with_capacity(initial_capacity); + for next_item in iterator { count += 1; buffer.push(next_item); @@ -102,8 +105,8 @@ impl ExternalSorter { if buffer.len() > initial_capacity { let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; self.sort_and_write_segment(sort_dir, &mut segments_file, &mut buffer, &cmp)?; - // Resize buffer after write out - // buffer.shrink_to_fit(); + // Truncate buffer back to initial capacity + buffer.truncate(initial_capacity); } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 2bbc02e78..571541fc6 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -293,10 +293,11 @@ impl Sortable for Line { let buf_reader = BufReader::new(read); let result = { let mut line_joined = String::new(); - let mut selections_joined = SmallVec::new(); + // Return an empty vec for selections + let selections_joined = SmallVec::new(); let mut p_iter = buf_reader.lines().peekable(); while let Some(line) = p_iter.next() { - let mut deserialized_line: Line = + let deserialized_line: Line = serde_json::from_str(&line.as_ref().unwrap()).unwrap(); if let Some(_next_line) = p_iter.peek() { line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line) @@ -305,7 +306,7 @@ impl Sortable for Line { } // I think we've done our sorting already and these selctions are irrelevant? // @miDeb what's your sense? Could we just return an empty vec? - selections_joined.append(&mut deserialized_line.selections); + //selections_joined.append(&mut deserialized_line.selections); } Some(Line { line: line_joined, @@ -909,13 +910,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(OPT_BUF_SIZE) { // 16G is the default in memory buffer. - // Although the "default" is never used unless extsort options are given + // Although the "default" is never used settings.buffer_size = { let input = matches .value_of(OPT_BUF_SIZE) .map(String::from) .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); - + GlobalSettings::human_numeric_convert(&input) } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 0ca917a86..c76ab219a 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -8,14 +8,28 @@ fn test_helper(file_name: &str, args: &str) { .stdout_is_fixture(format!("{}.expected", file_name)); } +// FYI, the initialization size of our Line struct is 96 bytes. +// +// At very small buffer sizes, with that overhead we are certainly going +// to overrun our buffer way, way, way too quickly because of these excess +// bytes for the struct. +// +// For instance, seq 0..20000 > ...text = 108894 bytes +// But overhead is 1920000 + 108894 = 2028894 bytes +// +// Or kjvbible-random.txt = 4332506 bytes, but minimum size of its +// 99817 lines in memory * 96 bytes = 9582432 bytes +// +// Here, we test 108894 bytes with a 50K buffer +// #[test] fn test_larger_than_specified_segment() { new_ucmd!() .arg("-n") - .arg("-S 100") - .arg("numeric_unsorted_ints.txt") + .arg("-S 50K") + .arg("ext_sort.txt") .succeeds() - .stdout_is_fixture(format!("{}", "numeric_unsorted_ints.expected")); + .stdout_is_fixture(format!("{}", "ext_sort.expected")); } #[test] @@ -202,17 +216,6 @@ fn test_non_printing_chars() { } } -#[test] -fn test_exponents_positive_general_fixed() { - for exponents_positive_general_param in vec!["-g"] { - new_ucmd!() - .pipe_in("100E6\n\n50e10\n+100000\n\n10000K78\n10E\n\n\n1000EDKLD\n\n\n100E6\n\n50e10\n+100000\n\n") - .arg(exponents_positive_general_param) - .succeeds() - .stdout_only("\n\n\n\n\n\n\n\n10000K78\n1000EDKLD\n10E\n+100000\n+100000\n100E6\n100E6\n50e10\n50e10\n"); - } -} - #[test] fn test_exponents_positive_numeric() { test_helper("exponents-positive-numeric", "-n"); diff --git a/tests/fixtures/sort/ext_sort.expected b/tests/fixtures/sort/ext_sort.expected new file mode 100644 index 000000000..7599e0c96 --- /dev/null +++ b/tests/fixtures/sort/ext_sort.expected @@ -0,0 +1,20000 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499 +500 +501 +502 +503 +504 +505 +506 +507 +508 +509 +510 +511 +512 +513 +514 +515 +516 +517 +518 +519 +520 +521 +522 +523 +524 +525 +526 +527 +528 +529 +530 +531 +532 +533 +534 +535 +536 +537 +538 +539 +540 +541 +542 +543 +544 +545 +546 +547 +548 +549 +550 +551 +552 +553 +554 +555 +556 +557 +558 +559 +560 +561 +562 +563 +564 +565 +566 +567 +568 +569 +570 +571 +572 +573 +574 +575 +576 +577 +578 +579 +580 +581 +582 +583 +584 +585 +586 +587 +588 +589 +590 +591 +592 +593 +594 +595 +596 +597 +598 +599 +600 +601 +602 +603 +604 +605 +606 +607 +608 +609 +610 +611 +612 +613 +614 +615 +616 +617 +618 +619 +620 +621 +622 +623 +624 +625 +626 +627 +628 +629 +630 +631 +632 +633 +634 +635 +636 +637 +638 +639 +640 +641 +642 +643 +644 +645 +646 +647 +648 +649 +650 +651 +652 +653 +654 +655 +656 +657 +658 +659 +660 +661 +662 +663 +664 +665 +666 +667 +668 +669 +670 +671 +672 +673 +674 +675 +676 +677 +678 +679 +680 +681 +682 +683 +684 +685 +686 +687 +688 +689 +690 +691 +692 +693 +694 +695 +696 +697 +698 +699 +700 +701 +702 +703 +704 +705 +706 +707 +708 +709 +710 +711 +712 +713 +714 +715 +716 +717 +718 +719 +720 +721 +722 +723 +724 +725 +726 +727 +728 +729 +730 +731 +732 +733 +734 +735 +736 +737 +738 +739 +740 +741 +742 +743 +744 +745 +746 +747 +748 +749 +750 +751 +752 +753 +754 +755 +756 +757 +758 +759 +760 +761 +762 +763 +764 +765 +766 +767 +768 +769 +770 +771 +772 +773 +774 +775 +776 +777 +778 +779 +780 +781 +782 +783 +784 +785 +786 +787 +788 +789 +790 +791 +792 +793 +794 +795 +796 +797 +798 +799 +800 +801 +802 +803 +804 +805 +806 +807 +808 +809 +810 +811 +812 +813 +814 +815 +816 +817 +818 +819 +820 +821 +822 +823 +824 +825 +826 +827 +828 +829 +830 +831 +832 +833 +834 +835 +836 +837 +838 +839 +840 +841 +842 +843 +844 +845 +846 +847 +848 +849 +850 +851 +852 +853 +854 +855 +856 +857 +858 +859 +860 +861 +862 +863 +864 +865 +866 +867 +868 +869 +870 +871 +872 +873 +874 +875 +876 +877 +878 +879 +880 +881 +882 +883 +884 +885 +886 +887 +888 +889 +890 +891 +892 +893 +894 +895 +896 +897 +898 +899 +900 +901 +902 +903 +904 +905 +906 +907 +908 +909 +910 +911 +912 +913 +914 +915 +916 +917 +918 +919 +920 +921 +922 +923 +924 +925 +926 +927 +928 +929 +930 +931 +932 +933 +934 +935 +936 +937 +938 +939 +940 +941 +942 +943 +944 +945 +946 +947 +948 +949 +950 +951 +952 +953 +954 +955 +956 +957 +958 +959 +960 +961 +962 +963 +964 +965 +966 +967 +968 +969 +970 +971 +972 +973 +974 +975 +976 +977 +978 +979 +980 +981 +982 +983 +984 +985 +986 +987 +988 +989 +990 +991 +992 +993 +994 +995 +996 +997 +998 +999 +1000 +1001 +1002 +1003 +1004 +1005 +1006 +1007 +1008 +1009 +1010 +1011 +1012 +1013 +1014 +1015 +1016 +1017 +1018 +1019 +1020 +1021 +1022 +1023 +1024 +1025 +1026 +1027 +1028 +1029 +1030 +1031 +1032 +1033 +1034 +1035 +1036 +1037 +1038 +1039 +1040 +1041 +1042 +1043 +1044 +1045 +1046 +1047 +1048 +1049 +1050 +1051 +1052 +1053 +1054 +1055 +1056 +1057 +1058 +1059 +1060 +1061 +1062 +1063 +1064 +1065 +1066 +1067 +1068 +1069 +1070 +1071 +1072 +1073 +1074 +1075 +1076 +1077 +1078 +1079 +1080 +1081 +1082 +1083 +1084 +1085 +1086 +1087 +1088 +1089 +1090 +1091 +1092 +1093 +1094 +1095 +1096 +1097 +1098 +1099 +1100 +1101 +1102 +1103 +1104 +1105 +1106 +1107 +1108 +1109 +1110 +1111 +1112 +1113 +1114 +1115 +1116 +1117 +1118 +1119 +1120 +1121 +1122 +1123 +1124 +1125 +1126 +1127 +1128 +1129 +1130 +1131 +1132 +1133 +1134 +1135 +1136 +1137 +1138 +1139 +1140 +1141 +1142 +1143 +1144 +1145 +1146 +1147 +1148 +1149 +1150 +1151 +1152 +1153 +1154 +1155 +1156 +1157 +1158 +1159 +1160 +1161 +1162 +1163 +1164 +1165 +1166 +1167 +1168 +1169 +1170 +1171 +1172 +1173 +1174 +1175 +1176 +1177 +1178 +1179 +1180 +1181 +1182 +1183 +1184 +1185 +1186 +1187 +1188 +1189 +1190 +1191 +1192 +1193 +1194 +1195 +1196 +1197 +1198 +1199 +1200 +1201 +1202 +1203 +1204 +1205 +1206 +1207 +1208 +1209 +1210 +1211 +1212 +1213 +1214 +1215 +1216 +1217 +1218 +1219 +1220 +1221 +1222 +1223 +1224 +1225 +1226 +1227 +1228 +1229 +1230 +1231 +1232 +1233 +1234 +1235 +1236 +1237 +1238 +1239 +1240 +1241 +1242 +1243 +1244 +1245 +1246 +1247 +1248 +1249 +1250 +1251 +1252 +1253 +1254 +1255 +1256 +1257 +1258 +1259 +1260 +1261 +1262 +1263 +1264 +1265 +1266 +1267 +1268 +1269 +1270 +1271 +1272 +1273 +1274 +1275 +1276 +1277 +1278 +1279 +1280 +1281 +1282 +1283 +1284 +1285 +1286 +1287 +1288 +1289 +1290 +1291 +1292 +1293 +1294 +1295 +1296 +1297 +1298 +1299 +1300 +1301 +1302 +1303 +1304 +1305 +1306 +1307 +1308 +1309 +1310 +1311 +1312 +1313 +1314 +1315 +1316 +1317 +1318 +1319 +1320 +1321 +1322 +1323 +1324 +1325 +1326 +1327 +1328 +1329 +1330 +1331 +1332 +1333 +1334 +1335 +1336 +1337 +1338 +1339 +1340 +1341 +1342 +1343 +1344 +1345 +1346 +1347 +1348 +1349 +1350 +1351 +1352 +1353 +1354 +1355 +1356 +1357 +1358 +1359 +1360 +1361 +1362 +1363 +1364 +1365 +1366 +1367 +1368 +1369 +1370 +1371 +1372 +1373 +1374 +1375 +1376 +1377 +1378 +1379 +1380 +1381 +1382 +1383 +1384 +1385 +1386 +1387 +1388 +1389 +1390 +1391 +1392 +1393 +1394 +1395 +1396 +1397 +1398 +1399 +1400 +1401 +1402 +1403 +1404 +1405 +1406 +1407 +1408 +1409 +1410 +1411 +1412 +1413 +1414 +1415 +1416 +1417 +1418 +1419 +1420 +1421 +1422 +1423 +1424 +1425 +1426 +1427 +1428 +1429 +1430 +1431 +1432 +1433 +1434 +1435 +1436 +1437 +1438 +1439 +1440 +1441 +1442 +1443 +1444 +1445 +1446 +1447 +1448 +1449 +1450 +1451 +1452 +1453 +1454 +1455 +1456 +1457 +1458 +1459 +1460 +1461 +1462 +1463 +1464 +1465 +1466 +1467 +1468 +1469 +1470 +1471 +1472 +1473 +1474 +1475 +1476 +1477 +1478 +1479 +1480 +1481 +1482 +1483 +1484 +1485 +1486 +1487 +1488 +1489 +1490 +1491 +1492 +1493 +1494 +1495 +1496 +1497 +1498 +1499 +1500 +1501 +1502 +1503 +1504 +1505 +1506 +1507 +1508 +1509 +1510 +1511 +1512 +1513 +1514 +1515 +1516 +1517 +1518 +1519 +1520 +1521 +1522 +1523 +1524 +1525 +1526 +1527 +1528 +1529 +1530 +1531 +1532 +1533 +1534 +1535 +1536 +1537 +1538 +1539 +1540 +1541 +1542 +1543 +1544 +1545 +1546 +1547 +1548 +1549 +1550 +1551 +1552 +1553 +1554 +1555 +1556 +1557 +1558 +1559 +1560 +1561 +1562 +1563 +1564 +1565 +1566 +1567 +1568 +1569 +1570 +1571 +1572 +1573 +1574 +1575 +1576 +1577 +1578 +1579 +1580 +1581 +1582 +1583 +1584 +1585 +1586 +1587 +1588 +1589 +1590 +1591 +1592 +1593 +1594 +1595 +1596 +1597 +1598 +1599 +1600 +1601 +1602 +1603 +1604 +1605 +1606 +1607 +1608 +1609 +1610 +1611 +1612 +1613 +1614 +1615 +1616 +1617 +1618 +1619 +1620 +1621 +1622 +1623 +1624 +1625 +1626 +1627 +1628 +1629 +1630 +1631 +1632 +1633 +1634 +1635 +1636 +1637 +1638 +1639 +1640 +1641 +1642 +1643 +1644 +1645 +1646 +1647 +1648 +1649 +1650 +1651 +1652 +1653 +1654 +1655 +1656 +1657 +1658 +1659 +1660 +1661 +1662 +1663 +1664 +1665 +1666 +1667 +1668 +1669 +1670 +1671 +1672 +1673 +1674 +1675 +1676 +1677 +1678 +1679 +1680 +1681 +1682 +1683 +1684 +1685 +1686 +1687 +1688 +1689 +1690 +1691 +1692 +1693 +1694 +1695 +1696 +1697 +1698 +1699 +1700 +1701 +1702 +1703 +1704 +1705 +1706 +1707 +1708 +1709 +1710 +1711 +1712 +1713 +1714 +1715 +1716 +1717 +1718 +1719 +1720 +1721 +1722 +1723 +1724 +1725 +1726 +1727 +1728 +1729 +1730 +1731 +1732 +1733 +1734 +1735 +1736 +1737 +1738 +1739 +1740 +1741 +1742 +1743 +1744 +1745 +1746 +1747 +1748 +1749 +1750 +1751 +1752 +1753 +1754 +1755 +1756 +1757 +1758 +1759 +1760 +1761 +1762 +1763 +1764 +1765 +1766 +1767 +1768 +1769 +1770 +1771 +1772 +1773 +1774 +1775 +1776 +1777 +1778 +1779 +1780 +1781 +1782 +1783 +1784 +1785 +1786 +1787 +1788 +1789 +1790 +1791 +1792 +1793 +1794 +1795 +1796 +1797 +1798 +1799 +1800 +1801 +1802 +1803 +1804 +1805 +1806 +1807 +1808 +1809 +1810 +1811 +1812 +1813 +1814 +1815 +1816 +1817 +1818 +1819 +1820 +1821 +1822 +1823 +1824 +1825 +1826 +1827 +1828 +1829 +1830 +1831 +1832 +1833 +1834 +1835 +1836 +1837 +1838 +1839 +1840 +1841 +1842 +1843 +1844 +1845 +1846 +1847 +1848 +1849 +1850 +1851 +1852 +1853 +1854 +1855 +1856 +1857 +1858 +1859 +1860 +1861 +1862 +1863 +1864 +1865 +1866 +1867 +1868 +1869 +1870 +1871 +1872 +1873 +1874 +1875 +1876 +1877 +1878 +1879 +1880 +1881 +1882 +1883 +1884 +1885 +1886 +1887 +1888 +1889 +1890 +1891 +1892 +1893 +1894 +1895 +1896 +1897 +1898 +1899 +1900 +1901 +1902 +1903 +1904 +1905 +1906 +1907 +1908 +1909 +1910 +1911 +1912 +1913 +1914 +1915 +1916 +1917 +1918 +1919 +1920 +1921 +1922 +1923 +1924 +1925 +1926 +1927 +1928 +1929 +1930 +1931 +1932 +1933 +1934 +1935 +1936 +1937 +1938 +1939 +1940 +1941 +1942 +1943 +1944 +1945 +1946 +1947 +1948 +1949 +1950 +1951 +1952 +1953 +1954 +1955 +1956 +1957 +1958 +1959 +1960 +1961 +1962 +1963 +1964 +1965 +1966 +1967 +1968 +1969 +1970 +1971 +1972 +1973 +1974 +1975 +1976 +1977 +1978 +1979 +1980 +1981 +1982 +1983 +1984 +1985 +1986 +1987 +1988 +1989 +1990 +1991 +1992 +1993 +1994 +1995 +1996 +1997 +1998 +1999 +2000 +2001 +2002 +2003 +2004 +2005 +2006 +2007 +2008 +2009 +2010 +2011 +2012 +2013 +2014 +2015 +2016 +2017 +2018 +2019 +2020 +2021 +2022 +2023 +2024 +2025 +2026 +2027 +2028 +2029 +2030 +2031 +2032 +2033 +2034 +2035 +2036 +2037 +2038 +2039 +2040 +2041 +2042 +2043 +2044 +2045 +2046 +2047 +2048 +2049 +2050 +2051 +2052 +2053 +2054 +2055 +2056 +2057 +2058 +2059 +2060 +2061 +2062 +2063 +2064 +2065 +2066 +2067 +2068 +2069 +2070 +2071 +2072 +2073 +2074 +2075 +2076 +2077 +2078 +2079 +2080 +2081 +2082 +2083 +2084 +2085 +2086 +2087 +2088 +2089 +2090 +2091 +2092 +2093 +2094 +2095 +2096 +2097 +2098 +2099 +2100 +2101 +2102 +2103 +2104 +2105 +2106 +2107 +2108 +2109 +2110 +2111 +2112 +2113 +2114 +2115 +2116 +2117 +2118 +2119 +2120 +2121 +2122 +2123 +2124 +2125 +2126 +2127 +2128 +2129 +2130 +2131 +2132 +2133 +2134 +2135 +2136 +2137 +2138 +2139 +2140 +2141 +2142 +2143 +2144 +2145 +2146 +2147 +2148 +2149 +2150 +2151 +2152 +2153 +2154 +2155 +2156 +2157 +2158 +2159 +2160 +2161 +2162 +2163 +2164 +2165 +2166 +2167 +2168 +2169 +2170 +2171 +2172 +2173 +2174 +2175 +2176 +2177 +2178 +2179 +2180 +2181 +2182 +2183 +2184 +2185 +2186 +2187 +2188 +2189 +2190 +2191 +2192 +2193 +2194 +2195 +2196 +2197 +2198 +2199 +2200 +2201 +2202 +2203 +2204 +2205 +2206 +2207 +2208 +2209 +2210 +2211 +2212 +2213 +2214 +2215 +2216 +2217 +2218 +2219 +2220 +2221 +2222 +2223 +2224 +2225 +2226 +2227 +2228 +2229 +2230 +2231 +2232 +2233 +2234 +2235 +2236 +2237 +2238 +2239 +2240 +2241 +2242 +2243 +2244 +2245 +2246 +2247 +2248 +2249 +2250 +2251 +2252 +2253 +2254 +2255 +2256 +2257 +2258 +2259 +2260 +2261 +2262 +2263 +2264 +2265 +2266 +2267 +2268 +2269 +2270 +2271 +2272 +2273 +2274 +2275 +2276 +2277 +2278 +2279 +2280 +2281 +2282 +2283 +2284 +2285 +2286 +2287 +2288 +2289 +2290 +2291 +2292 +2293 +2294 +2295 +2296 +2297 +2298 +2299 +2300 +2301 +2302 +2303 +2304 +2305 +2306 +2307 +2308 +2309 +2310 +2311 +2312 +2313 +2314 +2315 +2316 +2317 +2318 +2319 +2320 +2321 +2322 +2323 +2324 +2325 +2326 +2327 +2328 +2329 +2330 +2331 +2332 +2333 +2334 +2335 +2336 +2337 +2338 +2339 +2340 +2341 +2342 +2343 +2344 +2345 +2346 +2347 +2348 +2349 +2350 +2351 +2352 +2353 +2354 +2355 +2356 +2357 +2358 +2359 +2360 +2361 +2362 +2363 +2364 +2365 +2366 +2367 +2368 +2369 +2370 +2371 +2372 +2373 +2374 +2375 +2376 +2377 +2378 +2379 +2380 +2381 +2382 +2383 +2384 +2385 +2386 +2387 +2388 +2389 +2390 +2391 +2392 +2393 +2394 +2395 +2396 +2397 +2398 +2399 +2400 +2401 +2402 +2403 +2404 +2405 +2406 +2407 +2408 +2409 +2410 +2411 +2412 +2413 +2414 +2415 +2416 +2417 +2418 +2419 +2420 +2421 +2422 +2423 +2424 +2425 +2426 +2427 +2428 +2429 +2430 +2431 +2432 +2433 +2434 +2435 +2436 +2437 +2438 +2439 +2440 +2441 +2442 +2443 +2444 +2445 +2446 +2447 +2448 +2449 +2450 +2451 +2452 +2453 +2454 +2455 +2456 +2457 +2458 +2459 +2460 +2461 +2462 +2463 +2464 +2465 +2466 +2467 +2468 +2469 +2470 +2471 +2472 +2473 +2474 +2475 +2476 +2477 +2478 +2479 +2480 +2481 +2482 +2483 +2484 +2485 +2486 +2487 +2488 +2489 +2490 +2491 +2492 +2493 +2494 +2495 +2496 +2497 +2498 +2499 +2500 +2501 +2502 +2503 +2504 +2505 +2506 +2507 +2508 +2509 +2510 +2511 +2512 +2513 +2514 +2515 +2516 +2517 +2518 +2519 +2520 +2521 +2522 +2523 +2524 +2525 +2526 +2527 +2528 +2529 +2530 +2531 +2532 +2533 +2534 +2535 +2536 +2537 +2538 +2539 +2540 +2541 +2542 +2543 +2544 +2545 +2546 +2547 +2548 +2549 +2550 +2551 +2552 +2553 +2554 +2555 +2556 +2557 +2558 +2559 +2560 +2561 +2562 +2563 +2564 +2565 +2566 +2567 +2568 +2569 +2570 +2571 +2572 +2573 +2574 +2575 +2576 +2577 +2578 +2579 +2580 +2581 +2582 +2583 +2584 +2585 +2586 +2587 +2588 +2589 +2590 +2591 +2592 +2593 +2594 +2595 +2596 +2597 +2598 +2599 +2600 +2601 +2602 +2603 +2604 +2605 +2606 +2607 +2608 +2609 +2610 +2611 +2612 +2613 +2614 +2615 +2616 +2617 +2618 +2619 +2620 +2621 +2622 +2623 +2624 +2625 +2626 +2627 +2628 +2629 +2630 +2631 +2632 +2633 +2634 +2635 +2636 +2637 +2638 +2639 +2640 +2641 +2642 +2643 +2644 +2645 +2646 +2647 +2648 +2649 +2650 +2651 +2652 +2653 +2654 +2655 +2656 +2657 +2658 +2659 +2660 +2661 +2662 +2663 +2664 +2665 +2666 +2667 +2668 +2669 +2670 +2671 +2672 +2673 +2674 +2675 +2676 +2677 +2678 +2679 +2680 +2681 +2682 +2683 +2684 +2685 +2686 +2687 +2688 +2689 +2690 +2691 +2692 +2693 +2694 +2695 +2696 +2697 +2698 +2699 +2700 +2701 +2702 +2703 +2704 +2705 +2706 +2707 +2708 +2709 +2710 +2711 +2712 +2713 +2714 +2715 +2716 +2717 +2718 +2719 +2720 +2721 +2722 +2723 +2724 +2725 +2726 +2727 +2728 +2729 +2730 +2731 +2732 +2733 +2734 +2735 +2736 +2737 +2738 +2739 +2740 +2741 +2742 +2743 +2744 +2745 +2746 +2747 +2748 +2749 +2750 +2751 +2752 +2753 +2754 +2755 +2756 +2757 +2758 +2759 +2760 +2761 +2762 +2763 +2764 +2765 +2766 +2767 +2768 +2769 +2770 +2771 +2772 +2773 +2774 +2775 +2776 +2777 +2778 +2779 +2780 +2781 +2782 +2783 +2784 +2785 +2786 +2787 +2788 +2789 +2790 +2791 +2792 +2793 +2794 +2795 +2796 +2797 +2798 +2799 +2800 +2801 +2802 +2803 +2804 +2805 +2806 +2807 +2808 +2809 +2810 +2811 +2812 +2813 +2814 +2815 +2816 +2817 +2818 +2819 +2820 +2821 +2822 +2823 +2824 +2825 +2826 +2827 +2828 +2829 +2830 +2831 +2832 +2833 +2834 +2835 +2836 +2837 +2838 +2839 +2840 +2841 +2842 +2843 +2844 +2845 +2846 +2847 +2848 +2849 +2850 +2851 +2852 +2853 +2854 +2855 +2856 +2857 +2858 +2859 +2860 +2861 +2862 +2863 +2864 +2865 +2866 +2867 +2868 +2869 +2870 +2871 +2872 +2873 +2874 +2875 +2876 +2877 +2878 +2879 +2880 +2881 +2882 +2883 +2884 +2885 +2886 +2887 +2888 +2889 +2890 +2891 +2892 +2893 +2894 +2895 +2896 +2897 +2898 +2899 +2900 +2901 +2902 +2903 +2904 +2905 +2906 +2907 +2908 +2909 +2910 +2911 +2912 +2913 +2914 +2915 +2916 +2917 +2918 +2919 +2920 +2921 +2922 +2923 +2924 +2925 +2926 +2927 +2928 +2929 +2930 +2931 +2932 +2933 +2934 +2935 +2936 +2937 +2938 +2939 +2940 +2941 +2942 +2943 +2944 +2945 +2946 +2947 +2948 +2949 +2950 +2951 +2952 +2953 +2954 +2955 +2956 +2957 +2958 +2959 +2960 +2961 +2962 +2963 +2964 +2965 +2966 +2967 +2968 +2969 +2970 +2971 +2972 +2973 +2974 +2975 +2976 +2977 +2978 +2979 +2980 +2981 +2982 +2983 +2984 +2985 +2986 +2987 +2988 +2989 +2990 +2991 +2992 +2993 +2994 +2995 +2996 +2997 +2998 +2999 +3000 +3001 +3002 +3003 +3004 +3005 +3006 +3007 +3008 +3009 +3010 +3011 +3012 +3013 +3014 +3015 +3016 +3017 +3018 +3019 +3020 +3021 +3022 +3023 +3024 +3025 +3026 +3027 +3028 +3029 +3030 +3031 +3032 +3033 +3034 +3035 +3036 +3037 +3038 +3039 +3040 +3041 +3042 +3043 +3044 +3045 +3046 +3047 +3048 +3049 +3050 +3051 +3052 +3053 +3054 +3055 +3056 +3057 +3058 +3059 +3060 +3061 +3062 +3063 +3064 +3065 +3066 +3067 +3068 +3069 +3070 +3071 +3072 +3073 +3074 +3075 +3076 +3077 +3078 +3079 +3080 +3081 +3082 +3083 +3084 +3085 +3086 +3087 +3088 +3089 +3090 +3091 +3092 +3093 +3094 +3095 +3096 +3097 +3098 +3099 +3100 +3101 +3102 +3103 +3104 +3105 +3106 +3107 +3108 +3109 +3110 +3111 +3112 +3113 +3114 +3115 +3116 +3117 +3118 +3119 +3120 +3121 +3122 +3123 +3124 +3125 +3126 +3127 +3128 +3129 +3130 +3131 +3132 +3133 +3134 +3135 +3136 +3137 +3138 +3139 +3140 +3141 +3142 +3143 +3144 +3145 +3146 +3147 +3148 +3149 +3150 +3151 +3152 +3153 +3154 +3155 +3156 +3157 +3158 +3159 +3160 +3161 +3162 +3163 +3164 +3165 +3166 +3167 +3168 +3169 +3170 +3171 +3172 +3173 +3174 +3175 +3176 +3177 +3178 +3179 +3180 +3181 +3182 +3183 +3184 +3185 +3186 +3187 +3188 +3189 +3190 +3191 +3192 +3193 +3194 +3195 +3196 +3197 +3198 +3199 +3200 +3201 +3202 +3203 +3204 +3205 +3206 +3207 +3208 +3209 +3210 +3211 +3212 +3213 +3214 +3215 +3216 +3217 +3218 +3219 +3220 +3221 +3222 +3223 +3224 +3225 +3226 +3227 +3228 +3229 +3230 +3231 +3232 +3233 +3234 +3235 +3236 +3237 +3238 +3239 +3240 +3241 +3242 +3243 +3244 +3245 +3246 +3247 +3248 +3249 +3250 +3251 +3252 +3253 +3254 +3255 +3256 +3257 +3258 +3259 +3260 +3261 +3262 +3263 +3264 +3265 +3266 +3267 +3268 +3269 +3270 +3271 +3272 +3273 +3274 +3275 +3276 +3277 +3278 +3279 +3280 +3281 +3282 +3283 +3284 +3285 +3286 +3287 +3288 +3289 +3290 +3291 +3292 +3293 +3294 +3295 +3296 +3297 +3298 +3299 +3300 +3301 +3302 +3303 +3304 +3305 +3306 +3307 +3308 +3309 +3310 +3311 +3312 +3313 +3314 +3315 +3316 +3317 +3318 +3319 +3320 +3321 +3322 +3323 +3324 +3325 +3326 +3327 +3328 +3329 +3330 +3331 +3332 +3333 +3334 +3335 +3336 +3337 +3338 +3339 +3340 +3341 +3342 +3343 +3344 +3345 +3346 +3347 +3348 +3349 +3350 +3351 +3352 +3353 +3354 +3355 +3356 +3357 +3358 +3359 +3360 +3361 +3362 +3363 +3364 +3365 +3366 +3367 +3368 +3369 +3370 +3371 +3372 +3373 +3374 +3375 +3376 +3377 +3378 +3379 +3380 +3381 +3382 +3383 +3384 +3385 +3386 +3387 +3388 +3389 +3390 +3391 +3392 +3393 +3394 +3395 +3396 +3397 +3398 +3399 +3400 +3401 +3402 +3403 +3404 +3405 +3406 +3407 +3408 +3409 +3410 +3411 +3412 +3413 +3414 +3415 +3416 +3417 +3418 +3419 +3420 +3421 +3422 +3423 +3424 +3425 +3426 +3427 +3428 +3429 +3430 +3431 +3432 +3433 +3434 +3435 +3436 +3437 +3438 +3439 +3440 +3441 +3442 +3443 +3444 +3445 +3446 +3447 +3448 +3449 +3450 +3451 +3452 +3453 +3454 +3455 +3456 +3457 +3458 +3459 +3460 +3461 +3462 +3463 +3464 +3465 +3466 +3467 +3468 +3469 +3470 +3471 +3472 +3473 +3474 +3475 +3476 +3477 +3478 +3479 +3480 +3481 +3482 +3483 +3484 +3485 +3486 +3487 +3488 +3489 +3490 +3491 +3492 +3493 +3494 +3495 +3496 +3497 +3498 +3499 +3500 +3501 +3502 +3503 +3504 +3505 +3506 +3507 +3508 +3509 +3510 +3511 +3512 +3513 +3514 +3515 +3516 +3517 +3518 +3519 +3520 +3521 +3522 +3523 +3524 +3525 +3526 +3527 +3528 +3529 +3530 +3531 +3532 +3533 +3534 +3535 +3536 +3537 +3538 +3539 +3540 +3541 +3542 +3543 +3544 +3545 +3546 +3547 +3548 +3549 +3550 +3551 +3552 +3553 +3554 +3555 +3556 +3557 +3558 +3559 +3560 +3561 +3562 +3563 +3564 +3565 +3566 +3567 +3568 +3569 +3570 +3571 +3572 +3573 +3574 +3575 +3576 +3577 +3578 +3579 +3580 +3581 +3582 +3583 +3584 +3585 +3586 +3587 +3588 +3589 +3590 +3591 +3592 +3593 +3594 +3595 +3596 +3597 +3598 +3599 +3600 +3601 +3602 +3603 +3604 +3605 +3606 +3607 +3608 +3609 +3610 +3611 +3612 +3613 +3614 +3615 +3616 +3617 +3618 +3619 +3620 +3621 +3622 +3623 +3624 +3625 +3626 +3627 +3628 +3629 +3630 +3631 +3632 +3633 +3634 +3635 +3636 +3637 +3638 +3639 +3640 +3641 +3642 +3643 +3644 +3645 +3646 +3647 +3648 +3649 +3650 +3651 +3652 +3653 +3654 +3655 +3656 +3657 +3658 +3659 +3660 +3661 +3662 +3663 +3664 +3665 +3666 +3667 +3668 +3669 +3670 +3671 +3672 +3673 +3674 +3675 +3676 +3677 +3678 +3679 +3680 +3681 +3682 +3683 +3684 +3685 +3686 +3687 +3688 +3689 +3690 +3691 +3692 +3693 +3694 +3695 +3696 +3697 +3698 +3699 +3700 +3701 +3702 +3703 +3704 +3705 +3706 +3707 +3708 +3709 +3710 +3711 +3712 +3713 +3714 +3715 +3716 +3717 +3718 +3719 +3720 +3721 +3722 +3723 +3724 +3725 +3726 +3727 +3728 +3729 +3730 +3731 +3732 +3733 +3734 +3735 +3736 +3737 +3738 +3739 +3740 +3741 +3742 +3743 +3744 +3745 +3746 +3747 +3748 +3749 +3750 +3751 +3752 +3753 +3754 +3755 +3756 +3757 +3758 +3759 +3760 +3761 +3762 +3763 +3764 +3765 +3766 +3767 +3768 +3769 +3770 +3771 +3772 +3773 +3774 +3775 +3776 +3777 +3778 +3779 +3780 +3781 +3782 +3783 +3784 +3785 +3786 +3787 +3788 +3789 +3790 +3791 +3792 +3793 +3794 +3795 +3796 +3797 +3798 +3799 +3800 +3801 +3802 +3803 +3804 +3805 +3806 +3807 +3808 +3809 +3810 +3811 +3812 +3813 +3814 +3815 +3816 +3817 +3818 +3819 +3820 +3821 +3822 +3823 +3824 +3825 +3826 +3827 +3828 +3829 +3830 +3831 +3832 +3833 +3834 +3835 +3836 +3837 +3838 +3839 +3840 +3841 +3842 +3843 +3844 +3845 +3846 +3847 +3848 +3849 +3850 +3851 +3852 +3853 +3854 +3855 +3856 +3857 +3858 +3859 +3860 +3861 +3862 +3863 +3864 +3865 +3866 +3867 +3868 +3869 +3870 +3871 +3872 +3873 +3874 +3875 +3876 +3877 +3878 +3879 +3880 +3881 +3882 +3883 +3884 +3885 +3886 +3887 +3888 +3889 +3890 +3891 +3892 +3893 +3894 +3895 +3896 +3897 +3898 +3899 +3900 +3901 +3902 +3903 +3904 +3905 +3906 +3907 +3908 +3909 +3910 +3911 +3912 +3913 +3914 +3915 +3916 +3917 +3918 +3919 +3920 +3921 +3922 +3923 +3924 +3925 +3926 +3927 +3928 +3929 +3930 +3931 +3932 +3933 +3934 +3935 +3936 +3937 +3938 +3939 +3940 +3941 +3942 +3943 +3944 +3945 +3946 +3947 +3948 +3949 +3950 +3951 +3952 +3953 +3954 +3955 +3956 +3957 +3958 +3959 +3960 +3961 +3962 +3963 +3964 +3965 +3966 +3967 +3968 +3969 +3970 +3971 +3972 +3973 +3974 +3975 +3976 +3977 +3978 +3979 +3980 +3981 +3982 +3983 +3984 +3985 +3986 +3987 +3988 +3989 +3990 +3991 +3992 +3993 +3994 +3995 +3996 +3997 +3998 +3999 +4000 +4001 +4002 +4003 +4004 +4005 +4006 +4007 +4008 +4009 +4010 +4011 +4012 +4013 +4014 +4015 +4016 +4017 +4018 +4019 +4020 +4021 +4022 +4023 +4024 +4025 +4026 +4027 +4028 +4029 +4030 +4031 +4032 +4033 +4034 +4035 +4036 +4037 +4038 +4039 +4040 +4041 +4042 +4043 +4044 +4045 +4046 +4047 +4048 +4049 +4050 +4051 +4052 +4053 +4054 +4055 +4056 +4057 +4058 +4059 +4060 +4061 +4062 +4063 +4064 +4065 +4066 +4067 +4068 +4069 +4070 +4071 +4072 +4073 +4074 +4075 +4076 +4077 +4078 +4079 +4080 +4081 +4082 +4083 +4084 +4085 +4086 +4087 +4088 +4089 +4090 +4091 +4092 +4093 +4094 +4095 +4096 +4097 +4098 +4099 +4100 +4101 +4102 +4103 +4104 +4105 +4106 +4107 +4108 +4109 +4110 +4111 +4112 +4113 +4114 +4115 +4116 +4117 +4118 +4119 +4120 +4121 +4122 +4123 +4124 +4125 +4126 +4127 +4128 +4129 +4130 +4131 +4132 +4133 +4134 +4135 +4136 +4137 +4138 +4139 +4140 +4141 +4142 +4143 +4144 +4145 +4146 +4147 +4148 +4149 +4150 +4151 +4152 +4153 +4154 +4155 +4156 +4157 +4158 +4159 +4160 +4161 +4162 +4163 +4164 +4165 +4166 +4167 +4168 +4169 +4170 +4171 +4172 +4173 +4174 +4175 +4176 +4177 +4178 +4179 +4180 +4181 +4182 +4183 +4184 +4185 +4186 +4187 +4188 +4189 +4190 +4191 +4192 +4193 +4194 +4195 +4196 +4197 +4198 +4199 +4200 +4201 +4202 +4203 +4204 +4205 +4206 +4207 +4208 +4209 +4210 +4211 +4212 +4213 +4214 +4215 +4216 +4217 +4218 +4219 +4220 +4221 +4222 +4223 +4224 +4225 +4226 +4227 +4228 +4229 +4230 +4231 +4232 +4233 +4234 +4235 +4236 +4237 +4238 +4239 +4240 +4241 +4242 +4243 +4244 +4245 +4246 +4247 +4248 +4249 +4250 +4251 +4252 +4253 +4254 +4255 +4256 +4257 +4258 +4259 +4260 +4261 +4262 +4263 +4264 +4265 +4266 +4267 +4268 +4269 +4270 +4271 +4272 +4273 +4274 +4275 +4276 +4277 +4278 +4279 +4280 +4281 +4282 +4283 +4284 +4285 +4286 +4287 +4288 +4289 +4290 +4291 +4292 +4293 +4294 +4295 +4296 +4297 +4298 +4299 +4300 +4301 +4302 +4303 +4304 +4305 +4306 +4307 +4308 +4309 +4310 +4311 +4312 +4313 +4314 +4315 +4316 +4317 +4318 +4319 +4320 +4321 +4322 +4323 +4324 +4325 +4326 +4327 +4328 +4329 +4330 +4331 +4332 +4333 +4334 +4335 +4336 +4337 +4338 +4339 +4340 +4341 +4342 +4343 +4344 +4345 +4346 +4347 +4348 +4349 +4350 +4351 +4352 +4353 +4354 +4355 +4356 +4357 +4358 +4359 +4360 +4361 +4362 +4363 +4364 +4365 +4366 +4367 +4368 +4369 +4370 +4371 +4372 +4373 +4374 +4375 +4376 +4377 +4378 +4379 +4380 +4381 +4382 +4383 +4384 +4385 +4386 +4387 +4388 +4389 +4390 +4391 +4392 +4393 +4394 +4395 +4396 +4397 +4398 +4399 +4400 +4401 +4402 +4403 +4404 +4405 +4406 +4407 +4408 +4409 +4410 +4411 +4412 +4413 +4414 +4415 +4416 +4417 +4418 +4419 +4420 +4421 +4422 +4423 +4424 +4425 +4426 +4427 +4428 +4429 +4430 +4431 +4432 +4433 +4434 +4435 +4436 +4437 +4438 +4439 +4440 +4441 +4442 +4443 +4444 +4445 +4446 +4447 +4448 +4449 +4450 +4451 +4452 +4453 +4454 +4455 +4456 +4457 +4458 +4459 +4460 +4461 +4462 +4463 +4464 +4465 +4466 +4467 +4468 +4469 +4470 +4471 +4472 +4473 +4474 +4475 +4476 +4477 +4478 +4479 +4480 +4481 +4482 +4483 +4484 +4485 +4486 +4487 +4488 +4489 +4490 +4491 +4492 +4493 +4494 +4495 +4496 +4497 +4498 +4499 +4500 +4501 +4502 +4503 +4504 +4505 +4506 +4507 +4508 +4509 +4510 +4511 +4512 +4513 +4514 +4515 +4516 +4517 +4518 +4519 +4520 +4521 +4522 +4523 +4524 +4525 +4526 +4527 +4528 +4529 +4530 +4531 +4532 +4533 +4534 +4535 +4536 +4537 +4538 +4539 +4540 +4541 +4542 +4543 +4544 +4545 +4546 +4547 +4548 +4549 +4550 +4551 +4552 +4553 +4554 +4555 +4556 +4557 +4558 +4559 +4560 +4561 +4562 +4563 +4564 +4565 +4566 +4567 +4568 +4569 +4570 +4571 +4572 +4573 +4574 +4575 +4576 +4577 +4578 +4579 +4580 +4581 +4582 +4583 +4584 +4585 +4586 +4587 +4588 +4589 +4590 +4591 +4592 +4593 +4594 +4595 +4596 +4597 +4598 +4599 +4600 +4601 +4602 +4603 +4604 +4605 +4606 +4607 +4608 +4609 +4610 +4611 +4612 +4613 +4614 +4615 +4616 +4617 +4618 +4619 +4620 +4621 +4622 +4623 +4624 +4625 +4626 +4627 +4628 +4629 +4630 +4631 +4632 +4633 +4634 +4635 +4636 +4637 +4638 +4639 +4640 +4641 +4642 +4643 +4644 +4645 +4646 +4647 +4648 +4649 +4650 +4651 +4652 +4653 +4654 +4655 +4656 +4657 +4658 +4659 +4660 +4661 +4662 +4663 +4664 +4665 +4666 +4667 +4668 +4669 +4670 +4671 +4672 +4673 +4674 +4675 +4676 +4677 +4678 +4679 +4680 +4681 +4682 +4683 +4684 +4685 +4686 +4687 +4688 +4689 +4690 +4691 +4692 +4693 +4694 +4695 +4696 +4697 +4698 +4699 +4700 +4701 +4702 +4703 +4704 +4705 +4706 +4707 +4708 +4709 +4710 +4711 +4712 +4713 +4714 +4715 +4716 +4717 +4718 +4719 +4720 +4721 +4722 +4723 +4724 +4725 +4726 +4727 +4728 +4729 +4730 +4731 +4732 +4733 +4734 +4735 +4736 +4737 +4738 +4739 +4740 +4741 +4742 +4743 +4744 +4745 +4746 +4747 +4748 +4749 +4750 +4751 +4752 +4753 +4754 +4755 +4756 +4757 +4758 +4759 +4760 +4761 +4762 +4763 +4764 +4765 +4766 +4767 +4768 +4769 +4770 +4771 +4772 +4773 +4774 +4775 +4776 +4777 +4778 +4779 +4780 +4781 +4782 +4783 +4784 +4785 +4786 +4787 +4788 +4789 +4790 +4791 +4792 +4793 +4794 +4795 +4796 +4797 +4798 +4799 +4800 +4801 +4802 +4803 +4804 +4805 +4806 +4807 +4808 +4809 +4810 +4811 +4812 +4813 +4814 +4815 +4816 +4817 +4818 +4819 +4820 +4821 +4822 +4823 +4824 +4825 +4826 +4827 +4828 +4829 +4830 +4831 +4832 +4833 +4834 +4835 +4836 +4837 +4838 +4839 +4840 +4841 +4842 +4843 +4844 +4845 +4846 +4847 +4848 +4849 +4850 +4851 +4852 +4853 +4854 +4855 +4856 +4857 +4858 +4859 +4860 +4861 +4862 +4863 +4864 +4865 +4866 +4867 +4868 +4869 +4870 +4871 +4872 +4873 +4874 +4875 +4876 +4877 +4878 +4879 +4880 +4881 +4882 +4883 +4884 +4885 +4886 +4887 +4888 +4889 +4890 +4891 +4892 +4893 +4894 +4895 +4896 +4897 +4898 +4899 +4900 +4901 +4902 +4903 +4904 +4905 +4906 +4907 +4908 +4909 +4910 +4911 +4912 +4913 +4914 +4915 +4916 +4917 +4918 +4919 +4920 +4921 +4922 +4923 +4924 +4925 +4926 +4927 +4928 +4929 +4930 +4931 +4932 +4933 +4934 +4935 +4936 +4937 +4938 +4939 +4940 +4941 +4942 +4943 +4944 +4945 +4946 +4947 +4948 +4949 +4950 +4951 +4952 +4953 +4954 +4955 +4956 +4957 +4958 +4959 +4960 +4961 +4962 +4963 +4964 +4965 +4966 +4967 +4968 +4969 +4970 +4971 +4972 +4973 +4974 +4975 +4976 +4977 +4978 +4979 +4980 +4981 +4982 +4983 +4984 +4985 +4986 +4987 +4988 +4989 +4990 +4991 +4992 +4993 +4994 +4995 +4996 +4997 +4998 +4999 +5000 +5001 +5002 +5003 +5004 +5005 +5006 +5007 +5008 +5009 +5010 +5011 +5012 +5013 +5014 +5015 +5016 +5017 +5018 +5019 +5020 +5021 +5022 +5023 +5024 +5025 +5026 +5027 +5028 +5029 +5030 +5031 +5032 +5033 +5034 +5035 +5036 +5037 +5038 +5039 +5040 +5041 +5042 +5043 +5044 +5045 +5046 +5047 +5048 +5049 +5050 +5051 +5052 +5053 +5054 +5055 +5056 +5057 +5058 +5059 +5060 +5061 +5062 +5063 +5064 +5065 +5066 +5067 +5068 +5069 +5070 +5071 +5072 +5073 +5074 +5075 +5076 +5077 +5078 +5079 +5080 +5081 +5082 +5083 +5084 +5085 +5086 +5087 +5088 +5089 +5090 +5091 +5092 +5093 +5094 +5095 +5096 +5097 +5098 +5099 +5100 +5101 +5102 +5103 +5104 +5105 +5106 +5107 +5108 +5109 +5110 +5111 +5112 +5113 +5114 +5115 +5116 +5117 +5118 +5119 +5120 +5121 +5122 +5123 +5124 +5125 +5126 +5127 +5128 +5129 +5130 +5131 +5132 +5133 +5134 +5135 +5136 +5137 +5138 +5139 +5140 +5141 +5142 +5143 +5144 +5145 +5146 +5147 +5148 +5149 +5150 +5151 +5152 +5153 +5154 +5155 +5156 +5157 +5158 +5159 +5160 +5161 +5162 +5163 +5164 +5165 +5166 +5167 +5168 +5169 +5170 +5171 +5172 +5173 +5174 +5175 +5176 +5177 +5178 +5179 +5180 +5181 +5182 +5183 +5184 +5185 +5186 +5187 +5188 +5189 +5190 +5191 +5192 +5193 +5194 +5195 +5196 +5197 +5198 +5199 +5200 +5201 +5202 +5203 +5204 +5205 +5206 +5207 +5208 +5209 +5210 +5211 +5212 +5213 +5214 +5215 +5216 +5217 +5218 +5219 +5220 +5221 +5222 +5223 +5224 +5225 +5226 +5227 +5228 +5229 +5230 +5231 +5232 +5233 +5234 +5235 +5236 +5237 +5238 +5239 +5240 +5241 +5242 +5243 +5244 +5245 +5246 +5247 +5248 +5249 +5250 +5251 +5252 +5253 +5254 +5255 +5256 +5257 +5258 +5259 +5260 +5261 +5262 +5263 +5264 +5265 +5266 +5267 +5268 +5269 +5270 +5271 +5272 +5273 +5274 +5275 +5276 +5277 +5278 +5279 +5280 +5281 +5282 +5283 +5284 +5285 +5286 +5287 +5288 +5289 +5290 +5291 +5292 +5293 +5294 +5295 +5296 +5297 +5298 +5299 +5300 +5301 +5302 +5303 +5304 +5305 +5306 +5307 +5308 +5309 +5310 +5311 +5312 +5313 +5314 +5315 +5316 +5317 +5318 +5319 +5320 +5321 +5322 +5323 +5324 +5325 +5326 +5327 +5328 +5329 +5330 +5331 +5332 +5333 +5334 +5335 +5336 +5337 +5338 +5339 +5340 +5341 +5342 +5343 +5344 +5345 +5346 +5347 +5348 +5349 +5350 +5351 +5352 +5353 +5354 +5355 +5356 +5357 +5358 +5359 +5360 +5361 +5362 +5363 +5364 +5365 +5366 +5367 +5368 +5369 +5370 +5371 +5372 +5373 +5374 +5375 +5376 +5377 +5378 +5379 +5380 +5381 +5382 +5383 +5384 +5385 +5386 +5387 +5388 +5389 +5390 +5391 +5392 +5393 +5394 +5395 +5396 +5397 +5398 +5399 +5400 +5401 +5402 +5403 +5404 +5405 +5406 +5407 +5408 +5409 +5410 +5411 +5412 +5413 +5414 +5415 +5416 +5417 +5418 +5419 +5420 +5421 +5422 +5423 +5424 +5425 +5426 +5427 +5428 +5429 +5430 +5431 +5432 +5433 +5434 +5435 +5436 +5437 +5438 +5439 +5440 +5441 +5442 +5443 +5444 +5445 +5446 +5447 +5448 +5449 +5450 +5451 +5452 +5453 +5454 +5455 +5456 +5457 +5458 +5459 +5460 +5461 +5462 +5463 +5464 +5465 +5466 +5467 +5468 +5469 +5470 +5471 +5472 +5473 +5474 +5475 +5476 +5477 +5478 +5479 +5480 +5481 +5482 +5483 +5484 +5485 +5486 +5487 +5488 +5489 +5490 +5491 +5492 +5493 +5494 +5495 +5496 +5497 +5498 +5499 +5500 +5501 +5502 +5503 +5504 +5505 +5506 +5507 +5508 +5509 +5510 +5511 +5512 +5513 +5514 +5515 +5516 +5517 +5518 +5519 +5520 +5521 +5522 +5523 +5524 +5525 +5526 +5527 +5528 +5529 +5530 +5531 +5532 +5533 +5534 +5535 +5536 +5537 +5538 +5539 +5540 +5541 +5542 +5543 +5544 +5545 +5546 +5547 +5548 +5549 +5550 +5551 +5552 +5553 +5554 +5555 +5556 +5557 +5558 +5559 +5560 +5561 +5562 +5563 +5564 +5565 +5566 +5567 +5568 +5569 +5570 +5571 +5572 +5573 +5574 +5575 +5576 +5577 +5578 +5579 +5580 +5581 +5582 +5583 +5584 +5585 +5586 +5587 +5588 +5589 +5590 +5591 +5592 +5593 +5594 +5595 +5596 +5597 +5598 +5599 +5600 +5601 +5602 +5603 +5604 +5605 +5606 +5607 +5608 +5609 +5610 +5611 +5612 +5613 +5614 +5615 +5616 +5617 +5618 +5619 +5620 +5621 +5622 +5623 +5624 +5625 +5626 +5627 +5628 +5629 +5630 +5631 +5632 +5633 +5634 +5635 +5636 +5637 +5638 +5639 +5640 +5641 +5642 +5643 +5644 +5645 +5646 +5647 +5648 +5649 +5650 +5651 +5652 +5653 +5654 +5655 +5656 +5657 +5658 +5659 +5660 +5661 +5662 +5663 +5664 +5665 +5666 +5667 +5668 +5669 +5670 +5671 +5672 +5673 +5674 +5675 +5676 +5677 +5678 +5679 +5680 +5681 +5682 +5683 +5684 +5685 +5686 +5687 +5688 +5689 +5690 +5691 +5692 +5693 +5694 +5695 +5696 +5697 +5698 +5699 +5700 +5701 +5702 +5703 +5704 +5705 +5706 +5707 +5708 +5709 +5710 +5711 +5712 +5713 +5714 +5715 +5716 +5717 +5718 +5719 +5720 +5721 +5722 +5723 +5724 +5725 +5726 +5727 +5728 +5729 +5730 +5731 +5732 +5733 +5734 +5735 +5736 +5737 +5738 +5739 +5740 +5741 +5742 +5743 +5744 +5745 +5746 +5747 +5748 +5749 +5750 +5751 +5752 +5753 +5754 +5755 +5756 +5757 +5758 +5759 +5760 +5761 +5762 +5763 +5764 +5765 +5766 +5767 +5768 +5769 +5770 +5771 +5772 +5773 +5774 +5775 +5776 +5777 +5778 +5779 +5780 +5781 +5782 +5783 +5784 +5785 +5786 +5787 +5788 +5789 +5790 +5791 +5792 +5793 +5794 +5795 +5796 +5797 +5798 +5799 +5800 +5801 +5802 +5803 +5804 +5805 +5806 +5807 +5808 +5809 +5810 +5811 +5812 +5813 +5814 +5815 +5816 +5817 +5818 +5819 +5820 +5821 +5822 +5823 +5824 +5825 +5826 +5827 +5828 +5829 +5830 +5831 +5832 +5833 +5834 +5835 +5836 +5837 +5838 +5839 +5840 +5841 +5842 +5843 +5844 +5845 +5846 +5847 +5848 +5849 +5850 +5851 +5852 +5853 +5854 +5855 +5856 +5857 +5858 +5859 +5860 +5861 +5862 +5863 +5864 +5865 +5866 +5867 +5868 +5869 +5870 +5871 +5872 +5873 +5874 +5875 +5876 +5877 +5878 +5879 +5880 +5881 +5882 +5883 +5884 +5885 +5886 +5887 +5888 +5889 +5890 +5891 +5892 +5893 +5894 +5895 +5896 +5897 +5898 +5899 +5900 +5901 +5902 +5903 +5904 +5905 +5906 +5907 +5908 +5909 +5910 +5911 +5912 +5913 +5914 +5915 +5916 +5917 +5918 +5919 +5920 +5921 +5922 +5923 +5924 +5925 +5926 +5927 +5928 +5929 +5930 +5931 +5932 +5933 +5934 +5935 +5936 +5937 +5938 +5939 +5940 +5941 +5942 +5943 +5944 +5945 +5946 +5947 +5948 +5949 +5950 +5951 +5952 +5953 +5954 +5955 +5956 +5957 +5958 +5959 +5960 +5961 +5962 +5963 +5964 +5965 +5966 +5967 +5968 +5969 +5970 +5971 +5972 +5973 +5974 +5975 +5976 +5977 +5978 +5979 +5980 +5981 +5982 +5983 +5984 +5985 +5986 +5987 +5988 +5989 +5990 +5991 +5992 +5993 +5994 +5995 +5996 +5997 +5998 +5999 +6000 +6001 +6002 +6003 +6004 +6005 +6006 +6007 +6008 +6009 +6010 +6011 +6012 +6013 +6014 +6015 +6016 +6017 +6018 +6019 +6020 +6021 +6022 +6023 +6024 +6025 +6026 +6027 +6028 +6029 +6030 +6031 +6032 +6033 +6034 +6035 +6036 +6037 +6038 +6039 +6040 +6041 +6042 +6043 +6044 +6045 +6046 +6047 +6048 +6049 +6050 +6051 +6052 +6053 +6054 +6055 +6056 +6057 +6058 +6059 +6060 +6061 +6062 +6063 +6064 +6065 +6066 +6067 +6068 +6069 +6070 +6071 +6072 +6073 +6074 +6075 +6076 +6077 +6078 +6079 +6080 +6081 +6082 +6083 +6084 +6085 +6086 +6087 +6088 +6089 +6090 +6091 +6092 +6093 +6094 +6095 +6096 +6097 +6098 +6099 +6100 +6101 +6102 +6103 +6104 +6105 +6106 +6107 +6108 +6109 +6110 +6111 +6112 +6113 +6114 +6115 +6116 +6117 +6118 +6119 +6120 +6121 +6122 +6123 +6124 +6125 +6126 +6127 +6128 +6129 +6130 +6131 +6132 +6133 +6134 +6135 +6136 +6137 +6138 +6139 +6140 +6141 +6142 +6143 +6144 +6145 +6146 +6147 +6148 +6149 +6150 +6151 +6152 +6153 +6154 +6155 +6156 +6157 +6158 +6159 +6160 +6161 +6162 +6163 +6164 +6165 +6166 +6167 +6168 +6169 +6170 +6171 +6172 +6173 +6174 +6175 +6176 +6177 +6178 +6179 +6180 +6181 +6182 +6183 +6184 +6185 +6186 +6187 +6188 +6189 +6190 +6191 +6192 +6193 +6194 +6195 +6196 +6197 +6198 +6199 +6200 +6201 +6202 +6203 +6204 +6205 +6206 +6207 +6208 +6209 +6210 +6211 +6212 +6213 +6214 +6215 +6216 +6217 +6218 +6219 +6220 +6221 +6222 +6223 +6224 +6225 +6226 +6227 +6228 +6229 +6230 +6231 +6232 +6233 +6234 +6235 +6236 +6237 +6238 +6239 +6240 +6241 +6242 +6243 +6244 +6245 +6246 +6247 +6248 +6249 +6250 +6251 +6252 +6253 +6254 +6255 +6256 +6257 +6258 +6259 +6260 +6261 +6262 +6263 +6264 +6265 +6266 +6267 +6268 +6269 +6270 +6271 +6272 +6273 +6274 +6275 +6276 +6277 +6278 +6279 +6280 +6281 +6282 +6283 +6284 +6285 +6286 +6287 +6288 +6289 +6290 +6291 +6292 +6293 +6294 +6295 +6296 +6297 +6298 +6299 +6300 +6301 +6302 +6303 +6304 +6305 +6306 +6307 +6308 +6309 +6310 +6311 +6312 +6313 +6314 +6315 +6316 +6317 +6318 +6319 +6320 +6321 +6322 +6323 +6324 +6325 +6326 +6327 +6328 +6329 +6330 +6331 +6332 +6333 +6334 +6335 +6336 +6337 +6338 +6339 +6340 +6341 +6342 +6343 +6344 +6345 +6346 +6347 +6348 +6349 +6350 +6351 +6352 +6353 +6354 +6355 +6356 +6357 +6358 +6359 +6360 +6361 +6362 +6363 +6364 +6365 +6366 +6367 +6368 +6369 +6370 +6371 +6372 +6373 +6374 +6375 +6376 +6377 +6378 +6379 +6380 +6381 +6382 +6383 +6384 +6385 +6386 +6387 +6388 +6389 +6390 +6391 +6392 +6393 +6394 +6395 +6396 +6397 +6398 +6399 +6400 +6401 +6402 +6403 +6404 +6405 +6406 +6407 +6408 +6409 +6410 +6411 +6412 +6413 +6414 +6415 +6416 +6417 +6418 +6419 +6420 +6421 +6422 +6423 +6424 +6425 +6426 +6427 +6428 +6429 +6430 +6431 +6432 +6433 +6434 +6435 +6436 +6437 +6438 +6439 +6440 +6441 +6442 +6443 +6444 +6445 +6446 +6447 +6448 +6449 +6450 +6451 +6452 +6453 +6454 +6455 +6456 +6457 +6458 +6459 +6460 +6461 +6462 +6463 +6464 +6465 +6466 +6467 +6468 +6469 +6470 +6471 +6472 +6473 +6474 +6475 +6476 +6477 +6478 +6479 +6480 +6481 +6482 +6483 +6484 +6485 +6486 +6487 +6488 +6489 +6490 +6491 +6492 +6493 +6494 +6495 +6496 +6497 +6498 +6499 +6500 +6501 +6502 +6503 +6504 +6505 +6506 +6507 +6508 +6509 +6510 +6511 +6512 +6513 +6514 +6515 +6516 +6517 +6518 +6519 +6520 +6521 +6522 +6523 +6524 +6525 +6526 +6527 +6528 +6529 +6530 +6531 +6532 +6533 +6534 +6535 +6536 +6537 +6538 +6539 +6540 +6541 +6542 +6543 +6544 +6545 +6546 +6547 +6548 +6549 +6550 +6551 +6552 +6553 +6554 +6555 +6556 +6557 +6558 +6559 +6560 +6561 +6562 +6563 +6564 +6565 +6566 +6567 +6568 +6569 +6570 +6571 +6572 +6573 +6574 +6575 +6576 +6577 +6578 +6579 +6580 +6581 +6582 +6583 +6584 +6585 +6586 +6587 +6588 +6589 +6590 +6591 +6592 +6593 +6594 +6595 +6596 +6597 +6598 +6599 +6600 +6601 +6602 +6603 +6604 +6605 +6606 +6607 +6608 +6609 +6610 +6611 +6612 +6613 +6614 +6615 +6616 +6617 +6618 +6619 +6620 +6621 +6622 +6623 +6624 +6625 +6626 +6627 +6628 +6629 +6630 +6631 +6632 +6633 +6634 +6635 +6636 +6637 +6638 +6639 +6640 +6641 +6642 +6643 +6644 +6645 +6646 +6647 +6648 +6649 +6650 +6651 +6652 +6653 +6654 +6655 +6656 +6657 +6658 +6659 +6660 +6661 +6662 +6663 +6664 +6665 +6666 +6667 +6668 +6669 +6670 +6671 +6672 +6673 +6674 +6675 +6676 +6677 +6678 +6679 +6680 +6681 +6682 +6683 +6684 +6685 +6686 +6687 +6688 +6689 +6690 +6691 +6692 +6693 +6694 +6695 +6696 +6697 +6698 +6699 +6700 +6701 +6702 +6703 +6704 +6705 +6706 +6707 +6708 +6709 +6710 +6711 +6712 +6713 +6714 +6715 +6716 +6717 +6718 +6719 +6720 +6721 +6722 +6723 +6724 +6725 +6726 +6727 +6728 +6729 +6730 +6731 +6732 +6733 +6734 +6735 +6736 +6737 +6738 +6739 +6740 +6741 +6742 +6743 +6744 +6745 +6746 +6747 +6748 +6749 +6750 +6751 +6752 +6753 +6754 +6755 +6756 +6757 +6758 +6759 +6760 +6761 +6762 +6763 +6764 +6765 +6766 +6767 +6768 +6769 +6770 +6771 +6772 +6773 +6774 +6775 +6776 +6777 +6778 +6779 +6780 +6781 +6782 +6783 +6784 +6785 +6786 +6787 +6788 +6789 +6790 +6791 +6792 +6793 +6794 +6795 +6796 +6797 +6798 +6799 +6800 +6801 +6802 +6803 +6804 +6805 +6806 +6807 +6808 +6809 +6810 +6811 +6812 +6813 +6814 +6815 +6816 +6817 +6818 +6819 +6820 +6821 +6822 +6823 +6824 +6825 +6826 +6827 +6828 +6829 +6830 +6831 +6832 +6833 +6834 +6835 +6836 +6837 +6838 +6839 +6840 +6841 +6842 +6843 +6844 +6845 +6846 +6847 +6848 +6849 +6850 +6851 +6852 +6853 +6854 +6855 +6856 +6857 +6858 +6859 +6860 +6861 +6862 +6863 +6864 +6865 +6866 +6867 +6868 +6869 +6870 +6871 +6872 +6873 +6874 +6875 +6876 +6877 +6878 +6879 +6880 +6881 +6882 +6883 +6884 +6885 +6886 +6887 +6888 +6889 +6890 +6891 +6892 +6893 +6894 +6895 +6896 +6897 +6898 +6899 +6900 +6901 +6902 +6903 +6904 +6905 +6906 +6907 +6908 +6909 +6910 +6911 +6912 +6913 +6914 +6915 +6916 +6917 +6918 +6919 +6920 +6921 +6922 +6923 +6924 +6925 +6926 +6927 +6928 +6929 +6930 +6931 +6932 +6933 +6934 +6935 +6936 +6937 +6938 +6939 +6940 +6941 +6942 +6943 +6944 +6945 +6946 +6947 +6948 +6949 +6950 +6951 +6952 +6953 +6954 +6955 +6956 +6957 +6958 +6959 +6960 +6961 +6962 +6963 +6964 +6965 +6966 +6967 +6968 +6969 +6970 +6971 +6972 +6973 +6974 +6975 +6976 +6977 +6978 +6979 +6980 +6981 +6982 +6983 +6984 +6985 +6986 +6987 +6988 +6989 +6990 +6991 +6992 +6993 +6994 +6995 +6996 +6997 +6998 +6999 +7000 +7001 +7002 +7003 +7004 +7005 +7006 +7007 +7008 +7009 +7010 +7011 +7012 +7013 +7014 +7015 +7016 +7017 +7018 +7019 +7020 +7021 +7022 +7023 +7024 +7025 +7026 +7027 +7028 +7029 +7030 +7031 +7032 +7033 +7034 +7035 +7036 +7037 +7038 +7039 +7040 +7041 +7042 +7043 +7044 +7045 +7046 +7047 +7048 +7049 +7050 +7051 +7052 +7053 +7054 +7055 +7056 +7057 +7058 +7059 +7060 +7061 +7062 +7063 +7064 +7065 +7066 +7067 +7068 +7069 +7070 +7071 +7072 +7073 +7074 +7075 +7076 +7077 +7078 +7079 +7080 +7081 +7082 +7083 +7084 +7085 +7086 +7087 +7088 +7089 +7090 +7091 +7092 +7093 +7094 +7095 +7096 +7097 +7098 +7099 +7100 +7101 +7102 +7103 +7104 +7105 +7106 +7107 +7108 +7109 +7110 +7111 +7112 +7113 +7114 +7115 +7116 +7117 +7118 +7119 +7120 +7121 +7122 +7123 +7124 +7125 +7126 +7127 +7128 +7129 +7130 +7131 +7132 +7133 +7134 +7135 +7136 +7137 +7138 +7139 +7140 +7141 +7142 +7143 +7144 +7145 +7146 +7147 +7148 +7149 +7150 +7151 +7152 +7153 +7154 +7155 +7156 +7157 +7158 +7159 +7160 +7161 +7162 +7163 +7164 +7165 +7166 +7167 +7168 +7169 +7170 +7171 +7172 +7173 +7174 +7175 +7176 +7177 +7178 +7179 +7180 +7181 +7182 +7183 +7184 +7185 +7186 +7187 +7188 +7189 +7190 +7191 +7192 +7193 +7194 +7195 +7196 +7197 +7198 +7199 +7200 +7201 +7202 +7203 +7204 +7205 +7206 +7207 +7208 +7209 +7210 +7211 +7212 +7213 +7214 +7215 +7216 +7217 +7218 +7219 +7220 +7221 +7222 +7223 +7224 +7225 +7226 +7227 +7228 +7229 +7230 +7231 +7232 +7233 +7234 +7235 +7236 +7237 +7238 +7239 +7240 +7241 +7242 +7243 +7244 +7245 +7246 +7247 +7248 +7249 +7250 +7251 +7252 +7253 +7254 +7255 +7256 +7257 +7258 +7259 +7260 +7261 +7262 +7263 +7264 +7265 +7266 +7267 +7268 +7269 +7270 +7271 +7272 +7273 +7274 +7275 +7276 +7277 +7278 +7279 +7280 +7281 +7282 +7283 +7284 +7285 +7286 +7287 +7288 +7289 +7290 +7291 +7292 +7293 +7294 +7295 +7296 +7297 +7298 +7299 +7300 +7301 +7302 +7303 +7304 +7305 +7306 +7307 +7308 +7309 +7310 +7311 +7312 +7313 +7314 +7315 +7316 +7317 +7318 +7319 +7320 +7321 +7322 +7323 +7324 +7325 +7326 +7327 +7328 +7329 +7330 +7331 +7332 +7333 +7334 +7335 +7336 +7337 +7338 +7339 +7340 +7341 +7342 +7343 +7344 +7345 +7346 +7347 +7348 +7349 +7350 +7351 +7352 +7353 +7354 +7355 +7356 +7357 +7358 +7359 +7360 +7361 +7362 +7363 +7364 +7365 +7366 +7367 +7368 +7369 +7370 +7371 +7372 +7373 +7374 +7375 +7376 +7377 +7378 +7379 +7380 +7381 +7382 +7383 +7384 +7385 +7386 +7387 +7388 +7389 +7390 +7391 +7392 +7393 +7394 +7395 +7396 +7397 +7398 +7399 +7400 +7401 +7402 +7403 +7404 +7405 +7406 +7407 +7408 +7409 +7410 +7411 +7412 +7413 +7414 +7415 +7416 +7417 +7418 +7419 +7420 +7421 +7422 +7423 +7424 +7425 +7426 +7427 +7428 +7429 +7430 +7431 +7432 +7433 +7434 +7435 +7436 +7437 +7438 +7439 +7440 +7441 +7442 +7443 +7444 +7445 +7446 +7447 +7448 +7449 +7450 +7451 +7452 +7453 +7454 +7455 +7456 +7457 +7458 +7459 +7460 +7461 +7462 +7463 +7464 +7465 +7466 +7467 +7468 +7469 +7470 +7471 +7472 +7473 +7474 +7475 +7476 +7477 +7478 +7479 +7480 +7481 +7482 +7483 +7484 +7485 +7486 +7487 +7488 +7489 +7490 +7491 +7492 +7493 +7494 +7495 +7496 +7497 +7498 +7499 +7500 +7501 +7502 +7503 +7504 +7505 +7506 +7507 +7508 +7509 +7510 +7511 +7512 +7513 +7514 +7515 +7516 +7517 +7518 +7519 +7520 +7521 +7522 +7523 +7524 +7525 +7526 +7527 +7528 +7529 +7530 +7531 +7532 +7533 +7534 +7535 +7536 +7537 +7538 +7539 +7540 +7541 +7542 +7543 +7544 +7545 +7546 +7547 +7548 +7549 +7550 +7551 +7552 +7553 +7554 +7555 +7556 +7557 +7558 +7559 +7560 +7561 +7562 +7563 +7564 +7565 +7566 +7567 +7568 +7569 +7570 +7571 +7572 +7573 +7574 +7575 +7576 +7577 +7578 +7579 +7580 +7581 +7582 +7583 +7584 +7585 +7586 +7587 +7588 +7589 +7590 +7591 +7592 +7593 +7594 +7595 +7596 +7597 +7598 +7599 +7600 +7601 +7602 +7603 +7604 +7605 +7606 +7607 +7608 +7609 +7610 +7611 +7612 +7613 +7614 +7615 +7616 +7617 +7618 +7619 +7620 +7621 +7622 +7623 +7624 +7625 +7626 +7627 +7628 +7629 +7630 +7631 +7632 +7633 +7634 +7635 +7636 +7637 +7638 +7639 +7640 +7641 +7642 +7643 +7644 +7645 +7646 +7647 +7648 +7649 +7650 +7651 +7652 +7653 +7654 +7655 +7656 +7657 +7658 +7659 +7660 +7661 +7662 +7663 +7664 +7665 +7666 +7667 +7668 +7669 +7670 +7671 +7672 +7673 +7674 +7675 +7676 +7677 +7678 +7679 +7680 +7681 +7682 +7683 +7684 +7685 +7686 +7687 +7688 +7689 +7690 +7691 +7692 +7693 +7694 +7695 +7696 +7697 +7698 +7699 +7700 +7701 +7702 +7703 +7704 +7705 +7706 +7707 +7708 +7709 +7710 +7711 +7712 +7713 +7714 +7715 +7716 +7717 +7718 +7719 +7720 +7721 +7722 +7723 +7724 +7725 +7726 +7727 +7728 +7729 +7730 +7731 +7732 +7733 +7734 +7735 +7736 +7737 +7738 +7739 +7740 +7741 +7742 +7743 +7744 +7745 +7746 +7747 +7748 +7749 +7750 +7751 +7752 +7753 +7754 +7755 +7756 +7757 +7758 +7759 +7760 +7761 +7762 +7763 +7764 +7765 +7766 +7767 +7768 +7769 +7770 +7771 +7772 +7773 +7774 +7775 +7776 +7777 +7778 +7779 +7780 +7781 +7782 +7783 +7784 +7785 +7786 +7787 +7788 +7789 +7790 +7791 +7792 +7793 +7794 +7795 +7796 +7797 +7798 +7799 +7800 +7801 +7802 +7803 +7804 +7805 +7806 +7807 +7808 +7809 +7810 +7811 +7812 +7813 +7814 +7815 +7816 +7817 +7818 +7819 +7820 +7821 +7822 +7823 +7824 +7825 +7826 +7827 +7828 +7829 +7830 +7831 +7832 +7833 +7834 +7835 +7836 +7837 +7838 +7839 +7840 +7841 +7842 +7843 +7844 +7845 +7846 +7847 +7848 +7849 +7850 +7851 +7852 +7853 +7854 +7855 +7856 +7857 +7858 +7859 +7860 +7861 +7862 +7863 +7864 +7865 +7866 +7867 +7868 +7869 +7870 +7871 +7872 +7873 +7874 +7875 +7876 +7877 +7878 +7879 +7880 +7881 +7882 +7883 +7884 +7885 +7886 +7887 +7888 +7889 +7890 +7891 +7892 +7893 +7894 +7895 +7896 +7897 +7898 +7899 +7900 +7901 +7902 +7903 +7904 +7905 +7906 +7907 +7908 +7909 +7910 +7911 +7912 +7913 +7914 +7915 +7916 +7917 +7918 +7919 +7920 +7921 +7922 +7923 +7924 +7925 +7926 +7927 +7928 +7929 +7930 +7931 +7932 +7933 +7934 +7935 +7936 +7937 +7938 +7939 +7940 +7941 +7942 +7943 +7944 +7945 +7946 +7947 +7948 +7949 +7950 +7951 +7952 +7953 +7954 +7955 +7956 +7957 +7958 +7959 +7960 +7961 +7962 +7963 +7964 +7965 +7966 +7967 +7968 +7969 +7970 +7971 +7972 +7973 +7974 +7975 +7976 +7977 +7978 +7979 +7980 +7981 +7982 +7983 +7984 +7985 +7986 +7987 +7988 +7989 +7990 +7991 +7992 +7993 +7994 +7995 +7996 +7997 +7998 +7999 +8000 +8001 +8002 +8003 +8004 +8005 +8006 +8007 +8008 +8009 +8010 +8011 +8012 +8013 +8014 +8015 +8016 +8017 +8018 +8019 +8020 +8021 +8022 +8023 +8024 +8025 +8026 +8027 +8028 +8029 +8030 +8031 +8032 +8033 +8034 +8035 +8036 +8037 +8038 +8039 +8040 +8041 +8042 +8043 +8044 +8045 +8046 +8047 +8048 +8049 +8050 +8051 +8052 +8053 +8054 +8055 +8056 +8057 +8058 +8059 +8060 +8061 +8062 +8063 +8064 +8065 +8066 +8067 +8068 +8069 +8070 +8071 +8072 +8073 +8074 +8075 +8076 +8077 +8078 +8079 +8080 +8081 +8082 +8083 +8084 +8085 +8086 +8087 +8088 +8089 +8090 +8091 +8092 +8093 +8094 +8095 +8096 +8097 +8098 +8099 +8100 +8101 +8102 +8103 +8104 +8105 +8106 +8107 +8108 +8109 +8110 +8111 +8112 +8113 +8114 +8115 +8116 +8117 +8118 +8119 +8120 +8121 +8122 +8123 +8124 +8125 +8126 +8127 +8128 +8129 +8130 +8131 +8132 +8133 +8134 +8135 +8136 +8137 +8138 +8139 +8140 +8141 +8142 +8143 +8144 +8145 +8146 +8147 +8148 +8149 +8150 +8151 +8152 +8153 +8154 +8155 +8156 +8157 +8158 +8159 +8160 +8161 +8162 +8163 +8164 +8165 +8166 +8167 +8168 +8169 +8170 +8171 +8172 +8173 +8174 +8175 +8176 +8177 +8178 +8179 +8180 +8181 +8182 +8183 +8184 +8185 +8186 +8187 +8188 +8189 +8190 +8191 +8192 +8193 +8194 +8195 +8196 +8197 +8198 +8199 +8200 +8201 +8202 +8203 +8204 +8205 +8206 +8207 +8208 +8209 +8210 +8211 +8212 +8213 +8214 +8215 +8216 +8217 +8218 +8219 +8220 +8221 +8222 +8223 +8224 +8225 +8226 +8227 +8228 +8229 +8230 +8231 +8232 +8233 +8234 +8235 +8236 +8237 +8238 +8239 +8240 +8241 +8242 +8243 +8244 +8245 +8246 +8247 +8248 +8249 +8250 +8251 +8252 +8253 +8254 +8255 +8256 +8257 +8258 +8259 +8260 +8261 +8262 +8263 +8264 +8265 +8266 +8267 +8268 +8269 +8270 +8271 +8272 +8273 +8274 +8275 +8276 +8277 +8278 +8279 +8280 +8281 +8282 +8283 +8284 +8285 +8286 +8287 +8288 +8289 +8290 +8291 +8292 +8293 +8294 +8295 +8296 +8297 +8298 +8299 +8300 +8301 +8302 +8303 +8304 +8305 +8306 +8307 +8308 +8309 +8310 +8311 +8312 +8313 +8314 +8315 +8316 +8317 +8318 +8319 +8320 +8321 +8322 +8323 +8324 +8325 +8326 +8327 +8328 +8329 +8330 +8331 +8332 +8333 +8334 +8335 +8336 +8337 +8338 +8339 +8340 +8341 +8342 +8343 +8344 +8345 +8346 +8347 +8348 +8349 +8350 +8351 +8352 +8353 +8354 +8355 +8356 +8357 +8358 +8359 +8360 +8361 +8362 +8363 +8364 +8365 +8366 +8367 +8368 +8369 +8370 +8371 +8372 +8373 +8374 +8375 +8376 +8377 +8378 +8379 +8380 +8381 +8382 +8383 +8384 +8385 +8386 +8387 +8388 +8389 +8390 +8391 +8392 +8393 +8394 +8395 +8396 +8397 +8398 +8399 +8400 +8401 +8402 +8403 +8404 +8405 +8406 +8407 +8408 +8409 +8410 +8411 +8412 +8413 +8414 +8415 +8416 +8417 +8418 +8419 +8420 +8421 +8422 +8423 +8424 +8425 +8426 +8427 +8428 +8429 +8430 +8431 +8432 +8433 +8434 +8435 +8436 +8437 +8438 +8439 +8440 +8441 +8442 +8443 +8444 +8445 +8446 +8447 +8448 +8449 +8450 +8451 +8452 +8453 +8454 +8455 +8456 +8457 +8458 +8459 +8460 +8461 +8462 +8463 +8464 +8465 +8466 +8467 +8468 +8469 +8470 +8471 +8472 +8473 +8474 +8475 +8476 +8477 +8478 +8479 +8480 +8481 +8482 +8483 +8484 +8485 +8486 +8487 +8488 +8489 +8490 +8491 +8492 +8493 +8494 +8495 +8496 +8497 +8498 +8499 +8500 +8501 +8502 +8503 +8504 +8505 +8506 +8507 +8508 +8509 +8510 +8511 +8512 +8513 +8514 +8515 +8516 +8517 +8518 +8519 +8520 +8521 +8522 +8523 +8524 +8525 +8526 +8527 +8528 +8529 +8530 +8531 +8532 +8533 +8534 +8535 +8536 +8537 +8538 +8539 +8540 +8541 +8542 +8543 +8544 +8545 +8546 +8547 +8548 +8549 +8550 +8551 +8552 +8553 +8554 +8555 +8556 +8557 +8558 +8559 +8560 +8561 +8562 +8563 +8564 +8565 +8566 +8567 +8568 +8569 +8570 +8571 +8572 +8573 +8574 +8575 +8576 +8577 +8578 +8579 +8580 +8581 +8582 +8583 +8584 +8585 +8586 +8587 +8588 +8589 +8590 +8591 +8592 +8593 +8594 +8595 +8596 +8597 +8598 +8599 +8600 +8601 +8602 +8603 +8604 +8605 +8606 +8607 +8608 +8609 +8610 +8611 +8612 +8613 +8614 +8615 +8616 +8617 +8618 +8619 +8620 +8621 +8622 +8623 +8624 +8625 +8626 +8627 +8628 +8629 +8630 +8631 +8632 +8633 +8634 +8635 +8636 +8637 +8638 +8639 +8640 +8641 +8642 +8643 +8644 +8645 +8646 +8647 +8648 +8649 +8650 +8651 +8652 +8653 +8654 +8655 +8656 +8657 +8658 +8659 +8660 +8661 +8662 +8663 +8664 +8665 +8666 +8667 +8668 +8669 +8670 +8671 +8672 +8673 +8674 +8675 +8676 +8677 +8678 +8679 +8680 +8681 +8682 +8683 +8684 +8685 +8686 +8687 +8688 +8689 +8690 +8691 +8692 +8693 +8694 +8695 +8696 +8697 +8698 +8699 +8700 +8701 +8702 +8703 +8704 +8705 +8706 +8707 +8708 +8709 +8710 +8711 +8712 +8713 +8714 +8715 +8716 +8717 +8718 +8719 +8720 +8721 +8722 +8723 +8724 +8725 +8726 +8727 +8728 +8729 +8730 +8731 +8732 +8733 +8734 +8735 +8736 +8737 +8738 +8739 +8740 +8741 +8742 +8743 +8744 +8745 +8746 +8747 +8748 +8749 +8750 +8751 +8752 +8753 +8754 +8755 +8756 +8757 +8758 +8759 +8760 +8761 +8762 +8763 +8764 +8765 +8766 +8767 +8768 +8769 +8770 +8771 +8772 +8773 +8774 +8775 +8776 +8777 +8778 +8779 +8780 +8781 +8782 +8783 +8784 +8785 +8786 +8787 +8788 +8789 +8790 +8791 +8792 +8793 +8794 +8795 +8796 +8797 +8798 +8799 +8800 +8801 +8802 +8803 +8804 +8805 +8806 +8807 +8808 +8809 +8810 +8811 +8812 +8813 +8814 +8815 +8816 +8817 +8818 +8819 +8820 +8821 +8822 +8823 +8824 +8825 +8826 +8827 +8828 +8829 +8830 +8831 +8832 +8833 +8834 +8835 +8836 +8837 +8838 +8839 +8840 +8841 +8842 +8843 +8844 +8845 +8846 +8847 +8848 +8849 +8850 +8851 +8852 +8853 +8854 +8855 +8856 +8857 +8858 +8859 +8860 +8861 +8862 +8863 +8864 +8865 +8866 +8867 +8868 +8869 +8870 +8871 +8872 +8873 +8874 +8875 +8876 +8877 +8878 +8879 +8880 +8881 +8882 +8883 +8884 +8885 +8886 +8887 +8888 +8889 +8890 +8891 +8892 +8893 +8894 +8895 +8896 +8897 +8898 +8899 +8900 +8901 +8902 +8903 +8904 +8905 +8906 +8907 +8908 +8909 +8910 +8911 +8912 +8913 +8914 +8915 +8916 +8917 +8918 +8919 +8920 +8921 +8922 +8923 +8924 +8925 +8926 +8927 +8928 +8929 +8930 +8931 +8932 +8933 +8934 +8935 +8936 +8937 +8938 +8939 +8940 +8941 +8942 +8943 +8944 +8945 +8946 +8947 +8948 +8949 +8950 +8951 +8952 +8953 +8954 +8955 +8956 +8957 +8958 +8959 +8960 +8961 +8962 +8963 +8964 +8965 +8966 +8967 +8968 +8969 +8970 +8971 +8972 +8973 +8974 +8975 +8976 +8977 +8978 +8979 +8980 +8981 +8982 +8983 +8984 +8985 +8986 +8987 +8988 +8989 +8990 +8991 +8992 +8993 +8994 +8995 +8996 +8997 +8998 +8999 +9000 +9001 +9002 +9003 +9004 +9005 +9006 +9007 +9008 +9009 +9010 +9011 +9012 +9013 +9014 +9015 +9016 +9017 +9018 +9019 +9020 +9021 +9022 +9023 +9024 +9025 +9026 +9027 +9028 +9029 +9030 +9031 +9032 +9033 +9034 +9035 +9036 +9037 +9038 +9039 +9040 +9041 +9042 +9043 +9044 +9045 +9046 +9047 +9048 +9049 +9050 +9051 +9052 +9053 +9054 +9055 +9056 +9057 +9058 +9059 +9060 +9061 +9062 +9063 +9064 +9065 +9066 +9067 +9068 +9069 +9070 +9071 +9072 +9073 +9074 +9075 +9076 +9077 +9078 +9079 +9080 +9081 +9082 +9083 +9084 +9085 +9086 +9087 +9088 +9089 +9090 +9091 +9092 +9093 +9094 +9095 +9096 +9097 +9098 +9099 +9100 +9101 +9102 +9103 +9104 +9105 +9106 +9107 +9108 +9109 +9110 +9111 +9112 +9113 +9114 +9115 +9116 +9117 +9118 +9119 +9120 +9121 +9122 +9123 +9124 +9125 +9126 +9127 +9128 +9129 +9130 +9131 +9132 +9133 +9134 +9135 +9136 +9137 +9138 +9139 +9140 +9141 +9142 +9143 +9144 +9145 +9146 +9147 +9148 +9149 +9150 +9151 +9152 +9153 +9154 +9155 +9156 +9157 +9158 +9159 +9160 +9161 +9162 +9163 +9164 +9165 +9166 +9167 +9168 +9169 +9170 +9171 +9172 +9173 +9174 +9175 +9176 +9177 +9178 +9179 +9180 +9181 +9182 +9183 +9184 +9185 +9186 +9187 +9188 +9189 +9190 +9191 +9192 +9193 +9194 +9195 +9196 +9197 +9198 +9199 +9200 +9201 +9202 +9203 +9204 +9205 +9206 +9207 +9208 +9209 +9210 +9211 +9212 +9213 +9214 +9215 +9216 +9217 +9218 +9219 +9220 +9221 +9222 +9223 +9224 +9225 +9226 +9227 +9228 +9229 +9230 +9231 +9232 +9233 +9234 +9235 +9236 +9237 +9238 +9239 +9240 +9241 +9242 +9243 +9244 +9245 +9246 +9247 +9248 +9249 +9250 +9251 +9252 +9253 +9254 +9255 +9256 +9257 +9258 +9259 +9260 +9261 +9262 +9263 +9264 +9265 +9266 +9267 +9268 +9269 +9270 +9271 +9272 +9273 +9274 +9275 +9276 +9277 +9278 +9279 +9280 +9281 +9282 +9283 +9284 +9285 +9286 +9287 +9288 +9289 +9290 +9291 +9292 +9293 +9294 +9295 +9296 +9297 +9298 +9299 +9300 +9301 +9302 +9303 +9304 +9305 +9306 +9307 +9308 +9309 +9310 +9311 +9312 +9313 +9314 +9315 +9316 +9317 +9318 +9319 +9320 +9321 +9322 +9323 +9324 +9325 +9326 +9327 +9328 +9329 +9330 +9331 +9332 +9333 +9334 +9335 +9336 +9337 +9338 +9339 +9340 +9341 +9342 +9343 +9344 +9345 +9346 +9347 +9348 +9349 +9350 +9351 +9352 +9353 +9354 +9355 +9356 +9357 +9358 +9359 +9360 +9361 +9362 +9363 +9364 +9365 +9366 +9367 +9368 +9369 +9370 +9371 +9372 +9373 +9374 +9375 +9376 +9377 +9378 +9379 +9380 +9381 +9382 +9383 +9384 +9385 +9386 +9387 +9388 +9389 +9390 +9391 +9392 +9393 +9394 +9395 +9396 +9397 +9398 +9399 +9400 +9401 +9402 +9403 +9404 +9405 +9406 +9407 +9408 +9409 +9410 +9411 +9412 +9413 +9414 +9415 +9416 +9417 +9418 +9419 +9420 +9421 +9422 +9423 +9424 +9425 +9426 +9427 +9428 +9429 +9430 +9431 +9432 +9433 +9434 +9435 +9436 +9437 +9438 +9439 +9440 +9441 +9442 +9443 +9444 +9445 +9446 +9447 +9448 +9449 +9450 +9451 +9452 +9453 +9454 +9455 +9456 +9457 +9458 +9459 +9460 +9461 +9462 +9463 +9464 +9465 +9466 +9467 +9468 +9469 +9470 +9471 +9472 +9473 +9474 +9475 +9476 +9477 +9478 +9479 +9480 +9481 +9482 +9483 +9484 +9485 +9486 +9487 +9488 +9489 +9490 +9491 +9492 +9493 +9494 +9495 +9496 +9497 +9498 +9499 +9500 +9501 +9502 +9503 +9504 +9505 +9506 +9507 +9508 +9509 +9510 +9511 +9512 +9513 +9514 +9515 +9516 +9517 +9518 +9519 +9520 +9521 +9522 +9523 +9524 +9525 +9526 +9527 +9528 +9529 +9530 +9531 +9532 +9533 +9534 +9535 +9536 +9537 +9538 +9539 +9540 +9541 +9542 +9543 +9544 +9545 +9546 +9547 +9548 +9549 +9550 +9551 +9552 +9553 +9554 +9555 +9556 +9557 +9558 +9559 +9560 +9561 +9562 +9563 +9564 +9565 +9566 +9567 +9568 +9569 +9570 +9571 +9572 +9573 +9574 +9575 +9576 +9577 +9578 +9579 +9580 +9581 +9582 +9583 +9584 +9585 +9586 +9587 +9588 +9589 +9590 +9591 +9592 +9593 +9594 +9595 +9596 +9597 +9598 +9599 +9600 +9601 +9602 +9603 +9604 +9605 +9606 +9607 +9608 +9609 +9610 +9611 +9612 +9613 +9614 +9615 +9616 +9617 +9618 +9619 +9620 +9621 +9622 +9623 +9624 +9625 +9626 +9627 +9628 +9629 +9630 +9631 +9632 +9633 +9634 +9635 +9636 +9637 +9638 +9639 +9640 +9641 +9642 +9643 +9644 +9645 +9646 +9647 +9648 +9649 +9650 +9651 +9652 +9653 +9654 +9655 +9656 +9657 +9658 +9659 +9660 +9661 +9662 +9663 +9664 +9665 +9666 +9667 +9668 +9669 +9670 +9671 +9672 +9673 +9674 +9675 +9676 +9677 +9678 +9679 +9680 +9681 +9682 +9683 +9684 +9685 +9686 +9687 +9688 +9689 +9690 +9691 +9692 +9693 +9694 +9695 +9696 +9697 +9698 +9699 +9700 +9701 +9702 +9703 +9704 +9705 +9706 +9707 +9708 +9709 +9710 +9711 +9712 +9713 +9714 +9715 +9716 +9717 +9718 +9719 +9720 +9721 +9722 +9723 +9724 +9725 +9726 +9727 +9728 +9729 +9730 +9731 +9732 +9733 +9734 +9735 +9736 +9737 +9738 +9739 +9740 +9741 +9742 +9743 +9744 +9745 +9746 +9747 +9748 +9749 +9750 +9751 +9752 +9753 +9754 +9755 +9756 +9757 +9758 +9759 +9760 +9761 +9762 +9763 +9764 +9765 +9766 +9767 +9768 +9769 +9770 +9771 +9772 +9773 +9774 +9775 +9776 +9777 +9778 +9779 +9780 +9781 +9782 +9783 +9784 +9785 +9786 +9787 +9788 +9789 +9790 +9791 +9792 +9793 +9794 +9795 +9796 +9797 +9798 +9799 +9800 +9801 +9802 +9803 +9804 +9805 +9806 +9807 +9808 +9809 +9810 +9811 +9812 +9813 +9814 +9815 +9816 +9817 +9818 +9819 +9820 +9821 +9822 +9823 +9824 +9825 +9826 +9827 +9828 +9829 +9830 +9831 +9832 +9833 +9834 +9835 +9836 +9837 +9838 +9839 +9840 +9841 +9842 +9843 +9844 +9845 +9846 +9847 +9848 +9849 +9850 +9851 +9852 +9853 +9854 +9855 +9856 +9857 +9858 +9859 +9860 +9861 +9862 +9863 +9864 +9865 +9866 +9867 +9868 +9869 +9870 +9871 +9872 +9873 +9874 +9875 +9876 +9877 +9878 +9879 +9880 +9881 +9882 +9883 +9884 +9885 +9886 +9887 +9888 +9889 +9890 +9891 +9892 +9893 +9894 +9895 +9896 +9897 +9898 +9899 +9900 +9901 +9902 +9903 +9904 +9905 +9906 +9907 +9908 +9909 +9910 +9911 +9912 +9913 +9914 +9915 +9916 +9917 +9918 +9919 +9920 +9921 +9922 +9923 +9924 +9925 +9926 +9927 +9928 +9929 +9930 +9931 +9932 +9933 +9934 +9935 +9936 +9937 +9938 +9939 +9940 +9941 +9942 +9943 +9944 +9945 +9946 +9947 +9948 +9949 +9950 +9951 +9952 +9953 +9954 +9955 +9956 +9957 +9958 +9959 +9960 +9961 +9962 +9963 +9964 +9965 +9966 +9967 +9968 +9969 +9970 +9971 +9972 +9973 +9974 +9975 +9976 +9977 +9978 +9979 +9980 +9981 +9982 +9983 +9984 +9985 +9986 +9987 +9988 +9989 +9990 +9991 +9992 +9993 +9994 +9995 +9996 +9997 +9998 +9999 +10000 +10001 +10002 +10003 +10004 +10005 +10006 +10007 +10008 +10009 +10010 +10011 +10012 +10013 +10014 +10015 +10016 +10017 +10018 +10019 +10020 +10021 +10022 +10023 +10024 +10025 +10026 +10027 +10028 +10029 +10030 +10031 +10032 +10033 +10034 +10035 +10036 +10037 +10038 +10039 +10040 +10041 +10042 +10043 +10044 +10045 +10046 +10047 +10048 +10049 +10050 +10051 +10052 +10053 +10054 +10055 +10056 +10057 +10058 +10059 +10060 +10061 +10062 +10063 +10064 +10065 +10066 +10067 +10068 +10069 +10070 +10071 +10072 +10073 +10074 +10075 +10076 +10077 +10078 +10079 +10080 +10081 +10082 +10083 +10084 +10085 +10086 +10087 +10088 +10089 +10090 +10091 +10092 +10093 +10094 +10095 +10096 +10097 +10098 +10099 +10100 +10101 +10102 +10103 +10104 +10105 +10106 +10107 +10108 +10109 +10110 +10111 +10112 +10113 +10114 +10115 +10116 +10117 +10118 +10119 +10120 +10121 +10122 +10123 +10124 +10125 +10126 +10127 +10128 +10129 +10130 +10131 +10132 +10133 +10134 +10135 +10136 +10137 +10138 +10139 +10140 +10141 +10142 +10143 +10144 +10145 +10146 +10147 +10148 +10149 +10150 +10151 +10152 +10153 +10154 +10155 +10156 +10157 +10158 +10159 +10160 +10161 +10162 +10163 +10164 +10165 +10166 +10167 +10168 +10169 +10170 +10171 +10172 +10173 +10174 +10175 +10176 +10177 +10178 +10179 +10180 +10181 +10182 +10183 +10184 +10185 +10186 +10187 +10188 +10189 +10190 +10191 +10192 +10193 +10194 +10195 +10196 +10197 +10198 +10199 +10200 +10201 +10202 +10203 +10204 +10205 +10206 +10207 +10208 +10209 +10210 +10211 +10212 +10213 +10214 +10215 +10216 +10217 +10218 +10219 +10220 +10221 +10222 +10223 +10224 +10225 +10226 +10227 +10228 +10229 +10230 +10231 +10232 +10233 +10234 +10235 +10236 +10237 +10238 +10239 +10240 +10241 +10242 +10243 +10244 +10245 +10246 +10247 +10248 +10249 +10250 +10251 +10252 +10253 +10254 +10255 +10256 +10257 +10258 +10259 +10260 +10261 +10262 +10263 +10264 +10265 +10266 +10267 +10268 +10269 +10270 +10271 +10272 +10273 +10274 +10275 +10276 +10277 +10278 +10279 +10280 +10281 +10282 +10283 +10284 +10285 +10286 +10287 +10288 +10289 +10290 +10291 +10292 +10293 +10294 +10295 +10296 +10297 +10298 +10299 +10300 +10301 +10302 +10303 +10304 +10305 +10306 +10307 +10308 +10309 +10310 +10311 +10312 +10313 +10314 +10315 +10316 +10317 +10318 +10319 +10320 +10321 +10322 +10323 +10324 +10325 +10326 +10327 +10328 +10329 +10330 +10331 +10332 +10333 +10334 +10335 +10336 +10337 +10338 +10339 +10340 +10341 +10342 +10343 +10344 +10345 +10346 +10347 +10348 +10349 +10350 +10351 +10352 +10353 +10354 +10355 +10356 +10357 +10358 +10359 +10360 +10361 +10362 +10363 +10364 +10365 +10366 +10367 +10368 +10369 +10370 +10371 +10372 +10373 +10374 +10375 +10376 +10377 +10378 +10379 +10380 +10381 +10382 +10383 +10384 +10385 +10386 +10387 +10388 +10389 +10390 +10391 +10392 +10393 +10394 +10395 +10396 +10397 +10398 +10399 +10400 +10401 +10402 +10403 +10404 +10405 +10406 +10407 +10408 +10409 +10410 +10411 +10412 +10413 +10414 +10415 +10416 +10417 +10418 +10419 +10420 +10421 +10422 +10423 +10424 +10425 +10426 +10427 +10428 +10429 +10430 +10431 +10432 +10433 +10434 +10435 +10436 +10437 +10438 +10439 +10440 +10441 +10442 +10443 +10444 +10445 +10446 +10447 +10448 +10449 +10450 +10451 +10452 +10453 +10454 +10455 +10456 +10457 +10458 +10459 +10460 +10461 +10462 +10463 +10464 +10465 +10466 +10467 +10468 +10469 +10470 +10471 +10472 +10473 +10474 +10475 +10476 +10477 +10478 +10479 +10480 +10481 +10482 +10483 +10484 +10485 +10486 +10487 +10488 +10489 +10490 +10491 +10492 +10493 +10494 +10495 +10496 +10497 +10498 +10499 +10500 +10501 +10502 +10503 +10504 +10505 +10506 +10507 +10508 +10509 +10510 +10511 +10512 +10513 +10514 +10515 +10516 +10517 +10518 +10519 +10520 +10521 +10522 +10523 +10524 +10525 +10526 +10527 +10528 +10529 +10530 +10531 +10532 +10533 +10534 +10535 +10536 +10537 +10538 +10539 +10540 +10541 +10542 +10543 +10544 +10545 +10546 +10547 +10548 +10549 +10550 +10551 +10552 +10553 +10554 +10555 +10556 +10557 +10558 +10559 +10560 +10561 +10562 +10563 +10564 +10565 +10566 +10567 +10568 +10569 +10570 +10571 +10572 +10573 +10574 +10575 +10576 +10577 +10578 +10579 +10580 +10581 +10582 +10583 +10584 +10585 +10586 +10587 +10588 +10589 +10590 +10591 +10592 +10593 +10594 +10595 +10596 +10597 +10598 +10599 +10600 +10601 +10602 +10603 +10604 +10605 +10606 +10607 +10608 +10609 +10610 +10611 +10612 +10613 +10614 +10615 +10616 +10617 +10618 +10619 +10620 +10621 +10622 +10623 +10624 +10625 +10626 +10627 +10628 +10629 +10630 +10631 +10632 +10633 +10634 +10635 +10636 +10637 +10638 +10639 +10640 +10641 +10642 +10643 +10644 +10645 +10646 +10647 +10648 +10649 +10650 +10651 +10652 +10653 +10654 +10655 +10656 +10657 +10658 +10659 +10660 +10661 +10662 +10663 +10664 +10665 +10666 +10667 +10668 +10669 +10670 +10671 +10672 +10673 +10674 +10675 +10676 +10677 +10678 +10679 +10680 +10681 +10682 +10683 +10684 +10685 +10686 +10687 +10688 +10689 +10690 +10691 +10692 +10693 +10694 +10695 +10696 +10697 +10698 +10699 +10700 +10701 +10702 +10703 +10704 +10705 +10706 +10707 +10708 +10709 +10710 +10711 +10712 +10713 +10714 +10715 +10716 +10717 +10718 +10719 +10720 +10721 +10722 +10723 +10724 +10725 +10726 +10727 +10728 +10729 +10730 +10731 +10732 +10733 +10734 +10735 +10736 +10737 +10738 +10739 +10740 +10741 +10742 +10743 +10744 +10745 +10746 +10747 +10748 +10749 +10750 +10751 +10752 +10753 +10754 +10755 +10756 +10757 +10758 +10759 +10760 +10761 +10762 +10763 +10764 +10765 +10766 +10767 +10768 +10769 +10770 +10771 +10772 +10773 +10774 +10775 +10776 +10777 +10778 +10779 +10780 +10781 +10782 +10783 +10784 +10785 +10786 +10787 +10788 +10789 +10790 +10791 +10792 +10793 +10794 +10795 +10796 +10797 +10798 +10799 +10800 +10801 +10802 +10803 +10804 +10805 +10806 +10807 +10808 +10809 +10810 +10811 +10812 +10813 +10814 +10815 +10816 +10817 +10818 +10819 +10820 +10821 +10822 +10823 +10824 +10825 +10826 +10827 +10828 +10829 +10830 +10831 +10832 +10833 +10834 +10835 +10836 +10837 +10838 +10839 +10840 +10841 +10842 +10843 +10844 +10845 +10846 +10847 +10848 +10849 +10850 +10851 +10852 +10853 +10854 +10855 +10856 +10857 +10858 +10859 +10860 +10861 +10862 +10863 +10864 +10865 +10866 +10867 +10868 +10869 +10870 +10871 +10872 +10873 +10874 +10875 +10876 +10877 +10878 +10879 +10880 +10881 +10882 +10883 +10884 +10885 +10886 +10887 +10888 +10889 +10890 +10891 +10892 +10893 +10894 +10895 +10896 +10897 +10898 +10899 +10900 +10901 +10902 +10903 +10904 +10905 +10906 +10907 +10908 +10909 +10910 +10911 +10912 +10913 +10914 +10915 +10916 +10917 +10918 +10919 +10920 +10921 +10922 +10923 +10924 +10925 +10926 +10927 +10928 +10929 +10930 +10931 +10932 +10933 +10934 +10935 +10936 +10937 +10938 +10939 +10940 +10941 +10942 +10943 +10944 +10945 +10946 +10947 +10948 +10949 +10950 +10951 +10952 +10953 +10954 +10955 +10956 +10957 +10958 +10959 +10960 +10961 +10962 +10963 +10964 +10965 +10966 +10967 +10968 +10969 +10970 +10971 +10972 +10973 +10974 +10975 +10976 +10977 +10978 +10979 +10980 +10981 +10982 +10983 +10984 +10985 +10986 +10987 +10988 +10989 +10990 +10991 +10992 +10993 +10994 +10995 +10996 +10997 +10998 +10999 +11000 +11001 +11002 +11003 +11004 +11005 +11006 +11007 +11008 +11009 +11010 +11011 +11012 +11013 +11014 +11015 +11016 +11017 +11018 +11019 +11020 +11021 +11022 +11023 +11024 +11025 +11026 +11027 +11028 +11029 +11030 +11031 +11032 +11033 +11034 +11035 +11036 +11037 +11038 +11039 +11040 +11041 +11042 +11043 +11044 +11045 +11046 +11047 +11048 +11049 +11050 +11051 +11052 +11053 +11054 +11055 +11056 +11057 +11058 +11059 +11060 +11061 +11062 +11063 +11064 +11065 +11066 +11067 +11068 +11069 +11070 +11071 +11072 +11073 +11074 +11075 +11076 +11077 +11078 +11079 +11080 +11081 +11082 +11083 +11084 +11085 +11086 +11087 +11088 +11089 +11090 +11091 +11092 +11093 +11094 +11095 +11096 +11097 +11098 +11099 +11100 +11101 +11102 +11103 +11104 +11105 +11106 +11107 +11108 +11109 +11110 +11111 +11112 +11113 +11114 +11115 +11116 +11117 +11118 +11119 +11120 +11121 +11122 +11123 +11124 +11125 +11126 +11127 +11128 +11129 +11130 +11131 +11132 +11133 +11134 +11135 +11136 +11137 +11138 +11139 +11140 +11141 +11142 +11143 +11144 +11145 +11146 +11147 +11148 +11149 +11150 +11151 +11152 +11153 +11154 +11155 +11156 +11157 +11158 +11159 +11160 +11161 +11162 +11163 +11164 +11165 +11166 +11167 +11168 +11169 +11170 +11171 +11172 +11173 +11174 +11175 +11176 +11177 +11178 +11179 +11180 +11181 +11182 +11183 +11184 +11185 +11186 +11187 +11188 +11189 +11190 +11191 +11192 +11193 +11194 +11195 +11196 +11197 +11198 +11199 +11200 +11201 +11202 +11203 +11204 +11205 +11206 +11207 +11208 +11209 +11210 +11211 +11212 +11213 +11214 +11215 +11216 +11217 +11218 +11219 +11220 +11221 +11222 +11223 +11224 +11225 +11226 +11227 +11228 +11229 +11230 +11231 +11232 +11233 +11234 +11235 +11236 +11237 +11238 +11239 +11240 +11241 +11242 +11243 +11244 +11245 +11246 +11247 +11248 +11249 +11250 +11251 +11252 +11253 +11254 +11255 +11256 +11257 +11258 +11259 +11260 +11261 +11262 +11263 +11264 +11265 +11266 +11267 +11268 +11269 +11270 +11271 +11272 +11273 +11274 +11275 +11276 +11277 +11278 +11279 +11280 +11281 +11282 +11283 +11284 +11285 +11286 +11287 +11288 +11289 +11290 +11291 +11292 +11293 +11294 +11295 +11296 +11297 +11298 +11299 +11300 +11301 +11302 +11303 +11304 +11305 +11306 +11307 +11308 +11309 +11310 +11311 +11312 +11313 +11314 +11315 +11316 +11317 +11318 +11319 +11320 +11321 +11322 +11323 +11324 +11325 +11326 +11327 +11328 +11329 +11330 +11331 +11332 +11333 +11334 +11335 +11336 +11337 +11338 +11339 +11340 +11341 +11342 +11343 +11344 +11345 +11346 +11347 +11348 +11349 +11350 +11351 +11352 +11353 +11354 +11355 +11356 +11357 +11358 +11359 +11360 +11361 +11362 +11363 +11364 +11365 +11366 +11367 +11368 +11369 +11370 +11371 +11372 +11373 +11374 +11375 +11376 +11377 +11378 +11379 +11380 +11381 +11382 +11383 +11384 +11385 +11386 +11387 +11388 +11389 +11390 +11391 +11392 +11393 +11394 +11395 +11396 +11397 +11398 +11399 +11400 +11401 +11402 +11403 +11404 +11405 +11406 +11407 +11408 +11409 +11410 +11411 +11412 +11413 +11414 +11415 +11416 +11417 +11418 +11419 +11420 +11421 +11422 +11423 +11424 +11425 +11426 +11427 +11428 +11429 +11430 +11431 +11432 +11433 +11434 +11435 +11436 +11437 +11438 +11439 +11440 +11441 +11442 +11443 +11444 +11445 +11446 +11447 +11448 +11449 +11450 +11451 +11452 +11453 +11454 +11455 +11456 +11457 +11458 +11459 +11460 +11461 +11462 +11463 +11464 +11465 +11466 +11467 +11468 +11469 +11470 +11471 +11472 +11473 +11474 +11475 +11476 +11477 +11478 +11479 +11480 +11481 +11482 +11483 +11484 +11485 +11486 +11487 +11488 +11489 +11490 +11491 +11492 +11493 +11494 +11495 +11496 +11497 +11498 +11499 +11500 +11501 +11502 +11503 +11504 +11505 +11506 +11507 +11508 +11509 +11510 +11511 +11512 +11513 +11514 +11515 +11516 +11517 +11518 +11519 +11520 +11521 +11522 +11523 +11524 +11525 +11526 +11527 +11528 +11529 +11530 +11531 +11532 +11533 +11534 +11535 +11536 +11537 +11538 +11539 +11540 +11541 +11542 +11543 +11544 +11545 +11546 +11547 +11548 +11549 +11550 +11551 +11552 +11553 +11554 +11555 +11556 +11557 +11558 +11559 +11560 +11561 +11562 +11563 +11564 +11565 +11566 +11567 +11568 +11569 +11570 +11571 +11572 +11573 +11574 +11575 +11576 +11577 +11578 +11579 +11580 +11581 +11582 +11583 +11584 +11585 +11586 +11587 +11588 +11589 +11590 +11591 +11592 +11593 +11594 +11595 +11596 +11597 +11598 +11599 +11600 +11601 +11602 +11603 +11604 +11605 +11606 +11607 +11608 +11609 +11610 +11611 +11612 +11613 +11614 +11615 +11616 +11617 +11618 +11619 +11620 +11621 +11622 +11623 +11624 +11625 +11626 +11627 +11628 +11629 +11630 +11631 +11632 +11633 +11634 +11635 +11636 +11637 +11638 +11639 +11640 +11641 +11642 +11643 +11644 +11645 +11646 +11647 +11648 +11649 +11650 +11651 +11652 +11653 +11654 +11655 +11656 +11657 +11658 +11659 +11660 +11661 +11662 +11663 +11664 +11665 +11666 +11667 +11668 +11669 +11670 +11671 +11672 +11673 +11674 +11675 +11676 +11677 +11678 +11679 +11680 +11681 +11682 +11683 +11684 +11685 +11686 +11687 +11688 +11689 +11690 +11691 +11692 +11693 +11694 +11695 +11696 +11697 +11698 +11699 +11700 +11701 +11702 +11703 +11704 +11705 +11706 +11707 +11708 +11709 +11710 +11711 +11712 +11713 +11714 +11715 +11716 +11717 +11718 +11719 +11720 +11721 +11722 +11723 +11724 +11725 +11726 +11727 +11728 +11729 +11730 +11731 +11732 +11733 +11734 +11735 +11736 +11737 +11738 +11739 +11740 +11741 +11742 +11743 +11744 +11745 +11746 +11747 +11748 +11749 +11750 +11751 +11752 +11753 +11754 +11755 +11756 +11757 +11758 +11759 +11760 +11761 +11762 +11763 +11764 +11765 +11766 +11767 +11768 +11769 +11770 +11771 +11772 +11773 +11774 +11775 +11776 +11777 +11778 +11779 +11780 +11781 +11782 +11783 +11784 +11785 +11786 +11787 +11788 +11789 +11790 +11791 +11792 +11793 +11794 +11795 +11796 +11797 +11798 +11799 +11800 +11801 +11802 +11803 +11804 +11805 +11806 +11807 +11808 +11809 +11810 +11811 +11812 +11813 +11814 +11815 +11816 +11817 +11818 +11819 +11820 +11821 +11822 +11823 +11824 +11825 +11826 +11827 +11828 +11829 +11830 +11831 +11832 +11833 +11834 +11835 +11836 +11837 +11838 +11839 +11840 +11841 +11842 +11843 +11844 +11845 +11846 +11847 +11848 +11849 +11850 +11851 +11852 +11853 +11854 +11855 +11856 +11857 +11858 +11859 +11860 +11861 +11862 +11863 +11864 +11865 +11866 +11867 +11868 +11869 +11870 +11871 +11872 +11873 +11874 +11875 +11876 +11877 +11878 +11879 +11880 +11881 +11882 +11883 +11884 +11885 +11886 +11887 +11888 +11889 +11890 +11891 +11892 +11893 +11894 +11895 +11896 +11897 +11898 +11899 +11900 +11901 +11902 +11903 +11904 +11905 +11906 +11907 +11908 +11909 +11910 +11911 +11912 +11913 +11914 +11915 +11916 +11917 +11918 +11919 +11920 +11921 +11922 +11923 +11924 +11925 +11926 +11927 +11928 +11929 +11930 +11931 +11932 +11933 +11934 +11935 +11936 +11937 +11938 +11939 +11940 +11941 +11942 +11943 +11944 +11945 +11946 +11947 +11948 +11949 +11950 +11951 +11952 +11953 +11954 +11955 +11956 +11957 +11958 +11959 +11960 +11961 +11962 +11963 +11964 +11965 +11966 +11967 +11968 +11969 +11970 +11971 +11972 +11973 +11974 +11975 +11976 +11977 +11978 +11979 +11980 +11981 +11982 +11983 +11984 +11985 +11986 +11987 +11988 +11989 +11990 +11991 +11992 +11993 +11994 +11995 +11996 +11997 +11998 +11999 +12000 +12001 +12002 +12003 +12004 +12005 +12006 +12007 +12008 +12009 +12010 +12011 +12012 +12013 +12014 +12015 +12016 +12017 +12018 +12019 +12020 +12021 +12022 +12023 +12024 +12025 +12026 +12027 +12028 +12029 +12030 +12031 +12032 +12033 +12034 +12035 +12036 +12037 +12038 +12039 +12040 +12041 +12042 +12043 +12044 +12045 +12046 +12047 +12048 +12049 +12050 +12051 +12052 +12053 +12054 +12055 +12056 +12057 +12058 +12059 +12060 +12061 +12062 +12063 +12064 +12065 +12066 +12067 +12068 +12069 +12070 +12071 +12072 +12073 +12074 +12075 +12076 +12077 +12078 +12079 +12080 +12081 +12082 +12083 +12084 +12085 +12086 +12087 +12088 +12089 +12090 +12091 +12092 +12093 +12094 +12095 +12096 +12097 +12098 +12099 +12100 +12101 +12102 +12103 +12104 +12105 +12106 +12107 +12108 +12109 +12110 +12111 +12112 +12113 +12114 +12115 +12116 +12117 +12118 +12119 +12120 +12121 +12122 +12123 +12124 +12125 +12126 +12127 +12128 +12129 +12130 +12131 +12132 +12133 +12134 +12135 +12136 +12137 +12138 +12139 +12140 +12141 +12142 +12143 +12144 +12145 +12146 +12147 +12148 +12149 +12150 +12151 +12152 +12153 +12154 +12155 +12156 +12157 +12158 +12159 +12160 +12161 +12162 +12163 +12164 +12165 +12166 +12167 +12168 +12169 +12170 +12171 +12172 +12173 +12174 +12175 +12176 +12177 +12178 +12179 +12180 +12181 +12182 +12183 +12184 +12185 +12186 +12187 +12188 +12189 +12190 +12191 +12192 +12193 +12194 +12195 +12196 +12197 +12198 +12199 +12200 +12201 +12202 +12203 +12204 +12205 +12206 +12207 +12208 +12209 +12210 +12211 +12212 +12213 +12214 +12215 +12216 +12217 +12218 +12219 +12220 +12221 +12222 +12223 +12224 +12225 +12226 +12227 +12228 +12229 +12230 +12231 +12232 +12233 +12234 +12235 +12236 +12237 +12238 +12239 +12240 +12241 +12242 +12243 +12244 +12245 +12246 +12247 +12248 +12249 +12250 +12251 +12252 +12253 +12254 +12255 +12256 +12257 +12258 +12259 +12260 +12261 +12262 +12263 +12264 +12265 +12266 +12267 +12268 +12269 +12270 +12271 +12272 +12273 +12274 +12275 +12276 +12277 +12278 +12279 +12280 +12281 +12282 +12283 +12284 +12285 +12286 +12287 +12288 +12289 +12290 +12291 +12292 +12293 +12294 +12295 +12296 +12297 +12298 +12299 +12300 +12301 +12302 +12303 +12304 +12305 +12306 +12307 +12308 +12309 +12310 +12311 +12312 +12313 +12314 +12315 +12316 +12317 +12318 +12319 +12320 +12321 +12322 +12323 +12324 +12325 +12326 +12327 +12328 +12329 +12330 +12331 +12332 +12333 +12334 +12335 +12336 +12337 +12338 +12339 +12340 +12341 +12342 +12343 +12344 +12345 +12346 +12347 +12348 +12349 +12350 +12351 +12352 +12353 +12354 +12355 +12356 +12357 +12358 +12359 +12360 +12361 +12362 +12363 +12364 +12365 +12366 +12367 +12368 +12369 +12370 +12371 +12372 +12373 +12374 +12375 +12376 +12377 +12378 +12379 +12380 +12381 +12382 +12383 +12384 +12385 +12386 +12387 +12388 +12389 +12390 +12391 +12392 +12393 +12394 +12395 +12396 +12397 +12398 +12399 +12400 +12401 +12402 +12403 +12404 +12405 +12406 +12407 +12408 +12409 +12410 +12411 +12412 +12413 +12414 +12415 +12416 +12417 +12418 +12419 +12420 +12421 +12422 +12423 +12424 +12425 +12426 +12427 +12428 +12429 +12430 +12431 +12432 +12433 +12434 +12435 +12436 +12437 +12438 +12439 +12440 +12441 +12442 +12443 +12444 +12445 +12446 +12447 +12448 +12449 +12450 +12451 +12452 +12453 +12454 +12455 +12456 +12457 +12458 +12459 +12460 +12461 +12462 +12463 +12464 +12465 +12466 +12467 +12468 +12469 +12470 +12471 +12472 +12473 +12474 +12475 +12476 +12477 +12478 +12479 +12480 +12481 +12482 +12483 +12484 +12485 +12486 +12487 +12488 +12489 +12490 +12491 +12492 +12493 +12494 +12495 +12496 +12497 +12498 +12499 +12500 +12501 +12502 +12503 +12504 +12505 +12506 +12507 +12508 +12509 +12510 +12511 +12512 +12513 +12514 +12515 +12516 +12517 +12518 +12519 +12520 +12521 +12522 +12523 +12524 +12525 +12526 +12527 +12528 +12529 +12530 +12531 +12532 +12533 +12534 +12535 +12536 +12537 +12538 +12539 +12540 +12541 +12542 +12543 +12544 +12545 +12546 +12547 +12548 +12549 +12550 +12551 +12552 +12553 +12554 +12555 +12556 +12557 +12558 +12559 +12560 +12561 +12562 +12563 +12564 +12565 +12566 +12567 +12568 +12569 +12570 +12571 +12572 +12573 +12574 +12575 +12576 +12577 +12578 +12579 +12580 +12581 +12582 +12583 +12584 +12585 +12586 +12587 +12588 +12589 +12590 +12591 +12592 +12593 +12594 +12595 +12596 +12597 +12598 +12599 +12600 +12601 +12602 +12603 +12604 +12605 +12606 +12607 +12608 +12609 +12610 +12611 +12612 +12613 +12614 +12615 +12616 +12617 +12618 +12619 +12620 +12621 +12622 +12623 +12624 +12625 +12626 +12627 +12628 +12629 +12630 +12631 +12632 +12633 +12634 +12635 +12636 +12637 +12638 +12639 +12640 +12641 +12642 +12643 +12644 +12645 +12646 +12647 +12648 +12649 +12650 +12651 +12652 +12653 +12654 +12655 +12656 +12657 +12658 +12659 +12660 +12661 +12662 +12663 +12664 +12665 +12666 +12667 +12668 +12669 +12670 +12671 +12672 +12673 +12674 +12675 +12676 +12677 +12678 +12679 +12680 +12681 +12682 +12683 +12684 +12685 +12686 +12687 +12688 +12689 +12690 +12691 +12692 +12693 +12694 +12695 +12696 +12697 +12698 +12699 +12700 +12701 +12702 +12703 +12704 +12705 +12706 +12707 +12708 +12709 +12710 +12711 +12712 +12713 +12714 +12715 +12716 +12717 +12718 +12719 +12720 +12721 +12722 +12723 +12724 +12725 +12726 +12727 +12728 +12729 +12730 +12731 +12732 +12733 +12734 +12735 +12736 +12737 +12738 +12739 +12740 +12741 +12742 +12743 +12744 +12745 +12746 +12747 +12748 +12749 +12750 +12751 +12752 +12753 +12754 +12755 +12756 +12757 +12758 +12759 +12760 +12761 +12762 +12763 +12764 +12765 +12766 +12767 +12768 +12769 +12770 +12771 +12772 +12773 +12774 +12775 +12776 +12777 +12778 +12779 +12780 +12781 +12782 +12783 +12784 +12785 +12786 +12787 +12788 +12789 +12790 +12791 +12792 +12793 +12794 +12795 +12796 +12797 +12798 +12799 +12800 +12801 +12802 +12803 +12804 +12805 +12806 +12807 +12808 +12809 +12810 +12811 +12812 +12813 +12814 +12815 +12816 +12817 +12818 +12819 +12820 +12821 +12822 +12823 +12824 +12825 +12826 +12827 +12828 +12829 +12830 +12831 +12832 +12833 +12834 +12835 +12836 +12837 +12838 +12839 +12840 +12841 +12842 +12843 +12844 +12845 +12846 +12847 +12848 +12849 +12850 +12851 +12852 +12853 +12854 +12855 +12856 +12857 +12858 +12859 +12860 +12861 +12862 +12863 +12864 +12865 +12866 +12867 +12868 +12869 +12870 +12871 +12872 +12873 +12874 +12875 +12876 +12877 +12878 +12879 +12880 +12881 +12882 +12883 +12884 +12885 +12886 +12887 +12888 +12889 +12890 +12891 +12892 +12893 +12894 +12895 +12896 +12897 +12898 +12899 +12900 +12901 +12902 +12903 +12904 +12905 +12906 +12907 +12908 +12909 +12910 +12911 +12912 +12913 +12914 +12915 +12916 +12917 +12918 +12919 +12920 +12921 +12922 +12923 +12924 +12925 +12926 +12927 +12928 +12929 +12930 +12931 +12932 +12933 +12934 +12935 +12936 +12937 +12938 +12939 +12940 +12941 +12942 +12943 +12944 +12945 +12946 +12947 +12948 +12949 +12950 +12951 +12952 +12953 +12954 +12955 +12956 +12957 +12958 +12959 +12960 +12961 +12962 +12963 +12964 +12965 +12966 +12967 +12968 +12969 +12970 +12971 +12972 +12973 +12974 +12975 +12976 +12977 +12978 +12979 +12980 +12981 +12982 +12983 +12984 +12985 +12986 +12987 +12988 +12989 +12990 +12991 +12992 +12993 +12994 +12995 +12996 +12997 +12998 +12999 +13000 +13001 +13002 +13003 +13004 +13005 +13006 +13007 +13008 +13009 +13010 +13011 +13012 +13013 +13014 +13015 +13016 +13017 +13018 +13019 +13020 +13021 +13022 +13023 +13024 +13025 +13026 +13027 +13028 +13029 +13030 +13031 +13032 +13033 +13034 +13035 +13036 +13037 +13038 +13039 +13040 +13041 +13042 +13043 +13044 +13045 +13046 +13047 +13048 +13049 +13050 +13051 +13052 +13053 +13054 +13055 +13056 +13057 +13058 +13059 +13060 +13061 +13062 +13063 +13064 +13065 +13066 +13067 +13068 +13069 +13070 +13071 +13072 +13073 +13074 +13075 +13076 +13077 +13078 +13079 +13080 +13081 +13082 +13083 +13084 +13085 +13086 +13087 +13088 +13089 +13090 +13091 +13092 +13093 +13094 +13095 +13096 +13097 +13098 +13099 +13100 +13101 +13102 +13103 +13104 +13105 +13106 +13107 +13108 +13109 +13110 +13111 +13112 +13113 +13114 +13115 +13116 +13117 +13118 +13119 +13120 +13121 +13122 +13123 +13124 +13125 +13126 +13127 +13128 +13129 +13130 +13131 +13132 +13133 +13134 +13135 +13136 +13137 +13138 +13139 +13140 +13141 +13142 +13143 +13144 +13145 +13146 +13147 +13148 +13149 +13150 +13151 +13152 +13153 +13154 +13155 +13156 +13157 +13158 +13159 +13160 +13161 +13162 +13163 +13164 +13165 +13166 +13167 +13168 +13169 +13170 +13171 +13172 +13173 +13174 +13175 +13176 +13177 +13178 +13179 +13180 +13181 +13182 +13183 +13184 +13185 +13186 +13187 +13188 +13189 +13190 +13191 +13192 +13193 +13194 +13195 +13196 +13197 +13198 +13199 +13200 +13201 +13202 +13203 +13204 +13205 +13206 +13207 +13208 +13209 +13210 +13211 +13212 +13213 +13214 +13215 +13216 +13217 +13218 +13219 +13220 +13221 +13222 +13223 +13224 +13225 +13226 +13227 +13228 +13229 +13230 +13231 +13232 +13233 +13234 +13235 +13236 +13237 +13238 +13239 +13240 +13241 +13242 +13243 +13244 +13245 +13246 +13247 +13248 +13249 +13250 +13251 +13252 +13253 +13254 +13255 +13256 +13257 +13258 +13259 +13260 +13261 +13262 +13263 +13264 +13265 +13266 +13267 +13268 +13269 +13270 +13271 +13272 +13273 +13274 +13275 +13276 +13277 +13278 +13279 +13280 +13281 +13282 +13283 +13284 +13285 +13286 +13287 +13288 +13289 +13290 +13291 +13292 +13293 +13294 +13295 +13296 +13297 +13298 +13299 +13300 +13301 +13302 +13303 +13304 +13305 +13306 +13307 +13308 +13309 +13310 +13311 +13312 +13313 +13314 +13315 +13316 +13317 +13318 +13319 +13320 +13321 +13322 +13323 +13324 +13325 +13326 +13327 +13328 +13329 +13330 +13331 +13332 +13333 +13334 +13335 +13336 +13337 +13338 +13339 +13340 +13341 +13342 +13343 +13344 +13345 +13346 +13347 +13348 +13349 +13350 +13351 +13352 +13353 +13354 +13355 +13356 +13357 +13358 +13359 +13360 +13361 +13362 +13363 +13364 +13365 +13366 +13367 +13368 +13369 +13370 +13371 +13372 +13373 +13374 +13375 +13376 +13377 +13378 +13379 +13380 +13381 +13382 +13383 +13384 +13385 +13386 +13387 +13388 +13389 +13390 +13391 +13392 +13393 +13394 +13395 +13396 +13397 +13398 +13399 +13400 +13401 +13402 +13403 +13404 +13405 +13406 +13407 +13408 +13409 +13410 +13411 +13412 +13413 +13414 +13415 +13416 +13417 +13418 +13419 +13420 +13421 +13422 +13423 +13424 +13425 +13426 +13427 +13428 +13429 +13430 +13431 +13432 +13433 +13434 +13435 +13436 +13437 +13438 +13439 +13440 +13441 +13442 +13443 +13444 +13445 +13446 +13447 +13448 +13449 +13450 +13451 +13452 +13453 +13454 +13455 +13456 +13457 +13458 +13459 +13460 +13461 +13462 +13463 +13464 +13465 +13466 +13467 +13468 +13469 +13470 +13471 +13472 +13473 +13474 +13475 +13476 +13477 +13478 +13479 +13480 +13481 +13482 +13483 +13484 +13485 +13486 +13487 +13488 +13489 +13490 +13491 +13492 +13493 +13494 +13495 +13496 +13497 +13498 +13499 +13500 +13501 +13502 +13503 +13504 +13505 +13506 +13507 +13508 +13509 +13510 +13511 +13512 +13513 +13514 +13515 +13516 +13517 +13518 +13519 +13520 +13521 +13522 +13523 +13524 +13525 +13526 +13527 +13528 +13529 +13530 +13531 +13532 +13533 +13534 +13535 +13536 +13537 +13538 +13539 +13540 +13541 +13542 +13543 +13544 +13545 +13546 +13547 +13548 +13549 +13550 +13551 +13552 +13553 +13554 +13555 +13556 +13557 +13558 +13559 +13560 +13561 +13562 +13563 +13564 +13565 +13566 +13567 +13568 +13569 +13570 +13571 +13572 +13573 +13574 +13575 +13576 +13577 +13578 +13579 +13580 +13581 +13582 +13583 +13584 +13585 +13586 +13587 +13588 +13589 +13590 +13591 +13592 +13593 +13594 +13595 +13596 +13597 +13598 +13599 +13600 +13601 +13602 +13603 +13604 +13605 +13606 +13607 +13608 +13609 +13610 +13611 +13612 +13613 +13614 +13615 +13616 +13617 +13618 +13619 +13620 +13621 +13622 +13623 +13624 +13625 +13626 +13627 +13628 +13629 +13630 +13631 +13632 +13633 +13634 +13635 +13636 +13637 +13638 +13639 +13640 +13641 +13642 +13643 +13644 +13645 +13646 +13647 +13648 +13649 +13650 +13651 +13652 +13653 +13654 +13655 +13656 +13657 +13658 +13659 +13660 +13661 +13662 +13663 +13664 +13665 +13666 +13667 +13668 +13669 +13670 +13671 +13672 +13673 +13674 +13675 +13676 +13677 +13678 +13679 +13680 +13681 +13682 +13683 +13684 +13685 +13686 +13687 +13688 +13689 +13690 +13691 +13692 +13693 +13694 +13695 +13696 +13697 +13698 +13699 +13700 +13701 +13702 +13703 +13704 +13705 +13706 +13707 +13708 +13709 +13710 +13711 +13712 +13713 +13714 +13715 +13716 +13717 +13718 +13719 +13720 +13721 +13722 +13723 +13724 +13725 +13726 +13727 +13728 +13729 +13730 +13731 +13732 +13733 +13734 +13735 +13736 +13737 +13738 +13739 +13740 +13741 +13742 +13743 +13744 +13745 +13746 +13747 +13748 +13749 +13750 +13751 +13752 +13753 +13754 +13755 +13756 +13757 +13758 +13759 +13760 +13761 +13762 +13763 +13764 +13765 +13766 +13767 +13768 +13769 +13770 +13771 +13772 +13773 +13774 +13775 +13776 +13777 +13778 +13779 +13780 +13781 +13782 +13783 +13784 +13785 +13786 +13787 +13788 +13789 +13790 +13791 +13792 +13793 +13794 +13795 +13796 +13797 +13798 +13799 +13800 +13801 +13802 +13803 +13804 +13805 +13806 +13807 +13808 +13809 +13810 +13811 +13812 +13813 +13814 +13815 +13816 +13817 +13818 +13819 +13820 +13821 +13822 +13823 +13824 +13825 +13826 +13827 +13828 +13829 +13830 +13831 +13832 +13833 +13834 +13835 +13836 +13837 +13838 +13839 +13840 +13841 +13842 +13843 +13844 +13845 +13846 +13847 +13848 +13849 +13850 +13851 +13852 +13853 +13854 +13855 +13856 +13857 +13858 +13859 +13860 +13861 +13862 +13863 +13864 +13865 +13866 +13867 +13868 +13869 +13870 +13871 +13872 +13873 +13874 +13875 +13876 +13877 +13878 +13879 +13880 +13881 +13882 +13883 +13884 +13885 +13886 +13887 +13888 +13889 +13890 +13891 +13892 +13893 +13894 +13895 +13896 +13897 +13898 +13899 +13900 +13901 +13902 +13903 +13904 +13905 +13906 +13907 +13908 +13909 +13910 +13911 +13912 +13913 +13914 +13915 +13916 +13917 +13918 +13919 +13920 +13921 +13922 +13923 +13924 +13925 +13926 +13927 +13928 +13929 +13930 +13931 +13932 +13933 +13934 +13935 +13936 +13937 +13938 +13939 +13940 +13941 +13942 +13943 +13944 +13945 +13946 +13947 +13948 +13949 +13950 +13951 +13952 +13953 +13954 +13955 +13956 +13957 +13958 +13959 +13960 +13961 +13962 +13963 +13964 +13965 +13966 +13967 +13968 +13969 +13970 +13971 +13972 +13973 +13974 +13975 +13976 +13977 +13978 +13979 +13980 +13981 +13982 +13983 +13984 +13985 +13986 +13987 +13988 +13989 +13990 +13991 +13992 +13993 +13994 +13995 +13996 +13997 +13998 +13999 +14000 +14001 +14002 +14003 +14004 +14005 +14006 +14007 +14008 +14009 +14010 +14011 +14012 +14013 +14014 +14015 +14016 +14017 +14018 +14019 +14020 +14021 +14022 +14023 +14024 +14025 +14026 +14027 +14028 +14029 +14030 +14031 +14032 +14033 +14034 +14035 +14036 +14037 +14038 +14039 +14040 +14041 +14042 +14043 +14044 +14045 +14046 +14047 +14048 +14049 +14050 +14051 +14052 +14053 +14054 +14055 +14056 +14057 +14058 +14059 +14060 +14061 +14062 +14063 +14064 +14065 +14066 +14067 +14068 +14069 +14070 +14071 +14072 +14073 +14074 +14075 +14076 +14077 +14078 +14079 +14080 +14081 +14082 +14083 +14084 +14085 +14086 +14087 +14088 +14089 +14090 +14091 +14092 +14093 +14094 +14095 +14096 +14097 +14098 +14099 +14100 +14101 +14102 +14103 +14104 +14105 +14106 +14107 +14108 +14109 +14110 +14111 +14112 +14113 +14114 +14115 +14116 +14117 +14118 +14119 +14120 +14121 +14122 +14123 +14124 +14125 +14126 +14127 +14128 +14129 +14130 +14131 +14132 +14133 +14134 +14135 +14136 +14137 +14138 +14139 +14140 +14141 +14142 +14143 +14144 +14145 +14146 +14147 +14148 +14149 +14150 +14151 +14152 +14153 +14154 +14155 +14156 +14157 +14158 +14159 +14160 +14161 +14162 +14163 +14164 +14165 +14166 +14167 +14168 +14169 +14170 +14171 +14172 +14173 +14174 +14175 +14176 +14177 +14178 +14179 +14180 +14181 +14182 +14183 +14184 +14185 +14186 +14187 +14188 +14189 +14190 +14191 +14192 +14193 +14194 +14195 +14196 +14197 +14198 +14199 +14200 +14201 +14202 +14203 +14204 +14205 +14206 +14207 +14208 +14209 +14210 +14211 +14212 +14213 +14214 +14215 +14216 +14217 +14218 +14219 +14220 +14221 +14222 +14223 +14224 +14225 +14226 +14227 +14228 +14229 +14230 +14231 +14232 +14233 +14234 +14235 +14236 +14237 +14238 +14239 +14240 +14241 +14242 +14243 +14244 +14245 +14246 +14247 +14248 +14249 +14250 +14251 +14252 +14253 +14254 +14255 +14256 +14257 +14258 +14259 +14260 +14261 +14262 +14263 +14264 +14265 +14266 +14267 +14268 +14269 +14270 +14271 +14272 +14273 +14274 +14275 +14276 +14277 +14278 +14279 +14280 +14281 +14282 +14283 +14284 +14285 +14286 +14287 +14288 +14289 +14290 +14291 +14292 +14293 +14294 +14295 +14296 +14297 +14298 +14299 +14300 +14301 +14302 +14303 +14304 +14305 +14306 +14307 +14308 +14309 +14310 +14311 +14312 +14313 +14314 +14315 +14316 +14317 +14318 +14319 +14320 +14321 +14322 +14323 +14324 +14325 +14326 +14327 +14328 +14329 +14330 +14331 +14332 +14333 +14334 +14335 +14336 +14337 +14338 +14339 +14340 +14341 +14342 +14343 +14344 +14345 +14346 +14347 +14348 +14349 +14350 +14351 +14352 +14353 +14354 +14355 +14356 +14357 +14358 +14359 +14360 +14361 +14362 +14363 +14364 +14365 +14366 +14367 +14368 +14369 +14370 +14371 +14372 +14373 +14374 +14375 +14376 +14377 +14378 +14379 +14380 +14381 +14382 +14383 +14384 +14385 +14386 +14387 +14388 +14389 +14390 +14391 +14392 +14393 +14394 +14395 +14396 +14397 +14398 +14399 +14400 +14401 +14402 +14403 +14404 +14405 +14406 +14407 +14408 +14409 +14410 +14411 +14412 +14413 +14414 +14415 +14416 +14417 +14418 +14419 +14420 +14421 +14422 +14423 +14424 +14425 +14426 +14427 +14428 +14429 +14430 +14431 +14432 +14433 +14434 +14435 +14436 +14437 +14438 +14439 +14440 +14441 +14442 +14443 +14444 +14445 +14446 +14447 +14448 +14449 +14450 +14451 +14452 +14453 +14454 +14455 +14456 +14457 +14458 +14459 +14460 +14461 +14462 +14463 +14464 +14465 +14466 +14467 +14468 +14469 +14470 +14471 +14472 +14473 +14474 +14475 +14476 +14477 +14478 +14479 +14480 +14481 +14482 +14483 +14484 +14485 +14486 +14487 +14488 +14489 +14490 +14491 +14492 +14493 +14494 +14495 +14496 +14497 +14498 +14499 +14500 +14501 +14502 +14503 +14504 +14505 +14506 +14507 +14508 +14509 +14510 +14511 +14512 +14513 +14514 +14515 +14516 +14517 +14518 +14519 +14520 +14521 +14522 +14523 +14524 +14525 +14526 +14527 +14528 +14529 +14530 +14531 +14532 +14533 +14534 +14535 +14536 +14537 +14538 +14539 +14540 +14541 +14542 +14543 +14544 +14545 +14546 +14547 +14548 +14549 +14550 +14551 +14552 +14553 +14554 +14555 +14556 +14557 +14558 +14559 +14560 +14561 +14562 +14563 +14564 +14565 +14566 +14567 +14568 +14569 +14570 +14571 +14572 +14573 +14574 +14575 +14576 +14577 +14578 +14579 +14580 +14581 +14582 +14583 +14584 +14585 +14586 +14587 +14588 +14589 +14590 +14591 +14592 +14593 +14594 +14595 +14596 +14597 +14598 +14599 +14600 +14601 +14602 +14603 +14604 +14605 +14606 +14607 +14608 +14609 +14610 +14611 +14612 +14613 +14614 +14615 +14616 +14617 +14618 +14619 +14620 +14621 +14622 +14623 +14624 +14625 +14626 +14627 +14628 +14629 +14630 +14631 +14632 +14633 +14634 +14635 +14636 +14637 +14638 +14639 +14640 +14641 +14642 +14643 +14644 +14645 +14646 +14647 +14648 +14649 +14650 +14651 +14652 +14653 +14654 +14655 +14656 +14657 +14658 +14659 +14660 +14661 +14662 +14663 +14664 +14665 +14666 +14667 +14668 +14669 +14670 +14671 +14672 +14673 +14674 +14675 +14676 +14677 +14678 +14679 +14680 +14681 +14682 +14683 +14684 +14685 +14686 +14687 +14688 +14689 +14690 +14691 +14692 +14693 +14694 +14695 +14696 +14697 +14698 +14699 +14700 +14701 +14702 +14703 +14704 +14705 +14706 +14707 +14708 +14709 +14710 +14711 +14712 +14713 +14714 +14715 +14716 +14717 +14718 +14719 +14720 +14721 +14722 +14723 +14724 +14725 +14726 +14727 +14728 +14729 +14730 +14731 +14732 +14733 +14734 +14735 +14736 +14737 +14738 +14739 +14740 +14741 +14742 +14743 +14744 +14745 +14746 +14747 +14748 +14749 +14750 +14751 +14752 +14753 +14754 +14755 +14756 +14757 +14758 +14759 +14760 +14761 +14762 +14763 +14764 +14765 +14766 +14767 +14768 +14769 +14770 +14771 +14772 +14773 +14774 +14775 +14776 +14777 +14778 +14779 +14780 +14781 +14782 +14783 +14784 +14785 +14786 +14787 +14788 +14789 +14790 +14791 +14792 +14793 +14794 +14795 +14796 +14797 +14798 +14799 +14800 +14801 +14802 +14803 +14804 +14805 +14806 +14807 +14808 +14809 +14810 +14811 +14812 +14813 +14814 +14815 +14816 +14817 +14818 +14819 +14820 +14821 +14822 +14823 +14824 +14825 +14826 +14827 +14828 +14829 +14830 +14831 +14832 +14833 +14834 +14835 +14836 +14837 +14838 +14839 +14840 +14841 +14842 +14843 +14844 +14845 +14846 +14847 +14848 +14849 +14850 +14851 +14852 +14853 +14854 +14855 +14856 +14857 +14858 +14859 +14860 +14861 +14862 +14863 +14864 +14865 +14866 +14867 +14868 +14869 +14870 +14871 +14872 +14873 +14874 +14875 +14876 +14877 +14878 +14879 +14880 +14881 +14882 +14883 +14884 +14885 +14886 +14887 +14888 +14889 +14890 +14891 +14892 +14893 +14894 +14895 +14896 +14897 +14898 +14899 +14900 +14901 +14902 +14903 +14904 +14905 +14906 +14907 +14908 +14909 +14910 +14911 +14912 +14913 +14914 +14915 +14916 +14917 +14918 +14919 +14920 +14921 +14922 +14923 +14924 +14925 +14926 +14927 +14928 +14929 +14930 +14931 +14932 +14933 +14934 +14935 +14936 +14937 +14938 +14939 +14940 +14941 +14942 +14943 +14944 +14945 +14946 +14947 +14948 +14949 +14950 +14951 +14952 +14953 +14954 +14955 +14956 +14957 +14958 +14959 +14960 +14961 +14962 +14963 +14964 +14965 +14966 +14967 +14968 +14969 +14970 +14971 +14972 +14973 +14974 +14975 +14976 +14977 +14978 +14979 +14980 +14981 +14982 +14983 +14984 +14985 +14986 +14987 +14988 +14989 +14990 +14991 +14992 +14993 +14994 +14995 +14996 +14997 +14998 +14999 +15000 +15001 +15002 +15003 +15004 +15005 +15006 +15007 +15008 +15009 +15010 +15011 +15012 +15013 +15014 +15015 +15016 +15017 +15018 +15019 +15020 +15021 +15022 +15023 +15024 +15025 +15026 +15027 +15028 +15029 +15030 +15031 +15032 +15033 +15034 +15035 +15036 +15037 +15038 +15039 +15040 +15041 +15042 +15043 +15044 +15045 +15046 +15047 +15048 +15049 +15050 +15051 +15052 +15053 +15054 +15055 +15056 +15057 +15058 +15059 +15060 +15061 +15062 +15063 +15064 +15065 +15066 +15067 +15068 +15069 +15070 +15071 +15072 +15073 +15074 +15075 +15076 +15077 +15078 +15079 +15080 +15081 +15082 +15083 +15084 +15085 +15086 +15087 +15088 +15089 +15090 +15091 +15092 +15093 +15094 +15095 +15096 +15097 +15098 +15099 +15100 +15101 +15102 +15103 +15104 +15105 +15106 +15107 +15108 +15109 +15110 +15111 +15112 +15113 +15114 +15115 +15116 +15117 +15118 +15119 +15120 +15121 +15122 +15123 +15124 +15125 +15126 +15127 +15128 +15129 +15130 +15131 +15132 +15133 +15134 +15135 +15136 +15137 +15138 +15139 +15140 +15141 +15142 +15143 +15144 +15145 +15146 +15147 +15148 +15149 +15150 +15151 +15152 +15153 +15154 +15155 +15156 +15157 +15158 +15159 +15160 +15161 +15162 +15163 +15164 +15165 +15166 +15167 +15168 +15169 +15170 +15171 +15172 +15173 +15174 +15175 +15176 +15177 +15178 +15179 +15180 +15181 +15182 +15183 +15184 +15185 +15186 +15187 +15188 +15189 +15190 +15191 +15192 +15193 +15194 +15195 +15196 +15197 +15198 +15199 +15200 +15201 +15202 +15203 +15204 +15205 +15206 +15207 +15208 +15209 +15210 +15211 +15212 +15213 +15214 +15215 +15216 +15217 +15218 +15219 +15220 +15221 +15222 +15223 +15224 +15225 +15226 +15227 +15228 +15229 +15230 +15231 +15232 +15233 +15234 +15235 +15236 +15237 +15238 +15239 +15240 +15241 +15242 +15243 +15244 +15245 +15246 +15247 +15248 +15249 +15250 +15251 +15252 +15253 +15254 +15255 +15256 +15257 +15258 +15259 +15260 +15261 +15262 +15263 +15264 +15265 +15266 +15267 +15268 +15269 +15270 +15271 +15272 +15273 +15274 +15275 +15276 +15277 +15278 +15279 +15280 +15281 +15282 +15283 +15284 +15285 +15286 +15287 +15288 +15289 +15290 +15291 +15292 +15293 +15294 +15295 +15296 +15297 +15298 +15299 +15300 +15301 +15302 +15303 +15304 +15305 +15306 +15307 +15308 +15309 +15310 +15311 +15312 +15313 +15314 +15315 +15316 +15317 +15318 +15319 +15320 +15321 +15322 +15323 +15324 +15325 +15326 +15327 +15328 +15329 +15330 +15331 +15332 +15333 +15334 +15335 +15336 +15337 +15338 +15339 +15340 +15341 +15342 +15343 +15344 +15345 +15346 +15347 +15348 +15349 +15350 +15351 +15352 +15353 +15354 +15355 +15356 +15357 +15358 +15359 +15360 +15361 +15362 +15363 +15364 +15365 +15366 +15367 +15368 +15369 +15370 +15371 +15372 +15373 +15374 +15375 +15376 +15377 +15378 +15379 +15380 +15381 +15382 +15383 +15384 +15385 +15386 +15387 +15388 +15389 +15390 +15391 +15392 +15393 +15394 +15395 +15396 +15397 +15398 +15399 +15400 +15401 +15402 +15403 +15404 +15405 +15406 +15407 +15408 +15409 +15410 +15411 +15412 +15413 +15414 +15415 +15416 +15417 +15418 +15419 +15420 +15421 +15422 +15423 +15424 +15425 +15426 +15427 +15428 +15429 +15430 +15431 +15432 +15433 +15434 +15435 +15436 +15437 +15438 +15439 +15440 +15441 +15442 +15443 +15444 +15445 +15446 +15447 +15448 +15449 +15450 +15451 +15452 +15453 +15454 +15455 +15456 +15457 +15458 +15459 +15460 +15461 +15462 +15463 +15464 +15465 +15466 +15467 +15468 +15469 +15470 +15471 +15472 +15473 +15474 +15475 +15476 +15477 +15478 +15479 +15480 +15481 +15482 +15483 +15484 +15485 +15486 +15487 +15488 +15489 +15490 +15491 +15492 +15493 +15494 +15495 +15496 +15497 +15498 +15499 +15500 +15501 +15502 +15503 +15504 +15505 +15506 +15507 +15508 +15509 +15510 +15511 +15512 +15513 +15514 +15515 +15516 +15517 +15518 +15519 +15520 +15521 +15522 +15523 +15524 +15525 +15526 +15527 +15528 +15529 +15530 +15531 +15532 +15533 +15534 +15535 +15536 +15537 +15538 +15539 +15540 +15541 +15542 +15543 +15544 +15545 +15546 +15547 +15548 +15549 +15550 +15551 +15552 +15553 +15554 +15555 +15556 +15557 +15558 +15559 +15560 +15561 +15562 +15563 +15564 +15565 +15566 +15567 +15568 +15569 +15570 +15571 +15572 +15573 +15574 +15575 +15576 +15577 +15578 +15579 +15580 +15581 +15582 +15583 +15584 +15585 +15586 +15587 +15588 +15589 +15590 +15591 +15592 +15593 +15594 +15595 +15596 +15597 +15598 +15599 +15600 +15601 +15602 +15603 +15604 +15605 +15606 +15607 +15608 +15609 +15610 +15611 +15612 +15613 +15614 +15615 +15616 +15617 +15618 +15619 +15620 +15621 +15622 +15623 +15624 +15625 +15626 +15627 +15628 +15629 +15630 +15631 +15632 +15633 +15634 +15635 +15636 +15637 +15638 +15639 +15640 +15641 +15642 +15643 +15644 +15645 +15646 +15647 +15648 +15649 +15650 +15651 +15652 +15653 +15654 +15655 +15656 +15657 +15658 +15659 +15660 +15661 +15662 +15663 +15664 +15665 +15666 +15667 +15668 +15669 +15670 +15671 +15672 +15673 +15674 +15675 +15676 +15677 +15678 +15679 +15680 +15681 +15682 +15683 +15684 +15685 +15686 +15687 +15688 +15689 +15690 +15691 +15692 +15693 +15694 +15695 +15696 +15697 +15698 +15699 +15700 +15701 +15702 +15703 +15704 +15705 +15706 +15707 +15708 +15709 +15710 +15711 +15712 +15713 +15714 +15715 +15716 +15717 +15718 +15719 +15720 +15721 +15722 +15723 +15724 +15725 +15726 +15727 +15728 +15729 +15730 +15731 +15732 +15733 +15734 +15735 +15736 +15737 +15738 +15739 +15740 +15741 +15742 +15743 +15744 +15745 +15746 +15747 +15748 +15749 +15750 +15751 +15752 +15753 +15754 +15755 +15756 +15757 +15758 +15759 +15760 +15761 +15762 +15763 +15764 +15765 +15766 +15767 +15768 +15769 +15770 +15771 +15772 +15773 +15774 +15775 +15776 +15777 +15778 +15779 +15780 +15781 +15782 +15783 +15784 +15785 +15786 +15787 +15788 +15789 +15790 +15791 +15792 +15793 +15794 +15795 +15796 +15797 +15798 +15799 +15800 +15801 +15802 +15803 +15804 +15805 +15806 +15807 +15808 +15809 +15810 +15811 +15812 +15813 +15814 +15815 +15816 +15817 +15818 +15819 +15820 +15821 +15822 +15823 +15824 +15825 +15826 +15827 +15828 +15829 +15830 +15831 +15832 +15833 +15834 +15835 +15836 +15837 +15838 +15839 +15840 +15841 +15842 +15843 +15844 +15845 +15846 +15847 +15848 +15849 +15850 +15851 +15852 +15853 +15854 +15855 +15856 +15857 +15858 +15859 +15860 +15861 +15862 +15863 +15864 +15865 +15866 +15867 +15868 +15869 +15870 +15871 +15872 +15873 +15874 +15875 +15876 +15877 +15878 +15879 +15880 +15881 +15882 +15883 +15884 +15885 +15886 +15887 +15888 +15889 +15890 +15891 +15892 +15893 +15894 +15895 +15896 +15897 +15898 +15899 +15900 +15901 +15902 +15903 +15904 +15905 +15906 +15907 +15908 +15909 +15910 +15911 +15912 +15913 +15914 +15915 +15916 +15917 +15918 +15919 +15920 +15921 +15922 +15923 +15924 +15925 +15926 +15927 +15928 +15929 +15930 +15931 +15932 +15933 +15934 +15935 +15936 +15937 +15938 +15939 +15940 +15941 +15942 +15943 +15944 +15945 +15946 +15947 +15948 +15949 +15950 +15951 +15952 +15953 +15954 +15955 +15956 +15957 +15958 +15959 +15960 +15961 +15962 +15963 +15964 +15965 +15966 +15967 +15968 +15969 +15970 +15971 +15972 +15973 +15974 +15975 +15976 +15977 +15978 +15979 +15980 +15981 +15982 +15983 +15984 +15985 +15986 +15987 +15988 +15989 +15990 +15991 +15992 +15993 +15994 +15995 +15996 +15997 +15998 +15999 +16000 +16001 +16002 +16003 +16004 +16005 +16006 +16007 +16008 +16009 +16010 +16011 +16012 +16013 +16014 +16015 +16016 +16017 +16018 +16019 +16020 +16021 +16022 +16023 +16024 +16025 +16026 +16027 +16028 +16029 +16030 +16031 +16032 +16033 +16034 +16035 +16036 +16037 +16038 +16039 +16040 +16041 +16042 +16043 +16044 +16045 +16046 +16047 +16048 +16049 +16050 +16051 +16052 +16053 +16054 +16055 +16056 +16057 +16058 +16059 +16060 +16061 +16062 +16063 +16064 +16065 +16066 +16067 +16068 +16069 +16070 +16071 +16072 +16073 +16074 +16075 +16076 +16077 +16078 +16079 +16080 +16081 +16082 +16083 +16084 +16085 +16086 +16087 +16088 +16089 +16090 +16091 +16092 +16093 +16094 +16095 +16096 +16097 +16098 +16099 +16100 +16101 +16102 +16103 +16104 +16105 +16106 +16107 +16108 +16109 +16110 +16111 +16112 +16113 +16114 +16115 +16116 +16117 +16118 +16119 +16120 +16121 +16122 +16123 +16124 +16125 +16126 +16127 +16128 +16129 +16130 +16131 +16132 +16133 +16134 +16135 +16136 +16137 +16138 +16139 +16140 +16141 +16142 +16143 +16144 +16145 +16146 +16147 +16148 +16149 +16150 +16151 +16152 +16153 +16154 +16155 +16156 +16157 +16158 +16159 +16160 +16161 +16162 +16163 +16164 +16165 +16166 +16167 +16168 +16169 +16170 +16171 +16172 +16173 +16174 +16175 +16176 +16177 +16178 +16179 +16180 +16181 +16182 +16183 +16184 +16185 +16186 +16187 +16188 +16189 +16190 +16191 +16192 +16193 +16194 +16195 +16196 +16197 +16198 +16199 +16200 +16201 +16202 +16203 +16204 +16205 +16206 +16207 +16208 +16209 +16210 +16211 +16212 +16213 +16214 +16215 +16216 +16217 +16218 +16219 +16220 +16221 +16222 +16223 +16224 +16225 +16226 +16227 +16228 +16229 +16230 +16231 +16232 +16233 +16234 +16235 +16236 +16237 +16238 +16239 +16240 +16241 +16242 +16243 +16244 +16245 +16246 +16247 +16248 +16249 +16250 +16251 +16252 +16253 +16254 +16255 +16256 +16257 +16258 +16259 +16260 +16261 +16262 +16263 +16264 +16265 +16266 +16267 +16268 +16269 +16270 +16271 +16272 +16273 +16274 +16275 +16276 +16277 +16278 +16279 +16280 +16281 +16282 +16283 +16284 +16285 +16286 +16287 +16288 +16289 +16290 +16291 +16292 +16293 +16294 +16295 +16296 +16297 +16298 +16299 +16300 +16301 +16302 +16303 +16304 +16305 +16306 +16307 +16308 +16309 +16310 +16311 +16312 +16313 +16314 +16315 +16316 +16317 +16318 +16319 +16320 +16321 +16322 +16323 +16324 +16325 +16326 +16327 +16328 +16329 +16330 +16331 +16332 +16333 +16334 +16335 +16336 +16337 +16338 +16339 +16340 +16341 +16342 +16343 +16344 +16345 +16346 +16347 +16348 +16349 +16350 +16351 +16352 +16353 +16354 +16355 +16356 +16357 +16358 +16359 +16360 +16361 +16362 +16363 +16364 +16365 +16366 +16367 +16368 +16369 +16370 +16371 +16372 +16373 +16374 +16375 +16376 +16377 +16378 +16379 +16380 +16381 +16382 +16383 +16384 +16385 +16386 +16387 +16388 +16389 +16390 +16391 +16392 +16393 +16394 +16395 +16396 +16397 +16398 +16399 +16400 +16401 +16402 +16403 +16404 +16405 +16406 +16407 +16408 +16409 +16410 +16411 +16412 +16413 +16414 +16415 +16416 +16417 +16418 +16419 +16420 +16421 +16422 +16423 +16424 +16425 +16426 +16427 +16428 +16429 +16430 +16431 +16432 +16433 +16434 +16435 +16436 +16437 +16438 +16439 +16440 +16441 +16442 +16443 +16444 +16445 +16446 +16447 +16448 +16449 +16450 +16451 +16452 +16453 +16454 +16455 +16456 +16457 +16458 +16459 +16460 +16461 +16462 +16463 +16464 +16465 +16466 +16467 +16468 +16469 +16470 +16471 +16472 +16473 +16474 +16475 +16476 +16477 +16478 +16479 +16480 +16481 +16482 +16483 +16484 +16485 +16486 +16487 +16488 +16489 +16490 +16491 +16492 +16493 +16494 +16495 +16496 +16497 +16498 +16499 +16500 +16501 +16502 +16503 +16504 +16505 +16506 +16507 +16508 +16509 +16510 +16511 +16512 +16513 +16514 +16515 +16516 +16517 +16518 +16519 +16520 +16521 +16522 +16523 +16524 +16525 +16526 +16527 +16528 +16529 +16530 +16531 +16532 +16533 +16534 +16535 +16536 +16537 +16538 +16539 +16540 +16541 +16542 +16543 +16544 +16545 +16546 +16547 +16548 +16549 +16550 +16551 +16552 +16553 +16554 +16555 +16556 +16557 +16558 +16559 +16560 +16561 +16562 +16563 +16564 +16565 +16566 +16567 +16568 +16569 +16570 +16571 +16572 +16573 +16574 +16575 +16576 +16577 +16578 +16579 +16580 +16581 +16582 +16583 +16584 +16585 +16586 +16587 +16588 +16589 +16590 +16591 +16592 +16593 +16594 +16595 +16596 +16597 +16598 +16599 +16600 +16601 +16602 +16603 +16604 +16605 +16606 +16607 +16608 +16609 +16610 +16611 +16612 +16613 +16614 +16615 +16616 +16617 +16618 +16619 +16620 +16621 +16622 +16623 +16624 +16625 +16626 +16627 +16628 +16629 +16630 +16631 +16632 +16633 +16634 +16635 +16636 +16637 +16638 +16639 +16640 +16641 +16642 +16643 +16644 +16645 +16646 +16647 +16648 +16649 +16650 +16651 +16652 +16653 +16654 +16655 +16656 +16657 +16658 +16659 +16660 +16661 +16662 +16663 +16664 +16665 +16666 +16667 +16668 +16669 +16670 +16671 +16672 +16673 +16674 +16675 +16676 +16677 +16678 +16679 +16680 +16681 +16682 +16683 +16684 +16685 +16686 +16687 +16688 +16689 +16690 +16691 +16692 +16693 +16694 +16695 +16696 +16697 +16698 +16699 +16700 +16701 +16702 +16703 +16704 +16705 +16706 +16707 +16708 +16709 +16710 +16711 +16712 +16713 +16714 +16715 +16716 +16717 +16718 +16719 +16720 +16721 +16722 +16723 +16724 +16725 +16726 +16727 +16728 +16729 +16730 +16731 +16732 +16733 +16734 +16735 +16736 +16737 +16738 +16739 +16740 +16741 +16742 +16743 +16744 +16745 +16746 +16747 +16748 +16749 +16750 +16751 +16752 +16753 +16754 +16755 +16756 +16757 +16758 +16759 +16760 +16761 +16762 +16763 +16764 +16765 +16766 +16767 +16768 +16769 +16770 +16771 +16772 +16773 +16774 +16775 +16776 +16777 +16778 +16779 +16780 +16781 +16782 +16783 +16784 +16785 +16786 +16787 +16788 +16789 +16790 +16791 +16792 +16793 +16794 +16795 +16796 +16797 +16798 +16799 +16800 +16801 +16802 +16803 +16804 +16805 +16806 +16807 +16808 +16809 +16810 +16811 +16812 +16813 +16814 +16815 +16816 +16817 +16818 +16819 +16820 +16821 +16822 +16823 +16824 +16825 +16826 +16827 +16828 +16829 +16830 +16831 +16832 +16833 +16834 +16835 +16836 +16837 +16838 +16839 +16840 +16841 +16842 +16843 +16844 +16845 +16846 +16847 +16848 +16849 +16850 +16851 +16852 +16853 +16854 +16855 +16856 +16857 +16858 +16859 +16860 +16861 +16862 +16863 +16864 +16865 +16866 +16867 +16868 +16869 +16870 +16871 +16872 +16873 +16874 +16875 +16876 +16877 +16878 +16879 +16880 +16881 +16882 +16883 +16884 +16885 +16886 +16887 +16888 +16889 +16890 +16891 +16892 +16893 +16894 +16895 +16896 +16897 +16898 +16899 +16900 +16901 +16902 +16903 +16904 +16905 +16906 +16907 +16908 +16909 +16910 +16911 +16912 +16913 +16914 +16915 +16916 +16917 +16918 +16919 +16920 +16921 +16922 +16923 +16924 +16925 +16926 +16927 +16928 +16929 +16930 +16931 +16932 +16933 +16934 +16935 +16936 +16937 +16938 +16939 +16940 +16941 +16942 +16943 +16944 +16945 +16946 +16947 +16948 +16949 +16950 +16951 +16952 +16953 +16954 +16955 +16956 +16957 +16958 +16959 +16960 +16961 +16962 +16963 +16964 +16965 +16966 +16967 +16968 +16969 +16970 +16971 +16972 +16973 +16974 +16975 +16976 +16977 +16978 +16979 +16980 +16981 +16982 +16983 +16984 +16985 +16986 +16987 +16988 +16989 +16990 +16991 +16992 +16993 +16994 +16995 +16996 +16997 +16998 +16999 +17000 +17001 +17002 +17003 +17004 +17005 +17006 +17007 +17008 +17009 +17010 +17011 +17012 +17013 +17014 +17015 +17016 +17017 +17018 +17019 +17020 +17021 +17022 +17023 +17024 +17025 +17026 +17027 +17028 +17029 +17030 +17031 +17032 +17033 +17034 +17035 +17036 +17037 +17038 +17039 +17040 +17041 +17042 +17043 +17044 +17045 +17046 +17047 +17048 +17049 +17050 +17051 +17052 +17053 +17054 +17055 +17056 +17057 +17058 +17059 +17060 +17061 +17062 +17063 +17064 +17065 +17066 +17067 +17068 +17069 +17070 +17071 +17072 +17073 +17074 +17075 +17076 +17077 +17078 +17079 +17080 +17081 +17082 +17083 +17084 +17085 +17086 +17087 +17088 +17089 +17090 +17091 +17092 +17093 +17094 +17095 +17096 +17097 +17098 +17099 +17100 +17101 +17102 +17103 +17104 +17105 +17106 +17107 +17108 +17109 +17110 +17111 +17112 +17113 +17114 +17115 +17116 +17117 +17118 +17119 +17120 +17121 +17122 +17123 +17124 +17125 +17126 +17127 +17128 +17129 +17130 +17131 +17132 +17133 +17134 +17135 +17136 +17137 +17138 +17139 +17140 +17141 +17142 +17143 +17144 +17145 +17146 +17147 +17148 +17149 +17150 +17151 +17152 +17153 +17154 +17155 +17156 +17157 +17158 +17159 +17160 +17161 +17162 +17163 +17164 +17165 +17166 +17167 +17168 +17169 +17170 +17171 +17172 +17173 +17174 +17175 +17176 +17177 +17178 +17179 +17180 +17181 +17182 +17183 +17184 +17185 +17186 +17187 +17188 +17189 +17190 +17191 +17192 +17193 +17194 +17195 +17196 +17197 +17198 +17199 +17200 +17201 +17202 +17203 +17204 +17205 +17206 +17207 +17208 +17209 +17210 +17211 +17212 +17213 +17214 +17215 +17216 +17217 +17218 +17219 +17220 +17221 +17222 +17223 +17224 +17225 +17226 +17227 +17228 +17229 +17230 +17231 +17232 +17233 +17234 +17235 +17236 +17237 +17238 +17239 +17240 +17241 +17242 +17243 +17244 +17245 +17246 +17247 +17248 +17249 +17250 +17251 +17252 +17253 +17254 +17255 +17256 +17257 +17258 +17259 +17260 +17261 +17262 +17263 +17264 +17265 +17266 +17267 +17268 +17269 +17270 +17271 +17272 +17273 +17274 +17275 +17276 +17277 +17278 +17279 +17280 +17281 +17282 +17283 +17284 +17285 +17286 +17287 +17288 +17289 +17290 +17291 +17292 +17293 +17294 +17295 +17296 +17297 +17298 +17299 +17300 +17301 +17302 +17303 +17304 +17305 +17306 +17307 +17308 +17309 +17310 +17311 +17312 +17313 +17314 +17315 +17316 +17317 +17318 +17319 +17320 +17321 +17322 +17323 +17324 +17325 +17326 +17327 +17328 +17329 +17330 +17331 +17332 +17333 +17334 +17335 +17336 +17337 +17338 +17339 +17340 +17341 +17342 +17343 +17344 +17345 +17346 +17347 +17348 +17349 +17350 +17351 +17352 +17353 +17354 +17355 +17356 +17357 +17358 +17359 +17360 +17361 +17362 +17363 +17364 +17365 +17366 +17367 +17368 +17369 +17370 +17371 +17372 +17373 +17374 +17375 +17376 +17377 +17378 +17379 +17380 +17381 +17382 +17383 +17384 +17385 +17386 +17387 +17388 +17389 +17390 +17391 +17392 +17393 +17394 +17395 +17396 +17397 +17398 +17399 +17400 +17401 +17402 +17403 +17404 +17405 +17406 +17407 +17408 +17409 +17410 +17411 +17412 +17413 +17414 +17415 +17416 +17417 +17418 +17419 +17420 +17421 +17422 +17423 +17424 +17425 +17426 +17427 +17428 +17429 +17430 +17431 +17432 +17433 +17434 +17435 +17436 +17437 +17438 +17439 +17440 +17441 +17442 +17443 +17444 +17445 +17446 +17447 +17448 +17449 +17450 +17451 +17452 +17453 +17454 +17455 +17456 +17457 +17458 +17459 +17460 +17461 +17462 +17463 +17464 +17465 +17466 +17467 +17468 +17469 +17470 +17471 +17472 +17473 +17474 +17475 +17476 +17477 +17478 +17479 +17480 +17481 +17482 +17483 +17484 +17485 +17486 +17487 +17488 +17489 +17490 +17491 +17492 +17493 +17494 +17495 +17496 +17497 +17498 +17499 +17500 +17501 +17502 +17503 +17504 +17505 +17506 +17507 +17508 +17509 +17510 +17511 +17512 +17513 +17514 +17515 +17516 +17517 +17518 +17519 +17520 +17521 +17522 +17523 +17524 +17525 +17526 +17527 +17528 +17529 +17530 +17531 +17532 +17533 +17534 +17535 +17536 +17537 +17538 +17539 +17540 +17541 +17542 +17543 +17544 +17545 +17546 +17547 +17548 +17549 +17550 +17551 +17552 +17553 +17554 +17555 +17556 +17557 +17558 +17559 +17560 +17561 +17562 +17563 +17564 +17565 +17566 +17567 +17568 +17569 +17570 +17571 +17572 +17573 +17574 +17575 +17576 +17577 +17578 +17579 +17580 +17581 +17582 +17583 +17584 +17585 +17586 +17587 +17588 +17589 +17590 +17591 +17592 +17593 +17594 +17595 +17596 +17597 +17598 +17599 +17600 +17601 +17602 +17603 +17604 +17605 +17606 +17607 +17608 +17609 +17610 +17611 +17612 +17613 +17614 +17615 +17616 +17617 +17618 +17619 +17620 +17621 +17622 +17623 +17624 +17625 +17626 +17627 +17628 +17629 +17630 +17631 +17632 +17633 +17634 +17635 +17636 +17637 +17638 +17639 +17640 +17641 +17642 +17643 +17644 +17645 +17646 +17647 +17648 +17649 +17650 +17651 +17652 +17653 +17654 +17655 +17656 +17657 +17658 +17659 +17660 +17661 +17662 +17663 +17664 +17665 +17666 +17667 +17668 +17669 +17670 +17671 +17672 +17673 +17674 +17675 +17676 +17677 +17678 +17679 +17680 +17681 +17682 +17683 +17684 +17685 +17686 +17687 +17688 +17689 +17690 +17691 +17692 +17693 +17694 +17695 +17696 +17697 +17698 +17699 +17700 +17701 +17702 +17703 +17704 +17705 +17706 +17707 +17708 +17709 +17710 +17711 +17712 +17713 +17714 +17715 +17716 +17717 +17718 +17719 +17720 +17721 +17722 +17723 +17724 +17725 +17726 +17727 +17728 +17729 +17730 +17731 +17732 +17733 +17734 +17735 +17736 +17737 +17738 +17739 +17740 +17741 +17742 +17743 +17744 +17745 +17746 +17747 +17748 +17749 +17750 +17751 +17752 +17753 +17754 +17755 +17756 +17757 +17758 +17759 +17760 +17761 +17762 +17763 +17764 +17765 +17766 +17767 +17768 +17769 +17770 +17771 +17772 +17773 +17774 +17775 +17776 +17777 +17778 +17779 +17780 +17781 +17782 +17783 +17784 +17785 +17786 +17787 +17788 +17789 +17790 +17791 +17792 +17793 +17794 +17795 +17796 +17797 +17798 +17799 +17800 +17801 +17802 +17803 +17804 +17805 +17806 +17807 +17808 +17809 +17810 +17811 +17812 +17813 +17814 +17815 +17816 +17817 +17818 +17819 +17820 +17821 +17822 +17823 +17824 +17825 +17826 +17827 +17828 +17829 +17830 +17831 +17832 +17833 +17834 +17835 +17836 +17837 +17838 +17839 +17840 +17841 +17842 +17843 +17844 +17845 +17846 +17847 +17848 +17849 +17850 +17851 +17852 +17853 +17854 +17855 +17856 +17857 +17858 +17859 +17860 +17861 +17862 +17863 +17864 +17865 +17866 +17867 +17868 +17869 +17870 +17871 +17872 +17873 +17874 +17875 +17876 +17877 +17878 +17879 +17880 +17881 +17882 +17883 +17884 +17885 +17886 +17887 +17888 +17889 +17890 +17891 +17892 +17893 +17894 +17895 +17896 +17897 +17898 +17899 +17900 +17901 +17902 +17903 +17904 +17905 +17906 +17907 +17908 +17909 +17910 +17911 +17912 +17913 +17914 +17915 +17916 +17917 +17918 +17919 +17920 +17921 +17922 +17923 +17924 +17925 +17926 +17927 +17928 +17929 +17930 +17931 +17932 +17933 +17934 +17935 +17936 +17937 +17938 +17939 +17940 +17941 +17942 +17943 +17944 +17945 +17946 +17947 +17948 +17949 +17950 +17951 +17952 +17953 +17954 +17955 +17956 +17957 +17958 +17959 +17960 +17961 +17962 +17963 +17964 +17965 +17966 +17967 +17968 +17969 +17970 +17971 +17972 +17973 +17974 +17975 +17976 +17977 +17978 +17979 +17980 +17981 +17982 +17983 +17984 +17985 +17986 +17987 +17988 +17989 +17990 +17991 +17992 +17993 +17994 +17995 +17996 +17997 +17998 +17999 +18000 +18001 +18002 +18003 +18004 +18005 +18006 +18007 +18008 +18009 +18010 +18011 +18012 +18013 +18014 +18015 +18016 +18017 +18018 +18019 +18020 +18021 +18022 +18023 +18024 +18025 +18026 +18027 +18028 +18029 +18030 +18031 +18032 +18033 +18034 +18035 +18036 +18037 +18038 +18039 +18040 +18041 +18042 +18043 +18044 +18045 +18046 +18047 +18048 +18049 +18050 +18051 +18052 +18053 +18054 +18055 +18056 +18057 +18058 +18059 +18060 +18061 +18062 +18063 +18064 +18065 +18066 +18067 +18068 +18069 +18070 +18071 +18072 +18073 +18074 +18075 +18076 +18077 +18078 +18079 +18080 +18081 +18082 +18083 +18084 +18085 +18086 +18087 +18088 +18089 +18090 +18091 +18092 +18093 +18094 +18095 +18096 +18097 +18098 +18099 +18100 +18101 +18102 +18103 +18104 +18105 +18106 +18107 +18108 +18109 +18110 +18111 +18112 +18113 +18114 +18115 +18116 +18117 +18118 +18119 +18120 +18121 +18122 +18123 +18124 +18125 +18126 +18127 +18128 +18129 +18130 +18131 +18132 +18133 +18134 +18135 +18136 +18137 +18138 +18139 +18140 +18141 +18142 +18143 +18144 +18145 +18146 +18147 +18148 +18149 +18150 +18151 +18152 +18153 +18154 +18155 +18156 +18157 +18158 +18159 +18160 +18161 +18162 +18163 +18164 +18165 +18166 +18167 +18168 +18169 +18170 +18171 +18172 +18173 +18174 +18175 +18176 +18177 +18178 +18179 +18180 +18181 +18182 +18183 +18184 +18185 +18186 +18187 +18188 +18189 +18190 +18191 +18192 +18193 +18194 +18195 +18196 +18197 +18198 +18199 +18200 +18201 +18202 +18203 +18204 +18205 +18206 +18207 +18208 +18209 +18210 +18211 +18212 +18213 +18214 +18215 +18216 +18217 +18218 +18219 +18220 +18221 +18222 +18223 +18224 +18225 +18226 +18227 +18228 +18229 +18230 +18231 +18232 +18233 +18234 +18235 +18236 +18237 +18238 +18239 +18240 +18241 +18242 +18243 +18244 +18245 +18246 +18247 +18248 +18249 +18250 +18251 +18252 +18253 +18254 +18255 +18256 +18257 +18258 +18259 +18260 +18261 +18262 +18263 +18264 +18265 +18266 +18267 +18268 +18269 +18270 +18271 +18272 +18273 +18274 +18275 +18276 +18277 +18278 +18279 +18280 +18281 +18282 +18283 +18284 +18285 +18286 +18287 +18288 +18289 +18290 +18291 +18292 +18293 +18294 +18295 +18296 +18297 +18298 +18299 +18300 +18301 +18302 +18303 +18304 +18305 +18306 +18307 +18308 +18309 +18310 +18311 +18312 +18313 +18314 +18315 +18316 +18317 +18318 +18319 +18320 +18321 +18322 +18323 +18324 +18325 +18326 +18327 +18328 +18329 +18330 +18331 +18332 +18333 +18334 +18335 +18336 +18337 +18338 +18339 +18340 +18341 +18342 +18343 +18344 +18345 +18346 +18347 +18348 +18349 +18350 +18351 +18352 +18353 +18354 +18355 +18356 +18357 +18358 +18359 +18360 +18361 +18362 +18363 +18364 +18365 +18366 +18367 +18368 +18369 +18370 +18371 +18372 +18373 +18374 +18375 +18376 +18377 +18378 +18379 +18380 +18381 +18382 +18383 +18384 +18385 +18386 +18387 +18388 +18389 +18390 +18391 +18392 +18393 +18394 +18395 +18396 +18397 +18398 +18399 +18400 +18401 +18402 +18403 +18404 +18405 +18406 +18407 +18408 +18409 +18410 +18411 +18412 +18413 +18414 +18415 +18416 +18417 +18418 +18419 +18420 +18421 +18422 +18423 +18424 +18425 +18426 +18427 +18428 +18429 +18430 +18431 +18432 +18433 +18434 +18435 +18436 +18437 +18438 +18439 +18440 +18441 +18442 +18443 +18444 +18445 +18446 +18447 +18448 +18449 +18450 +18451 +18452 +18453 +18454 +18455 +18456 +18457 +18458 +18459 +18460 +18461 +18462 +18463 +18464 +18465 +18466 +18467 +18468 +18469 +18470 +18471 +18472 +18473 +18474 +18475 +18476 +18477 +18478 +18479 +18480 +18481 +18482 +18483 +18484 +18485 +18486 +18487 +18488 +18489 +18490 +18491 +18492 +18493 +18494 +18495 +18496 +18497 +18498 +18499 +18500 +18501 +18502 +18503 +18504 +18505 +18506 +18507 +18508 +18509 +18510 +18511 +18512 +18513 +18514 +18515 +18516 +18517 +18518 +18519 +18520 +18521 +18522 +18523 +18524 +18525 +18526 +18527 +18528 +18529 +18530 +18531 +18532 +18533 +18534 +18535 +18536 +18537 +18538 +18539 +18540 +18541 +18542 +18543 +18544 +18545 +18546 +18547 +18548 +18549 +18550 +18551 +18552 +18553 +18554 +18555 +18556 +18557 +18558 +18559 +18560 +18561 +18562 +18563 +18564 +18565 +18566 +18567 +18568 +18569 +18570 +18571 +18572 +18573 +18574 +18575 +18576 +18577 +18578 +18579 +18580 +18581 +18582 +18583 +18584 +18585 +18586 +18587 +18588 +18589 +18590 +18591 +18592 +18593 +18594 +18595 +18596 +18597 +18598 +18599 +18600 +18601 +18602 +18603 +18604 +18605 +18606 +18607 +18608 +18609 +18610 +18611 +18612 +18613 +18614 +18615 +18616 +18617 +18618 +18619 +18620 +18621 +18622 +18623 +18624 +18625 +18626 +18627 +18628 +18629 +18630 +18631 +18632 +18633 +18634 +18635 +18636 +18637 +18638 +18639 +18640 +18641 +18642 +18643 +18644 +18645 +18646 +18647 +18648 +18649 +18650 +18651 +18652 +18653 +18654 +18655 +18656 +18657 +18658 +18659 +18660 +18661 +18662 +18663 +18664 +18665 +18666 +18667 +18668 +18669 +18670 +18671 +18672 +18673 +18674 +18675 +18676 +18677 +18678 +18679 +18680 +18681 +18682 +18683 +18684 +18685 +18686 +18687 +18688 +18689 +18690 +18691 +18692 +18693 +18694 +18695 +18696 +18697 +18698 +18699 +18700 +18701 +18702 +18703 +18704 +18705 +18706 +18707 +18708 +18709 +18710 +18711 +18712 +18713 +18714 +18715 +18716 +18717 +18718 +18719 +18720 +18721 +18722 +18723 +18724 +18725 +18726 +18727 +18728 +18729 +18730 +18731 +18732 +18733 +18734 +18735 +18736 +18737 +18738 +18739 +18740 +18741 +18742 +18743 +18744 +18745 +18746 +18747 +18748 +18749 +18750 +18751 +18752 +18753 +18754 +18755 +18756 +18757 +18758 +18759 +18760 +18761 +18762 +18763 +18764 +18765 +18766 +18767 +18768 +18769 +18770 +18771 +18772 +18773 +18774 +18775 +18776 +18777 +18778 +18779 +18780 +18781 +18782 +18783 +18784 +18785 +18786 +18787 +18788 +18789 +18790 +18791 +18792 +18793 +18794 +18795 +18796 +18797 +18798 +18799 +18800 +18801 +18802 +18803 +18804 +18805 +18806 +18807 +18808 +18809 +18810 +18811 +18812 +18813 +18814 +18815 +18816 +18817 +18818 +18819 +18820 +18821 +18822 +18823 +18824 +18825 +18826 +18827 +18828 +18829 +18830 +18831 +18832 +18833 +18834 +18835 +18836 +18837 +18838 +18839 +18840 +18841 +18842 +18843 +18844 +18845 +18846 +18847 +18848 +18849 +18850 +18851 +18852 +18853 +18854 +18855 +18856 +18857 +18858 +18859 +18860 +18861 +18862 +18863 +18864 +18865 +18866 +18867 +18868 +18869 +18870 +18871 +18872 +18873 +18874 +18875 +18876 +18877 +18878 +18879 +18880 +18881 +18882 +18883 +18884 +18885 +18886 +18887 +18888 +18889 +18890 +18891 +18892 +18893 +18894 +18895 +18896 +18897 +18898 +18899 +18900 +18901 +18902 +18903 +18904 +18905 +18906 +18907 +18908 +18909 +18910 +18911 +18912 +18913 +18914 +18915 +18916 +18917 +18918 +18919 +18920 +18921 +18922 +18923 +18924 +18925 +18926 +18927 +18928 +18929 +18930 +18931 +18932 +18933 +18934 +18935 +18936 +18937 +18938 +18939 +18940 +18941 +18942 +18943 +18944 +18945 +18946 +18947 +18948 +18949 +18950 +18951 +18952 +18953 +18954 +18955 +18956 +18957 +18958 +18959 +18960 +18961 +18962 +18963 +18964 +18965 +18966 +18967 +18968 +18969 +18970 +18971 +18972 +18973 +18974 +18975 +18976 +18977 +18978 +18979 +18980 +18981 +18982 +18983 +18984 +18985 +18986 +18987 +18988 +18989 +18990 +18991 +18992 +18993 +18994 +18995 +18996 +18997 +18998 +18999 +19000 +19001 +19002 +19003 +19004 +19005 +19006 +19007 +19008 +19009 +19010 +19011 +19012 +19013 +19014 +19015 +19016 +19017 +19018 +19019 +19020 +19021 +19022 +19023 +19024 +19025 +19026 +19027 +19028 +19029 +19030 +19031 +19032 +19033 +19034 +19035 +19036 +19037 +19038 +19039 +19040 +19041 +19042 +19043 +19044 +19045 +19046 +19047 +19048 +19049 +19050 +19051 +19052 +19053 +19054 +19055 +19056 +19057 +19058 +19059 +19060 +19061 +19062 +19063 +19064 +19065 +19066 +19067 +19068 +19069 +19070 +19071 +19072 +19073 +19074 +19075 +19076 +19077 +19078 +19079 +19080 +19081 +19082 +19083 +19084 +19085 +19086 +19087 +19088 +19089 +19090 +19091 +19092 +19093 +19094 +19095 +19096 +19097 +19098 +19099 +19100 +19101 +19102 +19103 +19104 +19105 +19106 +19107 +19108 +19109 +19110 +19111 +19112 +19113 +19114 +19115 +19116 +19117 +19118 +19119 +19120 +19121 +19122 +19123 +19124 +19125 +19126 +19127 +19128 +19129 +19130 +19131 +19132 +19133 +19134 +19135 +19136 +19137 +19138 +19139 +19140 +19141 +19142 +19143 +19144 +19145 +19146 +19147 +19148 +19149 +19150 +19151 +19152 +19153 +19154 +19155 +19156 +19157 +19158 +19159 +19160 +19161 +19162 +19163 +19164 +19165 +19166 +19167 +19168 +19169 +19170 +19171 +19172 +19173 +19174 +19175 +19176 +19177 +19178 +19179 +19180 +19181 +19182 +19183 +19184 +19185 +19186 +19187 +19188 +19189 +19190 +19191 +19192 +19193 +19194 +19195 +19196 +19197 +19198 +19199 +19200 +19201 +19202 +19203 +19204 +19205 +19206 +19207 +19208 +19209 +19210 +19211 +19212 +19213 +19214 +19215 +19216 +19217 +19218 +19219 +19220 +19221 +19222 +19223 +19224 +19225 +19226 +19227 +19228 +19229 +19230 +19231 +19232 +19233 +19234 +19235 +19236 +19237 +19238 +19239 +19240 +19241 +19242 +19243 +19244 +19245 +19246 +19247 +19248 +19249 +19250 +19251 +19252 +19253 +19254 +19255 +19256 +19257 +19258 +19259 +19260 +19261 +19262 +19263 +19264 +19265 +19266 +19267 +19268 +19269 +19270 +19271 +19272 +19273 +19274 +19275 +19276 +19277 +19278 +19279 +19280 +19281 +19282 +19283 +19284 +19285 +19286 +19287 +19288 +19289 +19290 +19291 +19292 +19293 +19294 +19295 +19296 +19297 +19298 +19299 +19300 +19301 +19302 +19303 +19304 +19305 +19306 +19307 +19308 +19309 +19310 +19311 +19312 +19313 +19314 +19315 +19316 +19317 +19318 +19319 +19320 +19321 +19322 +19323 +19324 +19325 +19326 +19327 +19328 +19329 +19330 +19331 +19332 +19333 +19334 +19335 +19336 +19337 +19338 +19339 +19340 +19341 +19342 +19343 +19344 +19345 +19346 +19347 +19348 +19349 +19350 +19351 +19352 +19353 +19354 +19355 +19356 +19357 +19358 +19359 +19360 +19361 +19362 +19363 +19364 +19365 +19366 +19367 +19368 +19369 +19370 +19371 +19372 +19373 +19374 +19375 +19376 +19377 +19378 +19379 +19380 +19381 +19382 +19383 +19384 +19385 +19386 +19387 +19388 +19389 +19390 +19391 +19392 +19393 +19394 +19395 +19396 +19397 +19398 +19399 +19400 +19401 +19402 +19403 +19404 +19405 +19406 +19407 +19408 +19409 +19410 +19411 +19412 +19413 +19414 +19415 +19416 +19417 +19418 +19419 +19420 +19421 +19422 +19423 +19424 +19425 +19426 +19427 +19428 +19429 +19430 +19431 +19432 +19433 +19434 +19435 +19436 +19437 +19438 +19439 +19440 +19441 +19442 +19443 +19444 +19445 +19446 +19447 +19448 +19449 +19450 +19451 +19452 +19453 +19454 +19455 +19456 +19457 +19458 +19459 +19460 +19461 +19462 +19463 +19464 +19465 +19466 +19467 +19468 +19469 +19470 +19471 +19472 +19473 +19474 +19475 +19476 +19477 +19478 +19479 +19480 +19481 +19482 +19483 +19484 +19485 +19486 +19487 +19488 +19489 +19490 +19491 +19492 +19493 +19494 +19495 +19496 +19497 +19498 +19499 +19500 +19501 +19502 +19503 +19504 +19505 +19506 +19507 +19508 +19509 +19510 +19511 +19512 +19513 +19514 +19515 +19516 +19517 +19518 +19519 +19520 +19521 +19522 +19523 +19524 +19525 +19526 +19527 +19528 +19529 +19530 +19531 +19532 +19533 +19534 +19535 +19536 +19537 +19538 +19539 +19540 +19541 +19542 +19543 +19544 +19545 +19546 +19547 +19548 +19549 +19550 +19551 +19552 +19553 +19554 +19555 +19556 +19557 +19558 +19559 +19560 +19561 +19562 +19563 +19564 +19565 +19566 +19567 +19568 +19569 +19570 +19571 +19572 +19573 +19574 +19575 +19576 +19577 +19578 +19579 +19580 +19581 +19582 +19583 +19584 +19585 +19586 +19587 +19588 +19589 +19590 +19591 +19592 +19593 +19594 +19595 +19596 +19597 +19598 +19599 +19600 +19601 +19602 +19603 +19604 +19605 +19606 +19607 +19608 +19609 +19610 +19611 +19612 +19613 +19614 +19615 +19616 +19617 +19618 +19619 +19620 +19621 +19622 +19623 +19624 +19625 +19626 +19627 +19628 +19629 +19630 +19631 +19632 +19633 +19634 +19635 +19636 +19637 +19638 +19639 +19640 +19641 +19642 +19643 +19644 +19645 +19646 +19647 +19648 +19649 +19650 +19651 +19652 +19653 +19654 +19655 +19656 +19657 +19658 +19659 +19660 +19661 +19662 +19663 +19664 +19665 +19666 +19667 +19668 +19669 +19670 +19671 +19672 +19673 +19674 +19675 +19676 +19677 +19678 +19679 +19680 +19681 +19682 +19683 +19684 +19685 +19686 +19687 +19688 +19689 +19690 +19691 +19692 +19693 +19694 +19695 +19696 +19697 +19698 +19699 +19700 +19701 +19702 +19703 +19704 +19705 +19706 +19707 +19708 +19709 +19710 +19711 +19712 +19713 +19714 +19715 +19716 +19717 +19718 +19719 +19720 +19721 +19722 +19723 +19724 +19725 +19726 +19727 +19728 +19729 +19730 +19731 +19732 +19733 +19734 +19735 +19736 +19737 +19738 +19739 +19740 +19741 +19742 +19743 +19744 +19745 +19746 +19747 +19748 +19749 +19750 +19751 +19752 +19753 +19754 +19755 +19756 +19757 +19758 +19759 +19760 +19761 +19762 +19763 +19764 +19765 +19766 +19767 +19768 +19769 +19770 +19771 +19772 +19773 +19774 +19775 +19776 +19777 +19778 +19779 +19780 +19781 +19782 +19783 +19784 +19785 +19786 +19787 +19788 +19789 +19790 +19791 +19792 +19793 +19794 +19795 +19796 +19797 +19798 +19799 +19800 +19801 +19802 +19803 +19804 +19805 +19806 +19807 +19808 +19809 +19810 +19811 +19812 +19813 +19814 +19815 +19816 +19817 +19818 +19819 +19820 +19821 +19822 +19823 +19824 +19825 +19826 +19827 +19828 +19829 +19830 +19831 +19832 +19833 +19834 +19835 +19836 +19837 +19838 +19839 +19840 +19841 +19842 +19843 +19844 +19845 +19846 +19847 +19848 +19849 +19850 +19851 +19852 +19853 +19854 +19855 +19856 +19857 +19858 +19859 +19860 +19861 +19862 +19863 +19864 +19865 +19866 +19867 +19868 +19869 +19870 +19871 +19872 +19873 +19874 +19875 +19876 +19877 +19878 +19879 +19880 +19881 +19882 +19883 +19884 +19885 +19886 +19887 +19888 +19889 +19890 +19891 +19892 +19893 +19894 +19895 +19896 +19897 +19898 +19899 +19900 +19901 +19902 +19903 +19904 +19905 +19906 +19907 +19908 +19909 +19910 +19911 +19912 +19913 +19914 +19915 +19916 +19917 +19918 +19919 +19920 +19921 +19922 +19923 +19924 +19925 +19926 +19927 +19928 +19929 +19930 +19931 +19932 +19933 +19934 +19935 +19936 +19937 +19938 +19939 +19940 +19941 +19942 +19943 +19944 +19945 +19946 +19947 +19948 +19949 +19950 +19951 +19952 +19953 +19954 +19955 +19956 +19957 +19958 +19959 +19960 +19961 +19962 +19963 +19964 +19965 +19966 +19967 +19968 +19969 +19970 +19971 +19972 +19973 +19974 +19975 +19976 +19977 +19978 +19979 +19980 +19981 +19982 +19983 +19984 +19985 +19986 +19987 +19988 +19989 +19990 +19991 +19992 +19993 +19994 +19995 +19996 +19997 +19998 +19999 +20000 diff --git a/tests/fixtures/sort/ext_sort.txt b/tests/fixtures/sort/ext_sort.txt new file mode 100644 index 000000000..a409d67e1 --- /dev/null +++ b/tests/fixtures/sort/ext_sort.txt @@ -0,0 +1,20000 @@ +9155 +10575 +10442 +15874 +17013 +12130 +15558 +18263 +6574 +8957 +9851 +16606 +12331 +9865 +13795 +270 +6590 +11141 +4620 +5945 +10904 +8652 +8442 +8907 +1935 +13100 +3961 +7538 +4159 +11986 +4394 +9321 +15560 +5264 +5121 +11532 +6980 +2807 +19760 +8032 +5158 +13698 +4458 +9106 +3773 +1625 +9914 +8287 +5155 +14326 +18137 +19522 +9270 +6153 +1920 +12517 +13259 +17618 +9930 +3630 +15924 +4540 +263 +14212 +15620 +11328 +8704 +17848 +1614 +12587 +17970 +4542 +11976 +12885 +8743 +16323 +14582 +12101 +12472 +12620 +10713 +5148 +7522 +2417 +8602 +7860 +19596 +2892 +13359 +7731 +3707 +4628 +4710 +5642 +1610 +4784 +10128 +16341 +14168 +5829 +17901 +9447 +1041 +15193 +15260 +11224 +11723 +13368 +14011 +12200 +2001 +18479 +3965 +16642 +16680 +2384 +7557 +5539 +4305 +14588 +17386 +1359 +17721 +1142 +7287 +9946 +13139 +15022 +17237 +14454 +14358 +11297 +12485 +857 +19282 +117 +19179 +16040 +6978 +10743 +19161 +12308 +9509 +13233 +4666 +5850 +17040 +2473 +17323 +6728 +11500 +4401 +13887 +19944 +17601 +19731 +10715 +8327 +11196 +7944 +9068 +13253 +13913 +13419 +17649 +16894 +3333 +5747 +3000 +12724 +17772 +17341 +11854 +6042 +10362 +3438 +13828 +471 +17747 +13707 +17902 +8184 +3385 +19387 +46 +1848 +15848 +10768 +17481 +436 +17581 +4629 +13210 +5085 +19485 +44 +12549 +16407 +9646 +9884 +15802 +2461 +15134 +2936 +8156 +17411 +1228 +18200 +14616 +17443 +5512 +6017 +10137 +11424 +11940 +6655 +17669 +6147 +16570 +17281 +18915 +19689 +18013 +17895 +6925 +19766 +18634 +7195 +7001 +12335 +3908 +18754 +4833 +12486 +12892 +18879 +3691 +1693 +158 +8682 +1029 +3429 +644 +1664 +13972 +5352 +15422 +1928 +14561 +3322 +3570 +6298 +2251 +12216 +19823 +12658 +2081 +4177 +13843 +18454 +13755 +9340 +6373 +3290 +2893 +16274 +16610 +12782 +7269 +5681 +19372 +7843 +14844 +16236 +16004 +1136 +5759 +19347 +7684 +7435 +5892 +7305 +2504 +7064 +13264 +8781 +1327 +19943 +18122 +1803 +9999 +13695 +9742 +8274 +744 +2252 +10524 +1434 +4601 +6071 +10489 +19626 +10745 +18312 +872 +19166 +3417 +9272 +1433 +9431 +6486 +3532 +18452 +17830 +9835 +2495 +10475 +16725 +16019 +6594 +11355 +13055 +14782 +11924 +18838 +3563 +428 +12993 +14223 +6658 +2783 +1726 +6929 +13053 +19175 +8564 +4867 +19604 +17416 +9347 +2275 +16381 +9817 +11176 +14576 +6906 +9805 +14149 +2241 +11030 +453 +835 +15452 +2879 +4018 +17396 +16133 +1301 +3450 +10182 +2389 +19201 +4443 +14368 +2537 +7452 +1583 +13955 +3875 +7479 +17561 +3247 +16310 +5253 +8899 +6523 +10260 +13065 +11077 +15109 +7249 +12046 +5313 +8914 +19949 +2196 +3654 +7145 +12166 +18340 +17929 +12466 +7866 +3831 +15095 +2506 +17691 +12992 +6591 +9661 +19538 +2161 +3991 +6766 +18180 +182 +15952 +9709 +19601 +13427 +19071 +12698 +12157 +7963 +3485 +19327 +6029 +12948 +2261 +2844 +4864 +12148 +9187 +6695 +8171 +19771 +3782 +14122 +11658 +330 +10750 +6932 +4436 +15622 +5710 +18750 +5765 +10545 +10897 +16609 +9183 +802 +14708 +3423 +11557 +19098 +3641 +14490 +3249 +1355 +16886 +18500 +15309 +8010 +18543 +2342 +3813 +2135 +9055 +15148 +12720 +3253 +737 +11788 +253 +13352 +1521 +13949 +1957 +10884 +2273 +14730 +13979 +2401 +595 +12697 +9932 +11372 +11915 +760 +10930 +10659 +6472 +19706 +6488 +9730 +17705 +16085 +4134 +2070 +4852 +5122 +16359 +7044 +8510 +10868 +10172 +510 +11550 +260 +11181 +3018 +3668 +904 +10271 +17104 +12764 +3364 +1878 +2803 +14040 +1149 +15626 +12809 +11008 +2903 +8352 +12761 +13470 +11258 +1400 +8381 +12422 +8402 +3919 +7164 +17263 +18167 +4549 +7654 +6034 +8438 +19451 +12122 +5167 +19334 +9771 +1111 +2932 +18324 +4897 +12687 +1720 +2767 +7616 +6431 +16918 +579 +16504 +4559 +14384 +6337 +4008 +7937 +1086 +5314 +12489 +5211 +17500 +19641 +14815 +7967 +8140 +5239 +2571 +18601 +16197 +5142 +12714 +14895 +3432 +3995 +9206 +7668 +8703 +1661 +4315 +5941 +6849 +2505 +19547 +11736 +5319 +986 +7846 +16050 +9227 +13121 +1012 +18236 +4888 +3885 +11135 +17395 +4303 +3836 +18544 +9807 +15248 +10626 +13846 +17286 +5581 +14007 +2062 +4619 +14864 +13869 +2442 +17728 +11590 +16382 +19117 +19446 +6843 +8694 +14439 +3453 +2700 +9821 +8089 +9645 +14679 +4356 +11980 +5408 +2668 +8053 +1647 +19959 +17083 +14916 +8841 +2319 +11984 +12867 +4292 +4633 +1492 +10716 +12880 +8243 +3929 +2225 +2943 +17578 +12138 +5648 +9614 +15487 +18868 +10779 +12716 +403 +2908 +7040 +4772 +19912 +14823 +5045 +14250 +19733 +13073 +6947 +10387 +8021 +5201 +4488 +18161 +4100 +1422 +16865 +7646 +4370 +17271 +19121 +9808 +2613 +15130 +18893 +11654 +5903 +15058 +12954 +6480 +5764 +15830 +17813 +10224 +6324 +4412 +5607 +9497 +7849 +1291 +9401 +19126 +15067 +10197 +18480 +6258 +8590 +17216 +7437 +16511 +18807 +4267 +18809 +14488 +2345 +4395 +11054 +7624 +3708 +896 +18870 +19517 +2950 +7950 +19529 +155 +19786 +138 +7168 +2130 +10699 +19821 +19156 +11071 +8912 +9515 +17234 +10388 +195 +19909 +16687 +18628 +5870 +10436 +16939 +6562 +8748 +4929 +6282 +9147 +12262 +18067 +17029 +5593 +11043 +15042 +19516 +7618 +13353 +3793 +108 +8255 +8446 +9675 +6111 +5162 +17668 +11558 +17151 +4502 +4353 +19681 +9842 +9229 +13095 +6533 +5706 +14713 +130 +4435 +918 +5195 +4348 +13863 +1247 +13221 +11826 +14356 +7315 +19382 +14025 +10586 +13495 +2418 +13930 +17352 +10751 +5332 +19001 +148 +12921 +8219 +11851 +12210 +5136 +16621 +309 +11602 +3224 +2787 +831 +2423 +14749 +12053 +9389 +15413 +17125 +7143 +19332 +10719 +10433 +4229 +6003 +3725 +17978 +3086 +17007 +6993 +16758 +6015 +16832 +2292 +16344 +13161 +7474 +12363 +10615 +19599 +6856 +10583 +19326 +15872 +3318 +17397 +14798 +8994 +13566 +949 +8506 +17722 +14915 +16273 +14643 +12334 +6859 +3148 +16594 +1809 +16350 +10681 +5696 +19560 +11625 +12856 +14535 +12292 +10444 +14707 +19226 +15085 +17377 +10766 +1833 +14675 +6297 +11865 +9647 +1269 +11299 +18222 +12310 +5715 +3293 +5580 +16180 +3198 +5655 +14952 +4632 +18179 +14464 +18546 +19062 +14401 +10772 +11719 +12926 +6267 +9163 +7559 +6358 +6903 +12503 +842 +90 +11725 +5854 +2083 +9137 +3292 +2781 +10855 +13137 +1993 +6499 +1395 +4910 +9378 +445 +13443 +7039 +15416 +7434 +13093 +14981 +10377 +13571 +5652 +19404 +14335 +8636 +13404 +18715 +12722 +19189 +9989 +12956 +7993 +7848 +5771 +16660 +9063 +8452 +4845 +14997 +6883 +11982 +1931 +9618 +11593 +12712 +16973 +17452 +9993 +3728 +18598 +15326 +15360 +7612 +18837 +5048 +9659 +14572 +732 +10948 +8383 +15938 +9822 +15502 +231 +9438 +13629 +4987 +16527 +7250 +17364 +14600 +13817 +7983 +7154 +4103 +14313 +6931 +13983 +14009 +11778 +5478 +11543 +13446 +13037 +17142 +8307 +17676 +16495 +2274 +15324 +8828 +7999 +861 +6566 +17141 +9039 +694 +2882 +10144 +17051 +15743 +11263 +17156 +9122 +9916 +14398 +7063 +6237 +5638 +15763 +1781 +2049 +10105 +1246 +7266 +7500 +18846 +5535 +15135 +18616 +10987 +4354 +18659 +18721 +4950 +14666 +12702 +663 +11894 +11411 +19965 +3137 +5388 +7768 +5069 +7826 +19221 +1176 +18638 +3314 +36 +17709 +15578 +14710 +4791 +6413 +5461 +7776 +13204 +2302 +6382 +9622 +8769 +8967 +4208 +15083 +2464 +9639 +19391 +14833 +9760 +1577 +681 +236 +1985 +12792 +10147 +11477 +14536 +8012 +18444 +13183 +7810 +7717 +8593 +1654 +18958 +19769 +5659 +13596 +8469 +17760 +16629 +17402 +2140 +16696 +14869 +8103 +18250 +6429 +469 +7934 +16281 +16623 +7074 +4984 +9624 +16339 +7825 +19700 +6866 +15914 +19964 +19336 +13712 +11268 +3216 +10218 +12589 +4453 +14596 +16420 +1512 +18977 +11886 +11637 +12934 +496 +18503 +7326 +1082 +19257 +12682 +5177 +12065 +1500 +8097 +17636 +1973 +12448 +2209 +6165 +17875 +13036 +11911 +17546 +18476 +14284 +12114 +15674 +7720 +3484 +10471 +6089 +3441 +12887 +19772 +5168 +16222 +18075 +6793 +1074 +3428 +7593 +8465 +14048 +3463 +15554 +15329 +4352 +6135 +11756 +4268 +1482 +3523 +14390 +3681 +3090 +15748 +2656 +9255 +6536 +10528 +8574 +1926 +18022 +8625 +19593 +12288 +10632 +18212 +9782 +1875 +2082 +13643 +16171 +8385 +15465 +5751 +9420 +1004 +7771 +12499 +12525 +6097 +1513 +15921 +15484 +12663 +17072 +4693 +15491 +9584 +5864 +4977 +7841 +15412 +7923 +3536 +2540 +6911 +10031 +2206 +18931 +1856 +205 +8152 +12603 +4552 +19026 +9223 +10389 +11271 +16914 +16266 +9829 +8124 +3549 +11605 +16055 +5537 +19801 +11601 +3499 +7699 +735 +9037 +18773 +11078 +18295 +11935 +16267 +5072 +1822 +16710 +5097 +2812 +13191 +12077 +9819 +5524 +17767 +10587 +10513 +1994 +19862 +18907 +7968 +15272 +7789 +2409 +54 +19531 +8092 +3347 +673 +5449 +18599 +11457 +9023 +18216 +151 +3325 +7491 +12272 +1089 +17925 +12543 +4855 +910 +8895 +2662 +17435 +11455 +6263 +6049 +10308 +14696 +1120 +1857 +14658 +14709 +1827 +9133 +3732 +2308 +5021 +19895 +5130 +2321 +8478 +13751 +14668 +12758 +6368 +925 +6567 +13960 +4392 +17533 +11140 +8696 +4902 +19715 +19592 +19009 +6990 +7368 +2884 +1761 +9096 +5776 +19203 +12034 +13669 +14639 +8126 +11779 +16533 +1682 +208 +7340 +6351 +6564 +16168 +11449 +15859 +14482 +19057 +13581 +5965 +18130 +1427 +8670 +16155 +17607 +3754 +5050 +8369 +10157 +7623 +4580 +10289 +14147 +4613 +6316 +8441 +13630 +19850 +15986 +1160 +17770 +19266 +83 +6643 +4694 +10353 +4909 +4727 +15659 +5584 +2641 +3698 +3165 +13039 +13312 +4756 +3598 +8958 +1898 +16121 +18728 +6332 +6539 +12401 +18019 +13123 +15388 +1972 +3111 +13250 +13944 +15408 +9078 +452 +14346 +7560 +2306 +3883 +6312 +3541 +5518 +3377 +19440 +11416 +6459 +19730 +1018 +17717 +19070 +2979 +7628 +3294 +14038 +17208 +16535 +14850 +7 +16427 +18239 +5066 +9609 +9194 +11734 +14200 +4917 +13090 +2490 +3735 +3829 +13487 +7105 +4108 +450 +10527 +3391 +13080 +3884 +9246 +14599 +12759 +9940 +5528 +1425 +3209 +4 +11016 +14852 +17253 +15713 +4653 +5849 +4787 +16819 +12491 +19370 +12594 +3657 +10088 +3741 +9981 +3779 +18367 +4760 +8416 +16092 +18242 +12718 +18491 +13873 +10163 +11696 +4073 +1570 +18443 +8328 +3675 +4282 +7763 +6861 +17516 +18429 +7823 +11156 +9205 +18297 +13738 +7811 +3578 +8944 +2115 +16468 +3907 +15293 +10044 +6555 +2826 +9838 +6750 +15892 +4279 +6867 +7938 +9788 +1115 +13625 +2146 +11581 +1096 +18237 +10236 +9201 +17460 +13171 +9963 +7202 +3966 +4294 +5573 +10232 +13791 +19875 +9298 +11298 +18253 +10352 +14014 +10952 +16802 +17984 +3027 +11733 +3150 +3514 +5796 +16181 +16322 +15572 +9196 +9392 +6842 +13963 +7868 +4416 +5411 +4598 +12392 +8187 +8809 +1722 +14154 +7136 +10114 +806 +4506 +2210 +1234 +6487 +12601 +6345 +1222 +11032 +17837 +1436 +18510 +13192 +13409 +2916 +15236 +2171 +19849 +18365 +13536 +7186 +18356 +13562 +13042 +15819 +11301 +5777 +15869 +9462 +264 +6749 +6724 +19018 +3737 +4210 +13420 +16942 +8870 +14209 +19898 +882 +4021 +18094 +438 +15220 +12025 +19916 +8802 +16090 +7216 +13396 +19431 +15182 +11773 +2064 +2735 +4187 +14432 +6551 +14174 +4687 +14750 +19246 +5868 +8400 +16515 +3028 +1855 +15898 +12605 +2643 +9103 +7130 +10895 +9089 +2771 +19857 +10033 +19263 +9119 +14706 +14207 +18470 +11743 +18163 +859 +13245 +14698 +11764 +9748 +15584 +19089 +13388 +18928 +3289 +19660 +16732 +16194 +70 +19077 +2745 +11216 +5550 +15300 +7565 +1766 +9684 +18285 +3060 +14989 +5227 +2072 +8080 +14517 +12391 +13686 +7525 +14548 +11403 +8754 +16046 +10953 +7112 +5592 +6882 +9080 +4589 +13437 +16114 +5753 +6118 +16947 +4566 +9633 +7033 +13356 +9164 +13671 +9689 +11207 +18007 +2402 +15376 +16038 +6571 +4875 +3055 +16146 +18484 +19778 +12574 +966 +16007 +6908 +10248 +9118 +1820 +1830 +14591 +15893 +13881 +6987 +5794 +13255 +12003 +4537 +18485 +3567 +7203 +19079 +6338 +8671 +15590 +19654 +2552 +10446 +14189 +3207 +9582 +12209 +18993 +6941 +14448 +18639 +18694 +5814 +10292 +8377 +10796 +10680 +15 +870 +1777 +15404 +2044 +4232 +3917 +5371 +7725 +8974 +8227 +6073 +12404 +9084 +19466 +14267 +8223 +7381 +7682 +632 +12671 +1325 +11845 +11113 +8412 +19417 +6357 +17205 +380 +4919 +7842 +14100 +16317 +15235 +14613 +18211 +7461 +19872 +9913 +11784 +13436 +1859 +6597 +13750 +10136 +14029 +10842 +1795 +682 +11266 +16047 +5758 +1194 +16086 +7301 +6798 +10818 +3495 +17518 +298 +16767 +18903 +15113 +10358 +5676 +18567 +4812 +15165 +9477 +2283 +11622 +13722 +3073 +16187 +2544 +2482 +7534 +18691 +14260 +274 +14682 +3231 +16237 +6606 +16494 +12612 +17826 +18266 +17652 +11954 +19394 +16356 +15781 +6498 +7613 +18308 +9852 +12495 +11569 +6822 +15384 +18414 +16240 +17425 +3619 +2998 +4060 +281 +12126 +13552 +17956 +8648 +5199 +11470 +19559 +2184 +4127 +1782 +11302 +8272 +7227 +4378 +6617 +17001 +5565 +12212 +15110 +12717 +9177 +203 +15928 +14386 +18671 +3664 +5577 +5825 +18883 +11563 +7790 +16821 +19075 +11407 +4067 +481 +15007 +10607 +1437 +16173 +10793 +647 +3305 +2691 +15054 +1132 +8766 +14500 +14137 +11852 +14871 +765 +16596 +1727 +2747 +9784 +9337 +16309 +16810 +16508 +12105 +1127 +13997 +17502 +17305 +18799 +12701 +5281 +13678 +4044 +18969 +14380 +19526 +15873 +8501 +299 +1764 +18487 +19620 +4094 +9237 +12970 +4240 +14861 +11285 +1470 +3766 +9496 +9656 +5356 +7509 +9637 +15046 +19038 +1072 +13336 +784 +18523 +6996 +15991 +581 +14929 +16799 +4522 +8701 +9889 +5016 +7199 +19270 +15705 +2611 +10397 +19448 +17801 +11447 +16190 +16130 +3153 +12318 +5677 +14727 +13117 +9800 +15894 +15709 +9081 +5403 +15579 +6359 +5576 +552 +19272 +11427 +4343 +11192 +12732 +4424 +17024 +7715 +12633 +2562 +12400 +4330 +16677 +1150 +6402 +18957 +9338 +4726 +10064 +8644 +17968 +79 +14922 +12178 +7837 +11881 +18551 +2096 +7547 +6879 +16607 +17532 +17944 +10284 +10009 +9868 +2088 +7165 +17038 +14403 +11088 +9668 +13714 +9125 +13804 +13547 +200 +11058 +19642 +3016 +5375 +14376 +5202 +3268 +8476 +14820 +12021 +17213 +12957 +8549 +15190 +5397 +3339 +8084 +15262 +10297 +14127 +18950 +14402 +11073 +5657 +10318 +13866 +9180 +18302 +3183 +2397 +11747 +9357 +2022 +10447 +16582 +19247 +14261 +19923 +14503 +16422 +8778 +18439 +19673 +1679 +9156 +14756 +8836 +792 +14801 +14510 +4556 +12417 +6110 +15972 +1030 +2328 +525 +16238 +11164 +11570 +17610 +4118 +11897 +9444 +14758 +2978 +16124 +7333 +6201 +14646 +819 +16867 +758 +4095 +19080 +15720 +17338 +18410 +12800 +16679 +10495 +6063 +10001 +18512 +14521 +6137 +18160 +5900 +3606 +15776 +9127 +13491 +17690 +9173 +5013 +3745 +13371 +9315 +4604 +14737 +4625 +7850 +3071 +2681 +13915 +3716 +7617 +12597 +3512 +3565 +19435 +7564 +17592 +9035 +12394 +2941 +6473 +13995 +18469 +11981 +17680 +1932 +19172 +1959 +16466 +2043 +11677 +7471 +7635 +12870 +10337 +15199 +2517 +17905 +10239 +5615 +2269 +13794 +2664 +8883 +10087 +9073 +10617 +5548 +10940 +19822 +11659 +13147 +12171 +3164 +17034 +9484 +17682 +7142 +3559 +12980 +11388 +7572 +17292 +3311 +3561 +7590 +14998 +7454 +2874 +6168 +937 +14366 +8090 +8710 +12599 +8443 +8970 +18892 +329 +3254 +7781 +16904 +4896 +12343 +11478 +10553 +4132 +17450 +12864 +3510 +1402 +12055 +8183 +165 +9948 +3214 +6878 +5933 +228 +3230 +1281 +15215 +10404 +3367 +4014 +6632 +9043 +4818 +6661 +19398 +18517 +7085 +17011 +13682 +15769 +13317 +3843 +11294 +7302 +8179 +12539 +16626 +3607 +15203 +2254 +5182 +16659 +12406 +12573 +5917 +2373 +11062 +17231 +3916 +3105 +9085 +18613 +11408 +17161 +17366 +11591 +4238 +4660 +2848 +5891 +12804 +10288 +8174 +14239 +12514 +4654 +4250 +2852 +6314 +2368 +4331 +10102 +9215 +4154 +9775 +15534 +16486 +6072 +17897 +6804 +9025 +6688 +8484 +7362 +7728 +18465 +17285 +7308 +13520 +11839 +8577 +3723 +4068 +5628 +18978 +9294 +13440 +3112 +13821 +12027 +13670 +1564 +15275 +10179 +9740 +7109 +8716 +11499 +11306 +4092 +15096 +18461 +19703 +15349 +1079 +15249 +4831 +8609 +5402 +6252 +12462 +17598 +8256 +4873 +901 +1505 +10888 +13587 +16191 +1683 +13035 +13771 +4377 +17164 +4385 +9517 +18346 +10202 +807 +3176 +3393 +10311 +11856 +4420 +17807 +16514 +17296 +10185 +7689 +2121 +603 +19848 +14957 +6235 +3765 +6958 +16961 +16835 +11823 +15218 +3673 +14694 +9799 +10988 +11521 +16713 +2904 +6079 +3201 +18911 +4517 +5304 +6689 +3034 +17456 +14288 +3025 +9030 +10834 +72 +3640 +5419 +17997 +12664 +17332 +3392 +18034 +8415 +10580 +2582 +7067 +12922 +8116 +6069 +9456 +3945 +12121 +3205 +1471 +18203 +4563 +11758 +15828 +12696 +3029 +5878 +7584 +18021 +9781 +13367 +12159 +8708 +17930 +18482 +3810 +19468 +9992 +9466 +16650 +8319 +8075 +13026 +12750 +4248 +3756 +6904 +5987 +10300 +19513 +7835 +14553 +7997 +13118 +13895 +14201 +828 +19873 +14254 +14321 +6442 +7713 +18594 +9283 +3803 +18739 +5800 +8088 +11797 +12172 +17438 +8013 +6444 +2526 +19303 +7752 +5243 +10222 +14888 +1987 +12728 +2281 +14094 +13189 +18421 +232 +17792 +12315 +5112 +10252 +14472 +11282 +13205 +8666 +16375 +13298 +53 +16122 +15019 +18453 +11551 +17430 +10461 +11789 +5997 +2790 +18547 +12547 +6067 +19149 +2820 +18032 +10802 +10133 +18017 +7667 +13452 +16972 +4192 +2519 +15381 +12924 +10438 +19016 +4955 +3903 +10237 +15152 +2860 +19938 +5412 +14294 +2867 +15553 +8672 +13106 +11603 +408 +546 +6437 +7921 +7487 +11587 +14507 +4111 +15628 +13295 +8340 +18279 +7259 +14649 +19506 +14785 +10293 +14742 +19136 +6265 +1087 +12052 +15843 +18727 +5484 +845 +15185 +13976 +15646 +17664 +1068 +4335 +8722 +12020 +488 +2706 +282 +3534 +1294 +3043 +11397 +2713 +2975 +944 +10208 +100 +19744 +4924 +2107 +17519 +7385 +2507 +17006 +2763 +7137 +17314 +15094 +12296 +12237 +10544 +2758 +76 +12678 +10763 +3780 +17067 +8968 +975 +3659 +13505 +15863 +17766 +3363 +5423 +7463 +2178 +4126 +14612 +7382 +19859 +676 +12313 +9295 +8640 +11309 +10754 +13801 +12608 +13889 +18117 +16269 +3412 +10649 +12279 +3508 +7951 +13929 +12250 +17637 +8922 +18762 +18329 +14702 +9507 +19961 +850 +9597 +4414 +13247 +16822 +10422 +19205 +3713 +3436 +7248 +4243 +10902 +8762 +15545 +12653 +18681 +13455 +14691 +10967 +3489 +18920 +12677 +9470 +2134 +15958 +350 +19280 +17241 +19725 +3551 +7246 +19535 +7724 +18758 +1731 +3332 +17522 +467 +2625 +9608 +14969 +1323 +9233 +16160 +14099 +5302 +8554 +4449 +5333 +9273 +16185 +14522 +17604 +19605 +6303 +12330 +15146 +14008 +7440 +541 +11439 +1576 +16787 +16643 +3187 +1217 +9606 +833 +7777 +15549 +18991 +11612 +4806 +5366 +12275 +19215 +19449 +12385 +8947 +15472 +13739 +14947 +1225 +12670 +6053 +2155 +5435 +15931 +13807 +16673 +8413 +15099 +10069 +13744 +9676 +12002 +16917 +1192 +13262 +15795 +12097 +4419 +2704 +15031 +10581 +13753 +5473 +721 +8536 +15470 +12176 +11335 +3264 +1075 +3213 +12203 +1035 +18985 +621 +4571 +832 +18668 +1008 +6614 +10274 +7827 +18731 +2421 +10075 +15523 +2499 +3761 +13927 +7374 +2263 +4507 +209 +8779 +492 +9195 +18556 +10960 +16021 +10464 +5662 +18906 +13364 +13332 +13413 +8647 +2909 +6985 +16880 +16692 +17599 +788 +14233 +7122 +14248 +14319 +9678 +7149 +10526 +1587 +19510 +5975 +4104 +3319 +9437 +996 +4384 +6922 +5501 +17736 +3483 +7518 +8495 +368 +1461 +9559 +15729 +6638 +5407 +13111 +14491 +8824 +6615 +2284 +14186 +10279 +11785 +10097 +16919 +14273 +15533 +4079 +18344 +2175 +15635 +14883 +4928 +8034 +19930 +13460 +5421 +8072 +18176 +18157 +14272 +6271 +15237 +8601 +12886 +8145 +3390 +11900 +8524 +3615 +3893 +17727 +3817 +16812 +1177 +19385 +2759 +14657 +11853 +19444 +12660 +3987 +2960 +16110 +12808 +1706 +9939 +11800 +4441 +14638 +459 +13076 +18192 +10061 +18226 +8324 +19067 +17264 +14165 +19323 +3747 +9056 +4321 +9546 +8030 +2356 +18989 +16340 +11970 +16797 +14564 +5216 +359 +1881 +17087 +11333 +18725 +10957 +5118 +2959 +4838 +12425 +14879 +11620 +5585 +12994 +3695 +18873 +2286 +10737 +16869 +11498 +18970 +4309 +772 +4029 +18787 +8505 +4596 +2731 +12749 +1349 +16805 +7028 +14818 +5098 +7543 +6474 +8697 +4439 +5308 +12946 +18423 +13965 +11975 +2260 +7046 +17257 +15904 +1114 +7306 +5780 +2764 +8788 +19176 +15016 +7772 +5060 +10836 +9873 +11267 +10981 +8305 +5044 +5872 +19841 +7894 +8600 +19411 +5946 +13648 +255 +2447 +1558 +490 +17820 +17806 +15541 +893 +6841 +15371 +11170 +10039 +9423 +17294 +18620 +8311 +17065 +968 +3834 +545 +16969 +12174 +4056 +16630 +8260 +8432 +10238 +14779 +4390 +2883 +14841 +10338 +13475 +7354 +14736 +13167 +2772 +18162 +15228 +12305 +12719 +1644 +10361 +7441 +17815 +11553 +8711 +1066 +5487 +9175 +9275 +5210 +18973 +13240 +17780 +2550 +12982 +14832 +10927 +4661 +16513 +12471 +15925 +2872 +12541 +17750 +18498 +6578 +3611 +17994 +13313 +22 +18334 +2349 +1415 +16900 +13975 +3906 +7735 +9625 +11194 +18855 +11188 +16154 +10403 +3064 +15075 +1773 +5986 +8845 +12083 +920 +3050 +17827 +5658 +16020 +17909 +7881 +4933 +12873 +8433 +8631 +18760 +15821 +7722 +11460 +17342 +2034 +17490 +10780 +883 +14097 +15098 +14621 +6636 +5720 +1124 +16757 +10874 +15537 +1557 +10173 +17788 +1749 +8910 +13488 +11561 +18073 +15930 +12831 +14569 +2545 +11320 +11238 +19196 +3753 +12191 +1336 +17805 +8758 +8200 +7914 +2095 +8798 +6651 +11808 +2970 +19186 +15383 +19024 +8654 +14744 +12256 +1023 +963 +7280 +8548 +11983 +1423 +3704 +17529 +10627 +743 +3001 +16400 +5489 +5010 +180 +5853 +13238 +12358 +15963 +14717 +11350 +3159 +18665 +14449 +15660 +19809 +18975 +2655 +306 +4030 +7739 +8509 +14567 +8664 +9500 +13230 +16056 +567 +15875 +2716 +18559 +17496 +1212 +8394 +1828 +864 +16868 +16018 +4259 +2470 +10454 +14166 +5460 +4926 +10011 +3869 +1324 +13729 +5361 +11782 +9954 +10348 +16472 +11978 +10548 +2312 +14032 +4262 +4674 +1050 +855 +6933 +18283 +15984 +15960 +609 +5280 +7784 +3261 +10588 +13579 +19058 +13563 +15211 +17376 +18894 +13797 +14550 +15929 +11391 +19746 +2497 +2762 +5351 +3573 +7663 +13316 +5052 +18248 +10496 +9108 +17914 +1589 +10976 +12564 +5640 +7052 +3650 +19288 +17354 +5334 +1612 +8429 +19457 +12336 +844 +17372 +2722 +4020 +13935 +2141 +13269 +4597 +14880 +17706 +10707 +4160 +8588 +7049 +909 +17520 +17317 +18777 +10 +18364 +11199 +17916 +11227 +1696 +14325 +12214 +7281 +14539 +16857 +1591 +6780 +6432 +8874 +10189 +9146 +15350 +5802 +18704 +2868 +4437 +172 +4893 +14474 +13974 +13089 +5457 +10797 +13858 +6339 +821 +8035 +10046 +14123 +19109 +15420 +16257 +11615 +7462 +8867 +8253 +13737 +20 +107 +17031 +13078 +8634 +1974 +14051 +16800 +8299 +19040 +17896 +9109 +7124 +12631 +17931 +1890 +6823 +8000 +4767 +2295 +15732 +1915 +10664 +15103 +9049 +18741 +17887 +3320 +12120 +9574 +17724 +9563 +6734 +2316 +31 +3671 +4605 +1084 +9813 +6435 +520 +19309 +3978 +6141 +17935 +6068 +18886 +3372 +4828 +99 +14056 +13544 +17509 +853 +14034 +18028 +11904 +16706 +11293 +8434 +7642 +12996 +14371 +3298 +17073 +267 +18636 +6920 +4529 +4574 +8390 +11360 +5255 +5601 +12248 +10778 +13658 +5784 +5476 +7671 +19286 +18144 +1927 +14367 +6863 +3805 +17986 +16333 +3227 +18191 +3301 +18071 +7150 +8780 +13907 +12393 +16166 +11973 +15147 +9541 +14193 +7318 +3712 +13708 +11345 +10427 +4323 +12026 +10245 +19967 +1405 +388 +16958 +2546 +7439 +10000 +16496 +16774 +6023 +16728 +13478 +2087 +11657 +14383 +4916 +18463 +4677 +4421 +17658 +8330 +1958 +11352 +7005 +19148 +5629 +14120 +5343 +10312 +18412 +17808 +770 +13891 +7283 +1547 +19493 +8091 +13424 +303 +3252 +12888 +2924 +12908 +13878 +13442 +15359 +5838 +11929 +5483 +8948 +15000 +8852 +12754 +7103 +17378 +1990 +2635 +1314 +19376 +10616 +10319 +569 +16644 +18499 +6572 +7071 +8093 +2218 +7749 +8333 +3011 +6260 +3358 +13649 +3821 +11575 +17623 +8707 +19293 +9585 +16920 +16461 +15377 +19556 +8660 +11807 +9534 +12326 +6289 +16950 +12742 +7347 +339 +12876 +15013 +6976 +16105 +15043 +18193 +1348 +14931 +583 +19476 +8455 +7530 +13897 +13373 +13684 +13655 +5635 +9475 +377 +16225 +12223 +17025 +13706 +11763 +8756 +834 +10618 +19095 +11740 +15561 +7160 +11522 +8688 +7273 +4477 +14811 +9832 +12352 +14281 +7299 +8576 +1020 +2961 +12276 +1193 +16897 +3473 +1185 +1198 +1353 +12881 +4077 +15642 +5511 +12840 +9750 +13101 +8026 +2282 +14224 +5480 +10594 +9903 +16358 +458 +18927 +19045 +9355 +7312 +3094 +12584 +6116 +16287 +3091 +967 +19947 +5920 +5979 +4883 +11583 +15033 +2160 +3479 +15639 +1204 +16521 +2777 +13765 +3517 +18076 +16376 +4802 +1197 +11109 +11985 +7585 +11451 +19169 +10648 +18223 +9554 +18688 +16116 +19412 +19371 +17483 +10726 +13054 +15421 +15637 +5861 +2355 +6874 +2054 +8052 +4763 +5086 +17479 +16568 +286 +11314 +5630 +7638 +4785 +18259 +15063 +5927 +1902 +9487 +4430 +12127 +7984 +5323 +14082 +10574 +11798 +19408 +13315 +17400 +12810 +6405 +10885 +18982 +6347 +6107 +12879 +14257 +6212 +5437 +12789 +17322 +2320 +17370 +1352 +14275 +8316 +19375 +2163 +10911 +12257 +13001 +3599 +13911 +12429 +5992 +16178 +11690 +6844 +10925 +11040 +13829 +3140 +5848 +18252 +14050 +18210 +13620 +4801 +6090 +8264 +1200 +13511 +14581 +18087 +15366 +4711 +5058 +18509 +10826 +10217 +19119 +6825 +12066 +17167 +2869 +262 +14434 +2468 +11247 +4607 +16667 +4655 +4009 +19788 +11664 +15257 +3981 +11512 +18254 +241 +14508 +5385 +14884 +508 +1010 +3533 +9474 +12500 +476 +19878 +8245 +6221 +16128 +11381 +13202 +13770 +9372 +15750 +9753 +12263 +7979 +8317 +16030 +1428 +3304 +5890 +10040 +16840 +11006 +103 +15437 +1237 +11452 +6471 +14688 +815 +10416 +14624 +15082 +12329 +10168 +7885 +8055 +6173 +1700 +14259 +19945 +3396 +11197 +5867 +14058 +2610 +7808 +18272 +19800 +11895 +5748 +5678 +19439 +18288 +7690 +17950 +4670 +6855 +13832 +2375 +16247 +11035 +8411 +7119 +8172 +19577 +18407 +16033 +9886 +618 +6757 +8663 +7024 +4404 +3775 +5904 +3496 +11175 +1178 +18651 +540 +14839 +715 +14427 +19913 +11250 +10014 +18733 +1745 +8873 +19741 +17799 +19854 +17954 +1250 +10275 +9579 +1962 +7512 +15141 +4980 +7405 +8585 +10711 +16475 +11717 +18530 +7008 +17316 +2051 +17537 +13796 +1477 +7907 +6230 +8903 +15358 +17492 +12721 +12635 +9425 +7215 +4657 +15880 +5509 +9729 +5597 +15150 +928 +4194 +1885 +3352 +8450 +12802 +4699 +12373 +19727 +1007 +6365 +5531 +4505 +5181 +6139 +2523 +10598 +10730 +12987 +2547 +3516 +680 +18052 +19078 +4568 +18027 +2796 +1980 +19124 +7240 +10159 +19131 +17431 +8023 +2190 +13260 +5143 +7262 +5203 +1040 +509 +8107 +6132 +547 +7740 +865 +3225 +14348 +3 +17698 +4518 +14070 +8993 +3922 +15050 +7829 +15039 +9427 +18434 +2757 +6380 +6816 +19475 +4465 +1804 +19770 +10633 +6774 +768 +5538 +14069 +4741 +18234 +16102 +7251 +2454 +18843 +9332 +5262 +16198 +9344 +2407 +10856 +1814 +12693 +3466 +12384 +14777 +2629 +16280 +9697 +6262 +6032 +6644 +5723 +8028 +8690 +13169 +19321 +3191 +2749 +10658 +8676 +4347 +17068 +19545 +2317 +2813 +8048 +17365 +16087 +5632 +7524 +5071 +16539 +3634 +18685 +1946 +12445 +16760 +18307 +8191 +6206 +18602 +2410 +11873 +8244 +2398 +9473 +19936 +14607 +13920 +4149 +17223 +13809 +9226 +5991 +16764 +19141 +5709 +8294 +1017 +14026 +13389 +10963 +19174 +8850 +16622 +8344 +11248 +3721 +16117 +5731 +2810 +12726 +3596 +3072 +18576 +13369 +5425 +5430 +8846 +15783 +9879 +12443 +4728 +17085 +3188 +16256 +15425 +19267 +1227 +6170 +12137 +4440 +10004 +7949 +8129 +11707 +11220 +1256 +17421 +2007 +1174 +10396 +16049 +9773 +606 +2132 +9313 +5728 +4251 +14010 +10174 +16283 +3203 +11755 +2623 +2621 +15918 +18477 +14574 +6723 +5389 +14221 +7239 +2113 +16454 +16901 +6715 +4547 +12311 +19231 +15456 +18515 +5515 +245 +13456 +15386 +19927 +13823 +13702 +19978 +12766 +8962 +18392 +11673 +12983 +19551 +11223 +12740 +5505 +5675 +3331 +3480 +15799 +19170 +14468 +160 +19132 +6284 +19458 +16980 +1611 +15836 +2937 +10568 +14133 +7288 +13865 +13763 +19851 +16933 +12944 +10334 +9367 +8276 +11863 +9117 +2688 +16561 +13508 +14352 +13068 +4508 +14538 +13431 +5919 +17369 +12828 +13981 +2997 +16843 +4373 +8359 +15842 +12756 +2176 +13393 +811 +3988 +19814 +5265 +14033 +10860 +9549 +8273 +5788 +1121 +18359 +16169 +5434 +1772 +18744 +1245 +11874 +7131 +15168 +2703 +3079 +18947 +12397 +11053 +14900 +13163 +18433 +9651 +1265 +3100 +12452 +688 +18261 +19883 +14141 +4545 +16244 +2580 +9951 +16887 +9712 +2110 +17558 +13218 +5065 +4403 +2004 +18790 +17525 +285 +19661 +3891 +14106 +4975 +1769 +2406 +2606 +9744 +19775 +6918 +15410 +16248 +13083 +5247 +16733 +4649 +7420 +19254 +2432 +11139 +16608 +2 +11832 +7230 +1347 +16937 +15047 +3078 +9306 +6563 +10611 +5911 +7459 +634 +17381 +18323 +15443 +12902 +16385 +17239 +12229 +12747 +13786 +16923 +123 +11850 +17773 +12220 +14943 +4254 +3024 +6683 +9112 +9558 +16065 +9754 +6114 +10785 +9192 +15021 +4091 +10762 +3976 +13894 +7588 +13490 +2973 +3498 +6313 +14176 +12108 +3786 +17893 +5156 +18260 +7296 +4740 +15948 +10610 +12735 +5967 +2618 +7175 +558 +16931 +14886 +17757 +13469 +1182 +4252 +16241 +13638 +6514 +12370 +917 +18078 +16360 +8323 +8702 +7892 +1787 +9268 +12665 +16747 +8786 +10053 +4096 +4359 +9432 +12741 +4991 +9333 +12123 +1365 +1734 +8892 +804 +1002 +8888 +14001 +19777 +1742 +7191 +6492 +17321 +2068 +991 +15616 +18921 +2856 +8685 +8401 +19251 +7252 +16467 +4786 +1337 +7162 +12561 +660 +18128 +16329 +15805 +8765 +8106 +13931 +4253 +1899 +8547 +9874 +10070 +19855 +7469 +9369 +10993 +18299 +14560 +421 +11541 +17590 +11107 +17359 +8500 +18818 +17552 +3470 +8992 +2177 +9326 +16060 +1450 +8070 +6250 +9581 +5212 +12286 +10537 +16717 +17325 +16510 +11787 +9036 +16108 +16005 +15393 +15649 +12496 +1824 +561 +10873 +19807 +1411 +6834 +19425 +4272 +7146 +9611 +18896 +18294 +17823 +4757 +14002 +2660 +3994 +14277 +969 +5206 +6735 +16567 +4575 +13624 +9110 +798 +15233 +15876 +13539 +16505 +15269 +12232 +9980 +15998 +7732 +14228 +14795 +3918 +11534 +7952 +11627 +16205 +16946 +17793 +9019 +3471 +1285 +239 +17953 +15475 +5977 +12189 +13401 +10815 +6292 +19903 +7327 +6760 +6556 +16262 +12474 +3330 +12228 +2534 +19901 +5445 +6362 +3049 +1628 +6325 +16137 +15806 +3920 +19692 +1698 +8638 +10448 +7519 +2776 +2619 +13450 +19679 +12958 +18504 +7800 +15653 +19708 +13496 +11813 +3969 +1617 +5836 +14118 +3660 +3449 +959 +17861 +17043 +16370 +3242 +16253 +11202 +17489 +5862 +1627 +18955 +19988 +1843 +7603 +2440 +15520 +11045 +7795 +19496 +9489 +9346 +6272 +11126 +18224 +1203 +18554 +11653 +18772 +16097 +9587 +7388 +8880 +19014 +543 +14562 +12990 +3748 +5647 +8797 +14232 +11971 +19479 +7610 +10091 +1271 +7820 +1673 +17328 +2594 +7343 +11236 +874 +3089 +7018 +16476 +2543 +6482 +17442 +67 +8750 +16804 +12381 +5006 +10131 +14996 +19473 +8490 +2193 +16635 +1165 +7183 +814 +17197 +6759 +6047 +10512 +2091 +9638 +204 +12493 +4565 +15707 +8232 +6329 +12355 +5023 +17163 +729 +11060 +6342 +14723 +11649 +16541 +15807 +10782 +8629 +6041 +11896 +8801 +17349 +10470 +9858 +14075 +14626 +19697 +6045 +6171 +9956 +49 +12119 +3902 +14407 +15065 +15294 +6202 +11537 +16072 +17906 +2861 +7833 +16529 +7693 +8692 +614 +8952 +17057 +17227 +4899 +4345 +6184 +16556 +12150 +6706 +17543 +19292 +18274 +10212 +15172 +18316 +3349 +6396 +2695 +1666 +4209 +18277 +14509 +7526 +14587 +13711 +2701 +11318 +7359 +7451 +14937 +2357 +12236 +9354 +4396 +15724 +774 +9248 +8741 +13075 +13720 +1303 +8266 +297 +12084 +10243 +16373 +17437 +4478 +1064 +15761 +8887 +3265 +7413 +14851 +18791 +16852 +667 +6673 +251 +16703 +16756 +5197 +8570 +18065 +13676 +11280 +7581 +14199 +1681 +1094 +10998 +14419 +15655 +3625 +2520 +13185 +7110 +15500 +6580 +8159 +3202 +14387 +10684 +19307 +5348 +14265 +13122 +11848 +1550 +12552 +8395 +6506 +17169 +14684 +7286 +12039 +1476 +5620 +8775 +14177 +19511 +1005 +15419 +1601 +1675 +17138 +9162 +9172 +10298 +7258 +16748 +12974 +7620 +3694 +16357 +7943 +11426 +5682 +6565 +10333 +15589 +12567 +2227 +9777 +18432 +613 +839 +13344 +12205 +7941 +19245 +14968 +15595 +19147 +11613 +1695 +7502 +13653 +16324 +803 +4036 +14420 +2923 +18862 +11313 +10819 +8182 +15715 +6773 +12703 +6414 +17412 +2858 +17174 +7742 +9877 +11493 +38 +15556 +16962 +2829 +6205 +11902 +396 +11129 +5880 +14792 +12441 +17998 +69 +13664 +11580 +6084 +3525 +9990 +7069 +12692 +11726 +6145 +16715 +10095 +4293 +8728 +2823 +19998 +11276 +11329 +17646 +8009 +35 +5078 +10708 +7392 +11222 +4618 +12078 +1442 +14970 +15156 +6881 +3971 +2911 +7753 +4560 +14590 +4355 +10084 +6390 +8986 +7987 +7601 +2294 +17913 +7088 +15866 +15701 +5670 +14004 +8526 +17111 +14183 +14182 +6203 +18037 +4071 +18852 +10211 +7609 +15179 +15823 +10365 +13672 +19276 +15221 +11722 +6340 +16977 +9780 +14231 +11031 +719 +9797 +10705 +6775 +15435 +2592 +5224 +11256 +1386 +15251 +3125 +441 +6043 +16791 +19722 +16489 +15714 +2712 +7068 +4070 +16193 +7334 +8960 +1948 +9576 +13483 +11828 +18934 +7745 +13740 +16678 +718 +19145 +9998 +18233 +15327 +13971 +2756 +9602 +11095 +16348 +97 +10999 +8721 +13599 +4590 +11814 +8074 +16174 +8943 +12654 +12045 +15975 +3539 +10518 +15246 +15548 +14337 +16401 +468 +17409 +11063 +3783 +13608 +4621 +1536 +18990 +4994 +8020 +2780 +638 +11465 +14086 +4173 +14497 +6868 +316 +2151 +3672 +6934 +12739 +1026 +17584 +15290 +18151 +12861 +8203 +17444 +18532 +17967 +15313 +16661 +15814 +14410 +9567 +8268 +1418 +16936 +17695 +14013 +5707 +15870 +16975 +17117 +11635 +18866 +16530 +10301 +16424 +9982 +2076 +19111 +1867 +9856 +8551 +13097 +1984 +11011 +10725 +16015 +371 +17 +2327 +5102 +13302 +9442 +13025 +5669 +4622 +1660 +7876 +18531 +6832 +13570 +11001 +8166 +15467 +16666 +1251 +13377 +5770 +7265 +3851 +726 +18150 +18646 +10270 +3239 +871 +7779 +141 +11644 +879 +18105 +14687 +17158 +18605 +3895 +5298 +5081 +3272 +16441 +7632 +7404 +18597 +8138 +5721 +10678 +8186 +17795 +140 +16811 +3569 +6559 +5141 +7292 +15254 +325 +12348 +11977 +9511 +13403 +14563 +8578 +8341 +18473 +5754 +4098 +19989 +9179 +5426 +8502 +7425 +2617 +9323 +8332 +13509 +3338 +9921 +16739 +3402 +6214 +15692 +17999 +14914 +18267 +4713 +8558 +6464 +17779 +15722 +5367 +15661 +3244 +12322 +4212 +6552 +6885 +14614 +15927 +218 +14159 +10385 +12655 +10478 +1455 +16632 +8738 +19565 +1549 +14692 +17387 +17918 +17168 +4199 +19484 +16932 +5324 +5631 +4647 +10460 +10110 +17033 +13275 +12807 +19811 +5860 +2595 +16139 +10339 +11386 +17716 +15566 +822 +8953 +19795 +14760 +9985 +15775 +3328 +7022 +5666 +15704 +19926 +12575 +16347 +1313 +9941 +2840 +15093 +14148 +13627 +14344 +11941 +19970 +269 +1318 +14241 +3019 +8419 +19940 +10171 +4662 +5194 +4841 +4117 +14623 +19575 +14416 +824 +938 +7891 +6103 +15516 +10614 +5925 +16818 +18166 +13515 +95 +15406 +19134 +11628 +17979 +1319 +2744 +5070 +18099 +2359 +16881 +7317 +10696 +7630 +13391 +15771 +7346 +18313 +8844 +16699 +12388 +19591 +6455 +14269 +1646 +11540 +8579 +12195 +6517 +10214 +3041 +4329 +16465 +14135 +2266 +8262 +7932 +16970 +7365 +15173 +16220 +18402 +18717 +8235 +12535 +15352 +3006 +14791 +3232 +18218 +15011 +17642 +3547 +753 +5095 +17252 +3986 +3626 +4497 +1981 +9866 +1851 +2915 +633 +3008 +19073 +1672 +16067 +1304 +2770 +17594 +10531 +1316 +3147 +17340 +8314 +4191 +5851 +19306 +12734 +9571 +7472 +1162 +762 +12501 +14104 +3462 +12118 +19537 +10867 +16985 +17121 +10331 +7207 +19291 +14819 +19171 +16103 +1810 +11822 +10167 +5644 +2750 +11956 +2638 +13430 +8726 +15164 +16921 +14834 +3228 +13214 +12320 +9083 +7657 +9446 +14592 +14480 +11566 +17878 +625 +1444 +19721 +16743 +17076 +2512 +19734 +5115 +8468 +16031 +6970 +12674 +17941 +12439 +16286 +15833 +611 +8489 +15367 +1186 +12645 +19794 +12851 +15045 +5815 +13926 +7450 +5447 +5587 +10840 +1473 +19572 +10193 +4492 +1052 +13756 +13386 +14158 +15816 +17053 +19329 +19381 +4953 +8058 +4528 +12776 +18810 +2164 +9786 +16531 +7587 +17128 +6062 +15194 +8386 +15312 +4862 +10516 +18457 +15501 +8014 +5049 +18686 +9979 +13290 +15217 +8258 +11885 +4992 +4804 +1051 +6808 +8996 +11544 +11998 +15433 +2593 +3503 +4553 +11775 +11830 +11155 +19097 +2350 +16824 +3867 +16311 +4512 +886 +9208 +17629 +15605 +10366 +15288 +1553 +14342 +5401 +7079 +4521 +17777 +13521 +11149 +10379 +18112 +11283 +19934 +11485 +11910 +14617 +14283 +514 +19618 +13292 +9308 +19877 +1636 +17647 +13923 +370 +14766 +15962 +10177 +9713 +18321 +14160 +6218 +13667 +5873 +8849 +10158 +7221 +6839 +11160 +11390 +147 +5220 +6953 +934 +13510 +15121 +15338 +1278 +1170 +7464 +2101 +11571 +9134 +7423 +6336 +15471 +17458 +7918 +3378 +7319 +19483 +8734 +9613 +2720 +11265 +6765 +8372 +16232 +10871 +8774 +19504 +6028 +1021 +11576 +1953 +15631 +5944 +12261 +612 +6211 +3979 +2476 +10484 +9700 +12035 +6418 +4433 +12461 +1613 +9820 +10101 +13982 +11020 +6507 +14873 +12825 +848 +2488 +15477 +17672 +6393 +11936 +13241 +7177 +18271 +16063 +5349 +378 +19032 +2933 +3890 +15297 +14979 +1748 +1585 +19406 +16633 +9888 +14586 +8125 +5082 +12337 +16759 +19325 +7031 +19274 +17178 +16104 +12095 +11438 +9745 +3021 +19471 +6770 +685 +11585 +14793 +18847 +6305 +2910 +14485 +15512 +4915 +2846 +19600 +19826 +17586 +7415 +17362 +19755 +15594 +11122 +8146 +12596 +19407 +14661 +18918 +740 +10393 +10191 +8210 +19827 +13785 +5128 +8170 +12032 +11174 +18322 +10912 +9443 +9480 +17635 +11351 +17084 +1498 +19752 +6420 +16387 +2785 +10149 +9115 +16305 +15375 +5907 +16639 +11511 +12763 +14669 +19299 +18490 +18933 +6629 +16839 +13719 +15680 +7349 +17553 +14295 +18195 +18256 +10788 +9975 +6476 +10935 +10392 +389 +3844 +19319 +17549 +1842 +10006 +10265 +6475 +9405 +11574 +14731 +8437 +669 +2441 +8966 +7289 +17410 +1950 +4738 +12818 +4793 +15009 +9634 +4324 +18380 +9530 +9671 +17036 +6731 +13583 +7000 +9502 +12830 +2057 +15810 +19666 +2205 +4409 +18016 +410 +5606 +5739 +13569 +4962 +18270 +5416 +6407 +13061 +9050 +1272 +8997 +13365 +5845 +13713 +14425 +2265 +10491 +592 +18033 +6223 +16491 +15591 +738 +5190 +826 +5386 +2798 +293 +19242 +14475 +6215 +10246 +9257 +6672 +7032 +11375 +17202 +11877 +4074 +9239 +14305 +13632 +14683 +5833 +505 +3046 +14960 +11614 +3942 +14130 +5686 +16352 +10081 +8296 +13752 +8627 +1530 +338 +448 +13840 +18948 +8403 +9402 +5948 +19030 +4638 +176 +2649 +15559 +1741 +8406 +3346 +6269 +15673 +10151 +5292 +1710 +11436 +10951 +978 +12139 +6625 +1317 +8876 +5074 +16409 +4326 +3646 +2822 +10979 +11204 +4072 +3444 +16458 +4057 +16554 +1263 +3146 +5159 +15116 +6853 +2358 +18281 +8954 +1703 +3167 +8099 +12532 +7680 +19549 +5730 +6675 +10926 +13175 +915 +2866 +17992 +3420 +9885 +8557 +4295 +2335 +9846 +11275 +9523 +11905 +19419 +15062 +8896 +10465 +7295 +15543 +13428 +4676 +19146 +10621 +8863 +15911 +17798 +18672 +10390 +3336 +7783 +18747 +12730 +5523 +13064 +11019 +6104 +2153 +12998 +19804 +8382 +11757 +7257 +1593 +7521 +3356 +18181 +17710 +2027 +6584 +16875 +16215 +8757 +14496 +15645 +9207 +13318 +10850 +10286 +654 +6096 +16083 +6467 +17575 +4066 +16453 +9550 +6660 +7766 +19152 +18049 +10443 +41 +13514 +16334 +7436 +12473 +16583 +14053 +14297 +15656 +12490 +6886 +19208 +10432 +6377 +18863 +256 +10634 +2990 +7431 +19759 +6002 +2585 +9536 +3886 +6257 +16525 +15323 +1707 +16165 +15648 +5876 +1048 +4265 +15344 +307 +17066 +5471 +7759 +19561 +8494 +11599 +13033 +14924 +13999 +3749 +12344 +4163 +19128 +19181 +3887 +2925 +12744 +12087 +17291 +11311 +3662 +17814 +10228 +19105 +18335 +19362 +5667 +18221 +17856 +9600 +6035 +5779 +17796 +11086 +15926 +3731 +1908 +16463 +320 +3222 +1451 +14375 +14012 +939 +11594 +9718 +11737 +15853 +14296 +13639 +14856 +9824 +507 +4520 +15803 +16938 +15506 +16470 +11068 +17334 +585 +8791 +7489 +19713 +12283 +7707 +3277 +2922 +10703 +13287 +18629 +4452 +15291 +18096 +5059 +9210 +14525 +11264 +4363 +15985 +7696 +7087 +3120 +1823 +11117 +14340 +16978 +2399 +13609 +7387 +1223 +10276 +3259 +628 +10430 +13209 +1452 +14247 +5018 +7081 +1868 +14663 +11959 +3143 +1219 +9732 +14714 +13594 +1358 +17261 +12848 +9178 +3235 +7091 +4715 +16349 +3386 +4183 +2654 +15049 +11768 +6525 +1254 +19219 +17371 +2554 +19981 +14935 +19342 +3506 +1253 +6705 +5429 +12177 +8613 +16303 +15514 +1978 +4585 +8365 +10140 +11950 +10947 +10651 +16127 +104 +16307 +1472 +10463 +12915 +1869 +17835 +8275 +10313 +11693 +18844 +14498 +12745 +16452 +13059 +19867 +16929 +5899 +1169 +5028 +6875 +6076 +3645 +11112 +12965 +7086 +13105 +11584 +19634 +14686 +7704 +16408 +16825 +14870 +13901 +11735 +10240 +4206 +4372 +6818 +6966 +7073 +8078 +503 +10373 +19271 +7351 +1416 +9702 +14438 +4273 +16421 +10187 +17427 +17797 +19366 +2521 +19397 +12893 +18562 +14896 +7338 +17932 +14203 +5328 +14418 +1519 +1737 +13480 +16145 +9785 +8458 +14726 +6824 +3310 +5844 +16095 +6259 +7621 +407 +12332 +1768 +14428 +2949 +12868 +2459 +9959 +4235 +2453 +15633 +18381 +18265 +10798 +19553 +6570 +19683 +13912 +4956 +16701 +7492 +3388 +8057 +7060 +16726 +9445 +5902 +9935 +17300 +6714 +86 +9015 +6465 +12651 +13030 +6620 +7225 +10315 +7121 +9095 +13362 +14194 +16129 +5679 +13769 +5767 +13162 +14483 +5204 +1019 +3174 +1449 +2905 +6050 +16589 +17834 +13582 +6598 +12528 +6971 +13350 +6102 +16308 +17064 +12426 +10974 +8112 +1864 +5856 +10451 +17559 +12475 +10499 +8163 +13683 +3580 +7045 +18352 +7551 +3700 +10320 +12245 +10160 +9726 +14578 +2031 +5491 +10494 +15716 +15909 +9305 +4371 +1955 +2396 +14843 +607 +19224 +12076 +11346 +13781 +2080 +19571 +1340 +7270 +10090 +15693 +6521 +2337 +10535 +15053 +12504 +7303 +3973 +10345 +18965 +1756 +2566 +2982 +695 +11573 +12760 +19702 +17811 +18104 +11103 +8990 +3733 +13172 +3842 +7247 +16693 +19401 +432 +15256 +15841 +6010 +11327 +1286 +10485 +1971 +1866 +9683 +13271 +14751 +5999 +2766 +5272 +7797 +8620 +2299 +11316 +11133 +5782 +14115 +6175 +7272 +5107 +2945 +16915 +18776 +11021 +13335 +5957 +14566 +15760 +3989 +3800 +7457 +4890 +11228 +2531 +686 +6115 +15981 +14391 +13766 +17630 +17154 +9555 +13019 +10872 +11234 +5340 +1863 +1370 +14740 +4696 +5940 +16460 +13689 +16637 +8303 +12407 +5600 +9533 +19776 +18427 +17960 +4938 +10310 +4055 +12240 +11317 +548 +12440 +11530 +3898 +1574 +9605 +6099 +7442 +13387 +16315 +5396 +17035 +17877 +15302 +7410 +10299 +17225 +16548 +10487 +5377 +19773 +8735 +6194 +11480 +17802 +10625 +7793 +5671 +13685 +15966 +6278 +4336 +455 +15178 +11860 +16209 +7867 +8376 +3062 +12403 +12938 +8787 +17420 +9419 +4813 +5622 +13646 +10469 +2914 +8238 +5269 +13951 +6064 +6140 +18419 +11691 +14216 +14502 +15767 +16925 +10567 +6489 +14825 +2124 +11036 +19312 +6526 +1417 +19688 +15686 +1945 +8192 +250 +11190 +133 +14045 +1532 +8040 +6543 +9400 +3492 +19554 +2147 +19503 +13124 +3688 +4002 +14845 +8580 +18435 +14868 +5075 +15577 +354 +19876 +19144 +11075 +10962 +6633 +16722 +14885 +14299 +11838 +13305 +12772 +3932 +14951 +17939 +3031 +7323 +6950 +852 +13363 +14899 +2280 +16826 +3702 +18951 +1091 +6722 +11533 +13493 +10468 +11598 +12737 +6310 +577 +14609 +5214 +1762 +17122 +11048 +13327 +8569 +18535 +19894 +6281 +9934 +2634 +14894 +10866 +3475 +4038 +9783 +1273 +17957 +14467 +14236 +13904 +14214 +14541 +2074 +12175 +12103 +15535 +4486 +4398 +2964 +1949 +10382 +4874 +7688 +15407 +13482 +17181 +11261 +13127 +19277 +2023 +6077 +7275 +9377 +8527 +15550 +1333 +5219 +9526 +12349 +8185 +16890 +1711 +15551 +15787 +3093 +14372 +14809 +12968 +10799 +2962 +17273 +13548 +8261 +10261 +11642 +622 +1904 +2162 +17288 +15265 +17126 +12512 +5824 +11646 +11960 +17214 +19724 +9793 +14379 +2120 +16520 +16141 +8193 +12282 +15688 +10020 +4837 +7787 +10477 +15160 +10748 +705 +400 +460 +16391 +15749 +19480 +12273 +17088 +14699 +18840 +14936 +14023 +7279 +10559 +5689 +2224 +1153 +7903 +3552 +16959 +17667 +17120 +18425 +11482 +18264 +15253 +1657 +7214 +14136 +11004 +8188 +16316 +9540 +3864 +18474 +162 +18604 +902 +12929 +4256 +8560 +1027 +8281 +11085 +7595 +5993 +7698 +15978 +19090 +3045 +4970 +16614 +9643 +9131 +12238 +19655 +10226 +15959 +8282 +11286 +2984 +11394 +2133 +19586 +761 +4551 +18276 +13194 +7337 +14770 +19065 +15191 +17765 +18905 +11882 +12131 +10882 +18656 +19550 +596 +9725 +13297 +3693 +2428 +16485 +9569 +4201 +5363 +725 +5273 +17098 +8931 +362 +10770 +10340 +1940 +3013 +6516 +10542 +3308 +16371 +18478 +9044 +2307 +284 +1888 +2325 +18719 +9845 +423 +5781 +3273 +1467 +11749 +9641 +626 +7718 +13674 +10922 +15280 +6896 +7379 +10062 +9297 +17937 +11474 +9111 +4461 +19049 +8298 +14370 +1947 +5145 +18411 +19017 +12477 +11189 +16061 +1060 +15899 +5859 +19452 +16459 +11762 +742 +10520 +11469 +19933 +15974 +7395 +8820 +6119 +14287 +19360 +3181 +12085 +3968 +941 +11968 +11166 +1097 +18215 +14204 +16455 +2732 +13499 +6439 +1376 +18674 +14211 +13531 +2935 +4308 +791 +10316 +2696 +19330 +2037 +5054 +1723 +17714 +5760 +11347 +7117 +17910 +19498 +2365 +16898 +7394 +8668 +16335 +14940 +2094 +6481 +14309 +7661 +3493 +2715 +18742 +13598 +12327 +13445 +12533 +3927 +5610 +15112 +3957 +249 +4463 +16670 +776 +9071 +1894 +4716 +16708 +11464 +10753 +1725 +11680 +2220 +16285 +14743 +10295 +12075 +17333 +15529 +16010 +1641 +18549 +10728 +8515 +17018 +9765 +18745 +1754 +2877 +13622 +3889 +1753 +5032 +6425 +3998 +13268 +18086 +6124 +9672 +10994 +12976 +1493 +8039 +6802 +312 +11252 +18244 +19843 +18273 +17254 +7764 +2940 +16572 +8587 +11556 +18522 +3419 +11039 +14084 +3649 +15987 +7172 +14810 +7016 +17060 +13877 +1871 +11376 +5176 +4584 +12354 +10178 +18832 +18569 +14443 +219 +2150 +18328 +2311 +2370 +5488 +17743 +1970 +7738 +15562 +3845 +1146 +1100 +916 +1308 +1537 +19514 +9833 +3848 +16558 +8033 +1404 +6013 +6291 +1378 +19580 +7096 +17146 +3862 +17870 +846 +12736 +2855 +10034 +15604 +9230 +15270 +101 +440 +723 +2615 +13576 +3211 +19581 +17771 +11631 +3647 +1835 +8641 +4827 +8098 +13977 +10411 +17632 +16948 +2608 +373 +2233 +8980 +18729 +9277 +14417 +5691 +14108 +15746 +11056 +16425 +6320 +13618 +15906 +11572 +4595 +8773 +13120 +964 +15387 +15092 +9421 +7151 +17886 +4730 +16956 +16296 +3434 +10851 +9667 +4573 +10449 +6927 +16545 +12056 +11187 +892 +6889 +3017 +14949 +10702 +563 +17595 +17625 +12779 +713 +5529 +15189 +9512 +10591 +7277 +19636 +5160 +17784 +13013 +2258 +9892 +15846 +1049 +16321 +18912 +18534 +2541 +14494 +7645 +739 +593 +7652 +14119 +706 +4843 +248 +4645 +14126 +16547 +11262 +14393 +3967 +10667 +6900 +10923 +7138 +1350 +4868 +313 +19297 +18600 +11524 +4810 +5183 +11776 +2438 +2011 +15691 +6454 +1059 +13072 +1092 +6315 +16500 +17965 +6113 +12995 +17171 +19764 +14942 +2058 +16433 +2709 +159 +7077 +9580 +16157 +7733 +1964 +9762 +18158 +16611 +19454 +17005 +16738 +4569 +17220 +17232 +2063 +10749 +7924 +3763 +7563 +5801 +11663 +2951 +2760 +9996 +16059 +11545 +14519 +14320 +14530 +16587 +9330 +13952 +7913 +9424 +3939 +12379 +2805 +19612 +14863 +6327 +4216 +8927 +3457 +4161 +5192 +6276 +11930 +13811 +4947 +1551 +11051 +12770 +2603 +1568 +10025 +9815 +12170 +10886 +14733 +6125 +16147 +7647 +2089 +127 +5617 +4269 +9869 +211 +8821 +7182 +773 +3282 +2024 +18935 +18489 +9693 +12551 +2510 +4368 +15201 +11091 +13418 +8794 +16934 +8565 +7115 +14739 +12751 +9292 +11110 +797 +8362 +18954 +9219 +1305 +3852 +13234 +7947 +15610 +17274 +8364 +17507 +3799 +19316 +629 +5821 +5380 +9772 +8680 +2069 +9957 +19628 +14470 +1490 +6322 +9217 +10894 +15912 +19072 +7546 +18711 +9351 +17344 +16406 +6415 +3959 +11623 +9160 +16294 +7222 +10098 +18890 +19443 +734 +14258 +15864 +18055 +18246 +6952 +6609 +8740 +16001 +9927 +6227 +11079 +8645 +2831 +11816 +14363 +13110 +7886 +4339 +13742 +10878 +3590 +4367 +18580 +18164 +15478 +8747 +8293 +3047 +17462 +12161 +15668 +11724 +10645 +1356 +17465 +11186 +3727 +17337 +659 +2360 +17991 +19640 +18114 +4882 +8533 +10305 +10492 +1688 +3665 +10652 +19392 +11198 +10619 +8539 +6588 +202 +17699 +10700 +12293 +18008 +1095 +1288 +7094 +6729 +5572 +16779 +10148 +10890 +15373 +9518 +17190 +18618 +4302 +15469 +11796 +5261 +122 +17466 +19447 +3274 +9795 +11991 +9519 +5778 +9665 +1635 +18131 +15427 +17103 +8905 +18095 +11588 +261 +7822 +62 +11419 +10227 +10517 +7116 +16457 +16965 +18396 +8595 +15547 +3528 +7210 +9254 +11244 +11212 +19311 +4615 +8425 +18269 +18696 +13793 +5586 +13113 +5564 +17045 +2481 +6008 +2795 +4734 +18793 +17000 +1816 +10467 +9492 +15747 +3426 +9901 +17713 +18100 +17977 +18676 +4190 +6447 +7129 +19236 +3460 +7909 +4431 +19501 +18626 +5369 +12849 +6301 +10500 +4374 +4567 +17293 +6733 +2963 +17132 +605 +8854 +12211 +4789 +13052 +10774 +2017 +8015 +2614 +5858 +235 +12576 +14343 +4922 +10671 +11567 +15774 +14374 +4541 +19063 +19127 +7798 +8865 +12836 +2487 +7329 +1374 +17585 +15231 +7887 +178 +9238 +4853 +11334 +5582 +3938 +2466 +4554 +16792 +13012 +6969 +8270 +19735 +3772 +73 +5399 +12822 +2969 +2449 +11242 +16487 +10213 +2400 +16873 +16410 +858 +18653 +5307 +17497 +185 +13165 +6163 +4049 +13697 +3059 +4869 +1807 +17432 +16156 +10456 +17152 +12289 +2602 +8389 +19662 +15718 +5722 +11290 +10880 +9857 +887 +18643 +8479 +19928 +8683 +12984 +5602 +8661 +10905 +10283 +6754 +169 +14716 +9011 +8027 +14197 +6600 +14988 +12833 +14043 +14173 +4865 +15399 +16534 +18426 +18932 +7367 +8195 +19627 +3233 +11855 +10906 +1306 +3002 +12252 +15698 +3686 +8691 +7433 +6355 +515 +15365 +15792 +6835 +12959 +2492 +12167 +13079 +15476 +4448 +5609 +14565 +4766 +19833 +10251 +7996 +12524 +1900 +11099 +19864 +10577 +17485 +13293 +5513 +7649 +1258 +9091 +16369 +19542 +16597 +18165 +6074 +1919 +15933 +5830 +9029 +6037 +16183 +11732 +19318 +15450 +15849 +13164 +19983 +16278 +15728 +3899 +3546 +13134 +9193 +823 +5110 +2705 +17755 +17570 +17742 +324 +8350 +18757 +1701 +16686 +280 +5384 +18751 +14360 +4879 +12368 +1406 +15391 +16833 +18391 +11402 +5553 +12571 +19140 +6217 +12347 +17947 +19528 +17166 +2974 +18291 +6809 +6669 +17279 +19214 +3588 +16084 +9191 +495 +11090 +14771 +9669 +5983 +12523 +936 +10085 +4671 +16565 +18606 +9997 +3237 +4035 +14838 +14312 +19151 +10384 +3448 +2451 +17883 +15967 +11651 +17093 +10507 +19902 +10324 +17904 +19882 +19900 +14545 +9190 +3769 +12907 +1746 +3257 +11883 +6231 +11105 +8805 +8336 +5755 +645 +19052 +14412 +17748 +6387 +12985 +1393 +6764 +3877 +9361 +11440 +12346 +675 +12017 +4707 +13942 +10989 +10650 +12102 +11636 +14206 +19305 +2727 +15731 +9468 +6149 +14234 +12074 +16152 +7915 +5151 +12725 +8674 +4204 +19991 +13550 +14573 +17671 +17651 +27 +2640 +3554 +3361 +19783 +66 +2573 +12249 +14556 +5008 +61 +4264 +8263 +18409 +80 +6667 +3530 +9801 +7316 +7517 +17828 +52 +952 +5209 +5982 +3830 +7134 +15370 +11547 +13602 +6708 +16806 +19590 +5301 +5633 +8908 +9950 +841 +16730 +2109 +6060 +6915 +8353 +13086 +9952 +9301 +9449 +12165 +13024 +946 +19044 +30 +12837 +4968 +7857 +15117 +9839 +12898 +13051 +1341 +4535 +4982 +8334 +14171 +4635 +8435 +14079 +17272 +5774 +11843 +7989 +17933 +5953 +3467 +7520 +16231 +11182 +15613 +2789 +15163 +12458 +19614 +17393 +6277 +10314 +2354 +14161 +2824 +11909 +4770 +15581 +18891 +4826 +13497 +12040 +3540 +5560 +4277 +16318 +6738 +19472 +19344 +1475 +1705 +7973 +12852 +9735 +9016 +9592 +4391 +19209 +961 +7515 +14681 +7568 +2864 +17477 +8571 +9121 +3401 +14306 +8461 +14476 +10724 +4705 +15333 +4039 +1465 +2477 +2183 +17209 +18621 +17140 +8833 +18347 +19269 +17183 +19213 +13603 +18152 +6595 +15114 +11868 +17711 +2533 +591 +16742 +4052 +16053 +700 +8729 +3256 +19633 +18336 +8724 +6702 +1671 +19818 +382 +16 +16447 +5683 +5248 +4749 +6758 +11708 +752 +12797 +1494 +4703 +5157 +183 +19955 +12015 +9076 +15188 +15625 +19415 +7757 +11507 +15700 +19069 +18963 +14164 +11888 +19283 +17157 +394 +14840 +14513 +464 +11163 +15624 +4739 +18529 +12859 +2955 +3135 +3061 +4931 +6718 +10241 +17568 +17791 +19036 +4180 +11739 +15131 +14618 +17382 +242 +13273 +19837 +12625 +10363 +1344 +18788 +19028 +9907 +9261 +6656 +5113 +8095 +391 +3337 +16601 +1735 +6685 +10663 +913 +1721 +18056 +6468 +17737 +3098 +7917 +15449 +14897 +2475 +10296 +7658 +9244 +7393 +5544 +9486 +18857 +1685 +19229 +2170 +14208 +10582 +3690 +1221 +10675 +2753 +9972 +8881 +6155 +19762 +4474 +8736 +16992 +19671 +19979 +19110 +17617 +18592 +6479 +14134 +8375 +18015 +14678 +1071 +4820 +14020 +11147 +878 +10569 +6830 +6891 +439 +3795 +9936 +13331 +17982 +381 +3507 +6031 +12832 +15267 +6461 +6522 +2719 +8568 +3285 +4905 +15241 +1173 +11857 +10741 +15871 +5812 +4217 +15939 +11997 +273 +9654 +15536 +16036 +15105 +15708 +4665 +13862 +2954 +6634 +13320 +9861 +8301 +14575 +6778 +9339 +11272 +4880 +12945 +18349 +12218 +18024 +3129 +17170 +18168 +17759 +1360 +9435 +15989 +3431 +6851 +14891 +14738 +7321 +9336 +4558 +9461 +3548 +10630 +16000 +10483 +9918 +19210 +19569 +19896 +11083 +18936 +7653 +14225 +1514 +19748 +9034 +15258 +1516 +3770 +12785 +3781 +11315 +12424 +6892 +10414 +12314 +3157 +5096 +9166 +15303 +2189 +8838 +18492 +2680 +6426 +10697 +12333 +1503 +10769 +4426 +1744 +1667 +9736 +13034 +3518 +14252 +39 +19635 +7834 +19081 +8712 +6510 +2262 +18516 +16549 +18310 +1487 +6143 +3010 +8938 +8199 +19665 +17116 +12405 +9583 +9768 +14913 +7445 +7284 +1648 +17852 +16507 +19845 +8220 +7633 +11504 +16816 +12246 +9138 +19042 +2111 +2103 +8101 +4376 +19343 +17733 +9891 +11721 +14944 +13011 +7233 +13709 +7497 +8891 +18775 +10374 +9212 +9214 +18577 +8814 +11918 +3865 +3897 +8919 +18946 +18640 +12360 +17318 +8739 +383 +16762 +3873 +18292 +7537 +8677 +15824 +18232 +3194 +5787 +12028 +15332 +290 +12153 +1439 +5179 +16210 +19941 +13131 +11859 +11461 +17375 +14773 +146 +17014 +15061 +33 +9267 +16428 +9417 +12916 +9472 +1670 +19143 +16099 +5207 +8894 +11279 +16501 +361 +17180 +2569 +18449 +19718 +500 +19966 +3750 +4579 +16723 +8076 +4322 +18058 +17470 +4795 +13477 +5350 +6955 +5634 +14198 +3537 +13970 +17670 +6253 +15321 +10971 +328 +13640 +18502 +17659 +17541 +8991 +5567 +2298 +11490 +12821 +12527 +1208 +4152 +8764 +19745 +10821 +12727 +5492 +7025 +4223 +6156 +2073 +2799 +12353 +15820 +11932 +9629 +28 +8407 +9738 +11246 +13471 +12611 +11138 +6945 +6367 +7170 +7942 +13479 +4743 +8982 +8853 +6547 +12207 +19828 +3952 +8217 +538 +16993 +10727 +19123 +19118 +18574 +19974 +16464 +13631 +16828 +206 +13190 +11750 +658 +7879 +10083 +15459 +17922 +1443 +17613 +18045 +3329 +2714 +2587 +11538 +153 +10941 +11061 +2827 +2841 +15304 +13414 +3825 +4737 +1070 +4366 +15585 +7775 +2006 +19929 +1458 +7208 +15862 +9717 +7930 +10688 +18001 +9434 +4298 +1216 +7871 +15060 +15934 +9575 +12632 +549 +3870 +16910 +5007 +7226 +7174 +9203 +139 +4623 +12201 +15495 +18009 +3487 +8800 +13607 +4725 +1879 +8289 +10371 +19138 +16200 +3373 +6998 +16566 +15488 +837 +14529 +18170 +4815 +8308 +4611 +9564 +11694 +9410 +15106 +4664 +10041 +1510 +10835 +11841 +19533 +18327 +13441 +10486 +91 +4405 +9995 +1343 +17137 +13673 +19595 +10005 +14806 +18703 +14991 +17962 +3464 +11582 +13716 +14196 +7741 +8581 +1460 +18501 +5019 +10733 +1780 +4934 +9157 +19518 +7796 +15493 +13257 +10092 +1524 +9415 +7140 +18177 +8151 +4263 +7494 +3088 +10876 +19832 +16850 +6829 +2498 +18806 +4042 +15615 +19469 +4858 +18851 +13180 +11382 +13199 +533 +1464 +17678 +8929 +12911 +8209 +13900 +8388 +950 +11406 +11319 +1895 +9430 +199 +7061 +11799 +9794 +19258 +11861 +4923 +13227 +12434 +18900 +13917 +7992 +9379 +18004 +9398 +15494 +13617 +13277 +8486 +2229 +6800 +17394 +7196 +132 +17662 +8066 +11308 +18061 +7760 +2653 +19492 +4476 +11177 +17404 +1649 +12086 +5101 +16246 +15982 +16860 +2301 +17193 +14664 +15780 +3309 +85 +14415 +15586 +10800 +8965 +9384 +2047 +3118 +13551 +13157 +14022 +6192 +7371 +12258 +5589 +2697 +13140 +1495 +10028 +17589 +5970 +19410 +14039 +10096 +19607 +7758 +4702 +14821 +7755 +15005 +11592 +16602 +5062 +6178 +15634 +8306 +5969 +9557 +6840 +11698 +2913 +6519 +5613 +16986 +1618 +11728 +16964 +9827 +19768 +16351 +16081 +2041 +20000 +19840 +425 +16736 +5961 +9537 +7053 +7328 +18140 +17990 +5379 +2408 +16744 +10723 +13309 +18235 +532 +19810 +10423 +149 +5716 +10557 +8693 +6640 +16555 +4974 +16798 +11157 +7332 +17008 +9126 +4525 +6186 +15576 +8913 +8659 +11829 +4365 +1107 +1802 +9291 +197 +5368 +12778 +9264 +15612 +11287 +18333 +3699 +19486 +6968 +11494 +2145 +13340 +5803 +13996 +14826 +17842 +2556 +13613 +3740 +12562 +2313 +14665 +13324 +19985 +12914 +19182 +8045 +10325 +18876 +18386 +5056 +18655 +19922 +4245 +5120 +15460 +9364 +12080 +17101 +8511 +14332 +4612 +4782 +8532 +15298 +10424 +757 +8839 +5752 +16955 +2341 +3642 +5541 +5034 +4059 +12266 +18647 +8816 +781 +13267 +12622 +10514 +18828 +3037 +14938 +1907 +187 +17248 +16877 +5664 +18680 +16663 +15362 +10546 +8207 +771 +13824 +7488 +17846 +5690 +19954 +18339 +2900 +2687 +6356 +3583 +11656 +6225 +13465 +7291 +9296 +18139 +14052 +18149 +17484 +11145 +2560 +11009 +10813 +10828 +4193 +1624 +6162 +8175 +17588 +11955 +18029 +10115 +1322 +14889 +4515 +4691 +17524 +17787 +6618 +1481 +7508 +16484 +12415 +9174 +18111 +14062 +2897 +7417 +11700 +7743 +16106 +6174 +2279 +7390 +13408 +177 +4280 +14347 +12790 +9561 +16586 +14129 +16766 +9003 +14523 +10123 +12005 +15650 +310 +9924 +19436 +10417 +15540 +75 +8871 +10116 +15314 +15818 +14055 +16217 +7665 +728 +13464 +7945 +2752 +18395 +16814 +11817 +6977 +18240 +11923 +980 +15882 +17301 +19982 +15905 +14945 +13099 +7263 +14413 +6051 +7803 +4142 +2902 +2056 +2865 +799 +11741 +15907 +587 +14784 +10686 +336 +1854 +225 +142 +3592 +13961 +10503 +10488 +19160 +12375 +911 +2009 +2042 +17563 +8723 +17424 +4129 +11951 +5546 +13500 +11456 +14142 +13666 +9418 +18587 +15896 +9082 +5196 +18320 +19615 +17551 +9064 +10565 +4500 +14585 +16436 +12483 +1840 +17899 +9362 +9265 +5914 +11898 +11167 +9854 +4034 +5603 +8410 +3760 +19308 +17423 +16598 +15754 +8761 +6956 +3490 +3789 +12964 +13390 +10810 +12432 +3838 +3940 +12895 +15567 +15108 +7453 +8940 +5738 +11901 +6198 +3397 +14695 +16878 +5909 +13232 +17926 +3679 +3238 +7424 +15736 +5806 +19995 +7589 +88 +9062 +8669 +19248 +11074 +12419 +3215 +4120 +3170 +16863 +1817 +522 +4447 +13208 +13705 +6716 +6302 +10037 +6181 +17873 +6654 +16859 +6287 +19237 +16149 +1923 +891 +12891 +18735 +9451 +15936 +9271 +2738 +4239 +18558 +18238 +15856 +19092 +14689 +17996 +6589 +9790 +3509 +1147 +11270 +15614 +5257 +1000 +16135 +6852 +1573 +584 +126 +17694 +16945 +12143 +18584 +17415 +2443 +7664 +1929 +14887 +3007 +1571 +5508 +18113 +18537 +19310 +14948 +6219 +19055 +9327 +19233 +14977 +19158 +17198 +18438 +1297 +19463 +9834 +2185 +289 +2174 +3279 +18722 +3739 +5981 +18889 +8717 +6870 +12827 +16306 +6255 +15222 +3175 +10656 +8059 +5289 +6052 +10126 +3560 +19019 +19074 +12548 +5330 +15107 +18956 +5288 +4637 +6821 +11801 +5088 +5452 +2576 +2834 +4381 +13668 +11780 +11804 +5424 +14355 +6981 +2104 +16546 +8161 +10689 +18147 +15017 +12454 +18385 +2188 +343 +4839 +18811 +14270 +2967 +8460 +4386 +9252 +16823 +11793 +10022 +10653 +19374 +8698 +457 +894 +7594 +18736 +19646 +10901 +18689 +11811 +8414 +7155 +12829 +1658 +14408 +15804 +6374 +9964 +12155 +12140 +10759 +4932 +11621 +6410 +5931 +18136 +14831 +7458 +17869 +11119 +3141 +5364 +19699 +8658 +13235 +17915 +5036 +16390 +15583 +17150 +11454 +9910 +7687 +5442 +18189 +3206 +11422 +1501 +11769 +12845 +13812 +8449 +9623 +5164 +2168 +16268 +6984 +4464 +3909 +5980 +18325 +16419 +4709 +9107 +19421 +8760 +5287 +17942 +4958 +12387 +19304 +17419 +3734 +6524 +4895 +7821 +1716 +1546 +14829 +16990 +11678 +8834 +5479 +4444 +9386 +18126 +5303 +17539 +10729 +6092 +8165 +15001 +13300 +8236 +9150 +8047 +10551 +23 +3545 +1274 +11937 +8542 +7556 +10419 +10356 +17900 +17353 +9499 +3443 +1009 +19184 +2166 +3286 +4697 +17062 +8110 +18209 +10401 +4301 +1332 +10394 +6138 +12113 +15988 +518 +8851 +5053 +17620 +8265 +14409 +3035 +6495 +9881 +10823 +11887 +16272 +12427 +7411 +16009 +11610 +1774 +19133 +16576 +7034 +12846 +491 +3263 +10455 +12707 +6398 +18204 +11277 +18709 +7026 +1548 +1872 +4779 +10606 +10162 +14620 +9721 +11912 +2666 +9061 +15600 +13962 +3960 +17738 +10266 +18363 +296 +15379 +14978 +5068 +19585 +5063 +15742 +7960 +9703 +18496 +16483 +6962 +16024 +5847 +18880 +17082 +16627 +1158 +8713 +6611 +10861 +8002 +14392 +13153 +2005 +9893 +9373 +2981 +2404 +14315 +10758 +12193 +19139 +1109 +7555 +12242 +10900 +19676 +5433 +17606 +12966 +13381 +11096 +11559 +7201 +11098 +6936 +9796 +1639 +3491 +4075 +1847 +13108 +12556 +1138 +19437 +5810 +10578 +8128 +11076 +356 +14980 +16203 +17092 +6511 +11810 +2248 +7762 +2859 +19566 +15143 +16479 +7911 +334 +15087 +15355 +7859 +19225 +4031 +10309 +1850 +14217 +15213 +12197 +15837 +6239 +9542 +10685 +8347 +18355 +5353 +13973 +19705 +13486 +12640 +18286 +7935 +15308 +19663 +3828 +9048 +2102 +4413 +15773 +18959 +5736 +11906 +10121 +7106 +12519 +14813 +8283 +17995 +18245 +17528 +9664 +17514 +12975 +19180 +10776 +5813 +8248 +10809 +5714 +18275 +17511 +3266 +6741 +18541 +15913 +11962 +17903 +16954 +11969 +10747 +10817 +4768 +12361 +4644 +1078 +10225 +15292 +7541 +16364 +8121 +10849 +10508 +12672 +13805 +12359 +3415 +105 +12544 +2669 +6109 +8445 +10024 +1011 +13058 +12643 +25 +10913 +18623 +4593 +3931 +12 +2221 +3544 +15357 +193 +8483 +7874 +4218 +19784 +17358 +18553 +9388 +13395 +10285 +7980 +15753 +13612 +14110 +8226 +12962 +9413 +2444 +3839 +8799 +8120 +14653 +1806 +6353 +16080 +4093 +7975 +16968 +2983 +11979 +11746 +11862 +5064 +8423 +698 +10196 +18712 +5556 +17463 +16295 +14169 +6756 +4706 +6466 +12484 +6914 +5041 +444 +9153 +10742 +9094 +6229 +14835 +1788 +8358 +17297 +8194 +15854 +19056 +5225 +12367 +3178 +7933 +18853 +13392 +12610 +5908 +11284 +15829 +358 +8681 +243 +9363 +17255 +15461 +15770 +8428 +9848 +9961 +2207 +18578 +18187 +4603 +3604 +1631 +11597 +18360 +15034 +17912 +4234 +4361 +4811 +2907 +2663 +4064 +5274 +10457 +7662 +13062 +2525 +3350 +10254 +8331 +18062 +10376 +5816 +14534 +3204 +4854 +15587 +13195 +3990 +14365 +4017 +18262 +5611 +11794 +13077 +9691 +12285 +13835 +10349 +19198 +11448 +15544 +1689 +13986 +16431 +1164 +366 +19223 +9911 +14965 +3609 +9947 +1315 +19685 +18734 +11761 +6602 +511 +18486 +17360 +19869 +18089 +14054 +19378 +19515 +664 +13338 +12624 +19588 +13516 +15006 +19774 +717 +19102 +18654 +17433 +4752 +3812 +10509 +14495 +679 +5734 +13129 +12281 +1133 +3697 +7499 +18278 +15640 +10425 +15827 +8545 +18831 +13749 +17888 +10938 +6204 +18059 +5646 +11803 +13074 +10833 +14143 +11005 +9293 +12834 +1786 +4866 +19249 +9836 +16392 +9181 +13855 +13176 +9478 +701 +9132 +1542 +8862 +12341 +17309 +18661 +16443 +4640 +14857 +8290 +17283 +5496 +14242 +8793 +4576 +4679 +11889 +1438 +4925 +10355 +3819 +6376 +2532 +13526 +14322 +10347 +18854 +5956 +19423 +15665 +8915 +4663 +10692 +19644 +15832 +18590 +8621 +13148 +1924 +17794 +9825 +15755 +10445 +19190 +17758 +14146 +6411 +2590 +8168 +15901 +3581 +6440 +15382 +14036 +13349 +6535 +6592 +13203 +3161 +4546 +8444 +17303 +18984 +15002 +8550 +19625 +19884 +233 +7041 +19499 +18047 +4468 +17987 +13641 +10589 +17550 +7576 +7093 +19113 +1199 +1380 +9182 +12319 +19033 +1001 +17730 +13731 +7802 +19430 +6872 +8684 +6579 +8562 +6561 +11864 +6199 +15051 +15499 +11065 +18566 +5996 +13174 +4025 +7480 +15136 +17700 +3085 +5949 +7681 +9905 +11608 +13104 +17189 +7514 +3618 +3353 +17233 +8916 +15953 +18436 +13280 +5084 +7360 +5594 +6404 +6187 +10143 +3949 +14028 +4990 +2245 +13736 +17844 +11748 +15719 +17244 +876 +3124 +15721 +14763 +5663 +11000 +18467 +6098 +17920 +2939 +6864 +7719 +2765 +5726 +16995 +13325 +11705 +18849 +8292 +3368 +13861 +16438 +6196 +17304 +18376 +4872 +1275 +2509 +2557 +2340 +7021 +2650 +1794 +12043 +14059 +9408 +18205 +18720 +13010 +17627 +11010 +17859 +17921 +10917 +12656 +5336 +8553 +6975 +4383 +4116 +14693 +11771 +6093 +26 +9224 +14712 +17335 +17173 +9586 +17776 +1680 +10825 +4244 +15878 +8628 +17165 +3066 +818 +7383 +15126 +18393 +10612 +15174 +5763 +12617 +14167 +5003 +3096 +13585 +1448 +9831 +3218 +16882 +15123 +18705 +2600 +15032 +7241 +11132 +3297 +13040 +18608 +9974 +14192 +13958 +7070 +8843 +7418 +4296 +4012 +5566 +16074 +5409 +5514 +14423 +10210 +9240 +10558 +8792 +4174 +13021 +2508 +8744 +3624 +9826 +2099 +2450 +4997 +8300 +9202 +7290 +8354 +9455 +11114 +7824 +18422 +16705 +14690 +5329 +6739 +15794 +1497 +2426 +5643 +15341 +4695 +17307 +18641 +16288 +6546 +3326 +1076 +6025 +19048 +5454 +11089 +10899 +16578 +1260 +17090 +2870 +11028 +12158 +13954 +17078 +18718 +16446 +9290 +4775 +1233 +19424 +15685 +18182 +11836 +5741 +18378 +11354 +1724 +11609 +10093 +5968 +19296 +16590 +3108 +10896 +12243 +11 +14765 +11872 +12395 +11295 +17536 +18464 +5233 +995 +13449 +11846 +427 +4382 +11744 +12932 +18627 +1129 +7773 +1205 +15155 +12680 +10125 +16326 +6385 +13849 +11225 +3097 +16889 +399 +10042 +15394 +7503 +3511 +3026 +16831 +2012 +7969 +15158 +13916 +15538 +19165 +15629 +3383 +1615 +4608 +8345 +2181 +15602 +2985 +1206 +13910 +17242 +9573 +10789 +13366 +9324 +627 +10717 +18922 +2137 +6038 +8617 +14878 +1819 +12227 +16619 +7412 +15689 +13830 +14674 +15857 +18942 +8806 +3933 +5087 +17243 +17602 +4797 +9471 +12786 +6785 +15817 +12382 +5942 +13423 +657 +766 +12638 +15369 +2293 +4261 +4313 +19519 +13182 +13050 +294 +11471 +9368 +3217 +8225 +3714 +416 +12791 +19712 +11496 +10593 +1969 +1261 +9453 +5450 +2264 +1520 +17027 +14102 +8043 +16588 +17379 +16002 +7426 +4960 +1687 +12676 +17614 +7422 +2558 +10843 +16071 +16109 +11505 +9696 +2708 +15539 +15414 +560 +12428 +3582 +9599 +12300 +404 +5525 +7065 +19368 +15657 +14440 +15111 +13909 +3300 +10929 +13225 +17587 +4320 +2199 +5742 +11038 +4081 +7977 +12058 +3130 +1240 +13057 +11639 +5235 +8789 +2850 +5839 +19562 +8872 +17666 +18377 +10257 +17356 +18091 +7705 +215 +9917 +7102 +18268 +16976 +9009 +16032 +7962 +7666 +15885 +7128 +14114 +17499 +12351 +7184 +1280 +9320 +12328 +19482 +11432 +8508 +6719 +16981 +1659 +14253 +13553 +17390 +8635 +10945 +13402 +1201 +15144 +18331 +1181 +5335 +10864 +16844 +11055 +4745 +6403 +13747 +1579 +6133 +8077 +7402 +17330 +10048 +102 +838 +6401 +12303 +14218 +9804 +17107 +13621 +10206 +881 +11995 +17268 +15072 +12536 +9458 +18251 +8868 +13994 +13206 +15980 +17688 +13754 +1299 +17566 +15345 +6375 +3924 +17069 +5004 +14362 +9065 +18043 +714 +18225 +15089 +3084 +287 +11080 +7256 +7701 +18466 +15354 +11668 +3553 +14395 +19915 +18786 +825 +19835 +13575 +1831 +247 +2016 +4286 +8007 +12230 +1643 +3260 +1430 +19238 +16397 +11291 +17482 +18813 +12673 +11067 +1375 +13775 +16079 +7496 +6538 +8212 +10677 +12710 +15102 +1845 +10450 +11695 +3706 +8988 +16641 +3005 +5552 +11908 +6518 +18416 +18560 +14484 +10812 +311 +6700 +2489 +4814 +6725 +8462 +8875 +8512 +6328 +7243 +15055 +18698 +12542 +10775 +5152 +5131 +5549 +16658 +17703 +10351 +15186 +9407 +13696 +14357 +4714 +18871 +6300 +5477 +222 +5256 +14735 +9200 +16765 +4856 +13549 +1656 +18619 +13966 +2214 +5952 +796 +8537 +3193 +5094 +16830 +11210 +11692 +11994 +8379 +17603 +2092 +13879 +18172 +15026 +4179 +16301 +19341 +9136 +11273 +5930 +7936 +5398 +14274 +18913 +13555 +14098 +1330 +2296 +12950 +16616 +11661 +2529 +6732 +14431 +12913 +13222 +3720 +11359 +18399 +9565 +18706 +17075 +12089 +17130 +2672 +5208 +15159 +9660 +3923 +2693 +16745 +2934 +9287 +13990 +4122 +17963 +6070 +2628 +19739 +6648 +8719 +17515 +11774 +15489 +14652 +5338 +4892 +3433 +4484 +11632 +9010 +3033 +8230 +3200 +3977 +17847 +8202 +18675 +1119 +10056 +1413 +7573 +14341 +1036 +17108 +12098 +4137 +3651 +13379 +7904 +10641 +14976 +2180 +10944 +19356 +473 +16989 +8832 +15224 +6890 +2891 +2126 +4276 +11488 +12117 +6388 +4027 +8068 +409 +17687 +18778 +4822 +414 +18290 +3038 +6865 +10262 +14837 +10141 +9792 +13545 +18885 +4761 +4475 +3529 +19217 +19693 +3348 +7212 +18332 +7372 +12269 +3032 +11697 +16051 +13558 +2718 +19082 +6046 +11137 +6485 +9052 +14647 +13333 +5828 +4397 +7386 +8605 +5651 +6024 +5189 +16345 +15434 +14725 +4514 +11023 +13382 +7578 +13239 +4532 +15295 +11892 +9545 +2152 +14703 +18904 +8531 +15223 +18084 +4524 +74 +367 +3302 +15187 +17948 +5692 +6128 +6172 +862 +1800 +4684 +9414 +11033 +13573 +6000 +9615 +17476 +9059 +4805 +17054 +8582 +11549 +5650 +12626 +14308 +2238 +11714 +566 +5316 +2751 +4733 +970 +18769 +12843 +4658 +6209 +8830 +18171 +19497 +14780 +3730 +2661 +12284 +7012 +12277 +10008 +11933 +3142 +18428 +3667 +161 +12152 +12630 +18370 +7939 +2046 +3771 +16803 +13993 +2129 +14608 +18839 +12060 +16404 +11870 +18923 +10698 +6659 +2352 +1561 +4226 +14741 +17692 +9649 +5073 +15142 +19353 +6009 +3240 +5344 +10170 +4550 +17907 +11679 +7651 +6796 +397 +7148 +12621 +5558 +3054 +14049 +2090 +13856 +12317 +10660 +2514 +14338 +10482 +19202 +11648 +16403 +1335 +19574 +16665 +11989 +4482 +16313 +4481 +17290 +3742 +4407 +1528 +12901 +7516 +15834 +10231 +4945 +11337 +12609 +5516 +17039 +18026 +11509 +15831 +6085 +976 +18368 +2249 +17712 +16657 +2085 +1054 +1880 +6681 +8094 +9636 +18833 +14291 +7659 +11754 +19714 +17347 +1565 +18074 +7791 +6470 +2289 +10027 +1934 +12826 +19295 +17179 +6112 +15008 +17860 +9711 +12338 +18888 +9216 +17573 +15741 +6557 +11821 +3785 +13764 +14853 +5129 +15446 +1243 +2836 +11920 +4115 +19743 +1410 +16817 +18379 +3104 +5661 +13223 +4483 +2670 +945 +17124 +4863 +13476 +18169 +15448 +18006 +19349 +8378 +1572 +12920 +7204 +4230 +19243 +9426 +12260 +16377 +10194 +2825 +4908 +11368 +3711 +19583 +17367 +292 +12903 +16330 +5391 +13254 +494 +16445 +13690 +7236 +15348 +14251 +9734 +13249 +16374 +11516 +7995 +5557 +5923 +11866 +7832 +5799 +19500 +8329 +18652 +19034 +5186 +14753 +1061 +3947 +5497 +4861 +9169 +15018 +12213 +2179 +13884 +5766 +4358 +14866 +3880 +19470 +14598 +11652 +10155 +19732 +13138 +4287 +131 +16430 +7010 +4214 +12811 +1373 +9830 +13243 +3291 +12009 +14404 +3379 +12855 +1414 +13853 +14429 +4053 +3056 +4022 +9318 +7637 +3941 +5417 +16719 +8978 +16432 +19505 +12738 +1730 +16789 +18145 +18371 +3497 +8119 +7470 +17800 +13870 +18571 +6554 +11431 +14445 +7197 +1933 +15623 +12777 +7361 +13844 +143 +10883 +14593 +11806 +2645 +17512 +6887 +18030 +3128 +4333 +8911 +10198 +12129 +5486 +8823 +17547 +14551 +11753 +10950 +16379 +8214 +2067 +18622 +17927 +14904 +4844 +14790 +5246 +12162 +10631 +649 +15091 +14794 +11946 +6781 +17949 +15902 +6055 +8503 +9719 +7506 +15682 +4291 +15994 +13645 +9256 +4156 +12447 +7339 +3122 +3601 +19670 +15202 +14577 +17212 +6424 +6674 +4651 +2075 +8017 +8278 +12156 +8651 +5077 +1480 +3701 +4011 +9612 +17701 +12062 +19899 +15976 +12877 +14421 +18792 +6460 +12295 +13661 +3963 +3847 +12668 +5080 +16134 +2026 +12629 +10804 +10771 +12274 +214 +4299 +9503 +19905 +6019 +2035 +6059 +17521 +5218 +16809 +536 +6746 +12068 +9279 +19432 +15122 +10060 +10991 +6585 +1998 +10142 +3087 +11964 +1525 +1860 +11818 +2219 +14103 +13693 +5011 +7139 +13527 +5879 +7955 +9316 +16697 +8869 +11251 +1534 +4284 +733 +17527 +5569 +14351 +17418 +15542 +14860 +9767 +13284 +15301 +15678 +15219 +17311 +11024 +5699 +4445 +12438 +13540 +746 +6888 +19798 +6721 +1389 +145 +4078 +8049 +15621 +6921 +12862 +14073 +18173 +10683 +5789 +18085 +2374 +16355 +14745 +12662 +4894 +8104 +12685 +6399 +13724 +10841 +18069 +9887 +2652 +11241 +18298 +8535 +8474 +16926 +9249 +7268 +6697 +19167 +1891 +11305 +8575 +18527 +6213 +8178 +13619 +19999 +18898 +13321 +10640 +8731 +7009 +7686 +2565 +4069 +12508 +15261 +2480 +18716 +17238 +15868 +8197 +19619 +14934 +18585 +10536 +767 +13067 +8857 +18953 +16893 +13144 +19008 +4964 +9658 +4364 +9335 +8459 +379 +18910 +3409 +6979 +1409 +4906 +15490 +630 +2038 +2139 +5842 +6783 +696 +18035 +4543 +14226 +19690 +5147 +9021 +6036 +1128 +4325 +13201 +18119 +10323 +9701 +9494 +10550 +18311 +15527 +19917 +1122 +3407 +19589 +4884 +11907 +196 +19568 +6717 +13372 +13179 +16909 +14987 +6436 +14334 +5811 +15522 +1797 +3359 +15798 +8064 +7523 +11405 +15664 +8189 +14549 +19802 +2658 +3425 +14066 +15800 +11281 +16488 +9329 +8763 +10973 +3798 +5228 +3687 +13819 +9967 +3652 +8239 +8705 +8318 +10694 +7897 +9937 +11380 +17016 +10220 +12399 +17621 +1650 +17071 +14603 +15735 +7510 +3269 +17403 +4318 +3343 +2797 +3306 +3109 +5897 +3526 +9286 +14601 +10375 +16691 +10859 +16013 +14381 +1922 +8004 +7460 +19022 +10605 +14881 +8109 +16389 +9692 +435 +5 +1713 +7550 +12168 +4808 +9708 +9148 +4876 +16569 +6508 +13657 +17582 +1709 +1232 +16140 +4037 +17249 +14610 +1293 +8356 +7380 +8971 +14378 +19185 +16906 +15180 +14222 +3003 +8038 +17821 +11942 +18488 +10903 +11667 +608 +2157 +12806 +8649 +3983 +16528 +14 +15311 +17327 +13842 +19256 +16648 +184 +9485 +15858 +1309 +4362 +16655 +17745 +13184 +9441 +4408 +16967 +19195 +16827 +16671 +9188 +3824 +18471 +6317 +14144 +13056 +10402 +1108 +18707 +4121 +16297 +11827 +18002 +19301 +16290 +10822 +7114 +17389 +534 +2364 +1159 +17258 +18341 +6967 +13564 +2743 +6768 +8031 +18141 +363 +18456 +14654 +7884 +19790 +8115 +12111 +8521 +12506 +7756 +7607 +5973 +8925 +18952 +11686 +6895 +5100 +9731 +3107 +7847 +1580 +4143 +2344 +7721 +7342 +13237 +6268 +12144 +4032 +10943 +4063 +565 +15758 +18159 +1539 +14732 +8563 +18624 +7076 +9595 +13354 +4046 +7679 +5455 +14993 +13154 +4082 +2148 +10549 +12991 +5394 +4182 +12226 +16029 +19740 +16292 +4952 +19380 +10330 +10990 +17685 +6678 +6527 +18455 +17874 +9650 +18494 +3807 +2346 +6256 +11483 +1874 +14111 +10073 +10328 +692 +10003 +10563 +9710 +8137 +3685 +16689 +129 +6753 +5559 +12231 +1290 +4105 +8819 +5636 +14720 +5296 +11586 +16631 +7615 +11047 +14797 +7355 +1883 +14151 +8546 +15245 +11203 +1408 +11596 +1398 +4197 +14191 +7156 +16091 +19409 +1633 +5277 +4310 +6226 +454 +9288 +5453 +9412 +3163 +5238 +10146 +6151 +2424 +4145 +18612 +16892 +19907 +5693 +15343 +12492 +13020 +965 +4898 +17448 +4652 +10150 +17739 +16189 +4860 +4473 +8136 +8583 +7726 +1771 +2539 +8935 +12164 +12192 +11527 +5119 +14890 +9766 +2570 +16100 +9570 +14602 +16325 +7391 +6299 +14230 +8447 +5740 +6443 +14046 +17740 +10235 +19488 +13597 +2875 +17644 +16773 +1058 +12614 +13906 +16543 +16394 +14939 +14905 +11468 +18827 +18564 +19462 +13002 +14430 +13677 +9396 +5223 +16987 +11600 +8430 +6621 +13135 +12534 +4158 +10596 +14768 +3334 +16593 +7267 +82 +1042 +13700 +13928 +3593 +2728 +17783 +13616 +18860 +16477 +1884 +3341 +4455 +8523 +5360 +6744 +9787 +712 +12234 +64 +9539 +6601 +14071 +18366 +5169 +15012 +16284 +4748 +2977 +5519 +8456 +7158 +8475 +9258 +16579 +8251 +3106 +10428 +19736 +1630 +16367 +4317 +15059 +16195 +7278 +5500 +8624 +6245 +18284 +12479 +4221 +17256 +17119 +135 +9422 +8909 +7927 +10264 +13017 +7051 +12580 +4089 +192 +11988 +6610 +19422 +8392 +17224 +5795 +5322 +1995 +6348 +9236 +4495 +6127 +19207 +16255 +4836 +9538 +19629 +14271 +11400 +3575 +18824 +4141 +15209 +13699 +2686 +14671 +6450 +13634 +18974 +19253 +14865 +16388 +18247 +1431 +12762 +7066 +5608 +5924 +6989 +14656 +9274 +6333 +15028 +6261 +1608 +11467 +19389 +13533 +1491 +3458 +1015 +2794 +8610 +18369 +7108 +7727 +10071 +19320 +8491 +12090 +8934 +3015 +9067 +16834 +4963 +14605 +18596 +2405 +1991 +6503 +15378 +8231 +17919 +4285 +2496 +555 +793 +2366 +13341 +18687 +1202 +6599 +17973 +11949 +9640 +5875 +12414 +14414 +6094 +10920 +13905 +18533 +10406 +13899 +8976 +1188 +7873 +19188 +12857 +16770 +10304 +18077 +9698 +17593 +12390 +11536 +9862 +5744 +5033 +13998 +5743 +1031 +10481 +9928 +5362 +5057 +5149 +18682 +5295 +2131 +5474 +8808 +3726 +14455 +4422 +12459 +8650 +17260 +14754 +15993 +5910 +18194 +19914 +4904 +11235 +6712 +2430 +9566 +7643 +19651 +10145 +19816 +18051 +15056 +6195 +2502 +12838 +259 +8380 +13378 +5311 +5826 +19153 +13762 +14531 +12340 +19532 +4780 +15970 +5958 +17384 +15415 +11795 +9404 +7356 +11945 +8321 +10830 +321 +18044 +5786 +6319 +12251 +10016 +2739 +7976 +15319 +4319 +4911 +5599 +12753 +11303 +1210 +10440 +11546 +6635 +18617 +19729 +662 +18724 +16559 +17829 +8114 +19373 +14057 +16199 +5306 +2419 +3513 +17503 +3405 +1569 +15166 +3577 +15124 +9460 +3982 +12280 +18257 +5138 +15084 +5883 +3655 +18036 +18505 +11417 +7229 +11943 +19333 +7107 +19524 +4591 +754 +17184 +9066 +5834 +11322 +8985 +3984 +13421 +3023 +13334 +18132 +2854 +7330 +5410 +18118 +4630 +6441 +12951 +7407 +170 +12047 +5117 +683 +3403 +5236 +48 +14544 +18723 +2059 +816 +14524 +6826 +17732 +7325 +17945 +14759 +16490 +12132 +8464 +7878 +12376 +13459 +304 +11191 +13578 +3797 +16282 +3876 +14933 +9528 +3901 +16331 +429 +7880 +2484 +3398 +19582 +1596 +933 +9747 +18174 +2976 +12715 +1793 +8877 +2291 +2237 +6528 +16638 +6294 +279 +16974 +11660 +14995 +6694 +8041 +10029 +7527 +4754 +4013 +671 +7549 +18405 +12204 +9904 +11927 +11064 +7059 +14336 +4228 +15510 +1463 +1763 +17946 +1144 +14279 +15274 +9282 +16781 +17761 +14361 +4040 +9075 +19980 +4399 +13015 +8746 +17579 +17764 +18041 +7678 +3115 +14892 +13945 +4227 +1014 +5415 +570 +18354 +18808 +19142 +6619 +19507 +15353 +1381 +10597 +14597 +11939 +11383 +4369 +19836 +18420 +11034 +15451 +2912 +13181 +17693 +1844 +14101 +6400 +4602 +813 +17818 +14729 +13534 +10620 +18667 +3751 +15942 +9006 +10752 +11965 +13946 +2648 +11486 +19726 +7409 +11815 +15088 +1116 +5694 +1914 +433 +14816 +9631 +17106 +7899 +14328 +6912 +485 +16628 +11183 +17172 +7634 +10806 +5383 +16801 +14746 +19564 +19742 +13883 +13397 +15364 +1255 +2775 +12225 +7235 +19495 +3944 +17472 +1581 +19200 +7814 +3585 +19346 +14017 +899 +18850 +4157 +16396 +16035 +8732 +13932 +17177 +13345 +19379 +1145 +897 +17560 +4224 +16014 +16988 +4311 +13296 +1552 +10120 +13326 +5962 +16790 +19631 +10321 +10996 +4099 +17222 +7567 +18442 +19656 +6238 +19874 +16230 +12050 +2236 +11548 +7056 +14571 +1382 +1642 +14526 +14518 +16550 +6581 +19885 +223 +7449 +1605 +16119 +10624 +15328 +2097 +10058 +6965 +5217 +1196 +11506 +4668 +9397 +4270 +18060 +11229 +7898 +7554 +13048 +13155 +15733 +4900 +1715 +12112 +3515 +16874 +9746 +42 +13347 +11552 +1110 +3879 +19871 +974 +7765 +6836 +3355 +124 +13405 +1469 +13989 +11169 +14533 +1105 +3168 +15740 +18925 +9135 +736 +13177 +1101 +13116 +19539 +19803 +11259 +10571 +93 +4170 +2136 +2149 +8315 +5835 +5146 +16683 +14300 +8141 +14570 +16242 +10722 +6364 +11082 +13458 +8339 +7965 +6334 +16884 +12771 +14021 +550 +8998 +51 +17976 +19806 +703 +1339 +4380 +8285 +9915 +9806 +16158 +3052 +18125 +1999 +18684 +13119 +2395 +8639 +19154 +2367 +19891 +5733 +14450 +8069 +17660 +16498 +997 +1941 +7448 +8520 +14805 +6120 +11676 +14629 +186 +17567 +5106 +355 +8831 +8257 +18066 +19011 +17346 +17940 +17746 +3905 +11148 +14121 +16966 +9385 +13780 +6134 +13248 +17871 +19839 +8060 +2276 +19749 +2901 +15838 +16115 +7095 +8085 +16777 +1674 +10712 +15745 +3953 +3837 +8037 +10572 +9635 +14477 +11699 +827 +5470 +5852 +9543 +1751 +5889 +18669 +16258 +9054 +17331 +13029 +12408 +4429 +10307 +15826 +14301 +973 +16123 +276 +16012 +9945 +18403 +15923 +17262 +15662 +9810 +9923 +7320 +8457 +16911 +10992 +14162 +12969 +10473 +16845 +16935 +3251 +11115 +8087 +12731 +9823 +5242 +7432 +10216 +18202 +10939 +17401 +15813 +3446 +3081 +18924 +15029 +16668 +317 +3012 +19552 +11206 +6558 +6081 +3628 +9662 +6419 +15498 +13943 +15513 +7818 +990 +6923 +68 +15508 +15161 +5127 +4534 +9165 +3951 +8100 +14187 +16434 +18842 +10691 +1620 +14783 +7801 +3241 +2583 +10907 +14552 +19695 +3846 +13358 +2516 +6664 +13523 +1545 +11097 +17269 +19369 +14156 +17245 +17640 +14426 +780 +17131 +11392 +4758 +7819 +19694 +6701 +6 +13947 +17021 +17924 +7401 +4138 +322 +13588 +10490 +8715 +8267 +6898 +10476 +5051 +17392 +3042 +14317 +17980 +6122 +13792 +12560 +11510 +15968 +13481 +17974 +2988 +14875 +4936 +10290 +6408 +3543 +8371 +13400 +19824 +6448 +92 +5547 +6228 +15940 +19420 +2314 +14093 +19285 +16682 +17955 +18802 +7961 +15630 +16023 +11819 +13723 +10369 +3833 +9724 +14906 +18783 +8086 +12179 +6687 +19870 +18518 +4144 +16795 +4781 +14676 +9051 +16876 +6409 +5315 +12644 +1062 +2581 +14210 +14092 +18300 +7029 +15641 +9977 +12109 +13322 +1312 +7861 +8218 +17468 +156 +12623 +14235 +16161 +18642 +16952 +15766 +5871 +7614 +18929 +6142 +12679 +18408 +15070 +1377 +12570 +13385 +16243 +17250 +18081 +19255 +506 +3915 +571 +12497 +4080 +19023 +8920 +6752 +7676 +4499 +4480 +16328 +16656 +14090 +8466 +9488 +2876 +11340 +19962 +7599 +136 +11041 +1976 +13485 +15259 +6786 +16277 +17096 +6086 +1155 +1056 +7694 +19490 +14437 +7592 +19787 +5475 +17600 +2226 +18997 +9883 +12463 +12711 +6354 +2538 +13032 +13256 +5251 +9382 +15900 +342 +60 +14505 +10564 +16440 +18611 +12910 +4570 +5290 +6246 +12646 +12124 +11903 +4114 +5877 +14619 +16366 +17310 +8269 +8063 +12941 +5439 +4672 +13660 +19265 +6164 +691 +5819 +19567 +3586 +12884 +463 +3722 +9383 +15725 +13096 +18398 +16383 +2272 +12977 +14807 +9041 +1445 +4237 +6946 +15317 +1607 +11531 +11336 +16343 +4271 +19812 +6220 +2839 +8725 +13567 +3746 +7737 +8132 +5393 +1279 +19012 +9170 +12350 +7839 +19813 +840 +3313 +7600 +12378 +19107 +5762 +4650 +17686 +601 +13133 +8396 +3794 +19 +13838 +3387 +5441 +12389 +15696 +10875 +12803 +16761 +5972 +5866 +15504 +17399 +8488 +2958 +18573 +8886 +16413 +5205 +13433 +10655 +661 +6616 +15515 +7253 +8342 +2612 +3134 +4165 +4972 +782 +1829 +994 +12931 +12936 +4155 +13115 +4503 +15575 +12267 +17414 +16263 +6652 +15759 +8463 +2156 +4001 +17493 +17236 +10519 +15734 +9902 +2899 +12783 +11925 +19649 +18825 +15683 +18897 +18155 +278 +13790 +6180 +14615 +5616 +9942 +12801 +15687 +5249 +6727 +11421 +16176 +15891 +16112 +3219 +19367 +10367 +12593 +2889 +6144 +650 +12616 +7037 +8346 +808 +18834 +15385 +2616 +617 +14786 +12559 +19508 +17657 +9012 +19716 +4107 +8946 +956 +12154 +4215 +11869 +16151 +17287 +18666 +5318 +6745 +10410 +1067 +8018 +5305 +1983 +18588 +12684 +8572 +529 +13503 +5510 +6185 +14035 +10441 +1183 +7232 +9933 +12188 +18404 +3853 +11560 +15524 +789 +10959 +5000 +6490 +8541 +16202 +544 +19494 +8642 +4562 +4776 +19919 +13538 +18005 +9880 +8858 +13826 +16786 +4957 +16752 +4800 +15603 +9596 +16896 +2578 +12051 +7294 +17473 +3669 +11445 +18692 +6846 +10007 +4470 +5649 +16365 +5376 +13628 +13590 +5188 +2250 +2390 +14215 +18135 +420 +1652 +13217 +7493 +12689 +7716 +13398 +4297 +18088 +12986 +7571 +13886 +2045 +18737 +18243 +18971 +3610 +14397 +3579 +15336 +6321 +6391 +1719 +17319 +17572 +15230 +6030 +17246 +9619 +12294 +2268 +15325 +9604 +14781 +17879 +3826 +4181 +15004 +12981 +11931 +3501 +12309 +19467 +16188 +8 +5761 +5047 +7931 +9004 +17186 +14911 +2842 +1834 +19606 +12927 +12540 +17812 +13687 +17206 +9098 +16417 +11704 +486 +2431 +17720 +11230 +4778 +19738 +8234 +1102 +15532 +906 +10229 +4889 +16302 +10964 +9673 +4084 +9912 +3422 +7104 +14634 +10845 +10107 +11378 +14685 +2387 +18342 +15699 +3608 +13286 +12217 +5020 +16120 +8543 +12979 +15796 +6285 +16456 +4969 +18845 +751 +6159 +16228 +2015 +3849 +9102 +849 +13417 +1778 +2108 +810 +6833 +17100 +4136 +4526 +348 +17187 +272 +5831 +16304 +12874 +7566 +4659 +3616 +9359 +18645 +3395 +8105 +1424 +14024 +18314 +18123 +2361 +17857 +2684 +2114 +8646 +10010 +17429 +12024 +10773 +6044 +9399 +16045 +542 +15225 +7062 +17619 +4266 +9973 +17451 +11670 +749 +9490 +5913 +5705 +19084 +13556 +7908 +8753 +1939 +5448 +15643 +11219 +6994 +5668 +7353 +19668 +18092 +2999 +15511 +18207 +2458 +9269 +18782 +14041 +19358 +18175 +7113 +5123 +11606 +13263 +17892 +16218 +17449 +302 +8544 +15266 +19465 +15546 +10679 +13494 +6645 +18063 +12033 +8167 +4509 +13501 +258 +18154 +12290 +955 +13412 +12081 +7255 +3542 +11523 +5713 +16957 +10784 +19886 +6200 +12116 +10209 +10858 +18213 +4051 +5413 +3900 +5108 +16709 +17534 +19460 +8025 +8304 +7176 +9374 +3312 +4346 +8566 +19106 +6582 +14715 +12146 +18011 +1930 +353 +4631 +212 +7994 +9144 +18080 +12526 +18229 +5619 +6973 +19112 +9953 +5365 +7482 +3955 +19937 +7042 +13816 +17966 +2216 +9590 +17203 +8882 +4918 +5865 +17565 +10643 +6811 +12356 +3950 +3632 +5804 +2244 +15663 +19481 +19534 +14651 +15785 +152 +13732 +2563 +13773 +11820 +12989 +7313 +16704 +8337 +19352 +7817 +2633 +15432 +8955 +18497 +15417 +8848 +4148 +3764 +10395 +14673 +2832 +6877 +15756 +19707 +4682 +8989 +9450 +2535 +9723 +6006 +12912 +2255 +521 +7730 +277 +15079 +8893 +11751 +318 +15397 +6416 +4171 +12817 +12345 +11092 +19364 +18796 +19477 +5517 +5241 +14964 +10554 +16073 +5680 +9393 +12410 +305 +4274 +4973 +7369 +15226 +5568 +12591 +2721 +9655 +11535 +9032 +8160 +10585 +16016 +7561 +8812 +9828 +5266 +1351 +5358 +7389 +3806 +18586 +12487 +15436 +4986 +9498 +12180 +4337 +15483 +8271 +11957 +16279 +9859 +15845 +1502 +11066 +6412 +2620 +3718 +14954 +19976 +10790 +4471 +4189 +18102 +18519 +14333 +12339 +17361 +8204 +4937 +1295 +19094 +868 +4859 +19365 +3413 +1775 +13156 +5936 +5390 +11015 +2632 +11963 +7828 +6039 +3276 +5656 +17665 +7501 +1080 +9674 +11243 +7922 +11042 +6307 +13892 +13940 +9482 +8866 +15118 +6576 +1784 +17951 +1901 +8366 +17129 +10200 +16885 +19240 +16170 +3948 +3835 +7631 +12396 +19611 +17295 +9348 +8686 +12530 +3715 +3212 +9841 +8213 +1604 +11849 +1684 +465 +10127 +6136 +10256 +1758 +13323 +1538 +17436 +2032 +12036 +8338 +10050 +17148 +14112 +15592 +5090 +13384 +15492 +15339 +13173 +6208 +4736 +18764 +18690 +18124 +1396 +18789 +9741 +18713 +10086 +16093 +4799 +19020 +1485 +365 +17407 +15632 +3063 +5342 +13070 +1329 +8878 +11152 +13787 +15979 +4891 +16398 +7218 +11289 +18249 +5469 +11418 +10870 +1887 +16429 +8675 +7986 +11159 +5387 +58 +9168 +16846 +17467 +9452 +17447 +16503 +651 +5458 +6370 +10169 +10063 +18820 +406 +17339 +17417 +1384 +19709 +12480 +9303 +2448 +5043 +6791 +3077 +6666 +19340 +17611 +10865 +9900 +11742 +12695 +17002 +14767 +11634 +17841 +19678 +9097 +11650 +1808 +110 +1770 +10863 +17491 +14461 +3759 +5698 +12149 +19093 +17735 +5231 +2271 +14514 +12100 +13467 +2066 +2445 +15481 +5126 +3149 +16664 +6392 +10399 +65 +7855 +7970 +18525 +6126 +15844 +5768 +18805 +9621 +15101 +5300 +19530 +5312 +4151 +13860 +19004 +1266 +10777 +5150 +4816 +5172 +252 +702 +5823 +5863 +11111 +11508 +13339 +3729 +10030 +9843 +2624 +17380 +8679 +3709 +8051 +9589 +3365 +3280 +18010 +15057 +230 +12690 +8969 +11484 +19218 +13348 +17118 +2100 +14262 +10255 +594 +19852 +7830 +19461 +5345 +5502 +6363 +2256 +13852 +17012 +2494 +19958 +4995 +15409 +6789 +3974 +10122 +1511 +7786 +1248 +8127 +13084 +2500 +18524 +19013 +5258 +15961 +3004 +5337 +523 +19968 +6275 +8662 +19779 +10343 +1977 +15801 +12321 +16162 +7341 +9281 +14796 +9310 +19512 +19405 +19782 +14145 +13741 +19751 +150 +11990 +11568 +9552 +1184 +11165 +2873 +17804 +18743 +14394 +16041 +15676 +8228 +15025 +8795 +720 +5934 +349 +16651 +11366 +19021 +424 +14289 +2211 +8771 +5009 +18528 +17697 +19558 +3653 +11674 +14124 +6360 +18317 +13045 +16862 +17105 +14501 +10910 +18357 +13959 +17312 +11254 +6699 +1163 +14463 +17133 +1379 +3424 +19235 +16506 +13474 +2966 +750 +6251 +17774 +6665 +11172 +16449 +18591 +11218 +15077 +11681 +7650 +1566 +16690 +971 +5472 +19354 +623 +5621 +14700 +9018 +15048 +10975 +12805 +2052 +19103 +16048 +12467 +6146 +11128 +13798 +16299 +5624 +11716 +15569 +12681 +19003 +14089 +5005 +17643 +18054 +8507 +624 +9764 +430 +5139 +12955 +17633 +18795 +1088 +19129 +843 +19819 +14469 +15666 +4582 +9920 +13681 +10066 +8065 +9648 +2202 +96 +7484 +1267 +690 +16179 +3182 +5125 +10811 +5724 +15850 +2222 +9046 +6423 +13733 +15684 +15392 +17495 +12863 +498 +10164 +18966 +12418 +6222 +9976 +288 +16999 +15426 +13872 +14451 +9547 +10515 +7344 +854 +7902 +2768 +15997 +19294 +17308 +12595 +18887 +2689 +6909 +12091 +12844 +3926 +6542 +15474 +18227 +201 +5498 +17574 +4109 +9572 +4954 +6101 +7505 +16538 +16793 +17192 +17517 +351 +5133 +2050 +1282 +18186 +11233 +14645 +19337 +6158 +3351 +914 +5701 +3136 +5414 +1420 +11999 +11489 +7586 +442 +7805 +3809 +3138 +8291 +13592 +16618 +16338 +2127 +3972 +5562 +9028 +14912 +5653 +4166 +3743 +7872 +599 +6954 +10668 +2845 +19100 +16617 +154 +11374 +6779 +14547 +13415 +17097 +18994 +8608 +14527 +2486 +1154 +17689 +7869 +15346 +2994 +13851 +7350 +5431 +19834 +13357 +5989 +17009 +10533 +2351 +14369 +2285 +15677 +15380 +1910 +13132 +5639 +4735 +3009 +12004 +14330 +12835 +7845 +19647 +4479 +7473 +9394 +11296 +5279 +7863 +10458 +3954 +8373 +5988 +1220 +461 +2378 +14855 +5317 +10956 +1916 +4531 +4698 +15306 +11094 +12270 +13226 +8061 +14314 +9906 +8102 +11665 +3427 +962 +4411 +9899 +4033 +2513 +18110 +12481 +6737 +3661 +13591 +1504 +5545 +17726 +11208 +2362 +17204 +12377 +13014 +15207 +4708 +2123 +18387 +10018 +10180 +3360 +13529 +972 +12769 +6091 +11867 +2515 +17622 +7430 +18493 +19051 +18031 +221 +9493 +18830 +106 +12860 +6790 +1446 +1412 +3044 +4097 +9986 +3684 +9728 +656 +8351 +10291 +19880 +9679 +3629 +15564 +7370 +13046 +5887 +11953 +12133 +5966 +3892 +19597 +7750 +707 +5503 +1714 +9459 +12006 +1362 +12357 +14220 +5717 +18583 +1321 +15403 +19546 +3620 +890 +17508 +4586 +9158 +15310 +923 +17278 +6502 +9406 +1790 +1540 +16916 +14091 +13304 +4494 +10326 +2230 +56 +13219 +17320 +14202 +15503 +3861 +78 +10493 +4999 +1081 +17061 +11221 +10599 +12757 +8972 +1653 +15651 +9007 +3796 +5485 +14396 +13088 +4283 +5605 +13224 +12841 +4878 +9395 +10511 +11666 +4720 +5286 +9628 +18394 +3878 +16354 +4939 +8961 +14625 +19956 +1130 +227 +3913 +5466 +3840 +8302 +5604 +10791 +985 +7015 +210 +8742 +3455 +14814 +15884 +6188 +10504 +18972 +5749 +9758 +7990 +1103 +17428 +17696 +7485 +18153 +16044 +19587 +7929 +14339 +14636 +8643 +19199 +16971 +1486 +11765 +10794 +11709 +7038 +9211 +19761 +10590 +18417 +15283 +3850 +11144 +11415 +17838 +8117 +19322 +9644 +18451 +12386 +7331 +17077 +1241 +13242 +11125 +18440 +6857 +14276 +948 +13000 +13679 +18108 +14331 +14504 +9428 +15808 +15276 +1889 +15889 +7092 +4491 +9983 +5339 +14909 +2277 +18390 +8313 +19687 +2677 +15712 +4442 +8932 +17952 +9513 +9342 +16675 +14298 +11027 +2809 +12147 +16592 +1466 +9322 +17985 +1361 +15154 +6515 +15035 +3816 +7558 +11435 +19043 +6607 +13969 +4050 +19652 +17195 +16509 +18631 +13091 +121 +4472 +6568 +63 +12768 +10195 +16562 +13875 +19791 +1584 +7606 +4387 +15320 +11616 +711 +3083 +9262 +10710 +431 +15922 +17958 +4246 +513 +4971 +15273 +4172 +179 +3030 +3110 +17836 +3411 +7670 +12478 +2343 +11805 +5184 +18447 +4592 +12949 +16164 +8917 +14532 +10124 +14711 +18508 +5684 +2267 +15883 +5745 +19792 +5436 +3051 +8936 +8933 +11459 +14963 +7533 +14460 +16624 +19992 +10765 +10332 +14389 +13601 +10117 +19429 +7209 +5347 +2379 +4765 +12342 +17445 +13760 +6061 +13939 +3600 +4168 +17313 +11921 +7373 +2793 +4088 +10740 +9908 +1873 +13799 +11871 +6386 +17673 +18756 +6457 +9522 +14324 +7836 +19278 +19573 +14453 +710 +12582 +6241 +16363 +8485 +16552 +14762 +15601 +5534 +14245 +13043 +16439 +17385 +10985 +13933 +12906 +3602 +727 +18115 +3648 +17155 +10543 +8471 +13825 +10986 +5137 +9331 +15772 +18383 +11781 +10937 +10474 +3173 +13848 +10555 +15690 +7083 +3299 +12421 +5542 +9260 +14095 +9350 +14971 +6232 +7345 +77 +17822 +374 +912 +14680 +12700 +10161 +16652 +18815 +16724 +4907 +5555 +12470 +7205 +578 +7780 +851 +1865 +16522 +9008 +7293 +9463 +3192 +18536 +5030 +4959 +18579 +4587 +6512 +1024 +10980 +10898 +13636 +3095 +18779 +1733 +12699 +16512 +16480 +1179 +10921 +14594 +14388 +18748 +8898 +7486 +55 +17440 +9243 +1668 +8540 +13289 +9436 +17648 +346 +12871 +16749 +164 +12634 +19758 +8937 +15947 +1861 +17849 +4989 +12096 +16698 +11948 +8399 +12457 +15480 +12110 +11619 +16899 +11292 +9074 +5373 +14465 +15305 +10982 +6406 +112 +1284 +9356 +13890 +8959 +15879 +580 +7714 +5506 +3190 +17881 +14872 +4306 +18338 +9994 +17684 +17898 +87 +472 +6161 +4723 +3447 +18148 +3158 +18542 +4948 +3738 +5297 +19281 +14788 +15127 +13776 +13611 +10153 +7853 +12450 +10108 +14227 +19753 +6458 +1003 +19973 +15140 +6123 +16735 +6928 +4327 +10420 +16963 +940 +19622 +17196 +1925 +10566 +9358 +12819 +2030 +9681 +10077 +4334 +6690 +14318 +16043 +426 +1151 +8349 +4942 +5598 +2853 +19639 +11215 +4920 +13434 +9791 +2586 +5757 +5697 +14377 +16353 +18217 +8190 +10013 +18738 +19502 +18120 +9467 +9864 +16532 +11069 +8612 +1937 +1372 +15299 +16473 +5612 +12577 +15073 +716 +13517 +16768 +18633 +14902 +15125 +1093 +2678 +7539 +5928 +6696 +8923 +10954 +2918 +12820 +2851 +16694 +7692 +16254 +19650 +2386 +4220 +4701 +4004 +17718 +2989 +6924 +477 +5937 +9535 +4961 +16542 +12774 +1691 +10135 +14188 +17597 +15935 +14882 +4357 +13662 +18206 +8885 +5504 +7217 +5111 +5964 +11358 +3451 +8604 +8519 +15887 +12545 +982 +16497 +11323 +11385 +18188 +19645 +10984 +15886 +2551 +6679 +12606 +10824 +3956 +11180 +7574 +4028 +16212 +3914 +7677 +14076 +13885 +11514 +1767 +10012 +12423 +9778 +2821 +2811 +7956 +769 +14842 +19029 +13439 +18214 +2223 +3792 +10346 +9860 +517 +15205 +19674 +15361 +16625 +14219 +16820 +19101 +4351 +640 +18765 +11121 +4350 +16482 +6707 +6810 +14542 +2791 +8006 +12823 +689 +2929 +19232 +8752 +1342 +7597 +19672 +16838 +830 +14492 +16941 +14633 +3724 +989 +4184 +1453 +8029 +3327 +3677 +16206 +2457 +17398 +8335 +10118 +266 +5533 +4680 +11379 +17478 +9527 +19617 +1678 +10329 +2741 +18138 +2456 +16540 +12104 +11443 +1298 +2118 +17615 +5995 +6862 +3488 +15526 +11304 +4840 +8637 +15790 +775 +1137 +8983 +18397 +9798 +722 +5346 +15242 +12505 +15983 +19275 +15580 +13633 +13717 +11944 +12196 +10522 +13473 +2644 +10497 +17461 +17569 +6378 +8597 +16327 +1211 +4162 +6847 +4340 +19951 +1599 +7135 +15964 +5626 +14662 +11134 +5105 +12657 +17545 +3080 +18801 +11012 +16064 +4083 +2642 +18572 +977 +12963 +1483 +9630 +11837 +71 +10721 +3758 +18526 +10761 +8113 +4746 +9448 +3092 +16557 +17825 +18446 +8252 +392 +18106 +2957 +14830 +13937 +13308 +3804 +755 +7778 +17863 +8619 +13266 +17882 +16879 +19924 +11389 +11255 +14986 +15788 +1779 +7856 +19252 +11341 +805 +3380 +9491 +5805 +10094 +15694 +17089 +3040 +11579 +3307 +15278 +10400 +6058 +9593 +13908 +17052 +7700 +4979 +11938 +16412 +18864 +4819 +12537 +4342 +8687 +12683 +12049 +13008 +15497 +992 +6943 +19289 +12947 +1456 +14316 +8409 +16042 +13950 +13145 +14558 +14961 +10435 +15751 +2927 +15037 +13411 +13988 +16928 +12420 +14982 +3404 +15797 +14812 +19720 +11288 +1366 +4130 +9898 +998 +14385 +2657 +12093 +18980 +12937 +15151 +224 +10603 +2698 +3295 +6210 +10846 +11517 +11958 +7695 +9031 +2278 +17046 +15210 +7702 +11565 +2208 +877 +14359 +14139 +14411 +9053 +12569 +5575 +14752 +6876 +10816 +9560 +3562 +3894 +11237 +12842 +3335 +17299 +5688 +16983 +12775 +8516 +18919 +5076 +6027 +11966 +18406 +13103 +18220 +7896 +14930 +1302 +8829 +8453 +314 +10673 +4087 +7300 +19948 +800 +10413 +8825 +11842 +17749 +8810 +4490 +17681 +836 +12925 +11007 +9020 +5874 +9433 +7709 +8667 +4530 +1106 +9895 +17803 +16113 +8158 +1187 +19264 +6286 +19216 +16332 +2287 +19260 +15395 +17989 +17056 +4257 +6020 +12588 +11996 +9770 +6531 +5827 +1785 +12064 +10807 +10294 +19059 +4504 +15596 +6438 +4825 +7552 +9057 +18677 +8279 +17911 +19664 +4538 +17226 +19027 +9047 +17028 +14974 +14105 +2303 +11840 +9568 +10111 +3082 +10249 +13152 +5532 +14213 +16516 +18014 +19653 +8949 +10972 +14520 +16575 +3656 +14027 +15027 +9694 +12890 +10080 +18771 +16314 +10556 +12661 +19972 +18649 +699 +13948 +7751 +3131 +6784 +2676 +275 +2888 +16062 +7163 +19120 +12723 +17616 +18763 +10595 +15530 +2995 +783 +13107 +3416 +8081 +8176 +17959 +1728 +10932 +19737 +12468 +15440 +10682 +10281 +10498 +11967 +9314 +9189 +11332 +17661 +18196 +14557 +786 +17267 +8999 +4686 +1022 +7812 +18280 +456 +4312 +10302 +2906 +8606 +19544 +14775 +730 +16858 +19191 +6550 +13128 +17408 +18023 +9971 +19711 +11312 +4487 +4085 +15128 +14255 +8297 +18539 +787 +331 +6999 +12733 +10978 +14555 +16517 +15097 +8241 +3234 +15937 +13922 +17459 +1870 +17768 +14584 +19259 +5116 +5727 +931 +3767 +8005 +2607 +11446 +16883 +13893 +17149 +6018 +13261 +19066 +4685 +13725 +10893 +8622 +19603 +3692 +7888 +10635 +14670 +14441 +16672 +10622 +10074 +11786 +2699 +19206 +8367 +18976 +3421 +10434 +4451 +7403 +3857 +1799 +13049 +18178 +8514 +10714 +6771 +3152 +18337 +10109 +1083 +8326 +19805 +5950 +19847 +4527 +5855 +11501 +19808 +6397 +15479 +19244 +4186 +2528 +12598 +7043 +17510 +15752 +11162 +6982 +6216 +6264 +13484 +2039 +19015 +2433 +2694 +1544 +8215 +10538 +16695 +118 +16077 +11429 +1496 +11057 +13818 +14005 +12615 +5221 +12602 +3674 +301 +14175 +13343 +600 +14983 +524 +820 +15281 +11539 +4133 +4198 +19963 +5947 +10165 +15234 +11491 +4135 +4983 +1876 +7528 +7304 +1562 +15212 +1043 +19677 +4850 +16854 +8897 +18780 +14903 +3881 +5463 +5893 +15405 +16276 +1209 +13525 +6806 +10732 +7767 +13288 +15528 +6372 +12709 +6129 +12409 +9811 +18287 +2895 +15777 +401 +3278 +8573 +1554 +6500 +15252 +13647 +11399 +2242 +12502 +12507 +3477 +17207 +10341 +15525 +4823 +9161 +6308 +13294 +10629 +16245 +3296 +3538 +2801 +10584 +9403 +12498 +8714 +5198 +1338 +19990 +3574 +7314 +16620 +7569 +3494 +1 +14065 +3145 +17457 +9284 +7180 +10892 +7111 +8180 +1821 +2014 +16794 +19005 +12917 +8470 +17343 +1518 +7529 +6587 +11100 +1760 +16159 +6964 +19509 +8222 +11116 +15229 +13559 +6183 +709 +2098 +15239 +6169 +19667 +17363 +13213 +3144 +10767 +11387 +5134 +2003 +12185 +19682 +9113 +4577 +19384 +8926 +537 +11326 +11783 +16912 +4178 +16291 +10344 +17866 +5561 +2746 +10562 +11578 +5294 +14800 +19925 +12412 +15835 +11577 +9481 +14515 +17350 +14493 +7078 +6986 +18146 +9531 +16411 +5954 +2511 +5791 +11662 +13463 +14537 +6179 +2025 +4119 +6428 +7919 +12301 +741 +17003 +11520 +2322 +18545 +2816 +5994 +6105 +16107 +1073 +14622 +13342 +18050 +10078 +17345 +17554 +1543 +18 +7660 +5438 +9943 +3594 +3703 +5395 +11370 +16581 +15418 +3400 +10412 +14131 +13197 +13665 +12372 +18872 +12839 +2460 +16996 +12585 +13902 +15263 +9686 +16563 +1457 +13276 +947 +3589 +1135 +18197 +11713 +12163 +2204 +2919 +9626 +16423 +15582 +15949 +4205 +17938 +17413 +19576 +4993 +5283 +14579 +5309 +14546 +15726 +2849 +2142 +11410 +13810 +9479 +3820 +866 +9978 +16784 +11671 +6837 +16320 +11812 +12184 +8153 +6873 +7419 +7101 +3344 +19846 +9263 +1478 +10669 +12194 +4501 +10924 +7264 +13394 +18353 +1956 +1277 +12092 +12460 +5232 +19324 +2119 +16437 +9390 +11362 +7475 +2597 +15271 +10529 +6995 +19228 +4045 +4951 +8139 +4466 +18481 +4561 +16783 +10099 +6860 +17063 +927 +2033 +12648 +17781 +2898 +1320 +9871 +6326 +17469 +10601 +14705 +9870 +19192 +7219 +8847 +2792 +15438 +6422 +12590 +11420 +2627 +6361 +18901 +19204 +16903 +15181 +11050 +18231 +2536 +1986 +9228 +19402 +2165 +6001 +7311 +4519 +11158 +16208 +13691 +1942 +7375 +6005 +19039 +12972 +16462 +12515 +9276 +6880 +11200 +9931 +5623 +2862 +9716 +50 +9944 +8391 +12853 +11878 +7254 +15717 +14919 +19413 +7322 +4016 +2527 +16907 +2524 +5135 +17276 +10647 +13279 +10015 +9776 +12604 +3113 +16471 +14999 +11702 +19608 +10787 +8154 +5807 +13462 +604 +8939 +6273 +8979 +12619 +10386 +13069 +15727 +11689 +4536 +16075 +13938 +13580 +15599 +18962 +10735 +11802 +10383 +1399 +4410 +3535 +17480 +14985 +2814 +3123 +2724 +6224 +18865 +8240 +17731 +19163 +11893 +3245 +12224 +10103 +2013 +3435 +5017 +19932 +18568 +2339 +13 +3243 +17153 +10233 +4851 +2574 +17494 +10453 +8242 +9960 +372 +3410 +11084 +5357 +9627 +3896 +12304 +15424 +4673 +3841 +3832 +17147 +8123 +13614 +19935 +18746 +8343 +2887 +11414 +2347 +7245 +589 +13837 +5735 +18374 +5222 +2040 +10839 +15184 +16493 +1697 +8528 +3958 +15919 +14802 +14478 +12316 +47 +3814 +15941 +17556 +5025 +6505 +2599 +4807 +16573 +9026 +4753 +13330 +17961 +6951 +4176 +18581 +6280 +482 +7481 +847 +5703 +6686 +12430 +9343 +15955 +14406 +1063 +5293 +1944 +10734 +14286 +17833 +9802 +6627 +4307 +11310 +7206 +4848 +13644 +17175 +11330 +4675 +8977 +15247 +14724 +535 +16143 +3270 +4548 +6938 +6677 +9005 +7020 +9088 +15010 +17070 +704 +119 +12456 +15356 +13788 +9116 +7416 +237 +15198 +59 +13761 +10205 +9325 +10372 +1454 +12134 +17095 +13936 +11049 +502 +4606 +6182 +3863 +15307 +4583 +11554 +15812 +14138 +3258 +6628 +2885 +16888 +641 +11710 +5554 +14854 +9242 +16270 +11171 +19856 +18552 +15074 +17754 +2806 +7711 +4314 +19993 +3468 +12255 +6787 +15711 +7231 +4434 +10023 +9285 +5031 +19987 +13934 +12706 +5672 +9465 +7875 +10303 +19459 +4489 +6807 +6819 +11240 +5921 +12952 +3370 +9733 +10462 +12708 +4175 +5746 +5906 +4485 +10783 +12094 +9690 +18895 +10977 +880 +15847 +8134 +7030 +2154 +18109 +13728 +5571 +11363 +13663 +4131 +8656 +10532 +14433 +17810 +15439 +17251 +4456 +14559 +17505 +1586 +8790 +12115 +3177 +6671 +670 +8561 +17786 +18107 +16930 +14761 +1432 +5153 +6021 +111 +14955 +4624 +16076 +14237 +9844 +1270 +17917 +17015 +15227 +19331 +18072 +7003 +1960 +10914 +6799 +15138 +15068 +13651 +19053 +8900 +16740 +12291 +2503 +5685 +6639 +8718 +7792 +8309 +12079 +45 +84 +13028 +19830 +17453 +10848 +18701 +3321 +2212 +8817 +19290 +19602 +3811 +13789 +144 +7167 +7729 +2717 +6197 +13454 +13704 +14757 +17324 +15910 +9469 +14907 +4555 +708 +1123 +7625 +13782 +13126 +17596 +13957 +9220 +6709 +8019 +11503 +2173 +4388 +16776 +7399 +18430 +6831 +4496 +642 +7723 +5530 +19442 +3778 +2952 +11253 +8700 +5520 +4446 +12978 +1215 +7685 +9872 +15951 +3934 +9149 +1390 +2471 +12160 +14399 +2434 +18415 +1218 +32 +7982 +6812 +4124 +16855 +18941 +2465 +2734 +16235 +6157 +11791 +2737 +4717 +6371 +13109 +7912 +11730 +3555 +16654 +6456 +15368 +7580 +8096 +10407 +14311 +10043 +14424 +369 +12222 +930 +11154 +10068 +4544 +5456 +19536 +4857 +17200 +8804 +13532 +8733 +422 +1506 +18343 +14719 +14153 +3048 +10267 +14506 +5029 +19942 +5898 +2382 +3445 +9501 +18362 +15809 +12221 +17889 +1979 +2217 +16234 +5817 +10847 +16634 +18902 +1743 +4247 +8945 +19623 +10002 +4438 +12048 +6623 +9521 +13864 +17763 +9245 +17975 +11607 +1909 +17851 +10919 +18575 +9464 +10642 +6247 +2061 +18940 +6649 +2106 +14928 +9727 +5719 +8118 +11365 +19383 +4681 +1252 +5370 +9341 +932 +19183 +2420 +11824 +15570 +3993 +15431 +12494 +6548 +16249 +9922 +8387 +17677 +1629 +11720 +3636 +16336 +14087 +8133 +3755 +960 +3946 +16751 +8827 +4885 +11718 +17113 +2463 +17058 +2197 +11153 +17542 +13968 +1692 +9510 +1598 +17488 +19609 +3440 +173 +1849 +6383 +13880 +5226 +8374 +7870 +4086 +12667 +7468 +6736 +10201 +2125 +2589 +6608 +5270 +7683 +3717 +14243 +10644 +11638 +1189 +8767 +19540 +9812 +3980 +13301 +13319 +16386 +14660 +6190 +19137 +18090 +6845 +9849 +7178 +9816 +308 +9653 +11880 +4949 +1903 +7656 +2818 +15129 +13426 +14721 +12555 +7133 +9304 +1140 +5193 +17641 +19302 +2755 +9232 +2290 +12070 +14373 +18000 +6573 +1429 +18199 +12099 +9266 +19799 +19842 +14630 +15890 +5400 +1168 +1334 +678 +6820 +19767 +9867 +3627 +15482 +4219 +18098 +11759 +6569 +1522 +5971 +14975 +19122 +3102 +10832 +14152 +15486 +11430 +14481 +10335 +3613 +19350 +4041 +3053 +8884 +8173 +14611 +5180 +9204 +3476 +18133 +7054 +15400 +17218 +2182 +5252 +13778 +10306 +8472 +17474 +13635 +15264 +8964 +19173 +13839 +5596 +12642 +2008 +6478 +4278 +10186 +6869 +984 +4803 +3481 +18944 +14282 +13615 +15811 +19363 +11136 +7189 +15192 +1759 +3303 +7237 +13518 +11633 +14817 +14946 +16346 +5785 +4870 +8768 +4729 +14672 +13143 +12444 +7398 +14648 +16298 +8079 +14285 +7397 +14078 +13859 +4581 +14442 +16714 +9176 +8370 +188 +12186 +11185 +2192 +556 +6540 +17373 +7988 +7629 +8772 +15852 +1841 +7050 +3644 +1975 +16088 +10223 +3437 +5406 +398 +9409 +8221 +11022 +4599 +17923 +19212 +8525 +13827 +13178 +13063 +13568 +597 +8534 +12787 +9439 +12550 +17022 +7335 +4231 +14528 +17725 +4976 +14923 +15675 +12650 +747 +13745 +4043 +10501 +10969 +4941 +4153 +2195 +12187 +11124 +447 +15401 +18301 +10561 +6296 +1363 +3414 +12037 +4513 +9714 +4258 +4202 +1567 +9251 +3195 +2028 +13727 +10188 +17434 +5451 +393 +10472 +18208 +5378 +3288 +15950 +3389 +16984 +10052 +7082 +1509 +16150 +11193 +3482 +18241 +15206 +12694 +8589 +19453 +14117 +8083 +18296 +15671 +903 +16727 +14083 +19159 +10439 +493 +5493 +17086 +15611 +1575 +16574 +11052 +2315 +19831 +1676 +14627 +19279 +9199 +7674 +5230 +15390 +15977 +19765 +11356 +12465 +18800 +5462 +2300 +8322 +4146 +16519 +16998 +4393 +7958 +3184 +13159 +2890 +1996 +14181 +637 +13125 +13038 +3623 +3067 +11766 +12208 +5955 +12813 +16951 +8522 +3119 +15340 +19563 +19939 +4996 +19335 +1037 +18714 +17876 +18389 +13231 +15917 +7782 +14898 +18670 +11630 +17753 +8603 +3584 +10272 +18520 +19060 +15779 +6553 +2105 +3022 +6160 +11143 +15697 +13675 +220 +13874 +6529 +13783 +8706 +1262 +10317 +12012 +12435 +18774 +19300 +1419 +11201 +16096 +12869 +4769 +14310 +1364 +16991 +6117 +2631 +6040 +3103 +6942 +11453 +7901 +6972 +14364 +12145 +417 +9965 +12878 +2626 +17576 +19338 +15036 +13574 +12971 +12247 +13085 +2336 +19684 +5237 +5187 +11919 +562 +13007 +16523 +12939 +5250 +5588 +16600 +10405 +9663 +12558 +19723 +16362 +8135 +19763 +9391 +2427 +7476 +2029 +18293 +15895 +2385 +1489 +636 +981 +1156 +13282 +15115 +9598 +1213 +16544 +1296 +15737 +2784 +5932 +11104 +13888 +2530 +9706 +11081 +3524 +8956 +13166 +18557 +6065 +163 +6033 +516 +18459 +16866 +11518 +5428 +478 +11492 +10280 +7532 +7002 +14327 +3220 +14828 +7036 +16393 +13768 +2930 +1117 +6726 +2318 +13211 +4689 +3342 +10203 +12398 +16526 +8782 +4418 +8237 +3169 +19441 +10112 +18812 +13734 +4102 +17219 +5543 +1906 +14728 +19262 +15429 +7648 +7893 +17037 +4627 +16891 +11123 +5327 +6657 +2501 +1812 +13229 +3099 +3375 +5468 +3815 +4459 +860 +11151 +4140 +15652 +11792 +7192 +1686 +19348 +13925 +19637 +7427 +18068 +2010 +13216 +10431 +17501 +17030 +9687 +18018 +13730 +19817 +18679 +8473 +10827 +341 +4169 +1846 +9755 +14042 +1180 +13438 +7194 +15654 +9099 +15454 +12018 +19197 +1755 +4150 +15169 +10857 +6352 +7006 +10408 +13355 +19450 +15119 +7004 +257 +10915 +6684 +8840 +18930 +4641 +10877 +229 +7166 +9896 +5905 +7545 +344 +15920 +9152 +246 +9058 +19520 +4300 +4792 +19691 +3504 +817 +8737 +12905 +2415 +6907 +8860 +5027 +8024 +3888 +16163 +11647 +14006 +12023 +4877 +7644 +1563 +11462 +18219 +12019 +1989 +8497 +6763 +3996 +181 +11278 +11703 +554 +6004 +16785 +5024 +17144 +11675 +2857 +2485 +13659 +17741 +11130 +12704 +19747 +6166 +778 +6670 +19756 +8796 +19227 +385 +10731 +11961 +4047 +15990 +19669 +12521 +16944 +16474 +13432 +10829 +6960 +19887 +2559 +18752 +8361 +13833 +17824 +13186 +8008 +10746 +9302 +12298 +19491 +17194 +94 +2726 +9562 +17936 +14229 +17211 +1264 +2388 +7536 +9309 +1090 +4704 +2987 +16669 +1069 +5974 +666 +4110 +12960 +7011 +3430 +3658 +7957 +13867 +2305 +6814 +9642 +18184 +6066 +10263 +6331 +6583 +11401 +18710 +7761 +9120 +18732 +7057 +15086 +13461 +9607 +620 +357 +10244 +16750 +12518 +14047 +13311 +1637 +8493 +9688 +3472 +12299 +5998 +13924 +6504 +7669 +1921 +9307 +3126 +6080 +18350 +17884 +19096 +19377 +1765 +3605 +16444 +6057 +19150 +7336 +745 +12446 +18967 +17715 +954 +6207 +12627 +3154 +11384 +14172 +15881 +10026 +2786 +18142 +18848 +14487 +19946 +14489 +12259 +1326 +10541 +1739 +7035 +13278 +10706 +1226 +352 +7017 +19211 +1966 +5756 +19780 +18829 +34 +11245 +2462 +4457 +14180 +2692 +14910 +13246 +6710 +10638 +7946 +7282 +8727 +1368 +3519 +5185 +6794 +8042 +5645 +5808 +4835 +17102 +3921 +11209 +12136 +10628 +3576 +4998 +7242 +18183 +4817 +1796 +6850 +16380 +8288 +16177 +12374 +2774 +12953 +19135 +17368 +12202 +2671 +12030 +3502 +13272 +18318 +2947 +9429 +17981 +13291 +16647 +9353 +13506 +1345 +1606 +17865 +15331 +3223 +9213 +2329 +6693 +15916 +2748 +1597 +12141 +14140 +10183 +17864 +15681 +10661 +17526 +3179 +2682 +9129 +17455 +9142 +7890 +908 +4428 +1257 +6910 +4718 +19099 +15214 +168 +15250 +19414 +6957 +7013 +3617 +15080 +2637 +19908 +7213 +2778 +8424 +6742 +13447 +1388 +8942 +6449 +13283 +6520 +14973 +1632 +9919 +3172 +10166 +15706 +1954 +12442 +6048 +265 +884 +19234 +2575 +19868 +18708 +1191 +3020 +14077 +15453 +1936 +2761 +13577 +17654 +9508 +15423 +12865 +3808 +2055 +13652 +5522 +8046 +17315 +11752 +2304 +4058 +7626 +9317 +10968 +6605 +18609 +12029 +9514 +12933 +6311 +6106 +4719 +10381 +6532 +12583 +14195 +11685 +4255 +15557 +6704 +15840 +15208 +6497 +8632 +3696 +7840 +12973 +18856 +9969 +19613 +18563 +6743 +13422 +6575 +12637 +3962 +14827 +1112 +19268 +5240 +5901 +13009 +5481 +15268 +13265 +16451 +18319 +1837 +8355 +8598 +4207 +983 +12961 +13919 +14456 +1836 +1239 +3682 +2158 +15971 +12416 +17843 +12431 +5091 +2773 +11993 +10113 +10676 +17265 +115 +11344 +13027 +6813 +17885 +15398 +5191 +16829 +7964 +13565 +2469 +15284 +15334 +15636 +16577 +17943 +13522 +13595 +15555 +2598 +17872 +10530 +17790 +5792 +4328 +19037 +4015 +14635 +1783 +3928 +17277 +10138 +10764 +21 +7171 +5579 +2956 +15658 +4832 +9159 +12366 +2086 +1677 +1346 +13813 +14918 +19250 +3229 +5583 +15552 +17391 +19445 +19064 +19222 +8963 +10540 +14799 +9 +11515 +12578 +13606 +8198 +18794 +4379 +3132 +19960 +18979 +631 +5846 +12072 +15204 +5022 +7193 +12746 +8665 +4090 +8310 +4594 +11466 +15411 +13457 +5418 +16502 +10360 +13018 +1912 +2683 +19853 +2078 +3221 +17284 +6288 +14554 +7948 +16763 +3317 +3633 +3568 +6613 +16070 +17081 +15908 +12464 +8082 +13060 +2555 +3439 +8477 +12271 +5038 +19860 +4829 +11706 +7998 +8384 +1595 +1805 +1640 +474 +14984 +3614 +10814 +14329 +419 +4940 +10881 +9506 +13006 +10918 +8530 +17562 +5725 +17235 +1167 +17756 +462 +11525 +8492 +2843 +6761 +11361 +6935 +11226 +8856 +16721 +17026 +57 +5769 +14925 +9033 +10129 +2837 +8439 +1616 +6434 +11611 +14205 +6075 +11770 +5291 +2326 +7364 +586 +12935 +14640 +9955 +335 +18730 +1813 +11972 +4690 +672 +5793 +13220 +5178 +13492 +19396 +15145 +8657 +11858 +19994 +19162 +1033 +7147 +2330 +19757 +572 +643 +384 +9925 +1125 +16239 +1468 +14128 +11184 +17656 +19701 +15014 +2804 +19155 +18053 +16216 +4912 +17608 +12940 +9707 +16399 +9962 +1229 +2002 +9092 +4211 +14353 +15441 +18468 +9370 +16688 +987 +9588 +8286 +7260 +40 +2980 +12044 +3622 +16853 +6776 +1292 +11037 +14858 +11338 +3271 +6792 +5869 +17176 +4935 +9850 +3133 +530 +5284 +11481 +15789 +14037 +1792 +10600 +16448 +16402 +7271 +10259 +18759 +8559 +16204 +16052 +3246 +12904 +15531 +10970 +4450 +483 +12566 +15607 +588 +15992 +5443 +1447 +12752 +2411 +166 +19825 +16716 +6513 +18826 +12041 +10452 +16902 +829 +4188 +4493 +3208 +17099 +18648 +13847 +8111 +18607 +13743 +18881 +3827 +6130 +4139 +4965 +6177 +10051 +12253 +13041 +9234 +18908 +8614 +1776 +14967 +18358 +3366 +11589 +413 +18726 +18555 +17591 +2060 +8879 +3791 +18548 +2665 +7895 +15282 +19893 +2338 +14473 +11013 +207 +13584 +19911 +18401 +16144 +13453 +5700 +10181 +13519 +4557 +16640 +10933 +8755 +6884 +10786 +8921 +8348 +8360 +3935 +7072 +11688 +18657 +16435 +16612 +9620 +15485 +12302 +1287 +4750 +17816 +6462 +19313 +13429 +10803 +19658 +1811 +2333 +2788 +12509 +2391 +13374 +756 +10119 +526 +11502 +4846 +16478 +6897 +18228 +14718 +9334 +1484 +16094 +14722 +17019 +15695 +18441 +11231 +12369 +6240 +9002 +3631 +615 +7153 +4790 +10958 +17839 +10995 +9926 +6795 +12125 +1982 +4400 +9387 +1529 +5381 +18595 +17504 +2169 +4636 +2246 +18988 +17023 +11444 +16361 +13146 +17374 +14877 +18345 +300 +18462 +12824 +564 +134 +17523 +1134 +2452 +1038 +18878 +5092 +18926 +451 +11879 +15764 +13985 +5625 +8596 +12546 +12755 +10421 +3777 +10152 +11441 +19910 +12031 +16841 +7655 +10693 +5392 +18506 +12563 +18995 +15619 +11364 +14263 +13044 +12997 +13346 +13160 +11672 +17259 +2736 +319 +9151 +10928 +1832 +7854 +15517 +3459 +2878 +17775 +19969 +6304 +18289 +37 +7244 +13589 +3371 +8941 +13784 +7754 +5591 +89 +18899 +16011 +1249 +7622 +8592 +16098 +7570 +12850 +7579 +13637 +6854 +11655 +14479 +3180 +18981 +19456 +4523 +10036 +504 +8811 +12882 +14072 +4101 +602 +1818 +8398 +2965 +4771 +14349 +619 +3381 +9045 +17723 +11003 +551 +11987 +6641 +10576 +5382 +4609 +2232 +7400 +14606 +17782 +1882 +11952 +12883 +1013 +1045 +2239 +4588 +12565 +9769 +15195 +15723 +19314 +14132 +8295 +942 +5104 +16908 +10156 +4667 +13361 +13593 +17199 +17751 +15670 +16848 +4233 +7844 +13082 +18753 +16815 +5325 +9544 +18949 +15867 +18450 +1523 +13507 +3925 +9375 +8022 +16922 +19002 +18483 +6997 +9114 +869 +6463 +18983 +1099 +7611 +15944 +19025 +11257 +19088 +16057 +4747 +3210 +12362 +8623 +553 +7619 +8730 +13142 +12287 +17628 +5674 +2143 +2667 +19194 +14067 +14994 +5916 +6983 +11142 +18968 +9128 +234 +16008 +13918 +14901 +8770 +7161 +7706 +8783 +4742 +7058 +16184 +14926 +1098 +2953 +13416 +9591 +13168 +6290 +2187 +13489 +12669 +3316 +1603 +14778 +11423 +17530 +11102 +13092 +10690 +11992 +15793 +7483 +3486 +14589 +17624 +3801 +13150 +6451 +9171 +17302 +4744 +12516 +7190 +17853 +17486 +15132 +575 +16293 +10909 +19815 +7396 +16384 +18258 +4260 +9017 +1057 +418 +15588 +18699 +7408 +1669 +9855 +17928 +4332 +19007 +7179 +7097 +10636 +8254 +15571 +17880 +17858 +2467 +4824 +8630 +5507 +7748 +13207 +7708 +9601 +13215 +9756 +16026 +7734 +12007 +3985 +3975 +24 +8499 +19781 +16997 +19931 +7498 +18003 +898 +3774 +5267 +19904 +1141 +3236 +3997 +340 +8277 +10219 +19621 +9143 +17010 +11669 +12691 +8169 +9079 +14583 +14459 +12061 +11205 +14789 +3757 +4913 +4123 +1307 +687 +8143 +5783 +6087 +3116 +6893 +18700 +1126 +18939 +14849 +19168 +13694 +3354 +4732 +7220 +9715 +2729 +9476 +7883 +3283 +18448 +7298 +19525 +6913 +10908 +17048 +9894 +3151 +1736 +12383 +4842 +18693 +11914 +7090 +7865 +5521 +4669 +12482 +19952 +8529 +15462 +2518 +327 +10523 +1055 +13857 +19428 +11179 +2896 +15066 +11934 +935 +4289 +11899 +568 +1028 +3057 +1623 +12628 +9328 +9278 +19555 +8131 +10592 +16847 +11519 +4304 +18582 +5299 +2381 +2830 +3465 +14862 +2112 +3199 +15457 +9221 +15445 +6534 +10946 +1244 +4061 +14787 +19829 +10695 +19455 +5951 +3522 +14155 +18282 +4454 +13337 +16782 +9130 +8003 +1143 +6838 +6755 +16851 +6626 +15090 +5895 +18185 +9123 +19638 +11475 +1588 +5718 +8144 +12244 +7806 +9909 +13328 +12618 +16186 +14803 +3571 +1175 +6242 +13141 +4981 +16953 +15739 +15888 +15825 +8584 +12073 +12181 +6668 +13546 +17247 +7490 +5641 +18695 +17270 +19130 +15593 +9991 +8480 +19426 +17306 +3868 +2172 +4648 +16842 +7443 +12297 +16167 +16153 +8206 +17653 +16223 +18306 +8011 +13258 +8749 +11413 +17355 +18821 +2392 +7736 +1065 +3762 +11127 +13274 +14848 +7261 +11626 +14264 +10701 +9139 +6803 +7406 +16136 +15216 +18770 +8147 +10579 +5432 +1354 +10479 +8928 +13710 +2474 +11118 +13383 +15609 +5990 +19489 +15815 +8567 +16068 +2522 +3666 +11847 +16138 +7991 +12520 +15287 +18644 +4712 +9022 +14031 +1952 +10045 +2917 +13650 +2881 +19104 +3556 +5163 +6530 +16196 +3566 +9300 +5245 +2996 +18038 +2647 +3943 +13251 +13680 +17217 +1077 +6341 +15865 +1918 +437 +4834 +2572 +7862 +5711 +8408 +8924 +3418 +19594 +13314 +16731 +18513 +3075 +19643 +11476 +18781 +11249 +16940 +13425 +8467 +10608 +5912 +4692 +6496 +17159 +18817 +9774 +17707 +10887 +2646 +3101 +15565 +13814 +7910 +10234 +7672 +18593 +16125 +3872 +4688 +13502 +15285 +10258 +15956 +16674 +17513 +16734 +18127 +283 +16584 +3550 +2235 +19977 +4930 +2725 +2122 +13871 +5420 +13472 +5896 +17210 +9381 +2334 +15279 +7181 +3162 +10175 +2835 +6150 +13023 +1663 +5737 +5310 +2815 +7940 +12773 +16395 +11629 +15496 +2439 +17094 +5132 +3999 +17160 +10801 +3114 +4724 +1838 +4275 +19750 +10670 +7456 +12380 +15744 +14920 +4978 +5790 +9529 +8616 +12128 +4683 +6244 +1403 +3531 +360 +13158 +11178 +1997 +14972 +3992 +1034 +10672 +5935 +5578 +19888 +6243 +6653 +2371 +9222 +18521 +2413 +16275 +785 +3406 +16603 +9376 +4375 +12899 +17609 +8678 +13468 +16027 +14774 +14184 +3171 +2946 +15157 +6421 +18315 +18057 +13759 +14435 +17583 +11371 +4966 +15100 +1712 +8162 +13435 +7357 +11300 +17969 +16426 +13370 +6748 +10065 +6604 +5001 +9209 +3874 +14068 +5079 +14734 +434 +6630 +6747 +1394 +1085 +4425 +3478 +2053 +1367 +1025 +1560 +7981 +7309 +993 +15315 +9454 +7358 +11342 +7099 +7703 +12000 +4024 +14266 +9311 +2993 +15389 +19584 +18740 +8699 +11727 +6541 +11947 +3186 +7348 +13898 +693 +3189 +3074 +2425 +18658 +3065 +2567 +12010 +16450 +13757 +13022 +14769 +14444 +15466 +17832 +8016 +7075 +9185 +4847 +16771 +10268 +9578 +6919 +19624 +13031 +13380 +5124 +13554 +15078 +19046 +470 +3930 +655 +4019 +8440 +19527 +194 +9411 +9840 +7478 +6682 +4887 +4617 +17055 +759 +16034 +14466 +684 +16571 +2455 +16807 +6394 +5985 +15965 +10079 +2847 +13081 +7466 +15162 +17571 +13721 +6698 +11339 +14030 +14447 +10426 +13351 +8454 +18388 +4572 +13310 +13512 +6676 +2730 +18079 +15177 +895 +14003 +16172 +3572 +14307 +7807 +8842 +3323 +6491 +8751 +18445 +7376 +1592 +7774 +18987 +9532 +12636 +8777 +7540 +4054 +17548 +16481 +19598 +19357 +6078 +9000 +11767 +190 +2819 +17894 +3474 +4462 +13444 +8448 +15973 +10837 +13252 +12071 +15238 +5285 +7605 +7234 +7421 +8906 +11564 +1479 +14953 +19400 +5465 +15855 +2019 +16192 +10889 +8776 +15519 +6817 +345 +16653 +13688 +2403 +14018 +16537 +19797 +19997 +4634 +14400 +8655 +9483 +18683 +13841 +9504 +16741 +5014 +2577 +4225 +9013 +14064 +16518 +10623 +387 +17050 +6189 +999 +12600 +13726 +5888 +6007 +11479 +16718 +12795 +19897 +16182 +18458 +13772 +7673 +19890 +7047 +5618 +5959 +7185 +15330 +2138 +8591 +2605 +7691 +18431 +9186 +17634 +15342 +11687 +919 +8615 +10055 +2690 +11161 +2548 +10916 +8538 +5012 +1559 +5083 +8487 +2702 +18625 +9752 +6730 +11701 +10378 +7582 +5144 +16250 +17020 +18819 +17867 +2077 +10760 +6501 +16111 +5174 +1236 +8417 +15243 +15428 +4185 +17971 +11150 +2270 +4076 +10418 +9616 +14304 +9739 +14354 +11173 +9077 +2553 +17544 +16796 +7414 +5750 +16219 +11777 +3621 +12054 +6991 +4516 +3866 +13047 +17679 +2416 +13399 +337 +14655 +18382 +16054 +18064 +17422 +17215 +17498 +1750 +7126 +3127 +18565 +6762 +18960 +10966 +6662 +6082 +3070 +15563 +12371 +7905 +4700 +11434 +3014 +18514 +12815 +17114 +14966 +3384 +2723 +12173 +291 +1462 +3643 +17993 +1139 +7562 +8067 +8759 +8205 +14015 +3287 +1738 +10139 +1963 +15196 +7513 +5494 +15932 +17383 +6751 +7542 +5886 +15038 +3784 +2253 +7851 +2779 +12151 +7157 +19881 +11373 +17650 +174 +4010 +5259 +18803 +11562 +7188 +11876 +17734 +6949 +10287 +14157 +18143 +8517 +1622 +16646 +6088 +12780 +5103 +3603 +13777 +11604 +16943 +2542 +4967 +1369 +6433 +11825 +12008 +6494 +7531 +15518 +11926 +1104 +18507 +2332 +2257 +4511 +1230 +7978 +958 +10757 +4564 +15232 +5841 +6680 +10704 +17564 +15669 +5712 +2926 +3964 +3324 +15679 +15296 +5331 +3663 +1717 +17708 +5943 +11395 +16378 +5660 +4341 +1166 +17091 +1582 +6016 +216 +18784 +4639 +17240 +18603 +3597 +6350 +17348 +12607 +10364 +3058 +10709 +7744 +2871 +6905 +4195 +17230 +6343 +10021 +16126 +15597 +17983 +5405 +16319 +885 +15877 +14568 +3527 +7602 +13623 +1531 +14019 +12641 +157 +4128 +2733 +12364 +18822 +1283 +15255 +8284 +11428 +4062 +15606 +7132 +16755 +2863 +16252 +17191 +10100 +10459 +13758 +13200 +3710 +7159 +13800 +15040 +5271 +6711 +13654 +9837 +5173 +19220 +10368 +11398 +6330 +4006 +12476 +6586 +5797 +4242 +15703 +1665 +8427 +14178 +5244 +1968 +5818 +12107 +15505 +13285 +11529 +8822 +6603 +16260 +19339 +7627 +5654 +14824 +4389 +17580 +12135 +3680 +889 +10104 +15702 +13504 +1214 +7198 +19579 +15374 +12365 +12999 +11772 +14776 +9617 +6293 +9577 +13967 +18943 +16729 +7577 +14436 +19298 +6622 +10665 +8431 +19395 +19984 +10662 +14632 +16580 +16368 +12239 +6318 +13228 +8861 +7813 +6366 +1645 +13066 +2491 +11844 +15839 +12306 +14755 +19548 +18460 +2393 +5422 +364 +17185 +7575 +16636 +4921 +11833 +3376 +3818 +3752 +5026 +14107 +18861 +19157 +9505 +271 +17577 +2479 +9984 +2376 +15784 +14631 +6083 +957 +3039 +15020 +18999 +1508 +6772 +10931 +17855 +6992 +19287 +12666 +14170 +1740 +12923 +6858 +2309 +9440 +18070 +2886 +10054 +15402 +4626 +4196 +9897 +5702 +13410 +8233 +12649 +590 +19261 +238 +1938 +12592 +12402 +11487 +1732 +3452 +1752 +6939 +9524 +16769 +9060 +1911 +5708 +2742 +697 +13692 +17972 +10130 +12765 +6963 +1276 +7169 +9722 +3860 +9525 +6254 +5355 +8312 +12798 +8280 +1371 +18400 +15762 +1407 +4048 +9087 +15999 +2369 +6100 +7596 +1131 +19386 +3638 +1032 +12675 +18835 +812 +6274 +17032 +386 +2167 +3678 +10792 +13541 +10961 +7152 +18589 +11442 +7770 +2065 +9704 +5282 +6167 +16058 +5089 +18413 +6249 +8785 +1896 +171 +5035 +12241 +1190 +17145 +7144 +17135 +8142 +9241 +4830 +18909 +2636 +14302 +8987 +18964 +17752 +12531 +19686 +16553 +8709 +14063 +12538 +7511 +10082 +2968 +1893 +12781 +13845 +2048 +10552 +6937 +9259 +15372 +4901 +10380 +240 +19041 +17426 +9757 +244 +13914 +2372 +3036 +6899 +4460 +14836 +11513 +98 +2310 +12522 +7953 +531 +19399 +19557 +6452 +18372 +2159 +15351 +4288 +2639 +8950 +7120 +3454 +14580 +19091 +13984 +8855 +8837 +5926 +13956 +9366 +15757 +12812 +10755 +14921 +5929 +14512 +15133 +7920 +6593 +5687 +8889 +15860 +2928 +19630 +6427 +4721 +16681 +11324 +19844 +17817 +10370 +1708 +16101 +347 +10038 +8229 +315 +16837 +17819 +15710 +15957 +1594 +4849 +988 +14540 +14061 +466 +10409 +1590 +13376 +14016 +5551 +2128 +7123 +5960 +2971 +2894 +2673 +113 +18841 +19657 +943 +17769 +10736 +12748 +3823 +10505 +1441 +6012 +5093 +5732 +8689 +4642 +7535 +11738 +12190 +2754 +15430 +390 +6266 +12967 +3068 +18040 +14457 +19114 +17612 +1231 +14044 +17221 +6801 +6131 +763 +9072 +2323 +18702 +11260 +16132 +4616 +3505 +14452 +10756 +191 +8073 +479 +13987 +14278 +10273 +9516 +13779 +11450 +10853 +6560 +10506 +19086 +18039 +9299 +11353 +5320 +16870 +14867 +167 +4794 +17329 +14958 +10076 +17110 +6788 +18190 +16720 +8224 +795 +4646 +6493 +1161 +5263 +10666 +748 +10573 +9966 +6233 +17645 +6417 +16039 +10192 +17280 +6430 +18198 +8044 +4755 +7504 +2203 +6848 +16849 +7324 +7591 +5536 +2021 +18495 +17862 +3744 +11217 +7378 +9632 +10720 +11790 +7906 +8422 +16003 +9140 +11526 +7747 +1148 +17188 +15170 +14893 +2288 +1242 +1044 +15730 +2828 +17845 +15822 +8368 +18914 +3357 +16524 +3520 +7310 +8211 +9024 +6767 +9319 +7926 +7467 +8259 +2377 +2483 +15244 +4914 +17406 +8216 +17683 +19085 +1387 +6646 +14486 +10510 +18046 +11070 +13360 +16754 +15081 +9737 +3911 +4777 +10278 +15289 +14511 +17201 +7858 +18664 +12469 +18097 +6663 +10134 +3399 +3912 +10869 +3442 +17538 +17850 +17631 +2782 +326 +16022 +18134 +9553 +16702 +7882 +7788 +7816 +1877 +1951 +1047 +5321 +11269 +1555 +11555 +10739 +8981 +16342 +13822 +979 +17702 +9247 +921 +18937 +16591 +1535 +13921 +4600 +12206 +12042 +19793 +10602 +19418 +7187 +9416 +4614 +16415 +14096 +13803 +226 +16737 +19230 +13198 +18116 +10277 +14249 +17439 +19986 +13236 +17136 +12900 +5341 +7985 +2679 +5627 +9312 +10199 +16861 +2942 +1852 +10466 +7838 +11922 +8420 +15176 +2986 +17555 +5978 +7507 +4112 +1992 +1988 +6446 +10154 +6176 +16089 +9104 +11211 +18823 +18351 +8555 +11458 +10327 +15240 +3768 +1397 +16753 +1435 +19820 +2920 +17298 +14659 +9014 +16372 +7697 +17405 +11495 +14499 +7238 +2422 +3904 +13281 +17674 +10891 +2231 +16960 +7274 +12581 +14323 +16416 +15765 +724 +17605 +2601 +16006 +4423 +17675 +16772 +15137 +12001 +12449 +14088 +2348 +19239 +19610 +12057 +18615 +11643 +5915 +19006 +9876 +17123 +18082 +17785 +4026 +17663 +17042 +17964 +11624 +5976 +15444 +10059 +15667 +15786 +12511 +11528 +7769 +17282 +12557 +10398 +18996 +2584 +6022 +16775 +14244 +5590 +7363 +875 +6815 +15076 +14959 +557 +2437 +9090 +2020 +19675 +405 +18129 +12453 +18373 +8859 +16864 +17115 +14085 +17182 +11106 +12069 +12909 +7080 +11712 +16142 +217 +7297 +11393 +18678 +14908 +8633 +8695 +10844 +6283 +527 +17049 +8815 +18650 +18884 +13586 +15995 +19789 +4881 +7954 +19487 +12038 +2817 +19863 +9280 +8054 +19464 +13003 +3267 +8071 +16069 +10342 +6306 +11396 +6477 +8864 +5704 +2710 +12199 +19054 +8150 +18916 +598 +1798 +10862 +3564 +2493 +11425 +19355 +43 +2000 +6381 +7100 +11343 +863 +13642 +17638 +268 +13767 +4498 +5229 +3469 +11367 +14847 +4946 +11731 +13604 +11640 +402 +15347 +19050 +10879 +2674 +13005 +15778 +10391 +11884 +3160 +7852 +14628 +18986 +1357 +10049 +15644 +12059 +17044 +10057 +11641 +5467 +19892 +11377 +9988 +4316 +12799 +18538 +1474 +13114 +14950 +5039 +16025 +9250 +10657 +7118 +4222 +19918 +14667 +15052 +16492 +5938 +900 +7900 +7710 +5822 +18875 +8196 +18882 +2880 +1152 +12659 +5037 +10089 +10221 +4065 +13978 +926 +16585 +18992 +12586 +2191 +9141 +528 +19659 +7864 +12705 +19187 +17454 +5459 +794 +2685 +3822 +14642 +12897 +12235 +18761 +7972 +18561 +15638 +3558 +3281 +11239 +12014 +81 +14644 +6095 +7307 +19241 +11026 +19273 +4402 +9027 +17908 +13087 +9145 +5881 +13560 +12142 +15568 +7465 +5444 +16213 +19698 +2972 +6944 +17487 +19328 +2036 +9863 +13626 +18917 +11146 +9070 +19393 +7971 +11890 +5695 +635 +10250 +295 +15996 +17229 +9818 +17134 +15200 +3936 +1634 +12688 +3340 +7352 +18614 +16175 +12743 +12930 +17127 +15322 +11875 +7639 +13703 +17626 +17535 +19031 +6930 +14697 +1825 +16979 +3705 +18768 +8208 +16300 +4000 +11325 +16131 +15139 +12729 +13528 +332 +18635 +16676 +10965 +15507 +7223 +639 +13513 +856 +8973 +9929 +9743 +19521 +19061 +11018 +3185 +11891 +16895 +2198 +19403 +3970 +10820 +9093 +4773 +15782 +10687 +12854 +5884 +11595 +15030 +2711 +5526 +12793 +6369 +6740 +14772 +18938 +3802 +19193 +6108 +4678 +907 +3461 +16201 +5374 +11025 +539 +11369 +2622 +12082 +11433 +9720 +11321 +12455 +2353 +3790 +6902 +449 +7641 +5161 +19164 +14956 +8586 +2591 +16662 +19906 +2380 +12652 +9882 +14462 +779 +8482 +2800 +16037 +15024 +11349 +12814 +9198 +4722 +2446 +1556 +12784 +8673 +16469 +415 +7447 +11195 +10674 +1046 +12767 +18816 +14060 +6056 +4578 +10502 +12510 +2802 +10838 +3637 +1690 +3156 +8481 +1662 +7583 +6152 +1862 +9289 +7438 +7598 +5326 +13903 +16604 +17475 +9371 +3069 +4759 +19035 +16499 +11168 +2194 +10852 +8599 +8552 +4281 +18637 +18012 +375 +15003 +5165 +5574 +16872 +12433 +10637 +5673 +653 +10539 +10521 +8807 +12451 +19728 +10072 +5563 +13953 +8904 +15337 +16685 +19478 +7023 +9759 +616 +15171 +2579 +2117 +10525 +3776 +8050 +12265 +2659 +10955 +4871 +8826 +5215 +8556 +7636 +5354 +9040 +16536 +2478 +4610 +7428 +1310 +14804 +15627 +5729 +13802 +4533 +7916 +17655 +9218 +9878 +8901 +14109 +15473 +18424 +14516 +17854 +922 +9809 +777 +15617 +1328 +6959 +16227 +11101 +16684 +1801 +5166 +3937 +8975 +19858 +5359 +4985 +11760 +5918 +16780 +10560 +15954 +7804 +3155 +6148 +12639 +6777 +120 +15463 +11542 +3639 +1391 +19390 +8181 +7014 +10253 +3250 +15768 +1224 +3635 +17809 +1016 +18230 +8803 +11834 +14932 +9345 +8653 +9225 +17041 +213 +1541 +13991 +16261 +12413 +19838 +8918 +809 +13016 +5260 +5055 +10934 +10983 +19317 +3855 +18375 +13094 +17275 +3345 +582 +8155 +15791 +2259 +9749 +3612 +13735 +1619 +18673 +10429 +7055 +3676 +3787 +11307 +8421 +1385 +5837 +11412 +8363 +9380 +5042 +8496 +10106 +6121 +4167 +18201 +13748 +12307 +109 +6346 +13306 +10480 +12215 +17004 +14747 +13535 +16264 +1172 +14846 +5114 +19359 +11684 +10032 +8835 +12411 +4147 +19345 +2363 +10354 +9495 +9197 +15041 +1943 +19543 +13605 +2240 +7746 +4005 +15943 +2549 +1421 +13992 +15064 +12013 +5570 +18797 +16442 +13196 +6916 +9235 +137 +7200 +5527 +6624 +16927 +13187 +17080 +12312 +5464 +8951 +6445 +9958 +12928 +1917 +14677 +13004 +2429 +4643 +15183 +12254 +19921 +6191 +11831 +13834 +16233 +19879 +4656 +14240 +5820 +13572 +5939 +13868 +4798 +9814 +10744 +4417 +15897 +11917 +12437 +3197 +14604 +646 +17778 +6926 +7007 +18540 +4762 +11729 +6453 +16615 +15672 +8745 +8164 +1053 +10646 +18766 +7477 +14917 +19578 +4236 +6537 +10230 +18156 +10047 +16229 +497 +18798 +9853 +2838 +1259 +8518 +9677 +10997 +18749 +19570 +13151 +13964 +9652 +14081 +2596 +8611 +10570 +10854 +5200 +2944 +951 +12686 +16778 +2324 +17719 +8357 +489 +18662 +18101 +9890 +8036 +1729 +443 +1289 +3196 +12816 +12016 +559 +13561 +5773 +2018 +12529 +19632 +2992 +7877 +8451 +2213 +10654 +1527 +11087 +18384 +9763 +6650 +11809 +1609 +4467 +10204 +1488 +15738 +14701 +15455 +2079 +3456 +11029 +15574 +7377 +9987 +2948 +5175 +14345 +7366 +5213 +7608 +19866 +10247 +3374 +11014 +5775 +8122 +2564 +11404 +10184 +12264 +7048 +18945 +19704 +8325 +18305 +13815 +10132 +6797 +10936 +1702 +10350 +3315 +1039 +19438 +7141 +499 +4415 +17139 +12106 +15573 +13098 +9349 +2740 +6011 +17074 +14704 +13188 +12325 +5268 +128 +3117 +17471 +7712 +19416 +6805 +5840 +18755 +1747 +12198 +2297 +6940 +7928 +3557 +16605 +9100 +3284 +929 +2991 +5171 +9105 +6612 +8201 +17441 +10795 +7211 +9938 +4213 +9610 +15608 +6395 +14637 +18330 +11331 +13876 +9594 +2243 +13102 +12866 +13537 +8418 +15277 +16949 +11213 +19616 +8513 +15023 +14446 +6948 +8498 +10269 +9670 +14458 +6901 +11357 +19957 +10534 +16712 +18348 +1383 +5275 +16251 +333 +2412 +14927 +7276 +1858 +4349 +3275 +648 +5278 +9666 +6384 +7228 +17464 +4764 +16289 +2921 +19010 +7815 +3736 +11913 +19719 +16259 +8890 +18025 +1886 +18121 +4821 +7384 +5276 +11473 +6379 +12858 +13407 +2561 +19861 +7675 +15363 +16265 +10215 +15396 +17059 +14382 +9101 +13831 +17112 +1118 +19648 +12896 +15509 +1621 +18998 +17789 +14808 +2200 +17934 +9253 +16813 +8397 +9695 +17162 +12788 +15149 +4886 +9657 +17540 +5440 +6871 +446 +7966 +3719 +18020 +8594 +19427 +4106 +1655 +11974 +17266 +18550 +5482 +4944 +17868 +8149 +13808 +1459 +677 +4943 +3369 +13600 +12436 +4788 +16414 +13820 +12613 +17079 +9685 +1791 +19696 +11232 +8813 +6279 +13149 +4003 +1331 +4751 +13718 +17326 +10718 +4988 +8720 +13244 +12894 +4469 +19523 +4164 +574 +3262 +3121 +15197 +12488 +14422 +3394 +15618 +12647 +5404 +8148 +18660 +6270 +6769 +12942 +16017 +4344 +14650 +2435 +4241 +18303 +9680 +15286 +13701 +16994 +11131 +7553 +2568 +9968 +6309 +11711 +8818 +14293 +13980 +1157 +6691 +17351 +801 +17557 +1268 +10067 +18697 +15903 +11645 +7544 +7831 +10738 +18610 +19076 +10609 +7455 +12919 +412 +4731 +15175 +9352 +4903 +3856 +12088 +7429 +16924 +5427 +15464 +10176 +11120 +19541 +1626 +17762 +17228 +3076 +17289 +7974 +15069 +17047 +11409 +7799 +2707 +7794 +15153 +16613 +3139 +14179 +12875 +17143 +9761 +12067 +6544 +4249 +6828 +11108 +17891 +13329 +6349 +1526 +14859 +3882 +14876 +13375 +1311 +512 +19351 +1517 +10942 +19710 +12323 +3382 +16148 +9603 +15167 +7444 +1961 +17744 +18475 +6961 +11835 +12011 +13212 +19177 +15521 +16905 +8607 +13854 +905 +4023 +19920 +1113 +665 +888 +16595 +10604 +10613 +6549 +4809 +12713 +6988 +7495 +4539 +1238 +9551 +14471 +13112 +18767 +17729 +10282 +14074 +9360 +12943 +116 +10781 +18042 +8436 +14543 +5234 +5963 +14405 +19971 +1195 +1694 +19717 +16271 +9803 +2931 +3670 +5140 +12918 +9154 +15316 +4203 +5170 +13130 +12889 +18632 +12169 +5109 +4927 +13882 +1892 +9167 +5772 +11017 +7604 +8626 +254 +17357 +519 +11928 +610 +19953 +8246 +1815 +18859 +19000 +11059 +13451 +12572 +7127 +5540 +2675 +12233 +11916 +19785 +5499 +18869 +13170 +14185 +7098 +17890 +7125 +3362 +4796 +12872 +13299 +18437 +1515 +16028 +924 +15969 +13406 +6335 +19083 +19680 +8405 +6974 +14150 +18472 +175 +17704 +6026 +10359 +125 +9949 +323 +16066 +8426 +9705 +17840 +11618 +16405 +17831 +5495 +7925 +5446 +13136 +16856 +501 +5798 +5046 +18804 +2436 +15071 +1789 +3595 +9365 +573 +14080 +6014 +1401 +10190 +7089 +1300 +15946 +5040 +3788 +7285 +10547 +8250 +13270 +19474 +5254 +18304 +3166 +8157 +14246 +1905 +18877 +1826 +14292 +2116 +14290 +14303 +19315 +3500 +1440 +17336 +4125 +5832 +13656 +7446 +1578 +18255 +13466 +2394 +10357 +10322 +16599 +484 +14238 +16871 +576 +19975 +13303 +3587 +11715 +3858 +19108 +13193 +6484 +16913 +13746 +3689 +7785 +11274 +3226 +867 +3521 +9875 +7548 +11683 +2938 +2808 +10639 +5637 +12182 +14113 +8393 +19388 +14595 +2186 +13448 +16746 +4338 +14350 +6545 +15318 +11072 +12553 +189 +5154 +19178 +14990 +7640 +16551 +114 +1718 +17988 +16207 +18663 +8784 +19889 +15104 +14822 +10831 +1235 +3859 +6637 +8056 +15442 +3255 +12219 +1651 +13557 +2588 +674 +4427 +1533 +790 +4406 +19996 +6344 +6631 +2247 +5894 +5984 +9042 +18361 +16707 +1207 +10035 +2604 +9086 +9038 +2093 +9184 +12278 +14256 +475 +18083 +376 +16078 +17109 +16982 +6483 +14280 +18418 +18093 +16337 +12183 +11093 +11437 +16118 +14962 +13543 +11745 +4113 +5099 +13806 +19115 +15015 +16645 +10805 +16214 +19047 +12324 +16211 +18511 +16564 +14116 +7889 +1392 +16836 +764 +5843 +6642 +10019 +652 +9520 +12063 +12796 +5614 +3854 +13715 +1967 +6234 +19116 +17506 +5372 +1757 +9779 +1602 +17388 +1426 +10242 +15945 +15458 +16226 +3591 +1600 +8249 +19284 +11497 +19087 +19865 +1171 +13498 +9789 +11682 +10949 +1897 +16649 +6389 +17446 +4783 +6917 +7019 +16224 +11046 +6236 +15851 +1965 +6323 +15120 +13774 +1913 +668 +6193 +11617 +18867 +1839 +1507 +19433 +18326 +5922 +15468 +14764 +19434 +15915 +17531 +2201 +9970 +8108 +6647 +3871 +15044 +198 +13071 +13836 +2234 +3248 +7027 +18630 +6894 +8504 +14268 +13542 +12579 +4510 +14641 +11348 +10437 +411 +9682 +13850 +12554 +2215 +4432 +19754 +6577 +6692 +5885 +13896 +13530 +8995 +8984 +6713 +1638 +13610 +12847 +8177 +17639 +5061 +7959 +16560 +5002 +5882 +10017 +7224 +2383 +395 +10415 +14874 +10808 +19361 +1853 +5595 +2833 +2630 +4200 +9069 +953 +1704 +2651 +5490 +15447 +6469 +18785 +15647 +8618 +18836 +8001 +6054 +12794 +8130 +11472 +14941 +12022 +5015 +14748 +19796 +10336 +16788 +9751 +8247 +16711 +5665 +480 +9231 +19950 +9124 +5067 +12568 +8320 +16221 +12513 +4007 +11214 +7084 +18858 +15861 +5809 +3408 +12988 +18103 +2228 +2414 +11002 +11463 +487 +2084 +873 +1499 +6827 +8902 +19125 +1699 +6154 +29 +14125 +9699 +16418 +3683 +6248 +7809 +2144 +9001 +8404 +8930 +6509 +10207 +2609 +14163 +15598 +5857 +731 +6295 +16312 +13307 +8062 +18874 +6720 +14190 +9457 +18814 +18570 +4360 +18309 +6782 +9548 +14992 +17017 +16082 +2331 +6703 +13941 +16808 +6596 +18048 +12268 +2472 +11044 +19068 +18961 +3910 +14000 +9847 +2769 +4774 +15335 +2071 +7173 +13524 +16700 +1006 +4290 +9556 From 795d89f11df13d3efe5241e2666c72a888640299 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 11:08:40 +0200 Subject: [PATCH 089/399] ls: don't escape backslash in shell style quoting --- src/uu/ls/src/quoting_style.rs | 21 +++++++++++++++++++-- tests/by-util/test_ls.rs | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index ceb54466c..173831ac1 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -1,6 +1,6 @@ use std::char::from_digit; -const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! "; +const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};'\"<>?! "; pub(super) enum QuotingStyle { Shell { @@ -135,7 +135,6 @@ impl EscapedChar { '\x0B' => Backslash('v'), '\x0C' => Backslash('f'), '\r' => Backslash('r'), - '\\' => Backslash('\\'), '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)), '\'' => match quotes { Quotes::Single => Backslash('\''), @@ -627,4 +626,22 @@ mod tests { ], ); } + + #[test] + fn test_backslash() { + // Escaped in C-style, but not in Shell-style escaping + check_names( + "one\\two", + vec![ + ("one\\two", "literal"), + ("one\\two", "literal-show"), + ("one\\\\two", "escape"), + ("\"one\\\\two\"", "c"), + ("one\\two", "shell"), + ("\'one\\two\'", "shell-always"), + ("one\\two", "shell-escape"), + ("'one\\two'", "shell-escape-always"), + ], + ); + } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f0db7ca9c..75f50b640 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1049,6 +1049,7 @@ fn test_ls_quoting_style() { at.touch("one two"); at.touch("one"); + at.touch("one\\two"); // It seems that windows doesn't allow \n in filenames. #[cfg(unix)] @@ -1168,6 +1169,27 @@ fn test_ls_quoting_style() { .succeeds() .stdout_only(format!("{}\n", correct)); } + + for (arg, correct) in &[ + ("--quoting-style=literal", "one\\two"), + ("-N", "one\\two"), + ("--quoting-style=c", "\"one\\\\two\""), + ("-Q", "\"one\\\\two\""), + ("--quote-name", "\"one\\\\two\""), + ("--quoting-style=escape", "one\\\\two"), + ("-b", "one\\\\two"), + ("--quoting-style=shell-escape", "one\\two"), + ("--quoting-style=shell-escape-always", "'one\\two'"), + ("--quoting-style=shell", "one\\two"), + ("--quoting-style=shell-always", "'one\\two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\\two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } } #[test] From f84f23ddfefcef4e71c6ada5f7f35383ee0f613f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 11:22:10 +0200 Subject: [PATCH 090/399] tests/ls: add coverage for special shell character after escaped char --- src/uu/ls/src/quoting_style.rs | 21 +++++++++++++++++---- tests/by-util/test_ls.rs | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index 173831ac1..fd5cab57e 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -27,12 +27,10 @@ pub(super) enum Quotes { // This implementation is heavily inspired by the std::char::EscapeDefault implementation // in the Rust standard library. This custom implementation is needed because the // characters \a, \b, \e, \f & \v are not recognized by Rust. -#[derive(Clone, Debug)] struct EscapedChar { state: EscapeState, } -#[derive(Clone, Debug)] enum EscapeState { Done, Char(char), @@ -41,14 +39,12 @@ enum EscapeState { Octal(EscapeOctal), } -#[derive(Clone, Debug)] struct EscapeOctal { c: char, state: EscapeOctalState, idx: usize, } -#[derive(Clone, Debug)] enum EscapeOctalState { Done, Backslash, @@ -510,6 +506,23 @@ mod tests { ], ); + // A control character followed by a special shell character + check_names( + "one\n&two", + vec![ + ("one?&two", "literal"), + ("one\n&two", "literal-show"), + ("one\\n&two", "escape"), + ("\"one\\n&two\"", "c"), + ("'one?&two'", "shell"), + ("'one\n&two'", "shell-show"), + ("'one?&two'", "shell-always"), + ("'one\n&two'", "shell-always-show"), + ("'one'$'\\n''&two'", "shell-escape"), + ("'one'$'\\n''&two'", "shell-escape-always"), + ], + ); + // The first 16 control characters. NUL is also included, even though it is of // no importance for file names. check_names( diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 75f50b640..001a97d1a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1190,6 +1190,21 @@ fn test_ls_quoting_style() { .succeeds() .stdout_only(format!("{}\n", correct)); } + + // Tests for a character that forces quotation in shell-style escaping + // after a character in a dollar expression + at.touch("one\n&two"); + for (arg, correct) in &[ + ("--quoting-style=shell-escape", "'one'$'\\n''&two'"), + ("--quoting-style=shell-escape-always", "'one'$'\\n''&two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\n&two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } } #[test] From bee9156764b8a57845948c2c7adca498945b049f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 12:03:48 +0200 Subject: [PATCH 091/399] tests/ls: improve code cov --- tests/by-util/test_ls.rs | 149 +++++++++++++++++++++++++++++++++------ 1 file changed, 127 insertions(+), 22 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 001a97d1a..7546a606c 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -102,6 +102,12 @@ fn test_ls_width() { .succeeds() .stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); } + + scene + .ucmd() + .arg("-w=bad") + .fails() + .stderr_contains("invalid line width"); } #[test] @@ -435,6 +441,39 @@ fn test_ls_deref() { assert!(!re.is_match(result.stdout_str().trim())); } +#[test] +fn test_ls_sort_none() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test-3"); + at.touch("test-1"); + at.touch("test-2"); + + // Order is not specified so we just check that it doesn't + // give any errors. + scene.ucmd().arg("--sort=none").succeeds(); + scene.ucmd().arg("-U").succeeds(); +} + +#[test] +fn test_ls_sort_name() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("test-3"); + at.touch("test-1"); + at.touch("test-2"); + + let sep = if cfg!(unix) { "\n" } else { " " }; + + scene + .ucmd() + .arg("--sort=name") + .succeeds() + .stdout_is(["test-1", "test-2", "test-3\n"].join(sep)); +} + #[test] fn test_ls_order_size() { let scene = TestScenario::new(util_name!()); @@ -463,6 +502,18 @@ fn test_ls_order_size() { result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] result.stdout_only("test-1 test-2 test-3 test-4\n"); + + let result = scene.ucmd().arg("--sort=size").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + #[cfg(windows)] + result.stdout_only("test-4 test-3 test-2 test-1\n"); + + let result = scene.ucmd().arg("--sort=size").arg("-r").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); + #[cfg(windows)] + result.stdout_only("test-1 test-2 test-3 test-4\n"); } #[test] @@ -471,13 +522,16 @@ fn test_ls_long_ctime() { let at = &scene.fixtures; at.touch("test-long-ctime-1"); - let result = scene.ucmd().arg("-lc").succeeds(); - // Should show the time on Unix, but question marks on windows. - #[cfg(unix)] - result.stdout_contains(":"); - #[cfg(not(unix))] - result.stdout_contains("???"); + for arg in &["-c", "--time=ctime", "--time=status"] { + let result = scene.ucmd().arg("-l").arg(arg).succeeds(); + + // Should show the time on Unix, but question marks on windows. + #[cfg(unix)] + result.stdout_contains(":"); + #[cfg(not(unix))] + result.stdout_contains("???"); + } } #[test] @@ -518,32 +572,46 @@ fn test_ls_order_time() { #[cfg(windows)] result.stdout_only("test-4 test-3 test-2 test-1\n"); + let result = scene.ucmd().arg("--sort=time").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + #[cfg(windows)] + result.stdout_only("test-4 test-3 test-2 test-1\n"); + let result = scene.ucmd().arg("-tr").succeeds(); #[cfg(not(windows))] result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); #[cfg(windows)] result.stdout_only("test-1 test-2 test-3 test-4\n"); + let result = scene.ucmd().arg("--sort=time").arg("-r").succeeds(); + #[cfg(not(windows))] + result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); + #[cfg(windows)] + result.stdout_only("test-1 test-2 test-3 test-4\n"); + // 3 was accessed last in the read // So the order should be 2 3 4 1 - let result = scene.ucmd().arg("-tu").succeeds(); - let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); - let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); + for arg in &["-u", "--time=atime", "--time=access", "--time=use"] { + let result = scene.ucmd().arg("-t").arg(arg).succeeds(); + let file3_access = at.open("test-3").metadata().unwrap().accessed().unwrap(); + let file4_access = at.open("test-4").metadata().unwrap().accessed().unwrap(); - // It seems to be dependent on the platform whether the access time is actually set - if file3_access > file4_access { - if cfg!(not(windows)) { - result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); + // It seems to be dependent on the platform whether the access time is actually set + if file3_access > file4_access { + if cfg!(not(windows)) { + result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n"); + } else { + result.stdout_only("test-3 test-4 test-2 test-1\n"); + } } else { - result.stdout_only("test-3 test-4 test-2 test-1\n"); - } - } else { - // Access time does not seem to be set on Windows and some other - // systems so the order is 4 3 2 1 - if cfg!(not(windows)) { - result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); - } else { - result.stdout_only("test-4 test-3 test-2 test-1\n"); + // Access time does not seem to be set on Windows and some other + // systems so the order is 4 3 2 1 + if cfg!(not(windows)) { + result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); + } else { + result.stdout_only("test-4 test-3 test-2 test-1\n"); + } } } @@ -1351,3 +1419,40 @@ fn test_ls_ignore_hide() { .stderr_contains(&"Invalid pattern") .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); } + +#[test] +fn test_ls_ignore_backups() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("somefile"); + at.touch("somebackup~"); + at.touch(".somehiddenfile"); + at.touch(".somehiddenbackup~"); + + scene.ucmd().arg("-B").succeeds().stdout_is("somefile\n"); + scene + .ucmd() + .arg("--ignore-backups") + .succeeds() + .stdout_is("somefile\n"); + + scene + .ucmd() + .arg("-aB") + .succeeds() + .stdout_contains(".somehiddenfile") + .stdout_contains("somefile") + .stdout_does_not_contain("somebackup") + .stdout_does_not_contain(".somehiddenbackup~"); + + scene + .ucmd() + .arg("-a") + .arg("--ignore-backups") + .succeeds() + .stdout_contains(".somehiddenfile") + .stdout_contains("somefile") + .stdout_does_not_contain("somebackup") + .stdout_does_not_contain(".somehiddenbackup~"); +} From 387227087f1b755068cc92f36fe823d07cf9a269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81rni=20Dagur?= Date: Wed, 21 Apr 2021 10:21:31 +0000 Subject: [PATCH 092/399] cat: Put splice code in separate file, handle more failures (#2067) * cat: Refactor splice code, handle more failures * cat: Add tests for stdout redirected to files --- src/uu/cat/src/cat.rs | 85 +++--------------------------------- src/uu/cat/src/splice.rs | 91 +++++++++++++++++++++++++++++++++++++++ tests/by-util/test_cat.rs | 72 ++++++++++++++++++++++++++++++- tests/common/util.rs | 39 +++++++++++++---- 4 files changed, 199 insertions(+), 88 deletions(-) create mode 100644 src/uu/cat/src/splice.rs diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 7d56a7485..e507c5acd 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -22,6 +22,12 @@ use std::io::{self, Read, Write}; use thiserror::Error; use uucore::fs::is_stdin_interactive; +/// Linux splice support +#[cfg(any(target_os = "linux", target_os = "android"))] +mod splice; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::unix::io::{AsRawFd, RawFd}; + /// Unix domain socket support #[cfg(unix)] use std::net::Shutdown; @@ -30,14 +36,6 @@ use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use unix_socket::UnixStream; -/// Linux splice support -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::{splice, SpliceFFlags}; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::unistd::pipe; -#[cfg(any(target_os = "linux", target_os = "android"))] -use std::os::unix::io::{AsRawFd, RawFd}; - static NAME: &str = "cat"; static VERSION: &str = env!("CARGO_PKG_VERSION"); static SYNTAX: &str = "[OPTION]... [FILE]..."; @@ -395,7 +393,7 @@ fn write_fast(handle: &mut InputHandle) -> CatResult<()> { { // If we're on Linux or Android, try to use the splice() system call // for faster writing. If it works, we're done. - if !write_fast_using_splice(handle, stdout_lock.as_raw_fd())? { + if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? { return Ok(()); } } @@ -411,75 +409,6 @@ fn write_fast(handle: &mut InputHandle) -> CatResult<()> { Ok(()) } -/// This function is called from `write_fast()` on Linux and Android. The -/// function `splice()` is used to move data between two file descriptors -/// without copying between kernel- and userspace. This results in a large -/// speedup. -/// -/// The `bool` in the result value indicates if we need to fall back to normal -/// copying or not. False means we don't have to. -#[cfg(any(target_os = "linux", target_os = "android"))] -#[inline] -fn write_fast_using_splice(handle: &mut InputHandle, writer: RawFd) -> CatResult { - const BUF_SIZE: usize = 1024 * 16; - - let (pipe_rd, pipe_wr) = pipe()?; - - // We only fall back if splice fails on the first call. - match splice( - handle.file_descriptor, - None, - pipe_wr, - None, - BUF_SIZE, - SpliceFFlags::empty(), - ) { - Ok(n) => { - if n == 0 { - return Ok(false); - } - splice_exact(pipe_rd, writer, n)?; - } - Err(_) => { - return Ok(true); - } - } - - loop { - let n = splice( - handle.file_descriptor, - None, - pipe_wr, - None, - BUF_SIZE, - SpliceFFlags::empty(), - )?; - if n == 0 { - // We read 0 bytes from the input, - // which means we're done copying. - break; - } - splice_exact(pipe_rd, writer, n)?; - } - - Ok(false) -} - -/// Splice wrapper which handles short writes -#[cfg(any(target_os = "linux", target_os = "android"))] -#[inline] -fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { - let mut left = num_bytes; - loop { - let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?; - left -= written; - if left == 0 { - break; - } - } - Ok(()) -} - /// Outputs file contents to stdout in a line-by-line fashion, /// propagating any errors that might occur. fn write_lines( diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs new file mode 100644 index 000000000..ccc625467 --- /dev/null +++ b/src/uu/cat/src/splice.rs @@ -0,0 +1,91 @@ +use super::{CatResult, InputHandle}; + +use nix::fcntl::{splice, SpliceFFlags}; +use nix::unistd::{self, pipe}; +use std::io::Read; +use std::os::unix::io::RawFd; + +const BUF_SIZE: usize = 1024 * 16; + +/// This function is called from `write_fast()` on Linux and Android. The +/// function `splice()` is used to move data between two file descriptors +/// without copying between kernel- and userspace. This results in a large +/// speedup. +/// +/// The `bool` in the result value indicates if we need to fall back to normal +/// copying or not. False means we don't have to. +#[inline] +pub(super) fn write_fast_using_splice( + handle: &mut InputHandle, + write_fd: RawFd, +) -> CatResult { + let (pipe_rd, pipe_wr) = match pipe() { + Ok(r) => r, + Err(_) => { + // It is very rare that creating a pipe fails, but it can happen. + return Ok(true); + } + }; + + loop { + match splice( + handle.file_descriptor, + None, + pipe_wr, + None, + BUF_SIZE, + SpliceFFlags::empty(), + ) { + Ok(n) => { + if n == 0 { + return Ok(false); + } + if splice_exact(pipe_rd, write_fd, n).is_err() { + // If the first splice manages to copy to the intermediate + // pipe, but the second splice to stdout fails for some reason + // we can recover by copying the data that we have from the + // intermediate pipe to stdout using normal read/write. Then + // we tell the caller to fall back. + copy_exact(pipe_rd, write_fd, n)?; + return Ok(true); + } + } + Err(_) => { + return Ok(true); + } + } + } +} + +/// Splice wrapper which handles short writes. +#[inline] +fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { + let mut left = num_bytes; + loop { + let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?; + left -= written; + if left == 0 { + break; + } + } + Ok(()) +} + +/// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function +/// will panic. The way we use this function in `write_fast_using_splice` +/// above is safe because `splice` is set to write at most `BUF_SIZE` to the +/// pipe. +#[inline] +fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { + let mut left = num_bytes; + let mut buf = [0; BUF_SIZE]; + loop { + let read = unistd::read(read_fd, &mut buf[..left])?; + let written = unistd::write(write_fd, &mut buf[..read])?; + left -= written; + if left == 0 { + break; + } + } + Ok(()) +} diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 389269395..d7c3eec6b 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -1,4 +1,7 @@ use crate::common::util::*; +#[cfg(unix)] +use std::fs::OpenOptions; +#[cfg(unix)] use std::io::Read; #[test] @@ -54,7 +57,6 @@ fn test_no_options_big_input() { #[test] #[cfg(unix)] fn test_fifo_symlink() { - use std::fs::OpenOptions; use std::io::Write; use std::thread; @@ -85,6 +87,74 @@ fn test_fifo_symlink() { thread.join().unwrap(); } +#[test] +#[cfg(unix)] +fn test_piped_to_regular_file() { + use std::fs::read_to_string; + + for &append in &[true, false] { + let s = TestScenario::new(util_name!()); + let file_path = s.fixtures.plus("file.txt"); + + { + let file = OpenOptions::new() + .create_new(true) + .write(true) + .append(append) + .open(&file_path) + .unwrap(); + + s.ucmd() + .set_stdout(file) + .pipe_in_fixture("alpha.txt") + .succeeds(); + } + let contents = read_to_string(&file_path).unwrap(); + assert_eq!(contents, "abcde\nfghij\nklmno\npqrst\nuvwxyz\n"); + } +} + +#[test] +#[cfg(unix)] +fn test_piped_to_dev_null() { + for &append in &[true, false] { + let s = TestScenario::new(util_name!()); + { + let dev_null = OpenOptions::new() + .write(true) + .append(append) + .open("/dev/null") + .unwrap(); + + s.ucmd() + .set_stdout(dev_null) + .pipe_in_fixture("alpha.txt") + .succeeds(); + } + } +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_piped_to_dev_full() { + for &append in &[true, false] { + let s = TestScenario::new(util_name!()); + { + let dev_full = OpenOptions::new() + .write(true) + .append(append) + .open("/dev/full") + .unwrap(); + + s.ucmd() + .set_stdout(dev_full) + .pipe_in_fixture("alpha.txt") + .fails() + .stderr_contains(&"No space left on device".to_owned()); + } + } +} + #[test] fn test_directory() { let s = TestScenario::new(util_name!()); diff --git a/tests/common/util.rs b/tests/common/util.rs index 55e121737..95a30d47e 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -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 f34c992932d11864f1b7412458f549e9f2191898 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 12:45:21 +0200 Subject: [PATCH 093/399] ls: always quote backslash in shell style --- src/uu/ls/src/quoting_style.rs | 2 +- tests/by-util/test_ls.rs | 75 +++++++++++++++++----------------- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index fd5cab57e..c4c8200a4 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -1,6 +1,6 @@ use std::char::from_digit; -const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};'\"<>?! "; +const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! "; pub(super) enum QuotingStyle { Shell { diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d44074821..718e1db1c 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1118,12 +1118,13 @@ fn test_ls_quoting_style() { at.touch("one two"); at.touch("one"); - at.touch("one\\two"); // It seems that windows doesn't allow \n in filenames. + // And it also doesn't like \, of course. #[cfg(unix)] { at.touch("one\ntwo"); + at.touch("one\\two"); // Default is shell-escape scene .ucmd() @@ -1185,6 +1186,42 @@ fn test_ls_quoting_style() { .succeeds() .stdout_only(format!("{}\n", correct)); } + + for (arg, correct) in &[ + ("--quoting-style=literal", "one\\two"), + ("-N", "one\\two"), + ("--quoting-style=c", "\"one\\\\two\""), + ("-Q", "\"one\\\\two\""), + ("--quote-name", "\"one\\\\two\""), + ("--quoting-style=escape", "one\\\\two"), + ("-b", "one\\\\two"), + ("--quoting-style=shell-escape", "'one\\two'"), + ("--quoting-style=shell-escape-always", "'one\\two'"), + ("--quoting-style=shell", "'one\\two'"), + ("--quoting-style=shell-always", "'one\\two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\\two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } + + // Tests for a character that forces quotation in shell-style escaping + // after a character in a dollar expression + at.touch("one\n&two"); + for (arg, correct) in &[ + ("--quoting-style=shell-escape", "'one'$'\\n''&two'"), + ("--quoting-style=shell-escape-always", "'one'$'\\n''&two'"), + ] { + scene + .ucmd() + .arg(arg) + .arg("one\n&two") + .succeeds() + .stdout_only(format!("{}\n", correct)); + } } scene @@ -1238,42 +1275,6 @@ fn test_ls_quoting_style() { .succeeds() .stdout_only(format!("{}\n", correct)); } - - for (arg, correct) in &[ - ("--quoting-style=literal", "one\\two"), - ("-N", "one\\two"), - ("--quoting-style=c", "\"one\\\\two\""), - ("-Q", "\"one\\\\two\""), - ("--quote-name", "\"one\\\\two\""), - ("--quoting-style=escape", "one\\\\two"), - ("-b", "one\\\\two"), - ("--quoting-style=shell-escape", "one\\two"), - ("--quoting-style=shell-escape-always", "'one\\two'"), - ("--quoting-style=shell", "one\\two"), - ("--quoting-style=shell-always", "'one\\two'"), - ] { - scene - .ucmd() - .arg(arg) - .arg("one\\two") - .succeeds() - .stdout_only(format!("{}\n", correct)); - } - - // Tests for a character that forces quotation in shell-style escaping - // after a character in a dollar expression - at.touch("one\n&two"); - for (arg, correct) in &[ - ("--quoting-style=shell-escape", "'one'$'\\n''&two'"), - ("--quoting-style=shell-escape-always", "'one'$'\\n''&two'"), - ] { - scene - .ucmd() - .arg(arg) - .arg("one\n&two") - .succeeds() - .stdout_only(format!("{}\n", correct)); - } } #[test] From 29b5b6b27686cc87a4bdb538604c3821dcc4ea24 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 13:03:31 +0200 Subject: [PATCH 094/399] ls: fix unit tests to match last change --- src/uu/ls/src/quoting_style.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index c4c8200a4..49456fc22 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -650,9 +650,9 @@ mod tests { ("one\\two", "literal-show"), ("one\\\\two", "escape"), ("\"one\\\\two\"", "c"), - ("one\\two", "shell"), + ("'one\\two'", "shell"), ("\'one\\two\'", "shell-always"), - ("one\\two", "shell-escape"), + ("'one\\two'", "shell-escape"), ("'one\\two'", "shell-escape-always"), ], ); From fb2ae04b8f45e99b1e1217a08c40adfdd9673d78 Mon Sep 17 00:00:00 2001 From: jaggededgedjustice Date: Wed, 21 Apr 2021 13:22:05 +0100 Subject: [PATCH 095/399] Remove broken GNU test for printf (#2095) --- .github/workflows/GNU.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 35efccbe5..a68f0a083 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -80,6 +80,9 @@ jobs: -e '/tests\/misc\/help-version-getopt.sh/ D' \ Makefile + # printf doesn't limit the values used in its arg, so this produced ~2GB of output + sed -i '/INT_OFLOW/ D' tests/misc/printf.sh + # Use the system coreutils where the test fails due to error in a util that is not the one being tested sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh From 34a824af71705279e6f3a213668c495e0e6bd396 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 16:58:37 +0200 Subject: [PATCH 096/399] 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 097/399] 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 4a305b32c6e77af76e1af3a42b72eeeac4855d23 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 21 Apr 2021 17:49:40 +0200 Subject: [PATCH 098/399] sort: disallow certain flags with -d and -i GNU sort disallows these combinations, presumably because they are likely not what the user really wants. Ignoring characters would cause things to be put together that aren't together in the input. For example, -dn would cause "0.12" or "0,12" to be parsed as "12" which is highly unexpected and confusing. --- src/uu/sort/src/sort.rs | 34 +++++++++++++++++++++++++++++----- tests/by-util/test_sort.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 07b852921..7090a98ed 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -84,7 +84,7 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; -#[derive(Eq, Ord, PartialEq, PartialOrd, Clone)] +#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum SortMode { Numeric, HumanNumeric, @@ -153,7 +153,7 @@ struct KeySettings { impl From<&GlobalSettings> for KeySettings { fn from(settings: &GlobalSettings) -> Self { Self { - mode: settings.mode.clone(), + mode: settings.mode, ignore_blanks: settings.ignore_blanks, ignore_case: settings.ignore_case, ignore_non_printing: settings.ignore_non_printing, @@ -407,6 +407,28 @@ impl KeyPosition { crash!(1, "invalid option for key: `{}`", c) } } + // All numeric sorts and month sort conflict with dictionary_order and ignore_non_printing. + // Instad of reporting an error, let them overwrite each other. + + // FIXME: This should only override if the overridden flag is a global flag. + // If conflicting flags are attached to the key, GNU sort crashes and we should probably too. + match option { + 'h' | 'n' | 'g' | 'M' => { + settings.dictionary_order = false; + settings.ignore_non_printing = false; + } + 'd' | 'i' => { + settings.mode = match settings.mode { + SortMode::Numeric + | SortMode::HumanNumeric + | SortMode::GeneralNumeric + | SortMode::Month => SortMode::Default, + // Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing + m @ (SortMode::Default | SortMode::Version) => m, + } + } + _ => {} + } } // Strip away option characters from the original value so we can parse it later *value_with_options = &value_with_options[..options_start]; @@ -651,7 +673,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(OPT_DICTIONARY_ORDER) .short("d") .long(OPT_DICTIONARY_ORDER) - .help("consider only blanks and alphanumeric characters"), + .help("consider only blanks and alphanumeric characters") + .conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]), ) .arg( Arg::with_name(OPT_MERGE) @@ -679,9 +702,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) .arg( Arg::with_name(OPT_IGNORE_NONPRINTING) - .short("-i") + .short("i") .long(OPT_IGNORE_NONPRINTING) - .help("ignore nonprinting characters"), + .help("ignore nonprinting characters") + .conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]), ) .arg( Arg::with_name(OPT_IGNORE_BLANKS) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index a4a9a383c..4ee8826e3 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -581,3 +581,30 @@ fn test_check_silent() { .fails() .stdout_is(""); } + +#[test] +fn test_dictionary_and_nonprinting_conflicts() { + let conflicting_args = ["n", "h", "g", "M"]; + for restricted_arg in &["d", "i"] { + for conflicting_arg in &conflicting_args { + new_ucmd!() + .arg(&format!("-{}{}", restricted_arg, conflicting_arg)) + .fails(); + } + for conflicting_arg in &conflicting_args { + new_ucmd!() + .args(&[ + format!("-{}", restricted_arg).as_str(), + "-k", + &format!("1,1{}", conflicting_arg), + ]) + .succeeds(); + } + for conflicting_arg in &conflicting_args { + // FIXME: this should ideally fail. + new_ucmd!() + .args(&["-k", &format!("1{},1{}", restricted_arg, conflicting_arg)]) + .succeeds(); + } + } +} From b08f92cfa5cec53ff99a902ea3e4b5689b1bd922 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 21 Apr 2021 17:50:22 +0200 Subject: [PATCH 099/399] remove unneeded 'static --- tests/by-util/test_sort.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 4ee8826e3..0e6ac2e62 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -63,7 +63,7 @@ fn test_check_zero_terminated_success() { #[test] fn test_random_shuffle_len() { // check whether output is the same length as the input - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -75,7 +75,7 @@ fn test_random_shuffle_len() { #[test] fn test_random_shuffle_contains_all_lines() { // check whether lines of input are all in output - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -90,7 +90,7 @@ fn test_random_shuffle_two_runs_not_the_same() { // check to verify that two random shuffles are not equal; this has the // potential to fail in the very unlikely event that the random order is the same // as the starting order, or if both random sorts end up having the same order. - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -105,7 +105,7 @@ fn test_random_shuffle_contains_two_runs_not_the_same() { // check to verify that two random shuffles are not equal; this has the // potential to fail in the unlikely event that random order is the same // as the starting order, or if both random sorts end up having the same order. - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); From 8b906b954789acb42b0b1b2a43e8b679c93821b3 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 21 Apr 2021 18:00:01 +0200 Subject: [PATCH 100/399] remove feature use stabilized in 1.51 --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7090a98ed..7d9808c1b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -424,7 +424,7 @@ impl KeyPosition { | SortMode::GeneralNumeric | SortMode::Month => SortMode::Default, // Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing - m @ (SortMode::Default | SortMode::Version) => m, + m @ SortMode::Default | m @ SortMode::Version => m, } } _ => {} From ff3953837515257a32426e22d9f3bab75c984d5b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 21 Apr 2021 17:57:17 +0200 Subject: [PATCH 101/399] 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 102/399] 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 103/399] 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 104/399] 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 105/399] 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 106/399] 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 107/399] 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 108/399] 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 109/399] 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 110/399] 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 e241f3ad69c9895330c0555963b8be9d7f96edb8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 22 Apr 2021 22:45:24 +0200 Subject: [PATCH 111/399] ls: skip reading metadata --- src/uu/ls/src/ls.rs | 176 +++++++++++++++++++++++--------------------- 1 file changed, 93 insertions(+), 83 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index e0aa3ec4b..d744e2914 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -19,11 +19,11 @@ mod version_cmp; use clap::{App, Arg}; use globset::{self, Glob, GlobSet, GlobSetBuilder}; use number_prefix::NumberPrefix; +use once_cell::unsync::OnceCell; use quoting_style::{escape_name, QuotingStyle}; #[cfg(unix)] use std::collections::HashMap; -use std::fs; -use std::fs::{DirEntry, FileType, Metadata}; +use std::fs::{self, DirEntry, FileType, Metadata}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; #[cfg(any(unix, target_os = "redox"))] @@ -492,6 +492,10 @@ impl Config { } } + if files != Files::Normal { + ignore_patterns.add(Glob::new(".*").unwrap()); + } + let ignore_patterns = ignore_patterns.build().unwrap(); let dereference = if options.is_present(options::dereference::ALL) { @@ -1043,29 +1047,66 @@ pub fn uumain(args: impl uucore::Args) -> i32 { /// 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, + md: OnceCell>, + ft: OnceCell>, // Name of the file - will be empty for . or .. file_name: String, // PathBuf that all above data corresponds to p_buf: PathBuf, + must_dereference: bool, } 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(); + fn new( + p_buf: PathBuf, + file_type: Option>, + config: &Config, + command_line: bool, + ) -> Self { let name = p_buf .file_name() .map_or(String::new(), |s| s.to_string_lossy().into_owned()); + let must_dereference = match &config.dereference { + Dereference::All => true, + Dereference::Args => command_line, + Dereference::DirArgs => { + if command_line { + if let Ok(md) = p_buf.metadata() { + md.is_dir() + } else { + false + } + } else { + false + } + } + Dereference::None => false, + }; + let ft = match file_type { + Some(ft) => OnceCell::from(ft.ok()), + None => OnceCell::new(), + }; + Self { - md, - lossy_string, + md: OnceCell::new(), + ft, file_name: name, p_buf, + must_dereference, } } + + fn md(&self) -> Option<&Metadata> { + self.md + .get_or_init(|| get_metadata(&self.p_buf, self.must_dereference).ok()) + .as_ref() + } + + fn file_type(&self) -> Option<&FileType> { + self.ft + .get_or_init(|| self.md().map(|md| md.file_type())) + .as_ref() + } } fn list(locs: Vec, config: Config) -> i32 { @@ -1085,10 +1126,10 @@ fn list(locs: Vec, config: Config) -> i32 { continue; } - let path_data = PathData::new(p, &config, true); + let path_data = PathData::new(p, None, &config, true); - let show_dir_contents = if let Ok(md) = path_data.md.as_ref() { - !config.directory && md.is_dir() + let show_dir_contents = if let Some(ft) = path_data.file_type() { + !config.directory && ft.is_dir() } else { has_failed = true; false @@ -1106,7 +1147,7 @@ fn list(locs: Vec, config: Config) -> i32 { sort_entries(&mut dirs, &config); for dir in dirs { if number_of_locs > 1 { - println!("\n{}:", dir.lossy_string); + println!("\n{}:", dir.p_buf.display()); } enter_directory(&dir, &config); } @@ -1121,17 +1162,16 @@ fn sort_entries(entries: &mut Vec, config: &Config) { match config.sort { Sort::Time => entries.sort_by_key(|k| { Reverse( - k.md.as_ref() - .ok() + k.md() .and_then(|md| get_system_time(&md, config)) .unwrap_or(UNIX_EPOCH), ) }), Sort::Size => { - entries.sort_by_key(|k| Reverse(k.md.as_ref().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.file_name.to_lowercase()), + Sort::Name => entries.sort_by_key(|k| k.file_name.to_ascii_lowercase()), Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)), Sort::None => {} } @@ -1169,8 +1209,8 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { 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), + PathData::new(dir.p_buf.join("."), None, config, false), + PathData::new(dir.p_buf.join(".."), None, config, false), ] } else { vec![] @@ -1179,7 +1219,7 @@ fn enter_directory(dir: &PathData, config: &Config) { 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)) + .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), config, false)) .collect(); sort_entries(&mut temp, config); @@ -1192,31 +1232,15 @@ fn enter_directory(dir: &PathData, config: &Config) { 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)) + .filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) { - println!("\n{}:", e.lossy_string); + println!("\n{}:", e.p_buf.display()); enter_directory(&e, config); } } } -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, - }; +fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { if dereference { entry.metadata().or_else(|_| entry.symlink_metadata()) } else { @@ -1225,7 +1249,7 @@ fn get_metadata(entry: &Path, config: &Config, command_line: bool) -> std::io::R } fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { - if let Ok(md) = entry.md.as_ref() { + if let Some(md) = entry.md() { ( display_symlink_count(&md).len(), display_file_size(&md, config).len(), @@ -1251,17 +1275,9 @@ fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) { display_item_long(item, strip, max_links, max_size, config); } } else { - let names = items.iter().filter_map(|i| { - let md = i.md.as_ref(); - match md { - Err(e) => { - let filename = get_file_name(&i.p_buf, strip); - show_error!("'{}': {}", filename, e); - None - } - Ok(md) => Some(display_file_name(&i.p_buf, strip, &md, config)), - } - }); + let names = items + .iter() + .filter_map(|i| display_file_name(&i, strip, config)); match (&config.format, config.width) { (Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom), @@ -1325,13 +1341,13 @@ fn display_item_long( max_size: usize, config: &Config, ) { - let md = match &item.md { - Err(e) => { + let md = match item.md() { + None => { let filename = get_file_name(&item.p_buf, strip); - show_error!("{}: {}", filename, e); + show_error!("could not show file: {}", filename); return; } - Ok(md) => md, + Some(md) => md, }; #[cfg(unix)] @@ -1366,7 +1382,10 @@ fn display_item_long( " {} {} {}", pad_left(display_file_size(&md, config), max_size), display_date(&md, config), - display_file_name(&item.p_buf, strip, &md, config).contents, + // unwrap is fine because it fails when metadata is not available + // but we already know that it is because it's checked at the + // start of the function. + display_file_name(&item, strip, config).unwrap().contents, ); } @@ -1537,14 +1556,9 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String { } #[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(); +fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option { + let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style); + let file_type = path.file_type()?; match config.indicator_style { IndicatorStyle::Classify | IndicatorStyle::FileType => { @@ -1563,8 +1577,8 @@ fn display_file_name( _ => (), }; - if config.format == Format::Long && metadata.file_type().is_symlink() { - if let Ok(target) = path.read_link() { + if config.format == Format::Long && path.file_type()?.is_symlink() { + if let Ok(target) = path.p_buf.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(" -> "); @@ -1572,7 +1586,7 @@ fn display_file_name( } } - name.into() + Some(name.into()) } #[cfg(unix)] @@ -1604,26 +1618,22 @@ macro_rules! has { #[cfg(unix)] #[allow(clippy::cognitive_complexity)] -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); +fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option { + let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style); if config.format != Format::Long && config.inode { - name = get_inode(metadata) + " " + &name; + name = get_inode(path.md()?) + " " + &name; } let mut width = UnicodeWidthStr::width(&*name); let ext; if config.color || config.indicator_style != IndicatorStyle::None { - let file_type = metadata.file_type(); + let metadata = path.md()?; + let file_type = path.file_type()?; let (code, sym) = if file_type.is_dir() { ("di", Some('/')) } else if file_type.is_symlink() { - if path.exists() { + if path.p_buf.exists() { ("ln", Some('@')) } else { ("or", Some('@')) @@ -1657,7 +1667,7 @@ fn display_file_name( ("ex", sym) } else if metadata.nlink() > 1 { ("mh", sym) - } else if let Some(e) = path.extension() { + } else if let Some(e) = path.p_buf.extension() { ext = format!("*.{}", e.to_string_lossy()); (ext.as_str(), None) } else { @@ -1696,8 +1706,8 @@ fn display_file_name( } } - if config.format == Format::Long && metadata.file_type().is_symlink() { - if let Ok(target) = path.read_link() { + if config.format == Format::Long && path.file_type()?.is_symlink() { + if let Ok(target) = path.p_buf.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); @@ -1706,10 +1716,10 @@ fn display_file_name( } } - Cell { + Some(Cell { contents: name, width, - } + }) } #[cfg(not(unix))] From a114f855f08426ab33a36ac81d062fca23d5e2ba Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 22 Apr 2021 23:43:00 +0200 Subject: [PATCH 112/399] ls: revert to_ascii_lowercase --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d744e2914..f820ffffe 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1171,7 +1171,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { 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.file_name.to_ascii_lowercase()), + 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 => {} } From 3874a2445744a853608ef9854a3db5d44289f549 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 23 Apr 2021 00:35:45 +0200 Subject: [PATCH 113/399] ls: add once_cell to Cargo.toml --- src/uu/ls/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index dacdc7cd9..292a8b9d5 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -25,6 +25,7 @@ unicode-width = "0.1.5" globset = "0.4.6" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +once_cell = "1.7.2" [target.'cfg(unix)'.dependencies] atty = "0.2" From 72bf7afe5b048e50dfe45558ceaa7e39e6345d2c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 23 Apr 2021 00:49:05 +0200 Subject: [PATCH 114/399] ls: also update Cargo.lock --- Cargo.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 461716b1b..0a07b83b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,6 +897,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + [[package]] name = "onig" version = "4.3.3" @@ -1991,6 +1997,7 @@ dependencies = [ "globset", "lazy_static", "number_prefix", + "once_cell", "term_grid", "termsize", "time", From 646c6cacbc546d6f15435b4bb45458541c2d04d2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 22 Apr 2021 22:37:44 +0200 Subject: [PATCH 115/399] 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 1c5f47efaff48b301b23d39215dd1e1ba6a7568c Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Fri, 23 Apr 2021 07:24:47 +0200 Subject: [PATCH 116/399] use `CmdResult` methods rather than fields --- tests/by-util/test_cp.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c90dff061..931811d91 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1095,13 +1095,10 @@ fn test_cp_reflink_always() { #[cfg(target_os = "linux")] fn test_cp_reflink_auto() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg("--reflink=auto") + ucmd.arg("--reflink=auto") .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"); @@ -1111,13 +1108,10 @@ fn test_cp_reflink_auto() { #[cfg(target_os = "linux")] fn test_cp_reflink_never() { let (at, mut ucmd) = at_and_ucmd!(); - let result = ucmd - .arg("--reflink=never") + ucmd.arg("--reflink=never") .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"); @@ -1131,8 +1125,6 @@ fn test_cp_reflink_bad() { .arg("--reflink=bad") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_EXISTING_FILE) - .run(); - - assert!(!result.success); - assert!(result.stderr.contains("invalid argument")); + .fails() + .stderr_contains("invalid argument"); } From eccb86c9edc1b36d6733de66c4e37d305090d26d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 23 Apr 2021 08:26:20 +0200 Subject: [PATCH 117/399] ls: fix -a test --- src/uu/ls/src/ls.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f820ffffe..19097edda 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -492,7 +492,7 @@ impl Config { } } - if files != Files::Normal { + if files == Files::Normal { ignore_patterns.add(Glob::new(".*").unwrap()); } @@ -1185,25 +1185,21 @@ fn sort_entries(entries: &mut Vec, config: &Config) { fn is_hidden(file_path: &DirEntry) -> bool { let metadata = fs::metadata(file_path.path()).unwrap(); let attr = metadata.file_attributes(); - ((attr & 0x2) > 0) || file_path.file_name().to_string_lossy().starts_with('.') -} - -#[cfg(unix)] -fn is_hidden(file_path: &DirEntry) -> bool { - file_path.file_name().to_string_lossy().starts_with('.') + ((attr & 0x2) > 0) } fn should_display(entry: &DirEntry, config: &Config) -> bool { let ffi_name = entry.file_name(); - if config.files == Files::Normal && is_hidden(entry) { - return false; + // For unix, the hidden files are already included in the ignore pattern + #[cfg(windows)] + { + if config.files == Files::Normal && is_hidden(entry) { + return false; + } } - if config.ignore_patterns.is_match(&ffi_name) { - return false; - } - true + !config.ignore_patterns.is_match(&ffi_name) } fn enter_directory(dir: &PathData, config: &Config) { From b68ecf1269d9dcf11e5ed526d042bdb18a9722b1 Mon Sep 17 00:00:00 2001 From: James Robson Date: Fri, 23 Apr 2021 16:36:46 +0100 Subject: [PATCH 118/399] 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(); } + From e6f6b109a59d0db800f682396d6a1f04599e80cd Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 21 Apr 2021 19:03:36 +0200 Subject: [PATCH 119/399] sort: implement --debug This adds a --debug flag, which, when activated, will draw lines below the characters that are actually used for comparisons. This is not a complete implementation of --debug. It should, quoting the man page for GNU sort: "annotate the part of the line used to sort, and warn about questionable usage to stderr". Warning about "questionable usage" is not part of this patch. This change required some adjustments to be able to get the range that is actually used for comparisons. Most notably, general numeric comparisons were rewritten, fixing some bugs along the lines. Testing is mostly done by adding fixtures for the expected debug output of existing tests. --- Cargo.lock | 1 + src/uu/sort/Cargo.toml | 1 + src/uu/sort/src/numeric_str_cmp.rs | 10 +- src/uu/sort/src/sort.rs | 467 +++++++++++------- tests/by-util/test_sort.rs | 80 +-- .../sort/default_unsorted_ints.expected.debug | 200 ++++++++ .../sort/dictionary_order.expected.debug | 9 + .../exponents-positive-numeric.expected.debug | 36 ++ .../fixtures/sort/exponents_general.expected | 19 + .../sort/exponents_general.expected.debug | 57 +++ tests/fixtures/sort/exponents_general.txt | 19 + .../human-numeric-whitespace.expected.debug | 33 ++ .../sort/human_block_sizes.expected.debug | 33 ++ .../fixtures/sort/ignore_case.expected.debug | 21 + .../fixtures/sort/keys_closed_range.expected | 6 + .../sort/keys_closed_range.expected.debug | 18 + tests/fixtures/sort/keys_closed_range.txt | 6 + .../sort/keys_custom_separator.expected | 3 + .../sort/keys_custom_separator.expected.debug | 9 + tests/fixtures/sort/keys_custom_separator.txt | 3 + .../sort/keys_multiple_ranges.expected | 6 + .../sort/keys_multiple_ranges.expected.debug | 24 + tests/fixtures/sort/keys_multiple_ranges.txt | 6 + .../fixtures/sort/keys_no_char_match.expected | 3 + .../sort/keys_no_char_match.expected.debug | 9 + tests/fixtures/sort/keys_no_char_match.txt | 3 + .../sort/keys_no_field_match.expected | 6 + .../sort/keys_no_field_match.expected.debug | 18 + tests/fixtures/sort/keys_no_field_match.txt | 6 + tests/fixtures/sort/keys_open_ended.expected | 6 + .../sort/keys_open_ended.expected.debug | 18 + tests/fixtures/sort/keys_open_ended.txt | 6 + ...d_floats_ints_chars_numeric.expected.debug | 90 ++++ ...s_ints_chars_numeric_stable.expected.debug | 60 +++ ...s_ints_chars_numeric_unique.expected.debug | 40 ++ ...hars_numeric_unique_reverse.expected.debug | 40 ++ .../sort/month_default.expected.debug | 30 ++ .../fixtures/sort/month_stable.expected.debug | 20 + .../fixtures/sort/months-dedup.expected.debug | 12 + .../sort/months-whitespace.expected.debug | 24 + .../sort/multiple_decimals_general.expected | 37 ++ .../multiple_decimals_general.expected.debug | 111 +++++ .../sort/multiple_decimals_general.txt | 4 +- .../multiple_decimals_numeric.expected.debug | 105 ++++ .../numeric-floats-with-nan2.expected.debug | 69 +++ .../sort/numeric_fixed_floats.expected.debug | 6 + .../sort/numeric_floats.expected.debug | 6 + .../numeric_floats_and_ints.expected.debug | 6 + .../numeric_floats_with_nan.expected.debug | 9 + .../numeric_unfixed_floats.expected.debug | 6 + .../sort/numeric_unique.expected.debug | 4 + .../sort/numeric_unsorted_ints.expected.debug | 300 +++++++++++ ...umeric_unsorted_ints_unique.expected.debug | 8 + tests/fixtures/sort/version.expected.debug | 12 + .../fixtures/sort/words_unique.expected.debug | 6 + .../sort/zero-terminated.expected.debug | 84 ++++ 56 files changed, 2010 insertions(+), 221 deletions(-) create mode 100644 tests/fixtures/sort/default_unsorted_ints.expected.debug create mode 100644 tests/fixtures/sort/dictionary_order.expected.debug create mode 100644 tests/fixtures/sort/exponents-positive-numeric.expected.debug create mode 100644 tests/fixtures/sort/exponents_general.expected create mode 100644 tests/fixtures/sort/exponents_general.expected.debug create mode 100644 tests/fixtures/sort/exponents_general.txt create mode 100644 tests/fixtures/sort/human-numeric-whitespace.expected.debug create mode 100644 tests/fixtures/sort/human_block_sizes.expected.debug create mode 100644 tests/fixtures/sort/ignore_case.expected.debug create mode 100644 tests/fixtures/sort/keys_closed_range.expected create mode 100644 tests/fixtures/sort/keys_closed_range.expected.debug create mode 100644 tests/fixtures/sort/keys_closed_range.txt create mode 100644 tests/fixtures/sort/keys_custom_separator.expected create mode 100644 tests/fixtures/sort/keys_custom_separator.expected.debug create mode 100644 tests/fixtures/sort/keys_custom_separator.txt create mode 100644 tests/fixtures/sort/keys_multiple_ranges.expected create mode 100644 tests/fixtures/sort/keys_multiple_ranges.expected.debug create mode 100644 tests/fixtures/sort/keys_multiple_ranges.txt create mode 100644 tests/fixtures/sort/keys_no_char_match.expected create mode 100644 tests/fixtures/sort/keys_no_char_match.expected.debug create mode 100644 tests/fixtures/sort/keys_no_char_match.txt create mode 100644 tests/fixtures/sort/keys_no_field_match.expected create mode 100644 tests/fixtures/sort/keys_no_field_match.expected.debug create mode 100644 tests/fixtures/sort/keys_no_field_match.txt create mode 100644 tests/fixtures/sort/keys_open_ended.expected create mode 100644 tests/fixtures/sort/keys_open_ended.expected.debug create mode 100644 tests/fixtures/sort/keys_open_ended.txt create mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug create mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug create mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug create mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug create mode 100644 tests/fixtures/sort/month_default.expected.debug create mode 100644 tests/fixtures/sort/month_stable.expected.debug create mode 100644 tests/fixtures/sort/months-dedup.expected.debug create mode 100644 tests/fixtures/sort/months-whitespace.expected.debug create mode 100644 tests/fixtures/sort/multiple_decimals_general.expected create mode 100644 tests/fixtures/sort/multiple_decimals_general.expected.debug create mode 100644 tests/fixtures/sort/multiple_decimals_numeric.expected.debug create mode 100644 tests/fixtures/sort/numeric-floats-with-nan2.expected.debug create mode 100644 tests/fixtures/sort/numeric_fixed_floats.expected.debug create mode 100644 tests/fixtures/sort/numeric_floats.expected.debug create mode 100644 tests/fixtures/sort/numeric_floats_and_ints.expected.debug create mode 100644 tests/fixtures/sort/numeric_floats_with_nan.expected.debug create mode 100644 tests/fixtures/sort/numeric_unfixed_floats.expected.debug create mode 100644 tests/fixtures/sort/numeric_unique.expected.debug create mode 100644 tests/fixtures/sort/numeric_unsorted_ints.expected.debug create mode 100644 tests/fixtures/sort/numeric_unsorted_ints_unique.expected.debug create mode 100644 tests/fixtures/sort/version.expected.debug create mode 100644 tests/fixtures/sort/words_unique.expected.debug create mode 100644 tests/fixtures/sort/zero-terminated.expected.debug diff --git a/Cargo.lock b/Cargo.lock index 461716b1b..4f8cef859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2297,6 +2297,7 @@ dependencies = [ "rayon", "semver", "smallvec 1.6.1", + "unicode-width", "uucore", "uucore_procs", ] diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 6a9976278..464207a2c 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -22,6 +22,7 @@ fnv = "1.0.7" itertools = "0.10.0" semver = "0.9.0" smallvec = "1.6.1" +unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index a50734ebd..314b4c595 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -137,7 +137,15 @@ impl NumInfo { sign: if had_digit { sign } else { Sign::Positive }, exponent: 0, }, - 0..0, + if had_digit { + // In this case there were only zeroes. + // For debug output to work properly, we have to claim to match the end of the number. + num.len()..num.len() + } else { + // This was no number at all. + // For debug output to work properly, we have to claim to match the start of the number. + 0..0 + }, ) } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 07b852921..d9a639b3c 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -26,7 +26,6 @@ use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; use smallvec::SmallVec; -use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; @@ -34,8 +33,9 @@ use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; use std::mem::replace; -use std::ops::{Range, RangeInclusive}; +use std::ops::Range; use std::path::Path; +use unicode_width::UnicodeWidthStr; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() static NAME: &str = "sort"; @@ -62,6 +62,7 @@ static OPT_DICTIONARY_ORDER: &str = "dictionary-order"; static OPT_MERGE: &str = "merge"; static OPT_CHECK: &str = "check"; static OPT_CHECK_SILENT: &str = "check-silent"; +static OPT_DEBUG: &str = "debug"; static OPT_IGNORE_CASE: &str = "ignore-case"; static OPT_IGNORE_BLANKS: &str = "ignore-blanks"; static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting"; @@ -96,6 +97,7 @@ enum SortMode { struct GlobalSettings { mode: SortMode, + debug: bool, ignore_blanks: bool, ignore_case: bool, dictionary_order: bool, @@ -119,6 +121,7 @@ impl Default for GlobalSettings { fn default() -> GlobalSettings { GlobalSettings { mode: SortMode::Default, + debug: true, ignore_blanks: false, ignore_case: false, dictionary_order: false, @@ -196,13 +199,13 @@ impl SelectionRange { } enum NumCache { - AsF64(f64), + AsF64(GeneralF64ParseResult), WithInfo(NumInfo), None, } impl NumCache { - fn as_f64(&self) -> f64 { + fn as_f64(&self) -> GeneralF64ParseResult { match self { NumCache::AsF64(n) => *n, _ => unreachable!(), @@ -253,19 +256,14 @@ impl Line { .selectors .iter() .map(|selector| { - let mut range = - if let Some(range) = selector.get_selection(&line, fields.as_deref()) { - if let Some(transformed) = - transform(&line[range.to_owned()], &selector.settings) - { - SelectionRange::String(transformed) - } else { - SelectionRange::ByIndex(range.start().to_owned()..range.end() + 1) - } - } else { - // If there is no match, match the empty string. - SelectionRange::ByIndex(0..0) - }; + let range = selector.get_selection(&line, fields.as_deref()); + let mut range = if let Some(transformed) = + transform(&line[range.to_owned()], &selector.settings) + { + SelectionRange::String(transformed) + } else { + SelectionRange::ByIndex(range) + }; let num_cache = if selector.settings.mode == SortMode::Numeric || selector.settings.mode == SortMode::HumanNumeric { @@ -280,7 +278,8 @@ impl Line { range.shorten(num_range); NumCache::WithInfo(info) } else if selector.settings.mode == SortMode::GeneralNumeric { - NumCache::AsF64(permissive_f64_parse(get_leading_gen(range.get_str(&line)))) + let str = range.get_str(&line); + NumCache::AsF64(general_f64_parse(&str[get_leading_gen(str)])) } else { NumCache::None }; @@ -289,6 +288,129 @@ impl Line { .collect(); Self { line, selections } } + + /// Writes indicators for the selections this line matched. The original line content is NOT expected + /// to be already printed. + fn print_debug( + &self, + settings: &GlobalSettings, + writer: &mut dyn Write, + ) -> std::io::Result<()> { + // We do not consider this function performance critical, as debug output is only useful for small files, + // which are not a performance problem in any case. Therefore there aren't any special performance + // optimizations here. + + let line = self.line.replace('\t', ">"); + writeln!(writer, "{}", line)?; + + let fields = tokenize(&self.line, settings.separator); + for selector in settings.selectors.iter() { + let mut selection = selector.get_selection(&self.line, Some(&fields)); + match selector.settings.mode { + SortMode::Numeric | SortMode::HumanNumeric => { + // find out which range is used for numeric comparisons + let (_, num_range) = NumInfo::parse( + &self.line[selection.clone()], + NumInfoParseSettings { + accept_si_units: selector.settings.mode == SortMode::HumanNumeric, + thousands_separator: Some(THOUSANDS_SEP), + decimal_pt: Some(DECIMAL_PT), + }, + ); + let initial_selection = selection.clone(); + + // Shorten selection to num_range. + selection.start += num_range.start; + selection.end = selection.start + num_range.len(); + + // include a trailing si unit + if selector.settings.mode == SortMode::HumanNumeric + && self.line[selection.end..initial_selection.end] + .starts_with(&['k', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'][..]) + { + selection.end += 1; + } + + // include leading zeroes, a leading minus or a leading decimal point + while self.line[initial_selection.start..selection.start] + .ends_with(&['-', '0', '.'][..]) + { + selection.start -= 1; + } + } + SortMode::GeneralNumeric => { + let initial_selection = &self.line[selection.clone()]; + + let leading = get_leading_gen(initial_selection); + + // Shorten selection to leading. + selection.start += leading.start; + selection.end = selection.start + leading.len(); + } + SortMode::Month => { + let initial_selection = &self.line[selection.clone()]; + + let month = if month_parse(initial_selection) == Month::Unknown { + // We failed to parse a month, which is equivalent to matching nothing. + 0..0 + } else { + // We parsed a month. Match the three first non-whitespace characters, which must be the month we parsed. + let mut chars = initial_selection + .char_indices() + .skip_while(|(_, c)| c.is_whitespace()); + chars.next().unwrap().0 + ..chars.nth(2).map_or(initial_selection.len(), |(idx, _)| idx) + }; + + // Shorten selection to month. + selection.start += month.start; + selection.end = selection.start + month.len(); + } + _ => {} + } + + write!( + writer, + "{}", + " ".repeat(UnicodeWidthStr::width(&line[..selection.start])) + )?; + + // TODO: Once our minimum supported rust version is at least 1.47, use selection.is_empty() instead. + #[allow(clippy::len_zero)] + { + if selection.len() == 0 { + writeln!(writer, "^ no match for key")?; + } else { + writeln!( + writer, + "{}", + "_".repeat(UnicodeWidthStr::width(&line[selection])) + )?; + } + } + } + if !(settings.random + || settings.stable + || settings.unique + || !(settings.dictionary_order + || settings.ignore_blanks + || settings.ignore_case + || settings.ignore_non_printing + || settings.mode != SortMode::Default)) + { + // A last resort comparator is in use, underline the whole line. + if self.line.is_empty() { + writeln!(writer, "^ no match for key")?; + } else { + writeln!( + writer, + "{}", + "_".repeat(UnicodeWidthStr::width(line.as_str())) + )?; + } + } + Ok(()) + } } /// Transform this line. Returns None if there's no need to transform. @@ -449,13 +571,16 @@ impl FieldSelector { /// Look up the slice that corresponds to this selector for the given line. /// If needs_fields returned false, fields may be None. - fn get_selection<'a>( - &self, - line: &'a str, - tokens: Option<&[Field]>, - ) -> Option> { - enum ResolutionErr { + fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Range { + enum Resolution { + // The start index of the resolved character, inclusive + StartOfChar(usize), + // The end index of the resolved character, exclusive. + // This is only returned if the character index is 0. + EndOfChar(usize), + // The resolved character would be in front of the first character TooLow, + // The resolved character would be after the last character TooHigh, } @@ -464,15 +589,15 @@ impl FieldSelector { line: &str, tokens: Option<&[Field]>, position: &KeyPosition, - ) -> Result { + ) -> Resolution { if tokens.map_or(false, |fields| fields.len() < position.field) { - Err(ResolutionErr::TooHigh) + Resolution::TooHigh } else if position.char == 0 { let end = tokens.unwrap()[position.field - 1].end; if end == 0 { - Err(ResolutionErr::TooLow) + Resolution::TooLow } else { - Ok(end - 1) + Resolution::EndOfChar(end) } } else { let mut idx = if position.field == 1 { @@ -481,38 +606,52 @@ impl FieldSelector { 0 } else { tokens.unwrap()[position.field - 1].start - } + position.char - - 1; + }; + idx += line[idx..] + .char_indices() + .nth(position.char - 1) + .map_or(line.len(), |(idx, _)| idx); if idx >= line.len() { - Err(ResolutionErr::TooHigh) + Resolution::TooHigh } else { if position.ignore_blanks { - if let Some(not_whitespace) = - line[idx..].chars().position(|c| !c.is_whitespace()) + if let Some((not_whitespace, _)) = + line[idx..].char_indices().find(|(_, c)| !c.is_whitespace()) { idx += not_whitespace; } else { - return Err(ResolutionErr::TooHigh); + return Resolution::TooHigh; } } - Ok(idx) + Resolution::StartOfChar(idx) } } } - if let Ok(from) = resolve_index(line, tokens, &self.from) { - let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to)); - match to { - Some(Ok(to)) => Some(from..=to), - // If `to` was not given or the match would be after the end of the line, - // match everything until the end of the line. - None | Some(Err(ResolutionErr::TooHigh)) => Some(from..=line.len() - 1), - // If `to` is before the start of the line, report no match. - // This can happen if the line starts with a separator. - Some(Err(ResolutionErr::TooLow)) => None, + match resolve_index(line, tokens, &self.from) { + Resolution::StartOfChar(from) => { + let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to)); + + match to { + Some(Resolution::StartOfChar(mut to)) => { + to += line[to..].chars().next().map_or(1, |c| c.len_utf8()); + from..to + } + Some(Resolution::EndOfChar(to)) => from..to, + // If `to` was not given or the match would be after the end of the line, + // match everything until the end of the line. + None | Some(Resolution::TooHigh) => from..line.len(), + // If `to` is before the start of the line, report no match. + // This can happen if the line starts with a separator. + Some(Resolution::TooLow) => 0..0, + } } - } else { - None + Resolution::TooLow | Resolution::EndOfChar(_) => { + unreachable!("This should only happen if the field start index is 0, but that should already have caused an error.") + } + // While for comparisons it's only important that this is an empty slice, + // to produce accurate debug output we need to match an empty slice at the end of the line. + Resolution::TooHigh => line.len()..line.len(), } } } @@ -540,7 +679,7 @@ impl<'a> PartialOrd for MergeableFile<'a> { impl<'a> PartialEq for MergeableFile<'a> { fn eq(&self, other: &MergeableFile) -> bool { - Ordering::Equal == compare_by(&self.current_line, &other.current_line, self.settings) + Ordering::Equal == self.cmp(other) } } @@ -571,8 +710,8 @@ impl<'a> FileMerger<'a> { } impl<'a> Iterator for FileMerger<'a> { - type Item = String; - fn next(&mut self) -> Option { + type Item = Line; + fn next(&mut self) -> Option { match self.heap.pop() { Some(mut current) => { match current.lines.next() { @@ -582,12 +721,12 @@ impl<'a> Iterator for FileMerger<'a> { Line::new(next_line, &self.settings), ); self.heap.push(current); - Some(ret.line) + Some(ret) } _ => { // Don't put it back in the heap (it's empty/erroring) // but its first line is still valid. - Some(current.current_line.line) + Some(current.current_line) } } } @@ -756,9 +895,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .value_name("NUL_FILES") .multiple(true), ) + .arg( + Arg::with_name(OPT_DEBUG) + .long(OPT_DEBUG) + .help("underline the parts of the line that are actually used for sorting"), + ) .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); + settings.debug = matches.is_present(OPT_DEBUG); + // check whether user specified a zero terminated list of files for input, otherwise read files from args let mut files: Vec = if matches.is_present(OPT_FILES0_FROM) { let files0_from: Vec = matches @@ -862,6 +1008,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 1, &mut key_settings, ); + if from.char == 0 { + crash!( + 1, + "invalid character index 0 in `{}` for the start position of a field", + key + ) + } let to = from_to .next() .map(|to| KeyPosition::parse(to, 0, &mut key_settings)); @@ -933,7 +1086,10 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { if settings.merge { if settings.unique { - print_sorted(file_merger.dedup(), &settings) + print_sorted( + file_merger.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal), + &settings, + ) } else { print_sorted(file_merger, &settings) } @@ -941,12 +1097,11 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { print_sorted( lines .into_iter() - .dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal) - .map(|line| line.line), + .dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal), &settings, ) } else { - print_sorted(lines.into_iter().map(|line| line.line), &settings) + print_sorted(lines.into_iter(), &settings) } 0 @@ -1043,107 +1198,80 @@ fn default_compare(a: &str, b: &str) -> Ordering { a.cmp(b) } -// This function does the initial detection of numeric lines. -// Lines starting with a number or positive or negative sign. -// It also strips the string of any thing that could never -// be a number for the purposes of any type of numeric comparison. -#[inline(always)] -fn leading_num_common(a: &str) -> &str { - let mut s = ""; - - // check whether char is numeric, whitespace or decimal point or thousand separator - for (idx, c) in a.char_indices() { - if !c.is_numeric() - && !c.is_whitespace() - && !c.eq(&THOUSANDS_SEP) - && !c.eq(&DECIMAL_PT) - // check for e notation - && !c.eq(&'e') - && !c.eq(&'E') - // check whether first char is + or - - && !a.chars().next().unwrap_or('\0').eq(&POSITIVE) - && !a.chars().next().unwrap_or('\0').eq(&NEGATIVE) - { - // Strip string of non-numeric trailing chars - s = &a[..idx]; - break; - } - // If line is not a number line, return the line as is - s = &a; - } - s -} - // This function cleans up the initial comparison done by leading_num_common for a general numeric compare. // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. // For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000. -fn get_leading_gen(a: &str) -> &str { - // Make this iter peekable to see if next char is numeric - let raw_leading_num = leading_num_common(a); - let mut p_iter = raw_leading_num.chars().peekable(); - let mut result = ""; - // Cleanup raw stripped strings - for c in p_iter.to_owned() { - let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric(); - // Only general numeric recognizes e notation and, see block below, the '+' sign - // Only GNU (non-general) numeric recognize thousands seperators, takes only leading # - if (c.eq(&'e') || c.eq(&'E')) && !next_char_numeric || c.eq(&THOUSANDS_SEP) { - result = a.split(c).next().unwrap_or(""); - break; - // If positive sign and next char is not numeric, split at postive sign at keep trailing numbers - // There is a more elegant way to do this in Rust 1.45, std::str::strip_prefix - } else if c.eq(&POSITIVE) && !next_char_numeric { - result = a.trim().trim_start_matches('+'); - break; +fn get_leading_gen(input: &str) -> Range { + let trimmed = input.trim_start(); + let leading_whitespace_len = input.len() - trimmed.len(); + for allowed_prefix in &["inf", "-inf", "nan"] { + if trimmed.is_char_boundary(allowed_prefix.len()) + && trimmed[..allowed_prefix.len()].eq_ignore_ascii_case(allowed_prefix) + { + return leading_whitespace_len..(leading_whitespace_len + allowed_prefix.len()); } - // If no further processing needed to be done, return the line as-is to be sorted - result = a; } - result + // Make this iter peekable to see if next char is numeric + let mut char_indices = trimmed.char_indices().peekable(); + + let first = char_indices.peek(); + + if first.map_or(false, |&(_, c)| c == NEGATIVE || c == POSITIVE) { + char_indices.next(); + } + + let mut had_e_notation = false; + let mut had_decimal_pt = false; + while let Some((idx, c)) = char_indices.next() { + if c.is_ascii_digit() { + continue; + } + if c == DECIMAL_PT && !had_decimal_pt { + had_decimal_pt = true; + continue; + } + let next_char_numeric = char_indices + .peek() + .map_or(false, |(_, c)| c.is_ascii_digit()); + if (c == 'e' || c == 'E') && !had_e_notation && next_char_numeric { + had_e_notation = true; + continue; + } + return leading_whitespace_len..(leading_whitespace_len + idx); + } + leading_whitespace_len..input.len() } -#[inline(always)] -fn remove_trailing_dec<'a, S: Into>>(input: S) -> Cow<'a, str> { - let input = input.into(); - if let Some(s) = input.find(DECIMAL_PT) { - let (leading, trailing) = input.split_at(s); - let output = [leading, ".", trailing.replace(DECIMAL_PT, "").as_str()].concat(); - Cow::Owned(output) - } else { - input - } +#[derive(Copy, Clone, PartialEq, PartialOrd)] +enum GeneralF64ParseResult { + Invalid, + NaN, + NegInfinity, + Number(f64), + Infinity, } /// Parse the beginning string into an f64, returning -inf instead of NaN on errors. #[inline(always)] -fn permissive_f64_parse(a: &str) -> f64 { - // GNU sort treats "NaN" as non-number in numeric, so it needs special care. - // *Keep this trim before parse* despite what POSIX may say about -b and -n - // because GNU and BSD both seem to require it to match their behavior - // - // Remove any trailing decimals, ie 4568..890... becomes 4568.890 - // Then, we trim whitespace and parse - match remove_trailing_dec(a).trim().parse::() { - Ok(a) if a.is_nan() => std::f64::NEG_INFINITY, - Ok(a) => a, - Err(_) => std::f64::NEG_INFINITY, +fn general_f64_parse(a: &str) -> GeneralF64ParseResult { + // The actual behavior here relies on Rust's implementation of parsing floating points. + // For example "nan", "inf" (ignoring the case) and "infinity" are only parsed to floats starting from 1.53. + // TODO: Once our minimum supported Rust version is 1.53 or above, we should add tests for those cases. + match a.parse::() { + Ok(a) if a.is_nan() => GeneralF64ParseResult::NaN, + Ok(a) if a == std::f64::NEG_INFINITY => GeneralF64ParseResult::NegInfinity, + Ok(a) if a == std::f64::INFINITY => GeneralF64ParseResult::Infinity, + Ok(a) => GeneralF64ParseResult::Number(a), + Err(_) => GeneralF64ParseResult::Invalid, } } /// Compares two floats, with errors and non-numerics assumed to be -inf. /// Stops coercing at the first non-numeric char. /// We explicitly need to convert to f64 in this case. -fn general_numeric_compare(a: f64, b: f64) -> Ordering { - #![allow(clippy::comparison_chain)] - // f64::cmp isn't implemented (due to NaN issues); implement directly instead - if a > b { - Ordering::Greater - } else if a < b { - Ordering::Less - } else { - Ordering::Equal - } +fn general_numeric_compare(a: GeneralF64ParseResult, b: GeneralF64ParseResult) -> Ordering { + a.partial_cmp(&b).unwrap() } fn get_rand_string() -> String { @@ -1170,7 +1298,7 @@ fn random_shuffle(a: &str, b: &str, x: String) -> Ordering { da.cmp(&db) } -#[derive(Eq, Ord, PartialEq, PartialOrd)] +#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum Month { Unknown, January, @@ -1189,29 +1317,32 @@ enum Month { /// Parse the beginning string into a Month, returning Month::Unknown on errors. fn month_parse(line: &str) -> Month { - // GNU splits at any 3 letter match "JUNNNN" is JUN - let pattern = if line.trim().len().ge(&3) { - // Split a 3 and get first element of tuple ".0" - line.trim().split_at(3).0 - } else { - "" - }; + let line = line.trim(); - match pattern.to_uppercase().as_ref() { - "JAN" => Month::January, - "FEB" => Month::February, - "MAR" => Month::March, - "APR" => Month::April, - "MAY" => Month::May, - "JUN" => Month::June, - "JUL" => Month::July, - "AUG" => Month::August, - "SEP" => Month::September, - "OCT" => Month::October, - "NOV" => Month::November, - "DEC" => Month::December, - _ => Month::Unknown, + const MONTHS: [(&str, Month); 12] = [ + ("JAN", Month::January), + ("FEB", Month::February), + ("MAR", Month::March), + ("APR", Month::April), + ("MAY", Month::May), + ("JUN", Month::June), + ("JUL", Month::July), + ("AUG", Month::August), + ("SEP", Month::September), + ("OCT", Month::October), + ("NOV", Month::November), + ("DEC", Month::December), + ]; + + for (month_str, month) in &MONTHS { + if line.is_char_boundary(month_str.len()) + && line[..month_str.len()].eq_ignore_ascii_case(month_str) + { + return *month; + } } + + Month::Unknown } fn month_compare(a: &str, b: &str) -> Ordering { @@ -1269,7 +1400,7 @@ fn remove_nonprinting_chars(s: &str) -> String { .collect::() } -fn print_sorted>(iter: T, settings: &GlobalSettings) { +fn print_sorted>(iter: T, settings: &GlobalSettings) { let mut file: Box = match settings.outfile { Some(ref filename) => match File::create(Path::new(&filename)) { Ok(f) => Box::new(BufWriter::new(f)) as Box, @@ -1280,15 +1411,19 @@ fn print_sorted>(iter: T, settings: &GlobalSettings) }, None => Box::new(BufWriter::new(stdout())) as Box, }; - if settings.zero_terminated { + if settings.zero_terminated && !settings.debug { for line in iter { - crash_if_err!(1, file.write_all(line.as_bytes())); + crash_if_err!(1, file.write_all(line.line.as_bytes())); crash_if_err!(1, file.write_all("\0".as_bytes())); } } else { for line in iter { - crash_if_err!(1, file.write_all(line.as_bytes())); - crash_if_err!(1, file.write_all("\n".as_bytes())); + if !settings.debug { + crash_if_err!(1, file.write_all(line.line.as_bytes())); + crash_if_err!(1, file.write_all("\n".as_bytes())); + } else { + crash_if_err!(1, line.print_debug(settings, &mut file)); + } } } crash_if_err!(1, file.flush()); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index a4a9a383c..c68fa3dcf 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2,10 +2,17 @@ use crate::common::util::*; fn test_helper(file_name: &str, args: &str) { new_ucmd!() - .arg(args) .arg(format!("{}.txt", file_name)) + .args(&args.split(' ').collect::>()) .succeeds() .stdout_is_fixture(format!("{}.expected", file_name)); + + new_ucmd!() + .arg(format!("{}.txt", file_name)) + .arg("--debug") + .args(&args.split(' ').collect::>()) + .succeeds() + .stdout_is_fixture(format!("{}.expected.debug", file_name)); } #[test] @@ -29,11 +36,7 @@ fn test_human_numeric_whitespace() { #[test] fn test_multiple_decimals_general() { - new_ucmd!() - .arg("-g") - .arg("multiple_decimals_general.txt") - .succeeds() - .stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); + test_helper("multiple_decimals_general", "-g") } #[test] @@ -63,7 +66,7 @@ fn test_check_zero_terminated_success() { #[test] fn test_random_shuffle_len() { // check whether output is the same length as the input - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -75,7 +78,7 @@ fn test_random_shuffle_len() { #[test] fn test_random_shuffle_contains_all_lines() { // check whether lines of input are all in output - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -90,7 +93,7 @@ fn test_random_shuffle_two_runs_not_the_same() { // check to verify that two random shuffles are not equal; this has the // potential to fail in the very unlikely event that the random order is the same // as the starting order, or if both random sorts end up having the same order. - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -105,7 +108,7 @@ fn test_random_shuffle_contains_two_runs_not_the_same() { // check to verify that two random shuffles are not equal; this has the // potential to fail in the unlikely event that random order is the same // as the starting order, or if both random sorts end up having the same order. - const FILE: &'static str = "default_unsorted_ints.expected"; + const FILE: &str = "default_unsorted_ints.expected"; let (at, _ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("-R").arg(FILE).run().stdout_move_str(); let expected = at.read(FILE); @@ -209,13 +212,7 @@ fn test_non_printing_chars() { #[test] fn test_exponents_positive_general_fixed() { - for exponents_positive_general_param in vec!["-g"] { - new_ucmd!() - .pipe_in("100E6\n\n50e10\n+100000\n\n10000K78\n10E\n\n\n1000EDKLD\n\n\n100E6\n\n50e10\n+100000\n\n") - .arg(exponents_positive_general_param) - .succeeds() - .stdout_only("\n\n\n\n\n\n\n\n10000K78\n1000EDKLD\n10E\n+100000\n+100000\n100E6\n100E6\n50e10\n50e10\n"); - } + test_helper("exponents_general", "-g"); } #[test] @@ -334,62 +331,32 @@ fn test_numeric_unique_ints2() { #[test] fn test_keys_open_ended() { - let input = "aa bb cc\ndd aa ff\ngg aa cc\n"; - new_ucmd!() - .args(&["-k", "2.2"]) - .pipe_in(input) - .succeeds() - .stdout_only("gg aa cc\ndd aa ff\naa bb cc\n"); + test_helper("keys_open_ended", "-k 2.3"); } #[test] fn test_keys_closed_range() { - let input = "aa bb cc\ndd aa ff\ngg aa cc\n"; - new_ucmd!() - .args(&["-k", "2.2,2.2"]) - .pipe_in(input) - .succeeds() - .stdout_only("dd aa ff\ngg aa cc\naa bb cc\n"); + test_helper("keys_closed_range", "-k 2.2,2.2"); } #[test] fn test_keys_multiple_ranges() { - let input = "aa bb cc\ndd aa ff\ngg aa cc\n"; - new_ucmd!() - .args(&["-k", "2,2", "-k", "3,3"]) - .pipe_in(input) - .succeeds() - .stdout_only("gg aa cc\ndd aa ff\naa bb cc\n"); + test_helper("keys_multiple_ranges", "-k 2,2 -k 3,3"); } #[test] fn test_keys_no_field_match() { - let input = "aa aa aa aa\naa bb cc\ndd aa ff\n"; - new_ucmd!() - .args(&["-k", "4,4"]) - .pipe_in(input) - .succeeds() - .stdout_only("aa bb cc\ndd aa ff\naa aa aa aa\n"); + test_helper("keys_no_field_match", "-k 4,4"); } #[test] fn test_keys_no_char_match() { - let input = "aaa\nba\nc\n"; - new_ucmd!() - .args(&["-k", "1.2"]) - .pipe_in(input) - .succeeds() - .stdout_only("c\nba\naaa\n"); + test_helper("keys_no_char_match", "-k 1.2"); } #[test] fn test_keys_custom_separator() { - let input = "aaxbbxcc\nddxaaxff\nggxaaxcc\n"; - new_ucmd!() - .args(&["-k", "2.2,2.2", "-t", "x"]) - .pipe_in(input) - .succeeds() - .stdout_only("ddxaaxff\nggxaaxcc\naaxbbxcc\n"); + test_helper("keys_custom_separator", "-k 2.2,2.2 -t x"); } #[test] @@ -416,6 +383,13 @@ fn test_keys_invalid_field_zero() { .stderr_only("sort: error: field index was 0"); } +#[test] +fn test_keys_invalid_char_zero() { + new_ucmd!().args(&["-k", "1.0"]).fails().stderr_only( + "sort: error: invalid character index 0 in `1.0` for the start position of a field", + ); +} + #[test] fn test_keys_with_options() { let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n"; diff --git a/tests/fixtures/sort/default_unsorted_ints.expected.debug b/tests/fixtures/sort/default_unsorted_ints.expected.debug new file mode 100644 index 000000000..2bf082d3b --- /dev/null +++ b/tests/fixtures/sort/default_unsorted_ints.expected.debug @@ -0,0 +1,200 @@ +1 +_ +10 +__ +100 +___ +11 +__ +12 +__ +13 +__ +14 +__ +15 +__ +16 +__ +17 +__ +18 +__ +19 +__ +2 +_ +20 +__ +21 +__ +22 +__ +23 +__ +24 +__ +25 +__ +26 +__ +27 +__ +28 +__ +29 +__ +3 +_ +30 +__ +31 +__ +32 +__ +33 +__ +34 +__ +35 +__ +36 +__ +37 +__ +38 +__ +39 +__ +4 +_ +40 +__ +41 +__ +42 +__ +43 +__ +44 +__ +45 +__ +46 +__ +47 +__ +48 +__ +49 +__ +5 +_ +50 +__ +51 +__ +52 +__ +53 +__ +54 +__ +55 +__ +56 +__ +57 +__ +58 +__ +59 +__ +6 +_ +60 +__ +61 +__ +62 +__ +63 +__ +64 +__ +65 +__ +66 +__ +67 +__ +68 +__ +69 +__ +7 +_ +70 +__ +71 +__ +72 +__ +73 +__ +74 +__ +75 +__ +76 +__ +77 +__ +78 +__ +79 +__ +8 +_ +80 +__ +81 +__ +82 +__ +83 +__ +84 +__ +85 +__ +86 +__ +87 +__ +88 +__ +89 +__ +9 +_ +90 +__ +91 +__ +92 +__ +93 +__ +94 +__ +95 +__ +96 +__ +97 +__ +98 +__ +99 +__ diff --git a/tests/fixtures/sort/dictionary_order.expected.debug b/tests/fixtures/sort/dictionary_order.expected.debug new file mode 100644 index 000000000..f4a2d17db --- /dev/null +++ b/tests/fixtures/sort/dictionary_order.expected.debug @@ -0,0 +1,9 @@ +bbb +___ +___ +./bbc +_____ +_____ +bbd +___ +___ diff --git a/tests/fixtures/sort/exponents-positive-numeric.expected.debug b/tests/fixtures/sort/exponents-positive-numeric.expected.debug new file mode 100644 index 000000000..f5a32bad1 --- /dev/null +++ b/tests/fixtures/sort/exponents-positive-numeric.expected.debug @@ -0,0 +1,36 @@ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key ++100000 +^ no match for key +_______ +10E +__ +___ +50e10 +__ +_____ +100E6 +___ +_____ +1000EDKLD +____ +_________ +10000K78 +_____ +________ diff --git a/tests/fixtures/sort/exponents_general.expected b/tests/fixtures/sort/exponents_general.expected new file mode 100644 index 000000000..524b6e67f --- /dev/null +++ b/tests/fixtures/sort/exponents_general.expected @@ -0,0 +1,19 @@ + + + + + + + + +5.5.5.5 +10E +1000EDKLD +10000K78 ++100000 ++100000 +100E6 +100E6 +10e10e10e10 +50e10 +50e10 diff --git a/tests/fixtures/sort/exponents_general.expected.debug b/tests/fixtures/sort/exponents_general.expected.debug new file mode 100644 index 000000000..4dea45c39 --- /dev/null +++ b/tests/fixtures/sort/exponents_general.expected.debug @@ -0,0 +1,57 @@ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +5.5.5.5 +___ +_______ +10E +__ +___ +1000EDKLD +____ +_________ +10000K78 +_____ +________ ++100000 +_______ +_______ ++100000 +_______ +_______ +100E6 +_____ +_____ +100E6 +_____ +_____ +10e10e10e10 +_____ +___________ +50e10 +_____ +_____ +50e10 +_____ +_____ diff --git a/tests/fixtures/sort/exponents_general.txt b/tests/fixtures/sort/exponents_general.txt new file mode 100644 index 000000000..de2a6c31b --- /dev/null +++ b/tests/fixtures/sort/exponents_general.txt @@ -0,0 +1,19 @@ +100E6 + +50e10 ++100000 + +10000K78 +10E + + +1000EDKLD + + +100E6 + +50e10 ++100000 + +10e10e10e10 +5.5.5.5 diff --git a/tests/fixtures/sort/human-numeric-whitespace.expected.debug b/tests/fixtures/sort/human-numeric-whitespace.expected.debug new file mode 100644 index 000000000..66afcda66 --- /dev/null +++ b/tests/fixtures/sort/human-numeric-whitespace.expected.debug @@ -0,0 +1,33 @@ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +456K +____ +____ +4568K +_____ +_____ +>>>456M + ____ +_______ + 6.2G + ____ +__________________ diff --git a/tests/fixtures/sort/human_block_sizes.expected.debug b/tests/fixtures/sort/human_block_sizes.expected.debug new file mode 100644 index 000000000..5f4860a85 --- /dev/null +++ b/tests/fixtures/sort/human_block_sizes.expected.debug @@ -0,0 +1,33 @@ +844K +____ +____ +981K +____ +____ +11M +___ +___ +13M +___ +___ +14M +___ +___ +16M +___ +___ +18M +___ +___ +19M +___ +___ +20M +___ +___ +981T +____ +____ +20P +___ +___ diff --git a/tests/fixtures/sort/ignore_case.expected.debug b/tests/fixtures/sort/ignore_case.expected.debug new file mode 100644 index 000000000..08f0abb8d --- /dev/null +++ b/tests/fixtures/sort/ignore_case.expected.debug @@ -0,0 +1,21 @@ +aaa +___ +___ +BBB +___ +___ +ccc +___ +___ +DDD +___ +___ +eee +___ +___ +FFF +___ +___ +ggg +___ +___ diff --git a/tests/fixtures/sort/keys_closed_range.expected b/tests/fixtures/sort/keys_closed_range.expected new file mode 100644 index 000000000..45005621b --- /dev/null +++ b/tests/fixtures/sort/keys_closed_range.expected @@ -0,0 +1,6 @@ +dd aa ff +gg aa cc +aa bb cc +èè éé èè +👩‍🔬 👩‍🔬 👩‍🔬 +💣💣 💣💣 💣💣 diff --git a/tests/fixtures/sort/keys_closed_range.expected.debug b/tests/fixtures/sort/keys_closed_range.expected.debug new file mode 100644 index 000000000..b78db4af1 --- /dev/null +++ b/tests/fixtures/sort/keys_closed_range.expected.debug @@ -0,0 +1,18 @@ +dd aa ff + _ +________ +gg aa cc + _ +________ +aa bb cc + _ +________ +èè éé èè + _ +________ +👩‍🔬 👩‍🔬 👩‍🔬 + __ +______________ +💣💣 💣💣 💣💣 + __ +______________ diff --git a/tests/fixtures/sort/keys_closed_range.txt b/tests/fixtures/sort/keys_closed_range.txt new file mode 100644 index 000000000..d6bf40785 --- /dev/null +++ b/tests/fixtures/sort/keys_closed_range.txt @@ -0,0 +1,6 @@ +aa bb cc +dd aa ff +gg aa cc +èè éé èè +💣💣 💣💣 💣💣 +👩‍🔬 👩‍🔬 👩‍🔬 \ No newline at end of file diff --git a/tests/fixtures/sort/keys_custom_separator.expected b/tests/fixtures/sort/keys_custom_separator.expected new file mode 100644 index 000000000..2aba42033 --- /dev/null +++ b/tests/fixtures/sort/keys_custom_separator.expected @@ -0,0 +1,3 @@ +ddxaaxff +ggxaaxcc +aaxbbxcc diff --git a/tests/fixtures/sort/keys_custom_separator.expected.debug b/tests/fixtures/sort/keys_custom_separator.expected.debug new file mode 100644 index 000000000..5d4dbc776 --- /dev/null +++ b/tests/fixtures/sort/keys_custom_separator.expected.debug @@ -0,0 +1,9 @@ +ddxaaxff + _ +________ +ggxaaxcc + _ +________ +aaxbbxcc + _ +________ diff --git a/tests/fixtures/sort/keys_custom_separator.txt b/tests/fixtures/sort/keys_custom_separator.txt new file mode 100644 index 000000000..a8f473061 --- /dev/null +++ b/tests/fixtures/sort/keys_custom_separator.txt @@ -0,0 +1,3 @@ +aaxbbxcc +ddxaaxff +ggxaaxcc diff --git a/tests/fixtures/sort/keys_multiple_ranges.expected b/tests/fixtures/sort/keys_multiple_ranges.expected new file mode 100644 index 000000000..09e4e8729 --- /dev/null +++ b/tests/fixtures/sort/keys_multiple_ranges.expected @@ -0,0 +1,6 @@ +gg aa cc +dd aa ff +aa bb cc +èè éé èè +👩‍🔬 👩‍🔬 👩‍🔬 +💣💣 💣💣 💣💣 diff --git a/tests/fixtures/sort/keys_multiple_ranges.expected.debug b/tests/fixtures/sort/keys_multiple_ranges.expected.debug new file mode 100644 index 000000000..830e9afd0 --- /dev/null +++ b/tests/fixtures/sort/keys_multiple_ranges.expected.debug @@ -0,0 +1,24 @@ +gg aa cc + ___ + ___ +________ +dd aa ff + ___ + ___ +________ +aa bb cc + ___ + ___ +________ +èè éé èè + ___ + ___ +________ +👩‍🔬 👩‍🔬 👩‍🔬 + _____ + _____ +______________ +💣💣 💣💣 💣💣 + _____ + _____ +______________ diff --git a/tests/fixtures/sort/keys_multiple_ranges.txt b/tests/fixtures/sort/keys_multiple_ranges.txt new file mode 100644 index 000000000..d6bf40785 --- /dev/null +++ b/tests/fixtures/sort/keys_multiple_ranges.txt @@ -0,0 +1,6 @@ +aa bb cc +dd aa ff +gg aa cc +èè éé èè +💣💣 💣💣 💣💣 +👩‍🔬 👩‍🔬 👩‍🔬 \ No newline at end of file diff --git a/tests/fixtures/sort/keys_no_char_match.expected b/tests/fixtures/sort/keys_no_char_match.expected new file mode 100644 index 000000000..dcb361837 --- /dev/null +++ b/tests/fixtures/sort/keys_no_char_match.expected @@ -0,0 +1,3 @@ +c +ba +aaa diff --git a/tests/fixtures/sort/keys_no_char_match.expected.debug b/tests/fixtures/sort/keys_no_char_match.expected.debug new file mode 100644 index 000000000..5287a0de9 --- /dev/null +++ b/tests/fixtures/sort/keys_no_char_match.expected.debug @@ -0,0 +1,9 @@ +c + ^ no match for key +_ +ba + _ +__ +aaa + __ +___ diff --git a/tests/fixtures/sort/keys_no_char_match.txt b/tests/fixtures/sort/keys_no_char_match.txt new file mode 100644 index 000000000..1c952a6b8 --- /dev/null +++ b/tests/fixtures/sort/keys_no_char_match.txt @@ -0,0 +1,3 @@ +aaa +ba +c diff --git a/tests/fixtures/sort/keys_no_field_match.expected b/tests/fixtures/sort/keys_no_field_match.expected new file mode 100644 index 000000000..e2f183e13 --- /dev/null +++ b/tests/fixtures/sort/keys_no_field_match.expected @@ -0,0 +1,6 @@ +aa bb cc +dd aa ff +gg aa cc +èè éé èè +👩‍🔬 👩‍🔬 👩‍🔬 +💣💣 💣💣 💣💣 diff --git a/tests/fixtures/sort/keys_no_field_match.expected.debug b/tests/fixtures/sort/keys_no_field_match.expected.debug new file mode 100644 index 000000000..60197b1de --- /dev/null +++ b/tests/fixtures/sort/keys_no_field_match.expected.debug @@ -0,0 +1,18 @@ +aa bb cc + ^ no match for key +________ +dd aa ff + ^ no match for key +________ +gg aa cc + ^ no match for key +________ +èè éé èè + ^ no match for key +________ +👩‍🔬 👩‍🔬 👩‍🔬 + ^ no match for key +______________ +💣💣 💣💣 💣💣 + ^ no match for key +______________ diff --git a/tests/fixtures/sort/keys_no_field_match.txt b/tests/fixtures/sort/keys_no_field_match.txt new file mode 100644 index 000000000..d6bf40785 --- /dev/null +++ b/tests/fixtures/sort/keys_no_field_match.txt @@ -0,0 +1,6 @@ +aa bb cc +dd aa ff +gg aa cc +èè éé èè +💣💣 💣💣 💣💣 +👩‍🔬 👩‍🔬 👩‍🔬 \ No newline at end of file diff --git a/tests/fixtures/sort/keys_open_ended.expected b/tests/fixtures/sort/keys_open_ended.expected new file mode 100644 index 000000000..09e4e8729 --- /dev/null +++ b/tests/fixtures/sort/keys_open_ended.expected @@ -0,0 +1,6 @@ +gg aa cc +dd aa ff +aa bb cc +èè éé èè +👩‍🔬 👩‍🔬 👩‍🔬 +💣💣 💣💣 💣💣 diff --git a/tests/fixtures/sort/keys_open_ended.expected.debug b/tests/fixtures/sort/keys_open_ended.expected.debug new file mode 100644 index 000000000..d3a56ffd6 --- /dev/null +++ b/tests/fixtures/sort/keys_open_ended.expected.debug @@ -0,0 +1,18 @@ +gg aa cc + ____ +________ +dd aa ff + ____ +________ +aa bb cc + ____ +________ +èè éé èè + ____ +________ +👩‍🔬 👩‍🔬 👩‍🔬 + _______ +______________ +💣💣 💣💣 💣💣 + _______ +______________ diff --git a/tests/fixtures/sort/keys_open_ended.txt b/tests/fixtures/sort/keys_open_ended.txt new file mode 100644 index 000000000..d6bf40785 --- /dev/null +++ b/tests/fixtures/sort/keys_open_ended.txt @@ -0,0 +1,6 @@ +aa bb cc +dd aa ff +gg aa cc +èè éé èè +💣💣 💣💣 💣💣 +👩‍🔬 👩‍🔬 👩‍🔬 \ No newline at end of file diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug new file mode 100644 index 000000000..dbe295a1c --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug @@ -0,0 +1,90 @@ +-2028789030 +___________ +___________ +-896689 +_______ +_______ +-8.90880 +________ +________ +-1 +__ +__ +-.05 +____ +____ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +000 +___ +___ +CARAvan +^ no match for key +_______ +00000001 +________ +________ +1 +_ +_ +1.040000000 +___________ +___________ +1.444 +_____ +_____ +1.58590 +_______ +_______ +8.013 +_____ +_____ +45 +__ +__ +46.89 +_____ +_____ + 4567. + _____ +____________________ +>>>>37800 + _____ +_________ +576,446.88800000 +________________ +________________ +576,446.890 +___________ +___________ +4798908.340000000000 +____________________ +____________________ +4798908.45 +__________ +__________ +4798908.8909800 +_______________ +_______________ diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug new file mode 100644 index 000000000..b2782d93d --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug @@ -0,0 +1,60 @@ +-2028789030 +___________ +-896689 +_______ +-8.90880 +________ +-1 +__ +-.05 +____ + +^ no match for key + +^ no match for key + +^ no match for key + +^ no match for key + +^ no match for key +CARAvan +^ no match for key + +^ no match for key + +^ no match for key + +^ no match for key +000 +___ +1 +_ +00000001 +________ +1.040000000 +___________ +1.444 +_____ +1.58590 +_______ +8.013 +_____ +45 +__ +46.89 +_____ + 4567. + _____ +>>>>37800 + _____ +576,446.88800000 +________________ +576,446.890 +___________ +4798908.340000000000 +____________________ +4798908.45 +__________ +4798908.8909800 +_______________ diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug new file mode 100644 index 000000000..782a77724 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug @@ -0,0 +1,40 @@ +-2028789030 +___________ +-896689 +_______ +-8.90880 +________ +-1 +__ +-.05 +____ + +^ no match for key +1 +_ +1.040000000 +___________ +1.444 +_____ +1.58590 +_______ +8.013 +_____ +45 +__ +46.89 +_____ + 4567. + _____ +>>>>37800 + _____ +576,446.88800000 +________________ +576,446.890 +___________ +4798908.340000000000 +____________________ +4798908.45 +__________ +4798908.8909800 +_______________ diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug new file mode 100644 index 000000000..e0389c1d5 --- /dev/null +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug @@ -0,0 +1,40 @@ +4798908.8909800 +_______________ +4798908.45 +__________ +4798908.340000000000 +____________________ +576,446.890 +___________ +576,446.88800000 +________________ +>>>>37800 + _____ + 4567. + _____ +46.89 +_____ +45 +__ +8.013 +_____ +1.58590 +_______ +1.444 +_____ +1.040000000 +___________ +1 +_ + +^ no match for key +-.05 +____ +-1 +__ +-8.90880 +________ +-896689 +_______ +-2028789030 +___________ diff --git a/tests/fixtures/sort/month_default.expected.debug b/tests/fixtures/sort/month_default.expected.debug new file mode 100644 index 000000000..2c55a0e2a --- /dev/null +++ b/tests/fixtures/sort/month_default.expected.debug @@ -0,0 +1,30 @@ +N/A Ut enim ad minim veniam, quis +^ no match for key +_________________________________ +Jan Lorem ipsum dolor sit amet +___ +______________________________ +mar laboris nisi ut aliquip ex ea +___ +_________________________________ +May sed do eiusmod tempor incididunt +___ +____________________________________ +JUN nostrud exercitation ullamco +___ +________________________________ +Jul 1 should remain 2,1,3 +___ +_________________________ +Jul 2 these three lines +___ +_______________________ +Jul 3 if --stable is provided +___ +_____________________________ +Oct ut labore et dolore magna aliqua +___ +____________________________________ +Dec consectetur adipiscing elit +___ +_______________________________ diff --git a/tests/fixtures/sort/month_stable.expected.debug b/tests/fixtures/sort/month_stable.expected.debug new file mode 100644 index 000000000..4163ba39a --- /dev/null +++ b/tests/fixtures/sort/month_stable.expected.debug @@ -0,0 +1,20 @@ +N/A Ut enim ad minim veniam, quis +^ no match for key +Jan Lorem ipsum dolor sit amet +___ +mar laboris nisi ut aliquip ex ea +___ +May sed do eiusmod tempor incididunt +___ +JUN nostrud exercitation ullamco +___ +Jul 2 these three lines +___ +Jul 1 should remain 2,1,3 +___ +Jul 3 if --stable is provided +___ +Oct ut labore et dolore magna aliqua +___ +Dec consectetur adipiscing elit +___ diff --git a/tests/fixtures/sort/months-dedup.expected.debug b/tests/fixtures/sort/months-dedup.expected.debug new file mode 100644 index 000000000..aded4b951 --- /dev/null +++ b/tests/fixtures/sort/months-dedup.expected.debug @@ -0,0 +1,12 @@ + +^ no match for key +JAN +___ +apr +___ +MAY +___ +JUNNNN +___ +AUG +___ diff --git a/tests/fixtures/sort/months-whitespace.expected.debug b/tests/fixtures/sort/months-whitespace.expected.debug new file mode 100644 index 000000000..ef626f505 --- /dev/null +++ b/tests/fixtures/sort/months-whitespace.expected.debug @@ -0,0 +1,24 @@ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +JAN +___ +___ + FEb + ___ +_____ + apr + ___ +____ + apr + ___ +____ +>>>JUNNNN + ___ +_________ +AUG +___ +____ diff --git a/tests/fixtures/sort/multiple_decimals_general.expected b/tests/fixtures/sort/multiple_decimals_general.expected new file mode 100644 index 000000000..b08ada324 --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_general.expected @@ -0,0 +1,37 @@ + + + + + + + +CARAvan + NaN + -inf +-2028789030 +-896689 +-8.90880 +-1 +-.05 +000 +00000001 +1 +1.040000000 +1.444 +1.58590 +8.013 +45 +46.89 +576,446.88800000 +576,446.890 + 4567..457 + 4567. +4567.1 +4567.34 + 37800 + 45670.89079.098 + 45670.89079.1 +4798908.340000000000 +4798908.45 +4798908.8909800 +inf diff --git a/tests/fixtures/sort/multiple_decimals_general.expected.debug b/tests/fixtures/sort/multiple_decimals_general.expected.debug new file mode 100644 index 000000000..1bf5d2669 --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_general.expected.debug @@ -0,0 +1,111 @@ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +CARAvan +^ no match for key +_______ + NaN + ___ +_____ +>-inf + ____ +_____ +-2028789030 +___________ +___________ +-896689 +_______ +_______ +-8.90880 +________ +________ +-1 +__ +__ +-.05 +____ +____ +000 +___ +___ +00000001 +________ +________ +1 +_ +_ +1.040000000 +___________ +___________ +1.444 +_____ +_____ +1.58590 +_______ +_______ +8.013 +_____ +_____ +45 +__ +__ +46.89 +_____ +_____ +576,446.88800000 +___ +________________ +576,446.890 +___ +___________ +>>>>>>>>>>4567..457 + _____ +___________________ + 4567. + _____ +____________________ +4567.1 +______ +______ +4567.34 +_______ +_______ +>>>>37800 + _____ +_________ +>>>>>>45670.89079.098 + ___________ +_____________________ +>>>>>>45670.89079.1 + ___________ +___________________ +4798908.340000000000 +____________________ +____________________ +4798908.45 +__________ +__________ +4798908.8909800 +_______________ +_______________ +inf +___ +___ diff --git a/tests/fixtures/sort/multiple_decimals_general.txt b/tests/fixtures/sort/multiple_decimals_general.txt index 4e65ecfda..0feb0ce7f 100644 --- a/tests/fixtures/sort/multiple_decimals_general.txt +++ b/tests/fixtures/sort/multiple_decimals_general.txt @@ -32,4 +32,6 @@ CARAvan 8.013 000 - + NaN +inf + -inf diff --git a/tests/fixtures/sort/multiple_decimals_numeric.expected.debug b/tests/fixtures/sort/multiple_decimals_numeric.expected.debug new file mode 100644 index 000000000..f40ade9aa --- /dev/null +++ b/tests/fixtures/sort/multiple_decimals_numeric.expected.debug @@ -0,0 +1,105 @@ +-2028789030 +___________ +___________ +-896689 +_______ +_______ +-8.90880 +________ +________ +-1 +__ +__ +-.05 +____ +____ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +000 +___ +___ +CARAvan +^ no match for key +_______ +00000001 +________ +________ +1 +_ +_ +1.040000000 +___________ +___________ +1.444 +_____ +_____ +1.58590 +_______ +_______ +8.013 +_____ +_____ +45 +__ +__ +46.89 +_____ +_____ +>>>>>>>>>>4567..457 + _____ +___________________ + 4567. + _____ +____________________ +4567.1 +______ +______ +4567.34 +_______ +_______ +>>>>37800 + _____ +_________ +>>>>>>45670.89079.098 + ___________ +_____________________ +>>>>>>45670.89079.1 + ___________ +___________________ +576,446.88800000 +________________ +________________ +576,446.890 +___________ +___________ +4798908.340000000000 +____________________ +____________________ +4798908.45 +__________ +__________ +4798908.8909800 +_______________ +_______________ diff --git a/tests/fixtures/sort/numeric-floats-with-nan2.expected.debug b/tests/fixtures/sort/numeric-floats-with-nan2.expected.debug new file mode 100644 index 000000000..b5a2c2f64 --- /dev/null +++ b/tests/fixtures/sort/numeric-floats-with-nan2.expected.debug @@ -0,0 +1,69 @@ +-8.90880 +________ +________ +-.05 +____ +____ + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key + +^ no match for key +^ no match for key +Karma +^ no match for key +_____ +1 +_ +_ +1.0/0.0 +___ +_______ +1.040000000 +___________ +___________ +1.2 +___ +___ +1.444 +_____ +_____ +1.58590 +_______ +_______ diff --git a/tests/fixtures/sort/numeric_fixed_floats.expected.debug b/tests/fixtures/sort/numeric_fixed_floats.expected.debug new file mode 100644 index 000000000..fa8a909c5 --- /dev/null +++ b/tests/fixtures/sort/numeric_fixed_floats.expected.debug @@ -0,0 +1,6 @@ +.00 +___ +___ +.01 +___ +___ diff --git a/tests/fixtures/sort/numeric_floats.expected.debug b/tests/fixtures/sort/numeric_floats.expected.debug new file mode 100644 index 000000000..e24056376 --- /dev/null +++ b/tests/fixtures/sort/numeric_floats.expected.debug @@ -0,0 +1,6 @@ +.02 +___ +___ +.03 +___ +___ diff --git a/tests/fixtures/sort/numeric_floats_and_ints.expected.debug b/tests/fixtures/sort/numeric_floats_and_ints.expected.debug new file mode 100644 index 000000000..c43d6bfb6 --- /dev/null +++ b/tests/fixtures/sort/numeric_floats_and_ints.expected.debug @@ -0,0 +1,6 @@ +0 +_ +_ +.02 +___ +___ diff --git a/tests/fixtures/sort/numeric_floats_with_nan.expected.debug b/tests/fixtures/sort/numeric_floats_with_nan.expected.debug new file mode 100644 index 000000000..07e72db53 --- /dev/null +++ b/tests/fixtures/sort/numeric_floats_with_nan.expected.debug @@ -0,0 +1,9 @@ +NaN +^ no match for key +___ +.02 +___ +___ +.03 +___ +___ diff --git a/tests/fixtures/sort/numeric_unfixed_floats.expected.debug b/tests/fixtures/sort/numeric_unfixed_floats.expected.debug new file mode 100644 index 000000000..a60daf623 --- /dev/null +++ b/tests/fixtures/sort/numeric_unfixed_floats.expected.debug @@ -0,0 +1,6 @@ +.000 +____ +____ +.01 +___ +___ diff --git a/tests/fixtures/sort/numeric_unique.expected.debug b/tests/fixtures/sort/numeric_unique.expected.debug new file mode 100644 index 000000000..79ec70364 --- /dev/null +++ b/tests/fixtures/sort/numeric_unique.expected.debug @@ -0,0 +1,4 @@ +-10 bb +___ +aa +^ no match for key diff --git a/tests/fixtures/sort/numeric_unsorted_ints.expected.debug b/tests/fixtures/sort/numeric_unsorted_ints.expected.debug new file mode 100644 index 000000000..b16931f5e --- /dev/null +++ b/tests/fixtures/sort/numeric_unsorted_ints.expected.debug @@ -0,0 +1,300 @@ +1 +_ +_ +2 +_ +_ +3 +_ +_ +4 +_ +_ +5 +_ +_ +6 +_ +_ +7 +_ +_ +8 +_ +_ +9 +_ +_ +10 +__ +__ +11 +__ +__ +12 +__ +__ +13 +__ +__ +14 +__ +__ +15 +__ +__ +16 +__ +__ +17 +__ +__ +18 +__ +__ +19 +__ +__ +20 +__ +__ +21 +__ +__ +22 +__ +__ +23 +__ +__ +24 +__ +__ +25 +__ +__ +26 +__ +__ +27 +__ +__ +28 +__ +__ +29 +__ +__ +30 +__ +__ +31 +__ +__ +32 +__ +__ +33 +__ +__ +34 +__ +__ +35 +__ +__ +36 +__ +__ +37 +__ +__ +38 +__ +__ +39 +__ +__ +40 +__ +__ +41 +__ +__ +42 +__ +__ +43 +__ +__ +44 +__ +__ +45 +__ +__ +46 +__ +__ +47 +__ +__ +48 +__ +__ +49 +__ +__ +50 +__ +__ +51 +__ +__ +52 +__ +__ +53 +__ +__ +54 +__ +__ +55 +__ +__ +56 +__ +__ +57 +__ +__ +58 +__ +__ +59 +__ +__ +60 +__ +__ +61 +__ +__ +62 +__ +__ +63 +__ +__ +64 +__ +__ +65 +__ +__ +66 +__ +__ +67 +__ +__ +68 +__ +__ +69 +__ +__ +70 +__ +__ +71 +__ +__ +72 +__ +__ +73 +__ +__ +74 +__ +__ +75 +__ +__ +76 +__ +__ +77 +__ +__ +78 +__ +__ +79 +__ +__ +80 +__ +__ +81 +__ +__ +82 +__ +__ +83 +__ +__ +84 +__ +__ +85 +__ +__ +86 +__ +__ +87 +__ +__ +88 +__ +__ +89 +__ +__ +90 +__ +__ +91 +__ +__ +92 +__ +__ +93 +__ +__ +94 +__ +__ +95 +__ +__ +96 +__ +__ +97 +__ +__ +98 +__ +__ +99 +__ +__ +100 +___ +___ diff --git a/tests/fixtures/sort/numeric_unsorted_ints_unique.expected.debug b/tests/fixtures/sort/numeric_unsorted_ints_unique.expected.debug new file mode 100644 index 000000000..072a57ccf --- /dev/null +++ b/tests/fixtures/sort/numeric_unsorted_ints_unique.expected.debug @@ -0,0 +1,8 @@ +1 +_ +2 +_ +3 +_ +4 +_ diff --git a/tests/fixtures/sort/version.expected.debug b/tests/fixtures/sort/version.expected.debug new file mode 100644 index 000000000..2d922b5c0 --- /dev/null +++ b/tests/fixtures/sort/version.expected.debug @@ -0,0 +1,12 @@ +1.2.3-alpha +___________ +___________ +1.2.3-alpha2 +____________ +____________ +1.12.4 +______ +______ +11.2.3 +______ +______ diff --git a/tests/fixtures/sort/words_unique.expected.debug b/tests/fixtures/sort/words_unique.expected.debug new file mode 100644 index 000000000..0c32daf74 --- /dev/null +++ b/tests/fixtures/sort/words_unique.expected.debug @@ -0,0 +1,6 @@ +aaa +___ +bbb +___ +zzz +___ diff --git a/tests/fixtures/sort/zero-terminated.expected.debug b/tests/fixtures/sort/zero-terminated.expected.debug new file mode 100644 index 000000000..fbef272b0 --- /dev/null +++ b/tests/fixtures/sort/zero-terminated.expected.debug @@ -0,0 +1,84 @@ +../.. +_____ +../../by-util +_____________ +../../common +____________ +../../fixtures +______________ +../../fixtures/cat +__________________ +../../fixtures/cksum +____________________ +../../fixtures/comm +___________________ +../../fixtures/cp +_________________ +../../fixtures/cp/dir_with_mount +________________________________ +../../fixtures/cp/dir_with_mount/copy_me +________________________________________ +../../fixtures/cp/hello_dir +___________________________ +../../fixtures/cp/hello_dir_with_file +_____________________________________ +../../fixtures/csplit +_____________________ +../../fixtures/cut +__________________ +../../fixtures/cut/sequences +____________________________ +../../fixtures/dircolors +________________________ +../../fixtures/du +_________________ +../../fixtures/du/subdir +________________________ +../../fixtures/du/subdir/deeper +_______________________________ +../../fixtures/du/subdir/links +______________________________ +../../fixtures/env +__________________ +../../fixtures/expand +_____________________ +../../fixtures/fmt +__________________ +../../fixtures/fold +___________________ +../../fixtures/hashsum +______________________ +../../fixtures/head +___________________ +../../fixtures/join +___________________ +../../fixtures/mv +_________________ +../../fixtures/nl +_________________ +../../fixtures/numfmt +_____________________ +../../fixtures/od +_________________ +../../fixtures/paste +____________________ +../../fixtures/ptx +__________________ +../../fixtures/shuf +___________________ +../../fixtures/sort +___________________ +../../fixtures/sum +__________________ +../../fixtures/tac +__________________ +../../fixtures/tail +___________________ +../../fixtures/tsort +____________________ +../../fixtures/unexpand +_______________________ +../../fixtures/uniq +___________________ +../../fixtures/wc +_________________ From 9f79db287bb6323de4b0eec7d670ffb6aa9da674 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 24 Apr 2021 09:55:24 +0200 Subject: [PATCH 120/399] Update src/uu/ls/src/ls.rs Co-authored-by: Sylvestre Ledru --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 19097edda..11d8b261f 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1185,7 +1185,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { fn is_hidden(file_path: &DirEntry) -> bool { let metadata = fs::metadata(file_path.path()).unwrap(); let attr = metadata.file_attributes(); - ((attr & 0x2) > 0) + (attr & 0x2) > 0 } fn should_display(entry: &DirEntry, config: &Config) -> bool { From 728f0bd61d792a6263375b809567d92f50e291ce Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 24 Apr 2021 10:47:36 +0200 Subject: [PATCH 121/399] ls: remove redundant parentheses --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 1129d55c4..31e3de31c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1161,7 +1161,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { fn is_hidden(file_path: &DirEntry) -> bool { let metadata = fs::metadata(file_path.path()).unwrap(); let attr = metadata.file_attributes(); - ((attr & 0x2) > 0) + (attr & 0x2) > 0 } fn should_display(entry: &DirEntry, config: &Config) -> bool { From 5dcfb5111027dad087639e2f9003d1b9c7bd3fc8 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 24 Apr 2021 10:52:40 +0200 Subject: [PATCH 122/399] flip default for debug to the effective default --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index d9a639b3c..0630ec97a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -121,7 +121,7 @@ impl Default for GlobalSettings { fn default() -> GlobalSettings { GlobalSettings { mode: SortMode::Default, - debug: true, + debug: false, ignore_blanks: false, ignore_case: false, dictionary_order: false, From 5f61848a3812d18bae3d9dbfa672c11f43b1d526 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 Apr 2021 12:45:55 +0200 Subject: [PATCH 123/399] fix a build failure with success() --- tests/by-util/test_cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index bf5f9919d..53c16e677 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -976,7 +976,7 @@ fn test_cp_reflink_always() { .arg(TEST_EXISTING_FILE) .run(); - if result.success { + if result.succeeded() { // Check the content of the destination file assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); } else { @@ -1014,7 +1014,7 @@ fn test_cp_reflink_never() { #[cfg(target_os = "linux")] fn test_cp_reflink_bad() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd + let _result = ucmd .arg("--reflink=bad") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_EXISTING_FILE) From 3ac481e4d39717bfad5b57ca7df36ae5c55a593b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 Apr 2021 12:46:06 +0200 Subject: [PATCH 124/399] rustfmt the recent change --- tests/by-util/test_cat.rs | 2 +- tests/by-util/test_sort.rs | 10 +++++----- tests/by-util/test_tail.rs | 6 +----- tests/by-util/test_truncate.rs | 1 - 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 9f7ebdd37..c8ae29a9d 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -397,10 +397,10 @@ fn test_dev_full_show_all() { #[cfg(unix)] fn test_domain_socket() { use std::io::prelude::*; + use std::sync::{Arc, Barrier}; 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"); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 72d4f67fc..9825f1eb5 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -585,8 +585,8 @@ fn test_check_silent() { #[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 + .args(&["-t", "x", "-k", "1,1"]) + .pipe_in("aax\naaa\n") + .succeeds() + .stdout_is("aax\naaa\n"); +} diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 1f74a3a98..1c025cf4c 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -346,9 +346,5 @@ fn test_negative_indexing() { #[test] fn test_sleep_interval() { - new_ucmd!() - .arg("-s") - .arg("10") - .arg(FOOBAR_TXT) - .succeeds(); + new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds(); } diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 2a1f4429b..64573f2c0 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -79,4 +79,3 @@ fn test_failed_incorrect_arg() { let (_at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-s", "+5A", TFILE1]).fails(); } - From 99840b909933553dd110efbd6802a821eb26ff0a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 24 Apr 2021 13:15:59 +0200 Subject: [PATCH 125/399] refresh cargo.lock with recent updates --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ae0d078f..2ac0de37a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,9 +1212,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" dependencies = [ "aho-corasick", "memchr 2.3.4", From 1328d1887884eabb45fce7612f08f0fac817ae46 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 24 Apr 2021 13:19:50 +0200 Subject: [PATCH 126/399] ls: remove outdated comment --- src/uu/ls/src/ls.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 31e3de31c..2baf93193 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1606,7 +1606,6 @@ fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> if config.format == Format::Long && path.file_type()?.is_symlink() { if let Ok(target) = path.p_buf.read_link() { - // We don't bother updating width here because it's not used for long listings name.push_str(" -> "); name.push_str(&target.to_string_lossy()); } From e9e3d4100886943722a3bbebe3519bb881f9d0d3 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sat, 24 Apr 2021 15:25:14 +0100 Subject: [PATCH 127/399] Expand tests for truncate --- tests/by-util/test_truncate.rs | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 64573f2c0..b033385de 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -79,3 +79,63 @@ fn test_failed_incorrect_arg() { let (_at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-s", "+5A", TFILE1]).fails(); } + +#[test] +fn test_at_most_shrinks() { + 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_at_most_no_change() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut file = at.make_file(TFILE2); + file.write_all(b"1234567890").unwrap(); + ucmd.args(&["--size", "<40", TFILE2]).succeeds(); + file.seek(SeekFrom::End(0)).unwrap(); + assert!(file.seek(SeekFrom::Current(0)).unwrap() == 10); +} + +#[test] +fn test_at_least_grows() { + let (at, mut ucmd) = at_and_ucmd!(); + let mut file = at.make_file(TFILE2); + file.write_all(b"1234567890").unwrap(); + ucmd.args(&["--size", ">15", TFILE2]).succeeds(); + file.seek(SeekFrom::End(0)).unwrap(); + assert!(file.seek(SeekFrom::Current(0)).unwrap() == 15); +} + +#[test] +fn test_at_least_no_change() { + 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() == 10); +} + +#[test] +fn test_round_down() { + 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() == 8); +} + +#[test] +fn test_round_up() { + 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() == 12); +} From 2084c3ddf373261ab5d40999c33351eff6e905fe Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 24 Apr 2021 16:43:13 +0200 Subject: [PATCH 128/399] tests: show pretty diffs for assertion failures - All assert_eq in tests/common/util.rs now print a pretty diff on test failures. - {stdout, stderr}_is_fixture now compare the expected output and the fixture as Strings, which leads to more usable diffs. --- Cargo.lock | 38 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + tests/common/util.rs | 7 ++++--- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ac0de37a..1651259ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,6 +221,7 @@ dependencies = [ "lazy_static", "libc", "nix 0.20.0", + "pretty_assertions", "rand 0.7.3", "regex", "sha1", @@ -526,6 +527,16 @@ dependencies = [ "memchr 2.3.4", ] +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote 1.0.9", + "syn", +] + [[package]] name = "custom_derive" version = "0.1.7" @@ -538,6 +549,12 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + [[package]] name = "digest" version = "0.6.2" @@ -943,6 +960,15 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "paste" version = "0.1.18" @@ -1012,6 +1038,18 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "pretty_assertions" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +dependencies = [ + "ansi_term 0.12.1", + "ctor", + "diff", + "output_vt100", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" diff --git a/Cargo.toml b/Cargo.toml index 7e3fb9139..7c1a771fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -335,6 +335,7 @@ filetime = "0.2" glob = "0.3.0" libc = "0.2" nix = "0.20.0" +pretty_assertions = "0.7.2" rand = "0.7" regex = "1.0" sha1 = { version="0.6", features=["std"] } diff --git a/tests/common/util.rs b/tests/common/util.rs index 29b8a4633..93bbccc24 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -2,6 +2,7 @@ #[cfg(not(windows))] use libc; +use pretty_assertions::assert_eq; use std::env; #[cfg(not(windows))] use std::ffi::CString; @@ -221,7 +222,7 @@ impl CmdResult { /// like stdout_is(...), but expects the contents of the file at the provided relative path pub fn stdout_is_fixture>(&self, file_rel_path: T) -> &CmdResult { let contents = read_scenario_fixture(&self.tmpd, file_rel_path); - self.stdout_is_bytes(contents) + self.stdout_is(String::from_utf8(contents).unwrap()) } /// asserts that the command resulted in stderr stream output that equals the @@ -245,7 +246,7 @@ impl CmdResult { /// Like stdout_is_fixture, but for stderr pub fn stderr_is_fixture>(&self, file_rel_path: T) -> &CmdResult { let contents = read_scenario_fixture(&self.tmpd, file_rel_path); - self.stderr_is_bytes(contents) + self.stderr_is(String::from_utf8(contents).unwrap()) } /// asserts that @@ -619,7 +620,7 @@ impl TestScenario { }, util_name: String::from(util_name), fixtures: AtPath::new(tmpd.as_ref().path()), - tmpd: tmpd, + tmpd, }; let mut fixture_path_builder = env::current_dir().unwrap(); fixture_path_builder.push(TESTS_DIR); From 2c1459cbfc4c060ffdbf0bd8c21ecd63504424e9 Mon Sep 17 00:00:00 2001 From: Chirag Jadwani Date: Sat, 24 Apr 2021 21:34:42 +0530 Subject: [PATCH 129/399] cut: optimizations * Use buffered stdout to reduce write sys calls. This simple change yielded the biggest performace gain. * Use `for_byte_record_with_terminator` from the `bstr` crate. This is to minimize the per line copying needed by `BufReader::read_until`. The `cut_fields` and `cut_fields_delimiter` functions used `read_until` to iterate over lines. That required copying each input line to the line buffer. With `for_byte_record_with_terminator` copying is minimized as it calls our closure with a reference to BufReader's buffer most of the time. It needs to copy (internally) only to process any incomplete lines at the end of the buffer. * Re-write `Searcher` to use `memchr`. Switch from the naive implementation to one that uses `memchr`. * Rewrite `cut_bytes` almost entirely. This was already well optimized. The performance gain in this case is not from avoiding copying. In fact, it needed zero copying whereas new implementation introduces some copying similar to `cut_fields` described above. But the occassional copying cost is more than offset by the use of the very fast `memchr` inside `for_byte_record_with_terminator`. This change also simplifies the code significantly. Removed the `buffer` module. --- Cargo.lock | 2 + src/uu/cut/Cargo.toml | 2 + src/uu/cut/src/buffer.rs | 152 --------------------------- src/uu/cut/src/cut.rs | 208 ++++++++++++++----------------------- src/uu/cut/src/searcher.rs | 95 +++++++++++++---- 5 files changed, 157 insertions(+), 302 deletions(-) delete mode 100644 src/uu/cut/src/buffer.rs diff --git a/Cargo.lock b/Cargo.lock index 321f41d89..9df4994c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1777,7 +1777,9 @@ dependencies = [ name = "uu_cut" version = "0.0.6" dependencies = [ + "bstr", "clap", + "memchr 2.3.4", "uucore", "uucore_procs", ] diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index d892ddeb5..c863c1772 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -18,6 +18,8 @@ path = "src/cut.rs" clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +memchr = "2" +bstr = "0.2" [[bin]] name = "cut" diff --git a/src/uu/cut/src/buffer.rs b/src/uu/cut/src/buffer.rs deleted file mode 100644 index 6c3238be1..000000000 --- a/src/uu/cut/src/buffer.rs +++ /dev/null @@ -1,152 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// (c) Rolf Morel -// (c) kwantam -// * substantial rewrite to use the `std::io::BufReader` trait -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker:ignore (ToDO) SRes Newl - -use std::io::Result as IoResult; -use std::io::{BufRead, BufReader, Read, Write}; - -#[allow(non_snake_case)] -pub mod Bytes { - use std::io::Write; - - pub trait Select { - fn select(&mut self, bytes: usize, out: Option<&mut W>) -> Selected; - } - - #[derive(PartialEq, Eq, Debug)] - pub enum Selected { - NewlineFound, - Complete(usize), - Partial(usize), - EndOfFile, - } -} - -#[derive(Debug)] -pub struct ByteReader -where - R: Read, -{ - inner: BufReader, - newline_char: u8, -} - -impl ByteReader { - pub fn new(read: R, newline_char: u8) -> ByteReader { - ByteReader { - inner: BufReader::with_capacity(4096, read), - newline_char, - } - } -} - -impl Read for ByteReader { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - self.inner.read(buf) - } -} - -impl BufRead for ByteReader { - fn fill_buf(&mut self) -> IoResult<&[u8]> { - self.inner.fill_buf() - } - - fn consume(&mut self, amt: usize) { - self.inner.consume(amt) - } -} - -impl ByteReader { - pub fn consume_line(&mut self) -> usize { - let mut bytes_consumed = 0; - let mut consume_val; - let newline_char = self.newline_char; - - loop { - { - // need filled_buf to go out of scope - let filled_buf = match self.fill_buf() { - Ok(b) => { - if b.is_empty() { - return bytes_consumed; - } else { - b - } - } - Err(e) => crash!(1, "read error: {}", e), - }; - - if let Some(idx) = filled_buf.iter().position(|byte| *byte == newline_char) { - consume_val = idx + 1; - bytes_consumed += consume_val; - break; - } - - consume_val = filled_buf.len(); - } - - bytes_consumed += consume_val; - self.consume(consume_val); - } - - self.consume(consume_val); - bytes_consumed - } -} - -impl self::Bytes::Select for ByteReader { - fn select(&mut self, bytes: usize, out: Option<&mut W>) -> Bytes::Selected { - enum SRes { - Comp, - Part, - Newl, - } - - use self::Bytes::Selected::*; - - let newline_char = self.newline_char; - let (res, consume_val) = { - let buffer = match self.fill_buf() { - Err(e) => crash!(1, "read error: {}", e), - Ok(b) => b, - }; - - let (res, consume_val) = match buffer.len() { - 0 => return EndOfFile, - buf_used if bytes < buf_used => { - // because the output delimiter should only be placed between - // segments check if the byte after bytes is a newline - let buf_slice = &buffer[0..=bytes]; - - match buf_slice.iter().position(|byte| *byte == newline_char) { - Some(idx) => (SRes::Newl, idx + 1), - None => (SRes::Comp, bytes), - } - } - _ => match buffer.iter().position(|byte| *byte == newline_char) { - Some(idx) => (SRes::Newl, idx + 1), - None => (SRes::Part, buffer.len()), - }, - }; - - if let Some(out) = out { - crash_if_err!(1, out.write_all(&buffer[0..consume_val])); - } - (res, consume_val) - }; - - self.consume(consume_val); - match res { - SRes::Comp => Complete(consume_val), - SRes::Part => Partial(consume_val), - SRes::Newl => NewlineFound, - } - } -} diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 5bf310daa..91dc17e52 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -10,15 +10,16 @@ #[macro_use] extern crate uucore; +use bstr::io::BufReadExt; use clap::{App, Arg}; use std::fs::File; -use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write}; +use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; use self::searcher::Searcher; +use uucore::fs::is_stdout_interactive; use uucore::ranges::Range; -mod buffer; mod searcher; static NAME: &str = "cut"; @@ -125,6 +126,14 @@ enum Mode { Fields(Vec, FieldOptions), } +fn stdout_writer() -> Box { + if is_stdout_interactive() { + Box::new(stdout()) + } else { + Box::new(BufWriter::new(stdout())) as Box + } +} + fn list_to_ranges(list: &str, complement: bool) -> Result, String> { if complement { Range::from_list(list).map(|r| uucore::ranges::complement(&r)) @@ -134,72 +143,35 @@ fn list_to_ranges(list: &str, complement: bool) -> Result, String> { } fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> i32 { - use self::buffer::Bytes::Select; - use self::buffer::Bytes::Selected::*; - let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; - let mut buf_read = buffer::ByteReader::new(reader, newline_char); - let mut out = stdout(); + let buf_in = BufReader::new(reader); + let mut out = stdout_writer(); + let delim = opts + .out_delim + .as_ref() + .map_or("", String::as_str) + .as_bytes(); - 'newline: loop { - let mut cur_pos = 1; + let res = buf_in.for_byte_record(newline_char, |line| { let mut print_delim = false; - - for &Range { low, high } in ranges.iter() { - // skip up to low - let orig_pos = cur_pos; - loop { - match buf_read.select(low - cur_pos, None::<&mut Stdout>) { - NewlineFound => { - crash_if_err!(1, out.write_all(&[newline_char])); - continue 'newline; - } - Complete(len) => { - cur_pos += len; - break; - } - Partial(len) => cur_pos += len, - EndOfFile => { - if orig_pos != cur_pos { - crash_if_err!(1, out.write_all(&[newline_char])); - } - - break 'newline; - } - } + for &Range { low, high } in ranges { + if low > line.len() { + break; } - - if let Some(ref delim) = opts.out_delim { - if print_delim { - crash_if_err!(1, out.write_all(delim.as_bytes())); - } + if print_delim { + out.write_all(delim)?; + } else if opts.out_delim.is_some() { print_delim = true; } - - // write out from low to high - loop { - match buf_read.select(high - cur_pos + 1, Some(&mut out)) { - NewlineFound => continue 'newline, - Partial(len) => cur_pos += len, - Complete(_) => { - cur_pos = high + 1; - break; - } - EndOfFile => { - if cur_pos != low || low == high { - crash_if_err!(1, out.write_all(&[newline_char])); - } - - break 'newline; - } - } - } + // change `low` from 1-indexed value to 0-index value + let low = low - 1; + let high = high.min(line.len()); + out.write_all(&line[low..high])?; } - - buf_read.consume_line(); - crash_if_err!(1, out.write_all(&[newline_char])); - } - + out.write_all(&[newline_char])?; + Ok(true) + }); + crash_if_err!(1, res); 0 } @@ -212,23 +184,11 @@ fn cut_fields_delimiter( newline_char: u8, out_delim: &str, ) -> i32 { - let mut buf_in = BufReader::new(reader); - let mut out = stdout(); - let mut buffer = Vec::new(); + let buf_in = BufReader::new(reader); + let mut out = stdout_writer(); + let input_delim_len = delim.len(); - 'newline: loop { - buffer.clear(); - match buf_in.read_until(newline_char, &mut buffer) { - Ok(n) if n == 0 => break, - Err(e) => { - if buffer.is_empty() { - crash!(1, "read error: {}", e); - } - } - _ => (), - } - - let line = &buffer[..]; + let result = buf_in.for_byte_record_with_terminator(newline_char, |line| { let mut fields_pos = 1; let mut low_idx = 0; let mut delim_search = Searcher::new(line, delim.as_bytes()).peekable(); @@ -236,46 +196,46 @@ fn cut_fields_delimiter( if delim_search.peek().is_none() { if !only_delimited { - crash_if_err!(1, out.write_all(line)); + out.write_all(line)?; if line[line.len() - 1] != newline_char { - crash_if_err!(1, out.write_all(&[newline_char])); + out.write_all(&[newline_char])?; } } - continue; + return Ok(true); } for &Range { low, high } in ranges.iter() { if low - fields_pos > 0 { low_idx = match delim_search.nth(low - fields_pos - 1) { - Some((_, beyond_delim)) => beyond_delim, + Some(index) => index + input_delim_len, None => break, }; } for _ in 0..=high - low { if print_delim { - crash_if_err!(1, out.write_all(out_delim.as_bytes())); + out.write_all(out_delim.as_bytes())?; + } else { + print_delim = true; } match delim_search.next() { - Some((high_idx, next_low_idx)) => { + Some(high_idx) => { let segment = &line[low_idx..high_idx]; - crash_if_err!(1, out.write_all(segment)); + out.write_all(segment)?; - print_delim = true; - - low_idx = next_low_idx; + low_idx = high_idx + input_delim_len; fields_pos = high + 1; } None => { let segment = &line[low_idx..]; - crash_if_err!(1, out.write_all(segment)); + out.write_all(segment)?; if line[line.len() - 1] == newline_char { - continue 'newline; + return Ok(true); } break; } @@ -283,9 +243,10 @@ fn cut_fields_delimiter( } } - crash_if_err!(1, out.write_all(&[newline_char])); - } - + out.write_all(&[newline_char])?; + Ok(true) + }); + crash_if_err!(1, result); 0 } @@ -303,23 +264,11 @@ fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 ); } - let mut buf_in = BufReader::new(reader); - let mut out = stdout(); - let mut buffer = Vec::new(); + let buf_in = BufReader::new(reader); + let mut out = stdout_writer(); + let delim_len = opts.delimiter.len(); - 'newline: loop { - buffer.clear(); - match buf_in.read_until(newline_char, &mut buffer) { - Ok(n) if n == 0 => break, - Err(e) => { - if buffer.is_empty() { - crash!(1, "read error: {}", e); - } - } - _ => (), - } - - let line = &buffer[..]; + let result = buf_in.for_byte_record_with_terminator(newline_char, |line| { let mut fields_pos = 1; let mut low_idx = 0; let mut delim_search = Searcher::new(line, opts.delimiter.as_bytes()).peekable(); @@ -327,53 +276,54 @@ fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32 if delim_search.peek().is_none() { if !opts.only_delimited { - crash_if_err!(1, out.write_all(line)); + out.write_all(line)?; if line[line.len() - 1] != newline_char { - crash_if_err!(1, out.write_all(&[newline_char])); + out.write_all(&[newline_char])?; } } - continue; + return Ok(true); } - for &Range { low, high } in ranges.iter() { + for &Range { low, high } in ranges { if low - fields_pos > 0 { - low_idx = match delim_search.nth(low - fields_pos - 1) { - Some((_, beyond_delim)) => beyond_delim, - None => break, - }; - } - - if print_delim && low_idx >= opts.delimiter.as_bytes().len() { - low_idx -= opts.delimiter.as_bytes().len(); + if let Some(delim_pos) = delim_search.nth(low - fields_pos - 1) { + low_idx = if print_delim { + delim_pos + } else { + delim_pos + delim_len + } + } else { + break; + } } match delim_search.nth(high - low) { - Some((high_idx, next_low_idx)) => { + Some(high_idx) => { let segment = &line[low_idx..high_idx]; - crash_if_err!(1, out.write_all(segment)); + out.write_all(segment)?; print_delim = true; - low_idx = next_low_idx; + low_idx = high_idx; fields_pos = high + 1; } None => { let segment = &line[low_idx..line.len()]; - crash_if_err!(1, out.write_all(segment)); + out.write_all(segment)?; if line[line.len() - 1] == newline_char { - continue 'newline; + return Ok(true); } break; } } } - - crash_if_err!(1, out.write_all(&[newline_char])); - } - + out.write_all(&[newline_char])?; + Ok(true) + }); + crash_if_err!(1, result); 0 } diff --git a/src/uu/cut/src/searcher.rs b/src/uu/cut/src/searcher.rs index f0821ff3a..5e3c076df 100644 --- a/src/uu/cut/src/searcher.rs +++ b/src/uu/cut/src/searcher.rs @@ -5,7 +5,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -#[derive(Clone)] +use memchr::memchr; + pub struct Searcher<'a> { haystack: &'a [u8], needle: &'a [u8], @@ -14,6 +15,7 @@ pub struct Searcher<'a> { impl<'a> Searcher<'a> { pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Searcher<'a> { + assert!(!needle.is_empty()); Searcher { haystack, needle, @@ -23,30 +25,81 @@ impl<'a> Searcher<'a> { } impl<'a> Iterator for Searcher<'a> { - type Item = (usize, usize); + type Item = usize; - fn next(&mut self) -> Option<(usize, usize)> { - if self.needle.len() == 1 { - for offset in self.position..self.haystack.len() { - if self.haystack[offset] == self.needle[0] { - self.position = offset + 1; - return Some((offset, offset + 1)); + fn next(&mut self) -> Option { + loop { + if let Some(match_idx) = memchr(self.needle[0], self.haystack) { + if self.needle.len() == 1 + || self.haystack[match_idx + 1..].starts_with(&self.needle[1..]) + { + let skip = match_idx + self.needle.len(); + self.haystack = &self.haystack[skip..]; + let match_pos = self.position + match_idx; + self.position += skip; + return Some(match_pos); + } else { + let skip = match_idx + 1; + self.haystack = &self.haystack[skip..]; + self.position += skip; + // continue } - } - - self.position = self.haystack.len(); - return None; - } - - while self.position + self.needle.len() <= self.haystack.len() { - if &self.haystack[self.position..self.position + self.needle.len()] == self.needle { - let match_pos = self.position; - self.position += self.needle.len(); - return Some((match_pos, match_pos + self.needle.len())); } else { - self.position += 1; + return None; } } - None + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + const NEEDLE: &[u8] = "ab".as_bytes(); + + #[test] + fn test_normal() { + let iter = Searcher::new("a.a.a".as_bytes(), "a".as_bytes()); + let items: Vec = iter.collect(); + assert_eq!(vec![0, 2, 4], items); + } + + #[test] + fn test_empty() { + let iter = Searcher::new("".as_bytes(), "a".as_bytes()); + let items: Vec = iter.collect(); + assert_eq!(vec![] as Vec, items); + } + + fn test_multibyte(line: &[u8], expected: Vec) { + let iter = Searcher::new(line, NEEDLE); + let items: Vec = iter.collect(); + assert_eq!(expected, items); + } + + #[test] + fn test_multibyte_normal() { + test_multibyte("...ab...ab...".as_bytes(), vec![3, 8]); + } + + #[test] + fn test_multibyte_needle_head_at_end() { + test_multibyte("a".as_bytes(), vec![]); + } + + #[test] + fn test_multibyte_starting_needle() { + test_multibyte("ab...ab...".as_bytes(), vec![0, 5]); + } + + #[test] + fn test_multibyte_trailing_needle() { + test_multibyte("...ab...ab".as_bytes(), vec![3, 8]); + } + + #[test] + fn test_multibyte_first_byte_false_match() { + test_multibyte("aA..aCaC..ab..aD".as_bytes(), vec![10]); } } From b8e23c20c213a34a2e13056f4330f747e5e47ecf Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 24 Apr 2021 17:45:28 +0200 Subject: [PATCH 130/399] cp: extract linux COW logic into function --- src/uu/cp/src/cp.rs | 77 +++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index da8c9037e..519590e85 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -155,7 +155,8 @@ pub enum OverwriteMode { NoClobber, } -#[derive(Clone, Eq, PartialEq)] +/// Possible arguments for `--reflink`. +#[derive(Copy, Clone, Eq, PartialEq)] pub enum ReflinkMode { Always, Auto, @@ -1200,39 +1201,7 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> return Err("--reflink is only supported on linux".to_string().into()); #[cfg(target_os = "linux")] - { - let src_file = File::open(source).unwrap().into_raw_fd(); - let dst_file = OpenOptions::new() - .write(true) - .truncate(false) - .create(true) - .open(dest) - .unwrap() - .into_raw_fd(); - match options.reflink_mode { - ReflinkMode::Always => unsafe { - let result = ficlone(dst_file, src_file as *const i32); - if result != 0 { - return Err(format!( - "failed to clone {:?} from {:?}: {}", - source, - dest, - std::io::Error::last_os_error() - ) - .into()); - } else { - return Ok(()); - } - }, - ReflinkMode::Auto => unsafe { - let result = ficlone(dst_file, src_file as *const i32); - if result != 0 { - fs::copy(source, dest).context(&*context_for(source, dest))?; - } - }, - ReflinkMode::Never => {} - } - } + copy_on_write_linux(source, dest, options.reflink_mode)?; } else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { // Here, we will copy the symlink itself (actually, just recreate it) let link = fs::read_link(&source)?; @@ -1265,6 +1234,46 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> Ok(()) } +/// Copies `source` to `dest` using copy-on-write if possible. +#[cfg(target_os = "linux")] +fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { + debug_assert!(mode != ReflinkMode::Never); + + let src_file = File::open(source).unwrap().into_raw_fd(); + let dst_file = OpenOptions::new() + .write(true) + .truncate(false) + .create(true) + .open(dest) + .unwrap() + .into_raw_fd(); + match mode { + ReflinkMode::Always => unsafe { + let result = ficlone(dst_file, src_file as *const i32); + if result != 0 { + return Err(format!( + "failed to clone {:?} from {:?}: {}", + source, + dest, + std::io::Error::last_os_error() + ) + .into()); + } else { + return Ok(()); + } + }, + ReflinkMode::Auto => unsafe { + let result = ficlone(dst_file, src_file as *const i32); + if result != 0 { + fs::copy(source, dest).context(&*context_for(source, dest))?; + } + }, + ReflinkMode::Never => unreachable!(), + } + + Ok(()) +} + /// Generate an error message if `target` is not the correct `target_type` pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { match (target_type, target.is_dir()) { From 4bf33e98a8153891bb3b745b700e96bee0901506 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 24 Apr 2021 19:20:31 +0200 Subject: [PATCH 131/399] cp: add --reflink support for macOS Fixes #1773 --- src/uu/cp/src/cp.rs | 68 +++++++++++++++++++++++++++++++++++++--- tests/by-util/test_cp.rs | 8 ++--- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 519590e85..ca564e37c 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1193,12 +1193,17 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { Ok(()) } -///Copy the file from `source` to `dest` either using the normal `fs::copy` or the -///`FICLONE` ioctl if --reflink is specified and the filesystem supports it. +/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a +/// copy-on-write scheme if --reflink is specified and the filesystem supports it. fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { if options.reflink_mode != ReflinkMode::Never { - #[cfg(not(target_os = "linux"))] - return Err("--reflink is only supported on linux".to_string().into()); + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + return Err("--reflink is only supported on linux and macOS" + .to_string() + .into()); + + #[cfg(target_os = "macos")] + copy_on_write_macos(source, dest, options.reflink_mode)?; #[cfg(target_os = "linux")] copy_on_write_linux(source, dest, options.reflink_mode)?; @@ -1274,6 +1279,61 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes Ok(()) } +/// Copies `source` to `dest` using copy-on-write if possible. +#[cfg(target_os = "macos")] +fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { + debug_assert!(mode != ReflinkMode::Never); + + // Extract paths in a form suitable to be passed to a syscall. + // The unwrap() is safe because they come from the command-line and so contain non nul + // character. + use std::os::unix::ffi::OsStrExt; + let src = CString::new(source.as_os_str().as_bytes()).unwrap(); + let dst = CString::new(dest.as_os_str().as_bytes()).unwrap(); + + // clonefile(2) was introduced in macOS 10.12 so we cannot statically link against it + // for backward compatibility. + let clonefile = CString::new("clonefile").unwrap(); + let raw_pfn = unsafe { libc::dlsym(libc::RTLD_NEXT, clonefile.as_ptr()) }; + + let mut error = 0; + if !raw_pfn.is_null() { + // Call clonefile(2). + // Safety: Casting a C function pointer to a rust function value is one of the few + // blessed uses of `transmute()`. + unsafe { + let pfn: extern "C" fn( + src: *const libc::c_char, + dst: *const libc::c_char, + flags: u32, + ) -> libc::c_int = std::mem::transmute(raw_pfn); + error = pfn(src.as_ptr(), dst.as_ptr(), 0); + if std::io::Error::last_os_error().kind() == std::io::ErrorKind::AlreadyExists { + // clonefile(2) fails if the destination exists. Remove it and try again. Do not + // bother to check if removal worked because we're going to try to clone again. + let _ = fs::remove_file(dest); + error = pfn(src.as_ptr(), dst.as_ptr(), 0); + } + } + } + + if raw_pfn.is_null() || error != 0 { + // clonefile(2) is not supported or it error'ed out (possibly because the FS does not + // support COW). + match mode { + ReflinkMode::Always => { + return Err( + format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(), + ) + } + ReflinkMode::Auto => fs::copy(source, dest).context(&*context_for(source, dest))?, + ReflinkMode::Never => unreachable!(), + }; + } + + Ok(()) +} + /// Generate an error message if `target` is not the correct `target_type` pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { match (target_type, target.is_dir()) { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 53c16e677..1e99da0fb 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -967,7 +967,7 @@ fn test_cp_one_file_system() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn test_cp_reflink_always() { let (at, mut ucmd) = at_and_ucmd!(); let result = ucmd @@ -985,7 +985,7 @@ fn test_cp_reflink_always() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn test_cp_reflink_auto() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg("--reflink=auto") @@ -998,7 +998,7 @@ fn test_cp_reflink_auto() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn test_cp_reflink_never() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg("--reflink=never") @@ -1011,7 +1011,7 @@ fn test_cp_reflink_never() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn test_cp_reflink_bad() { let (_, mut ucmd) = at_and_ucmd!(); let _result = ucmd From ce04f8a759641250b7d13987cb8caeffb5f78254 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 24 Apr 2021 23:46:19 +0200 Subject: [PATCH 132/399] ls: use bufwriter to write stdout --- src/uu/ls/src/ls.rs | 85 ++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 2baf93193..eff7fbb77 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -24,16 +24,22 @@ use once_cell::unsync::OnceCell; use quoting_style::{escape_name, QuotingStyle}; #[cfg(unix)] use std::collections::HashMap; -use std::fs::{self, DirEntry, FileType, Metadata}; #[cfg(any(unix, target_os = "redox"))] use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; -use std::path::{Path, PathBuf}; #[cfg(unix)] use std::time::Duration; -use std::time::{SystemTime, UNIX_EPOCH}; use std::{cmp::Reverse, process::exit}; +use std::{ + fs::{self, DirEntry, FileType, Metadata}, + io::{stdout, BufWriter, Write}, + path::{Path, PathBuf}, +}; +use std::{ + io::Stdout, + time::{SystemTime, UNIX_EPOCH}, +}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; @@ -1092,6 +1098,8 @@ fn list(locs: Vec, config: Config) -> i32 { let mut dirs = Vec::::new(); let mut has_failed = false; + let mut out = BufWriter::new(stdout()); + for loc in locs { let p = PathBuf::from(&loc); if !p.exists() { @@ -1118,14 +1126,14 @@ fn list(locs: Vec, config: Config) -> i32 { } } sort_entries(&mut files, &config); - display_items(&files, None, &config); + display_items(&files, None, &config, &mut out); sort_entries(&mut dirs, &config); for dir in dirs { if number_of_locs > 1 { - println!("\n{}:", dir.p_buf.display()); + let _ = writeln!(out, "\n{}:", dir.p_buf.display()); } - enter_directory(&dir, &config); + enter_directory(&dir, &config, &mut out); } if has_failed { 1 @@ -1178,7 +1186,7 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { !config.ignore_patterns.is_match(&ffi_name) } -fn enter_directory(dir: &PathData, config: &Config) { +fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) { let mut entries: Vec<_> = if config.files == Files::All { vec![ PathData::new(dir.p_buf.join("."), None, config, false), @@ -1198,7 +1206,7 @@ fn enter_directory(dir: &PathData, config: &Config) { entries.append(&mut temp); - display_items(&entries, Some(&dir.p_buf), config); + display_items(&entries, Some(&dir.p_buf), config, out); if config.recursive { for e in entries @@ -1206,8 +1214,8 @@ fn enter_directory(dir: &PathData, config: &Config) { .skip(if config.files == Files::All { 2 } else { 0 }) .filter(|p| p.file_type().map(|ft| ft.is_dir()).unwrap_or(false)) { - println!("\n{}:", e.p_buf.display()); - enter_directory(&e, config); + let _ = writeln!(out, "\n{}:", e.p_buf.display()); + enter_directory(&e, config, out); } } } @@ -1235,7 +1243,12 @@ fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } -fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) { +fn display_items( + items: &[PathData], + strip: Option<&Path>, + config: &Config, + out: &mut BufWriter, +) { if config.format == Format::Long { let (mut max_links, mut max_size) = (1, 1); for item in items { @@ -1244,7 +1257,7 @@ fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) { max_size = size.max(max_size); } for item in items { - display_item_long(item, strip, max_links, max_size, config); + display_item_long(item, strip, max_links, max_size, config, out); } } else { let names = items @@ -1252,42 +1265,51 @@ fn display_items(items: &[PathData], strip: Option<&Path>, config: &Config) { .filter_map(|i| display_file_name(&i, strip, config)); match (&config.format, config.width) { - (Format::Columns, Some(width)) => display_grid(names, width, Direction::TopToBottom), - (Format::Across, Some(width)) => display_grid(names, width, Direction::LeftToRight), + (Format::Columns, Some(width)) => { + display_grid(names, width, Direction::TopToBottom, out) + } + (Format::Across, Some(width)) => { + display_grid(names, width, Direction::LeftToRight, out) + } (Format::Commas, width_opt) => { let term_width = width_opt.unwrap_or(1); let mut current_col = 0; let mut names = names; if let Some(name) = names.next() { - print!("{}", name.contents); + let _ = write!(out, "{}", name.contents); current_col = name.width as u16 + 2; } for name in names { let name_width = name.width as u16; if current_col + name_width + 1 > term_width { current_col = name_width + 2; - print!(",\n{}", name.contents); + let _ = write!(out, ",\n{}", name.contents); } else { current_col += name_width + 2; - print!(", {}", name.contents); + let _ = write!(out, ", {}", name.contents); } } // Current col is never zero again if names have been printed. // So we print a newline. if current_col > 0 { - println!(); + let _ = writeln!(out,); } } _ => { for name in names { - println!("{}", name.contents); + let _ = writeln!(out, "{}", name.contents); } } } } } -fn display_grid(names: impl Iterator, width: u16, direction: Direction) { +fn display_grid( + names: impl Iterator, + width: u16, + direction: Direction, + out: &mut BufWriter, +) { let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(2), direction, @@ -1298,9 +1320,13 @@ fn display_grid(names: impl Iterator, width: u16, direction: Direct } match grid.fit_into_width(width as usize) { - Some(output) => print!("{}", output), + Some(output) => { + let _ = write!(out, "{}", output); + } // Width is too small for the grid, so we fit it in one column - None => print!("{}", grid.fit_into_columns(1)), + None => { + let _ = write!(out, "{}", grid.fit_into_columns(1)); + } } } @@ -1312,6 +1338,7 @@ fn display_item_long( max_links: usize, max_size: usize, config: &Config, + out: &mut BufWriter, ) { let md = match item.md() { None => { @@ -1325,11 +1352,12 @@ fn display_item_long( #[cfg(unix)] { if config.inode { - print!("{} ", get_inode(&md)); + let _ = write!(out, "{} ", get_inode(&md)); } } - print!( + let _ = write!( + out, "{}{} {}", display_file_type(md.file_type()), display_permissions(&md), @@ -1337,20 +1365,21 @@ fn display_item_long( ); if config.long.owner { - print!(" {}", display_uname(&md, config)); + let _ = write!(out, " {}", display_uname(&md, config)); } if config.long.group { - print!(" {}", display_group(&md, config)); + let _ = write!(out, " {}", display_group(&md, config)); } // Author is only different from owner on GNU/Hurd, so we reuse // the owner, since GNU/Hurd is not currently supported by Rust. if config.long.author { - print!(" {}", display_uname(&md, config)); + let _ = write!(out, " {}", display_uname(&md, config)); } - println!( + let _ = writeln!( + out, " {} {} {}", pad_left(display_file_size(&md, config), max_size), display_date(&md, config), From e995eea5799c7cb87cefa6baeb8af00e03876108 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 25 Apr 2021 00:23:14 +0200 Subject: [PATCH 133/399] ls: general cleanup --- src/uu/ls/src/ls.rs | 90 +++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index eff7fbb77..6abaaa0b9 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -92,10 +92,8 @@ pub mod options { pub static C: &str = "quote-name"; } pub static QUOTING_STYLE: &str = "quoting-style"; - pub mod indicator_style { - pub static NONE: &str = "none"; - pub static SLASH: &str = "slash"; + pub static SLASH: &str = "p"; pub static FILE_TYPE: &str = "file-type"; pub static CLASSIFY: &str = "classify"; } @@ -114,9 +112,6 @@ pub mod options { pub static TIME: &str = "time"; pub static IGNORE_BACKUPS: &str = "ignore-backups"; pub static DIRECTORY: &str = "directory"; - pub static CLASSIFY: &str = "classify"; - pub static FILE_TYPE: &str = "file-type"; - pub static SLASH: &str = "p"; pub static INODE: &str = "inode"; pub static REVERSE: &str = "reverse"; pub static RECURSIVE: &str = "recursive"; @@ -431,19 +426,11 @@ impl Config { "slash" => IndicatorStyle::Slash, &_ => IndicatorStyle::None, } - } else if options.is_present(options::indicator_style::NONE) { - IndicatorStyle::None - } else if options.is_present(options::indicator_style::CLASSIFY) - || options.is_present(options::CLASSIFY) - { + } else if options.is_present(options::indicator_style::CLASSIFY) { IndicatorStyle::Classify - } else if options.is_present(options::indicator_style::SLASH) - || options.is_present(options::SLASH) - { + } else if options.is_present(options::indicator_style::SLASH) { IndicatorStyle::Slash - } else if options.is_present(options::indicator_style::FILE_TYPE) - || options.is_present(options::FILE_TYPE) - { + } else if options.is_present(options::indicator_style::FILE_TYPE) { IndicatorStyle::FileType } else { IndicatorStyle::None @@ -969,45 +956,45 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .possible_values(&["none", "slash", "file-type", "classify"]) .overrides_with_all(&[ - options::FILE_TYPE, - options::SLASH, - options::CLASSIFY, + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, ])) .arg( - Arg::with_name(options::CLASSIFY) + Arg::with_name(options::indicator_style::CLASSIFY) .short("F") - .long(options::CLASSIFY) + .long(options::indicator_style::CLASSIFY) .help("Append a character to each file name indicating the file type. Also, for \ regular files that are executable, append '*'. The file type indicators are \ '/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \ '>' for doors, and nothing for regular files.") .overrides_with_all(&[ - options::FILE_TYPE, - options::SLASH, - options::CLASSIFY, + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, ]) ) .arg( - Arg::with_name(options::FILE_TYPE) - .long(options::FILE_TYPE) + Arg::with_name(options::indicator_style::FILE_TYPE) + .long(options::indicator_style::FILE_TYPE) .help("Same as --classify, but do not append '*'") .overrides_with_all(&[ - options::FILE_TYPE, - options::SLASH, - options::CLASSIFY, + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, ])) .arg( - Arg::with_name(options::SLASH) - .short(options::SLASH) + Arg::with_name(options::indicator_style::SLASH) + .short(options::indicator_style::SLASH) .help("Append / indicator to directories." ) .overrides_with_all(&[ - options::FILE_TYPE, - options::SLASH, - options::CLASSIFY, + options::indicator_style::FILE_TYPE, + options::indicator_style::SLASH, + options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, ])) @@ -1409,14 +1396,10 @@ fn cached_uid2usr(uid: u32) -> String { } 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 - } - } + uid_cache + .entry(uid) + .or_insert_with(|| entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string())) + .clone() } #[cfg(unix)] @@ -1435,14 +1418,10 @@ fn cached_gid2grp(gid: u32) -> String { } 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 - } - } + gid_cache + .entry(gid) + .or_insert_with(|| entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string())) + .clone() } #[cfg(unix)] @@ -1460,7 +1439,6 @@ fn display_uname(_metadata: &Metadata, _config: &Config) -> String { } #[cfg(not(unix))] -#[allow(unused_variables)] fn display_group(_metadata: &Metadata, _config: &Config) -> String { "somegroup".to_string() } @@ -1535,13 +1513,13 @@ fn display_file_size(metadata: &Metadata, config: &Config) -> String { } } -fn display_file_type(file_type: FileType) -> String { +fn display_file_type(file_type: FileType) -> char { if file_type.is_dir() { - "d".to_string() + 'd' } else if file_type.is_symlink() { - "l".to_string() + 'l' } else { - "-".to_string() + '-' } } From 2b8a6e98eeed5d86a5af8a0a87df2d04dca55a0a Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 00:20:56 -0500 Subject: [PATCH 134/399] Working ExtSort --- Cargo.lock | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/sort/src/ext_sorter/LICENSE | 202 --------------------- src/uu/sort/src/ext_sorter/NOTICE | 9 - src/uu/sort/src/ext_sorter/mod.rs | 277 ----------------------------- src/uu/sort/src/sort.rs | 108 +++++------ 6 files changed, 47 insertions(+), 553 deletions(-) delete mode 100644 src/uu/sort/src/ext_sorter/LICENSE delete mode 100644 src/uu/sort/src/ext_sorter/NOTICE delete mode 100644 src/uu/sort/src/ext_sorter/mod.rs diff --git a/Cargo.lock b/Cargo.lock index eb99af34b..d5dbf3508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2305,7 +2305,7 @@ dependencies = [ "serde", "serde_json", "smallvec 1.6.1", - "tempfile", + "tempdir", "uucore", "uucore_procs", ] diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index f29df6ab8..12c685e23 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -26,7 +26,7 @@ semver = "0.9.0" smallvec = { version = "1.6.1", features = ["serde"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -tempfile = "3.1.0" +tempdir = "0.3.7" [[bin]] name = "sort" diff --git a/src/uu/sort/src/ext_sorter/LICENSE b/src/uu/sort/src/ext_sorter/LICENSE deleted file mode 100644 index fe647bd7f..000000000 --- a/src/uu/sort/src/ext_sorter/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/src/uu/sort/src/ext_sorter/NOTICE b/src/uu/sort/src/ext_sorter/NOTICE deleted file mode 100644 index fdfc6f04f..000000000 --- a/src/uu/sort/src/ext_sorter/NOTICE +++ /dev/null @@ -1,9 +0,0 @@ -ext_sorter -Copyright 2018 Andre-Philippe Paquet -Modifications copyright 2021 Robert Swinford - -This ext_sorter module includes software developed by Andre-Philippe Paquet as extsort. - -The sorter.rs file was copied and modified for use in the uutils' coreutils subproject, sort. - -sort is licensed according to the term of the LICENSE file found in root directory of the uutils' coreutils project. \ No newline at end of file diff --git a/src/uu/sort/src/ext_sorter/mod.rs b/src/uu/sort/src/ext_sorter/mod.rs deleted file mode 100644 index a90be6bb0..000000000 --- a/src/uu/sort/src/ext_sorter/mod.rs +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2018 Andre-Philippe Paquet -// Modifications copyright 2021 Robert Swinford -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file has been modified for use in the uutils' coreutils subproject, sort. - -use rayon::prelude::*; -use std::{ - cmp::Ordering, - collections::VecDeque, - fs::{File, OpenOptions}, - io::{BufReader, BufWriter, Error, Read, Seek, SeekFrom, Write}, - path::{Path, PathBuf}, -}; - -/// Exposes external sorting (i.e. on disk sorting) capability on arbitrarily -/// sized iterator, even if the generated content of the iterator doesn't fit in -/// memory. -/// -/// It uses an in-memory buffer sorted and flushed to disk in segment files when -/// full. Once sorted, it returns a new sorted iterator with all items. In order -/// to remain efficient for all implementations, the crate doesn't handle -/// serialization, but leaves that to the user. -pub struct ExternalSorter { - segment_size: usize, - sort_dir: Option, - parallel: bool, -} - -impl ExternalSorter { - pub fn new() -> ExternalSorter { - ExternalSorter { - // Default is 16G - But we never use it, - // because we always set or ignore - segment_size: 16000000000, - sort_dir: None, - parallel: false, - } - } - - /// Sets the maximum size of each segment in number of sorted items. - /// - /// This number of items needs to fit in memory. While sorting, a - /// in-memory buffer is used to collect the items to be sorted. Once - /// it reaches the maximum size, it is sorted and then written to disk. - /// - /// Using a higher segment size makes sorting faster by leveraging - /// faster in-memory operations. - pub fn with_segment_size(mut self, size: usize) -> Self { - self.segment_size = size; - self - } - - /// Sets directory in which sorted segments will be written (if it doesn't - /// fit in memory). - pub fn with_sort_dir(mut self, path: PathBuf) -> Self { - self.sort_dir = Some(path); - self - } - - /// Uses Rayon to sort the in-memory buffer. - /// - /// This may not be needed if the buffer isn't big enough for parallelism to - /// be gainful over the overhead of multithreading. - pub fn with_parallel_sort(mut self) -> Self { - self.parallel = true; - self - } - - /// Sorts a given iterator with a comparator function, returning a new iterator with items - pub fn sort_by(&self, iterator: I, cmp: F) -> Result, Error> - where - T: Sortable, - I: Iterator, - F: Fn(&T, &T) -> Ordering + Send + Sync, - { - let mut tempdir: Option = None; - let mut sort_dir: Option = None; - - let mut count = 0; - let mut segments_file: Vec = Vec::new(); - - let size_of_items = std::mem::size_of::(); - // Get size of iterator - let (_, upper_bound) = iterator.size_hint(); - // Buffer size specified + minimum overhead of struct / size of items - let initial_capacity = (self.segment_size + (upper_bound.unwrap() * size_of_items)) / size_of_items; - let mut buffer: Vec = Vec::with_capacity(initial_capacity); - - for next_item in iterator { - count += 1; - buffer.push(next_item); - // if after push, number of elements in vector > initial capacity - if buffer.len() > initial_capacity { - let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; - self.sort_and_write_segment(sort_dir, &mut segments_file, &mut buffer, &cmp)?; - // Truncate buffer back to initial capacity - buffer.truncate(initial_capacity); - } - } - - // Write any items left in buffer, but only if we had at least 1 segment - // written. Otherwise we use the buffer itself to iterate from memory - let pass_through_queue = if !buffer.is_empty() && !segments_file.is_empty() { - let sort_dir = self.lazy_create_dir(&mut tempdir, &mut sort_dir)?; - self.sort_and_write_segment(sort_dir, &mut segments_file, &mut buffer, &cmp)?; - None - } else { - buffer.sort_by(&cmp); - Some(VecDeque::from(buffer)) - }; - - SortedIterator::new(tempdir, pass_through_queue, segments_file, count, cmp) - } - - /// We only want to create directory if it's needed (i.e. if the dataset - /// doesn't fit in memory) to prevent filesystem latency - fn lazy_create_dir<'a>( - &self, - tempdir: &mut Option, - sort_dir: &'a mut Option, - ) -> Result<&'a Path, Error> { - if let Some(sort_dir) = sort_dir { - return Ok(sort_dir); - } - - *sort_dir = if let Some(ref sort_dir) = self.sort_dir { - Some(sort_dir.to_path_buf()) - } else { - *tempdir = Some(tempfile::TempDir::new()?); - Some(tempdir.as_ref().unwrap().path().to_path_buf()) - }; - - Ok(sort_dir.as_ref().unwrap()) - } - - fn sort_and_write_segment( - &self, - sort_dir: &Path, - segments: &mut Vec, - buffer: &mut Vec, - cmp: F, - ) -> Result<(), Error> - where - T: Sortable, - F: Fn(&T, &T) -> Ordering + Send + Sync, - { - if self.parallel { - buffer.par_sort_by(|a, b| cmp(a, b)); - } else { - buffer.sort_by(|a, b| cmp(a, b)); - } - - let segment_path = sort_dir.join(format!("{}", segments.len())); - let segment_file = OpenOptions::new() - .create(true) - .truncate(true) - .read(true) - .write(true) - .open(&segment_path)?; - let mut buf_writer = BufWriter::new(segment_file); - - // Possible panic here. - // Why use drain here, if we want to dump the entire buffer? - // Was "buffer.drain(0..)" - for item in buffer { - item.encode(&mut buf_writer); - } - - let file = buf_writer.into_inner()?; - segments.push(file); - - Ok(()) - } -} - -impl Default for ExternalSorter { - fn default() -> Self { - ExternalSorter::new() - } -} - -pub trait Sortable: Sized + Send { - fn encode(&self, writer: &mut W); - fn decode(reader: &mut R) -> Option; -} - -pub struct SortedIterator { - _tempdir: Option, - pass_through_queue: Option>, - segments_file: Vec>, - next_values: Vec>, - count: u64, - cmp: F, -} - -impl Ordering + Send + Sync> SortedIterator { - fn new( - tempdir: Option, - pass_through_queue: Option>, - mut segments_file: Vec, - count: u64, - cmp: F, - ) -> Result, Error> { - for segment in &mut segments_file { - segment.seek(SeekFrom::Start(0))?; - } - - let next_values = segments_file - .iter_mut() - .map(|file| T::decode(file)) - .collect(); - - let segments_file_buffered = segments_file.into_iter().map(BufReader::new).collect(); - - Ok(SortedIterator { - _tempdir: tempdir, - pass_through_queue, - segments_file: segments_file_buffered, - next_values, - count, - cmp, - }) - } - - pub fn sorted_count(&self) -> u64 { - self.count - } -} - -impl Ordering> Iterator for SortedIterator { - type Item = T; - - fn next(&mut self) -> Option { - // if we have a pass through, we dequeue from it directly - if let Some(ptb) = self.pass_through_queue.as_mut() { - return ptb.pop_front(); - } - - // otherwise, we iter from segments on disk - let mut smallest_idx: Option = None; - { - let mut smallest: Option<&T> = None; - for idx in 0..self.segments_file.len() { - let next_value = self.next_values[idx].as_ref(); - if next_value.is_none() { - continue; - } - - if smallest.is_none() - || (self.cmp)(next_value.unwrap(), smallest.unwrap()) == Ordering::Less - { - smallest = Some(next_value.unwrap()); - smallest_idx = Some(idx); - } - } - } - - smallest_idx.map(|idx| { - let file = &mut self.segments_file[idx]; - let value = self.next_values[idx].take().unwrap(); - self.next_values[idx] = T::decode(file); - value - }) - } -} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 571541fc6..8c3a0cf7f 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,11 +15,11 @@ #[macro_use] extern crate uucore; -mod ext_sorter; mod numeric_str_cmp; +mod external_sort; +use external_sort::{ExternalSorter, ExternallySortable}; use clap::{App, Arg}; -use ext_sorter::{ExternalSorter, Sortable}; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -27,7 +27,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; -use serde::{Deserialize, Serialize}; +use serde::{Deserializer, Deserialize, Serialize}; use smallvec::SmallVec; use std::borrow::Cow; use std::cmp::Ordering; @@ -103,7 +103,7 @@ enum SortMode { Version, Default, } - +#[derive(Clone)] struct GlobalSettings { mode: SortMode, ignore_blanks: bool, @@ -176,7 +176,7 @@ impl Default for GlobalSettings { } } } - +#[derive(Clone)] struct KeySettings { mode: SortMode, ignore_blanks: bool, @@ -201,7 +201,7 @@ impl From<&GlobalSettings> for KeySettings { } } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone)] /// Represents the string selected by a FieldSelector. enum SelectionRange { /// If we had to transform this selection, we have to store a new string. @@ -232,13 +232,23 @@ impl SelectionRange { } } } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] + +#[derive(Debug, Serialize, Deserialize, Clone)] enum NumCache { + #[serde(deserialize_with="bailout_parse_f64")] AsF64(f64), WithInfo(NumInfo), None, } +// Only used when serde can't parse a null value +fn bailout_parse_f64<'de, D>(d: D) -> Result where D: Deserializer<'de> { + Deserialize::deserialize(d) + .map(|x: Option<_>| { + x.unwrap_or(0f64) + }) +} + impl NumCache { fn as_f64(&self) -> f64 { match self { @@ -253,7 +263,7 @@ impl NumCache { } } } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone)] struct Selection { range: SelectionRange, num_cache: NumCache, @@ -267,56 +277,29 @@ impl Selection { } type Field = Range; -#[derive(Debug, Serialize, Deserialize)] + +#[derive(Serialize, Deserialize, Clone)] struct Line { line: String, // The common case is not to specify fields. Let's make this fast. selections: SmallVec<[Selection; 1]>, } -impl Sortable for Line { - fn encode(&self, write: &mut W) { - let line = Line { - line: self.line.to_owned(), - selections: self.selections.to_owned(), - }; - let serialized = serde_json::to_string(&line).unwrap(); - // Each instance of valid JSON needs to be seperated by something, so here we use a newline - write - .write_all(format!("{}{}", serialized, "\n").as_bytes()) - .unwrap(); - } - - // This crate asks us to write one Line struct at a time, but then returns multiple Lines to us at once. - // We concatanate them and return them as one big Line here. - fn decode(read: &mut R) -> Option { - let buf_reader = BufReader::new(read); - let result = { - let mut line_joined = String::new(); - // Return an empty vec for selections - let selections_joined = SmallVec::new(); - let mut p_iter = buf_reader.lines().peekable(); - while let Some(line) = p_iter.next() { - let deserialized_line: Line = - serde_json::from_str(&line.as_ref().unwrap()).unwrap(); - if let Some(_next_line) = p_iter.peek() { - line_joined = format!("{}\n{}\n", line_joined, deserialized_line.line) - } else { - line_joined = format!("{}\n{}", line_joined, deserialized_line.line) - } - // I think we've done our sorting already and these selctions are irrelevant? - // @miDeb what's your sense? Could we just return an empty vec? - //selections_joined.append(&mut deserialized_line.selections); - } - Some(Line { - line: line_joined, - selections: selections_joined, - }) - }; - result +impl ExternallySortable for Line { + fn get_size(&self) -> u64 { + // Currently 96 bytes, but that could change, so we get that size here + std::mem::size_of::() as u64 } } +impl PartialEq for Line { + fn eq(&self, other: &Self) -> bool { + self.line == other.line + } +} + +impl Eq for Line {} + impl Line { fn new(line: String, settings: &GlobalSettings) -> Self { let fields = if settings @@ -449,6 +432,7 @@ fn tokenize_with_separator(line: &str, separator: char) -> Vec { tokens } +#[derive(Clone)] struct KeyPosition { /// 1-indexed, 0 is invalid. field: usize, @@ -516,7 +500,7 @@ impl KeyPosition { } } } - +#[derive(Clone)] struct FieldSelector { from: KeyPosition, to: Option, @@ -1014,10 +998,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }); } - exec(files, &settings) + exec(files, settings) } -fn exec(files: Vec, settings: &GlobalSettings) -> i32 { +fn exec(files: Vec, settings: GlobalSettings) -> i32 { let mut lines = Vec::new(); let mut file_merger = FileMerger::new(&settings); @@ -1059,7 +1043,7 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { // Probably faster that we don't create // an owned value each run if settings.buffer_size != DEFAULT_BUF_SIZE { - lines = ext_sort_by(lines, &settings); + lines = ext_sort_by(lines, settings.clone()); } else { sort_by(&mut lines, &settings); } @@ -1074,7 +1058,7 @@ fn exec(files: Vec, settings: &GlobalSettings) -> i32 { print_sorted( lines .into_iter() - .dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal) + .dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal) .map(|line| line.line), &settings, ) @@ -1117,15 +1101,13 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn ext_sort_by(lines: Vec, settings: &GlobalSettings) -> Vec { - let sorter = ExternalSorter::new() - .with_segment_size(settings.buffer_size) - .with_sort_dir(settings.tmp_dir.clone()) - .with_parallel_sort(); - sorter - .sort_by(lines.into_iter(), |a, b| compare_by(a, b, &settings)) - .unwrap() - .collect() +fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { + let external_sorter = ExternalSorter::new(settings.buffer_size as u64, Some(settings.tmp_dir.clone()), settings.clone()); + let iter = external_sorter.sort_by(unsorted.into_iter(), settings.clone()).unwrap(); + let vec = iter.filter(|x| x.is_ok() ) + .map(|x| x.unwrap()) + .collect::>(); + vec } fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { From 7e06316ece2606d89bf6f1bef50fff8a11b63e8f Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sun, 25 Apr 2021 13:37:07 +0530 Subject: [PATCH 135/399] ls: Use sort_by_cached_key --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 2baf93193..e3728d2a4 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1147,7 +1147,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { 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.file_name.to_lowercase()), + Sort::Name => entries.sort_by_cached_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 => {} } From fc6c7a279ea17145476fc1b7af0b22c21b3cd47d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 25 Apr 2021 10:46:51 +0200 Subject: [PATCH 136/399] ls: clean up imports --- src/uu/ls/src/ls.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6abaaa0b9..73f350f45 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -22,25 +22,22 @@ use lscolors::LsColors; use number_prefix::NumberPrefix; use once_cell::unsync::OnceCell; use quoting_style::{escape_name, QuotingStyle}; -#[cfg(unix)] -use std::collections::HashMap; -#[cfg(any(unix, target_os = "redox"))] -use std::os::unix::fs::{FileTypeExt, MetadataExt}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; -#[cfg(unix)] -use std::time::Duration; -use std::{cmp::Reverse, process::exit}; use std::{ + cmp::Reverse, fs::{self, DirEntry, FileType, Metadata}, - io::{stdout, BufWriter, Write}, + io::{stdout, BufWriter, Stdout, Write}, path::{Path, PathBuf}, -}; -use std::{ - io::Stdout, + process::exit, time::{SystemTime, UNIX_EPOCH}, }; - +#[cfg(unix)] +use std::{ + collections::HashMap, + os::unix::fs::{FileTypeExt, MetadataExt}, + time::Duration, +}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; #[cfg(unix)] From 9f6a8815923157674bfb812ef9982ffdf0f69c2d Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 25 Apr 2021 13:46:57 +0100 Subject: [PATCH 137/399] improve assert error messages --- tests/by-util/test_truncate.rs | 44 +++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index b033385de..d0a93f871 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -6,26 +6,31 @@ static TFILE2: &'static str = "truncate_test_2"; #[test] fn test_increase_file_size() { + let expected = 5 * 1024; let (at, mut ucmd) = at_and_ucmd!(); let mut file = at.make_file(TFILE1); ucmd.args(&["-s", "+5K", TFILE1]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); - assert!(file.seek(SeekFrom::Current(0)).unwrap() == 5 * 1024); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_increase_file_size_kb() { + let expected = 5 * 1000; let (at, mut ucmd) = at_and_ucmd!(); let mut file = at.make_file(TFILE1); ucmd.args(&["-s", "+5KB", TFILE1]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); - assert!(file.seek(SeekFrom::Current(0)).unwrap() == 5 * 1000); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_reference() { + let expected = 5 * 1000; let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let mut file = at.make_file(TFILE2); @@ -40,27 +45,32 @@ fn test_reference() { .run(); file.seek(SeekFrom::End(0)).unwrap(); - assert!(file.seek(SeekFrom::Current(0)).unwrap() == 5 * 1000); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_decrease_file_size() { + let expected = 6; 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() == 6); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_space_in_size() { + let expected = 4; 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); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] @@ -82,60 +92,72 @@ fn test_failed_incorrect_arg() { #[test] fn test_at_most_shrinks() { + let expected = 4; 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); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_at_most_no_change() { + let expected = 10; let (at, mut ucmd) = at_and_ucmd!(); let mut file = at.make_file(TFILE2); file.write_all(b"1234567890").unwrap(); ucmd.args(&["--size", "<40", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); - assert!(file.seek(SeekFrom::Current(0)).unwrap() == 10); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_at_least_grows() { + let expected = 15; let (at, mut ucmd) = at_and_ucmd!(); let mut file = at.make_file(TFILE2); file.write_all(b"1234567890").unwrap(); ucmd.args(&["--size", ">15", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); - assert!(file.seek(SeekFrom::Current(0)).unwrap() == 15); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_at_least_no_change() { + let expected = 10; 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() == 10); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_round_down() { + let expected = 8; 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() == 8); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } #[test] fn test_round_up() { + let expected = 12; 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() == 12); + let actual = file.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } From 26fc8e57c7746305901f1933c9c14f53b4d32e32 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 10:03:29 -0500 Subject: [PATCH 138/399] Fix NumCache and Serde JSON conflict by disabling NumCache during extsort general numeric compares --- src/uu/sort/src/external_sort/LICENSE | 19 ++ src/uu/sort/src/external_sort/mod.rs | 246 ++++++++++++++++++++++++++ src/uu/sort/src/sort.rs | 36 ++-- tests/by-util/test_sort.rs | 12 ++ 4 files changed, 294 insertions(+), 19 deletions(-) create mode 100644 src/uu/sort/src/external_sort/LICENSE create mode 100644 src/uu/sort/src/external_sort/mod.rs diff --git a/src/uu/sort/src/external_sort/LICENSE b/src/uu/sort/src/external_sort/LICENSE new file mode 100644 index 000000000..e26c89c9f --- /dev/null +++ b/src/uu/sort/src/external_sort/LICENSE @@ -0,0 +1,19 @@ +Copyright 2018 Battelle Memorial Institute + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs new file mode 100644 index 000000000..9fcaadcc3 --- /dev/null +++ b/src/uu/sort/src/external_sort/mod.rs @@ -0,0 +1,246 @@ +use std::{clone::Clone}; +use std::cmp::Ordering::Less; +use std::collections::VecDeque; +use std::error::Error; +use std::fs::{File, OpenOptions}; +use std::io::SeekFrom::Start; +use std::io::{BufRead, BufReader, Seek, Write}; +use std::marker::PhantomData; +use std::path::PathBuf; + +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json; +use tempdir::TempDir; + +use super::{GlobalSettings, Line}; + +/// Trait for types that can be used by +/// [ExternalSorter](struct.ExternalSorter.html). Must be sortable, cloneable, +/// serializeable, and able to report on it's size +pub trait ExternallySortable: Clone + Serialize + DeserializeOwned { + /// Get the size, in bytes, of this object (used to constrain the buffer + /// used in the external sort). + fn get_size(&self) -> u64; +} + +/// Iterator that provides sorted `T`s +pub struct ExtSortedIterator { + buffers: Vec>, + chunk_offsets: Vec, + max_per_chunk: u64, + chunks: u64, + tmp_dir: TempDir, + settings: GlobalSettings, + failed: bool, +} + +impl Iterator for ExtSortedIterator +where + Line: ExternallySortable, +{ + type Item = Result>; + + /// # Errors + /// + /// This method can fail due to issues reading intermediate sorted chunks + /// from disk, or due to serde deserialization issues + fn next(&mut self) -> Option { + if self.failed { + return None; + } + // fill up any empty buffers + let mut empty = true; + for chunk_num in 0..self.chunks { + if self.buffers[chunk_num as usize].is_empty() { + let mut f = match File::open(self.tmp_dir.path().join(chunk_num.to_string())) { + Ok(f) => f, + Err(e) => { + self.failed = true; + return Some(Err(Box::new(e))); + } + }; + match f.seek(Start(self.chunk_offsets[chunk_num as usize])) { + Ok(_) => (), + Err(e) => { + self.failed = true; + return Some(Err(Box::new(e))); + } + } + let bytes_read = + match fill_buff(&mut self.buffers[chunk_num as usize], f, self.max_per_chunk) { + Ok(bytes_read) => bytes_read, + Err(e) => { + self.failed = true; + return Some(Err(e)); + } + }; + self.chunk_offsets[chunk_num as usize] += bytes_read; + if !self.buffers[chunk_num as usize].is_empty() { + empty = false; + } + } else { + empty = false; + } + } + if empty { + return None; + } + + // find the next record to write + // check is_empty() before unwrap()ing + let mut idx = 0; + for chunk_num in 0..self.chunks as usize { + if !self.buffers[chunk_num].is_empty() { + if self.buffers[idx].is_empty() || (super::compare_by)( + self.buffers[chunk_num].front().unwrap(), + self.buffers[idx].front().unwrap(), + &self.settings + ) == Less + { + idx = chunk_num; + } + } + } + + // unwrap due to checks above + let r = self.buffers[idx].pop_front().unwrap(); + Some(Ok(r)) + } +} + +/// Perform an external sort on an unsorted stream of incoming data +pub struct ExternalSorter +where + Line: ExternallySortable, +{ + tmp_dir: Option, + buffer_bytes: u64, + phantom: PhantomData, + settings: GlobalSettings, +} + +impl ExternalSorter +where + Line: ExternallySortable, +{ + /// Create a new `ExternalSorter` with a specified memory buffer and + /// temporary directory + pub fn new(buffer_bytes: u64, tmp_dir: Option, settings: GlobalSettings) -> ExternalSorter { + ExternalSorter { + buffer_bytes, + tmp_dir, + phantom: PhantomData, + settings, + } + } + + /// Sort (based on `compare`) the `T`s provided by `unsorted` and return an + /// iterator + /// + /// # Errors + /// + /// This method can fail due to issues writing intermediate sorted chunks + /// to disk, or due to serde serialization issues + pub fn sort_by(&self, unsorted: I, settings: GlobalSettings) -> Result, Box> + where + I: Iterator, + { + let tmp_dir = match self.tmp_dir { + Some(ref p) => TempDir::new_in(p, "uutils_sort")?, + None => TempDir::new("uutils_sort")?, + }; + // creating the thing we need to return first due to the face that we need to + // borrow tmp_dir and move it out + let mut iter = ExtSortedIterator { + buffers: Vec::new(), + chunk_offsets: Vec::new(), + max_per_chunk: 0, + chunks: 0, + tmp_dir, + settings, + failed: false, + }; + + { + let mut total_read = 0; + let mut chunk = Vec::new(); + + // make the initial chunks on disk + for seq in unsorted { + total_read += seq.get_size(); + chunk.push(seq); + + if total_read >= self.buffer_bytes { + super::sort_by(&mut chunk, &self.settings); + self.write_chunk( + &iter.tmp_dir.path().join(iter.chunks.to_string()), + &mut chunk, + )?; + chunk.clear(); + total_read = 0; + iter.chunks += 1; + } + } + // write the last chunk + if chunk.len() > 0 { + super::sort_by(&mut chunk, &self.settings); + self.write_chunk( + &iter.tmp_dir.path().join(iter.chunks.to_string()), + &mut chunk, + )?; + iter.chunks += 1; + } + + // initialize buffers for each chunk + iter.max_per_chunk = self.buffer_bytes.checked_div(iter.chunks).unwrap_or(self.buffer_bytes); + iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; + iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; + for chunk_num in 0..iter.chunks { + let offset = fill_buff( + &mut iter.buffers[chunk_num as usize], + File::open(iter.tmp_dir.path().join(chunk_num.to_string()))?, + iter.max_per_chunk, + )?; + iter.chunk_offsets[chunk_num as usize] = offset; + } + } + + Ok(iter) + } + + fn write_chunk(&self, file: &PathBuf, chunk: &mut Vec) -> Result<(), Box> { + let mut new_file = OpenOptions::new().create(true).append(true).open(file)?; + for s in chunk { + let mut serialized = serde_json::to_string(&s).expect("JSON write error: "); + serialized.push_str("\n"); + new_file.write_all(serialized.as_bytes())?; + } + + Ok(()) + } +} + +fn fill_buff(vec: &mut VecDeque, file: File, max_bytes: u64) -> Result> +where + Line: ExternallySortable, +{ + let mut total_read = 0; + let mut bytes_read = 0; + for line in BufReader::new(file).lines() { + let line_s = line?; + bytes_read += line_s.len() + 1; + // This is where the bad stuff happens usually + let deserialized: Line = match serde_json::from_str(&line_s) { + Ok(x) => x, + Err(err) => panic!("JSON read error: {}", err), + }; + total_read += deserialized.get_size(); + vec.push_back(deserialized); + if total_read > max_bytes { + break; + } + } + + Ok(bytes_read as u64) +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8c3a0cf7f..e77271557 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -91,7 +91,7 @@ static NEGATIVE: char = '-'; static POSITIVE: char = '+'; static DEFAULT_TMPDIR: &str = r"/tmp"; -// 16GB buffer for Vec before we dump to disk +// 16GB buffer for Vec before we dump to disk, never used static DEFAULT_BUF_SIZE: usize = 16000000000; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone)] @@ -292,14 +292,6 @@ impl ExternallySortable for Line { } } -impl PartialEq for Line { - fn eq(&self, other: &Self) -> bool { - self.line == other.line - } -} - -impl Eq for Line {} - impl Line { fn new(line: String, settings: &GlobalSettings) -> Self { let fields = if settings @@ -343,7 +335,7 @@ impl Line { ); range.shorten(num_range); NumCache::WithInfo(info) - } else if selector.settings.mode == SortMode::GeneralNumeric { + } else if selector.settings.mode == SortMode::GeneralNumeric && settings.buffer_size == DEFAULT_BUF_SIZE { NumCache::AsF64(permissive_f64_parse(get_leading_gen(range.get_str(&line)))) } else { NumCache::None @@ -1103,11 +1095,12 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { let external_sorter = ExternalSorter::new(settings.buffer_size as u64, Some(settings.tmp_dir.clone()), settings.clone()); - let iter = external_sorter.sort_by(unsorted.into_iter(), settings.clone()).unwrap(); - let vec = iter.filter(|x| x.is_ok() ) - .map(|x| x.unwrap()) - .collect::>(); - vec + let iter = external_sorter + .sort_by(unsorted.into_iter(), settings.clone()) + .unwrap() + .map(|x| x.unwrap()) + .collect::>(); + iter } fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { @@ -1130,10 +1123,15 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering (a_str, a_selection.num_cache.as_num_info()), (b_str, b_selection.num_cache.as_num_info()), ), - SortMode::GeneralNumeric => general_numeric_compare( - a_selection.num_cache.as_f64(), - b_selection.num_cache.as_f64(), - ), + // serde JSON has issues with f64 null values, so caching them won't work for us with ext sort + SortMode::GeneralNumeric => + if global_settings.buffer_size == DEFAULT_BUF_SIZE { + general_numeric_compare(a_selection.num_cache.as_f64(), + b_selection.num_cache.as_f64()) + } else { + general_numeric_compare(permissive_f64_parse(get_leading_gen(a_str)), + permissive_f64_parse(get_leading_gen(b_str))) + }, SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), SortMode::Default => default_compare(a_str, b_str), diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index c76ab219a..63883cd63 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -51,6 +51,18 @@ fn test_human_numeric_whitespace() { test_helper("human-numeric-whitespace", "-h"); } +// This doesn't test the ext sort feature as such, just this codepath where +// ext sort can fail when reading back JSON if it finds a null value +#[test] +fn test_extsort_as64_bailout() { + new_ucmd!() + .arg("-g") + .arg("-S 10K") + .arg("multiple_decimals_general.txt") + .succeeds() + .stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); +} + #[test] fn test_multiple_decimals_general() { new_ucmd!() From cb0c667da5bdaa396921cb7c894dcae6b6be6a98 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 10:12:03 -0500 Subject: [PATCH 139/399] Ran Rustfmt --- Cargo.lock | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4aefd98c..fab7d57d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,9 +1432,6 @@ name = "smallvec" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" -dependencies = [ - "serde", -] [[package]] name = "strsim" From 094d9a9e476882d6a437f3889681b3bda5ec9867 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 12:27:11 -0500 Subject: [PATCH 140/399] Fix bug in human_numeric convert --- Cargo.lock | 3 ++ src/uu/sort/Cargo.toml | 2 +- src/uu/sort/src/sort.rs | 60 +++++++++++++++++--------------------- tests/by-util/test_sort.rs | 4 +-- 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fab7d57d1..d4aefd98c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,6 +1432,9 @@ name = "smallvec" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +dependencies = [ + "serde", +] [[package]] name = "strsim" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 02ab385da..80ffc92c9 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -23,7 +23,7 @@ clap = "2.33" fnv = "1.0.7" itertools = "0.10.0" semver = "0.9.0" -smallvec = "1.6.1" +smallvec = { version="1.6.1", features=["serde"] } unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 049c09970..8d513b837 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -27,7 +27,7 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; -use serde::{Deserializer, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::BinaryHeap; @@ -41,6 +41,7 @@ use std::ops::Range; use std::path::Path; use unicode_width::UnicodeWidthStr; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() +use std::path::PathBuf; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -133,22 +134,20 @@ impl GlobalSettings { // It's back to do conversions for command line opts! // Probably want to do through numstrcmp somehow now? fn human_numeric_convert(a: &str) -> usize { - let num_part = leading_num_common(a); - let (_, s) = a.split_at(num_part.len()); - let num_part = permissive_f64_parse(num_part); - let suffix = match s.parse().unwrap_or('\0') { + let num_str = &a[get_leading_gen(a)]; + let (_, suf_str) = a.split_at(num_str.len()); + let num_usize = num_str.parse::().expect("Error parsing buffer size: "); + let suf_usize: usize = match suf_str.to_uppercase().as_str() { // SI Units - 'K' | 'k' => 1E3, - 'M' => 1E6, - 'G' => 1E9, - 'T' => 1E12, - 'P' => 1E15, - 'E' => 1E18, - 'Z' => 1E21, - 'Y' => 1E24, - _ => 1f64, + "K" => 1000usize, + "M" => 1000000usize, + "G" => 1000000000usize, + "T" => 1000000000000usize, + "P" => 1000000000000000usize, + "E" => 1000000000000000000usize, + _ => 1usize, }; - num_part as usize * suffix as usize + num_usize * suf_usize } } @@ -236,22 +235,13 @@ impl SelectionRange { } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone)] enum NumCache { - #[serde(deserialize_with="bailout_parse_f64")] AsF64(GeneralF64ParseResult), WithInfo(NumInfo), None, } -// Only used when serde can't parse a null value -fn bailout_parse_f64<'de, D>(d: D) -> Result where D: Deserializer<'de> { - Deserialize::deserialize(d) - .map(|x: Option<_>| { - x.unwrap_or(0f64) - }) -} - impl NumCache { fn as_f64(&self) -> GeneralF64ParseResult { match self { @@ -266,7 +256,7 @@ impl NumCache { } } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone)] struct Selection { range: SelectionRange, num_cache: NumCache, @@ -1218,7 +1208,7 @@ fn exec(files: Vec, settings: GlobalSettings) -> i32 { if settings.merge { if settings.unique { print_sorted( - file_merger.dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal), + file_merger.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), &settings, ) } else { @@ -1228,7 +1218,7 @@ fn exec(files: Vec, settings: GlobalSettings) -> i32 { print_sorted( lines .into_iter() - .dedup_by(|a, b| compare_by(a, b, settings) == Ordering::Equal), + .dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), &settings, ) } else { @@ -1303,11 +1293,15 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering // serde JSON has issues with f64 null values, so caching them won't work for us with ext sort SortMode::GeneralNumeric => if global_settings.buffer_size == DEFAULT_BUF_SIZE { - general_numeric_compare(a_selection.num_cache.as_f64(), - b_selection.num_cache.as_f64()) + general_numeric_compare( + a_selection.num_cache.as_f64(), + b_selection.num_cache.as_f64() + ) } else { - general_numeric_compare(permissive_f64_parse(get_leading_gen(a_str)), - permissive_f64_parse(get_leading_gen(b_str))) + general_numeric_compare( + general_f64_parse(&a_str[get_leading_gen(a_str)]), + general_f64_parse(&b_str[get_leading_gen(b_str)]) + ) }, SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), @@ -1385,7 +1379,7 @@ fn get_leading_gen(input: &str) -> Range { leading_whitespace_len..input.len() } -#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, PartialOrd)] enum GeneralF64ParseResult { Invalid, NaN, diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 894626c55..c5b63205f 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -33,7 +33,7 @@ fn test_helper(file_name: &str, args: &str) { fn test_larger_than_specified_segment() { new_ucmd!() .arg("-n") - .arg("-S 50K") + .arg("-S 50M") .arg("ext_sort.txt") .succeeds() .stdout_is_fixture(format!("{}", "ext_sort.expected")); @@ -67,7 +67,7 @@ fn test_extsort_as64_bailout() { .arg("-S 10K") .arg("multiple_decimals_general.txt") .succeeds() - .stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n"); + .stdout_is_fixture("multiple_decimals_general.expected"); } #[test] From f0a473f40e26a1dcd684244305e3a214c5e0552f Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 12:38:43 -0500 Subject: [PATCH 141/399] Fix tests --- src/uu/sort/src/external_sort/mod.rs | 32 ++++++++++++++++++------ src/uu/sort/src/sort.rs | 37 +++++++++++++++++----------- tests/by-util/test_sort.rs | 3 ++- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 9fcaadcc3..e2595fc78 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -1,4 +1,4 @@ -use std::{clone::Clone}; +use std::clone::Clone; use std::cmp::Ordering::Less; use std::collections::VecDeque; use std::error::Error; @@ -92,10 +92,11 @@ where let mut idx = 0; for chunk_num in 0..self.chunks as usize { if !self.buffers[chunk_num].is_empty() { - if self.buffers[idx].is_empty() || (super::compare_by)( + if self.buffers[idx].is_empty() + || (super::compare_by)( self.buffers[chunk_num].front().unwrap(), self.buffers[idx].front().unwrap(), - &self.settings + &self.settings, ) == Less { idx = chunk_num; @@ -106,7 +107,7 @@ where // unwrap due to checks above let r = self.buffers[idx].pop_front().unwrap(); Some(Ok(r)) - } + } } /// Perform an external sort on an unsorted stream of incoming data @@ -126,7 +127,11 @@ where { /// Create a new `ExternalSorter` with a specified memory buffer and /// temporary directory - pub fn new(buffer_bytes: u64, tmp_dir: Option, settings: GlobalSettings) -> ExternalSorter { + pub fn new( + buffer_bytes: u64, + tmp_dir: Option, + settings: GlobalSettings, + ) -> ExternalSorter { ExternalSorter { buffer_bytes, tmp_dir, @@ -142,7 +147,11 @@ where /// /// This method can fail due to issues writing intermediate sorted chunks /// to disk, or due to serde serialization issues - pub fn sort_by(&self, unsorted: I, settings: GlobalSettings) -> Result, Box> + pub fn sort_by( + &self, + unsorted: I, + settings: GlobalSettings, + ) -> Result, Box> where I: Iterator, { @@ -193,7 +202,10 @@ where } // initialize buffers for each chunk - iter.max_per_chunk = self.buffer_bytes.checked_div(iter.chunks).unwrap_or(self.buffer_bytes); + iter.max_per_chunk = self + .buffer_bytes + .checked_div(iter.chunks) + .unwrap_or(self.buffer_bytes); iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; for chunk_num in 0..iter.chunks { @@ -221,7 +233,11 @@ where } } -fn fill_buff(vec: &mut VecDeque, file: File, max_bytes: u64) -> Result> +fn fill_buff( + vec: &mut VecDeque, + file: File, + max_bytes: u64, +) -> Result> where Line: ExternallySortable, { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8d513b837..a519bece5 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,11 +15,11 @@ #[macro_use] extern crate uucore; -mod numeric_str_cmp; mod external_sort; +mod numeric_str_cmp; -use external_sort::{ExternalSorter, ExternallySortable}; use clap::{App, Arg}; +use external_sort::{ExternalSorter, ExternallySortable}; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -39,9 +39,9 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; use std::mem::replace; use std::ops::Range; use std::path::Path; +use std::path::PathBuf; use unicode_width::UnicodeWidthStr; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() -use std::path::PathBuf; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -136,7 +136,9 @@ impl GlobalSettings { fn human_numeric_convert(a: &str) -> usize { let num_str = &a[get_leading_gen(a)]; let (_, suf_str) = a.split_at(num_str.len()); - let num_usize = num_str.parse::().expect("Error parsing buffer size: "); + let num_usize = num_str + .parse::() + .expect("Error parsing buffer size: "); let suf_usize: usize = match suf_str.to_uppercase().as_str() { // SI Units "K" => 1000usize, @@ -323,7 +325,9 @@ impl Line { ); range.shorten(num_range); NumCache::WithInfo(info) - } else if selector.settings.mode == SortMode::GeneralNumeric && settings.buffer_size == DEFAULT_BUF_SIZE { + } else if selector.settings.mode == SortMode::GeneralNumeric + && settings.buffer_size == DEFAULT_BUF_SIZE + { let str = range.get_str(&line); NumCache::AsF64(general_f64_parse(&str[get_leading_gen(str)])) } else { @@ -1050,7 +1054,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .value_of(OPT_BUF_SIZE) .map(String::from) .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); - + GlobalSettings::human_numeric_convert(&input) } } @@ -1261,13 +1265,17 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { - let external_sorter = ExternalSorter::new(settings.buffer_size as u64, Some(settings.tmp_dir.clone()), settings.clone()); + let external_sorter = ExternalSorter::new( + settings.buffer_size as u64, + Some(settings.tmp_dir.clone()), + settings.clone(), + ); let iter = external_sorter .sort_by(unsorted.into_iter(), settings.clone()) .unwrap() .map(|x| x.unwrap()) .collect::>(); - iter + iter } fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { @@ -1291,18 +1299,19 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering (b_str, b_selection.num_cache.as_num_info()), ), // serde JSON has issues with f64 null values, so caching them won't work for us with ext sort - SortMode::GeneralNumeric => + SortMode::GeneralNumeric => { if global_settings.buffer_size == DEFAULT_BUF_SIZE { general_numeric_compare( a_selection.num_cache.as_f64(), - b_selection.num_cache.as_f64() - ) + b_selection.num_cache.as_f64(), + ) } else { general_numeric_compare( general_f64_parse(&a_str[get_leading_gen(a_str)]), - general_f64_parse(&b_str[get_leading_gen(b_str)]) - ) - }, + general_f64_parse(&b_str[get_leading_gen(b_str)]), + ) + } + } SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), SortMode::Default => default_compare(a_str, b_str), diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index c5b63205f..86951c1a4 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -33,7 +33,8 @@ fn test_helper(file_name: &str, args: &str) { fn test_larger_than_specified_segment() { new_ucmd!() .arg("-n") - .arg("-S 50M") + .arg("-S") + .arg("50K") .arg("ext_sort.txt") .succeeds() .stdout_is_fixture(format!("{}", "ext_sort.expected")); From bbcca3eefd98c06984cd22b016513dd6c10435e7 Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Mon, 29 Mar 2021 22:26:55 +0200 Subject: [PATCH 142/399] ls: Implements https://github.com/uutils/coreutils/issues/1880 extension sorting. --- src/uu/ls/src/ls.rs | 32 ++++++++++++++++++++++-- tests/by-util/test_ls.rs | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0351227eb..9f2c2d993 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -74,6 +74,7 @@ pub mod options { pub static TIME: &str = "t"; pub static NONE: &str = "U"; pub static VERSION: &str = "v"; + pub static EXTENSION: &str = "X"; } pub mod time { pub static ACCESS: &str = "u"; @@ -134,6 +135,7 @@ enum Sort { Size, Time, Version, + Extension, } enum SizeFormat { @@ -277,6 +279,7 @@ impl Config { "time" => Sort::Time, "size" => Sort::Size, "version" => Sort::Version, + "extension" => Sort::Extension, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --sort"), } @@ -288,6 +291,8 @@ impl Config { Sort::None } else if options.is_present(options::sort::VERSION) { Sort::Version + } else if options.is_present(options::sort::EXTENSION) { + Sort::Extension } else { Sort::Name }; @@ -752,10 +757,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg( Arg::with_name(options::SORT) .long(options::SORT) - .help("Sort by : name, none (-U), time (-t) or size (-S)") + .help("Sort by : name, none (-U), time (-t), size (-S) or extension (-X)") .value_name("field") .takes_value(true) - .possible_values(&["name", "none", "time", "size", "version"]) + .possible_values(&["name", "none", "time", "size", "version", "extension"]) .require_equals(true) .overrides_with_all(&[ options::SORT, @@ -763,6 +768,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::TIME, options::sort::NONE, options::sort::VERSION, + options::sort::EXTENSION, ]) ) .arg( @@ -775,6 +781,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::TIME, options::sort::NONE, options::sort::VERSION, + options::sort::EXTENSION, ]) ) .arg( @@ -787,6 +794,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::TIME, options::sort::NONE, options::sort::VERSION, + options::sort::EXTENSION, ]) ) .arg( @@ -799,6 +807,20 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::TIME, options::sort::NONE, options::sort::VERSION, + options::sort::EXTENSION, + ]) + ) + .arg( + Arg::with_name(options::sort::EXTENSION) + .short(options::sort::EXTENSION) + .help("Sort alphabetically by entry extension.") + .overrides_with_all(&[ + options::SORT, + options::sort::SIZE, + options::sort::TIME, + options::sort::NONE, + options::sort::VERSION, + options::sort::EXTENSION, ]) ) .arg( @@ -813,6 +835,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::sort::TIME, options::sort::NONE, options::sort::VERSION, + options::sort::EXTENSION, ]) ) @@ -1141,6 +1164,11 @@ fn sort_entries(entries: &mut Vec, config: &Config) { // The default sort in GNU ls is case insensitive Sort::Name => entries.sort_by_cached_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::Extension => entries.sort_by(|a, b| { + a.extension() + .cmp(&b.extension()) + .then(a.to_string_lossy().cmp(&b.to_string_lossy())) + }), Sort::None => {} } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 09e02f264..364a71d47 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1681,3 +1681,57 @@ fn test_ls_deref_command_line_dir() { assert!(!result.stdout_str().ends_with("sym_dir")); } + +#[test] +fn test_ls_sort_extension() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + for filename in &[ + "file1", + "file2", + "anotherFile", + ".hidden", + ".file.1", + ".file.2", + "file.1", + "file.2", + "anotherFile.1", + "anotherFile.2", + "file.ext", + "file.debug", + "anotherFile.ext", + "anotherFile.debug", + ] { + at.touch(filename); + } + + let expected = vec![ + ".", + "..", + ".hidden", + "anotherFile", + "file1", + "file2", + ".file.1", + "anotherFile.1", + "file.1", + ".file.2", + "anotherFile.2", + "file.2", + "anotherFile.debug", + "file.debug", + "anotherFile.ext", + "file.ext", + "", // because of '\n' at the end of the output + ]; + + let result = scene.ucmd().arg("-1aX").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert_eq!(result.stdout.split('\n').collect::>(), expected,); + + let result = scene.ucmd().arg("-1a").arg("--sort=extension").run(); + println!("stderr = {:?}", result.stderr); + println!("stdout = {:?}", result.stdout); + assert_eq!(result.stdout.split('\n').collect::>(), expected,); +} \ No newline at end of file From 9c221148a86ff7b2003c1913aaebcb1a6be7007c Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Tue, 30 Mar 2021 22:54:06 +0200 Subject: [PATCH 143/399] ls: Extension sorting, use file_stem() instead of to_string_lossy() --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 9f2c2d993..145ed0428 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1167,7 +1167,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { Sort::Extension => entries.sort_by(|a, b| { a.extension() .cmp(&b.extension()) - .then(a.to_string_lossy().cmp(&b.to_string_lossy())) + .then(a.file_stem().cmp(&b.file_stem())) }), Sort::None => {} } From 2f37b85426d12ff07828d063257515653fb90cf5 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 12:58:04 -0500 Subject: [PATCH 144/399] unwrap_or_else can be an unwrap_or --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index a519bece5..bf138e0c0 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1063,7 +1063,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let result = matches .value_of(OPT_TMP_DIR) .map(String::from) - .unwrap_or_else(|| DEFAULT_TMPDIR.to_owned()); + .unwrap_or(DEFAULT_TMPDIR.to_owned()); settings.tmp_dir = PathBuf::from(result); } else { for (key, value) in env::vars_os() { From ab594b7b4c8e0b103b336120076303d7da8fb8fa Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 13:08:55 -0500 Subject: [PATCH 145/399] Fix test comment --- tests/by-util/test_sort.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 86951c1a4..865e2be21 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -59,8 +59,8 @@ fn test_human_numeric_whitespace() { test_helper("human-numeric-whitespace", "-h"); } -// This doesn't test the ext sort feature as such, just this codepath where -// ext sort can fail when reading back JSON if it finds a null value +// This tests the ext sort feature, but it also tests where +// serde might fail when reading back JSON if it finds a null value #[test] fn test_extsort_as64_bailout() { new_ucmd!() From 43f3f7e01c0707d6617ec5239f683d2f6790a498 Mon Sep 17 00:00:00 2001 From: Alessandro Stoltenberg Date: Sun, 25 Apr 2021 20:13:42 +0200 Subject: [PATCH 146/399] feat2: Rebased on current master and incorporated changes done to the filetype handling. --- src/uu/ls/src/ls.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 145ed0428..3cd1e037d 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1165,9 +1165,10 @@ fn sort_entries(entries: &mut Vec, config: &Config) { Sort::Name => entries.sort_by_cached_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::Extension => entries.sort_by(|a, b| { - a.extension() - .cmp(&b.extension()) - .then(a.file_stem().cmp(&b.file_stem())) + a.p_buf + .extension() + .cmp(&b.p_buf.extension()) + .then(a.p_buf.file_stem().cmp(&b.p_buf.file_stem())) }), Sort::None => {} } From c3d7358df632f8ea3e6a82f3ab74a79a7d17d12b Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Sun, 25 Apr 2021 12:08:05 -0700 Subject: [PATCH 147/399] ls: ignore leading period when sorting by name (#2112) * ls: ignore leading period when sorting by name ls now behaves like GNU ls with respect to sorting files by ignoring leading periods when sorting by main. Added tests to ensure "touch a .a b .b ; ls" returns ".a a .b b" * Replaced clone/collect calls. --- src/uu/ls/src/ls.rs | 8 +++++++- tests/by-util/test_ls.rs | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0351227eb..6317c1975 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1139,7 +1139,13 @@ fn sort_entries(entries: &mut Vec, config: &Config) { 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_cached_key(|k| k.file_name.to_lowercase()), + Sort::Name => entries.sort_by_cached_key(|k| { + let has_dot: bool = k.file_name.starts_with('.'); + let filename_nodot: &str = &k.file_name[if has_dot { 1 } else { 0 }..]; + // We want hidden files to appear before regular files of the same + // name, so we need to negate the "has_dot" variable. + (filename_nodot.to_lowercase(), !has_dot) + }), Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)), Sort::None => {} } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 09e02f264..da45934e9 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -481,6 +481,21 @@ fn test_ls_sort_name() { .arg("--sort=name") .succeeds() .stdout_is(["test-1", "test-2", "test-3\n"].join(sep)); + + // Order of a named sort ignores leading dots. + let scene_dot = TestScenario::new(util_name!()); + let at = &scene_dot.fixtures; + at.touch(".a"); + at.touch("a"); + at.touch(".b"); + at.touch("b"); + + scene_dot + .ucmd() + .arg("--sort=name") + .arg("-A") + .succeeds() + .stdout_is([".a", "a", ".b", "b\n"].join(sep)); } #[test] From 733949b2e7f4d1672fdcf8e0521d8b25b8ffab5a Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 15:13:27 -0500 Subject: [PATCH 148/399] Add dynamic buffer adjustment, fix test comment --- src/uu/sort/src/external_sort/mod.rs | 19 +++++++++++++------ src/uu/sort/src/sort.rs | 13 ++++++------- tests/by-util/test_sort.rs | 6 +++--- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index e2595fc78..f5a3a03af 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -174,13 +174,23 @@ where { let mut total_read = 0; let mut chunk = Vec::new(); + // Initial buffer is specified by user + let mut adjusted_buffer_size = self.buffer_bytes; // make the initial chunks on disk for seq in unsorted { - total_read += seq.get_size(); + let seq_size = seq.get_size(); + total_read += seq_size; + // Grow buffer size for a Line larger than buffer + adjusted_buffer_size = + if adjusted_buffer_size < seq_size { + seq_size + } else { + adjusted_buffer_size + }; chunk.push(seq); - if total_read >= self.buffer_bytes { + if total_read >= adjusted_buffer_size { super::sort_by(&mut chunk, &self.settings); self.write_chunk( &iter.tmp_dir.path().join(iter.chunks.to_string()), @@ -247,10 +257,7 @@ where let line_s = line?; bytes_read += line_s.len() + 1; // This is where the bad stuff happens usually - let deserialized: Line = match serde_json::from_str(&line_s) { - Ok(x) => x, - Err(err) => panic!("JSON read error: {}", err), - }; + let deserialized: Line = serde_json::from_str(&line_s).expect("JSON read error: "); total_read += deserialized.get_size(); vec.push_back(deserialized); if total_read > max_bytes { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bf138e0c0..0be91eef6 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -141,13 +141,12 @@ impl GlobalSettings { .expect("Error parsing buffer size: "); let suf_usize: usize = match suf_str.to_uppercase().as_str() { // SI Units - "K" => 1000usize, - "M" => 1000000usize, - "G" => 1000000000usize, - "T" => 1000000000000usize, - "P" => 1000000000000000usize, - "E" => 1000000000000000000usize, - _ => 1usize, + "K" => 1024usize, + "M" => 1024000usize, + "G" => 1024000000usize, + "T" => 1024000000000usize, + // GNU regards empty human numeric value as 1024 bytes + _ => 1024usize, }; num_usize * suf_usize } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 865e2be21..cd3a3a496 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -59,13 +59,13 @@ fn test_human_numeric_whitespace() { test_helper("human-numeric-whitespace", "-h"); } -// This tests the ext sort feature, but it also tests where -// serde might fail when reading back JSON if it finds a null value +// This tests where serde often fails when reading back JSON +// if it finds a null value #[test] fn test_extsort_as64_bailout() { new_ucmd!() .arg("-g") - .arg("-S 10K") + .arg("-S 5K") .arg("multiple_decimals_general.txt") .succeeds() .stdout_is_fixture("multiple_decimals_general.expected"); From 5fb7014c2b37cfbd4f617ddf0ba6c892770ddcd7 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 15:42:36 -0500 Subject: [PATCH 149/399] Add a BufWriter for writes out to temp files --- src/uu/sort/src/external_sort/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index f5a3a03af..222da5b58 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use std::error::Error; use std::fs::{File, OpenOptions}; use std::io::SeekFrom::Start; -use std::io::{BufRead, BufReader, Seek, Write}; +use std::io::{BufRead, BufReader, BufWriter, Seek, Write}; use std::marker::PhantomData; use std::path::PathBuf; @@ -232,12 +232,14 @@ where } fn write_chunk(&self, file: &PathBuf, chunk: &mut Vec) -> Result<(), Box> { - let mut new_file = OpenOptions::new().create(true).append(true).open(file)?; + let new_file = OpenOptions::new().create(true).append(true).open(file)?; + let mut buf_write = Box::new(BufWriter::new(new_file)) as Box; for s in chunk { let mut serialized = serde_json::to_string(&s).expect("JSON write error: "); serialized.push_str("\n"); - new_file.write_all(serialized.as_bytes())?; + buf_write.write(serialized.as_bytes())?; } + buf_write.flush()?; Ok(()) } From dbdac2226220c8182b51928f1b5e92fe63bdd5ec Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 15:48:20 -0500 Subject: [PATCH 150/399] Add back unstable sort --- src/uu/sort/src/sort.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 0be91eef6..cbd02c18a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1278,7 +1278,11 @@ fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { } fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { - lines.par_sort_by(|a, b| compare_by(a, b, &settings)) + if settings.stable || settings.unique { + lines.par_sort_by(|a, b| compare_by(a, b, &settings)) + } else { + lines.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) + } } fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { From 6f82cd4f15f56029bd524b0e962a95553caf5a6c Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 16:27:36 -0500 Subject: [PATCH 151/399] Fix errors for usize on 32bit platforms --- src/uu/sort/src/sort.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index cbd02c18a..c24d930dc 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -93,8 +93,8 @@ static NEGATIVE: char = '-'; static POSITIVE: char = '+'; static DEFAULT_TMPDIR: &str = r"/tmp"; -// 16GB buffer for Vec before we dump to disk, never used -static DEFAULT_BUF_SIZE: usize = 16000000000; +// 4GB buffer for Vec before we dump to disk, never used +static DEFAULT_BUF_SIZE: usize = 4000000000; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum SortMode { @@ -141,10 +141,10 @@ impl GlobalSettings { .expect("Error parsing buffer size: "); let suf_usize: usize = match suf_str.to_uppercase().as_str() { // SI Units + "B" => 1usize, "K" => 1024usize, "M" => 1024000usize, "G" => 1024000000usize, - "T" => 1024000000000usize, // GNU regards empty human numeric value as 1024 bytes _ => 1024usize, }; @@ -1046,8 +1046,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if matches.is_present(OPT_BUF_SIZE) { - // 16G is the default in memory buffer. - // Although the "default" is never used settings.buffer_size = { let input = matches .value_of(OPT_BUF_SIZE) @@ -1277,11 +1275,11 @@ fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { iter } -fn sort_by(lines: &mut Vec, settings: &GlobalSettings) { +fn sort_by(unsorted: &mut Vec, settings: &GlobalSettings) { if settings.stable || settings.unique { - lines.par_sort_by(|a, b| compare_by(a, b, &settings)) + unsorted.par_sort_by(|a, b| compare_by(a, b, &settings)) } else { - lines.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) + unsorted.par_sort_unstable_by(|a, b| compare_by(a, b, &settings)) } } From 368e984fac148aa10b898fea9927d7680ad63723 Mon Sep 17 00:00:00 2001 From: Christopher Regali <60792386+ChrisRega@users.noreply.github.com> Date: Sun, 25 Apr 2021 23:28:42 +0200 Subject: [PATCH 152/399] Change unchecked unwrapping to unwrap_or_default for Args-trait (#1845) (#1852) * Change unchecked unwrapping to unwrap_or_default for argument parsing (resolving #1845) * Added unit-testing for the collect_str function on invalid utf8 OsStrs * Added a warning-message for identification purpose to the collect_str method. * - Add removal of wrongly encoded empty strings to basename - Add testing of broken encoding to basename - Changed UCommand to use collect_str in args method to allow for integration testing of that method - Change UCommand to use unwarp_or_default in arg method to match the behaviour of collect_str * Trying out a new pattern for convert_str for getting a feeling of how the API feels with more control * Adding convenience API for compact calls * Add new API to everywhere, fix test for basename * Added unit-testing for the conversion options * Added unit-testing for the conversion options for windows * fixed compilation and some merge hiccups * Remove windows tests in order to make merge request build * Fix formatting to match rustfmt for the merged file * Improve documentation of the collect_str method and the unit-tests * Fix compilation problems with test Co-authored-by: Christopher Regali Co-authored-by: Sylvestre Ledru --- src/uu/arch/src/arch.rs | 6 +- src/uu/base32/src/base32.rs | 4 +- src/uu/base64/src/base64.rs | 4 +- src/uu/basename/src/basename.rs | 6 +- src/uu/cat/src/cat.rs | 5 +- src/uu/chgrp/src/chgrp.rs | 5 +- src/uu/chmod/src/chmod.rs | 5 +- src/uu/chown/src/chown.rs | 5 +- src/uu/chroot/src/chroot.rs | 6 +- src/uu/cksum/src/cksum.rs | 5 +- src/uu/comm/src/comm.rs | 4 + src/uu/csplit/src/csplit.rs | 5 +- src/uu/cut/src/cut.rs | 5 +- src/uu/dircolors/src/dircolors.rs | 6 +- src/uu/dirname/src/dirname.rs | 5 +- src/uu/du/src/du.rs | 5 +- src/uu/echo/src/echo.rs | 4 + src/uu/expr/src/expr.rs | 6 +- src/uu/factor/src/cli.rs | 6 +- src/uu/fold/src/fold.rs | 5 +- src/uu/hostid/src/hostid.rs | 6 +- src/uu/kill/src/kill.rs | 5 +- src/uu/link/src/link.rs | 6 +- src/uu/logname/src/logname.rs | 6 +- src/uu/ls/src/ls.rs | 5 +- src/uu/mkfifo/src/mkfifo.rs | 5 +- src/uu/mknod/src/mknod.rs | 5 +- src/uu/more/src/more.rs | 4 + src/uu/nl/src/nl.rs | 5 +- src/uu/nohup/src/nohup.rs | 4 + src/uu/od/src/od.rs | 5 +- src/uu/pathchk/src/pathchk.rs | 4 + src/uu/pinky/src/pinky.rs | 3 +- src/uu/printf/src/printf.rs | 6 +- src/uu/ptx/src/ptx.rs | 5 +- src/uu/relpath/src/relpath.rs | 5 +- src/uu/shred/src/shred.rs | 5 +- src/uu/shuf/src/shuf.rs | 17 +-- src/uu/sort/src/sort.rs | 5 +- src/uu/stdbuf/src/stdbuf.rs | 4 + src/uu/sum/src/sum.rs | 5 +- src/uu/tac/src/tac.rs | 5 +- src/uu/timeout/src/timeout.rs | 6 +- src/uu/tr/src/tr.rs | 4 + src/uu/tsort/src/tsort.rs | 5 +- src/uu/tty/src/tty.rs | 5 +- src/uu/unexpand/src/unexpand.rs | 5 +- src/uu/unlink/src/unlink.rs | 5 +- src/uu/who/src/who.rs | 5 +- src/uucore/src/lib/lib.rs | 170 +++++++++++++++++++++++++++++- tests/by-util/test_basename.rs | 16 +++ tests/common/util.rs | 14 ++- 52 files changed, 402 insertions(+), 55 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 20392b11f..a4c57e282 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -10,13 +10,17 @@ extern crate uucore; use platform_info::*; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "Display machine architecture"; static SUMMARY: &str = "Determine architecture name for current machine."; static LONG_HELP: &str = ""; pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); + app!(SYNTAX, SUMMARY, LONG_HELP).parse( + args.collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(), + ); let uts = return_if_err!(1, PlatformInfo::new()); println!("{}", uts.machine().trim()); 0 diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index b47f4d4cc..b5f346f48 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -8,6 +8,7 @@ #[macro_use] extern crate uucore; use uucore::encoding::Format; +use uucore::InvalidEncodingHandling; mod base_common; @@ -25,7 +26,8 @@ static LONG_HELP: &str = " pub fn uumain(args: impl uucore::Args) -> i32 { base_common::execute( - args.collect_str(), + args.collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(), SYNTAX, SUMMARY, LONG_HELP, diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index ee60f3de5..61a8dc5cb 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -9,6 +9,7 @@ #[macro_use] extern crate uucore; use uucore::encoding::Format; +use uucore::InvalidEncodingHandling; mod base_common; @@ -26,7 +27,8 @@ static LONG_HELP: &str = " pub fn uumain(args: impl uucore::Args) -> i32 { base_common::execute( - args.collect_str(), + args.collect_str(InvalidEncodingHandling::Ignore) + .accept_any(), SYNTAX, SUMMARY, LONG_HELP, diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 7b02a7a83..68b705d53 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -11,6 +11,7 @@ extern crate uucore; use std::path::{is_separator, PathBuf}; +use uucore::InvalidEncodingHandling; static NAME: &str = "basename"; static SYNTAX: &str = "NAME [SUFFIX]"; @@ -19,8 +20,9 @@ static SUMMARY: &str = "Print NAME with any leading directory components removed static LONG_HELP: &str = ""; pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); - + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); // // Argument parsing // diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index e507c5acd..8dea096be 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -35,6 +35,7 @@ use std::net::Shutdown; use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use unix_socket::UnixStream; +use uucore::InvalidEncodingHandling; static NAME: &str = "cat"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -166,7 +167,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 592a0a905..2afef7de0 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -22,6 +22,7 @@ use std::fs::Metadata; use std::os::unix::fs::MetadataExt; use std::path::Path; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; @@ -32,7 +33,9 @@ const FTS_PHYSICAL: u8 = 1 << 1; const FTS_LOGICAL: u8 = 1 << 2; pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let mut opts = app!(SYNTAX, SUMMARY, ""); opts.optflag("c", diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index dc11be7b8..d01f0316e 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -17,6 +17,7 @@ use std::path::Path; use uucore::fs::display_permissions_unix; #[cfg(not(windows))] use uucore::mode; +use uucore::InvalidEncodingHandling; use walkdir::WalkDir; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -49,7 +50,9 @@ fn get_long_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let mut args = args.collect_str(); + let mut args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); // Before we can parse 'args' with clap (and previously getopts), // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 23fb030ba..ff9c42dd0 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -23,6 +23,7 @@ use std::os::unix::fs::MetadataExt; use std::convert::AsRef; use std::path::Path; +use uucore::InvalidEncodingHandling; static ABOUT: &str = "change file owner and group"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -67,7 +68,9 @@ fn get_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let usage = get_usage(); diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 7e672da1e..9480830e5 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -15,8 +15,8 @@ use std::ffi::CString; use std::io::Error; use std::path::Path; use std::process::Command; -use uucore::entries; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; +use uucore::{entries, InvalidEncodingHandling}; static VERSION: &str = env!("CARGO_PKG_VERSION"); static NAME: &str = "chroot"; @@ -32,7 +32,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index b6436de87..1d45c1332 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -14,6 +14,7 @@ use clap::{App, Arg}; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; +use uucore::InvalidEncodingHandling; // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 const CRC_TABLE_LEN: usize = 256; @@ -180,7 +181,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 34b4330c9..d1d07461c 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -14,6 +14,7 @@ use std::cmp::Ordering; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; +use uucore::InvalidEncodingHandling; use clap::{App, Arg, ArgMatches}; @@ -134,6 +135,9 @@ fn open_file(name: &str) -> io::Result { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index ce11ba49a..9d2f81f43 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -17,6 +17,7 @@ mod splitname; use crate::csplit_error::CsplitError; use crate::splitname::SplitName; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static SUMMARY: &str = "split a file into sections determined by context lines"; @@ -711,7 +712,9 @@ mod tests { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 5bf310daa..71c52601e 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -17,6 +17,7 @@ use std::path::Path; use self::searcher::Searcher; use uucore::ranges::Range; +use uucore::InvalidEncodingHandling; mod buffer; mod searcher; @@ -443,7 +444,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 6cb5f9e1b..a2d819620 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -53,7 +53,9 @@ pub fn guess_syntax() -> OutputFmt { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = app!(SYNTAX, SUMMARY, LONG_HELP) .optflag("b", "sh", "output Bourne shell code to set LS_COLORS") @@ -202,6 +204,8 @@ enum ParseState { Pass, } use std::collections::HashMap; +use uucore::InvalidEncodingHandling; + fn parse(lines: T, fmt: OutputFmt, fp: &str) -> Result where T: IntoIterator, diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 1cf35d0c4..5937f16ca 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -10,6 +10,7 @@ extern crate uucore; use clap::{App, Arg}; use std::path::Path; +use uucore::InvalidEncodingHandling; static NAME: &str = "dirname"; static SYNTAX: &str = "[OPTION] NAME..."; @@ -27,7 +28,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index fa3b3c80a..89dd3f739 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -25,6 +25,7 @@ use std::os::windows::fs::MetadataExt; use std::os::windows::io::AsRawHandle; use std::path::PathBuf; use std::time::{Duration, UNIX_EPOCH}; +use uucore::InvalidEncodingHandling; #[cfg(windows)] use winapi::shared::minwindef::{DWORD, LPVOID}; #[cfg(windows)] @@ -362,7 +363,9 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String { #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let syntax = format!( "[OPTION]... [FILE]... diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 4d38d7748..56cd967f4 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -13,6 +13,7 @@ use clap::{crate_version, App, Arg}; use std::io::{self, Write}; use std::iter::Peekable; use std::str::Chars; +use uucore::InvalidEncodingHandling; const NAME: &str = "echo"; const SUMMARY: &str = "display a line of text"; @@ -113,6 +114,9 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { } pub fn uumain(args: impl uucore::Args) -> i32 { + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .name(NAME) // TrailingVarArg specifies the final positional argument is a VarArg diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 4a13812d3..5d63bed80 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -8,6 +8,8 @@ #[macro_use] extern crate uucore; +use uucore::InvalidEncodingHandling; + mod syntax_tree; mod tokens; @@ -15,7 +17,9 @@ static NAME: &str = "expr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); // For expr utility we do not want getopts. // The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)` diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index 34bcf6a2d..fb7b3f192 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -14,6 +14,7 @@ use std::io::{self, stdin, stdout, BufRead, Write}; mod factor; pub(crate) use factor::*; +use uucore::InvalidEncodingHandling; mod miller_rabin; pub mod numeric; @@ -33,7 +34,10 @@ fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box i32 { - let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); + let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse( + args.collect_str(InvalidEncodingHandling::Ignore) + .accept_any(), + ); let stdout = stdout(); let mut w = io::BufWriter::new(stdout.lock()); diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index fa703eade..1f52748f1 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -14,6 +14,7 @@ use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; +use uucore::InvalidEncodingHandling; const TAB_WIDTH: usize = 8; @@ -31,7 +32,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let (args, obs_width) = handle_obsolete(&args[..]); let matches = App::new(executable!()) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 5a4909c62..551866521 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -11,6 +11,7 @@ extern crate uucore; use libc::c_long; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "[options]"; static SUMMARY: &str = ""; @@ -22,7 +23,10 @@ extern "C" { } pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); + app!(SYNTAX, SUMMARY, LONG_HELP).parse( + args.collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(), + ); hostid(); 0 } diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 9af9c74f7..916c13cc3 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -13,6 +13,7 @@ extern crate uucore; use libc::{c_int, pid_t}; use std::io::Error; use uucore::signals::ALL_SIGNALS; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "[options] [...]"; static SUMMARY: &str = ""; @@ -29,7 +30,9 @@ pub enum Mode { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let (args, obs_signal) = handle_obsolete(args); let matches = app!(SYNTAX, SUMMARY, LONG_HELP) diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 51c7acb5f..b82d7cfac 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -11,6 +11,7 @@ extern crate uucore; use std::fs::hard_link; use std::io::Error; use std::path::Path; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; static SUMMARY: &str = "Create a link named FILE2 to FILE1"; @@ -24,7 +25,10 @@ pub fn normalize_error_message(e: Error) -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); + let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse( + args.collect_str(InvalidEncodingHandling::Ignore) + .accept_any(), + ); if matches.free.len() != 2 { crash!(1, "{}", msg_wrong_number_of_arguments!(2)); } diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index c1f0c31aa..8c6a946f5 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -13,6 +13,7 @@ extern crate uucore; use std::ffi::CStr; +use uucore::InvalidEncodingHandling; extern "C" { // POSIX requires using getlogin (or equivalent code) @@ -35,7 +36,10 @@ static SUMMARY: &str = "Print user's login name"; static LONG_HELP: &str = ""; pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); + app!(SYNTAX, SUMMARY, LONG_HELP).parse( + args.collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(), + ); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6317c1975..9a3b98e66 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -503,7 +503,9 @@ impl Config { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let usage = get_usage(); @@ -1391,6 +1393,7 @@ fn get_inode(metadata: &Metadata) -> String { use std::sync::Mutex; #[cfg(unix)] use uucore::entries; +use uucore::InvalidEncodingHandling; #[cfg(unix)] fn cached_uid2usr(uid: u32) -> String { diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 14701af4d..2fdd4abec 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -11,6 +11,7 @@ extern crate uucore; use clap::{App, Arg}; use libc::mkfifo; use std::ffi::CString; +use uucore::InvalidEncodingHandling; static NAME: &str = "mkfifo"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -25,7 +26,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 98404f89f..fc6fb0870 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -16,6 +16,7 @@ use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOT use getopts::Options; use std::ffi::CString; +use uucore::InvalidEncodingHandling; static NAME: &str = "mknod"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -40,7 +41,9 @@ fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let mut opts = Options::new(); diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 59e7d0faa..eabdbee85 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -17,6 +17,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Read, Write}; extern crate nix; #[cfg(all(unix, not(target_os = "fuchsia")))] use nix::sys::termios::{self, LocalFlags, SetArg}; +use uucore::InvalidEncodingHandling; #[cfg(target_os = "redox")] extern crate redox_termios; @@ -38,6 +39,9 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 3b5b5a2e8..7c5f01811 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; use std::path::Path; +use uucore::InvalidEncodingHandling; mod helper; @@ -84,7 +85,9 @@ pub mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index afbf2541b..83153ad37 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -20,6 +20,7 @@ use std::io::Error; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive}; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Run COMMAND ignoring hangup signals."; @@ -42,6 +43,9 @@ mod options { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 36eae66ab..571ca543d 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -42,6 +42,7 @@ use crate::partialreader::*; use crate::peekreader::*; use crate::prn_char::format_ascii_dump; use clap::{self, AppSettings, Arg, ArgMatches}; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes @@ -221,7 +222,9 @@ impl OdOptions { /// parses and validates command line parameters, prepares data structures, /// opens the input and calls `odfunc` to process the input. pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let clap_opts = clap::App::new(executable!()) .version(VERSION) diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index c27e52513..5606c4d6a 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -15,6 +15,7 @@ extern crate uucore; use clap::{App, Arg}; use std::fs; use std::io::{ErrorKind, Write}; +use uucore::InvalidEncodingHandling; // operating mode enum Mode { @@ -45,6 +46,9 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 851a3cd42..3392c9a79 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -20,6 +20,7 @@ use std::fs::File; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "[OPTION]... [USER]..."; static SUMMARY: &str = "A lightweight 'finger' program; print user information."; @@ -27,7 +28,7 @@ static SUMMARY: &str = "A lightweight 'finger' program; print user information. const BUFSIZE: usize = 1024; pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args.collect_str(InvalidEncodingHandling::Ignore).accept_any(); let long_help = &format!( " diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index d947a7d83..88d18838d 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -2,6 +2,8 @@ // spell-checker:ignore (change!) each's // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr +use uucore::InvalidEncodingHandling; + mod cli; mod memo; mod tokenize; @@ -271,7 +273,9 @@ COPYRIGHT : "; pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let location = &args[0]; if args.len() <= 1 { diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 69b3f7aa1..d2aa619b4 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -17,6 +17,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::default::Default; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use uucore::InvalidEncodingHandling; static NAME: &str = "ptx"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -630,7 +631,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); // let mut opts = Options::new(); let matches = App::new(executable!()) diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 82779107a..f1b2c9a43 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -14,6 +14,7 @@ use clap::{App, Arg}; use std::env; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir. @@ -30,7 +31,9 @@ fn get_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let usage = get_usage(); let matches = App::new(executable!()) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index b89d48a10..15a4eff26 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -17,6 +17,7 @@ use std::io; use std::io::prelude::*; use std::io::SeekFrom; use std::path::{Path, PathBuf}; +use uucore::InvalidEncodingHandling; #[macro_use] extern crate uucore; @@ -270,7 +271,9 @@ pub mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let usage = get_usage(); diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index f7af05214..9c735673c 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -14,6 +14,13 @@ use clap::{App, Arg}; use rand::Rng; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; +use uucore::InvalidEncodingHandling; + +enum Mode { + Default(String), + Echo(Vec), + InputRange((usize, usize)), +} static NAME: &str = "shuf"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -34,12 +41,6 @@ struct Options { sep: u8, } -enum Mode { - Default(String), - Echo(Vec), - InputRange((usize, usize)), -} - mod options { pub static ECHO: &str = "echo"; pub static INPUT_RANGE: &str = "input-range"; @@ -52,6 +53,10 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); + let matches = App::new(executable!()) .name(NAME) .version(VERSION) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 5fd7ddcce..de19d0a5c 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -37,6 +37,7 @@ use std::ops::Range; use std::path::Path; use unicode_width::UnicodeWidthStr; use uucore::fs::is_stdin_interactive; // for Iterator::dedup() +use uucore::InvalidEncodingHandling; static NAME: &str = "sort"; static ABOUT: &str = "Display sorted concatenation of all FILE(s)."; @@ -768,7 +769,9 @@ With no FILE, or when FILE is -, read standard input.", } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let usage = get_usage(); let mut settings: GlobalSettings = Default::default(); diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index ddbd76133..77f6d9dad 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -19,6 +19,7 @@ use std::path::PathBuf; use std::process::Command; use tempfile::tempdir; use tempfile::TempDir; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = @@ -186,6 +187,9 @@ fn get_preload_env(tmp_dir: &mut TempDir) -> io::Result<(String, PathBuf)> { } pub fn uumain(args: impl uucore::Args) -> i32 { + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let usage = get_usage(); let matches = App::new(executable!()) diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index d0fbc7c0d..ea833c0d2 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -14,6 +14,7 @@ use clap::{App, Arg}; use std::fs::File; use std::io::{stdin, Read, Result}; use std::path::Path; +use uucore::InvalidEncodingHandling; static NAME: &str = "sum"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -94,7 +95,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 1fb6489da..a638d578d 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -13,6 +13,7 @@ extern crate uucore; use clap::{App, Arg}; use std::io::{stdin, stdout, BufReader, Read, Stdout, Write}; use std::{fs::File, path::Path}; +use uucore::InvalidEncodingHandling; static NAME: &str = "tac"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -27,7 +28,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 23e2ec842..7d557f1ce 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -18,6 +18,7 @@ use std::process::{Command, Stdio}; use std::time::Duration; use uucore::process::ChildExt; use uucore::signals::signal_by_name_or_value; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; @@ -98,7 +99,10 @@ impl Config { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); + let usage = get_usage(); let app = App::new("timeout") diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index b94b11b9d..6c1c0746a 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -21,6 +21,7 @@ use fnv::FnvHashMap; use std::io::{stdin, stdout, BufRead, BufWriter, Write}; use crate::expand::ExpandSet; +use uucore::InvalidEncodingHandling; static NAME: &str = "tr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -187,6 +188,9 @@ fn get_usage() -> String { pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 967a9514a..c96939b20 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -14,6 +14,7 @@ use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static SUMMARY: &str = "Topological sort the strings in FILE. @@ -26,7 +27,9 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 815c6f96b..ef2d848e9 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -15,6 +15,7 @@ extern crate uucore; use clap::{App, Arg}; use std::ffi::CStr; use uucore::fs::is_stdin_interactive; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Print the file name of the terminal connected to standard input."; @@ -28,8 +29,10 @@ fn get_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); let usage = get_usage(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let matches = App::new(executable!()) .version(VERSION) diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index a811d3b66..22b6b807a 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; +use uucore::InvalidEncodingHandling; static NAME: &str = "unexpand"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -90,7 +91,9 @@ impl Options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let matches = App::new(executable!()) .name(NAME) diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 26460e59c..9d9d6385b 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -17,6 +17,7 @@ use libc::{lstat, stat, unlink}; use libc::{S_IFLNK, S_IFMT, S_IFREG}; use std::ffi::CString; use std::io::{Error, ErrorKind}; +use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Unlink the file at [FILE]."; @@ -27,7 +28,9 @@ fn get_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); let usage = get_usage(); diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 9444985dc..e979d2d46 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -16,6 +16,7 @@ use std::borrow::Cow; use std::ffi::CStr; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; +use uucore::InvalidEncodingHandling; static SYNTAX: &str = "[OPTION]... [ FILE | ARG1 ARG2 ]"; static SUMMARY: &str = "Print information about users who are currently logged in."; @@ -44,7 +45,9 @@ If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual. "; pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let mut opts = app!(SYNTAX, SUMMARY, LONG_HELP); opts.optflag("a", "all", "same as -b -d --login -p -r -t -T -u"); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 208e9536c..6dddf8696 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -68,10 +68,94 @@ pub use crate::features::wide; use std::ffi::OsString; +pub enum InvalidEncodingHandling { + Ignore, + ConvertLossy, + Panic, +} + +#[must_use] +pub enum ConversionResult { + Complete(Vec), + Lossy(Vec), +} + +impl ConversionResult { + pub fn accept_any(self) -> Vec { + match self { + Self::Complete(result) => result, + Self::Lossy(result) => result, + } + } + + pub fn expect_lossy(self, msg: &str) -> Vec { + match self { + Self::Lossy(result) => result, + Self::Complete(_) => { + panic!("{}", msg); + } + } + } + + pub fn expect_complete(self, msg: &str) -> Vec { + match self { + Self::Complete(result) => result, + Self::Lossy(_) => { + panic!("{}", msg); + } + } + } +} + pub trait Args: Iterator + Sized { - fn collect_str(self) -> Vec { - // FIXME: avoid unwrap() - self.map(|s| s.into_string().unwrap()).collect() + /// Converts each iterator item to a String and collects these into a vector + /// On invalid encoding, the result will depend on the argument. This method allows to either drop entries with illegal encoding + /// completely (```InvalidEncodingHandling::Ignore```), convert them using lossy-conversion (```InvalidEncodingHandling::Lossy```) which will + /// result in strange strings or can chosen to panic (```InvalidEncodingHandling::Panic```). + /// # Arguments + /// * `handling` - This switch allows to switch the behavior, when invalid encoding is encountered + /// # Panics + /// * Occurs, when invalid encoding is encountered and handling is set to ```InvalidEncodingHandling::Panic``` + fn collect_str(self, handling: InvalidEncodingHandling) -> ConversionResult { + let mut full_conversion = true; + let result_vector: Vec = self + .map(|s| match s.into_string() { + Ok(string) => Ok(string), + Err(s_ret) => { + full_conversion = false; + let lossy_conversion = s_ret.to_string_lossy(); + eprintln!( + "Input with broken encoding occured! (s = '{}') ", + &lossy_conversion + ); + match handling { + InvalidEncodingHandling::Ignore => Err(String::new()), + InvalidEncodingHandling::ConvertLossy => Err(lossy_conversion.to_string()), + InvalidEncodingHandling::Panic => { + panic!("Broken encoding found but caller cannot handle it") + } + } + } + }) + .filter(|s| match handling { + InvalidEncodingHandling::Ignore => s.is_ok(), + _ => true, + }) + .map(|s| match s.is_ok() { + true => s.unwrap(), + false => s.unwrap_err(), + }) + .collect(); + + match full_conversion { + true => ConversionResult::Complete(result_vector), + false => ConversionResult::Lossy(result_vector), + } + } + + /// convience function for a more slim interface + fn collect_str_lossy(self) -> ConversionResult { + self.collect_str(InvalidEncodingHandling::ConvertLossy) } } @@ -85,3 +169,83 @@ pub fn args() -> impl Iterator { pub fn args_os() -> impl Iterator { wild::args_os() } + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::OsStr; + + fn make_os_vec(os_str: &OsStr) -> Vec { + vec![ + OsString::from("test"), + OsString::from("สวัสดี"), + os_str.to_os_string(), + ] + } + + fn collect_os_str(vec: Vec, handling: InvalidEncodingHandling) -> ConversionResult { + vec.into_iter().collect_str(handling) + } + + fn test_invalid_utf8_args_lossy(os_str: &OsStr) { + //assert our string is invalid utf8 + assert!(os_str.to_os_string().into_string().is_err()); + let test_vec = make_os_vec(os_str); + let collected_to_str = + collect_os_str(test_vec.clone(), InvalidEncodingHandling::ConvertLossy) + .expect_lossy("Lossy conversion expected in this test: bad encoding entries should be converted as good as possible"); + //conservation of length - when accepting lossy conversion no arguments may be dropped + assert_eq!(collected_to_str.len(), test_vec.len()); + //first indices identical + for index in 0..2 { + assert_eq!( + collected_to_str.get(index).unwrap(), + test_vec.get(index).unwrap().to_str().unwrap() + ); + } + //lossy conversion for string with illegal encoding is done + assert_eq!( + *collected_to_str.get(2).unwrap(), + os_str.to_os_string().to_string_lossy() + ); + } + + fn test_invalid_utf8_args_ignore(os_str: &OsStr) { + //assert our string is invalid utf8 + assert!(os_str.to_os_string().into_string().is_err()); + let test_vec = make_os_vec(os_str); + let collected_to_str = collect_os_str(test_vec.clone(), InvalidEncodingHandling::Ignore) + .expect_lossy( + "Lossy conversion expected in this test: bad encoding entries should be filtered", + ); + //assert that the broken entry is filtered out + assert_eq!(collected_to_str.len(), test_vec.len() - 1); + //assert that the unbroken indices are converted as expected + for index in 0..2 { + assert_eq!( + collected_to_str.get(index).unwrap(), + test_vec.get(index).unwrap().to_str().unwrap() + ); + } + } + + #[test] + fn valid_utf8_encoding_args() { + //create a vector containing only correct encoding + let test_vec = make_os_vec(&OsString::from("test2")); + //expect complete conversion without losses, even when lossy conversion is accepted + let _ = collect_os_str(test_vec.clone(), InvalidEncodingHandling::ConvertLossy) + .expect_complete("Lossy conversion not expected in this test"); + } + + #[cfg(any(unix, target_os = "redox"))] + #[test] + fn invalid_utf8_args_unix() { + use std::os::unix::ffi::OsStrExt; + + let source = [0x66, 0x6f, 0x80, 0x6f]; + let os_str = OsStr::from_bytes(&source[..]); + test_invalid_utf8_args_lossy(os_str); + test_invalid_utf8_args_ignore(os_str); + } +} diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 3483e800c..8d32b4008 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -1,4 +1,5 @@ use crate::common::util::*; +use std::ffi::OsStr; #[test] fn test_directory() { @@ -84,3 +85,18 @@ fn test_no_args() { fn test_too_many_args() { expect_error(vec!["a", "b", "c"]); } + +fn test_invalid_utf8_args(os_str: &OsStr) { + let test_vec = vec![os_str.to_os_string()]; + new_ucmd!().args(&test_vec).succeeds().stdout_is("fo�o\n"); +} + +#[cfg(any(unix, target_os = "redox"))] +#[test] +fn invalid_utf8_args_unix() { + use std::os::unix::ffi::OsStrExt; + + let source = [0x66, 0x6f, 0x80, 0x6f]; + let os_str = OsStr::from_bytes(&source[..]); + test_invalid_utf8_args(os_str); +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 93bbccc24..1ade70127 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -20,6 +20,7 @@ use std::str::from_utf8; use std::thread::sleep; use std::time::Duration; use tempfile::TempDir; +use uucore::{Args, InvalidEncodingHandling}; #[cfg(windows)] static PROGNAME: &str = concat!(env!("CARGO_PKG_NAME"), ".exe"); @@ -751,7 +752,8 @@ impl UCommand { panic!("{}", ALREADY_RUN); } self.comm_string.push_str(" "); - self.comm_string.push_str(arg.as_ref().to_str().unwrap()); + self.comm_string + .push_str(arg.as_ref().to_str().unwrap_or_default()); self.raw.arg(arg.as_ref()); self } @@ -762,9 +764,15 @@ impl UCommand { if self.has_run { panic!("{}", MULTIPLE_STDIN_MEANINGLESS); } - for s in args { + let strings = args + .iter() + .map(|s| s.as_ref().to_os_string()) + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); + + for s in strings { self.comm_string.push_str(" "); - self.comm_string.push_str(s.as_ref().to_str().unwrap()); + self.comm_string.push_str(&s); } self.raw.args(args.as_ref()); From 0f707cdb25792d003d0cabb9df20428ebc34548d Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 16:33:12 -0500 Subject: [PATCH 153/399] Adjust max buffer size for read back as well --- src/uu/sort/src/external_sort/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 222da5b58..fae00fb72 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -181,7 +181,7 @@ where for seq in unsorted { let seq_size = seq.get_size(); total_read += seq_size; - // Grow buffer size for a Line larger than buffer + // Grow buffer size for a struct/Line larger than buffer adjusted_buffer_size = if adjusted_buffer_size < seq_size { seq_size @@ -212,10 +212,9 @@ where } // initialize buffers for each chunk - iter.max_per_chunk = self - .buffer_bytes + iter.max_per_chunk = adjusted_buffer_size .checked_div(iter.chunks) - .unwrap_or(self.buffer_bytes); + .unwrap_or(adjusted_buffer_size); iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; for chunk_num in 0..iter.chunks { From f8e7fe4c27b9f9c74218fe02ab24d5ecc5b7a8f2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 25 Apr 2021 23:33:19 +0200 Subject: [PATCH 154/399] Update to use stdout_str() --- tests/by-util/test_ls.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 364a71d47..b819ef159 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1726,12 +1726,8 @@ fn test_ls_sort_extension() { ]; let result = scene.ucmd().arg("-1aX").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert_eq!(result.stdout.split('\n').collect::>(), expected,); + assert_eq!(result.stdout_str().split('\n').collect::>(), expected,); let result = scene.ucmd().arg("-1a").arg("--sort=extension").run(); - println!("stderr = {:?}", result.stderr); - println!("stdout = {:?}", result.stdout); - assert_eq!(result.stdout.split('\n').collect::>(), expected,); -} \ No newline at end of file + assert_eq!(result.stdout_str().split('\n').collect::>(), expected,); +} From 32222c1ee7a1efe778624ddbe55577d604f045b0 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 17:52:20 -0500 Subject: [PATCH 155/399] Remove unneeded condition for use of NumCache --- src/uu/sort/src/external_sort/mod.rs | 10 +++++++--- src/uu/sort/src/sort.rs | 15 +++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index fae00fb72..9f3eb3776 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -212,9 +212,13 @@ where } // initialize buffers for each chunk - iter.max_per_chunk = adjusted_buffer_size - .checked_div(iter.chunks) - .unwrap_or(adjusted_buffer_size); + // iter.max_per_chunk = adjusted_buffer_size + // .checked_div(iter.chunks) + // .unwrap_or(adjusted_buffer_size); + // + // + // + iter.max_per_chunk = adjusted_buffer_size; iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; for chunk_num in 0..iter.chunks { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c24d930dc..ea7d36bae 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1299,19 +1299,10 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering (a_str, a_selection.num_cache.as_num_info()), (b_str, b_selection.num_cache.as_num_info()), ), - // serde JSON has issues with f64 null values, so caching them won't work for us with ext sort SortMode::GeneralNumeric => { - if global_settings.buffer_size == DEFAULT_BUF_SIZE { - general_numeric_compare( - a_selection.num_cache.as_f64(), - b_selection.num_cache.as_f64(), - ) - } else { - general_numeric_compare( - general_f64_parse(&a_str[get_leading_gen(a_str)]), - general_f64_parse(&b_str[get_leading_gen(b_str)]), - ) - } + general_numeric_compare( + general_f64_parse(&a_str[get_leading_gen(a_str)]), + general_f64_parse(&b_str[get_leading_gen(b_str)]),) } SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), From fc899ffe7a7a0698db414cb642cfcfee562fe1db Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 19:07:24 -0500 Subject: [PATCH 156/399] Implement a minimum readback buffer --- src/uu/sort/src/external_sort/mod.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 9f3eb3776..628911fe7 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -212,13 +212,22 @@ where } // initialize buffers for each chunk - // iter.max_per_chunk = adjusted_buffer_size - // .checked_div(iter.chunks) - // .unwrap_or(adjusted_buffer_size); // - // + // Having a right sized buffer for each chunk for smallish values seems silly to me? // - iter.max_per_chunk = adjusted_buffer_size; + // We will have to have the entire iter in memory sometime right? + // Set minimum to the size of the writer buffer, ~8K + // + const MINIMUM_READBACK_BUFFER: u64 = 8200; + let right_sized_buffer = adjusted_buffer_size + .checked_div(iter.chunks) + .unwrap_or(adjusted_buffer_size); + iter.max_per_chunk = + if right_sized_buffer > MINIMUM_READBACK_BUFFER { + right_sized_buffer + } else { + MINIMUM_READBACK_BUFFER + }; iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; for chunk_num in 0..iter.chunks { From 8e258075f600b7b6891487ecae4a154ab8ee1573 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 19:21:19 -0500 Subject: [PATCH 157/399] Potential fix to tests on Windows --- src/uu/sort/src/sort.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index ea7d36bae..aa2e1bfe7 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1064,7 +1064,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.tmp_dir = PathBuf::from(result); } else { for (key, value) in env::vars_os() { - if key == OsString::from("TMPDIR") { + if key == OsString::from("TMPDIR") + || key == OsString::from("TEMP") + || key == OsString::from("TMP") + { settings.tmp_dir = PathBuf::from(value); break; } From 1a407c2328ba479b11e0a9af1e50c607958ebede Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 21:17:56 -0500 Subject: [PATCH 158/399] Set a dynamic minimum buffer size --- src/uu/sort/src/external_sort/mod.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 628911fe7..81455eb18 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -176,15 +176,28 @@ where let mut chunk = Vec::new(); // Initial buffer is specified by user let mut adjusted_buffer_size = self.buffer_bytes; + let (iter_size, _) = unsorted.size_hint(); // make the initial chunks on disk for seq in unsorted { let seq_size = seq.get_size(); total_read += seq_size; - // Grow buffer size for a struct/Line larger than buffer + + // GNU minimum is 16 * (sizeof struct + 2), but GNU uses about + // 1/10 the memory that we do. And GNU even says in the code it may + // not work on small buffer sizes. + // + // The following seems to work pretty well, and has about the same max + // RSS as lower minimum values. + // + let minimum_buffer_size: u64 = iter_size as u64 * seq_size / 8; + adjusted_buffer_size = + // Grow buffer size for a struct/Line larger than buffer if adjusted_buffer_size < seq_size { seq_size + } else if adjusted_buffer_size < minimum_buffer_size { + minimum_buffer_size } else { adjusted_buffer_size }; From e5c19734c8a3d7f3b94a25887149b261ccffd1a8 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 21:38:22 -0500 Subject: [PATCH 159/399] Change Default Buffer to usize::MAX --- src/uu/sort/src/sort.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index aa2e1bfe7..55362d4ad 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -93,8 +93,7 @@ static NEGATIVE: char = '-'; static POSITIVE: char = '+'; static DEFAULT_TMPDIR: &str = r"/tmp"; -// 4GB buffer for Vec before we dump to disk, never used -static DEFAULT_BUF_SIZE: usize = 4000000000; +static DEFAULT_BUF_SIZE: usize = usize::MAX; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum SortMode { @@ -142,11 +141,11 @@ impl GlobalSettings { let suf_usize: usize = match suf_str.to_uppercase().as_str() { // SI Units "B" => 1usize, - "K" => 1024usize, - "M" => 1024000usize, - "G" => 1024000000usize, - // GNU regards empty human numeric value as 1024 bytes - _ => 1024usize, + "K" => 1000usize, + "M" => 1000000usize, + "G" => 1000000000usize, + // GNU regards empty human numeric values as K by default + _ => 1000usize, }; num_usize * suf_usize } From 6654519c7d91bfd22b14787106a4700977813020 Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 22:39:17 -0500 Subject: [PATCH 160/399] Specify a default tempdir for Windows --- src/uu/sort/src/sort.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 55362d4ad..53f9a356a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -92,7 +92,17 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; +#[cfg(any( + target_os = "windows", +))] +static DEFAULT_TMPDIR: &str = r"%USERPROFILE%\AppData\Local\Temp"; + +#[cfg(not(any( + target_os = "windows", +)))] static DEFAULT_TMPDIR: &str = r"/tmp"; + + static DEFAULT_BUF_SIZE: usize = usize::MAX; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] From c01c6a7d78427d766c71dc3f1fc2753c863cc1cb Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Sun, 25 Apr 2021 22:41:11 -0500 Subject: [PATCH 161/399] Ran rustfmt --- src/uu/sort/src/external_sort/mod.rs | 29 ++++++++++++++-------------- src/uu/sort/src/sort.rs | 24 +++++++++-------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 81455eb18..fd942d4a7 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -183,16 +183,16 @@ where let seq_size = seq.get_size(); total_read += seq_size; - // GNU minimum is 16 * (sizeof struct + 2), but GNU uses about - // 1/10 the memory that we do. And GNU even says in the code it may + // GNU minimum is 16 * (sizeof struct + 2), but GNU uses about + // 1/10 the memory that we do. And GNU even says in the code it may // not work on small buffer sizes. - // - // The following seems to work pretty well, and has about the same max + // + // The following seems to work pretty well, and has about the same max // RSS as lower minimum values. - // + // let minimum_buffer_size: u64 = iter_size as u64 * seq_size / 8; - - adjusted_buffer_size = + + adjusted_buffer_size = // Grow buffer size for a struct/Line larger than buffer if adjusted_buffer_size < seq_size { seq_size @@ -233,14 +233,13 @@ where // const MINIMUM_READBACK_BUFFER: u64 = 8200; let right_sized_buffer = adjusted_buffer_size - .checked_div(iter.chunks) - .unwrap_or(adjusted_buffer_size); - iter.max_per_chunk = - if right_sized_buffer > MINIMUM_READBACK_BUFFER { - right_sized_buffer - } else { - MINIMUM_READBACK_BUFFER - }; + .checked_div(iter.chunks) + .unwrap_or(adjusted_buffer_size); + iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER { + right_sized_buffer + } else { + MINIMUM_READBACK_BUFFER + }; iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; for chunk_num in 0..iter.chunks { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 53f9a356a..c9e797579 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -92,17 +92,12 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; -#[cfg(any( - target_os = "windows", -))] +#[cfg(any(target_os = "windows",))] static DEFAULT_TMPDIR: &str = r"%USERPROFILE%\AppData\Local\Temp"; -#[cfg(not(any( - target_os = "windows", -)))] +#[cfg(not(any(target_os = "windows",)))] static DEFAULT_TMPDIR: &str = r"/tmp"; - static DEFAULT_BUF_SIZE: usize = usize::MAX; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] @@ -1073,9 +1068,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.tmp_dir = PathBuf::from(result); } else { for (key, value) in env::vars_os() { - if key == OsString::from("TMPDIR") - || key == OsString::from("TEMP") - || key == OsString::from("TMP") + if key == OsString::from("TMPDIR") + || key == OsString::from("TEMP") + || key == OsString::from("TMP") { settings.tmp_dir = PathBuf::from(value); break; @@ -1311,11 +1306,10 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering (a_str, a_selection.num_cache.as_num_info()), (b_str, b_selection.num_cache.as_num_info()), ), - SortMode::GeneralNumeric => { - general_numeric_compare( - general_f64_parse(&a_str[get_leading_gen(a_str)]), - general_f64_parse(&b_str[get_leading_gen(b_str)]),) - } + SortMode::GeneralNumeric => general_numeric_compare( + general_f64_parse(&a_str[get_leading_gen(a_str)]), + general_f64_parse(&b_str[get_leading_gen(b_str)]), + ), SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), SortMode::Default => default_compare(a_str, b_str), From 5578ba6eed000249122627099303cbb6c7db52d2 Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Sun, 25 Apr 2021 22:24:55 -0700 Subject: [PATCH 162/399] base32: move from getopts to clap Note, I needed to change the error messages in one of the tests because getopt and clap have different error messages when not providing a default value --- Cargo.lock | 3 + src/uu/base32/Cargo.toml | 1 + src/uu/base32/src/base32.rs | 9 +- src/uu/base32/src/base_common.rs | 204 ++++++++++++++++++++++++------- tests/by-util/test_base32.rs | 21 +++- 5 files changed, 182 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 321f41d89..aea4956e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "advapi32-sys" version = "0.2.0" @@ -1653,6 +1655,7 @@ dependencies = [ name = "uu_base32" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index a1d7ba17e..1b448af0a 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/base32.rs" [dependencies] +clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index b47f4d4cc..b11b02da7 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -11,7 +11,6 @@ use uucore::encoding::Format; mod base_common; -static SYNTAX: &str = "[OPTION]... [FILE]"; static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output."; static LONG_HELP: &str = " With no FILE, or when FILE is -, read standard input. @@ -24,11 +23,5 @@ static LONG_HELP: &str = " "; pub fn uumain(args: impl uucore::Args) -> i32 { - base_common::execute( - args.collect_str(), - SYNTAX, - SUMMARY, - LONG_HELP, - Format::Base32, - ) + base_common::execute(args.collect_str(), SUMMARY, LONG_HELP, Format::Base32) } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 3f1436fb2..136a54425 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -11,60 +11,172 @@ use std::fs::File; use std::io::{stdin, stdout, BufReader, Read, Write}; use std::path::Path; +use clap::{App, Arg}; use uucore::encoding::{wrap_print, Data, Format}; -pub fn execute( - args: Vec, - syntax: &str, - summary: &str, - long_help: &str, - format: Format, -) -> i32 { - let matches = app!(syntax, summary, long_help) - .optflag("d", "decode", "decode data") - .optflag( - "i", - "ignore-garbage", - "when decoding, ignore non-alphabetic characters", - ) - .optopt( - "w", - "wrap", - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - "COLS", - ) - .parse(args); +static VERSION: &str = env!("CARGO_PKG_VERSION"); - let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() { - Ok(n) => n, - Err(e) => { - crash!(1, "invalid wrap size: ‘{}’: {}", s, e); +pub mod options { + pub static DECODE: &str = "decode"; + pub static WRAP: &str = "wrap"; + pub static IGNORE_GARBAGE: &str = "ignore-garbage"; + pub static FILE: &str = "file"; +} + +struct Config { + decode: bool, + ignore_garbage: bool, + wrap_cols: Option, + to_read: Option, +} + +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let file: Option = match options.values_of(options::FILE) { + Some(mut values) => { + if values.len() != 1 { + crash!(3, "extra operand ‘{}’", values.nth(0).unwrap()) + } + let name = values.nth(0).unwrap(); + if !Path::exists(Path::new(name)) { + crash!(2, "{}: No such file or directory", name); + } + + if name == "-" { + None // stdin + } else { + Some(name.to_owned()) + } + } + None => None, + }; + + let cols = match options.value_of(options::WRAP) { + Some(num) => match num.parse::() { + Ok(n) => Some(n), + Err(e) => { + crash!(1, "invalid wrap size: ‘{}’: {}", num, e); + } + }, + None => None, + }; + + Config { + decode: options.is_present(options::DECODE), + ignore_garbage: options.is_present(options::IGNORE_GARBAGE), + wrap_cols: cols, + to_read: file, } - }); - let ignore_garbage = matches.opt_present("ignore-garbage"); - let decode = matches.opt_present("decode"); - - if matches.free.len() > 1 { - show_usage_error!("extra operand ‘{}’", matches.free[0]); - return 1; } +} - if matches.free.is_empty() || &matches.free[0][..] == "-" { - let stdin_raw = stdin(); - handle_input( - &mut stdin_raw.lock(), - format, - line_wrap, - ignore_garbage, - decode, - ); - } else { - let path = Path::new(matches.free[0].as_str()); - let file_buf = safe_unwrap!(File::open(&path)); - let mut input = BufReader::new(file_buf); - handle_input(&mut input, format, line_wrap, ignore_garbage, decode); +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]", executable!()) +} + +pub fn execute(args: Vec, _summary: &str, long_help: &str, format: Format) -> i32 { + let usage = get_usage(); + let app = App::new(executable!()) + .version(VERSION) + .usage(&usage[..]) + .about(long_help) + // Format arguments. + .arg( + Arg::with_name(options::DECODE) + .short("d") + .long(options::DECODE) + .help("decode data"), + ) + .arg( + Arg::with_name(options::IGNORE_GARBAGE) + .short("i") + .long(options::IGNORE_GARBAGE) + .help("when decoding, ignore non-alphabetic characters"), + ) + .arg( + Arg::with_name(options::WRAP) + .short("w") + .long(options::WRAP) + .takes_value(true) + .help( + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + ), + ) + // "multiple" arguments are used to check whether there is more than one + // file passed in. + .arg(Arg::with_name(options::FILE).index(1).multiple(true)); + + let config: Config = Config::from(app.get_matches_from(args)); + match config.to_read { + // Read from file. + Some(name) => { + let file_buf = safe_unwrap!(File::open(Path::new(&name))); + let mut input = BufReader::new(file_buf); + handle_input( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + // stdin + None => { + handle_input( + &mut stdin().lock(), + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } }; + // let matches = app!(syntax, summary, long_help) + // .optflag("d", "decode", "decode data") + // .optflag( + // "i", + // "ignore-garbage", + // "when decoding, ignore non-alphabetic characters", + // ) + // .optopt( + // "w", + // "wrap", + // "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + // "COLS", + // ) + // .parse(args); + + // let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() { + // Ok(n) => n, + // Err(e) => { + // crash!(1, "invalid wrap size: ‘{}’: {}", s, e); + // } + // }); + // let ignore_garbage = matches.opt_present("ignore-garbage"); + // let decode = matches.opt_present("decode"); + + // if matches.free.len() > 1 { + // show_usage_error!("extra operand ‘{}’", matches.free[0]); + // return 1; + // } + + // if matches.free.is_empty() || &matches.free[0][..] == "-" { + // let stdin_raw = stdin(); + // handle_input( + // &mut stdin_raw.lock(), + // format, + // line_wrap, + // ignore_garbage, + // decode, + // ); + // } else { + // let path = Path::new(matches.free[0].as_str()); + // let file_buf = safe_unwrap!(File::open(&path)); + // let mut input = BufReader::new(file_buf); + // + // }; + 0 } diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index d3527d26a..68e6b5050 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -71,8 +71,7 @@ fn test_wrap() { fn test_wrap_no_arg() { for wrap_param in vec!["-w", "--wrap"] { new_ucmd!().arg(wrap_param).fails().stderr_only(format!( - "base32: error: Argument to option '{}' missing\n", - if wrap_param == "-w" { "w" } else { "wrap" } + "error: The argument '--wrap \' requires a value but none was supplied\n\nUSAGE:\n base32 [OPTION]... [FILE]\n\nFor more information try --help" )); } } @@ -87,3 +86,21 @@ fn test_wrap_bad_arg() { .stderr_only("base32: error: invalid wrap size: ‘b’: invalid digit found in string\n"); } } + +#[test] +fn test_base32_extra_operand() { + // Expect a failure when multiple files are specified. + new_ucmd!() + .arg("a.txt") + .arg("a.txt") + .fails() + .stderr_only("base32: error: extra operand ‘a.txt’"); +} + +#[test] +fn test_base32_file_not_found() { + new_ucmd!() + .arg("a.txt") + .fails() + .stderr_only("base32: error: a.txt: No such file or directory"); +} From 322478d9a238cee3292131840bfd5e7cdd617f41 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 09:35:31 +0200 Subject: [PATCH 163/399] ls: document flamegraph --- src/uu/ls/BENCHMARKING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/uu/ls/BENCHMARKING.md b/src/uu/ls/BENCHMARKING.md index 84a0c3d84..c6568f879 100644 --- a/src/uu/ls/BENCHMARKING.md +++ b/src/uu/ls/BENCHMARKING.md @@ -32,3 +32,19 @@ This can also be used to compare with version of ls built before your changes to - 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` + +## Cargo Flamegraph + +With Cargo Flamegraph you can easily make a flamegraph of `ls`: +```bash +cargo flamegraph --cmd coreutils -- ls [additional parameters] +``` + +However, if the `-R` option is given, the output becomes pretty much useless due to recursion. We can fix this by merging all the direct recursive calls with `uniq`, below is a `bash` script that does this. +```bash +#!/bin/bash + +cargo build --release --no-default-features --features ls +perf record target/release/coreutils ls "$@" +perf script | uniq | inferno-collapse-perf | inferno-flamegraph > flamegraph.svg +``` \ No newline at end of file From e4c006949394ae9d98388f3b77bd00e2e6e089ba Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 09:53:13 +0200 Subject: [PATCH 164/399] ls: remove path strip --- src/uu/ls/src/ls.rs | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 44c644849..b5e76b7d3 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1135,7 +1135,7 @@ fn list(locs: Vec, config: Config) -> i32 { } } sort_entries(&mut files, &config); - display_items(&files, None, &config, &mut out); + display_items(&files, &config, &mut out); sort_entries(&mut dirs, &config); for dir in dirs { @@ -1227,7 +1227,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) entries.append(&mut temp); - display_items(&entries, Some(&dir.p_buf), config, out); + display_items(&entries, config, out); if config.recursive { for e in entries @@ -1264,12 +1264,7 @@ fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } -fn display_items( - items: &[PathData], - strip: Option<&Path>, - config: &Config, - out: &mut BufWriter, -) { +fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter) { if config.format == Format::Long { let (mut max_links, mut max_size) = (1, 1); for item in items { @@ -1278,12 +1273,10 @@ fn display_items( max_size = size.max(max_size); } for item in items { - display_item_long(item, strip, max_links, max_size, config, out); + display_item_long(item, max_links, max_size, config, out); } } else { - let names = items - .iter() - .filter_map(|i| display_file_name(&i, strip, config)); + let names = items.iter().filter_map(|i| display_file_name(&i, config)); match (&config.format, config.width) { (Format::Columns, Some(width)) => { @@ -1355,7 +1348,6 @@ use uucore::fs::display_permissions; fn display_item_long( item: &PathData, - strip: Option<&Path>, max_links: usize, max_size: usize, config: &Config, @@ -1363,7 +1355,7 @@ fn display_item_long( ) { let md = match item.md() { None => { - let filename = get_file_name(&item.p_buf, strip); + let filename = get_file_name(&item.p_buf); show_error!("could not show file: {}", filename); return; } @@ -1407,7 +1399,7 @@ fn display_item_long( // unwrap is fine because it fails when metadata is not available // but we already know that it is because it's checked at the // start of the function. - display_file_name(&item, strip, config).unwrap().contents, + display_file_name(&item, config).unwrap().contents, ); } @@ -1558,15 +1550,11 @@ fn display_file_type(file_type: FileType) -> char { } } -fn get_file_name(name: &Path, strip: Option<&Path>) -> String { - let mut name = match strip { - Some(prefix) => name.strip_prefix(prefix).unwrap_or(name), - None => name, - }; - if name.as_os_str().is_empty() { - name = Path::new("."); - } - name.to_string_lossy().into_owned() +fn get_file_name(name: &Path) -> String { + name.file_name() + .unwrap_or(name.iter().last().unwrap()) + .to_string_lossy() + .into() } #[cfg(unix)] @@ -1605,8 +1593,8 @@ fn classify_file(path: &PathData) -> Option { } } -fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> Option { - let mut name = escape_name(get_file_name(&path.p_buf, strip), &config.quoting_style); +fn display_file_name(path: &PathData, config: &Config) -> Option { + let mut name = escape_name(get_file_name(&path.p_buf), &config.quoting_style); #[cfg(unix)] { From cfc11b47a575011630c8755e61adec82a5e61bd1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 14:41:41 +0200 Subject: [PATCH 165/399] ls: fix grid alignment with --color --- src/uu/ls/src/ls.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 44c644849..b5d8162cd 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1615,6 +1615,10 @@ fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> } } + // We need to keep track of the width ourselfs instead of letting term_grid + // infer it because the color codes mess up term_grid's width calculation. + let mut width = name.len(); + if let Some(ls_colors) = &config.color { name = color_name(&ls_colors, &path.p_buf, name, path.md()?); } @@ -1643,6 +1647,7 @@ fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> if let Some(c) = char_opt { name.push(c); + width += 1; } } @@ -1653,7 +1658,10 @@ fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> } } - Some(name.into()) + Some(Cell { + contents: name, + width, + }) } fn color_name(ls_colors: &LsColors, path: &Path, name: String, md: &Metadata) -> String { From a0eb4a0283046a20acd3b9cde674015f8d573caf Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 14:42:42 +0200 Subject: [PATCH 166/399] ls: add test for color grid alignment --- tests/by-util/test_ls.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 6a1c7764b..1a3bdf78a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -775,6 +775,18 @@ fn test_ls_color() { .arg("z") .succeeds() .stdout_only(""); + + // The colors must not mess up the grid layout + at.touch("b"); + scene + .ucmd() + .arg("--color") + .arg("-w=15") + .succeeds() + .stdout_only(format!( + "{} test-color\nb {}\n", + a_with_colors, z_with_colors + )); } #[cfg(unix)] @@ -1723,7 +1735,7 @@ fn test_ls_sort_extension() { let expected = vec![ ".", "..", - ".hidden", + ".hidden", "anotherFile", "file1", "file2", @@ -1741,8 +1753,14 @@ fn test_ls_sort_extension() { ]; let result = scene.ucmd().arg("-1aX").run(); - assert_eq!(result.stdout_str().split('\n').collect::>(), expected,); + assert_eq!( + result.stdout_str().split('\n').collect::>(), + expected, + ); let result = scene.ucmd().arg("-1a").arg("--sort=extension").run(); - assert_eq!(result.stdout_str().split('\n').collect::>(), expected,); + assert_eq!( + result.stdout_str().split('\n').collect::>(), + expected, + ); } From 58fd61b3e83b1c7de6f0429893b60e7069002725 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 15:00:39 +0200 Subject: [PATCH 167/399] ls: fix grid alignment for unicode --- src/uu/ls/src/ls.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index b5d8162cd..6b87a21c4 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -40,6 +40,7 @@ use std::{ }; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use time::{strftime, Timespec}; +use unicode_width::UnicodeWidthStr; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; @@ -1617,7 +1618,7 @@ fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> // We need to keep track of the width ourselfs instead of letting term_grid // infer it because the color codes mess up term_grid's width calculation. - let mut width = name.len(); + let mut width = name.width(); if let Some(ls_colors) = &config.color { name = color_name(&ls_colors, &path.p_buf, name, path.md()?); From c69b72c84036bc23013cb51941ce352f2a6c6fd3 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 15:04:55 +0200 Subject: [PATCH 168/399] ls: forgot to commit Cargo.{toml, lock} --- Cargo.lock | 1 + src/uu/ls/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 321f41d89..e35bcc68c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2058,6 +2058,7 @@ dependencies = [ "term_grid", "termsize", "time", + "unicode-width", "uucore", "uucore_procs", ] diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 3787d3562..d479a57f4 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -16,6 +16,7 @@ path = "src/ls.rs" [dependencies] clap = "2.33" +unicode-width = "0.1.8" number_prefix = "0.4" term_grid = "0.1.5" termsize = "0.1.6" From f3ed5a100fdc301dbf05d94e5042bdc17d39d32c Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Mon, 26 Apr 2021 08:54:40 -0500 Subject: [PATCH 169/399] Possible fix to Windows issues, ext_sort bool setting --- src/uu/sort/src/sort.rs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c9e797579..b6610be64 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -32,7 +32,6 @@ use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; -use std::ffi::OsString; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; @@ -98,7 +97,7 @@ static DEFAULT_TMPDIR: &str = r"%USERPROFILE%\AppData\Local\Temp"; #[cfg(not(any(target_os = "windows",)))] static DEFAULT_TMPDIR: &str = r"/tmp"; -static DEFAULT_BUF_SIZE: usize = usize::MAX; +static DEFAULT_BUF_SIZE: usize = std::usize::MAX; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum SortMode { @@ -132,6 +131,7 @@ struct GlobalSettings { zero_terminated: bool, buffer_size: usize, tmp_dir: PathBuf, + ext_sort: bool, } impl GlobalSettings { @@ -180,6 +180,7 @@ impl Default for GlobalSettings { zero_terminated: false, buffer_size: DEFAULT_BUF_SIZE, tmp_dir: PathBuf::from(DEFAULT_TMPDIR), + ext_sort: false, } } } @@ -328,9 +329,7 @@ impl Line { ); range.shorten(num_range); NumCache::WithInfo(info) - } else if selector.settings.mode == SortMode::GeneralNumeric - && settings.buffer_size == DEFAULT_BUF_SIZE - { + } else if selector.settings.mode == SortMode::GeneralNumeric { let str = range.get_str(&line); NumCache::AsF64(general_f64_parse(&str[get_leading_gen(str)])) } else { @@ -1057,7 +1056,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); GlobalSettings::human_numeric_convert(&input) - } + }; + settings.ext_sort = true; } if matches.is_present(OPT_TMP_DIR) { @@ -1066,17 +1066,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(String::from) .unwrap_or(DEFAULT_TMPDIR.to_owned()); settings.tmp_dir = PathBuf::from(result); + settings.ext_sort = true; } else { - for (key, value) in env::vars_os() { - if key == OsString::from("TMPDIR") - || key == OsString::from("TEMP") - || key == OsString::from("TMP") - { - settings.tmp_dir = PathBuf::from(value); - break; - } - settings.tmp_dir = PathBuf::from(DEFAULT_TMPDIR); - } + settings.tmp_dir = env::temp_dir(); } settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); @@ -1207,7 +1199,7 @@ fn exec(files: Vec, settings: GlobalSettings) -> i32 { // Only use ext_sorter when we need to. // Probably faster that we don't create // an owned value each run - if settings.buffer_size != DEFAULT_BUF_SIZE { + if settings.ext_sort { lines = ext_sort_by(lines, settings.clone()); } else { sort_by(&mut lines, &settings); From d56462a4b3141479ccd2ed0981abdac3f4481c9f Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Mon, 26 Apr 2021 08:00:55 -0700 Subject: [PATCH 170/399] base32: Fixed style violations. Added tests Tests now cover using "-" as standard input and reading from a file. --- src/uu/base32/src/base32.rs | 5 ++ src/uu/base32/src/base_common.rs | 68 ++++---------------------- tests/by-util/test_base32.rs | 15 ++++++ tests/fixtures/base32/input-simple.txt | 1 + 4 files changed, 30 insertions(+), 59 deletions(-) create mode 100644 tests/fixtures/base32/input-simple.txt diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 7eb1f67a8..86109d4ae 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -23,12 +23,17 @@ static LONG_HELP: &str = " encoded stream. "; +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]", executable!()) +} + pub fn uumain(args: impl uucore::Args) -> i32 { base_common::execute( args.collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(), SUMMARY, LONG_HELP, + &get_usage(), Format::Base32, ) } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 136a54425..5c7dde4df 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -34,17 +34,17 @@ impl Config { fn from(options: clap::ArgMatches) -> Config { let file: Option = match options.values_of(options::FILE) { Some(mut values) => { - if values.len() != 1 { - crash!(3, "extra operand ‘{}’", values.nth(0).unwrap()) - } - let name = values.nth(0).unwrap(); - if !Path::exists(Path::new(name)) { - crash!(2, "{}: No such file or directory", name); + let name = values.next().unwrap(); + if values.len() != 0 { + crash!(3, "extra operand ‘{}’", name); } if name == "-" { - None // stdin + None } else { + if !Path::exists(Path::new(name)) { + crash!(2, "{}: No such file or directory", name); + } Some(name.to_owned()) } } @@ -70,15 +70,10 @@ impl Config { } } -fn get_usage() -> String { - format!("{0} [OPTION]... [FILE]", executable!()) -} - -pub fn execute(args: Vec, _summary: &str, long_help: &str, format: Format) -> i32 { - let usage = get_usage(); +pub fn execute(args: Vec, _summary: &str, long_help: &str, usage: &str, format: Format) -> i32 { let app = App::new(executable!()) .version(VERSION) - .usage(&usage[..]) + .usage(usage) .about(long_help) // Format arguments. .arg( @@ -132,51 +127,6 @@ pub fn execute(args: Vec, _summary: &str, long_help: &str, format: Forma } }; - // let matches = app!(syntax, summary, long_help) - // .optflag("d", "decode", "decode data") - // .optflag( - // "i", - // "ignore-garbage", - // "when decoding, ignore non-alphabetic characters", - // ) - // .optopt( - // "w", - // "wrap", - // "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - // "COLS", - // ) - // .parse(args); - - // let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() { - // Ok(n) => n, - // Err(e) => { - // crash!(1, "invalid wrap size: ‘{}’: {}", s, e); - // } - // }); - // let ignore_garbage = matches.opt_present("ignore-garbage"); - // let decode = matches.opt_present("decode"); - - // if matches.free.len() > 1 { - // show_usage_error!("extra operand ‘{}’", matches.free[0]); - // return 1; - // } - - // if matches.free.is_empty() || &matches.free[0][..] == "-" { - // let stdin_raw = stdin(); - // handle_input( - // &mut stdin_raw.lock(), - // format, - // line_wrap, - // ignore_garbage, - // decode, - // ); - // } else { - // let path = Path::new(matches.free[0].as_str()); - // let file_buf = safe_unwrap!(File::open(&path)); - // let mut input = BufReader::new(file_buf); - // - // }; - 0 } diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 68e6b5050..157503d83 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -15,6 +15,21 @@ fn test_encode() { .pipe_in(input) .succeeds() .stdout_only("JBSWY3DPFQQFO33SNRSCC===\n"); + + // Using '-' as our file + new_ucmd!() + .arg("-") + .pipe_in(input) + .succeeds() + .stdout_only("JBSWY3DPFQQFO33SNRSCC===\n"); +} + +#[test] +fn test_base32_encode_file() { + new_ucmd!() + .arg("input-simple.txt") + .succeeds() + .stdout_only("JBSWY3DPFQQFO33SNRSCCCQ=\n"); } #[test] diff --git a/tests/fixtures/base32/input-simple.txt b/tests/fixtures/base32/input-simple.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/tests/fixtures/base32/input-simple.txt @@ -0,0 +1 @@ +Hello, World! From 11d0565f0e328b4c3ed8a6f1e822f3c1da5bc2ec Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Mon, 26 Apr 2021 08:22:41 -0700 Subject: [PATCH 171/399] base32: Moved clap argument parsing to base32.rs Now, all base_common.rs has is the handle_input function. --- src/uu/base32/src/base32.rs | 123 ++++++++++++++++++++++++++++++- src/uu/base32/src/base_common.rs | 123 +------------------------------ 2 files changed, 124 insertions(+), 122 deletions(-) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 86109d4ae..821ebd9e8 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -10,6 +10,12 @@ extern crate uucore; use uucore::encoding::Format; use uucore::InvalidEncodingHandling; +use std::fs::File; +use std::io::{stdin, BufReader}; +use std::path::Path; + +use clap::{App, Arg}; + mod base_common; static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output."; @@ -22,13 +28,68 @@ static LONG_HELP: &str = " to attempt to recover from any other non-alphabet bytes in the encoded stream. "; +static VERSION: &str = env!("CARGO_PKG_VERSION"); fn get_usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } +pub mod options { + pub static DECODE: &str = "decode"; + pub static WRAP: &str = "wrap"; + pub static IGNORE_GARBAGE: &str = "ignore-garbage"; + pub static FILE: &str = "file"; +} + +struct Config { + decode: bool, + ignore_garbage: bool, + wrap_cols: Option, + to_read: Option, +} + +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let file: Option = match options.values_of(options::FILE) { + Some(mut values) => { + let name = values.next().unwrap(); + if values.len() != 0 { + crash!(3, "extra operand ‘{}’", name); + } + + if name == "-" { + None + } else { + if !Path::exists(Path::new(name)) { + crash!(2, "{}: No such file or directory", name); + } + Some(name.to_owned()) + } + } + None => None, + }; + + let cols = match options.value_of(options::WRAP) { + Some(num) => match num.parse::() { + Ok(n) => Some(n), + Err(e) => { + crash!(1, "invalid wrap size: ‘{}’: {}", num, e); + } + }, + None => None, + }; + + Config { + decode: options.is_present(options::DECODE), + ignore_garbage: options.is_present(options::IGNORE_GARBAGE), + wrap_cols: cols, + to_read: file, + } + } +} + pub fn uumain(args: impl uucore::Args) -> i32 { - base_common::execute( + execute( args.collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(), SUMMARY, @@ -37,3 +98,63 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Format::Base32, ) } + +fn execute(args: Vec, _summary: &str, long_help: &str, usage: &str, format: Format) -> i32 { + let app = App::new(executable!()) + .version(VERSION) + .usage(usage) + .about(long_help) + // Format arguments. + .arg( + Arg::with_name(options::DECODE) + .short("d") + .long(options::DECODE) + .help("decode data"), + ) + .arg( + Arg::with_name(options::IGNORE_GARBAGE) + .short("i") + .long(options::IGNORE_GARBAGE) + .help("when decoding, ignore non-alphabetic characters"), + ) + .arg( + Arg::with_name(options::WRAP) + .short("w") + .long(options::WRAP) + .takes_value(true) + .help( + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + ), + ) + // "multiple" arguments are used to check whether there is more than one + // file passed in. + .arg(Arg::with_name(options::FILE).index(1).multiple(true)); + + let config: Config = Config::from(app.get_matches_from(args)); + match config.to_read { + // Read from file. + Some(name) => { + let file_buf = safe_unwrap!(File::open(Path::new(&name))); + let mut input = BufReader::new(file_buf); + base_common::handle_input( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + // stdin + None => { + base_common::handle_input( + &mut stdin().lock(), + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + }; + + 0 +} diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 5c7dde4df..a4b49e499 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -7,130 +7,11 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -use std::fs::File; -use std::io::{stdin, stdout, BufReader, Read, Write}; -use std::path::Path; +use std::io::{stdout, Read, Write}; -use clap::{App, Arg}; use uucore::encoding::{wrap_print, Data, Format}; -static VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub mod options { - pub static DECODE: &str = "decode"; - pub static WRAP: &str = "wrap"; - pub static IGNORE_GARBAGE: &str = "ignore-garbage"; - pub static FILE: &str = "file"; -} - -struct Config { - decode: bool, - ignore_garbage: bool, - wrap_cols: Option, - to_read: Option, -} - -impl Config { - fn from(options: clap::ArgMatches) -> Config { - let file: Option = match options.values_of(options::FILE) { - Some(mut values) => { - let name = values.next().unwrap(); - if values.len() != 0 { - crash!(3, "extra operand ‘{}’", name); - } - - if name == "-" { - None - } else { - if !Path::exists(Path::new(name)) { - crash!(2, "{}: No such file or directory", name); - } - Some(name.to_owned()) - } - } - None => None, - }; - - let cols = match options.value_of(options::WRAP) { - Some(num) => match num.parse::() { - Ok(n) => Some(n), - Err(e) => { - crash!(1, "invalid wrap size: ‘{}’: {}", num, e); - } - }, - None => None, - }; - - Config { - decode: options.is_present(options::DECODE), - ignore_garbage: options.is_present(options::IGNORE_GARBAGE), - wrap_cols: cols, - to_read: file, - } - } -} - -pub fn execute(args: Vec, _summary: &str, long_help: &str, usage: &str, format: Format) -> i32 { - let app = App::new(executable!()) - .version(VERSION) - .usage(usage) - .about(long_help) - // Format arguments. - .arg( - Arg::with_name(options::DECODE) - .short("d") - .long(options::DECODE) - .help("decode data"), - ) - .arg( - Arg::with_name(options::IGNORE_GARBAGE) - .short("i") - .long(options::IGNORE_GARBAGE) - .help("when decoding, ignore non-alphabetic characters"), - ) - .arg( - Arg::with_name(options::WRAP) - .short("w") - .long(options::WRAP) - .takes_value(true) - .help( - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - ), - ) - // "multiple" arguments are used to check whether there is more than one - // file passed in. - .arg(Arg::with_name(options::FILE).index(1).multiple(true)); - - let config: Config = Config::from(app.get_matches_from(args)); - match config.to_read { - // Read from file. - Some(name) => { - let file_buf = safe_unwrap!(File::open(Path::new(&name))); - let mut input = BufReader::new(file_buf); - handle_input( - &mut input, - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); - } - // stdin - None => { - handle_input( - &mut stdin().lock(), - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); - } - }; - - 0 -} - -fn handle_input( +pub fn handle_input( input: &mut R, format: Format, line_wrap: Option, From 4023e40174485fe7e5bf514c761774d484f8acc2 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 18:03:56 +0200 Subject: [PATCH 172/399] ls: further reduce OsStr -> String conversions --- src/uu/ls/src/ls.rs | 16 +++++----------- src/uu/ls/src/quoting_style.rs | 10 +++++----- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index b5e76b7d3..1cebeb38a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1056,7 +1056,9 @@ impl PathData { ) -> Self { let name = p_buf .file_name() - .map_or(String::new(), |s| s.to_string_lossy().into_owned()); + .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) + .to_string_lossy() + .into_owned(); let must_dereference = match &config.dereference { Dereference::All => true, Dereference::Args => command_line, @@ -1355,8 +1357,7 @@ fn display_item_long( ) { let md = match item.md() { None => { - let filename = get_file_name(&item.p_buf); - show_error!("could not show file: {}", filename); + show_error!("could not show file: {}", &item.p_buf.display()); return; } Some(md) => md, @@ -1550,13 +1551,6 @@ fn display_file_type(file_type: FileType) -> char { } } -fn get_file_name(name: &Path) -> String { - name.file_name() - .unwrap_or(name.iter().last().unwrap()) - .to_string_lossy() - .into() -} - #[cfg(unix)] fn file_is_executable(md: &Metadata) -> bool { // Mode always returns u32, but the flags might not be, based on the platform @@ -1594,7 +1588,7 @@ fn classify_file(path: &PathData) -> Option { } fn display_file_name(path: &PathData, config: &Config) -> Option { - let mut name = escape_name(get_file_name(&path.p_buf), &config.quoting_style); + let mut name = escape_name(&path.file_name, &config.quoting_style); #[cfg(unix)] { diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index 49456fc22..01bffa7ec 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -171,7 +171,7 @@ impl Iterator for EscapedChar { } } -fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool) -> (String, bool) { +fn shell_without_escape(name: &str, quotes: Quotes, show_control_chars: bool) -> (String, bool) { let mut must_quote = false; let mut escaped_str = String::with_capacity(name.len()); @@ -201,7 +201,7 @@ fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool) (escaped_str, must_quote) } -fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) { +fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) { // We need to keep track of whether we are in a dollar expression // because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n' let mut in_dollar = false; @@ -249,7 +249,7 @@ fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) { (escaped_str, must_quote) } -pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String { +pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String { match style { QuotingStyle::Literal { show_control } => { if !show_control { @@ -257,7 +257,7 @@ pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String { .flat_map(|c| EscapedChar::new_literal(c).hide_control()) .collect() } else { - name + name.into() } } QuotingStyle::C { quotes } => { @@ -354,7 +354,7 @@ mod tests { fn check_names(name: &str, map: Vec<(&str, &str)>) { assert_eq!( map.iter() - .map(|(_, style)| escape_name(name.to_string(), &get_style(style))) + .map(|(_, style)| escape_name(name, &get_style(style))) .collect::>(), map.iter() .map(|(correct, _)| correct.to_string()) From 35838dc8a9f6f1e8d2dfb65932f92d3b57f2158b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 26 Apr 2021 18:36:15 +0200 Subject: [PATCH 173/399] ls: document hyperfine script --- src/uu/ls/BENCHMARKING.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/BENCHMARKING.md b/src/uu/ls/BENCHMARKING.md index c6568f879..852220496 100644 --- a/src/uu/ls/BENCHMARKING.md +++ b/src/uu/ls/BENCHMARKING.md @@ -26,7 +26,15 @@ Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/n `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 +This can also be used to compare with version of ls built before your changes to ensure your change does not regress this. + +Here is a `bash` script for doing this comparison: +```bash +#!/bin/bash +cargo build --no-default-features --features ls --release +args="$@" +hyperfine "ls $args" "target/release/coreutils ls $args" +``` ## Checking system call count @@ -43,7 +51,6 @@ cargo flamegraph --cmd coreutils -- ls [additional parameters] However, if the `-R` option is given, the output becomes pretty much useless due to recursion. We can fix this by merging all the direct recursive calls with `uniq`, below is a `bash` script that does this. ```bash #!/bin/bash - cargo build --release --no-default-features --features ls perf record target/release/coreutils ls "$@" perf script | uniq | inferno-collapse-perf | inferno-flamegraph > flamegraph.svg From a7037b1ca925f3858ec7ce5ff80c9bc288ba633b Mon Sep 17 00:00:00 2001 From: James Robson Date: Mon, 26 Apr 2021 18:39:32 +0100 Subject: [PATCH 174/399] Allow truncate to take --size and --reference --- src/uu/truncate/src/truncate.rs | 61 +++++++++++++++++++++------------ tests/by-util/test_truncate.rs | 13 +++++++ 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 2c232a3d1..91f705bd1 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -16,6 +16,7 @@ use std::path::Path; #[derive(Eq, PartialEq)] enum TruncateMode { + Absolute, Reference, Extend, Reduce, @@ -131,18 +132,30 @@ fn truncate( size: Option, filenames: Vec, ) { - let (refsize, mode) = match reference { - Some(rfilename) => { - let _ = match File::open(Path::new(&rfilename)) { + let (modsize, mode) = match size { + Some(size_string) => parse_size(&size_string), + None => (0, TruncateMode::Reference), + }; + + let refsize = match reference { + Some(ref rfilename) => { + match mode { + // Only Some modes work with a reference + TruncateMode::Reference => (), //No --size was given + TruncateMode::Extend => (), + TruncateMode::Reduce => (), + _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"), + }; + let _ = match File::open(Path::new(rfilename)) { Ok(m) => m, Err(f) => crash!(1, "{}", f.to_string()), }; match metadata(rfilename) { - Ok(meta) => (meta.len(), TruncateMode::Reference), + Ok(meta) => meta.len(), Err(f) => crash!(1, "{}", f.to_string()), } } - None => parse_size(size.unwrap().as_ref()), + None => 0, }; for filename in &filenames { let path = Path::new(filename); @@ -153,33 +166,37 @@ fn truncate( .open(path) { Ok(file) => { - let fsize = match metadata(filename) { - Ok(meta) => meta.len(), - Err(f) => { - show_warning!("{}", f.to_string()); - continue; - } + let fsize = match reference { + Some(_) => refsize, + None => match metadata(filename) { + Ok(meta) => meta.len(), + Err(f) => { + show_warning!("{}", f.to_string()); + continue; + } + }, }; let tsize: u64 = match mode { - TruncateMode::Reference => refsize, - TruncateMode::Extend => fsize + refsize, - TruncateMode::Reduce => fsize - refsize, + TruncateMode::Absolute => modsize, + TruncateMode::Reference => fsize, + TruncateMode::Extend => fsize + modsize, + TruncateMode::Reduce => fsize - modsize, TruncateMode::AtMost => { - if fsize > refsize { - refsize + if fsize > modsize { + modsize } else { fsize } } TruncateMode::AtLeast => { - if fsize < refsize { - refsize + if fsize < modsize { + modsize } else { fsize } } - TruncateMode::RoundDown => fsize - fsize % refsize, - TruncateMode::RoundUp => fsize + fsize % refsize, + TruncateMode::RoundDown => fsize - fsize % modsize, + TruncateMode::RoundUp => fsize + fsize % modsize, }; match file.set_len(tsize) { Ok(_) => {} @@ -200,10 +217,10 @@ fn parse_size(size: &str) -> (u64, TruncateMode) { '>' => TruncateMode::AtLeast, '/' => TruncateMode::RoundDown, '*' => TruncateMode::RoundUp, - _ => TruncateMode::Reference, /* assume that the size is just a number */ + _ => TruncateMode::Absolute, /* assume that the size is just a number */ }; let bytes = { - let mut slice = if mode == TruncateMode::Reference { + let mut slice = if mode == TruncateMode::Absolute { &clean_size } else { &clean_size[1..] diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index d0a93f871..304ff820c 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -161,3 +161,16 @@ fn test_round_up() { let actual = file.seek(SeekFrom::Current(0)).unwrap(); assert!(expected == actual, "expected '{}' got '{}'", expected, actual); } + +#[test] +fn test_size_and_reference() { + let expected = 15; + let (at, mut ucmd) = at_and_ucmd!(); + let mut file1 = at.make_file(TFILE1); + let mut file2 = at.make_file(TFILE2); + file1.write_all(b"1234567890").unwrap(); + ucmd.args(&["--reference", TFILE1, "--size", "+5", TFILE2]).succeeds(); + file2.seek(SeekFrom::End(0)).unwrap(); + let actual = file2.seek(SeekFrom::Current(0)).unwrap(); + assert!(expected == actual, "expected '{}' got '{}'", expected, actual); +} From ece5e14b0d62a52607973aaa363e3a024afd8258 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 26 Apr 2021 22:51:02 +0200 Subject: [PATCH 175/399] fix a typo --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6b87a21c4..3903baba9 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1616,7 +1616,7 @@ fn display_file_name(path: &PathData, strip: Option<&Path>, config: &Config) -> } } - // We need to keep track of the width ourselfs instead of letting term_grid + // We need to keep track of the width ourselves instead of letting term_grid // infer it because the color codes mess up term_grid's width calculation. let mut width = name.width(); From ae0cabc60a67a3f989afd961c470db3a8d9fde2b Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Mon, 26 Apr 2021 20:14:11 -0700 Subject: [PATCH 176/399] Moved argument parsing to uumain. --- src/uu/base32/src/base32.rs | 23 ++++------ src/uu/pinky/src/pinky.rs | 4 +- tests/by-util/test_truncate.rs | 77 +++++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 821ebd9e8..6d9f7f08f 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -89,21 +89,13 @@ impl Config { } pub fn uumain(args: impl uucore::Args) -> i32 { - execute( - args.collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(), - SUMMARY, - LONG_HELP, - &get_usage(), - Format::Base32, - ) -} - -fn execute(args: Vec, _summary: &str, long_help: &str, usage: &str, format: Format) -> i32 { + let format = Format::Base32; + let usage = get_usage(); let app = App::new(executable!()) .version(VERSION) - .usage(usage) - .about(long_help) + .about(SUMMARY) + .usage(&usage[..]) + .about(LONG_HELP) // Format arguments. .arg( Arg::with_name(options::DECODE) @@ -130,7 +122,10 @@ fn execute(args: Vec, _summary: &str, long_help: &str, usage: &str, form // file passed in. .arg(Arg::with_name(options::FILE).index(1).multiple(true)); - let config: Config = Config::from(app.get_matches_from(args)); + let arg_list = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); + let config: Config = Config::from(app.get_matches_from(arg_list)); match config.to_read { // Read from file. Some(name) => { diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 3392c9a79..a02096bc8 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -28,7 +28,9 @@ static SUMMARY: &str = "A lightweight 'finger' program; print user information. const BUFSIZE: usize = 1024; pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args.collect_str(InvalidEncodingHandling::Ignore).accept_any(); + let args = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); let long_help = &format!( " diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index d0a93f871..187b9a1ba 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -13,7 +13,12 @@ fn test_increase_file_size() { file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -25,7 +30,12 @@ fn test_increase_file_size_kb() { file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -46,7 +56,12 @@ fn test_reference() { file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -58,7 +73,12 @@ fn test_decrease_file_size() { ucmd.args(&["--size=-4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -70,7 +90,12 @@ fn test_space_in_size() { ucmd.args(&["--size", " 4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -99,7 +124,12 @@ fn test_at_most_shrinks() { ucmd.args(&["--size", "<4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -111,7 +141,12 @@ fn test_at_most_no_change() { ucmd.args(&["--size", "<40", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -123,7 +158,12 @@ fn test_at_least_grows() { ucmd.args(&["--size", ">15", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -135,7 +175,12 @@ fn test_at_least_no_change() { ucmd.args(&["--size", ">4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -147,7 +192,12 @@ fn test_round_down() { ucmd.args(&["--size", "/4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } #[test] @@ -159,5 +209,10 @@ fn test_round_up() { ucmd.args(&["--size", "*4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } From ec19bb72d56be25f7cfb795aa61458b029794def Mon Sep 17 00:00:00 2001 From: electricboogie <32370782+electricboogie@users.noreply.github.com> Date: Tue, 27 Apr 2021 15:39:20 -0500 Subject: [PATCH 177/399] Modified to remove 2 unnecessary consts now that we use std::env::temp_dir --- src/uu/sort/src/sort.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index b6610be64..2c67f1063 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -91,12 +91,6 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; -#[cfg(any(target_os = "windows",))] -static DEFAULT_TMPDIR: &str = r"%USERPROFILE%\AppData\Local\Temp"; - -#[cfg(not(any(target_os = "windows",)))] -static DEFAULT_TMPDIR: &str = r"/tmp"; - static DEFAULT_BUF_SIZE: usize = std::usize::MAX; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] @@ -179,7 +173,7 @@ impl Default for GlobalSettings { threads: String::new(), zero_terminated: false, buffer_size: DEFAULT_BUF_SIZE, - tmp_dir: PathBuf::from(DEFAULT_TMPDIR), + tmp_dir: PathBuf::new(), ext_sort: false, } } @@ -1064,7 +1058,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let result = matches .value_of(OPT_TMP_DIR) .map(String::from) - .unwrap_or(DEFAULT_TMPDIR.to_owned()); + .unwrap_or(format!("{}", env::temp_dir().display())); settings.tmp_dir = PathBuf::from(result); settings.ext_sort = true; } else { From 25f99097cc28298536282b545269b99dac484d22 Mon Sep 17 00:00:00 2001 From: Chirag Jadwani Date: Wed, 28 Apr 2021 23:28:26 +0530 Subject: [PATCH 178/399] cut: add BENCHMARKING.md and minor refactoring --- src/uu/cut/BENCHMARKING.md | 46 ++++++++++++++++++++++++++++++++++++++ src/uu/cut/src/cut.rs | 2 +- src/uu/cut/src/searcher.rs | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/uu/cut/BENCHMARKING.md diff --git a/src/uu/cut/BENCHMARKING.md b/src/uu/cut/BENCHMARKING.md new file mode 100644 index 000000000..bd94cf93c --- /dev/null +++ b/src/uu/cut/BENCHMARKING.md @@ -0,0 +1,46 @@ +## Benchmarking cut + +### Performance profile + +In normal use cases a significant amount of the total execution time of `cut` +is spent performing I/O. When invoked with the `-f` option (cut fields) some +CPU time is spent on detecting fields (in `Searcher::next`). Other than that +some small amount of CPU time is spent on breaking the input stream into lines. + + +### How to + +When fixing bugs or adding features you might want to compare +performance before and after your code changes. + +- `hyperfine` can be used to accurately measure and compare the total + execution time of one or more commands. + + ``` + $ cargo build --release --package uu_cut + + $ hyperfine -w3 "./target/release/cut -f2-4,8 -d' ' input.txt" "cut -f2-4,8 -d' ' input.txt" + ``` + You can put those two commands in a shell script to be sure that you don't + forget to build after making any changes. + +When optimizing or fixing performance regressions seeing the number of times a +function is called, and the amount of time it takes can be useful. + +- `cargo flamegraph` generates flame graphs from function level metrics it records using `perf` or `dtrace` + + ``` + $ cargo flamegraph --bin cut --package uu_cut -- -f1,3-4 input.txt > /dev/null + ``` + + +### What to benchmark + +There are four different performance paths in `cut` to benchmark. + +- Byte ranges `-c`/`--characters` or `-b`/`--bytes` e.g. `cut -c 2,4,6-` +- Byte ranges with output delimiters e.g. `cut -c 4- --output-delimiter=/` +- Fields e.g. `cut -f -4` +- Fields with output delimiters e.g. `cut -f 7-10 --output-delimiter=:` + +Choose a test input file with large number of lines so that program startup time does not significantly affect the benchmark. diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 91dc17e52..40b41e98f 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -205,7 +205,7 @@ fn cut_fields_delimiter( return Ok(true); } - for &Range { low, high } in ranges.iter() { + for &Range { low, high } in ranges { if low - fields_pos > 0 { low_idx = match delim_search.nth(low - fields_pos - 1) { Some(index) => index + input_delim_len, diff --git a/src/uu/cut/src/searcher.rs b/src/uu/cut/src/searcher.rs index 5e3c076df..5fe4a723b 100644 --- a/src/uu/cut/src/searcher.rs +++ b/src/uu/cut/src/searcher.rs @@ -33,9 +33,9 @@ impl<'a> Iterator for Searcher<'a> { if self.needle.len() == 1 || self.haystack[match_idx + 1..].starts_with(&self.needle[1..]) { + let match_pos = self.position + match_idx; let skip = match_idx + self.needle.len(); self.haystack = &self.haystack[skip..]; - let match_pos = self.position + match_idx; self.position += skip; return Some(match_pos); } else { From a60fd07bc3b249b6bc0a07e675b5f3d8023a7dd4 Mon Sep 17 00:00:00 2001 From: Rein F Date: Wed, 28 Apr 2021 20:54:27 +0200 Subject: [PATCH 179/399] ls: improvements on time handling (#1986) * ls: added creation time * ls: Added most time features Missing support for posix-,Format+, translating via locales. Also required more tests * ls: rustfmt * ls: Additional changes and fixes Fixed the argument order, fixed a wrong iso format. * ls: additional tests for styles * ls: perfected arg parsing on time styles * fix birthime test * ls: Use 'stdout_str' in new tests * ls: Disabled birthtime test for windows * ls: removed indoc as a dependency * ls: birthime test, sync first created file * ls: birthime test, add comment explaining sync * Removed ruby testfile birth_test.rb This accidentally got commited in a merge --- Cargo.lock | 33 +++++++++--- src/uu/ls/Cargo.toml | 9 ++-- src/uu/ls/src/ls.rs | 112 ++++++++++++++++++++++++++++++++++----- src/uucore/Cargo.toml | 2 +- tests/by-util/test_ls.rs | 112 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 245 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea67b34af..863a36451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,13 +165,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.11" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ + "libc", "num-integer", "num-traits", "time", + "winapi 0.3.9", ] [[package]] @@ -732,6 +734,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" +[[package]] +name = "indoc" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" +dependencies = [ + "unindent", +] + [[package]] name = "ioctl-sys" version = "0.5.2" @@ -802,6 +813,15 @@ version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" +[[package]] +name = "locale" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" +dependencies = [ + "libc", +] + [[package]] name = "log" version = "0.4.14" @@ -1573,12 +1593,11 @@ dependencies = [ [[package]] name = "time" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "redox_syscall 0.1.57", "winapi 0.3.9", ] @@ -2060,15 +2079,17 @@ name = "uu_ls" version = "0.0.6" dependencies = [ "atty", + "chrono", "clap", "globset", + "indoc", "lazy_static", + "locale", "lscolors", "number_prefix", "once_cell", "term_grid", "termsize", - "time", "unicode-width", "uucore", "uucore_procs", diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index d479a57f4..ab58a7300 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -15,16 +15,17 @@ edition = "2018" path = "src/ls.rs" [dependencies] +locale = "0.2.2" +chrono = "0.4.19" clap = "2.33" unicode-width = "0.1.8" number_prefix = "0.4" term_grid = "0.1.5" termsize = "0.1.6" -time = "0.1.40" 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" } +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" } once_cell = "1.7.2" atty = "0.2" diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3245e2a56..d78e1977a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -38,8 +38,11 @@ use std::{ os::unix::fs::{FileTypeExt, MetadataExt}, time::Duration, }; + +use chrono; + use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; -use time::{strftime, Timespec}; + use unicode_width::UnicodeWidthStr; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; @@ -50,6 +53,8 @@ static ABOUT: &str = " the command line, expect that it will ignore files and directories whose names start with '.' "; +static AFTER_HELP: &str = "The TIME_STYLE argument can be full-iso, long-iso, iso. +Also the TIME_STYLE environment variable sets the default style to use."; fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) @@ -117,6 +122,8 @@ pub mod options { pub static COLOR: &str = "color"; pub static PATHS: &str = "paths"; pub static INDICATOR_STYLE: &str = "indicator-style"; + pub static TIME_STYLE: &str = "time-style"; + pub static FULL_TIME: &str = "full-time"; pub static HIDE: &str = "hide"; pub static IGNORE: &str = "ignore"; } @@ -156,6 +163,15 @@ enum Time { Modification, Access, Change, + Birth, +} + +#[derive(Debug)] +enum TimeStyle { + FullIso, + LongIso, + Iso, + Locale, } enum Dereference { @@ -191,6 +207,7 @@ struct Config { width: Option, quoting_style: QuotingStyle, indicator_style: IndicatorStyle, + time_style: TimeStyle, } // Fields that can be removed or added to the long format @@ -251,6 +268,7 @@ impl Config { options::format::LONG_NO_OWNER, options::format::LONG_NO_GROUP, options::format::LONG_NUMERIC_UID_GID, + options::FULL_TIME, ] .iter() .flat_map(|opt| options.indices_of(opt)) @@ -302,6 +320,7 @@ impl Config { match field { "ctime" | "status" => Time::Change, "access" | "atime" | "use" => Time::Access, + "birth" | "creation" => Time::Birth, // below should never happen as clap already restricts the values. _ => unreachable!("Invalid field for --time"), } @@ -439,6 +458,30 @@ impl Config { IndicatorStyle::None }; + let time_style = if let Some(field) = options.value_of(options::TIME_STYLE) { + //If both FULL_TIME and TIME_STYLE are present + //The one added last is dominant + if options.is_present(options::FULL_TIME) + && options.indices_of(options::FULL_TIME).unwrap().last() + > options.indices_of(options::TIME_STYLE).unwrap().last() + { + TimeStyle::FullIso + } else { + //Clap handles the env variable "TIME_STYLE" + match field { + "full-iso" => TimeStyle::FullIso, + "long-iso" => TimeStyle::LongIso, + "iso" => TimeStyle::Iso, + "locale" => TimeStyle::Locale, + // below should never happen as clap already restricts the values. + _ => unreachable!("Invalid field for --time-style"), + } + } + } else if options.is_present(options::FULL_TIME) { + TimeStyle::FullIso + } else { + TimeStyle::Locale + }; let mut ignore_patterns = GlobSetBuilder::new(); if options.is_present(options::IGNORE_BACKUPS) { ignore_patterns.add(Glob::new("*~").unwrap()); @@ -504,6 +547,7 @@ impl Config { width, quoting_style, indicator_style, + time_style, } } } @@ -696,10 +740,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .long(options::TIME) .help("Show time in :\n\ \taccess time (-u): atime, access, use;\n\ - \tchange time (-t): ctime, status.") + \tchange time (-t): ctime, status.\n\ + \tbirth time: birth, creation;") .value_name("field") .takes_value(true) - .possible_values(&["atime", "access", "use", "ctime", "status"]) + .possible_values(&["atime", "access", "use", "ctime", "status", "birth", "creation"]) .hide_possible_values(true) .require_equals(true) .overrides_with_all(&[ @@ -1020,9 +1065,34 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options::indicator_style::CLASSIFY, options::INDICATOR_STYLE, ])) + .arg( + //This still needs support for posix-*, +FORMAT + Arg::with_name(options::TIME_STYLE) + .long(options::TIME_STYLE) + .help("time/date format with -l; see TIME_STYLE below") + .value_name("TIME_STYLE") + .env("TIME_STYLE") + .possible_values(&[ + "full-iso", + "long-iso", + "iso", + "locale", + ]) + .overrides_with_all(&[ + options::TIME_STYLE + ]) + ) + .arg( + Arg::with_name(options::FULL_TIME) + .long(options::FULL_TIME) + .overrides_with(options::FULL_TIME) + .help("like -l --time-style=full-iso") + ) // Positional arguments - .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)); + .arg(Arg::with_name(options::PATHS).multiple(true).takes_value(true)) + + .after_help(AFTER_HELP); let matches = app.get_matches_from(args); @@ -1480,6 +1550,7 @@ fn get_system_time(md: &Metadata, config: &Config) -> Option { Time::Change => Some(UNIX_EPOCH + Duration::new(md.ctime() as u64, md.ctime_nsec() as u32)), Time::Modification => md.modified().ok(), Time::Access => md.accessed().ok(), + Time::Birth => md.created().ok(), } } @@ -1492,18 +1563,35 @@ fn get_system_time(md: &Metadata, config: &Config) -> Option { } } -fn get_time(md: &Metadata, config: &Config) -> Option { - let duration = get_system_time(md, config)? - .duration_since(UNIX_EPOCH) - .ok()?; - let secs = duration.as_secs() as i64; - let nsec = duration.subsec_nanos() as i32; - Some(time::at(Timespec::new(secs, nsec))) +fn get_time(md: &Metadata, config: &Config) -> Option> { + let time = get_system_time(md, config)?; + Some(time.into()) } fn display_date(metadata: &Metadata, config: &Config) -> String { match get_time(metadata, config) { - Some(time) => strftime("%F %R", &time).unwrap(), + Some(time) => { + //Date is recent if from past 6 months + //According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. + let recent = time + chrono::Duration::seconds(31556952 / 2) > chrono::Local::now(); + + match config.time_style { + TimeStyle::FullIso => time.format("%Y-%m-%d %H:%M:%S.%f %z"), + TimeStyle::LongIso => time.format("%Y-%m-%d %H:%M"), + TimeStyle::Iso => time.format(if recent { "%m-%d %H:%M" } else { "%Y-%m-%d " }), + TimeStyle::Locale => { + let fmt = if recent { "%b %e %H:%M" } else { "%b %e %Y" }; + + //In this version of chrono translating can be done + //The function is chrono::datetime::DateTime::format_localized + //However it's currently still hard to get the current pure-rust-locale + //So it's not yet implemented + + time.format(fmt) + } + } + .to_string() + } None => "???".into(), } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 855e64b36..291456760 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -24,7 +24,7 @@ thiserror = { version="1.0", optional=true } lazy_static = { version="1.3", optional=true } nix = { version="<= 0.13", optional=true } platform-info = { version="<= 0.1", optional=true } -time = { version="<= 0.1.42", optional=true } +time = { version="<= 0.1.43", optional=true } # * "problem" dependencies (pinned) data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0 libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0 diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 1a3bdf78a..eeb7a6248 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -558,6 +558,118 @@ fn test_ls_long_ctime() { } } +#[test] +#[cfg(not(windows))] +// This test is currently failing on windows +fn test_ls_order_birthtime() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + /* + Here we make 2 files with a timeout in between. + After creating the first file try to sync it. + This ensures the file gets created immediately instead of being saved + inside the OS's IO operation buffer. + Without this, both files might accidentally be created at the same time, + even though we placed a timeout between creating the two. + + https://github.com/uutils/coreutils/pull/1986/#issuecomment-828490651 + */ + at.make_file("test-birthtime-1").sync_all().unwrap(); + std::thread::sleep(std::time::Duration::from_millis(1)); + at.make_file("test-birthtime-2"); + at.touch("test-birthtime-1"); + + let result = scene.ucmd().arg("--time=birth").arg("-t").run(); + + #[cfg(not(windows))] + assert_eq!(result.stdout_str(), "test-birthtime-2\ntest-birthtime-1\n"); + #[cfg(windows)] + assert_eq!(result.stdout_str(), "test-birthtime-2 test-birthtime-1\n"); +} + +#[test] +fn test_ls_styles() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("test"); + + let re_full = Regex::new( + r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d* \+\d{4} test\n", + ) + .unwrap(); + let re_long = + Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2} test\n").unwrap(); + let re_iso = Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{2}-\d{2} \d{2}:\d{2} test\n").unwrap(); + let re_locale = + Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} \d{2} \d{2}:\d{2} test\n").unwrap(); + + //full-iso + let result = scene + .ucmd() + .arg("-l") + .arg("--time-style=full-iso") + .succeeds(); + assert!(re_full.is_match(&result.stdout_str())); + //long-iso + let result = scene + .ucmd() + .arg("-l") + .arg("--time-style=long-iso") + .succeeds(); + assert!(re_long.is_match(&result.stdout_str())); + //iso + let result = scene.ucmd().arg("-l").arg("--time-style=iso").succeeds(); + assert!(re_iso.is_match(&result.stdout_str())); + //locale + let result = scene.ucmd().arg("-l").arg("--time-style=locale").succeeds(); + assert!(re_locale.is_match(&result.stdout_str())); + + //Overwrite options tests + let result = scene + .ucmd() + .arg("-l") + .arg("--time-style=long-iso") + .arg("--time-style=iso") + .succeeds(); + assert!(re_iso.is_match(&result.stdout_str())); + let result = scene + .ucmd() + .arg("--time-style=iso") + .arg("--full-time") + .succeeds(); + assert!(re_full.is_match(&result.stdout_str())); + let result = scene + .ucmd() + .arg("--full-time") + .arg("--time-style=iso") + .succeeds(); + assert!(re_iso.is_match(&result.stdout_str())); + + let result = scene + .ucmd() + .arg("--full-time") + .arg("--time-style=iso") + .arg("--full-time") + .succeeds(); + assert!(re_full.is_match(&result.stdout_str())); + + let result = scene + .ucmd() + .arg("--full-time") + .arg("-x") + .arg("-l") + .succeeds(); + assert!(re_full.is_match(&result.stdout_str())); + + at.touch("test2"); + let result = scene.ucmd().arg("--full-time").arg("-x").succeeds(); + #[cfg(not(windows))] + assert_eq!(result.stdout_str(), "test\ntest2\n"); + #[cfg(windows)] + assert_eq!(result.stdout_str(), "test test2\n"); +} + #[test] fn test_ls_order_time() { let scene = TestScenario::new(util_name!()); From 1ca6edb560187d8e4d2df558bd673594117c63fa Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 28 Apr 2021 20:55:08 +0200 Subject: [PATCH 180/399] fix the min rust version --- Cargo.lock | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 863a36451..cdba3b784 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" @@ -734,15 +732,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" -[[package]] -name = "indoc" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a75aeaaef0ce18b58056d306c27b07436fbb34b8816c53094b76dd81803136" -dependencies = [ - "unindent", -] - [[package]] name = "ioctl-sys" version = "0.5.2" @@ -1484,9 +1473,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883" +checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -2082,7 +2071,6 @@ dependencies = [ "chrono", "clap", "globset", - "indoc", "lazy_static", "locale", "lscolors", From 6f16cafe88d5c807c731a1b1d01b83e7c000c3ff Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 28 Apr 2021 22:58:28 +0200 Subject: [PATCH 181/399] who: move from getopts to clap (#2124) --- src/uu/who/Cargo.toml | 1 + src/uu/who/src/who.rs | 221 ++++++++++++++++++++++++++------------ tests/by-util/test_who.rs | 183 ++++++++++++++++++++++++++++--- 3 files changed, 322 insertions(+), 83 deletions(-) diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index c0cd63795..ff1c5b2af 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -17,6 +17,7 @@ path = "src/who.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +clap = "3.0.0-beta.2" [[bin]] name = "who" diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index e979d2d46..47c9dfa6e 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -12,79 +12,164 @@ extern crate uucore; use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP}; use uucore::utmpx::{self, time, Utmpx}; +use clap::{App, Arg}; use std::borrow::Cow; use std::ffi::CStr; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTION]... [ FILE | ARG1 ARG2 ]"; -static SUMMARY: &str = "Print information about users who are currently logged in."; -static LONG_HELP: &str = " - -a, --all same as -b -d --login -p -r -t -T -u - -b, --boot time of last system boot - -d, --dead print dead processes - -H, --heading print line of column headings - -l, --login print system login processes - --lookup attempt to canonicalize hostnames via DNS - -m only hostname and user associated with stdin - -p, --process print active processes spawned by init - -q, --count all login names and number of users logged on - -r, --runlevel print current runlevel (not available on BSDs) - -s, --short print only name, line, and time (default) - -t, --time print last system clock change - -T, -w, --mesg add user's message status as +, - or ? - -u, --users list users logged in - --message same as -T - --writable same as -T - --help display this help and exit - --version output version information and exit +mod options { + pub const ALL: &str = "all"; + pub const BOOT: &str = "boot"; + pub const DEAD: &str = "dead"; + pub const HEADING: &str = "heading"; + pub const LOGIN: &str = "login"; + pub const LOOKUP: &str = "lookup"; + pub const ONLY_HOSTNAME_USER: &str = "only_hostname_user"; + pub const PROCESS: &str = "process"; + pub const COUNT: &str = "count"; + #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] + pub const RUNLEVEL: &str = "runlevel"; + pub const SHORT: &str = "short"; + pub const TIME: &str = "time"; + pub const USERS: &str = "users"; + pub const MESG: &str = "mesg"; // aliases: --message, --writable + pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2 +} -If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common. -If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual. -"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Print information about users who are currently logged in."; + +fn get_usage() -> String { + format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", executable!()) +} + +fn get_long_usage() -> String { + String::from( + "If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common.\n\ +If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.", + ) +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let mut opts = app!(SYNTAX, SUMMARY, LONG_HELP); - opts.optflag("a", "all", "same as -b -d --login -p -r -t -T -u"); - opts.optflag("b", "boot", "time of last system boot"); - opts.optflag("d", "dead", "print dead processes"); - opts.optflag("H", "heading", "print line of column headings"); - opts.optflag("l", "login", "print system login processes"); - opts.optflag("", "lookup", "attempt to canonicalize hostnames via DNS"); - opts.optflag("m", "", "only hostname and user associated with stdin"); - opts.optflag("p", "process", "print active processes spawned by init"); - opts.optflag( - "q", - "count", - "all login names and number of users logged on", - ); - #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] - opts.optflag("r", "runlevel", "print current runlevel"); - opts.optflag("s", "short", "print only name, line, and time (default)"); - opts.optflag("t", "time", "print last system clock change"); - opts.optflag("u", "users", "list users logged in"); - opts.optflag("w", "mesg", "add user's message status as +, - or ?"); - // --message, --writable are the same as --mesg - opts.optflag("T", "message", ""); - opts.optflag("T", "writable", ""); + let usage = get_usage(); + let after_help = get_long_usage(); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .override_usage(&usage[..]) + .after_help(&after_help[..]) + .arg( + Arg::new(options::ALL) + .long(options::ALL) + .short('a') + .about("same as -b -d --login -p -r -t -T -u"), + ) + .arg( + Arg::new(options::BOOT) + .long(options::BOOT) + .short('b') + .about("time of last system boot"), + ) + .arg( + Arg::new(options::DEAD) + .long(options::DEAD) + .short('d') + .about("print dead processes"), + ) + .arg( + Arg::new(options::HEADING) + .long(options::HEADING) + .short('H') + .about("print line of column headings"), + ) + .arg( + Arg::new(options::LOGIN) + .long(options::LOGIN) + .short('l') + .about("print system login processes"), + ) + .arg( + Arg::new(options::LOOKUP) + .long(options::LOOKUP) + .about("attempt to canonicalize hostnames via DNS"), + ) + .arg( + Arg::new(options::ONLY_HOSTNAME_USER) + .short('m') + .about("only hostname and user associated with stdin"), + ) + .arg( + Arg::new(options::PROCESS) + .long(options::PROCESS) + .short('p') + .about("print active processes spawned by init"), + ) + .arg( + Arg::new(options::COUNT) + .long(options::COUNT) + .short('q') + .about("all login names and number of users logged on"), + ) + .arg( + #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] + Arg::new(options::RUNLEVEL) + .long(options::RUNLEVEL) + .short('r') + .about("print current runlevel"), + ) + .arg( + Arg::new(options::SHORT) + .long(options::SHORT) + .short('s') + .about("print only name, line, and time (default)"), + ) + .arg( + Arg::new(options::TIME) + .long(options::TIME) + .short('t') + .about("print last system clock change"), + ) + .arg( + Arg::new(options::USERS) + .long(options::USERS) + .short('u') + .about("list users logged in"), + ) + .arg( + Arg::new(options::MESG) + .long(options::MESG) + .short('T') + .visible_short_alias('w') + .visible_aliases(&["message", "writable"]) + .about("add user's message status as +, - or ?"), + ) + .arg( + Arg::new(options::FILE) + .takes_value(true) + .min_values(1) + .max_values(2), + ) + .get_matches_from(args); - let matches = opts.parse(args); + let files: Vec = matches + .values_of(options::FILE) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); // If true, attempt to canonicalize hostnames via a DNS lookup. - let do_lookup = matches.opt_present("lookup"); + let do_lookup = matches.is_present(options::LOOKUP); // If true, display only a list of usernames and count of // the users logged on. // Ignored for 'who am i'. - let short_list = matches.opt_present("q"); + let short_list = matches.is_present(options::COUNT); // If true, display only name, line, and time fields. let mut short_output = false; @@ -95,12 +180,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut include_idle = false; // If true, display a line at the top describing each field. - let include_heading = matches.opt_present("H"); + let include_heading = matches.is_present(options::HEADING); // If true, display a '+' for each user if mesg y, a '-' if mesg n, // or a '?' if their tty cannot be statted. - let include_mesg = - matches.opt_present("a") || matches.opt_present("T") || matches.opt_present("w"); + let include_mesg = matches.is_present(options::ALL) || matches.is_present(options::MESG); // If true, display process termination & exit status. let mut include_exit = false; @@ -133,7 +217,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { #[allow(clippy::useless_let_if_seq)] { - if matches.opt_present("a") { + if matches.is_present(options::ALL) { need_boottime = true; need_deadprocs = true; need_login = true; @@ -146,49 +230,49 @@ pub fn uumain(args: impl uucore::Args) -> i32 { assumptions = false; } - if matches.opt_present("b") { + if matches.is_present(options::BOOT) { need_boottime = true; assumptions = false; } - if matches.opt_present("d") { + if matches.is_present(options::DEAD) { need_deadprocs = true; include_idle = true; include_exit = true; assumptions = false; } - if matches.opt_present("l") { + if matches.is_present(options::LOGIN) { need_login = true; include_idle = true; assumptions = false; } - if matches.opt_present("m") || matches.free.len() == 2 { + if matches.is_present(options::ONLY_HOSTNAME_USER) || files.len() == 2 { my_line_only = true; } - if matches.opt_present("p") { + if matches.is_present(options::PROCESS) { need_initspawn = true; assumptions = false; } - if matches.opt_present("r") { + if matches.is_present(options::RUNLEVEL) { need_runlevel = true; include_idle = true; assumptions = false; } - if matches.opt_present("s") { + if matches.is_present(options::SHORT) { short_output = true; } - if matches.opt_present("t") { + if matches.is_present(options::TIME) { need_clockchange = true; assumptions = false; } - if matches.opt_present("u") { + if matches.is_present(options::USERS) { need_users = true; include_idle = true; assumptions = false; @@ -202,11 +286,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if include_exit { short_output = false; } - - if matches.free.len() > 2 { - show_usage_error!("{}", msg_wrong_number_of_arguments!()); - exit!(1); - } } let mut who = Who { @@ -225,7 +304,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { need_runlevel, need_users, my_line_only, - args: matches.free, + args: files, }; who.exec(); diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 32d2427e0..9bd607ec3 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -1,11 +1,13 @@ -#[cfg(target_os = "linux")] use crate::common::util::*; #[cfg(target_os = "linux")] #[test] fn test_count() { for opt in vec!["-q", "--count"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -13,17 +15,21 @@ fn test_count() { #[test] fn test_boot() { for opt in vec!["-b", "--boot"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } #[cfg(target_os = "linux")] #[test] fn test_heading() { - for opt in vec!["-H"] { + for opt in vec!["-H", "--heading"] { // 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_move_str(); + // * 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).succeeds().stdout_move_str(); let expect = expected_result(opt); println!("actual: {:?}", actual); println!("expect: {:?}", expect); @@ -37,7 +43,10 @@ fn test_heading() { #[test] fn test_short() { for opt in vec!["-s", "--short"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -45,7 +54,10 @@ fn test_short() { #[test] fn test_login() { for opt in vec!["-l", "--login"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -53,7 +65,109 @@ fn test_login() { #[test] fn test_m() { for opt in vec!["-m"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_process() { + for opt in vec!["-p", "--process"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_runlevel() { + for opt in vec!["-r", "--runlevel"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_time() { + for opt in vec!["-t", "--time"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_mesg() { + for opt in vec!["-w", "-T", "--users", "--message", "--writable"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_arg1_arg2() { + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("am") + .arg("i") + .succeeds(); + + scene + .ucmd() + .arg("am") + .arg("i") + .succeeds() + .stdout_is(expected.stdout_str()); +} + +#[test] +fn test_too_many_args() { + let expected = + "error: The value 'u' was provided to '...' but it wasn't expecting any more values"; + + new_ucmd!() + .arg("am") + .arg("i") + .arg("u") + .fails() + .stderr_contains(expected); +} + +#[cfg(target_os = "linux")] +#[test] +fn test_users() { + for opt in vec!["-u", "--users"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); + } +} + +#[cfg(target_os = "linux")] +#[test] +fn test_lookup() { + for opt in vec!["--lookup"] { + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -61,15 +175,60 @@ fn test_m() { #[test] fn test_dead() { for opt in vec!["-d", "--dead"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } +#[cfg(target_os = "linux")] +#[test] +fn test_all_separately() { + // -a, --all same as -b -d --login -p -r -t -T -u + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("-b") + .arg("-d") + .arg("--login") + .arg("-p") + .arg("-r") + .arg("-t") + .arg("-T") + .arg("-u") + .succeeds(); + + scene + .ucmd() + .arg("-b") + .arg("-d") + .arg("--login") + .arg("-p") + .arg("-r") + .arg("-t") + .arg("-T") + .arg("-u") + .succeeds() + .stdout_is(expected.stdout_str()); + + scene + .ucmd() + .arg("--all") + .succeeds() + .stdout_is(expected.stdout_str()); +} + #[cfg(target_os = "linux")] #[test] fn test_all() { for opt in vec!["-a", "--all"] { - new_ucmd!().arg(opt).run().stdout_is(expected_result(opt)); + new_ucmd!() + .arg(opt) + .succeeds() + .stdout_is(expected_result(opt)); } } @@ -79,6 +238,6 @@ fn expected_result(arg: &str) -> String { .cmd_keepenv(util_name!()) .env("LANGUAGE", "C") .args(&[arg]) - .run() + .succeeds() .stdout_move_str() } From 512d206f1e87b546428c2aa45f8a9ef93ce89bef Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 29 Apr 2021 00:11:21 +0200 Subject: [PATCH 182/399] who: move from getopts to clap 2.33.3 (#2124) --- src/uu/who/Cargo.toml | 2 +- src/uu/who/src/who.rs | 97 +++++++++++++++++++++------------------ tests/by-util/test_who.rs | 2 +- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index ff1c5b2af..4d8eccb45 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -17,7 +17,7 @@ path = "src/who.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -clap = "3.0.0-beta.2" +clap = "2.33.3" [[bin]] name = "who" diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 47c9dfa6e..ba1360eff 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -63,95 +63,100 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) - .override_usage(&usage[..]) + .usage(&usage[..]) .after_help(&after_help[..]) .arg( - Arg::new(options::ALL) + Arg::with_name(options::ALL) .long(options::ALL) - .short('a') - .about("same as -b -d --login -p -r -t -T -u"), + .short("a") + .help("same as -b -d --login -p -r -t -T -u"), ) .arg( - Arg::new(options::BOOT) + Arg::with_name(options::BOOT) .long(options::BOOT) - .short('b') - .about("time of last system boot"), + .short("b") + .help("time of last system boot"), ) .arg( - Arg::new(options::DEAD) + Arg::with_name(options::DEAD) .long(options::DEAD) - .short('d') - .about("print dead processes"), + .short("d") + .help("print dead processes"), ) .arg( - Arg::new(options::HEADING) + Arg::with_name(options::HEADING) .long(options::HEADING) - .short('H') - .about("print line of column headings"), + .short("H") + .help("print line of column headings"), ) .arg( - Arg::new(options::LOGIN) + Arg::with_name(options::LOGIN) .long(options::LOGIN) - .short('l') - .about("print system login processes"), + .short("l") + .help("print system login processes"), ) .arg( - Arg::new(options::LOOKUP) + Arg::with_name(options::LOOKUP) .long(options::LOOKUP) - .about("attempt to canonicalize hostnames via DNS"), + .help("attempt to canonicalize hostnames via DNS"), ) .arg( - Arg::new(options::ONLY_HOSTNAME_USER) - .short('m') - .about("only hostname and user associated with stdin"), + Arg::with_name(options::ONLY_HOSTNAME_USER) + .short("m") + .help("only hostname and user associated with stdin"), ) .arg( - Arg::new(options::PROCESS) + Arg::with_name(options::PROCESS) .long(options::PROCESS) - .short('p') - .about("print active processes spawned by init"), + .short("p") + .help("print active processes spawned by init"), ) .arg( - Arg::new(options::COUNT) + Arg::with_name(options::COUNT) .long(options::COUNT) - .short('q') - .about("all login names and number of users logged on"), + .short("q") + .help("all login names and number of users logged on"), ) .arg( #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] - Arg::new(options::RUNLEVEL) + Arg::with_name(options::RUNLEVEL) .long(options::RUNLEVEL) - .short('r') - .about("print current runlevel"), + .short("r") + .help("print current runlevel"), ) .arg( - Arg::new(options::SHORT) + Arg::with_name(options::SHORT) .long(options::SHORT) - .short('s') - .about("print only name, line, and time (default)"), + .short("s") + .help("print only name, line, and time (default)"), ) .arg( - Arg::new(options::TIME) + Arg::with_name(options::TIME) .long(options::TIME) - .short('t') - .about("print last system clock change"), + .short("t") + .help("print last system clock change"), ) .arg( - Arg::new(options::USERS) + Arg::with_name(options::USERS) .long(options::USERS) - .short('u') - .about("list users logged in"), + .short("u") + .help("list users logged in"), ) .arg( - Arg::new(options::MESG) + Arg::with_name(options::MESG) .long(options::MESG) - .short('T') - .visible_short_alias('w') + .short("T") + // .visible_short_alias('w') // TODO: requires clap "3.0.0-beta.2" .visible_aliases(&["message", "writable"]) - .about("add user's message status as +, - or ?"), + .help("add user's message status as +, - or ?"), ) .arg( - Arg::new(options::FILE) + Arg::with_name("w") // work around for `Arg::visible_short_alias` + .short("w") + .help("same as -T"), + ) + .arg( + Arg::with_name(options::FILE) .takes_value(true) .min_values(1) .max_values(2), @@ -184,7 +189,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // If true, display a '+' for each user if mesg y, a '-' if mesg n, // or a '?' if their tty cannot be statted. - let include_mesg = matches.is_present(options::ALL) || matches.is_present(options::MESG); + let include_mesg = matches.is_present(options::ALL) + || matches.is_present(options::MESG) + || matches.is_present("w"); // If true, display process termination & exit status. let mut include_exit = false; diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 9bd607ec3..a5637f23a 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -139,7 +139,7 @@ fn test_arg1_arg2() { #[test] fn test_too_many_args() { let expected = - "error: The value 'u' was provided to '...' but it wasn't expecting any more values"; + "error: The value 'u' was provided to '...', but it wasn't expecting any more values"; new_ucmd!() .arg("am") From b89978a4c97085924dbc0656b5ead2a103d1274e Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 29 Apr 2021 15:56:56 +0200 Subject: [PATCH 183/399] factor: Add annotations for coz, the causal profiler (#2142) * factor: Add annotations for coz, the causal profiler * Update Cargo.lock Generated with `nix-shell -p rustup --run 'cargo +1.40.0 update'` --- Cargo.lock | 21 ++++++++++++++++----- src/uu/factor/Cargo.toml | 2 +- src/uu/factor/src/factor.rs | 15 +++++++++++++-- src/uu/factor/src/table.rs | 2 ++ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdba3b784..31787e626 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,6 +333,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "coz" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef55b3fe2f5477d59e12bc792e8b3c95a25bd099eadcfae006ecea136de76e2" +dependencies = [ + "libc", + "once_cell", +] + [[package]] name = "cpp" version = "0.5.6" @@ -608,7 +618,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.6", + "redox_syscall 0.2.7", "winapi 0.3.9", ] @@ -1249,9 +1259,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" +checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2" dependencies = [ "bitflags", ] @@ -1262,7 +1272,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.6", + "redox_syscall 0.2.7", ] [[package]] @@ -1533,7 +1543,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", - "redox_syscall 0.2.6", + "redox_syscall 0.2.7", "redox_termios", ] @@ -1898,6 +1908,7 @@ dependencies = [ name = "uu_factor" version = "0.0.6" dependencies = [ + "coz", "criterion", "num-traits", "paste", diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 489c713be..c4e7e8469 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -14,8 +14,8 @@ edition = "2018" [build-dependencies] num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs - [dependencies] +coz = { version = "0.1.3", optional = true } num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" rand = { version="0.7", features=["small_rng"] } smallvec = { version="0.6.14, < 1.0" } diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 7d2e16a11..42586d1da 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -125,6 +125,8 @@ fn _factor(num: u64, f: Factors) -> Factors let n = A::new(num); let divisor = match miller_rabin::test::
(n) { Prime => { + #[cfg(feature="coz")] + coz::progress!("factor found"); let mut r = f; r.push(num); return r; @@ -139,6 +141,8 @@ fn _factor(num: u64, f: Factors) -> Factors } pub fn factor(mut n: u64) -> Factors { + #[cfg(feature="coz")] + coz::begin!("factorization"); let mut factors = Factors::one(); if n < 2 { @@ -152,16 +156,23 @@ pub fn factor(mut n: u64) -> Factors { } if n == 1 { + #[cfg(feature="coz")] + coz::end!("factorization"); return factors; } let (factors, n) = table::factor(n, factors); - if n < (1 << 32) { + let r = if n < (1 << 32) { _factor::>(n, factors) } else { _factor::>(n, factors) - } + }; + + #[cfg(feature="coz")] + coz::end!("factorization"); + + return r; } #[cfg(test)] diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index d6ef796fc..6291b92c1 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -33,6 +33,8 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { if x <= ceil { num = x; k += 1; + #[cfg(feature="coz")] + coz::progress!("factor found"); } else { if k > 0 { factors.add(prime, k); From 9f45431bf041053268bd4e76c10189432122a637 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 29 Apr 2021 18:02:06 +0200 Subject: [PATCH 184/399] sort: add some custom string comparisons This removes the need to allocate a new string for each line when used with -f, -d or -i. Instead, a custom string comparison algorithm takes care of these cases. The resulting performance improvement is about 20% per flag (i.e. there is a 60% improvement when combining all three flags) As a side-effect, the size of the Line struct was reduced from 96 to 80 bytes, reducing the overhead for each line. --- src/uu/sort/src/custom_str_cmp.rs | 64 +++++++++++++++++ src/uu/sort/src/sort.rs | 111 ++++++------------------------ 2 files changed, 86 insertions(+), 89 deletions(-) create mode 100644 src/uu/sort/src/custom_str_cmp.rs diff --git a/src/uu/sort/src/custom_str_cmp.rs b/src/uu/sort/src/custom_str_cmp.rs new file mode 100644 index 000000000..a087a9fc2 --- /dev/null +++ b/src/uu/sort/src/custom_str_cmp.rs @@ -0,0 +1,64 @@ +// * This file is part of the uutils coreutils package. +// * +// * (c) Michael Debertol +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +//! Custom string comparisons. +//! +//! The goal is to compare strings without transforming them first (i.e. not allocating new strings) + +use std::cmp::Ordering; + +fn filter_char(c: char, ignore_non_printing: bool, ignore_non_dictionary: bool) -> bool { + if ignore_non_dictionary && !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) { + return false; + } + if ignore_non_printing && (c.is_ascii_control() || !c.is_ascii()) { + return false; + } + true +} + +fn cmp_chars(a: char, b: char, ignore_case: bool) -> Ordering { + if ignore_case { + a.to_ascii_uppercase().cmp(&b.to_ascii_uppercase()) + } else { + a.cmp(&b) + } +} + +pub fn custom_str_cmp( + a: &str, + b: &str, + ignore_non_printing: bool, + ignore_non_dictionary: bool, + ignore_case: bool, +) -> Ordering { + if !(ignore_case || ignore_non_dictionary || ignore_non_printing) { + // There are no custom settings. Fall back to the default strcmp, which is faster. + return a.cmp(&b); + } + let mut a_chars = a + .chars() + .filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); + let mut b_chars = b + .chars() + .filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); + loop { + let a_char = a_chars.next(); + let b_char = b_chars.next(); + match (a_char, b_char) { + (None, None) => return Ordering::Equal, + (Some(_), None) => return Ordering::Greater, + (None, Some(_)) => return Ordering::Less, + (Some(a_char), Some(b_char)) => { + let ordering = cmp_chars(a_char, b_char, ignore_case); + if ordering != Ordering::Equal { + return ordering; + } + } + } + } +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 18d9304fa..a18850e9d 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,10 +15,12 @@ #[macro_use] extern crate uucore; +mod custom_str_cmp; mod external_sort; mod numeric_str_cmp; use clap::{App, Arg}; +use custom_str_cmp::custom_str_cmp; use external_sort::{ExternalSorter, ExternallySortable}; use fnv::FnvHasher; use itertools::Itertools; @@ -206,33 +208,23 @@ impl From<&GlobalSettings> for KeySettings { #[derive(Debug, Serialize, Deserialize, Clone)] /// Represents the string selected by a FieldSelector. -enum SelectionRange { - /// If we had to transform this selection, we have to store a new string. - String(String), - /// If there was no transformation, we can store an index into the line. - ByIndex(Range), +struct SelectionRange { + range: Range, } impl SelectionRange { + fn new(range: Range) -> Self { + Self { range } + } + /// Gets the actual string slice represented by this Selection. - fn get_str<'a>(&'a self, line: &'a str) -> &'a str { - match self { - SelectionRange::String(string) => string.as_str(), - SelectionRange::ByIndex(range) => &line[range.to_owned()], - } + fn get_str<'a>(&self, line: &'a str) -> &'a str { + &line[self.range.to_owned()] } fn shorten(&mut self, new_range: Range) { - match self { - SelectionRange::String(string) => { - string.drain(new_range.end..); - string.drain(..new_range.start); - } - SelectionRange::ByIndex(range) => { - range.end = range.start + new_range.end; - range.start += new_range.start; - } - } + self.range.end = self.range.start + new_range.end; + self.range.start += new_range.start; } } @@ -303,14 +295,8 @@ impl Line { .selectors .iter() .map(|selector| { - let range = selector.get_selection(&line, fields.as_deref()); - let mut range = if let Some(transformed) = - transform(&line[range.to_owned()], &selector.settings) - { - SelectionRange::String(transformed) - } else { - SelectionRange::ByIndex(range) - }; + let mut range = + SelectionRange::new(selector.get_selection(&line, fields.as_deref())); let num_cache = if selector.settings.mode == SortMode::Numeric || selector.settings.mode == SortMode::HumanNumeric { @@ -460,34 +446,6 @@ impl Line { } } -/// Transform this line. Returns None if there's no need to transform. -fn transform(line: &str, settings: &KeySettings) -> Option { - let mut transformed = None; - if settings.ignore_case { - transformed = Some(line.to_uppercase()); - } - if settings.ignore_blanks { - transformed = Some( - transformed - .as_deref() - .unwrap_or(line) - .trim_start() - .to_string(), - ); - } - if settings.dictionary_order { - transformed = Some(remove_nondictionary_chars( - transformed.as_deref().unwrap_or(line), - )); - } - if settings.ignore_non_printing { - transformed = Some(remove_nonprinting_chars( - transformed.as_deref().unwrap_or(line), - )); - } - transformed -} - /// Tokenize a line into fields. fn tokenize(line: &str, separator: Option) -> Vec { if let Some(separator) = separator { @@ -1301,7 +1259,13 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering ), SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), - SortMode::Default => default_compare(a_str, b_str), + SortMode::Default => custom_str_cmp( + a_str, + b_str, + settings.ignore_non_printing, + settings.dictionary_order, + settings.ignore_case, + ), } }; if cmp != Ordering::Equal { @@ -1313,7 +1277,7 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering let cmp = if global_settings.random || global_settings.stable || global_settings.unique { Ordering::Equal } else { - default_compare(&a.line, &b.line) + a.line.cmp(&b.line) }; if global_settings.reverse { @@ -1323,13 +1287,6 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } } -// Test output against BSDs and GNU with their locale -// env var set to lc_ctype=utf-8 to enjoy the exact same output. -#[inline(always)] -fn default_compare(a: &str, b: &str) -> Ordering { - a.cmp(b) -} - // This function cleans up the initial comparison done by leading_num_common for a general numeric compare. // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. @@ -1516,22 +1473,6 @@ fn version_compare(a: &str, b: &str) -> Ordering { } } -fn remove_nondictionary_chars(s: &str) -> String { - // According to GNU, dictionary chars are those of ASCII - // and a blank is a space or a tab - s.chars() - .filter(|c| c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) - .collect::() -} - -fn remove_nonprinting_chars(s: &str) -> String { - // However, GNU says nonprinting chars are more permissive. - // All of ASCII except control chars ie, escape, newline - s.chars() - .filter(|c| c.is_ascii() && !c.is_ascii_control()) - .collect::() -} - fn print_sorted>(iter: T, settings: &GlobalSettings) { let mut file: Box = match settings.outfile { Some(ref filename) => match File::create(Path::new(&filename)) { @@ -1598,14 +1539,6 @@ mod tests { assert_eq!(Ordering::Equal, random_shuffle(a, b, c)); } - #[test] - fn test_default_compare() { - let a = "your own"; - let b = "your place"; - - assert_eq!(Ordering::Less, default_compare(a, b)); - } - #[test] fn test_month_compare() { let a = "JaN"; From a4813c26468963b7053de57fcddb0c95fa5b23fa Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 29 Apr 2021 18:03:00 +0200 Subject: [PATCH 185/399] sort: actually use the f64 cache This was probably reverted accidentally. --- src/uu/sort/src/sort.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index a18850e9d..6768c82c1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1254,8 +1254,8 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering (b_str, b_selection.num_cache.as_num_info()), ), SortMode::GeneralNumeric => general_numeric_compare( - general_f64_parse(&a_str[get_leading_gen(a_str)]), - general_f64_parse(&b_str[get_leading_gen(b_str)]), + a_selection.num_cache.as_f64(), + b_selection.num_cache.as_f64(), ), SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), From fecbf3dc856a37db6cf8d7a7a4ca15953b6b3f9d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 29 Apr 2021 18:03:12 +0200 Subject: [PATCH 186/399] sort: remove an unneeded clone() --- src/uu/sort/src/sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 6768c82c1..c82524796 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1222,7 +1222,7 @@ fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { settings.clone(), ); let iter = external_sorter - .sort_by(unsorted.into_iter(), settings.clone()) + .sort_by(unsorted.into_iter(), settings) .unwrap() .map(|x| x.unwrap()) .collect::>(); From c69afa00ffcd5721b7bcbaa452495f8b8e673007 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Apr 2021 18:25:34 +0200 Subject: [PATCH 187/399] ls: implement device symbol and id --- src/uu/ls/src/ls.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d78e1977a..777f16e7f 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1623,10 +1623,18 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { fn display_file_size(metadata: &Metadata, config: &Config) -> String { // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. - match config.size_format { - SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), - SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), - SizeFormat::Bytes => metadata.len().to_string(), + let ft = metadata.file_type(); + if ft.is_char_device() || ft.is_block_device() { + let dev: u64 = metadata.rdev(); + let major = (dev >> 8) as u8; + let minor = dev as u8; + return format!("{}, {}", major, minor); + } else { + match config.size_format { + SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), + SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), + SizeFormat::Bytes => metadata.len().to_string(), + } } } @@ -1635,6 +1643,10 @@ fn display_file_type(file_type: FileType) -> char { 'd' } else if file_type.is_symlink() { 'l' + } else if file_type.is_block_device() { + 'b' + } else if file_type.is_char_device() { + 'c' } else { '-' } From d6248279133a001f66d6c2138a0ffbbb8f3f36fb Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Apr 2021 18:44:46 +0200 Subject: [PATCH 188/399] ls: fix windows and add more file types --- src/uu/ls/src/ls.rs | 48 ++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 777f16e7f..adfc654ba 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1621,20 +1621,23 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { } fn display_file_size(metadata: &Metadata, config: &Config) -> String { + #[cfg(unix)] + { + let ft = metadata.file_type(); + if ft.is_char_device() || ft.is_block_device() { + let dev: u64 = metadata.rdev(); + let major = (dev >> 8) as u8; + let minor = dev as u8; + return format!("{}, {}", major, minor); + } + } + // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. - let ft = metadata.file_type(); - if ft.is_char_device() || ft.is_block_device() { - let dev: u64 = metadata.rdev(); - let major = (dev >> 8) as u8; - let minor = dev as u8; - return format!("{}, {}", major, minor); - } else { - match config.size_format { - SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), - SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), - SizeFormat::Bytes => metadata.len().to_string(), - } + match config.size_format { + SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), + SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), + SizeFormat::Bytes => metadata.len().to_string(), } } @@ -1643,11 +1646,24 @@ fn display_file_type(file_type: FileType) -> char { 'd' } else if file_type.is_symlink() { 'l' - } else if file_type.is_block_device() { - 'b' - } else if file_type.is_char_device() { - 'c' } else { + #[cfg(unix)] + { + if file_type.is_block_device() { + 'b' + } else if file_type.is_char_device() { + 'c' + } else if file_type.is_fifo() { + 'p' + } else if file_type.is_socket() { + 's' + } else if file_type.is_file() { + '-' + } else { + '?' + } + } + #[cfg(not(unix))] '-' } } From d300895d28127befcc1f2922acba1b9279bc0f93 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 29 Apr 2021 22:23:04 +0200 Subject: [PATCH 189/399] ls: add birth time for windows and attampt to fix test --- src/uu/ls/src/ls.rs | 1 + tests/by-util/test_ls.rs | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index d78e1977a..dc7ea4c08 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1559,6 +1559,7 @@ fn get_system_time(md: &Metadata, config: &Config) -> Option { match config.time { Time::Modification => md.modified().ok(), Time::Access => md.accessed().ok(), + Time::Birth => md.created().ok(), _ => None, } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index eeb7a6248..cfcbf9fd1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -559,8 +559,6 @@ fn test_ls_long_ctime() { } #[test] -#[cfg(not(windows))] -// This test is currently failing on windows fn test_ls_order_birthtime() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -570,15 +568,11 @@ fn test_ls_order_birthtime() { After creating the first file try to sync it. This ensures the file gets created immediately instead of being saved inside the OS's IO operation buffer. - Without this, both files might accidentally be created at the same time, - even though we placed a timeout between creating the two. - - https://github.com/uutils/coreutils/pull/1986/#issuecomment-828490651 + Without this, both files might accidentally be created at the same time. */ at.make_file("test-birthtime-1").sync_all().unwrap(); - std::thread::sleep(std::time::Duration::from_millis(1)); - at.make_file("test-birthtime-2"); - at.touch("test-birthtime-1"); + at.make_file("test-birthtime-2").sync_all().unwrap(); + at.open("test-birthtime-1"); let result = scene.ucmd().arg("--time=birth").arg("-t").run(); From 45dd9d4e963c23ef0dce7f120ba43f80f14d2399 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 30 Apr 2021 20:19:43 +0200 Subject: [PATCH 190/399] tr/dirname: fix clap short_alias --- src/uu/dirname/src/dirname.rs | 33 +++++++++++++++++++-------------- src/uu/tr/src/tr.rs | 34 +++++++++++++++++++++++----------- tests/by-util/test_dirname.rs | 15 +++++++++++++++ tests/by-util/test_tr.rs | 14 ++++++++++++++ 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 5937f16ca..63693c982 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -12,37 +12,42 @@ use clap::{App, Arg}; use std::path::Path; use uucore::InvalidEncodingHandling; -static NAME: &str = "dirname"; -static SYNTAX: &str = "[OPTION] NAME..."; -static SUMMARY: &str = "strip last component from file name"; +static ABOUT: &str = "strip last component from file name"; static VERSION: &str = env!("CARGO_PKG_VERSION"); -static LONG_HELP: &str = " - Output each NAME with its last non-slash component and trailing slashes - removed; if NAME contains no /'s, output '.' (meaning the current - directory). -"; mod options { pub const ZERO: &str = "zero"; pub const DIR: &str = "dir"; } +fn get_usage() -> String { + format!("{0} [OPTION] NAME...", executable!()) +} + +fn get_long_usage() -> String { + String::from( + "Output each NAME with its last non-slash component and trailing slashes + removed; if NAME contains no /'s, output '.' (meaning the current directory).", + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); + let usage = get_usage(); + let after_help = get_long_usage(); + let matches = App::new(executable!()) - .name(NAME) - .usage(SYNTAX) - .about(SUMMARY) - .after_help(LONG_HELP) + .about(ABOUT) + .usage(&usage[..]) + .after_help(&after_help[..]) .version(VERSION) .arg( Arg::with_name(options::ZERO) - .short(options::ZERO) + .long(options::ZERO) .short("z") - .takes_value(false) .help("separate output with NUL rather than newline"), ) .arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..706151c35 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -23,11 +23,9 @@ use std::io::{stdin, stdout, BufRead, BufWriter, Write}; use crate::expand::ExpandSet; use uucore::InvalidEncodingHandling; -static NAME: &str = "tr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "translate or delete characters"; -static LONG_HELP: &str = "Translate, squeeze, and/or delete characters from standard input, -writing to standard output."; + const BUFFER_LEN: usize = 1024; mod options { @@ -186,24 +184,38 @@ fn get_usage() -> String { format!("{} [OPTION]... SET1 [SET2]", executable!()) } +fn get_long_usage() -> String { + String::from( + "Translate, squeeze, and/or delete characters from standard input, +writing to standard output.", + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); + let usage = get_usage(); + let after_help = get_long_usage(); + let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) .usage(&usage[..]) - .after_help(LONG_HELP) + .after_help(&after_help[..]) .arg( Arg::with_name(options::COMPLEMENT) - .short("C") + // .visible_short_alias('C') // TODO: requires clap "3.0.0-beta.2" .short("c") .long(options::COMPLEMENT) .help("use the complement of SET1"), ) + .arg( + Arg::with_name("C") // work around for `Arg::visible_short_alias` + .short("C") + .help("same as -c"), + ) .arg( Arg::with_name(options::DELETE) .short("d") @@ -216,8 +228,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .short("s") .help( "replace each sequence of a repeated character that is - listed in the last specified SET, with a single occurrence - of that character", + listed in the last specified SET, with a single occurrence + of that character", ), ) .arg( @@ -230,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .get_matches_from(args); let delete_flag = matches.is_present(options::DELETE); - let complement_flag = matches.is_present(options::COMPLEMENT); + let complement_flag = matches.is_present(options::COMPLEMENT) || matches.is_present("C"); let squeeze_flag = matches.is_present(options::SQUEEZE); let truncate_flag = matches.is_present(options::TRUNCATE); @@ -242,7 +254,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if sets.is_empty() { show_error!( "missing operand\nTry `{} --help` for more information.", - NAME + executable!() ); return 1; } @@ -251,7 +263,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!( "missing operand after ‘{}’\nTry `{} --help` for more information.", sets[0], - NAME + executable!() ); return 1; } diff --git a/tests/by-util/test_dirname.rs b/tests/by-util/test_dirname.rs index bcb4378d6..026ac22bb 100644 --- a/tests/by-util/test_dirname.rs +++ b/tests/by-util/test_dirname.rs @@ -16,6 +16,21 @@ fn test_path_without_trailing_slashes() { .stdout_is("/root/alpha/beta/gamma/delta/epsilon\n"); } +#[test] +fn test_path_without_trailing_slashes_and_zero() { + new_ucmd!() + .arg("-z") + .arg("/root/alpha/beta/gamma/delta/epsilon/omega") + .succeeds() + .stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}"); + + new_ucmd!() + .arg("--zero") + .arg("/root/alpha/beta/gamma/delta/epsilon/omega") + .succeeds() + .stdout_is("/root/alpha/beta/gamma/delta/epsilon\u{0}"); +} + #[test] fn test_root() { new_ucmd!().arg("/").run().stdout_is("/\n"); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..995fb6533 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -45,6 +45,20 @@ fn test_delete_complement() { .stdout_is("ac"); } +#[test] +fn test_delete_complement_2() { + new_ucmd!() + .args(&["-d", "-C", "0-9"]) + .pipe_in("Phone: 01234 567890") + .succeeds() + .stdout_is("01234567890"); + new_ucmd!() + .args(&["-d", "--complement", "0-9"]) + .pipe_in("Phone: 01234 567890") + .succeeds() + .stdout_is("01234567890"); +} + #[test] fn test_squeeze() { new_ucmd!() From 798a03331170766b0f83e8de8ad451b67bd6ca94 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 30 Apr 2021 15:23:54 +0200 Subject: [PATCH 191/399] pinky: move from getopts to clap (#2123) --- src/uu/pinky/Cargo.toml | 1 + src/uu/pinky/src/pinky.rs | 164 ++++++++++++++++++++++-------------- tests/by-util/test_pinky.rs | 30 +++++++ 3 files changed, 132 insertions(+), 63 deletions(-) diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 3f4a75241..a3c36259a 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -17,6 +17,7 @@ path = "src/pinky.rs" [dependencies] uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +clap = "2.33.3" [[bin]] name = "pinky" diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index a02096bc8..e116a2382 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -19,67 +19,110 @@ use std::io::BufReader; use std::fs::File; use std::os::unix::fs::MetadataExt; +use clap::{App, Arg}; use std::path::PathBuf; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTION]... [USER]..."; -static SUMMARY: &str = "A lightweight 'finger' program; print user information."; - const BUFSIZE: usize = 1024; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "pinky - lightweight finger"; + +mod options { + pub const LONG_FORMAT: &str = "long_format"; + pub const OMIT_HOME_DIR: &str = "omit_home_dir"; + pub const OMIT_PROJECT_FILE: &str = "omit_project_file"; + pub const OMIT_PLAN_FILE: &str = "omit_plan_file"; + pub const SHORT_FORMAT: &str = "short_format"; + pub const OMIT_HEADINGS: &str = "omit_headings"; + pub const OMIT_NAME: &str = "omit_name"; + pub const OMIT_NAME_HOST: &str = "omit_name_host"; + pub const OMIT_NAME_HOST_TIME: &str = "omit_name_host_time"; + pub const USER: &str = "user"; +} + +fn get_usage() -> String { + format!("{0} [OPTION]... [USER]...", executable!()) +} + +fn get_long_usage() -> String { + format!( + "A lightweight 'finger' program; print user information.\n\ + The utmp file will be {}.", + utmpx::DEFAULT_FILE + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let long_help = &format!( - " - -l produce long format output for the specified USERs - -b omit the user's home directory and shell in long format - -h omit the user's project file in long format - -p omit the user's plan file in long format - -s do short format output, this is the default - -f omit the line of column headings in short format - -w omit the user's full name in short format - -i omit the user's full name and remote host in short format - -q omit the user's full name, remote host and idle time - in short format - --help display this help and exit - --version output version information and exit + let usage = get_usage(); + let after_help = get_long_usage(); -The utmp file will be {}", - utmpx::DEFAULT_FILE - ); - let mut opts = app!(SYNTAX, SUMMARY, &long_help); - opts.optflag( - "l", - "", - "produce long format output for the specified USERs", - ); - opts.optflag( - "b", - "", - "omit the user's home directory and shell in long format", - ); - opts.optflag("h", "", "omit the user's project file in long format"); - opts.optflag("p", "", "omit the user's plan file in long format"); - opts.optflag("s", "", "do short format output, this is the default"); - opts.optflag("f", "", "omit the line of column headings in short format"); - opts.optflag("w", "", "omit the user's full name in short format"); - opts.optflag( - "i", - "", - "omit the user's full name and remote host in short format", - ); - opts.optflag( - "q", - "", - "omit the user's full name, remote host and idle time in short format", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .after_help(&after_help[..]) + .arg( + Arg::with_name(options::LONG_FORMAT) + .short("l") + .requires(options::USER) + .help("produce long format output for the specified USERs"), + ) + .arg( + Arg::with_name(options::OMIT_HOME_DIR) + .short("b") + .help("omit the user's home directory and shell in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PROJECT_FILE) + .short("h") + .help("omit the user's project file in long format"), + ) + .arg( + Arg::with_name(options::OMIT_PLAN_FILE) + .short("p") + .help("omit the user's plan file in long format"), + ) + .arg( + Arg::with_name(options::SHORT_FORMAT) + .short("s") + .help("do short format output, this is the default"), + ) + .arg( + Arg::with_name(options::OMIT_HEADINGS) + .short("f") + .help("omit the line of column headings in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME) + .short("w") + .help("omit the user's full name in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST) + .short("i") + .help("omit the user's full name and remote host in short format"), + ) + .arg( + Arg::with_name(options::OMIT_NAME_HOST_TIME) + .short("q") + .help("omit the user's full name, remote host and idle time in short format"), + ) + .arg( + Arg::with_name(options::USER) + .takes_value(true) + .multiple(true), + ) + .get_matches_from(args); - let matches = opts.parse(args); + let users: Vec = matches + .values_of(options::USER) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); // If true, display the hours:minutes since each user has touched // the keyboard, or blank if within the last minute, or days followed @@ -87,45 +130,40 @@ The utmp file will be {}", let mut include_idle = true; // If true, display a line at the top describing each field. - let include_heading = !matches.opt_present("f"); + let include_heading = !matches.is_present(options::OMIT_HEADINGS); // if true, display the user's full name from pw_gecos. let mut include_fullname = true; // if true, display the user's ~/.project file when doing long format. - let include_project = !matches.opt_present("h"); + let include_project = !matches.is_present(options::OMIT_PROJECT_FILE); // if true, display the user's ~/.plan file when doing long format. - let include_plan = !matches.opt_present("p"); + let include_plan = !matches.is_present(options::OMIT_PLAN_FILE); // if true, display the user's home directory and shell // when doing long format. - let include_home_and_shell = !matches.opt_present("b"); + let include_home_and_shell = !matches.is_present(options::OMIT_HOME_DIR); // if true, use the "short" output format. - let do_short_format = !matches.opt_present("l"); + let do_short_format = !matches.is_present(options::LONG_FORMAT); /* if true, display the ut_host field. */ let mut include_where = true; - if matches.opt_present("w") { + if matches.is_present(options::OMIT_NAME) { include_fullname = false; } - if matches.opt_present("i") { + if matches.is_present(options::OMIT_NAME_HOST) { include_fullname = false; include_where = false; } - if matches.opt_present("q") { + if matches.is_present(options::OMIT_NAME_HOST_TIME) { include_fullname = false; include_idle = false; include_where = false; } - if !do_short_format && matches.free.is_empty() { - show_usage_error!("no username specified; at least one must be specified when using -l"); - return 1; - } - let pk = Pinky { include_idle, include_heading, @@ -134,7 +172,7 @@ The utmp file will be {}", include_plan, include_home_and_shell, include_where, - names: matches.free, + names: users, }; if do_short_format { diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 7a4a3f3df..1a7ef8b61 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -34,6 +34,36 @@ fn test_long_format() { )); } +#[cfg(target_os = "linux")] +#[test] +fn test_long_format_multiple_users() { + let scene = TestScenario::new(util_name!()); + + let expected = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .arg("-l") + .arg("root") + .arg("root") + .arg("root") + .succeeds(); + + scene + .ucmd() + .arg("-l") + .arg("root") + .arg("root") + .arg("root") + .succeeds() + .stdout_is(expected.stdout_str()); +} + +#[test] +fn test_long_format_wo_user() { + // "no username specified; at least one must be specified when using -l" + new_ucmd!().arg("-l").fails().code_is(1); +} + #[cfg(target_os = "linux")] #[test] fn test_short_format_i() { From 0f3bc237393adef485182656b0edc986575c592d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 29 Apr 2021 23:13:20 -0400 Subject: [PATCH 192/399] tr: implement translate and squeeze (-s) mode Add translate and squeeze mode to the `tr` program. For example: $ printf xx | tr -s x y y Fixes #2141. --- src/uu/tr/src/tr.rs | 54 ++++++++++++++++++++++++++++++++++++++-- tests/by-util/test_tr.rs | 18 ++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..09c4304a5 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -278,8 +278,58 @@ pub fn uumain(args: impl uucore::Args) -> i32 { translate_input(&mut locked_stdin, &mut buffered_stdout, op); } } else if squeeze_flag { - let op = SqueezeOperation::new(set1, complement_flag); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + if sets.len() < 2 { + let op = SqueezeOperation::new(set1, complement_flag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); + } else { + // Define a closure that computes the translation using a hash map. + // + // The `unwrap()` should never panic because the + // `TranslateOperation.translate()` method always returns + // `Some`. + let mut set2 = ExpandSet::new(sets[1].as_ref()); + let translator = TranslateOperation::new(set1, &mut set2, truncate_flag); + let translate = |c| translator.translate(c, 0 as char).unwrap(); + + // Prepare some variables to be used for the closure that + // computes the squeeze operation. + // + // The `squeeze()` closure needs to be defined anew for + // each line of input, but these variables do not change + // while reading the input so they can be defined before + // the `while` loop. + let set2 = ExpandSet::new(sets[1].as_ref()); + let squeezer = SqueezeOperation::new(set2, complement_flag); + + // Prepare some memory to read each line of the input (`buf`) and to write + let mut buf = String::with_capacity(BUFFER_LEN + 4); + + // Loop over each line of stdin. + while let Ok(length) = locked_stdin.read_line(&mut buf) { + if length == 0 { + break; + } + + // Define a closure that computes the squeeze operation. + // + // We keep track of the previously seen character on + // each call to `squeeze()`, but we need to reset the + // `prev_c` variable at the beginning of each line of + // the input. That's why we define the closure inside + // the `while` loop. + let mut prev_c = 0 as char; + let squeeze = |c| { + let result = squeezer.translate(c, prev_c); + prev_c = c; + result + }; + + // First translate, then squeeze each character of the input line. + let filtered: String = buf.chars().map(translate).filter_map(squeeze).collect(); + buf.clear(); + buffered_stdout.write_all(filtered.as_bytes()).unwrap(); + } + } } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); let op = TranslateOperation::new(set1, &mut set2, truncate_flag); diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..5d044a187 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -63,6 +63,24 @@ fn test_squeeze_complement() { .stdout_is("aaBcDcc"); } +#[test] +fn test_translate_and_squeeze() { + new_ucmd!() + .args(&["-s", "x", "y"]) + .pipe_in("xx") + .run() + .stdout_is("y"); +} + +#[test] +fn test_translate_and_squeeze_multiple_lines() { + new_ucmd!() + .args(&["-s", "x", "y"]) + .pipe_in("xxaax\nxaaxx") + .run() + .stdout_is("yaay\nyaay"); +} + #[test] fn test_delete_and_squeeze() { new_ucmd!() From 59ea28628bce74b5c17ea9d3462c27f82f5e5e84 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 13:11:41 +0200 Subject: [PATCH 193/399] printf: remove useless declaration --- .../printf/src/tokenize/num_format/formatters/base_conv/mod.rs | 1 - 1 file changed, 1 deletion(-) 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 82971df3e..7d1d805c6 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 @@ -158,7 +158,6 @@ pub fn base_conv_float(src: &[u8], radix_src: u8, _radix_dest: u8) -> f64 { // to implement this for arbitrary string input. // until then, the below operates as an outline // of how it would work. - let result: Vec = vec![0]; let mut factor: f64 = 1_f64; let radix_src_float: f64 = f64::from(radix_src); let mut r: f64 = 0_f64; From 66930186319c6c0a502e8aad80383313ebb6cbc3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 13:12:00 +0200 Subject: [PATCH 194/399] refresh cargo.lock with recent updates --- Cargo.lock | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..33d599762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,11 +12,11 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "memchr 2.3.4", + "memchr 2.4.0", ] [[package]] @@ -111,7 +111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" dependencies = [ "lazy_static", - "memchr 2.3.4", + "memchr 2.4.0", "regex-automata", "serde", ] @@ -495,9 +495,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -536,7 +536,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ - "memchr 2.3.4", + "memchr 2.4.0", ] [[package]] @@ -868,9 +868,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "memoffset" @@ -1089,7 +1089,7 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -1277,12 +1277,12 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.6" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" +checksum = "a068b905b8cb93815aa3069ae48653d90f382308aebd1d33d940ac1f1d771e2d" dependencies = [ "aho-corasick", - "memchr 2.3.4", + "memchr 2.4.0", "regex-syntax", ] @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "00efb87459ba4f6fb2169d20f68565555688e1250ee6825cdf6254f8b48fafb2" [[package]] name = "remove_dir_all" @@ -1489,7 +1489,7 @@ checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" dependencies = [ "proc-macro2", "quote 1.0.9", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -1636,9 +1636,9 @@ checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "unindent" @@ -1806,7 +1806,7 @@ version = "0.0.6" dependencies = [ "bstr", "clap", - "memchr 2.3.4", + "memchr 2.4.0", "uucore", "uucore_procs", ] @@ -2175,7 +2175,7 @@ dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.3.4", + "memchr 2.4.0", "regex", "regex-syntax", "uucore", @@ -2276,7 +2276,7 @@ dependencies = [ "aho-corasick", "clap", "libc", - "memchr 2.3.4", + "memchr 2.4.0", "regex", "regex-syntax", "uucore", From d2913f80804430c3101f7bdb33f89df15d91204b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 13:12:10 +0200 Subject: [PATCH 195/399] rustfmt the recent change --- src/uu/factor/src/factor.rs | 8 ++++---- src/uu/factor/src/table.rs | 2 +- tests/by-util/test_sort.rs | 10 +++++----- tests/by-util/test_truncate.rs | 10 ++++++++-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 42586d1da..5a85194c4 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -125,7 +125,7 @@ fn _factor(num: u64, f: Factors) -> Factors let n = A::new(num); let divisor = match miller_rabin::test::(n) { Prime => { - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::progress!("factor found"); let mut r = f; r.push(num); @@ -141,7 +141,7 @@ fn _factor(num: u64, f: Factors) -> Factors } pub fn factor(mut n: u64) -> Factors { - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::begin!("factorization"); let mut factors = Factors::one(); @@ -156,7 +156,7 @@ pub fn factor(mut n: u64) -> Factors { } if n == 1 { - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::end!("factorization"); return factors; } @@ -169,7 +169,7 @@ pub fn factor(mut n: u64) -> Factors { _factor::>(n, factors) }; - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::end!("factorization"); return r; diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 6291b92c1..94ad6df4c 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -33,7 +33,7 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { if x <= ceil { num = x; k += 1; - #[cfg(feature="coz")] + #[cfg(feature = "coz")] coz::progress!("factor found"); } else { if k > 0 { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index cd3a3a496..eac9490a5 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -16,15 +16,15 @@ fn test_helper(file_name: &str, args: &str) { } // FYI, the initialization size of our Line struct is 96 bytes. -// -// At very small buffer sizes, with that overhead we are certainly going -// to overrun our buffer way, way, way too quickly because of these excess +// +// At very small buffer sizes, with that overhead we are certainly going +// to overrun our buffer way, way, way too quickly because of these excess // bytes for the struct. // // For instance, seq 0..20000 > ...text = 108894 bytes // But overhead is 1920000 + 108894 = 2028894 bytes // -// Or kjvbible-random.txt = 4332506 bytes, but minimum size of its +// Or kjvbible-random.txt = 4332506 bytes, but minimum size of its // 99817 lines in memory * 96 bytes = 9582432 bytes // // Here, we test 108894 bytes with a 50K buffer @@ -59,7 +59,7 @@ fn test_human_numeric_whitespace() { test_helper("human-numeric-whitespace", "-h"); } -// This tests where serde often fails when reading back JSON +// This tests where serde often fails when reading back JSON // if it finds a null value #[test] fn test_extsort_as64_bailout() { diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index d524c096f..8f88f4c74 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -224,8 +224,14 @@ fn test_size_and_reference() { let mut file1 = at.make_file(TFILE1); let mut file2 = at.make_file(TFILE2); file1.write_all(b"1234567890").unwrap(); - ucmd.args(&["--reference", TFILE1, "--size", "+5", TFILE2]).succeeds(); + ucmd.args(&["--reference", TFILE1, "--size", "+5", TFILE2]) + .succeeds(); file2.seek(SeekFrom::End(0)).unwrap(); let actual = file2.seek(SeekFrom::Current(0)).unwrap(); - assert!(expected == actual, "expected '{}' got '{}'", expected, actual); + assert!( + expected == actual, + "expected '{}' got '{}'", + expected, + actual + ); } From e1cc434c24144fba951266d12508a3eafbfb26dd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 15:27:54 +0200 Subject: [PATCH 196/399] ignore the test_ls_styles --- tests/by-util/test_ls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index cfcbf9fd1..4258331e0 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -583,6 +583,7 @@ fn test_ls_order_birthtime() { } #[test] +#[ignore] fn test_ls_styles() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; From 308bdd7fc99fdf6aba21cb48f09c86bc3791f5a6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 15:55:58 +0200 Subject: [PATCH 197/399] ignore test_ls_order_birthtime too --- tests/by-util/test_ls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 4258331e0..2b8f311d1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -559,6 +559,7 @@ fn test_ls_long_ctime() { } #[test] +#[ignore] fn test_ls_order_birthtime() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; From 70ab0d01d294575f0f9b769ae53339e63c6b9a40 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 1 May 2021 08:48:18 +0200 Subject: [PATCH 198/399] kill: change default signal The default signal is SIGTERM, not SIGKILL. --- src/uu/kill/src/kill.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 916c13cc3..fe925ce37 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return kill( &matches .opt_str("signal") - .unwrap_or_else(|| obs_signal.unwrap_or_else(|| "9".to_owned())), + .unwrap_or_else(|| obs_signal.unwrap_or_else(|| "TERM".to_owned())), matches.free, ) } From 0ff10589984ca8a7c81bf496cbca4d5df8f1da93 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Mon, 26 Apr 2021 22:14:59 +0200 Subject: [PATCH 199/399] kill: add integration tests --- tests/by-util/test_kill.rs | 127 ++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 651491045..637aea9a2 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -1 +1,126 @@ -// ToDO: add tests +use crate::common::util::*; +use regex::Regex; +use std::os::unix::process::ExitStatusExt; +use std::process::{Child, Command}; + +// A child process the tests will try to kill. +struct Target { + child: Child, + killed: bool, +} + +impl Target { + // Creates a target that will naturally die after some time if not killed + // fast enough. + // This timeout avoids hanging failing tests. + fn new() -> Target { + Target { + child: Command::new("sleep") + .arg("30") + .spawn() + .expect("cannot spawn target"), + killed: false, + } + } + + // Waits for the target to complete and returns the signal it received if any. + fn wait_for_signal(&mut self) -> Option { + let sig = self.child.wait().expect("cannot wait on target").signal(); + self.killed = true; + sig + } + + fn pid(&self) -> u32 { + self.child.id() + } +} + +impl Drop for Target { + // Terminates this target to avoid littering test boxes with zombi processes + // when a test fails after creating a target but before killing it. + fn drop(&mut self) { + if !self.killed { + self.child.kill().expect("cannot kill target"); + } + } +} + +#[test] +fn test_kill_list_all_signals() { + // Check for a few signals. Do not try to be comprehensive. + new_ucmd!() + .arg("-l") + .succeeds() + .stdout_contains("KILL") + .stdout_contains("TERM") + .stdout_contains("HUP"); +} + +#[test] +fn test_kill_list_all_signals_as_table() { + // Check for a few signals. Do not try to be comprehensive. + new_ucmd!() + .arg("-t") + .succeeds() + .stdout_contains("KILL") + .stdout_contains("TERM") + .stdout_contains("HUP"); +} + +#[test] +fn test_kill_list_one_signal_from_name() { + // Use SIGKILL because it is 9 on all unixes. + new_ucmd!() + .arg("-l") + .arg("KILL") + .succeeds() + .stdout_matches(&Regex::new("\\b9\\b").unwrap()); +} + +#[test] +fn test_kill_set_bad_signal_name() { + new_ucmd!() + .arg("-s") + .arg("IAMNOTASIGNAL") + .fails() + .stderr_contains("unknown signal"); +} + +#[test] +fn test_kill_with_default_signal() { + let mut target = Target::new(); + new_ucmd!().arg(format!("{}", target.pid())).succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGTERM)); +} + +#[test] +fn test_kill_with_signal_number_old_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-9") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(9)); +} + +#[test] +fn test_kill_with_signal_number_new_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("9") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(9)); +} + +#[test] +fn test_kill_with_signal_name_new_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("KILL") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} From 01d178cf172bb205721be32c4e9d52e0e38b722d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 16:53:34 +0200 Subject: [PATCH 200/399] sort: don't rely on serde-json for extsort It is much faster to just write the lines to disk, separated by \n (or \0 if zero-terminated is enabled), instead of serializing to json. external_sort now knows of the Line struct instead of interacting with it using the ExternallySortable trait. Similarly, it now uses the crash_if_err! macro to handle errors, instead of bubbling them up. Some functions were changed from taking &[Line] as the input to taking an Iterator. This removes the need to collect to a Vec when not necessary. --- Cargo.lock | 8 - src/uu/sort/Cargo.toml | 4 +- src/uu/sort/src/external_sort/mod.rs | 356 ++++++++++----------------- src/uu/sort/src/numeric_str_cmp.rs | 7 +- src/uu/sort/src/sort.rs | 220 ++++++++--------- 5 files changed, 249 insertions(+), 346 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..9ffc56720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1372,9 +1372,6 @@ name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" -dependencies = [ - "serde_derive", -] [[package]] name = "serde_cbor" @@ -1453,9 +1450,6 @@ name = "smallvec" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" -dependencies = [ - "serde", -] [[package]] name = "strsim" @@ -2391,8 +2385,6 @@ dependencies = [ "rand 0.7.3", "rayon", "semver", - "serde", - "serde_json", "smallvec 1.6.1", "tempdir", "unicode-width", diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 80ffc92c9..3784ccbb0 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -15,15 +15,13 @@ edition = "2018" path = "src/sort.rs" [dependencies] -serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } -serde = { version = "1.0", features = ["derive"] } rayon = "1.5" rand = "0.7" clap = "2.33" fnv = "1.0.7" itertools = "0.10.0" semver = "0.9.0" -smallvec = { version="1.6.1", features=["serde"] } +smallvec = "1.6.1" unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index fd942d4a7..725b17bbd 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -1,50 +1,32 @@ -use std::clone::Clone; -use std::cmp::Ordering::Less; +use std::cmp::Ordering; use std::collections::VecDeque; -use std::error::Error; use std::fs::{File, OpenOptions}; -use std::io::SeekFrom::Start; +use std::io::SeekFrom; use std::io::{BufRead, BufReader, BufWriter, Seek, Write}; -use std::marker::PhantomData; -use std::path::PathBuf; +use std::path::Path; -use serde::de::DeserializeOwned; -use serde::Serialize; -use serde_json; use tempdir::TempDir; use super::{GlobalSettings, Line}; -/// Trait for types that can be used by -/// [ExternalSorter](struct.ExternalSorter.html). Must be sortable, cloneable, -/// serializeable, and able to report on it's size -pub trait ExternallySortable: Clone + Serialize + DeserializeOwned { - /// Get the size, in bytes, of this object (used to constrain the buffer - /// used in the external sort). - fn get_size(&self) -> u64; -} - /// Iterator that provides sorted `T`s -pub struct ExtSortedIterator { +pub struct ExtSortedIterator { buffers: Vec>, chunk_offsets: Vec, - max_per_chunk: u64, - chunks: u64, + max_per_chunk: usize, + chunks: usize, tmp_dir: TempDir, settings: GlobalSettings, failed: bool, } -impl Iterator for ExtSortedIterator -where - Line: ExternallySortable, -{ - type Item = Result>; +impl Iterator for ExtSortedIterator { + type Item = Line; /// # Errors /// /// This method can fail due to issues reading intermediate sorted chunks - /// from disk, or due to serde deserialization issues + /// from disk fn next(&mut self) -> Option { if self.failed { return None; @@ -53,29 +35,18 @@ where let mut empty = true; for chunk_num in 0..self.chunks { if self.buffers[chunk_num as usize].is_empty() { - let mut f = match File::open(self.tmp_dir.path().join(chunk_num.to_string())) { - Ok(f) => f, - Err(e) => { - self.failed = true; - return Some(Err(Box::new(e))); - } - }; - match f.seek(Start(self.chunk_offsets[chunk_num as usize])) { - Ok(_) => (), - Err(e) => { - self.failed = true; - return Some(Err(Box::new(e))); - } - } - let bytes_read = - match fill_buff(&mut self.buffers[chunk_num as usize], f, self.max_per_chunk) { - Ok(bytes_read) => bytes_read, - Err(e) => { - self.failed = true; - return Some(Err(e)); - } - }; - self.chunk_offsets[chunk_num as usize] += bytes_read; + let mut f = crash_if_err!( + 1, + File::open(self.tmp_dir.path().join(chunk_num.to_string())) + ); + crash_if_err!(1, f.seek(SeekFrom::Start(self.chunk_offsets[chunk_num]))); + let bytes_read = fill_buff( + &mut self.buffers[chunk_num as usize], + f, + self.max_per_chunk, + &self.settings, + ); + self.chunk_offsets[chunk_num as usize] += bytes_read as u64; if !self.buffers[chunk_num as usize].is_empty() { empty = false; } @@ -91,205 +62,150 @@ where // check is_empty() before unwrap()ing let mut idx = 0; for chunk_num in 0..self.chunks as usize { - if !self.buffers[chunk_num].is_empty() { - if self.buffers[idx].is_empty() - || (super::compare_by)( + if !self.buffers[chunk_num].is_empty() + && (self.buffers[idx].is_empty() + || super::compare_by( self.buffers[chunk_num].front().unwrap(), self.buffers[idx].front().unwrap(), &self.settings, - ) == Less - { - idx = chunk_num; - } + ) == Ordering::Less) + { + idx = chunk_num; } } // unwrap due to checks above let r = self.buffers[idx].pop_front().unwrap(); - Some(Ok(r)) + Some(r) } } -/// Perform an external sort on an unsorted stream of incoming data -pub struct ExternalSorter -where - Line: ExternallySortable, -{ - tmp_dir: Option, - buffer_bytes: u64, - phantom: PhantomData, - settings: GlobalSettings, -} +/// Sort (based on `compare`) the `T`s provided by `unsorted` and return an +/// iterator +/// +/// # Errors +/// +/// This method can fail due to issues writing intermediate sorted chunks +/// to disk. +pub fn ext_sort( + unsorted: impl Iterator, + settings: &GlobalSettings, +) -> ExtSortedIterator { + let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); -impl ExternalSorter -where - Line: ExternallySortable, -{ - /// Create a new `ExternalSorter` with a specified memory buffer and - /// temporary directory - pub fn new( - buffer_bytes: u64, - tmp_dir: Option, - settings: GlobalSettings, - ) -> ExternalSorter { - ExternalSorter { - buffer_bytes, - tmp_dir, - phantom: PhantomData, + let mut iter = ExtSortedIterator { + buffers: Vec::new(), + chunk_offsets: Vec::new(), + max_per_chunk: 0, + chunks: 0, + tmp_dir, + settings: settings.clone(), + failed: false, + }; + + let mut total_read = 0; + let mut chunk = Vec::new(); + + // make the initial chunks on disk + for seq in unsorted { + let seq_size = seq.estimate_size(); + total_read += seq_size; + + chunk.push(seq); + + if total_read >= settings.buffer_size { + super::sort_by(&mut chunk, &settings); + write_chunk( + settings, + &iter.tmp_dir.path().join(iter.chunks.to_string()), + &mut chunk, + ); + chunk.clear(); + total_read = 0; + iter.chunks += 1; + } + } + // write the last chunk + if !chunk.is_empty() { + super::sort_by(&mut chunk, &settings); + write_chunk( settings, - } + &iter.tmp_dir.path().join(iter.chunks.to_string()), + &mut chunk, + ); + iter.chunks += 1; } - /// Sort (based on `compare`) the `T`s provided by `unsorted` and return an - /// iterator - /// - /// # Errors - /// - /// This method can fail due to issues writing intermediate sorted chunks - /// to disk, or due to serde serialization issues - pub fn sort_by( - &self, - unsorted: I, - settings: GlobalSettings, - ) -> Result, Box> - where - I: Iterator, - { - let tmp_dir = match self.tmp_dir { - Some(ref p) => TempDir::new_in(p, "uutils_sort")?, - None => TempDir::new("uutils_sort")?, - }; - // creating the thing we need to return first due to the face that we need to - // borrow tmp_dir and move it out - let mut iter = ExtSortedIterator { - buffers: Vec::new(), - chunk_offsets: Vec::new(), - max_per_chunk: 0, - chunks: 0, - tmp_dir, - settings, - failed: false, - }; + // initialize buffers for each chunk + // + // Having a right sized buffer for each chunk for smallish values seems silly to me? + // + // We will have to have the entire iter in memory sometime right? + // Set minimum to the size of the writer buffer, ~8K - { - let mut total_read = 0; - let mut chunk = Vec::new(); - // Initial buffer is specified by user - let mut adjusted_buffer_size = self.buffer_bytes; - let (iter_size, _) = unsorted.size_hint(); - - // make the initial chunks on disk - for seq in unsorted { - let seq_size = seq.get_size(); - total_read += seq_size; - - // GNU minimum is 16 * (sizeof struct + 2), but GNU uses about - // 1/10 the memory that we do. And GNU even says in the code it may - // not work on small buffer sizes. - // - // The following seems to work pretty well, and has about the same max - // RSS as lower minimum values. - // - let minimum_buffer_size: u64 = iter_size as u64 * seq_size / 8; - - adjusted_buffer_size = - // Grow buffer size for a struct/Line larger than buffer - if adjusted_buffer_size < seq_size { - seq_size - } else if adjusted_buffer_size < minimum_buffer_size { - minimum_buffer_size - } else { - adjusted_buffer_size - }; - chunk.push(seq); - - if total_read >= adjusted_buffer_size { - super::sort_by(&mut chunk, &self.settings); - self.write_chunk( - &iter.tmp_dir.path().join(iter.chunks.to_string()), - &mut chunk, - )?; - chunk.clear(); - total_read = 0; - iter.chunks += 1; - } - } - // write the last chunk - if chunk.len() > 0 { - super::sort_by(&mut chunk, &self.settings); - self.write_chunk( - &iter.tmp_dir.path().join(iter.chunks.to_string()), - &mut chunk, - )?; - iter.chunks += 1; - } - - // initialize buffers for each chunk - // - // Having a right sized buffer for each chunk for smallish values seems silly to me? - // - // We will have to have the entire iter in memory sometime right? - // Set minimum to the size of the writer buffer, ~8K - // - const MINIMUM_READBACK_BUFFER: u64 = 8200; - let right_sized_buffer = adjusted_buffer_size - .checked_div(iter.chunks) - .unwrap_or(adjusted_buffer_size); - iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER { - right_sized_buffer - } else { - MINIMUM_READBACK_BUFFER - }; - iter.buffers = vec![VecDeque::new(); iter.chunks as usize]; - iter.chunk_offsets = vec![0 as u64; iter.chunks as usize]; - for chunk_num in 0..iter.chunks { - let offset = fill_buff( - &mut iter.buffers[chunk_num as usize], - File::open(iter.tmp_dir.path().join(chunk_num.to_string()))?, - iter.max_per_chunk, - )?; - iter.chunk_offsets[chunk_num as usize] = offset; - } - } - - Ok(iter) + const MINIMUM_READBACK_BUFFER: usize = 8200; + let right_sized_buffer = settings + .buffer_size + .checked_div(iter.chunks) + .unwrap_or(settings.buffer_size); + iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER { + right_sized_buffer + } else { + MINIMUM_READBACK_BUFFER + }; + iter.buffers = vec![VecDeque::new(); iter.chunks]; + iter.chunk_offsets = vec![0; iter.chunks]; + for chunk_num in 0..iter.chunks { + let offset = fill_buff( + &mut iter.buffers[chunk_num], + crash_if_err!( + 1, + File::open(iter.tmp_dir.path().join(chunk_num.to_string())) + ), + iter.max_per_chunk, + &settings, + ); + iter.chunk_offsets[chunk_num] = offset as u64; } - fn write_chunk(&self, file: &PathBuf, chunk: &mut Vec) -> Result<(), Box> { - let new_file = OpenOptions::new().create(true).append(true).open(file)?; - let mut buf_write = Box::new(BufWriter::new(new_file)) as Box; - for s in chunk { - let mut serialized = serde_json::to_string(&s).expect("JSON write error: "); - serialized.push_str("\n"); - buf_write.write(serialized.as_bytes())?; - } - buf_write.flush()?; - - Ok(()) - } + iter } -fn fill_buff( +fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec) { + let new_file = crash_if_err!(1, OpenOptions::new().create(true).append(true).open(file)); + let mut buf_write = BufWriter::new(new_file); + for s in chunk { + crash_if_err!(1, buf_write.write_all(s.line.as_bytes())); + crash_if_err!( + 1, + buf_write.write_all(if settings.zero_terminated { "\0" } else { "\n" }.as_bytes(),) + ); + } + crash_if_err!(1, buf_write.flush()); +} + +fn fill_buff( vec: &mut VecDeque, file: File, - max_bytes: u64, -) -> Result> -where - Line: ExternallySortable, -{ + max_bytes: usize, + settings: &GlobalSettings, +) -> usize { let mut total_read = 0; let mut bytes_read = 0; - for line in BufReader::new(file).lines() { - let line_s = line?; + for line in BufReader::new(file).split(if settings.zero_terminated { + b'\0' + } else { + b'\n' + }) { + let line_s = String::from_utf8(crash_if_err!(1, line)).unwrap(); bytes_read += line_s.len() + 1; - // This is where the bad stuff happens usually - let deserialized: Line = serde_json::from_str(&line_s).expect("JSON read error: "); - total_read += deserialized.get_size(); + let deserialized = Line::new(line_s, settings); + total_read += deserialized.estimate_size(); vec.push_back(deserialized); if total_read > max_bytes { break; } } - Ok(bytes_read as u64) + bytes_read } diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index b74d97867..f8666b701 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -14,21 +14,20 @@ //! More specifically, exponent can be understood so that the original number is in (1..10)*10^exponent. //! From that follows the constraints of this algorithm: It is able to compare numbers in ±(1*10^[i64::MIN]..10*10^[i64::MAX]). -use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, ops::Range}; -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] enum Sign { Negative, Positive, } -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct NumInfo { exponent: i64, sign: Sign, } -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct NumInfoParseSettings { pub accept_si_units: bool, pub thousands_separator: Option, diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 18d9304fa..7c053e4ad 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -19,7 +19,7 @@ mod external_sort; mod numeric_str_cmp; use clap::{App, Arg}; -use external_sort::{ExternalSorter, ExternallySortable}; +use external_sort::ext_sort; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -27,14 +27,13 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; -use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; use std::fs::File; use std::hash::{Hash, Hasher}; -use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write}; +use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::mem::replace; use std::ops::Range; use std::path::Path; @@ -104,7 +103,7 @@ enum SortMode { Default, } #[derive(Clone)] -struct GlobalSettings { +pub struct GlobalSettings { mode: SortMode, debug: bool, ignore_blanks: bool, @@ -204,7 +203,7 @@ impl From<&GlobalSettings> for KeySettings { } } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Clone)] /// Represents the string selected by a FieldSelector. enum SelectionRange { /// If we had to transform this selection, we have to store a new string. @@ -236,7 +235,7 @@ impl SelectionRange { } } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Clone)] enum NumCache { AsF64(GeneralF64ParseResult), WithInfo(NumInfo), @@ -257,7 +256,8 @@ impl NumCache { } } } -#[derive(Serialize, Deserialize, Clone)] + +#[derive(Clone)] struct Selection { range: SelectionRange, num_cache: NumCache, @@ -272,22 +272,19 @@ impl Selection { type Field = Range; -#[derive(Serialize, Deserialize, Clone)] -struct Line { +#[derive(Clone)] +pub struct Line { line: String, // The common case is not to specify fields. Let's make this fast. selections: SmallVec<[Selection; 1]>, } -impl ExternallySortable for Line { - fn get_size(&self) -> u64 { - // Currently 96 bytes, but that could change, so we get that size here - std::mem::size_of::() as u64 - } -} - impl Line { - fn new(line: String, settings: &GlobalSettings) -> Self { + pub fn estimate_size(&self) -> usize { + self.line.capacity() + self.selections.capacity() * std::mem::size_of::() + } + + pub fn new(line: String, settings: &GlobalSettings) -> Self { let fields = if settings .selectors .iter() @@ -299,7 +296,7 @@ impl Line { None }; - let selections = settings + let selections: SmallVec<[Selection; 1]> = settings .selectors .iter() .map(|selector| { @@ -725,7 +722,7 @@ impl FieldSelector { } struct MergeableFile<'a> { - lines: Lines>>, + lines: Box + 'a>, current_line: Line, settings: &'a GlobalSettings, } @@ -765,11 +762,11 @@ impl<'a> FileMerger<'a> { settings, } } - fn push_file(&mut self, mut lines: Lines>>) { - if let Some(Ok(next_line)) = lines.next() { + fn push_file(&mut self, mut lines: Box + 'a>) { + if let Some(next_line) = lines.next() { let mergeable_file = MergeableFile { lines, - current_line: Line::new(next_line, &self.settings), + current_line: next_line, settings: &self.settings, }; self.heap.push(mergeable_file); @@ -783,11 +780,8 @@ impl<'a> Iterator for FileMerger<'a> { match self.heap.pop() { Some(mut current) => { match current.lines.next() { - Some(Ok(next_line)) => { - let ret = replace( - &mut current.current_line, - Line::new(next_line, &self.settings), - ); + Some(next_line) => { + let ret = replace(&mut current.current_line, next_line); self.heap.push(current); Some(ret) } @@ -1155,90 +1149,108 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exec(files, settings) } -fn exec(files: Vec, settings: GlobalSettings) -> i32 { - let mut lines = Vec::new(); - let mut file_merger = FileMerger::new(&settings); +fn file_to_lines_iter<'a>( + file: &str, + settings: &'a GlobalSettings, +) -> Option + 'a> { + let (reader, _) = match open(file) { + Some(x) => x, + None => return None, + }; - for path in &files { - let (reader, _) = match open(path) { - Some(x) => x, - None => continue, - }; + let buf_reader = BufReader::new(reader); - let buf_reader = BufReader::new(reader); - - if settings.merge { - file_merger.push_file(buf_reader.lines()); - } else if settings.zero_terminated { - for line in buf_reader.split(b'\0').flatten() { - lines.push(Line::new( - std::str::from_utf8(&line) - .expect("Could not parse string from zero terminated input.") + Some( + buf_reader + .split(if settings.zero_terminated { + b'\0' + } else { + b'\n' + }) + .map(move |line| { + Line::new( + std::str::from_utf8(&line.unwrap()) + .expect("input is not valid utf-8") .to_string(), - &settings, - )); - } + settings, + ) + }), + ) +} + +fn output_sorted_lines(iter: impl Iterator, settings: &GlobalSettings) { + if settings.unique { + print_sorted( + iter.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), + &settings, + ); + } else { + print_sorted(iter, &settings); + } +} + +fn exec(files: Vec, settings: GlobalSettings) -> i32 { + if settings.merge { + let mut file_merger = FileMerger::new(&settings); + for lines in files + .iter() + .filter_map(|file| file_to_lines_iter(file, &settings)) + { + file_merger.push_file(Box::new(lines)); + } + output_sorted_lines(file_merger, &settings); + } else { + let lines = files + .iter() + .filter_map(|file| file_to_lines_iter(file, &settings)) + .flatten(); + + if settings.check { + return exec_check_file(lines, &settings); + } + + // Only use ext_sorter when we need to. + // Probably faster that we don't create + // an owned value each run + if settings.ext_sort { + let sorted_lines = ext_sort(lines, &settings); + output_sorted_lines(sorted_lines, &settings); } else { - for line in buf_reader.lines() { - if let Ok(n) = line { - lines.push(Line::new(n, &settings)); + let mut lines = vec![]; + + // This is duplicated from fn file_to_lines_iter, but using that function directly results in a performance regression. + for (file, _) in files.iter().map(|file| open(file)).flatten() { + let buf_reader = BufReader::new(file); + for line in buf_reader.split(if settings.zero_terminated { + b'\0' } else { - break; + b'\n' + }) { + let string = String::from_utf8(line.unwrap()).unwrap(); + lines.push(Line::new(string, &settings)); } } + + sort_by(&mut lines, &settings); + output_sorted_lines(lines.into_iter(), &settings); } } - if settings.check { - return exec_check_file(&lines, &settings); - } - - // Only use ext_sorter when we need to. - // Probably faster that we don't create - // an owned value each run - if settings.ext_sort { - lines = ext_sort_by(lines, settings.clone()); - } else { - sort_by(&mut lines, &settings); - } - - if settings.merge { - if settings.unique { - print_sorted( - file_merger.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), - &settings, - ) - } else { - print_sorted(file_merger, &settings) - } - } else if settings.unique { - print_sorted( - lines - .into_iter() - .dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), - &settings, - ) - } else { - print_sorted(lines.into_iter(), &settings) - } - 0 } -fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { +fn exec_check_file(unwrapped_lines: impl Iterator, settings: &GlobalSettings) -> i32 { // errors yields the line before each disorder, // plus the last line (quirk of .coalesce()) - let mut errors = - unwrapped_lines - .iter() - .enumerate() - .coalesce(|(last_i, last_line), (i, line)| { - if compare_by(&last_line, &line, &settings) == Ordering::Greater { - Err(((last_i, last_line), (i, line))) - } else { - Ok((i, line)) - } - }); + let mut errors = unwrapped_lines + .enumerate() + .coalesce(|(last_i, last_line), (i, line)| { + if compare_by(&last_line, &line, &settings) == Ordering::Greater { + Err(((last_i, last_line), (i, line))) + } else { + Ok((i, line)) + } + }); if let Some((first_error_index, _line)) = errors.next() { // Check for a second "error", as .coalesce() always returns the last // line, no matter what our merging function does. @@ -1257,20 +1269,6 @@ fn exec_check_file(unwrapped_lines: &[Line], settings: &GlobalSettings) -> i32 { } } -fn ext_sort_by(unsorted: Vec, settings: GlobalSettings) -> Vec { - let external_sorter = ExternalSorter::new( - settings.buffer_size as u64, - Some(settings.tmp_dir.clone()), - settings.clone(), - ); - let iter = external_sorter - .sort_by(unsorted.into_iter(), settings.clone()) - .unwrap() - .map(|x| x.unwrap()) - .collect::>(); - iter -} - fn sort_by(unsorted: &mut Vec, settings: &GlobalSettings) { if settings.stable || settings.unique { unsorted.par_sort_by(|a, b| compare_by(a, b, &settings)) @@ -1375,7 +1373,7 @@ fn get_leading_gen(input: &str) -> Range { leading_whitespace_len..input.len() } -#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, PartialOrd)] +#[derive(Copy, Clone, PartialEq, PartialOrd)] enum GeneralF64ParseResult { Invalid, NaN, From 11f387dc93f4d0f218870cc63ad1f1d5d4a80532 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 1 May 2021 17:29:00 +0200 Subject: [PATCH 201/399] ls: fix style test --- tests/by-util/test_ls.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 2b8f311d1..79e26f200 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -584,7 +584,6 @@ fn test_ls_order_birthtime() { } #[test] -#[ignore] fn test_ls_styles() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -598,7 +597,7 @@ fn test_ls_styles() { Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2} test\n").unwrap(); let re_iso = Regex::new(r"[a-z-]* \d* \w* \w* \d* \d{2}-\d{2} \d{2}:\d{2} test\n").unwrap(); let re_locale = - Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} \d{2} \d{2}:\d{2} test\n").unwrap(); + Regex::new(r"[a-z-]* \d* \w* \w* \d* [A-Z][a-z]{2} ( |\d)\d \d{2}:\d{2} test\n").unwrap(); //full-iso let result = scene From 5567f32f5811a7b48a83f4e57ba284a35b2d199a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 1 May 2021 17:49:45 +0200 Subject: [PATCH 202/399] refresh cargo.lock with recent updates --- Cargo.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33d599762..c988d6d12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ "lazy_static", "memchr 2.4.0", @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a068b905b8cb93815aa3069ae48653d90f382308aebd1d33d940ac1f1d771e2d" +checksum = "1efb2352a0f4d4b128f734b5c44c79ff80117351138733f12f982fe3e2b13343" dependencies = [ "aho-corasick", "memchr 2.4.0", @@ -2247,6 +2247,7 @@ dependencies = [ name = "uu_pinky" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] From 117e84eed3adabb54fa852030712848f08c319f1 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 1 May 2021 18:46:13 +0200 Subject: [PATCH 203/399] tr: implement complement separately from delete or squeeze (#2147) --- src/uu/tr/src/tr.rs | 34 ++++++++++++++++++++++++---------- tests/by-util/test_tr.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6c1c0746a..f8fabd69f 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -125,10 +125,17 @@ impl SymbolTranslator for DeleteAndSqueezeOperation { struct TranslateOperation { translate_map: FnvHashMap, + complement: bool, + s2_last: char, } impl TranslateOperation { - fn new(set1: ExpandSet, set2: &mut ExpandSet, truncate: bool) -> TranslateOperation { + fn new( + set1: ExpandSet, + set2: &mut ExpandSet, + truncate: bool, + complement: bool, + ) -> TranslateOperation { let mut map = FnvHashMap::default(); let mut s2_prev = '_'; for i in set1 { @@ -141,13 +148,25 @@ impl TranslateOperation { map.insert(i as usize, s2_prev); } } - TranslateOperation { translate_map: map } + TranslateOperation { + translate_map: map, + complement, + s2_last: s2_prev, + } } } impl SymbolTranslator for TranslateOperation { fn translate(&self, c: char, _prev_c: char) -> Option { - Some(*self.translate_map.get(&(c as usize)).unwrap_or(&c)) + if self.complement { + Some(if self.translate_map.contains_key(&(c as usize)) { + c + } else { + self.s2_last + }) + } else { + Some(*self.translate_map.get(&(c as usize)).unwrap_or(&c)) + } } } @@ -256,11 +275,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - if complement_flag && !delete_flag && !squeeze_flag { - show_error!("-c is only supported with -d or -s"); - return 1; - } - let stdin = stdin(); let mut locked_stdin = stdin.lock(); let stdout = stdout(); @@ -282,8 +296,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { translate_input(&mut locked_stdin, &mut buffered_stdout, op); } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); - let op = TranslateOperation::new(set1, &mut set2, truncate_flag); - translate_input(&mut locked_stdin, &mut buffered_stdout, op) + let op = TranslateOperation::new(set1, &mut set2, truncate_flag, complement_flag); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); } 0 diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 630c305c6..637c9b109 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -45,6 +45,33 @@ fn test_delete_complement() { .stdout_is("ac"); } +#[test] +fn test_complement1() { + new_ucmd!() + .args(&["-c", "a", "X"]) + .pipe_in("ab") + .run() + .stdout_is("aX"); +} + +#[test] +fn test_complement2() { + new_ucmd!() + .args(&["-c", "0-9", "x"]) + .pipe_in("Phone: 01234 567890") + .run() + .stdout_is("xxxxxxx01234x567890"); +} + +#[test] +fn test_complement3() { + new_ucmd!() + .args(&["-c", "abcdefgh", "123"]) + .pipe_in("the cat and the bat") + .run() + .stdout_is("3he3ca33a3d33he3ba3"); +} + #[test] fn test_squeeze() { new_ucmd!() From 5674d093275ea9ddc3be9ffbb3627eb5d6cae3a1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 1 May 2021 13:01:55 -0400 Subject: [PATCH 204/399] fixup! tr: implement translate and squeeze (-s) mode --- src/uu/tr/src/tr.rs | 89 +++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 09c4304a5..e04737a45 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -151,6 +151,35 @@ impl SymbolTranslator for TranslateOperation { } } +struct TranslateAndSqueezeOperation { + translate: TranslateOperation, + squeeze: SqueezeOperation, +} + +impl TranslateAndSqueezeOperation { + fn new( + set1: ExpandSet, + set2: &mut ExpandSet, + set2_: ExpandSet, + truncate: bool, + complement: bool, + ) -> TranslateAndSqueezeOperation { + TranslateAndSqueezeOperation { + translate: TranslateOperation::new(set1, set2, truncate), + squeeze: SqueezeOperation::new(set2_, complement), + } + } +} + +impl SymbolTranslator for TranslateAndSqueezeOperation { + fn translate(&self, c: char, prev_c: char) -> Option { + // `unwrap()` will never panic because `Translate.translate()` + // always returns `Some`. + self.squeeze + .translate(self.translate.translate(c, 0 as char).unwrap(), prev_c) + } +} + fn translate_input( input: &mut dyn BufRead, output: &mut dyn Write, @@ -168,8 +197,11 @@ fn translate_input( // isolation to make borrow checker happy let filtered = buf.chars().filter_map(|c| { let res = translator.translate(c, prev_c); + // Set `prev_c` to the post-translate character. This + // allows the squeeze operation to correctly function + // after the translate operation. if res.is_some() { - prev_c = c; + prev_c = res.unwrap(); } res }); @@ -282,53 +314,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let op = SqueezeOperation::new(set1, complement_flag); translate_input(&mut locked_stdin, &mut buffered_stdout, op); } else { - // Define a closure that computes the translation using a hash map. - // - // The `unwrap()` should never panic because the - // `TranslateOperation.translate()` method always returns - // `Some`. let mut set2 = ExpandSet::new(sets[1].as_ref()); - let translator = TranslateOperation::new(set1, &mut set2, truncate_flag); - let translate = |c| translator.translate(c, 0 as char).unwrap(); - - // Prepare some variables to be used for the closure that - // computes the squeeze operation. - // - // The `squeeze()` closure needs to be defined anew for - // each line of input, but these variables do not change - // while reading the input so they can be defined before - // the `while` loop. - let set2 = ExpandSet::new(sets[1].as_ref()); - let squeezer = SqueezeOperation::new(set2, complement_flag); - - // Prepare some memory to read each line of the input (`buf`) and to write - let mut buf = String::with_capacity(BUFFER_LEN + 4); - - // Loop over each line of stdin. - while let Ok(length) = locked_stdin.read_line(&mut buf) { - if length == 0 { - break; - } - - // Define a closure that computes the squeeze operation. - // - // We keep track of the previously seen character on - // each call to `squeeze()`, but we need to reset the - // `prev_c` variable at the beginning of each line of - // the input. That's why we define the closure inside - // the `while` loop. - let mut prev_c = 0 as char; - let squeeze = |c| { - let result = squeezer.translate(c, prev_c); - prev_c = c; - result - }; - - // First translate, then squeeze each character of the input line. - let filtered: String = buf.chars().map(translate).filter_map(squeeze).collect(); - buf.clear(); - buffered_stdout.write_all(filtered.as_bytes()).unwrap(); - } + let set2_ = ExpandSet::new(sets[1].as_ref()); + let op = TranslateAndSqueezeOperation::new( + set1, + &mut set2, + set2_, + complement_flag, + truncate_flag, + ); + translate_input(&mut locked_stdin, &mut buffered_stdout, op); } } else { let mut set2 = ExpandSet::new(sets[1].as_ref()); From 05b20c32a996e56c9ced87e520e7a23cd19358af Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Wed, 28 Apr 2021 00:36:27 -0700 Subject: [PATCH 205/399] base64: Moved argument parsing to clap. Moved argument parsing to clap and added tests to cover using "-" as stdin, passing in too many file arguments, and updated the "wrap" error message in the tests. --- Cargo.lock | 1 + src/uu/base64/Cargo.toml | 1 + src/uu/base64/src/base64.rs | 139 +++++++++++++++++++++++-- src/uu/base64/src/base_common.rs | 61 +---------- tests/by-util/test_base64.rs | 40 ++++++- tests/fixtures/base64/input-simple.txt | 1 + 6 files changed, 171 insertions(+), 72 deletions(-) create mode 100644 tests/fixtures/base64/input-simple.txt diff --git a/Cargo.lock b/Cargo.lock index c988d6d12..254ae8fda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1688,6 +1688,7 @@ dependencies = [ name = "uu_base64" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index 841ab140c..97241a571 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/base64.rs" [dependencies] +clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 61a8dc5cb..62d36cc5f 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -8,12 +8,19 @@ #[macro_use] extern crate uucore; + use uucore::encoding::Format; use uucore::InvalidEncodingHandling; +use std::fs::File; +use std::io::{stdin, BufReader}; +use std::path::Path; + +use clap::{App, Arg}; + mod base_common; -static SYNTAX: &str = "[OPTION]... [FILE]"; +static BASE64_ARG_ERROR: i32 = 1; static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output."; static LONG_HELP: &str = " With no FILE, or when FILE is -, read standard input. @@ -24,14 +31,128 @@ static LONG_HELP: &str = " to attempt to recover from any other non-alphabet bytes in the encoded stream. "; +static VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn get_usage() -> String { + format!("{0} [OPTION]... [FILE]", executable!()) +} + +pub mod options { + pub static DECODE: &str = "decode"; + pub static WRAP: &str = "wrap"; + pub static IGNORE_GARBAGE: &str = "ignore-garbage"; + pub static FILE: &str = "file"; +} + +struct Config { + decode: bool, + ignore_garbage: bool, + wrap_cols: Option, + to_read: Option, +} + +impl Config { + fn from(options: clap::ArgMatches) -> Config { + let file: Option = match options.values_of(options::FILE) { + Some(mut values) => { + let name = values.next().unwrap(); + if values.len() != 0 { + crash!(BASE64_ARG_ERROR, "extra operand ‘{}’", name); + } + + if name == "-" { + None + } else { + if !Path::exists(Path::new(name)) { + crash!(BASE64_ARG_ERROR, "{}: No such file or directory", name); + } + Some(name.to_owned()) + } + } + None => None, + }; + + let cols = match options.value_of(options::WRAP) { + Some(num) => match num.parse::() { + Ok(n) => Some(n), + Err(e) => { + crash!(BASE64_ARG_ERROR, "invalid wrap size: ‘{}’: {}", num, e); + } + }, + None => None, + }; + + Config { + decode: options.is_present(options::DECODE), + ignore_garbage: options.is_present(options::IGNORE_GARBAGE), + wrap_cols: cols, + to_read: file, + } + } +} pub fn uumain(args: impl uucore::Args) -> i32 { - base_common::execute( - args.collect_str(InvalidEncodingHandling::Ignore) - .accept_any(), - SYNTAX, - SUMMARY, - LONG_HELP, - Format::Base64, - ) + let format = Format::Base64; + let usage = get_usage(); + let app = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .usage(&usage[..]) + .about(LONG_HELP) + // Format arguments. + .arg( + Arg::with_name(options::DECODE) + .short("d") + .long(options::DECODE) + .help("decode data"), + ) + .arg( + Arg::with_name(options::IGNORE_GARBAGE) + .short("i") + .long(options::IGNORE_GARBAGE) + .help("when decoding, ignore non-alphabetic characters"), + ) + .arg( + Arg::with_name(options::WRAP) + .short("w") + .long(options::WRAP) + .takes_value(true) + .help( + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + ), + ) + // "multiple" arguments are used to check whether there is more than one + // file passed in. + .arg(Arg::with_name(options::FILE).index(1).multiple(true)); + + let arg_list = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); + let config: Config = Config::from(app.get_matches_from(arg_list)); + match config.to_read { + // Read from file. + Some(name) => { + let file_buf = safe_unwrap!(File::open(Path::new(&name))); + let mut input = BufReader::new(file_buf); + base_common::handle_input( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + // stdin + None => { + base_common::handle_input( + &mut stdin().lock(), + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + ); + } + }; + + 0 } diff --git a/src/uu/base64/src/base_common.rs b/src/uu/base64/src/base_common.rs index 3f1436fb2..a4b49e499 100644 --- a/src/uu/base64/src/base_common.rs +++ b/src/uu/base64/src/base_common.rs @@ -7,68 +7,11 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -use std::fs::File; -use std::io::{stdin, stdout, BufReader, Read, Write}; -use std::path::Path; +use std::io::{stdout, Read, Write}; use uucore::encoding::{wrap_print, Data, Format}; -pub fn execute( - args: Vec, - syntax: &str, - summary: &str, - long_help: &str, - format: Format, -) -> i32 { - let matches = app!(syntax, summary, long_help) - .optflag("d", "decode", "decode data") - .optflag( - "i", - "ignore-garbage", - "when decoding, ignore non-alphabetic characters", - ) - .optopt( - "w", - "wrap", - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - "COLS", - ) - .parse(args); - - let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() { - Ok(n) => n, - Err(e) => { - crash!(1, "invalid wrap size: ‘{}’: {}", s, e); - } - }); - let ignore_garbage = matches.opt_present("ignore-garbage"); - let decode = matches.opt_present("decode"); - - if matches.free.len() > 1 { - show_usage_error!("extra operand ‘{}’", matches.free[0]); - return 1; - } - - if matches.free.is_empty() || &matches.free[0][..] == "-" { - let stdin_raw = stdin(); - handle_input( - &mut stdin_raw.lock(), - format, - line_wrap, - ignore_garbage, - decode, - ); - } else { - let path = Path::new(matches.free[0].as_str()); - let file_buf = safe_unwrap!(File::open(&path)); - let mut input = BufReader::new(file_buf); - handle_input(&mut input, format, line_wrap, ignore_garbage, decode); - }; - - 0 -} - -fn handle_input( +pub fn handle_input( input: &mut R, format: Format, line_wrap: Option, diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 6bc0436c5..c9adc5c0f 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -7,6 +7,21 @@ fn test_encode() { .pipe_in(input) .succeeds() .stdout_only("aGVsbG8sIHdvcmxkIQ==\n"); + + // Using '-' as our file + new_ucmd!() + .arg("-") + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxkIQ==\n"); +} + +#[test] +fn test_base64_encode_file() { + new_ucmd!() + .arg("input-simple.txt") + .succeeds() + .stdout_only("SGVsbG8sIFdvcmxkIQo=\n"); } #[test] @@ -60,10 +75,9 @@ fn test_wrap() { #[test] fn test_wrap_no_arg() { for wrap_param in vec!["-w", "--wrap"] { - new_ucmd!().arg(wrap_param).fails().stderr_only(format!( - "base64: error: Argument to option '{}' missing\n", - if wrap_param == "-w" { "w" } else { "wrap" } - )); + new_ucmd!().arg(wrap_param).fails().stderr_contains( + &"The argument '--wrap ' requires a value but none was supplied", + ); } } @@ -77,3 +91,21 @@ fn test_wrap_bad_arg() { .stderr_only("base64: error: invalid wrap size: ‘b’: invalid digit found in string\n"); } } + +#[test] +fn test_base64_extra_operand() { + // Expect a failure when multiple files are specified. + new_ucmd!() + .arg("a.txt") + .arg("a.txt") + .fails() + .stderr_only("base64: error: extra operand ‘a.txt’"); +} + +#[test] +fn test_base64_file_not_found() { + new_ucmd!() + .arg("a.txt") + .fails() + .stderr_only("base64: error: a.txt: No such file or directory"); +} diff --git a/tests/fixtures/base64/input-simple.txt b/tests/fixtures/base64/input-simple.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/tests/fixtures/base64/input-simple.txt @@ -0,0 +1 @@ +Hello, World! From f307de22d0636247c7cfbb4b6db88ea29971a7d6 Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Thu, 29 Apr 2021 01:59:43 -0700 Subject: [PATCH 206/399] base64: Refactor argument parsing Moved most of the argument parsing logic to `base32/base_common.rs` to allow for significant code reuse. --- Cargo.lock | 1 + src/uu/base32/src/base32.rs | 152 +++++++------------------------ src/uu/base32/src/base_common.rs | 125 ++++++++++++++++++++++++- src/uu/base64/Cargo.toml | 1 + src/uu/base64/src/base64.rs | 151 ++++++------------------------ src/uu/base64/src/base_common.rs | 40 -------- tests/by-util/test_base32.rs | 2 +- tests/by-util/test_base64.rs | 2 +- 8 files changed, 187 insertions(+), 287 deletions(-) delete mode 100644 src/uu/base64/src/base_common.rs diff --git a/Cargo.lock b/Cargo.lock index 254ae8fda..54ddbd88e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1689,6 +1689,7 @@ name = "uu_base64" version = "0.0.6" dependencies = [ "clap", + "uu_base32", "uucore", "uucore_procs", ] diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 6d9f7f08f..f0e187c31 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -7,19 +7,14 @@ #[macro_use] extern crate uucore; + +use std::io::{stdin, Read}; + use uucore::encoding::Format; -use uucore::InvalidEncodingHandling; -use std::fs::File; -use std::io::{stdin, BufReader}; -use std::path::Path; +pub mod base_common; -use clap::{App, Arg}; - -mod base_common; - -static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output."; -static LONG_HELP: &str = " +static ABOUT: &str = " With no FILE, or when FILE is -, read standard input. The data are encoded as described for the base32 alphabet in RFC @@ -30,126 +25,41 @@ static LONG_HELP: &str = " "; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static BASE_CMD_PARSE_ERROR: i32 = 1; + fn get_usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } -pub mod options { - pub static DECODE: &str = "decode"; - pub static WRAP: &str = "wrap"; - pub static IGNORE_GARBAGE: &str = "ignore-garbage"; - pub static FILE: &str = "file"; -} - -struct Config { - decode: bool, - ignore_garbage: bool, - wrap_cols: Option, - to_read: Option, -} - -impl Config { - fn from(options: clap::ArgMatches) -> Config { - let file: Option = match options.values_of(options::FILE) { - Some(mut values) => { - let name = values.next().unwrap(); - if values.len() != 0 { - crash!(3, "extra operand ‘{}’", name); - } - - if name == "-" { - None - } else { - if !Path::exists(Path::new(name)) { - crash!(2, "{}: No such file or directory", name); - } - Some(name.to_owned()) - } - } - None => None, - }; - - let cols = match options.value_of(options::WRAP) { - Some(num) => match num.parse::() { - Ok(n) => Some(n), - Err(e) => { - crash!(1, "invalid wrap size: ‘{}’: {}", num, e); - } - }, - None => None, - }; - - Config { - decode: options.is_present(options::DECODE), - ignore_garbage: options.is_present(options::IGNORE_GARBAGE), - wrap_cols: cols, - to_read: file, - } - } -} - pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base32; let usage = get_usage(); - let app = App::new(executable!()) - .version(VERSION) - .about(SUMMARY) - .usage(&usage[..]) - .about(LONG_HELP) - // Format arguments. - .arg( - Arg::with_name(options::DECODE) - .short("d") - .long(options::DECODE) - .help("decode data"), - ) - .arg( - Arg::with_name(options::IGNORE_GARBAGE) - .short("i") - .long(options::IGNORE_GARBAGE) - .help("when decoding, ignore non-alphabetic characters"), - ) - .arg( - Arg::with_name(options::WRAP) - .short("w") - .long(options::WRAP) - .takes_value(true) - .help( - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - ), - ) - // "multiple" arguments are used to check whether there is more than one - // file passed in. - .arg(Arg::with_name(options::FILE).index(1).multiple(true)); + let name = executable!(); - let arg_list = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); - let config: Config = Config::from(app.get_matches_from(arg_list)); - match config.to_read { - // Read from file. - Some(name) => { - let file_buf = safe_unwrap!(File::open(Path::new(&name))); - let mut input = BufReader::new(file_buf); - base_common::handle_input( - &mut input, - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); + let config_result: Result = + base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); + + if config_result.is_err() { + match config_result { + Ok(_) => panic!(), + Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s), } - // stdin - None => { - base_common::handle_input( - &mut stdin().lock(), - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); - } - }; + } + + // Create a reference to stdin so we can return a locked stdin from + // parse_base_cmd_args + let stdin_raw = stdin(); + let config = config_result.unwrap(); + let mut input: Box = base_common::get_input(&config, &stdin_raw); + + base_common::handle_input( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + name, + ); 0 } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index a4b49e499..d0f47f500 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -10,6 +10,122 @@ use std::io::{stdout, Read, Write}; use uucore::encoding::{wrap_print, Data, Format}; +use uucore::InvalidEncodingHandling; + +use std::fs::File; +use std::io::{BufReader, Stdin}; +use std::path::Path; + +use clap::{App, Arg}; + +// Config. +pub struct Config { + pub decode: bool, + pub ignore_garbage: bool, + pub wrap_cols: Option, + pub to_read: Option, +} + +pub mod options { + pub static DECODE: &str = "decode"; + pub static WRAP: &str = "wrap"; + pub static IGNORE_GARBAGE: &str = "ignore-garbage"; + pub static FILE: &str = "file"; +} + +impl Config { + fn from(options: clap::ArgMatches) -> Result { + let file: Option = match options.values_of(options::FILE) { + Some(mut values) => { + let name = values.next().unwrap(); + if values.len() != 0 { + return Err(format!("extra operand ‘{}’", name)); + } + + if name == "-" { + None + } else { + if !Path::exists(Path::new(name)) { + return Err(format!("{}: No such file or directory", name)); + } + Some(name.to_owned()) + } + } + None => None, + }; + + let cols = match options.value_of(options::WRAP) { + Some(num) => match num.parse::() { + Ok(n) => Some(n), + Err(e) => { + return Err(format!("Invalid wrap size: ‘{}’: {}", num, e)); + } + }, + None => None, + }; + + Ok(Config { + decode: options.is_present(options::DECODE), + ignore_garbage: options.is_present(options::IGNORE_GARBAGE), + wrap_cols: cols, + to_read: file, + }) + } +} + +pub fn parse_base_cmd_args( + args: impl uucore::Args, + name: &str, + version: &str, + about: &str, + usage: &str, +) -> Result { + let app = App::new(name) + .version(version) + .about(about) + .usage(&usage[..]) + // Format arguments. + .arg( + Arg::with_name(options::DECODE) + .short("d") + .long(options::DECODE) + .help("decode data"), + ) + .arg( + Arg::with_name(options::IGNORE_GARBAGE) + .short("i") + .long(options::IGNORE_GARBAGE) + .help("when decoding, ignore non-alphabetic characters"), + ) + .arg( + Arg::with_name(options::WRAP) + .short("w") + .long(options::WRAP) + .takes_value(true) + .help( + "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", + ), + ) + // "multiple" arguments are used to check whether there is more than one + // file passed in. + .arg(Arg::with_name(options::FILE).index(1).multiple(true)); + let arg_list = args + .collect_str(InvalidEncodingHandling::ConvertLossy) + .accept_any(); + Config::from(app.get_matches_from(arg_list)) +} + +pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box { + match &config.to_read { + Some(name) => { + let file_buf = safe_unwrap!(File::open(Path::new(name))); + Box::new(BufReader::new(file_buf)) // as Box + } + None => { + Box::new(stdin_ref.lock()) // as Box + } + } +} pub fn handle_input( input: &mut R, @@ -17,6 +133,7 @@ pub fn handle_input( line_wrap: Option, ignore_garbage: bool, decode: bool, + name: &str, ) { let mut data = Data::new(input, format).ignore_garbage(ignore_garbage); if let Some(wrap) = line_wrap { @@ -31,10 +148,14 @@ pub fn handle_input( Ok(s) => { if stdout().write_all(&s).is_err() { // on windows console, writing invalid utf8 returns an error - crash!(1, "Cannot write non-utf8 data"); + eprintln!("{}: error: Cannot write non-utf8 data", name); + exit!(1) } } - Err(_) => crash!(1, "invalid input"), + Err(_) => { + eprintln!("{}: error: invalid input", name); + exit!(1) + } } } } diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index 97241a571..d4ee69f03 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -18,6 +18,7 @@ path = "src/base64.rs" clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"} [[bin]] name = "base64" diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 62d36cc5f..810df4fe8 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -9,20 +9,13 @@ #[macro_use] extern crate uucore; +use uu_base32::base_common; + use uucore::encoding::Format; -use uucore::InvalidEncodingHandling; -use std::fs::File; -use std::io::{stdin, BufReader}; -use std::path::Path; +use std::io::{stdin, Read}; -use clap::{App, Arg}; - -mod base_common; - -static BASE64_ARG_ERROR: i32 = 1; -static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output."; -static LONG_HELP: &str = " +static ABOUT: &str = " With no FILE, or when FILE is -, read standard input. The data are encoded as described for the base64 alphabet in RFC @@ -33,126 +26,40 @@ static LONG_HELP: &str = " "; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static BASE_CMD_PARSE_ERROR: i32 = 1; + fn get_usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } -pub mod options { - pub static DECODE: &str = "decode"; - pub static WRAP: &str = "wrap"; - pub static IGNORE_GARBAGE: &str = "ignore-garbage"; - pub static FILE: &str = "file"; -} - -struct Config { - decode: bool, - ignore_garbage: bool, - wrap_cols: Option, - to_read: Option, -} - -impl Config { - fn from(options: clap::ArgMatches) -> Config { - let file: Option = match options.values_of(options::FILE) { - Some(mut values) => { - let name = values.next().unwrap(); - if values.len() != 0 { - crash!(BASE64_ARG_ERROR, "extra operand ‘{}’", name); - } - - if name == "-" { - None - } else { - if !Path::exists(Path::new(name)) { - crash!(BASE64_ARG_ERROR, "{}: No such file or directory", name); - } - Some(name.to_owned()) - } - } - None => None, - }; - - let cols = match options.value_of(options::WRAP) { - Some(num) => match num.parse::() { - Ok(n) => Some(n), - Err(e) => { - crash!(BASE64_ARG_ERROR, "invalid wrap size: ‘{}’: {}", num, e); - } - }, - None => None, - }; - - Config { - decode: options.is_present(options::DECODE), - ignore_garbage: options.is_present(options::IGNORE_GARBAGE), - wrap_cols: cols, - to_read: file, - } - } -} - pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base64; let usage = get_usage(); - let app = App::new(executable!()) - .version(VERSION) - .about(SUMMARY) - .usage(&usage[..]) - .about(LONG_HELP) - // Format arguments. - .arg( - Arg::with_name(options::DECODE) - .short("d") - .long(options::DECODE) - .help("decode data"), - ) - .arg( - Arg::with_name(options::IGNORE_GARBAGE) - .short("i") - .long(options::IGNORE_GARBAGE) - .help("when decoding, ignore non-alphabetic characters"), - ) - .arg( - Arg::with_name(options::WRAP) - .short("w") - .long(options::WRAP) - .takes_value(true) - .help( - "wrap encoded lines after COLS character (default 76, 0 to disable wrapping)", - ), - ) - // "multiple" arguments are used to check whether there is more than one - // file passed in. - .arg(Arg::with_name(options::FILE).index(1).multiple(true)); + let name = executable!(); + let config_result: Result = + base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); - let arg_list = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); - let config: Config = Config::from(app.get_matches_from(arg_list)); - match config.to_read { - // Read from file. - Some(name) => { - let file_buf = safe_unwrap!(File::open(Path::new(&name))); - let mut input = BufReader::new(file_buf); - base_common::handle_input( - &mut input, - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); + if config_result.is_err() { + match config_result { + Ok(_) => panic!(), + Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s), } - // stdin - None => { - base_common::handle_input( - &mut stdin().lock(), - format, - config.wrap_cols, - config.ignore_garbage, - config.decode, - ); - } - }; + } + + // Create a reference to stdin so we can return a locked stdin from + // parse_base_cmd_args + let stdin_raw = stdin(); + let config = config_result.unwrap(); + let mut input: Box = base_common::get_input(&config, &stdin_raw); + + base_common::handle_input( + &mut input, + format, + config.wrap_cols, + config.ignore_garbage, + config.decode, + name, + ); 0 } diff --git a/src/uu/base64/src/base_common.rs b/src/uu/base64/src/base_common.rs deleted file mode 100644 index a4b49e499..000000000 --- a/src/uu/base64/src/base_common.rs +++ /dev/null @@ -1,40 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// (c) Jordy Dickinson -// (c) Jian Zeng -// (c) Alex Lyon -// -// For the full copyright and license information, please view the LICENSE file -// that was distributed with this source code. - -use std::io::{stdout, Read, Write}; - -use uucore::encoding::{wrap_print, Data, Format}; - -pub fn handle_input( - input: &mut R, - format: Format, - line_wrap: Option, - ignore_garbage: bool, - decode: bool, -) { - let mut data = Data::new(input, format).ignore_garbage(ignore_garbage); - if let Some(wrap) = line_wrap { - data = data.line_wrap(wrap); - } - - if !decode { - let encoded = data.encode(); - wrap_print(&data, encoded); - } else { - match data.decode() { - Ok(s) => { - if stdout().write_all(&s).is_err() { - // on windows console, writing invalid utf8 returns an error - crash!(1, "Cannot write non-utf8 data"); - } - } - Err(_) => crash!(1, "invalid input"), - } - } -} diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 157503d83..fd49aa951 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -98,7 +98,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base32: error: invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base32: error: Invalid wrap size: ‘b’: invalid digit found in string\n"); } } diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index c9adc5c0f..8d9dc5639 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -88,7 +88,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base64: error: invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base64: error: Invalid wrap size: ‘b’: invalid digit found in string\n"); } } From 193ad56c2a5e9a615618de8e74368c8cc2c1baad Mon Sep 17 00:00:00 2001 From: Ricardo Iglesias Date: Thu, 29 Apr 2021 02:07:59 -0700 Subject: [PATCH 207/399] Removed clippy warnings. --- src/uu/base32/src/base_common.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index d0f47f500..ee5fe8675 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -83,7 +83,7 @@ pub fn parse_base_cmd_args( let app = App::new(name) .version(version) .about(about) - .usage(&usage[..]) + .usage(usage) // Format arguments. .arg( Arg::with_name(options::DECODE) From 83554f4475a2c1c9ddbfe33bedc9ef53bafd922d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 17:48:01 +0200 Subject: [PATCH 208/399] add benchmarking instructions --- src/uu/sort/BENCHMARKING.md | 6 ++++++ src/uu/sort/src/sort.rs | 6 ++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 1caea0326..1810e8a4e 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -69,6 +69,12 @@ Run `cargo build --release` before benchmarking after you make a change! - Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers_si.txt -h -o output.txt"`. +## External sorting + +Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting +huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` +multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). + ## Stdout and stdin performance Try to run the above benchmarks by piping the input through stdin (standard input) and redirect the diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 7c053e4ad..be7944a0f 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1169,9 +1169,7 @@ fn file_to_lines_iter<'a>( }) .map(move |line| { Line::new( - std::str::from_utf8(&line.unwrap()) - .expect("input is not valid utf-8") - .to_string(), + crash_if_err!(1, String::from_utf8(crash_if_err!(1, line))), settings, ) }), @@ -1226,7 +1224,7 @@ fn exec(files: Vec, settings: GlobalSettings) -> i32 { } else { b'\n' }) { - let string = String::from_utf8(line.unwrap()).unwrap(); + let string = crash_if_err!(1, String::from_utf8(crash_if_err!(1, line))); lines.push(Line::new(string, &settings)); } } From b21a309c3f75d77b7e1c4f21a145e8ff893509e3 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 21:29:18 +0200 Subject: [PATCH 209/399] add a benchmarking example --- src/uu/sort/BENCHMARKING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 1810e8a4e..17a944b29 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -74,6 +74,7 @@ Run `cargo build --release` before benchmarking after you make a change! Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). +Example: Run `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -S 1M"` ## Stdout and stdin performance From 484558e37dbc95ba583a4210205164ef5521655b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 1 May 2021 21:38:36 +0200 Subject: [PATCH 210/399] Update src/uu/sort/BENCHMARKING.md Co-authored-by: Sylvestre Ledru --- src/uu/sort/BENCHMARKING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 17a944b29..71c331105 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -74,7 +74,8 @@ Run `cargo build --release` before benchmarking after you make a change! Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). -Example: Run `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -S 1M"` +Example: Run `hyperfine './target/release/coreutils sort shuffled_wordlist.txt -S 1M' 'sort shuffled_wordlist.txt -S 1M'` +` ## Stdout and stdin performance From c3912d53ac1430b718cebbcf724de3af59a06e2e Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Thu, 22 Apr 2021 20:29:04 -0400 Subject: [PATCH 211/399] test: add tests for basic tests & edge cases Some edge cases covered: - no args - operator by itself (!, -a, etc.) - string, file tests of nothing - compound negations --- tests/by-util/test_test.rs | 442 ++++++++++++++++++++++++++++- tests/fixtures/test/non_empty_file | 1 + tests/fixtures/test/regular_file | 0 3 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/test/non_empty_file create mode 100644 tests/fixtures/test/regular_file diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 4b9a9ff55..2173371fc 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -2,6 +2,7 @@ // This file is part of the uutils coreutils package. // // (c) mahkoh (ju.orth [at] gmail [dot] com) +// (c) Daniel Rocco // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. @@ -9,6 +10,436 @@ use crate::common::util::*; +#[test] +fn test_empty_test_equivalent_to_false() { + new_ucmd!().run().status_code(1); +} + +#[test] +fn test_empty_string_is_false() { + new_ucmd!().arg("").run().status_code(1); +} + +#[test] +fn test_solo_not() { + new_ucmd!().arg("!").succeeds(); +} + +#[test] +fn test_solo_and_or_or_is_a_literal() { + // /bin/test '' -a '' => 1; so test(1) must interpret `-a` by itself as + // a literal string + new_ucmd!().arg("-a").succeeds(); + new_ucmd!().arg("-o").succeeds(); +} + +#[test] +fn test_double_not_is_false() { + new_ucmd!().args(&["!", "!"]).run().status_code(1); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +fn test_and_not_is_false() { + new_ucmd!().args(&["-a", "!"]).run().status_code(1); +} + +#[test] +fn test_not_and_is_false() { + // `-a` is a literal here & has nonzero length + new_ucmd!().args(&["!", "-a"]).run().status_code(1); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 0"] +fn test_not_and_not_succeeds() { + new_ucmd!().args(&["!", "-a", "!"]).succeeds(); +} + +#[test] +fn test_simple_or() { + new_ucmd!().args(&["foo", "-o", ""]).succeeds(); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 0); GNU returns 1"] +fn test_negated_or() { + new_ucmd!() + .args(&["!", "foo", "-o", "bar"]) + .run() + .status_code(1); + new_ucmd!().args(&["foo", "-o", "!", "bar"]).succeeds(); + new_ucmd!() + .args(&["!", "foo", "-o", "!", "bar"]) + .run() + .status_code(1); +} + +#[test] +fn test_strlen_of_nothing() { + // odd but matches GNU, which must interpret -n as a literal here + new_ucmd!().arg("-n").succeeds(); +} + +#[test] +fn test_strlen_of_empty() { + new_ucmd!().args(&["-n", ""]).run().status_code(1); + + // STRING equivalent to -n STRING + new_ucmd!().arg("").run().status_code(1); +} + +#[test] +fn test_nothing_is_empty() { + // -z is a literal here and has nonzero length + new_ucmd!().arg("-z").succeeds(); +} + +#[test] +fn test_zero_len_of_empty() { + new_ucmd!().args(&["-z", ""]).succeeds(); +} + +#[test] +fn test_solo_paren_is_literal() { + let scenario = TestScenario::new(util_name!()); + let tests = [["("], [")"]]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "GNU considers this an error"] +fn test_solo_empty_parenthetical_is_error() { + new_ucmd!() + .args(&["(", ")"]) + .run() + .status_code(2) + .stderr_is("test: missing argument after ‘)’"); +} + +#[test] +fn test_zero_len_equals_zero_len() { + new_ucmd!().args(&["", "=", ""]).succeeds(); +} + +#[test] +fn test_zero_len_not_equals_zero_len_is_false() { + new_ucmd!().args(&["", "!=", ""]).run().status_code(1); +} + +#[test] +fn test_string_comparison() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + ["foo", "!=", "bar"], + ["contained\nnewline", "=", "contained\nnewline"], + ["(", "=", "("], + ["(", "!=", ")"], + ["!", "=", "!"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "fixme: error reporting"] +fn test_dangling_string_comparison_is_error() { + new_ucmd!() + .args(&["missing_something", "="]) + .run() + .status_code(2) + .stderr_is("test: missing argument after ‘=’"); +} + +#[test] +fn test_stringop_is_literal_after_bang() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + ["!", "="], + ["!", "!="], + ["!", "-eq"], + ["!", "-ne"], + ["!", "-lt"], + ["!", "-le"], + ["!", "-gt"], + ["!", "-ge"], + ["!", "-ef"], + ["!", "-nt"], + ["!", "-ot"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).run().status_code(1); + } +} + +#[test] +fn test_a_bunch_of_not() { + new_ucmd!() + .args(&["!", "", "!=", "", "-a", "!", "", "!=", ""]) + .succeeds(); +} + +#[test] +fn test_pseudofloat_equal() { + new_ucmd!().args(&["123.45", "=", "123.45"]).succeeds(); +} + +#[test] +fn test_pseudofloat_not_equal() { + new_ucmd!().args(&["123.45", "!=", "123.450"]).succeeds(); +} + +#[test] +fn test_negative_arg_is_a_string() { + new_ucmd!().arg("-12345").succeeds(); + new_ucmd!().arg("--qwert").succeeds(); +} + +#[test] +fn test_some_int_compares() { + let scenario = TestScenario::new(util_name!()); + + let tests = [ + ["0", "-eq", "0"], + ["0", "-ne", "1"], + ["421", "-lt", "3720"], + ["0", "-le", "0"], + ["11", "-gt", "10"], + ["1024", "-ge", "512"], + ["9223372036854775806", "-le", "9223372036854775807"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "fixme: evaluation error (code 1); GNU returns 0"] +fn test_values_greater_than_i64_allowed() { + new_ucmd!() + .args(&["9223372036854775808", "-gt", "0"]) + .succeeds(); +} + +#[test] +fn test_negative_int_compare() { + let scenario = TestScenario::new(util_name!()); + + let tests = [ + ["-1", "-eq", "-1"], + ["-1", "-ne", "-2"], + ["-3720", "-lt", "-421"], + ["-10", "-le", "-10"], + ["-21", "-gt", "-22"], + ["-128", "-ge", "-256"], + ["-9223372036854775808", "-le", "-9223372036854775807"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } +} + +#[test] +#[ignore = "fixme: error reporting"] +fn test_float_inequality_is_error() { + new_ucmd!() + .args(&["123.45", "-ge", "6"]) + .run() + .status_code(2) + .stderr_is("test: invalid integer ‘123.45’"); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +fn test_file_is_itself() { + new_ucmd!() + .args(&["regular_file", "-ef", "regular_file"]) + .succeeds(); +} + +#[test] +#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] +fn test_file_is_newer_than_and_older_than_itself() { + // odd but matches GNU + new_ucmd!() + .args(&["regular_file", "-nt", "regular_file"]) + .run() + .status_code(1); + new_ucmd!() + .args(&["regular_file", "-ot", "regular_file"]) + .run() + .status_code(1); +} + +#[test] +#[ignore = "todo: implement these"] +fn test_newer_file() { + let scenario = TestScenario::new(util_name!()); + + scenario.cmd("touch").arg("newer_file").succeeds(); + scenario + .cmd("touch") + .args(&["-m", "-d", "last Thursday", "regular_file"]) + .succeeds(); + + scenario + .ucmd() + .args(&["newer_file", "-nt", "regular_file"]) + .succeeds(); + scenario + .ucmd() + .args(&["regular_file", "-ot", "newer_file"]) + .succeeds(); +} + +#[test] +fn test_file_exists() { + new_ucmd!().args(&["-e", "regular_file"]).succeeds(); +} + +#[test] +fn test_nonexistent_file_does_not_exist() { + new_ucmd!() + .args(&["-e", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +fn test_nonexistent_file_is_not_regular() { + new_ucmd!() + .args(&["-f", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +fn test_file_exists_and_is_regular() { + new_ucmd!().args(&["-f", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_readable() { + new_ucmd!().args(&["-r", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_not_readable() { + let scenario = TestScenario::new(util_name!()); + let mut ucmd = scenario.ucmd(); + let mut chmod = scenario.cmd("chmod"); + + scenario.fixtures.touch("crypto_file"); + chmod.args(&["u-r", "crypto_file"]).succeeds(); + + ucmd.args(&["!", "-r", "crypto_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_writable() { + new_ucmd!().args(&["-w", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_not_writable() { + let scenario = TestScenario::new(util_name!()); + let mut ucmd = scenario.ucmd(); + let mut chmod = scenario.cmd("chmod"); + + scenario.fixtures.touch("immutable_file"); + chmod.args(&["u-w", "immutable_file"]).succeeds(); + + ucmd.args(&["!", "-w", "immutable_file"]).succeeds(); +} + +#[test] +fn test_file_is_not_executable() { + new_ucmd!().args(&["!", "-x", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] // FIXME: implement on Windows +fn test_file_is_executable() { + let scenario = TestScenario::new(util_name!()); + let mut chmod = scenario.cmd("chmod"); + + chmod.args(&["u+x", "regular_file"]).succeeds(); + + scenario.ucmd().args(&["-x", "regular_file"]).succeeds(); +} + +#[test] +fn test_is_not_empty() { + new_ucmd!().args(&["-s", "non_empty_file"]).succeeds(); +} + +#[test] +fn test_nonexistent_file_size_test_is_false() { + new_ucmd!() + .args(&["-s", "nonexistent_file"]) + .run() + .status_code(1); +} + +#[test] +fn test_not_is_not_empty() { + new_ucmd!().args(&["!", "-s", "regular_file"]).succeeds(); +} + +#[test] +#[cfg(not(windows))] +fn test_symlink_is_symlink() { + let scenario = TestScenario::new(util_name!()); + let mut ln = scenario.cmd("ln"); + + // creating symlinks requires admin on Windows + ln.args(&["-s", "regular_file", "symlink"]).succeeds(); + + // FIXME: implement on Windows + scenario.ucmd().args(&["-h", "symlink"]).succeeds(); + scenario.ucmd().args(&["-L", "symlink"]).succeeds(); +} + +#[test] +fn test_file_is_not_symlink() { + let scenario = TestScenario::new(util_name!()); + + scenario + .ucmd() + .args(&["!", "-h", "regular_file"]) + .succeeds(); + scenario + .ucmd() + .args(&["!", "-L", "regular_file"]) + .succeeds(); +} + +#[test] +fn test_nonexistent_file_is_not_symlink() { + let scenario = TestScenario::new(util_name!()); + + scenario + .ucmd() + .args(&["!", "-h", "nonexistent_file"]) + .succeeds(); + scenario + .ucmd() + .args(&["!", "-L", "nonexistent_file"]) + .succeeds(); +} + #[test] fn test_op_prec_and_or_1() { new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds(); @@ -23,5 +454,14 @@ fn test_op_prec_and_or_2() { #[test] fn test_or_as_filename() { - new_ucmd!().args(&["x", "-a", "-z", "-o"]).fails(); + new_ucmd!() + .args(&["x", "-a", "-z", "-o"]) + .run() + .status_code(1); +} + +#[test] +#[ignore = "GNU considers this an error"] +fn test_strlen_and_nothing() { + new_ucmd!().args(&["-n", "a", "-a"]).run().status_code(2); } diff --git a/tests/fixtures/test/non_empty_file b/tests/fixtures/test/non_empty_file new file mode 100644 index 000000000..34425caf6 --- /dev/null +++ b/tests/fixtures/test/non_empty_file @@ -0,0 +1 @@ +Not empty! diff --git a/tests/fixtures/test/regular_file b/tests/fixtures/test/regular_file new file mode 100644 index 000000000..e69de29bb From 3c126bad72f9f26245b4aad02b1267c38b248032 Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Mon, 26 Apr 2021 21:27:29 -0400 Subject: [PATCH 212/399] test: implement parenthesized expressions, additional tests - Replace the parser with a recursive descent implementation that handles parentheses and produces a stack of operations in postfix order. Parsing now operates directly on OsStrings passed by the uucore framework. - Replace the dispatch mechanism with a stack machine operating on the symbol stack produced by the parser. - Add tests for parenthesized expressions. - Begin testing character encoding handling. --- src/uu/test/src/parser.rs | 292 +++++++++++++++++++++++++ src/uu/test/src/test.rs | 436 ++++++++++++------------------------- tests/by-util/test_test.rs | 84 ++++++- 3 files changed, 504 insertions(+), 308 deletions(-) create mode 100644 src/uu/test/src/parser.rs diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs new file mode 100644 index 000000000..f1ca9dad6 --- /dev/null +++ b/src/uu/test/src/parser.rs @@ -0,0 +1,292 @@ +// This file is part of the uutils coreutils package. +// +// (c) Daniel Rocco +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::ffi::OsString; +use std::iter::Peekable; + +/// Represents a parsed token from a test expression +#[derive(Debug, PartialEq)] +pub enum Symbol { + LParen, + Bang, + BoolOp(OsString), + Literal(OsString), + StringOp(OsString), + IntOp(OsString), + FileOp(OsString), + StrlenOp(OsString), + FiletestOp(OsString), + None, +} + +impl Symbol { + /// Create a new Symbol from an OsString. + /// + /// Returns Symbol::None in place of None + fn new(token: Option) -> Symbol { + match token { + Some(s) => match s.to_string_lossy().as_ref() { + "(" => Symbol::LParen, + "!" => Symbol::Bang, + "-a" | "-o" => Symbol::BoolOp(s), + "=" | "!=" => Symbol::StringOp(s), + "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::IntOp(s), + "-ef" | "-nt" | "-ot" => Symbol::FileOp(s), + "-n" | "-z" => Symbol::StrlenOp(s), + "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" + | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => Symbol::FiletestOp(s), + _ => Symbol::Literal(s), + }, + None => Symbol::None, + } + } + + /// Convert this Symbol into a Symbol::Literal, useful for cases where + /// test treats an operator as a string operand (test has no reserved + /// words). + /// + /// # Panics + /// + /// Panics if `self` is Symbol::None + fn into_literal(self) -> Symbol { + Symbol::Literal(match self { + Symbol::LParen => OsString::from("("), + Symbol::Bang => OsString::from("!"), + Symbol::BoolOp(s) + | Symbol::Literal(s) + | Symbol::StringOp(s) + | Symbol::IntOp(s) + | Symbol::FileOp(s) + | Symbol::StrlenOp(s) + | Symbol::FiletestOp(s) => s, + Symbol::None => panic!(), + }) + } +} + +/// Recursive descent parser for test, which converts a list of OsStrings +/// (typically command line arguments) into a stack of Symbols in postfix +/// order. +/// +/// Grammar: +/// +/// EXPR → TERM | EXPR BOOLOP EXPR +/// TERM → ( EXPR ) +/// TERM → ( ) +/// TERM → ! EXPR +/// TERM → UOP str +/// UOP → STRLEN | FILETEST +/// TERM → str OP str +/// TERM → str | 𝜖 +/// OP → STRINGOP | INTOP | FILEOP +/// STRINGOP → = | != +/// INTOP → -eq | -ge | -gt | -le | -lt | -ne +/// FILEOP → -ef | -nt | -ot +/// STRLEN → -n | -z +/// FILETEST → -b | -c | -d | -e | -f | -g | -G | -h | -k | -L | -O | -p | +/// -r | -s | -S | -t | -u | -w | -x +/// BOOLOP → -a | -o +/// +#[derive(Debug)] +struct Parser { + tokens: Peekable>, + pub stack: Vec, +} + +impl Parser { + /// Construct a new Parser from a `Vec` of tokens. + fn new(tokens: Vec) -> Parser { + Parser { + tokens: tokens.into_iter().peekable(), + stack: vec![], + } + } + + /// Fetch the next token from the input stream as a Symbol. + fn next_token(&mut self) -> Symbol { + Symbol::new(self.tokens.next()) + } + + /// Peek at the next token from the input stream, returning it as a Symbol. + /// The stream is unchanged and will return the same Symbol on subsequent + /// calls to `next()` or `peek()`. + fn peek(&mut self) -> Symbol { + Symbol::new(self.tokens.peek().map(|s| s.to_os_string())) + } + + /// Test if the next token in the stream is a BOOLOP (-a or -o), without + /// removing the token from the stream. + fn peek_is_boolop(&mut self) -> bool { + if let Symbol::BoolOp(_) = self.peek() { + true + } else { + false + } + } + + /// Parse an expression. + /// + /// EXPR → TERM | EXPR BOOLOP EXPR + fn expr(&mut self) { + if !self.peek_is_boolop() { + self.term(); + } + self.maybe_boolop(); + } + + /// Parse a term token and possible subsequent symbols: "(", "!", UOP, + /// literal, or None. + fn term(&mut self) { + let symbol = self.next_token(); + + match symbol { + Symbol::LParen => self.lparen(), + Symbol::Bang => self.bang(), + Symbol::StrlenOp(_) => self.uop(symbol), + Symbol::FiletestOp(_) => self.uop(symbol), + Symbol::None => self.stack.push(symbol), + literal => self.literal(literal), + } + } + + /// Parse a (possibly) parenthesized expression. + /// + /// test has no reserved keywords, so "(" will be interpreted as a literal + /// if it is followed by nothing or a comparison operator OP. + fn lparen(&mut self) { + match self.peek() { + // lparen is a literal when followed by nothing or comparison + Symbol::None | Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { + self.literal(Symbol::Literal(OsString::from("("))); + } + // empty parenthetical + Symbol::Literal(s) if s == ")" => {} + _ => { + self.expr(); + match self.next_token() { + Symbol::Literal(s) if s == ")" => (), + _ => panic!("expected ‘)’"), + } + } + } + } + + /// Parse a (possibly) negated expression. + /// + /// Example cases: + /// + /// * `! =`: negate the result of the implicit string length test of `=` + /// * `! = foo`: compare the literal strings `!` and `foo` + /// * `! `: negate the result of the expression + /// + fn bang(&mut self) { + if let Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) = self.peek() { + // we need to peek ahead one more token to disambiguate the first + // two cases listed above: case 1 — `! ` — and + // case 2: ` OP str`. + let peek2 = self.tokens.clone().nth(1); + + if peek2.is_none() { + // op is literal + let op = self.next_token().into_literal(); + self.stack.push(op); + self.stack.push(Symbol::Bang); + } else { + // bang is literal; parsing continues with op + self.literal(Symbol::Literal(OsString::from("!"))); + } + } else { + self.expr(); + self.stack.push(Symbol::Bang); + } + } + + /// Peek at the next token and parse it as a BOOLOP or string literal, + /// as appropriate. + fn maybe_boolop(&mut self) { + if self.peek_is_boolop() { + let token = self.tokens.next().unwrap(); // safe because we peeked + + // BoolOp by itself interpreted as Literal + if let Symbol::None = self.peek() { + self.literal(Symbol::Literal(token)) + } else { + self.boolop(Symbol::BoolOp(token)) + } + } + } + + /// Parse a Boolean expression. + /// + /// Logical and (-a) has higher precedence than or (-o), so in an + /// expression like `foo -o '' -a ''`, the and subexpression is evaluated + /// first. + fn boolop(&mut self, op: Symbol) { + if op == Symbol::BoolOp(OsString::from("-a")) { + self.term(); + self.stack.push(op); + self.maybe_boolop(); + } else { + self.expr(); + self.stack.push(op); + } + } + + /// Parse a (possible) unary argument test (string length or file + /// attribute check). + /// + /// If a UOP is followed by nothing it is interpreted as a literal string. + fn uop(&mut self, op: Symbol) { + match self.next_token() { + Symbol::None => self.stack.push(op.into_literal()), + symbol => { + self.stack.push(symbol.into_literal()); + self.stack.push(op); + } + } + } + + /// Parse a string literal, optionally followed by a comparison operator + /// and a second string literal. + fn literal(&mut self, token: Symbol) { + self.stack.push(token.into_literal()); + + // EXPR → str OP str + match self.peek() { + Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { + let op = self.next_token(); + + match self.next_token() { + Symbol::None => panic!("missing argument after {:?}", op), + token => self.stack.push(token.into_literal()), + } + + self.stack.push(op); + } + _ => {} + } + } + + /// Parser entry point: parse the token stream `self.tokens`, storing the + /// resulting `Symbol` stack in `self.stack`. + fn parse(&mut self) -> Result<(), String> { + self.expr(); + + match self.tokens.next() { + Some(token) => Err(format!("extra argument ‘{}’", token.to_string_lossy())), + None => Ok(()), + } + } +} + +/// Parse the token stream `args`, returning a `Symbol` stack representing the +/// operations to perform in postfix order. +pub fn parse(args: Vec) -> Result, String> { + let mut p = Parser::new(args); + p.parse()?; + Ok(p.stack) +} diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index f882ff5ae..3e97af0a6 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -1,144 +1,154 @@ -// * This file is part of the uutils coreutils package. -// * -// * (c) mahkoh (ju.orth [at] gmail [dot] com) -// * -// * For the full copyright and license information, please view the LICENSE -// * file that was distributed with this source code. +// This file is part of the uutils coreutils package. +// +// (c) mahkoh (ju.orth [at] gmail [dot] com) +// (c) Daniel Rocco +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. // spell-checker:ignore (ToDO) retval paren prec subprec cond -use std::collections::HashMap; -use std::str::from_utf8; +mod parser; -static NAME: &str = "test"; +use parser::{parse, Symbol}; +use std::ffi::{OsStr, OsString}; pub fn uumain(args: impl uucore::Args) -> i32 { - let args: Vec<_> = args.collect(); - // This is completely disregarding valid windows paths that aren't valid unicode - let args = args - .iter() - .map(|a| a.to_str().unwrap().as_bytes()) - .collect::>(); - if args.is_empty() { - return 2; - } - let args = if !args[0].ends_with(NAME.as_bytes()) { - &args[1..] - } else { - &args[..] - }; - let args = match args[0] { - b"[" => match args[args.len() - 1] { - b"]" => &args[1..args.len() - 1], - _ => return 2, - }, - _ => &args[1..args.len()], - }; - let mut error = false; - let retval = 1 - parse_expr(args, &mut error) as i32; - if error { - 2 - } else { - retval - } -} + // TODO: handle being called as `[` + let args: Vec<_> = args.skip(1).collect(); -fn one(args: &[&[u8]]) -> bool { - !args[0].is_empty() -} + let result = parse(args).and_then(|mut stack| eval(&mut stack)); -fn two(args: &[&[u8]], error: &mut bool) -> bool { - match args[0] { - b"!" => !one(&args[1..]), - b"-b" => path(args[1], PathCondition::BlockSpecial), - b"-c" => path(args[1], PathCondition::CharacterSpecial), - b"-d" => path(args[1], PathCondition::Directory), - b"-e" => path(args[1], PathCondition::Exists), - b"-f" => path(args[1], PathCondition::Regular), - b"-g" => path(args[1], PathCondition::GroupIdFlag), - b"-h" => path(args[1], PathCondition::SymLink), - b"-L" => path(args[1], PathCondition::SymLink), - b"-n" => one(&args[1..]), - b"-p" => path(args[1], PathCondition::Fifo), - b"-r" => path(args[1], PathCondition::Readable), - b"-S" => path(args[1], PathCondition::Socket), - b"-s" => path(args[1], PathCondition::NonEmpty), - b"-t" => isatty(args[1]), - b"-u" => path(args[1], PathCondition::UserIdFlag), - b"-w" => path(args[1], PathCondition::Writable), - b"-x" => path(args[1], PathCondition::Executable), - b"-z" => !one(&args[1..]), - _ => { - *error = true; - false - } - } -} - -fn three(args: &[&[u8]], error: &mut bool) -> bool { - match args[1] { - b"=" => args[0] == args[2], - b"==" => args[0] == args[2], - b"!=" => args[0] != args[2], - b"-eq" => integers(args[0], args[2], IntegerCondition::Equal), - b"-ne" => integers(args[0], args[2], IntegerCondition::Unequal), - b"-gt" => integers(args[0], args[2], IntegerCondition::Greater), - b"-ge" => integers(args[0], args[2], IntegerCondition::GreaterEqual), - b"-lt" => integers(args[0], args[2], IntegerCondition::Less), - b"-le" => integers(args[0], args[2], IntegerCondition::LessEqual), - _ => match args[0] { - b"!" => !two(&args[1..], error), - _ => { - *error = true; - false + match result { + Ok(result) => { + if result { + 0 + } else { + 1 } - }, - } -} - -fn four(args: &[&[u8]], error: &mut bool) -> bool { - match args[0] { - b"!" => !three(&args[1..], error), - _ => { - *error = true; - false + } + Err(e) => { + eprintln!("test: {}", e); + 2 } } } -enum IntegerCondition { - Equal, - Unequal, - Greater, - GreaterEqual, - Less, - LessEqual, -} +/// Evaluate a stack of Symbols, returning the result of the evaluation or +/// an error message if evaluation failed. +fn eval(stack: &mut Vec) -> Result { + macro_rules! pop_literal { + () => { + match stack.pop() { + Some(Symbol::Literal(s)) => s, + _ => panic!(), + } + }; + } -fn integers(a: &[u8], b: &[u8], cond: IntegerCondition) -> bool { - let (a, b): (&str, &str) = match (from_utf8(a), from_utf8(b)) { - (Ok(a), Ok(b)) => (a, b), - _ => return false, - }; - let (a, b): (i64, i64) = match (a.parse(), b.parse()) { - (Ok(a), Ok(b)) => (a, b), - _ => return false, - }; - match cond { - IntegerCondition::Equal => a == b, - IntegerCondition::Unequal => a != b, - IntegerCondition::Greater => a > b, - IntegerCondition::GreaterEqual => a >= b, - IntegerCondition::Less => a < b, - IntegerCondition::LessEqual => a <= b, + let s = stack.pop(); + + match s { + Some(Symbol::Bang) => { + let result = eval(stack)?; + + Ok(!result) + } + Some(Symbol::StringOp(op)) => { + let b = stack.pop(); + let a = stack.pop(); + Ok(if op == "=" { a == b } else { a != b }) + } + Some(Symbol::IntOp(op)) => { + let b = pop_literal!(); + let a = pop_literal!(); + + Ok(integers(&a, &b, &op)?) + } + Some(Symbol::FileOp(_op)) => unimplemented!(), + Some(Symbol::StrlenOp(op)) => { + let s = match stack.pop() { + Some(Symbol::Literal(s)) => s, + Some(Symbol::None) => OsString::from(""), + None => { + return Ok(true); + } + _ => { + return Err(format!("missing argument after ‘{:?}’", op)); + } + }; + + Ok(if op == "-z" { + s.is_empty() + } else { + !s.is_empty() + }) + } + Some(Symbol::FiletestOp(op)) => { + let op = op.to_string_lossy(); + + let f = pop_literal!(); + + Ok(match op.as_ref() { + "-b" => path(&f, PathCondition::BlockSpecial), + "-c" => path(&f, PathCondition::CharacterSpecial), + "-d" => path(&f, PathCondition::Directory), + "-e" => path(&f, PathCondition::Exists), + "-f" => path(&f, PathCondition::Regular), + "-g" => path(&f, PathCondition::GroupIdFlag), + "-h" => path(&f, PathCondition::SymLink), + "-L" => path(&f, PathCondition::SymLink), + "-p" => path(&f, PathCondition::Fifo), + "-r" => path(&f, PathCondition::Readable), + "-S" => path(&f, PathCondition::Socket), + "-s" => path(&f, PathCondition::NonEmpty), + "-t" => isatty(&f)?, + "-u" => path(&f, PathCondition::UserIdFlag), + "-w" => path(&f, PathCondition::Writable), + "-x" => path(&f, PathCondition::Executable), + _ => panic!(), + }) + } + Some(Symbol::Literal(s)) => Ok(!s.is_empty()), + Some(Symbol::None) => Ok(false), + Some(Symbol::BoolOp(op)) => { + let b = eval(stack)?; + let a = eval(stack)?; + + Ok(if op == "-a" { a && b } else { a || b }) + } + None => Ok(false), + _ => Err("expected value".to_string()), } } -fn isatty(fd: &[u8]) -> bool { - from_utf8(fd) - .ok() - .and_then(|s| s.parse().ok()) - .map_or(false, |i| { +fn integers(a: &OsStr, b: &OsStr, cond: &OsStr) -> Result { + let format_err = |value| format!("invalid integer ‘{}’", value); + + let a = a.to_string_lossy(); + let a: i64 = a.parse().map_err(|_| format_err(a))?; + + let b = b.to_string_lossy(); + let b: i64 = b.parse().map_err(|_| format_err(b))?; + + let cond = cond.to_string_lossy(); + Ok(match cond.as_ref() { + "-eq" => a == b, + "-ne" => a != b, + "-gt" => a > b, + "-ge" => a >= b, + "-lt" => a < b, + "-le" => a <= b, + _ => return Err(format!("unknown operator ‘{}’", cond)), + }) +} + +fn isatty(fd: &OsStr) -> Result { + let fd = fd.to_string_lossy(); + + fd.parse() + .map_err(|_| format!("invalid integer ‘{}’", fd)) + .map(|i| { #[cfg(not(target_os = "redox"))] unsafe { libc::isatty(i) == 1 @@ -148,173 +158,6 @@ fn isatty(fd: &[u8]) -> bool { }) } -fn dispatch(args: &mut &[&[u8]], error: &mut bool) -> bool { - let (val, idx) = match args.len() { - 0 => { - *error = true; - (false, 0) - } - 1 => (one(*args), 1), - 2 => dispatch_two(args, error), - 3 => dispatch_three(args, error), - _ => dispatch_four(args, error), - }; - *args = &(*args)[idx..]; - val -} - -fn dispatch_two(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { - let val = two(*args, error); - if *error { - *error = false; - (one(*args), 1) - } else { - (val, 2) - } -} - -fn dispatch_three(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { - let val = three(*args, error); - if *error { - *error = false; - dispatch_two(args, error) - } else { - (val, 3) - } -} - -fn dispatch_four(args: &mut &[&[u8]], error: &mut bool) -> (bool, usize) { - let val = four(*args, error); - if *error { - *error = false; - dispatch_three(args, error) - } else { - (val, 4) - } -} - -#[derive(Clone, Copy)] -enum Precedence { - Unknown = 0, - Paren, // FIXME: this is useless (parentheses have not been implemented) - Or, - And, - BUnOp, - BinOp, - UnOp, -} - -fn parse_expr(mut args: &[&[u8]], error: &mut bool) -> bool { - if args.is_empty() { - false - } else { - let hashmap = setup_hashmap(); - let lhs = dispatch(&mut args, error); - - if !args.is_empty() { - parse_expr_helper(&hashmap, &mut args, lhs, Precedence::Unknown, error) - } else { - lhs - } - } -} - -fn parse_expr_helper<'a>( - hashmap: &HashMap<&'a [u8], Precedence>, - args: &mut &[&'a [u8]], - mut lhs: bool, - min_prec: Precedence, - error: &mut bool, -) -> bool { - let mut prec = *hashmap.get(&args[0]).unwrap_or_else(|| { - *error = true; - &min_prec - }); - while !*error && !args.is_empty() && prec as usize >= min_prec as usize { - let op = args[0]; - *args = &(*args)[1..]; - let mut rhs = dispatch(args, error); - while !args.is_empty() { - let subprec = *hashmap.get(&args[0]).unwrap_or_else(|| { - *error = true; - &min_prec - }); - if subprec as usize <= prec as usize || *error { - break; - } - rhs = parse_expr_helper(hashmap, args, rhs, subprec, error); - } - lhs = match prec { - Precedence::UnOp | Precedence::BUnOp => { - *error = true; - false - } - Precedence::And => lhs && rhs, - Precedence::Or => lhs || rhs, - Precedence::BinOp => three( - &[ - if lhs { b" " } else { b"" }, - op, - if rhs { b" " } else { b"" }, - ], - error, - ), - Precedence::Paren => unimplemented!(), // TODO: implement parentheses - _ => unreachable!(), - }; - if !args.is_empty() { - prec = *hashmap.get(&args[0]).unwrap_or_else(|| { - *error = true; - &min_prec - }); - } - } - lhs -} - -#[inline] -fn setup_hashmap<'a>() -> HashMap<&'a [u8], Precedence> { - let mut hashmap = HashMap::<&'a [u8], Precedence>::new(); - - hashmap.insert(b"-b", Precedence::UnOp); - hashmap.insert(b"-c", Precedence::UnOp); - hashmap.insert(b"-d", Precedence::UnOp); - hashmap.insert(b"-e", Precedence::UnOp); - hashmap.insert(b"-f", Precedence::UnOp); - hashmap.insert(b"-g", Precedence::UnOp); - hashmap.insert(b"-h", Precedence::UnOp); - hashmap.insert(b"-L", Precedence::UnOp); - hashmap.insert(b"-n", Precedence::UnOp); - hashmap.insert(b"-p", Precedence::UnOp); - hashmap.insert(b"-r", Precedence::UnOp); - hashmap.insert(b"-S", Precedence::UnOp); - hashmap.insert(b"-s", Precedence::UnOp); - hashmap.insert(b"-t", Precedence::UnOp); - hashmap.insert(b"-u", Precedence::UnOp); - hashmap.insert(b"-w", Precedence::UnOp); - hashmap.insert(b"-x", Precedence::UnOp); - hashmap.insert(b"-z", Precedence::UnOp); - - hashmap.insert(b"=", Precedence::BinOp); - hashmap.insert(b"!=", Precedence::BinOp); - hashmap.insert(b"-eq", Precedence::BinOp); - hashmap.insert(b"-ne", Precedence::BinOp); - hashmap.insert(b"-gt", Precedence::BinOp); - hashmap.insert(b"-ge", Precedence::BinOp); - hashmap.insert(b"-lt", Precedence::BinOp); - hashmap.insert(b"-le", Precedence::BinOp); - - hashmap.insert(b"!", Precedence::BUnOp); - - hashmap.insert(b"-a", Precedence::And); - hashmap.insert(b"-o", Precedence::Or); - - hashmap.insert(b"(", Precedence::Paren); - hashmap.insert(b")", Precedence::Paren); - - hashmap -} - #[derive(Eq, PartialEq)] enum PathCondition { BlockSpecial, @@ -334,14 +177,10 @@ enum PathCondition { } #[cfg(not(windows))] -fn path(path: &[u8], cond: PathCondition) -> bool { - use std::ffi::OsStr; +fn path(path: &OsStr, cond: PathCondition) -> bool { use std::fs::{self, Metadata}; - use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::{FileTypeExt, MetadataExt}; - let path = OsStr::from_bytes(path); - const S_ISUID: u32 = 0o4000; const S_ISGID: u32 = 0o2000; @@ -403,13 +242,14 @@ fn path(path: &[u8], cond: PathCondition) -> bool { } #[cfg(windows)] -fn path(path: &[u8], cond: PathCondition) -> bool { +fn path(path: &OsStr, cond: PathCondition) -> bool { use std::fs::metadata; - let path = from_utf8(path).unwrap(); + let stat = match metadata(path) { Ok(s) => s, _ => return false, }; + match cond { PathCondition::BlockSpecial => false, PathCondition::CharacterSpecial => false, diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 2173371fc..000013d9c 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -39,7 +39,6 @@ fn test_double_not_is_false() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] fn test_and_not_is_false() { new_ucmd!().args(&["-a", "!"]).run().status_code(1); } @@ -51,7 +50,6 @@ fn test_not_and_is_false() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 2); GNU returns 0"] fn test_not_and_not_succeeds() { new_ucmd!().args(&["!", "-a", "!"]).succeeds(); } @@ -62,7 +60,6 @@ fn test_simple_or() { } #[test] -#[ignore = "fixme: parse/evaluation error (code 0); GNU returns 1"] fn test_negated_or() { new_ucmd!() .args(&["!", "foo", "-o", "bar"]) @@ -111,13 +108,8 @@ fn test_solo_paren_is_literal() { } #[test] -#[ignore = "GNU considers this an error"] fn test_solo_empty_parenthetical_is_error() { - new_ucmd!() - .args(&["(", ")"]) - .run() - .status_code(2) - .stderr_is("test: missing argument after ‘)’"); + new_ucmd!().args(&["(", ")"]).run().status_code(2); } #[test] @@ -248,7 +240,6 @@ fn test_negative_int_compare() { } #[test] -#[ignore = "fixme: error reporting"] fn test_float_inequality_is_error() { new_ucmd!() .args(&["123.45", "-ge", "6"]) @@ -257,6 +248,32 @@ fn test_float_inequality_is_error() { .stderr_is("test: invalid integer ‘123.45’"); } +#[test] +#[cfg(not(windows))] +fn test_invalid_utf8_integer_compare() { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + let source = [0x66, 0x6f, 0x80, 0x6f]; + let arg = OsStr::from_bytes(&source[..]); + + let mut cmd = new_ucmd!(); + cmd.arg("123").arg("-ne"); + cmd.raw.arg(arg); + + cmd.run() + .status_code(2) + .stderr_is("test: invalid integer ‘fo�o’"); + + let mut cmd = new_ucmd!(); + cmd.raw.arg(arg); + cmd.arg("-eq").arg("456"); + + cmd.run() + .status_code(2) + .stderr_is("test: invalid integer ‘fo�o’"); +} + #[test] #[ignore = "fixme: parse/evaluation error (code 2); GNU returns 1"] fn test_file_is_itself() { @@ -445,6 +462,14 @@ fn test_op_prec_and_or_1() { new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds(); } +#[test] +fn test_op_prec_and_or_1_overridden_by_parentheses() { + new_ucmd!() + .args(&["(", " ", "-o", "", ")", "-a", ""]) + .run() + .status_code(1); +} + #[test] fn test_op_prec_and_or_2() { new_ucmd!() @@ -452,6 +477,45 @@ fn test_op_prec_and_or_2() { .succeeds(); } +#[test] +fn test_op_prec_and_or_2_overridden_by_parentheses() { + new_ucmd!() + .args(&["", "-a", "(", "", "-o", " ", ")", "-a", " "]) + .run() + .status_code(1); +} + +#[test] +#[ignore = "fixme: error reporting"] +fn test_dangling_parenthesis() { + new_ucmd!() + .args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c"]) + .run() + .status_code(2); + new_ucmd!() + .args(&["(", "(", "a", "!=", "b", ")", "-o", "-n", "c", ")"]) + .succeeds(); +} + +#[test] +fn test_complicated_parenthesized_expression() { + new_ucmd!() + .args(&[ + "(", "(", "!", "(", "a", "=", "b", ")", "-o", "c", "=", "d", ")", "-a", "(", "q", "!=", + "r", ")", ")", + ]) + .succeeds(); +} + +#[test] +fn test_erroneous_parenthesized_expression() { + new_ucmd!() + .args(&["a", "!=", "(", "b", "-a", "b", ")", "!=", "c"]) + .run() + .status_code(2) + .stderr_is("test: extra argument ‘b’"); +} + #[test] fn test_or_as_filename() { new_ucmd!() From f5c7d9bd80c27341efad1af981e69d9e47d47234 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sat, 1 May 2021 09:51:31 +0800 Subject: [PATCH 213/399] link: replace getopts with clap --- Cargo.lock | 1 + src/uu/link/Cargo.toml | 1 + src/uu/link/src/link.rs | 44 +++++++++++++++++++++++++++++------------ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..217533df6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2050,6 +2050,7 @@ dependencies = [ name = "uu_link" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 13c3453cf..14a6ac7c9 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -18,6 +18,7 @@ path = "src/link.rs" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +clap = "2.33" [[bin]] name = "link" diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index b82d7cfac..bd8b33355 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -8,14 +8,21 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::fs::hard_link; use std::io::Error; use std::path::Path; -use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; -static SUMMARY: &str = "Create a link named FILE2 to FILE1"; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; + +pub mod options { + pub static FILES: &str = "FILES"; +} + +fn get_usage() -> String { + format!("{0} FILE1 FILE2", executable!()) +} pub fn normalize_error_message(e: Error) -> String { match e.raw_os_error() { @@ -25,16 +32,27 @@ pub fn normalize_error_message(e: Error) -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::Ignore) - .accept_any(), - ); - if matches.free.len() != 2 { - crash!(1, "{}", msg_wrong_number_of_arguments!(2)); - } + let usage = get_usage(); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg( + Arg::with_name(options::FILES) + .hidden(true) + .required(true) + .min_values(2) + .max_values(2) + .takes_value(true), + ) + .get_matches_from(args); - let old = Path::new(&matches.free[0]); - let new = Path::new(&matches.free[1]); + let files: Vec<_> = matches + .values_of_os(options::FILES) + .unwrap_or_default() + .collect(); + let old = Path::new(files[0]); + let new = Path::new(files[1]); match hard_link(old, new) { Ok(_) => 0, From 42756610206e5efd8ac1a0687b9d856d7b21e68a Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Sun, 2 May 2021 13:47:36 +0900 Subject: [PATCH 214/399] tests/basename: add tests for --version, --help --- tests/by-util/test_basename.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 8d32b4008..6c7a3ecae 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -1,6 +1,29 @@ use crate::common::util::*; use std::ffi::OsStr; +#[test] +fn test_help() { + for help_flg in vec!["-h", "--help"] { + new_ucmd!() + .arg(&help_flg) + .succeeds() + .no_stderr() + .stdout_contains("USAGE:"); + } +} + +#[test] +fn test_version() { + for version_flg in vec!["-V", "--version"] { + assert!(new_ucmd!() + .arg(&version_flg) + .succeeds() + .no_stderr() + .stdout_str() + .starts_with("basename")); + } +} + #[test] fn test_directory() { new_ucmd!() From 5e82b195bd3f8da8e34e4ccf061227498af3abf1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:35:00 +0200 Subject: [PATCH 215/399] ls: remove redundant import --- src/uu/ls/src/ls.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc7ea4c08..381612189 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -39,8 +39,6 @@ use std::{ time::Duration, }; -use chrono; - use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use unicode_width::UnicodeWidthStr; From e723b8db4321a56ddfe1fec5c538ac88370aff40 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:35:59 +0200 Subject: [PATCH 216/399] factor: unneeded statement --- src/uu/factor/src/factor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 5a85194c4..138254b51 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -172,7 +172,7 @@ pub fn factor(mut n: u64) -> Factors { #[cfg(feature = "coz")] coz::end!("factorization"); - return r; + r } #[cfg(test)] From 108f9928ef77a6d1b811140a87a0ad053e400ef5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:39:09 +0200 Subject: [PATCH 217/399] cp: fix 'variable does not need to be mutable' --- src/uu/cp/src/cp.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index ca564e37c..3d6faf66a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -551,6 +551,10 @@ impl FromStr for Attribute { fn add_all_attributes() -> Vec { use Attribute::*; + #[cfg(target_os = "windows")] + let attr = vec![Ownership, Timestamps, Context, Xattr, Links]; + + #[cfg(not(target_os = "windows"))] let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links]; #[cfg(unix)] From c03a7d88561106f1fef24c1ea805513bd0283ff2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:41:04 +0200 Subject: [PATCH 218/399] uname(test): fix 'unused variable: result' --- tests/by-util/test_uname.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index 6b8d2d59d..da901d985 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -36,7 +36,12 @@ fn test_uname_kernel_version() { fn test_uname_kernel() { let (_, mut ucmd) = at_and_ucmd!(); - let result = ucmd.arg("-o").succeeds(); #[cfg(target_os = "linux")] - assert!(result.stdout_str().to_lowercase().contains("linux")); + { + let result = ucmd.arg("-o").succeeds(); + assert!(result.stdout_str().to_lowercase().contains("linux")); + } + + #[cfg(not(target_os = "linux"))] + let result = ucmd.arg("-o").succeeds(); } From a34b49ad6053e29ba457f2a43deab21f48ccdf68 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:42:30 +0200 Subject: [PATCH 219/399] relpath(test) - fix: 'value assigned to 'result_stdout' is never read' --- tests/by-util/test_relpath.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs index cc17b45c3..5094d25a8 100644 --- a/tests/by-util/test_relpath.rs +++ b/tests/by-util/test_relpath.rs @@ -103,7 +103,7 @@ fn test_relpath_with_from_with_d() { at.mkdir_all(from); // d is part of subpath -> expect relative path - let mut result_stdout = scene + let mut _result_stdout = scene .ucmd() .arg(to) .arg(from) @@ -112,17 +112,17 @@ fn test_relpath_with_from_with_d() { .stdout_move_str(); // relax rules for windows test environment #[cfg(not(windows))] - assert!(Path::new(&result_stdout).is_relative()); + assert!(Path::new(&_result_stdout).is_relative()); // d is not part of subpath -> expect absolut path - result_stdout = scene + _result_stdout = scene .ucmd() .arg(to) .arg(from) .arg("-dnon_existing") .succeeds() .stdout_move_str(); - assert!(Path::new(&result_stdout).is_absolute()); + assert!(Path::new(&_result_stdout).is_absolute()); } } @@ -135,12 +135,12 @@ fn test_relpath_no_from_no_d() { let to: &str = &convert_path(test.to); at.mkdir_all(to); - let result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); + let _result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); #[cfg(not(windows))] - assert_eq!(result_stdout, format!("{}\n", to)); + assert_eq!(_result_stdout, format!("{}\n", to)); // relax rules for windows test environment #[cfg(windows)] - assert!(result_stdout.ends_with(&format!("{}\n", to))); + assert!(_result_stdout.ends_with(&format!("{}\n", to))); } } From d0512618d5212d8991556a98c44bb688144c4d45 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 09:43:32 +0200 Subject: [PATCH 220/399] refresh cargo.lock with recent updates --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 091758278..c74f4e2be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efb2352a0f4d4b128f734b5c44c79ff80117351138733f12f982fe3e2b13343" +checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" dependencies = [ "aho-corasick", "memchr 2.4.0", @@ -1297,9 +1297,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.24" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00efb87459ba4f6fb2169d20f68565555688e1250ee6825cdf6254f8b48fafb2" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" From 28c7800f73ec2712a296fd930a9e7706efa6a9bd Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 10:03:01 +0200 Subject: [PATCH 221/399] ls: fix subdirectory name --- src/uu/ls/src/ls.rs | 25 ++++++++++------ tests/by-util/test_ls.rs | 63 ++++++++++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc7ea4c08..4ebcc479c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1122,14 +1122,21 @@ impl PathData { fn new( p_buf: PathBuf, file_type: Option>, + file_name: Option, config: &Config, command_line: bool, ) -> Self { - let name = p_buf - .file_name() - .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) - .to_string_lossy() - .into_owned(); + // We cannot use `Path::ends_with` or `Path::Components`, because they remove occurrences of '.' + // For '..', the filename is None + let name = if let Some(name) = file_name { + name + } else { + p_buf + .file_name() + .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) + .to_string_lossy() + .into_owned() + }; let must_dereference = match &config.dereference { Dereference::All => true, Dereference::Args => command_line, @@ -1192,7 +1199,7 @@ fn list(locs: Vec, config: Config) -> i32 { continue; } - let path_data = PathData::new(p, None, &config, true); + let path_data = PathData::new(p, None, None, &config, true); let show_dir_contents = if let Some(ft) = path_data.file_type() { !config.directory && ft.is_dir() @@ -1283,8 +1290,8 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) { let mut entries: Vec<_> = if config.files == Files::All { vec![ - PathData::new(dir.p_buf.join("."), None, config, false), - PathData::new(dir.p_buf.join(".."), None, config, false), + PathData::new(dir.p_buf.join("."), None, Some(".".into()), config, false), + PathData::new(dir.p_buf.join(".."), None, Some("..".into()), config, false), ] } else { vec![] @@ -1293,7 +1300,7 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) 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), Some(e.file_type()), config, false)) + .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false)) .collect(); sort_entries(&mut temp, config); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 2b8f311d1..0331d0214 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -43,23 +43,68 @@ fn test_ls_a() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch(".test-1"); + at.mkdir("some-dir"); + at.touch( + Path::new("some-dir") + .join(".test-2") + .as_os_str() + .to_str() + .unwrap(), + ); - let result = scene.ucmd().succeeds(); - let stdout = result.stdout_str(); - assert!(!stdout.contains(".test-1")); - assert!(!stdout.contains("..")); + let re_pwd = Regex::new(r"^\.\n").unwrap(); + + // Using the present working directory + scene + .ucmd() + .succeeds() + .stdout_does_not_contain(".test-1") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); scene .ucmd() .arg("-a") .succeeds() .stdout_contains(&".test-1") - .stdout_contains(&".."); + .stdout_contains(&"..") + .stdout_matches(&re_pwd); - let result = scene.ucmd().arg("-A").succeeds(); - result.stdout_contains(".test-1"); - let stdout = result.stdout_str(); - assert!(!stdout.contains("..")); + scene + .ucmd() + .arg("-A") + .succeeds() + .stdout_contains(".test-1") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); + + // Using a subdirectory + scene + .ucmd() + .arg("some-dir") + .succeeds() + .stdout_does_not_contain(".test-2") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); + + scene + .ucmd() + .arg("-a") + .arg("some-dir") + .succeeds() + .stdout_contains(&".test-2") + .stdout_contains(&"..") + .no_stderr() + .stdout_matches(&re_pwd); + + scene + .ucmd() + .arg("-A") + .arg("some-dir") + .succeeds() + .stdout_contains(".test-2") + .stdout_does_not_contain("..") + .stdout_does_not_match(&re_pwd); } #[test] From 361408cbe53e37e5b42a4d2b496cb03b2334f406 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 10:04:11 +0200 Subject: [PATCH 222/399] ls: remove case-insensitivity and leading period of name sort --- src/uu/ls/BENCHMARKING.md | 2 ++ src/uu/ls/src/ls.rs | 10 ++-------- tests/by-util/test_ls.rs | 3 +-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/uu/ls/BENCHMARKING.md b/src/uu/ls/BENCHMARKING.md index 852220496..b009b703a 100644 --- a/src/uu/ls/BENCHMARKING.md +++ b/src/uu/ls/BENCHMARKING.md @@ -36,6 +36,8 @@ args="$@" hyperfine "ls $args" "target/release/coreutils ls $args" ``` +**Note**: No localization is currently implemented. This means that the comparison above is not really fair. We can fix this by setting `LC_ALL=C`, so GNU `ls` can ignore localization. + ## Checking system call count - Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems. diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 4ebcc479c..2e8a5e5ed 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1244,14 +1244,8 @@ fn sort_entries(entries: &mut Vec, config: &Config) { 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_cached_key(|k| { - let has_dot: bool = k.file_name.starts_with('.'); - let filename_nodot: &str = &k.file_name[if has_dot { 1 } else { 0 }..]; - // We want hidden files to appear before regular files of the same - // name, so we need to negate the "has_dot" variable. - (filename_nodot.to_lowercase(), !has_dot) - }), - Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)), + Sort::Name => entries.sort_by(|a, b| a.file_name.cmp(&b.file_name)), + Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(&a.p_buf, &b.p_buf)), Sort::Extension => entries.sort_by(|a, b| { a.p_buf .extension() diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 0331d0214..4be24a99a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -527,7 +527,6 @@ fn test_ls_sort_name() { .succeeds() .stdout_is(["test-1", "test-2", "test-3\n"].join(sep)); - // Order of a named sort ignores leading dots. let scene_dot = TestScenario::new(util_name!()); let at = &scene_dot.fixtures; at.touch(".a"); @@ -540,7 +539,7 @@ fn test_ls_sort_name() { .arg("--sort=name") .arg("-A") .succeeds() - .stdout_is([".a", "a", ".b", "b\n"].join(sep)); + .stdout_is([".a", ".b", "a", "b\n"].join(sep)); } #[test] From 47a5dd0f9737cebe2859fc1ee5f9fff5e239f3e2 Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Sun, 2 May 2021 17:08:14 +0900 Subject: [PATCH 223/399] basename: move from getopts to clap (#2117) Use clap for argument parsing instead of getopts Also, make the following changes * Use `executable!()` macro to output the name of utility * Add another usage to help message --- src/uu/basename/Cargo.toml | 1 + src/uu/basename/src/basename.rs | 91 +++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 92d0ca4cd..0072619b7 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/basename.rs" [dependencies] +clap = "2.33.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 68b705d53..b54a69f44 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -10,80 +10,103 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use std::path::{is_separator, PathBuf}; use uucore::InvalidEncodingHandling; -static NAME: &str = "basename"; -static SYNTAX: &str = "NAME [SUFFIX]"; +static VERSION: &str = env!("CARGO_PKG_VERSION"); static SUMMARY: &str = "Print NAME with any leading directory components removed - If specified, also remove a trailing SUFFIX"; -static LONG_HELP: &str = ""; +If specified, also remove a trailing SUFFIX"; + +fn get_usage() -> String { + format!( + "{0} NAME [SUFFIX] + {0} OPTION... NAME...", + executable!() + ) +} + +pub mod options { + pub static MULTIPLE: &str = "multiple"; + pub static NAME: &str = "name"; + pub static SUFFIX: &str = "suffix"; + pub static ZERO: &str = "zero"; +} pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); + let usage = get_usage(); // // Argument parsing // - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optflag( - "a", - "multiple", - "Support more than one argument. Treat every argument as a name.", + let matches = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .usage(&usage[..]) + .arg( + Arg::with_name(options::MULTIPLE) + .short("a") + .long(options::MULTIPLE) + .help("support multiple arguments and treat each as a NAME"), ) - .optopt( - "s", - "suffix", - "Remove a trailing suffix. This option implies the -a option.", - "SUFFIX", + .arg(Arg::with_name(options::NAME).multiple(true).hidden(true)) + .arg( + Arg::with_name(options::SUFFIX) + .short("s") + .long(options::SUFFIX) + .value_name("SUFFIX") + .help("remove a trailing SUFFIX; implies -a"), ) - .optflag( - "z", - "zero", - "Output a zero byte (ASCII NUL) at the end of each line, rather than a newline.", + .arg( + Arg::with_name(options::ZERO) + .short("z") + .long(options::ZERO) + .help("end each output line with NUL, not newline"), ) - .parse(args); + .get_matches_from(args); // too few arguments - if matches.free.is_empty() { + if !matches.is_present(options::NAME) { crash!( 1, "{0}: {1}\nTry '{0} --help' for more information.", - NAME, + executable!(), "missing operand" ); } - let opt_s = matches.opt_present("s"); - let opt_a = matches.opt_present("a"); - let opt_z = matches.opt_present("z"); + + let opt_s = matches.is_present(options::SUFFIX); + let opt_a = matches.is_present(options::MULTIPLE); + let opt_z = matches.is_present(options::ZERO); let multiple_paths = opt_s || opt_a; // too many arguments - if !multiple_paths && matches.free.len() > 2 { + if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( 1, "{0}: extra operand '{1}'\nTry '{0} --help' for more information.", - NAME, - matches.free[2] + executable!(), + matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); } let suffix = if opt_s { - matches.opt_str("s").unwrap() - } else if !opt_a && matches.free.len() > 1 { - matches.free[1].clone() + matches.value_of(options::SUFFIX).unwrap() + } else if !opt_a && matches.occurrences_of(options::NAME) > 1 { + matches.values_of(options::NAME).unwrap().nth(1).unwrap() } else { - "".to_owned() + "" }; // // Main Program Processing // - let paths = if multiple_paths { - &matches.free[..] + let paths: Vec<_> = if multiple_paths { + matches.values_of(options::NAME).unwrap().collect() } else { - &matches.free[0..1] + matches.values_of(options::NAME).unwrap().take(1).collect() }; let line_ending = if opt_z { "\0" } else { "\n" }; From eb3206737b61dab810e7c115ac3fbd077f348702 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 10:20:14 +0200 Subject: [PATCH 224/399] ls: give '.' a file_type --- src/uu/ls/src/ls.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 2e8a5e5ed..482648e79 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1284,7 +1284,13 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool { fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) { let mut entries: Vec<_> = if config.files == Files::All { vec![ - PathData::new(dir.p_buf.join("."), None, Some(".".into()), config, false), + PathData::new( + dir.p_buf.clone(), + Some(Ok(*dir.file_type().unwrap())), + Some(".".into()), + config, + false, + ), PathData::new(dir.p_buf.join(".."), None, Some("..".into()), config, false), ] } else { From 09178360d8286a83e683c16ac18e7e622c1d92fb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 10:30:28 +0200 Subject: [PATCH 225/399] date: unneeded 'return' statement --- src/uu/date/src/date.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 43573437d..317fd72d4 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -348,7 +348,7 @@ fn set_system_datetime(_date: DateTime) -> i32 { #[cfg(target_os = "macos")] fn set_system_datetime(_date: DateTime) -> i32 { eprintln!("date: setting the date is not supported by macOS"); - return 1; + 1 } #[cfg(all(unix, not(target_os = "macos")))] From 9554710ab5d71ce9d42dd86d41e752eea607f4ec Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 2 May 2021 10:31:28 +0200 Subject: [PATCH 226/399] cat: the function 'unistd::write' doesn't need a mutable reference --- src/uu/cat/src/splice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index ccc625467..bd6be60f1 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -81,7 +81,7 @@ fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result< let mut buf = [0; BUF_SIZE]; loop { let read = unistd::read(read_fd, &mut buf[..left])?; - let written = unistd::write(write_fd, &mut buf[..read])?; + let written = unistd::write(write_fd, &buf[..read])?; left -= written; if left == 0 { break; From 34c22dc3ad359ebdd08ebbfcf6d3651c433de2df Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 12:15:16 +0200 Subject: [PATCH 227/399] tr: fix complement if set2 is range --- src/uu/tr/src/tr.rs | 2 +- tests/by-util/test_tr.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index b1143b4bb..68543229e 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -149,7 +149,7 @@ impl TranslateOperation { TranslateOperation { translate_map: map, complement, - s2_last: s2_prev, + s2_last: set2.last().unwrap_or(s2_prev), } } } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 064fbf779..f840dee62 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -86,6 +86,26 @@ fn test_complement3() { .stdout_is("3he3ca33a3d33he3ba3"); } +#[test] +fn test_complement4() { + // $ echo -n '0x1y2z3' | tr -c '0-@' '*-~' + // 0~1~2~3 + new_ucmd!() + .args(&["-c", "0-@", "*-~"]) + .pipe_in("0x1y2z3") + .run() + .stdout_is("0~1~2~3"); + + // TODO: fix this + // $ echo '0x1y2z3' | tr -c '\0-@' '*-~' + // 0a1b2c3 + // new_ucmd!() + // .args(&["-c", "\\0-@", "*-~"]) + // .pipe_in("0x1y2z3") + // .run() + // .stdout_is("0a1b2c3"); +} + #[test] fn test_squeeze() { new_ucmd!() From 1dccbfd21e2859b06008307e001a20c9516d0cf9 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sat, 1 May 2021 15:51:12 +0200 Subject: [PATCH 228/399] kill: migrate to clap Fixes #2122. --- Cargo.lock | 1 + src/uu/kill/Cargo.toml | 1 + src/uu/kill/src/kill.rs | 89 +++++++++++++++++++++++++++++------------ 3 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31787e626..05040dff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2041,6 +2041,7 @@ dependencies = [ name = "uu_kill" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 6b66806bc..e33411c70 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/kill.rs" [dependencies] +clap = "2.33" libc = "0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index fe925ce37..362f13f18 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -10,18 +10,26 @@ #[macro_use] extern crate uucore; +use clap::{App, Arg}; use libc::{c_int, pid_t}; use std::io::Error; use uucore::signals::ALL_SIGNALS; use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "[options] [...]"; -static SUMMARY: &str = ""; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Send signal to processes or list information about signals."; static EXIT_OK: i32 = 0; static EXIT_ERR: i32 = 1; +pub mod options { + pub static PIDS_OR_SIGNALS: &str = "pids_of_signals"; + pub static LIST: &str = "list"; + pub static TABLE: &str = "table"; + pub static TABLE_OLD: &str = "table_old"; + pub static SIGNAL: &str = "signal"; +} + #[derive(Clone, Copy)] pub enum Mode { Kill, @@ -33,41 +41,70 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let (args, obs_signal) = handle_obsolete(args); - let matches = app!(SYNTAX, SUMMARY, LONG_HELP) - .optopt("s", "signal", "specify the to be sent", "SIGNAL") - .optflagopt( - "l", - "list", - "list all signal names, or convert one to a name", - "LIST", - ) - .optflag("L", "table", "list all signal names in a nice table") - .parse(args); - let mode = if matches.opt_present("table") { + let usage = format!("{} [OPTIONS]... PID...", executable!()); + let matches = App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .usage(&usage[..]) + .arg( + Arg::with_name(options::LIST) + .short("l") + .long(options::LIST) + .help("Lists signals") + .conflicts_with(options::TABLE) + .conflicts_with(options::TABLE_OLD), + ) + .arg( + Arg::with_name(options::TABLE) + .short("t") + .long(options::TABLE) + .help("Lists table of signals"), + ) + .arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true)) + .arg( + Arg::with_name(options::SIGNAL) + .short("s") + .long(options::SIGNAL) + .help("Sends given signal") + .takes_value(true), + ) + .arg( + Arg::with_name(options::PIDS_OR_SIGNALS) + .hidden(true) + .multiple(true), + ) + .get_matches_from(args); + + let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { Mode::Table - } else if matches.opt_present("list") { + } else if matches.is_present(options::LIST) { Mode::List } else { Mode::Kill }; + let pids_or_signals: Vec = matches + .values_of(options::PIDS_OR_SIGNALS) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + match mode { Mode::Kill => { - return kill( - &matches - .opt_str("signal") - .unwrap_or_else(|| obs_signal.unwrap_or_else(|| "TERM".to_owned())), - matches.free, - ) + let sig = match (obs_signal, matches.value_of(options::SIGNAL)) { + (Some(s), Some(_)) => s, // -s takes precedence + (Some(s), None) => s, + (None, Some(s)) => s.to_owned(), + (None, None) => "TERM".to_owned(), + }; + return kill(&sig, &pids_or_signals); } Mode::Table => table(), - Mode::List => list(matches.opt_str("list")), + Mode::List => list(pids_or_signals.get(0).cloned()), } - 0 + EXIT_OK } fn handle_obsolete(mut args: Vec) -> (Vec, Option) { @@ -148,14 +185,14 @@ fn list(arg: Option) { }; } -fn kill(signalname: &str, pids: std::vec::Vec) -> i32 { +fn kill(signalname: &str, pids: &[String]) -> i32 { let mut status = 0; let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); let signal_value = match optional_signal_value { Some(x) => x, None => crash!(EXIT_ERR, "unknown signal name {}", signalname), }; - for pid in &pids { + for pid in pids { match pid.parse::() { Ok(x) => { if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { From 0e6b63b47bdf95c8458c717144ba1df9b1a66215 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Sun, 2 May 2021 18:32:34 +0800 Subject: [PATCH 229/399] Add tests to check link fails with 1 or 3 argument(s) --- tests/by-util/test_link.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/by-util/test_link.rs b/tests/by-util/test_link.rs index 381ea168a..99559a7fe 100644 --- a/tests/by-util/test_link.rs +++ b/tests/by-util/test_link.rs @@ -39,3 +39,25 @@ fn test_link_nonexistent_file() { assert!(!at.file_exists(file)); assert!(!at.file_exists(link)); } + +#[test] +fn test_link_one_argument() { + let (_, mut ucmd) = at_and_ucmd!(); + let file = "test_link_argument"; + ucmd.args(&[file]).fails().stderr_contains( + "error: The argument '...' requires at least 2 values, but only 1 was provide", + ); +} + +#[test] +fn test_link_three_arguments() { + let (_, mut ucmd) = at_and_ucmd!(); + let arguments = vec![ + "test_link_argument1", + "test_link_argument2", + "test_link_argument3", + ]; + ucmd.args(&arguments[..]).fails().stderr_contains( + format!("error: The value '{}' was provided to '...', but it wasn't expecting any more values", arguments[2]), + ); +} From 000bd73edceae756be09693d4f7db9ed5899210a Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 12:39:25 +0200 Subject: [PATCH 230/399] tr: fix merge conflict --- src/uu/tr/src/tr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 5e6c41a86..dcb64a127 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -182,7 +182,7 @@ impl TranslateAndSqueezeOperation { complement: bool, ) -> TranslateAndSqueezeOperation { TranslateAndSqueezeOperation { - translate: TranslateOperation::new(set1, set2, truncate), + translate: TranslateOperation::new(set1, set2, truncate, complement), squeeze: SqueezeOperation::new(set2_, complement), } } From acd30526a26ccfaf0a6860c913e552b1f7363729 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 13:53:11 +0200 Subject: [PATCH 231/399] tr: fix clippy warning --- src/uu/tr/src/tr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index dcb64a127..a44f2733c 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -217,8 +217,8 @@ fn translate_input( // Set `prev_c` to the post-translate character. This // allows the squeeze operation to correctly function // after the translate operation. - if res.is_some() { - prev_c = res.unwrap(); + if let Some(rc) = res { + prev_c = rc; } res }); From fba245b176d47b9e9d25975dd87f5d4babfaf5dd Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 2 May 2021 14:26:23 +0200 Subject: [PATCH 232/399] ls: add -1 to tests to separate names with \n on windows --- tests/by-util/test_ls.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 4be24a99a..65dd51c8b 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -57,6 +57,7 @@ fn test_ls_a() { // Using the present working directory scene .ucmd() + .arg("-1") .succeeds() .stdout_does_not_contain(".test-1") .stdout_does_not_contain("..") @@ -65,6 +66,7 @@ fn test_ls_a() { scene .ucmd() .arg("-a") + .arg("-1") .succeeds() .stdout_contains(&".test-1") .stdout_contains(&"..") @@ -73,6 +75,7 @@ fn test_ls_a() { scene .ucmd() .arg("-A") + .arg("-1") .succeeds() .stdout_contains(".test-1") .stdout_does_not_contain("..") @@ -81,6 +84,7 @@ fn test_ls_a() { // Using a subdirectory scene .ucmd() + .arg("-1") .arg("some-dir") .succeeds() .stdout_does_not_contain(".test-2") @@ -90,6 +94,7 @@ fn test_ls_a() { scene .ucmd() .arg("-a") + .arg("-1") .arg("some-dir") .succeeds() .stdout_contains(&".test-2") @@ -100,6 +105,7 @@ fn test_ls_a() { scene .ucmd() .arg("-A") + .arg("-1") .arg("some-dir") .succeeds() .stdout_contains(".test-2") From dc5bd9f0bed39fa659edd43ac7c670c71ebf53bc Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 2 May 2021 17:27:44 +0200 Subject: [PATCH 233/399] improve memory usage estimation --- src/uu/sort/src/sort.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index be7944a0f..7436b9fda 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -281,7 +281,9 @@ pub struct Line { impl Line { pub fn estimate_size(&self) -> usize { - self.line.capacity() + self.selections.capacity() * std::mem::size_of::() + self.line.capacity() + + self.selections.capacity() * std::mem::size_of::() + + std::mem::size_of::() } pub fn new(line: String, settings: &GlobalSettings) -> Self { From 8b35dd974141218987749cf3b60ba8f5a190c1a0 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 2 May 2021 17:27:52 +0200 Subject: [PATCH 234/399] add requested tests --- tests/by-util/test_sort.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index eac9490a5..4465e861f 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -37,7 +37,29 @@ fn test_larger_than_specified_segment() { .arg("50K") .arg("ext_sort.txt") .succeeds() - .stdout_is_fixture(format!("{}", "ext_sort.expected")); + .stdout_is_fixture("ext_sort.expected"); +} + +#[test] +fn test_smaller_than_specified_segment() { + new_ucmd!() + .arg("-n") + .arg("-S") + .arg("100M") + .arg("ext_sort.txt") + .succeeds() + .stdout_is_fixture("ext_sort.expected"); +} + +#[test] +fn test_extsort_zero_terminated() { + new_ucmd!() + .arg("-z") + .arg("-S") + .arg("10K") + .arg("zero-terminated.txt") + .succeeds() + .stdout_is_fixture("zero-terminated.expected"); } #[test] From 3fcac152f8ef1e57de96bc999b2104da1a94ca91 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 2 May 2021 18:35:52 +0200 Subject: [PATCH 235/399] tr: add test --- tests/by-util/test_tr.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index a035faae7..29712b44a 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -95,15 +95,18 @@ fn test_complement4() { .pipe_in("0x1y2z3") .run() .stdout_is("0~1~2~3"); +} - // TODO: fix this +#[test] +#[ignore = "fixme: GNU tr returns '0a1b2c3' instead of '0~1~2~3', see #2158"] +fn test_complement5() { // $ echo '0x1y2z3' | tr -c '\0-@' '*-~' // 0a1b2c3 - // new_ucmd!() - // .args(&["-c", "\\0-@", "*-~"]) - // .pipe_in("0x1y2z3") - // .run() - // .stdout_is("0a1b2c3"); + new_ucmd!() + .args(&["-c", "\\0-@", "*-~"]) + .pipe_in("0x1y2z3") + .run() + .stdout_is("0a1b2c3"); } #[test] From 0a3e2216d72b925d6e47237c37f5e233a656c358 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 2 May 2021 16:29:34 -0400 Subject: [PATCH 236/399] wc: add lines() method for iterating over lines Add the `WordCountable::lines()` method that returns an iterator over lines of a file-like object. This mirrors the `std::io::BufRead::lines()` method, with some minor differences due to the particular use case of `wc`. This commit also creates a new module, `countable.rs`, to contain the `WordCountable` trait and the new `Lines` struct returned by `lines()`. --- src/uu/wc/src/countable.rs | 72 ++++++++++++++++++++++++++++++++++++++ src/uu/wc/src/wc.rs | 55 +++++------------------------ 2 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 src/uu/wc/src/countable.rs diff --git a/src/uu/wc/src/countable.rs b/src/uu/wc/src/countable.rs new file mode 100644 index 000000000..3da910a03 --- /dev/null +++ b/src/uu/wc/src/countable.rs @@ -0,0 +1,72 @@ +//! Traits and implementations for iterating over lines in a file-like object. +//! +//! This module provides a [`WordCountable`] trait and implementations +//! for some common file-like objects. Use the [`WordCountable::lines`] +//! method to get an iterator over lines of a file-like object. +use std::fs::File; +use std::io::{self, BufRead, BufReader, Read, StdinLock}; + +#[cfg(unix)] +use std::os::unix::io::AsRawFd; + +#[cfg(unix)] +pub trait WordCountable: AsRawFd + Read { + type Buffered: BufRead; + fn lines(self) -> Lines; +} + +#[cfg(not(unix))] +pub trait WordCountable: Read { + type Buffered: BufRead; + fn lines(self) -> Lines; +} + +impl WordCountable for StdinLock<'_> { + type Buffered = Self; + + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { buf: self } + } +} +impl WordCountable for File { + type Buffered = BufReader; + + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { + buf: BufReader::new(self), + } + } +} + +/// An iterator over the lines of an instance of `BufRead`. +/// +/// Similar to [`io::Lines`] but yields each line as a `Vec` and +/// includes the newline character (`\n`, the `0xA` byte) that +/// terminates the line. +/// +/// [`io::Lines`]:: io::Lines +pub struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = io::Result>; + + fn next(&mut self) -> Option { + let mut line = Vec::new(); + + // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. + // hence the option wrapped in a result here + match self.buf.read_until(b'\n', &mut line) { + Ok(0) => None, + Ok(_n) => Some(Ok(line)), + Err(e) => Some(Err(e)), + } + } +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 59ca10141..3b70856fa 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -11,17 +11,17 @@ extern crate uucore; mod count_bytes; +mod countable; use count_bytes::count_bytes_fast; +use countable::WordCountable; use clap::{App, Arg, ArgMatches}; use thiserror::Error; use std::cmp::max; use std::fs::File; -use std::io::{self, BufRead, BufReader, Read, StdinLock, Write}; +use std::io::{self, Write}; use std::ops::{Add, AddAssign}; -#[cfg(unix)] -use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::from_utf8; @@ -82,32 +82,6 @@ impl Settings { } } -#[cfg(unix)] -trait WordCountable: AsRawFd + Read { - type Buffered: BufRead; - fn get_buffered(self) -> Self::Buffered; -} -#[cfg(not(unix))] -trait WordCountable: Read { - type Buffered: BufRead; - fn get_buffered(self) -> Self::Buffered; -} - -impl WordCountable for StdinLock<'_> { - type Buffered = Self; - - fn get_buffered(self) -> Self::Buffered { - self - } -} -impl WordCountable for File { - type Buffered = BufReader; - - fn get_buffered(self) -> Self::Buffered { - BufReader::new(self) - } -} - #[derive(Debug, Default, Copy, Clone)] struct WordCount { bytes: usize, @@ -270,25 +244,16 @@ fn word_count_from_reader( let mut byte_count: usize = 0; let mut char_count: usize = 0; let mut longest_line_length: usize = 0; - let mut raw_line = Vec::new(); let mut ends_lf: bool; // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. // hence the option wrapped in a result here - let mut buffered_reader = reader.get_buffered(); - loop { - match buffered_reader.read_until(LF, &mut raw_line) { - Ok(n) => { - if n == 0 { - break; - } - } - Err(ref e) => { - if !raw_line.is_empty() { - show_warning!("Error while reading {}: {}", path, e); - } else { - break; - } + for line_result in reader.lines() { + let raw_line = match line_result { + Ok(l) => l, + Err(e) => { + show_warning!("Error while reading {}: {}", path, e); + continue; } }; @@ -317,8 +282,6 @@ fn word_count_from_reader( longest_line_length = current_char_count - (ends_lf as usize); } } - - raw_line.truncate(0); } Ok(WordCount { From 219fc48487c2095958bf46f70adf6da0436a4fd4 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Mon, 3 May 2021 08:42:23 +0100 Subject: [PATCH 237/399] compile_table: adding mac M1 to report. --- README.md | 1 + docs/compiles_table.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12dfb5609..95dc036fd 100644 --- a/README.md +++ b/README.md @@ -429,6 +429,7 @@ This is an auto-generated table showing which binaries compile for each target-t |windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| |windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y| |windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| +|apple MacOS|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y| diff --git a/docs/compiles_table.py b/docs/compiles_table.py index 0cbfdf0e9..a051287c9 100644 --- a/docs/compiles_table.py +++ b/docs/compiles_table.py @@ -26,6 +26,7 @@ TARGETS = [ "x86_64-pc-windows-gnu", "x86_64-pc-windows-msvc", # Apple + "aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-apple-ios", "x86_64-apple-ios", @@ -231,4 +232,4 @@ if __name__ == "__main__": prev_table, _, _ = load_csv(CACHE_PATH) new_table = merge_tables(prev_table, table) - save_csv(CACHE_PATH, new_table) \ No newline at end of file + save_csv(CACHE_PATH, new_table) From 91c736bd95bc8eafdff1ff428bc68e3ed76fa34c Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Mon, 3 May 2021 23:22:00 +0900 Subject: [PATCH 238/399] tests/basename: add tests for error messages --- tests/by-util/test_basename.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 6c7a3ecae..2a40ba4b9 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -104,11 +104,25 @@ fn test_no_args() { expect_error(vec![]); } +#[test] +fn test_no_args_output() { + new_ucmd!() + .fails() + .stderr_is("basename: error: missing operand\nTry 'basename --help' for more information."); +} + #[test] fn test_too_many_args() { expect_error(vec!["a", "b", "c"]); } +#[test] +fn test_too_many_args_output() { + new_ucmd!().args(&["a", "b", "c"]).fails().stderr_is( + "basename: error: extra operand 'c'\nTry 'basename --help' for more information.", + ); +} + fn test_invalid_utf8_args(os_str: &OsStr) { let test_vec = vec![os_str.to_os_string()]; new_ucmd!().args(&test_vec).succeeds().stdout_is("fo�o\n"); From 74802f9f0fca4579315ce6f7097ff131534d211f Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Mon, 3 May 2021 23:26:46 +0900 Subject: [PATCH 239/399] basename: improve error messages Remove duplicated utility name from error messages --- src/uu/basename/src/basename.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index b54a69f44..d5512863f 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::NAME) { crash!( 1, - "{0}: {1}\nTry '{0} --help' for more information.", + "{1}\nTry '{0} --help' for more information.", executable!(), "missing operand" ); @@ -85,7 +85,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( 1, - "{0}: extra operand '{1}'\nTry '{0} --help' for more information.", + "extra operand '{1}'\nTry '{0} --help' for more information.", executable!(), matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); From 5a4bb610ffec547fdac3f749a17f46b572f62962 Mon Sep 17 00:00:00 2001 From: bashi8128 Date: Mon, 3 May 2021 23:32:01 +0900 Subject: [PATCH 240/399] basename: rename variable names Rename variable names to be more explicit ones --- src/uu/basename/src/basename.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index d5512863f..c20561b30 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -77,10 +77,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - let opt_s = matches.is_present(options::SUFFIX); - let opt_a = matches.is_present(options::MULTIPLE); - let opt_z = matches.is_present(options::ZERO); - let multiple_paths = opt_s || opt_a; + let opt_suffix = matches.is_present(options::SUFFIX); + let opt_multiple = matches.is_present(options::MULTIPLE); + let opt_zero = matches.is_present(options::ZERO); + let multiple_paths = opt_suffix || opt_multiple; // too many arguments if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( @@ -91,9 +91,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); } - let suffix = if opt_s { + let suffix = if opt_suffix { matches.value_of(options::SUFFIX).unwrap() - } else if !opt_a && matches.occurrences_of(options::NAME) > 1 { + } else if !opt_multiple && matches.occurrences_of(options::NAME) > 1 { matches.values_of(options::NAME).unwrap().nth(1).unwrap() } else { "" @@ -109,7 +109,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { matches.values_of(options::NAME).unwrap().take(1).collect() }; - let line_ending = if opt_z { "\0" } else { "\n" }; + let line_ending = if opt_zero { "\0" } else { "\n" }; for path in paths { print!("{}{}", basename(&path, &suffix), line_ending); } From 224c8b3f9469e3f9030dc5d076b4e398f2df694b Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Mon, 3 May 2021 09:55:17 +0100 Subject: [PATCH 241/399] df output update (non inode mode) proposal specific for mac. on this platform, capacity column is also displayed. --- src/uu/df/src/df.rs | 8 ++++++++ tests/by-util/test_df.rs | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index e898b187c..c917eb2e8 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -916,6 +916,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "Use%", ] }); + if cfg!(target_os = "macos") && !opt.show_inode_instead { + header.insert(header.len() - 1, "Capacity"); + } header.push("Mounted on"); for (idx, title) in header.iter().enumerate() { @@ -970,6 +973,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { "{0: >12} ", human_readable(free_size, opt.human_readable_base) ); + if cfg!(target_os = "macos") { + let used = fs.usage.blocks - fs.usage.bfree; + let blocks = used + fs.usage.bavail; + print!("{0: >12} ", use_size(used, blocks)); + } print!("{0: >5} ", use_size(free_size, total_size)); } print!("{0: <16}", fs.mountinfo.mount_dir); diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 0ae8d2339..e3b7141d1 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -20,4 +20,16 @@ fn test_df_compatible_si() { new_ucmd!().arg("-aH").succeeds(); } +#[test] +fn test_df_output() { + if cfg!(target_os = "macos") { + new_ucmd!().arg("-H").arg("-total").succeeds(). + stdout_only("Filesystem Size Used Available Capacity Use% Mounted on \n"); + } else { + new_ucmd!().arg("-H").arg("-total").succeeds().stdout_only( + "Filesystem Size Used Available Use% Mounted on \n" + ); + } +} + // ToDO: more tests... From 56761ba584112c84f71523b2a3ef101bbc14ae49 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 3 May 2021 22:30:56 +0200 Subject: [PATCH 242/399] stat: implement support for macos --- src/uu/stat/Cargo.toml | 1 + src/uu/stat/src/fsext.rs | 187 +++++++++++++++++++++++++++++++++++++ src/uu/stat/src/stat.rs | 13 +-- tests/by-util/test_stat.rs | 14 +++ 4 files changed, 205 insertions(+), 10 deletions(-) diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 96bf63ffe..43c5432f8 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -17,6 +17,7 @@ path = "src/stat.rs" [dependencies] clap = "2.33" time = "0.1.40" +libc = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/stat/src/fsext.rs b/src/uu/stat/src/fsext.rs index d90099892..4e949047d 100644 --- a/src/uu/stat/src/fsext.rs +++ b/src/uu/stat/src/fsext.rs @@ -9,6 +9,12 @@ extern crate time; +#[cfg(target_os = "linux")] +static LINUX_MTAB: &str = "/etc/mtab"; +#[cfg(target_os = "linux")] +static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo"; +static MOUNT_OPT_BIND: &str = "bind"; + use self::time::Timespec; use std::time::UNIX_EPOCH; pub use uucore::libc::{ @@ -413,3 +419,184 @@ pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { other => format!("UNKNOWN ({:#x})", other).into(), } } + +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +extern "C" { + #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] + #[link_name = "getmntinfo$INODE64"] + fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int; + + #[cfg(any( + all(target_os = "freebsd"), + all(target_vendor = "apple", target_arch = "aarch64") + ))] + fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int; +} + +#[derive(Debug, Clone)] +pub struct MountInfo { + // it stores `volume_name` in windows platform and `dev_id` in unix platform + dev_id: String, + dev_name: String, + fs_type: String, + pub mount_dir: String, + mount_option: String, // we only care "bind" option + mount_root: String, + remote: bool, + dummy: bool, +} + +impl MountInfo { + fn set_missing_fields(&mut self) { + #[cfg(unix)] + { + // We want to keep the dev_id on Windows + // but set dev_id + let path = CString::new(self.mount_dir.clone()).unwrap(); + unsafe { + let mut stat = mem::zeroed(); + if libc::stat(path.as_ptr(), &mut stat) == 0 { + self.dev_id = (stat.st_dev as i32).to_string(); + } else { + self.dev_id = "".to_string(); + } + } + } + // set MountInfo::dummy + match self.fs_type.as_ref() { + "autofs" | "proc" | "subfs" + /* for Linux 2.6/3.x */ + | "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs" + /* FreeBSD, Linux 2.4 */ + | "devfs" + /* for NetBSD 3.0 */ + | "kernfs" + /* for Irix 6.5 */ + | "ignore" => self.dummy = true, + _ => self.dummy = self.fs_type == "none" + && self.mount_option.find(MOUNT_OPT_BIND).is_none(), + } + // set MountInfo::remote + #[cfg(unix)] + { + if self.dev_name.find(':').is_some() + || (self.dev_name.starts_with("//") && self.fs_type == "smbfs" + || self.fs_type == "cifs") + || self.dev_name == "-hosts" + { + self.remote = true; + } else { + self.remote = false; + } + } + } + + #[cfg(target_os = "linux")] + fn new(file_name: &str, raw: Vec<&str>) -> Option { + match file_name { + // Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + // "man proc" for more details + "/proc/self/mountinfo" => { + let mut m = MountInfo { + dev_id: "".to_string(), + dev_name: raw[9].to_string(), + fs_type: raw[8].to_string(), + mount_root: raw[3].to_string(), + mount_dir: raw[4].to_string(), + mount_option: raw[5].to_string(), + remote: false, + dummy: false, + }; + m.set_missing_fields(); + Some(m) + } + "/etc/mtab" => { + let mut m = MountInfo { + dev_id: "".to_string(), + dev_name: raw[0].to_string(), + fs_type: raw[2].to_string(), + mount_root: "".to_string(), + mount_dir: raw[1].to_string(), + mount_option: raw[3].to_string(), + remote: false, + dummy: false, + }; + m.set_missing_fields(); + Some(m) + } + _ => None, + } + } +} + +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +use std::ffi::CStr; +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +impl From for MountInfo { + fn from(statfs: Sstatfs) -> Self { + let mut info = MountInfo { + dev_id: "".to_string(), + dev_name: unsafe { + CStr::from_ptr(&statfs.f_mntfromname[0]) + .to_string_lossy() + .into_owned() + }, + fs_type: unsafe { + CStr::from_ptr(&statfs.f_fstypename[0]) + .to_string_lossy() + .into_owned() + }, + mount_dir: unsafe { + CStr::from_ptr(&statfs.f_mntonname[0]) + .to_string_lossy() + .into_owned() + }, + mount_root: "".to_string(), + mount_option: "".to_string(), + remote: false, + dummy: false, + }; + info.set_missing_fields(); + info + } +} + +#[cfg(target_os = "linux")] +use std::fs::File; +#[cfg(target_os = "linux")] +use std::io::{BufRead, BufReader}; +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +use std::ptr; +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +use std::slice; +pub fn read_fs_list() -> Vec { + #[cfg(target_os = "linux")] + { + let (file_name, fobj) = File::open(LINUX_MOUNTINFO) + .map(|f| (LINUX_MOUNTINFO, f)) + .or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f))) + .expect("failed to find mount list files"); + let reader = BufReader::new(fobj); + reader + .lines() + .filter_map(|line| line.ok()) + .filter_map(|line| { + let raw_data = line.split_whitespace().collect::>(); + MountInfo::new(file_name, raw_data) + }) + .collect::>() + } + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + { + let mut mptr: *mut Sstatfs = ptr::null_mut(); + let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) }; + if len < 0 { + crash!(1, "getmntinfo failed"); + } + let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; + mounts + .iter() + .map(|m| MountInfo::from(*m)) + .collect::>() + } +} diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 5216fb293..dab5f6d97 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -18,8 +18,6 @@ use uucore::entries; use clap::{App, Arg, ArgMatches}; use std::borrow::Cow; use std::convert::AsRef; -use std::fs::File; -use std::io::{BufRead, BufReader}; use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::path::Path; use std::{cmp, fs, iter}; @@ -97,7 +95,6 @@ pub mod options { static ARG_FILES: &str = "files"; -const MOUNT_INFO: &str = "/etc/mtab"; pub const F_ALTER: u8 = 1; pub const F_ZERO: u8 = 1 << 1; pub const F_LEFT: u8 = 1 << 2; @@ -490,13 +487,9 @@ impl Stater { // mount points aren't displayed when showing filesystem information None } else { - let reader = BufReader::new( - File::open(MOUNT_INFO).unwrap_or_else(|_| panic!("Failed to read {}", MOUNT_INFO)), - ); - let mut mount_list = reader - .lines() - .filter_map(Result::ok) - .filter_map(|line| line.split_whitespace().nth(1).map(ToOwned::to_owned)) + let mut mount_list = read_fs_list() + .iter() + .map(|mi| mi.mount_dir.clone()) .collect::>(); // Reverse sort. The longer comes first. mount_list.sort(); diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 60d735c51..0069d2f0d 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -317,6 +317,20 @@ fn test_multi_files() { .stdout_is(expected_result(&args)); } +#[cfg(any(target_os = "linux", target_os = "freebsd", target_vendor = "apple"))] +#[test] +fn test_one_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "TEST_FILE.mp4"; + at.touch(file); + + ucmd.arg(file) + .succeeds() + .stdout_contains(format!("File: `{}'", file)) + .stdout_contains(format!("Size: 0")) + .stdout_contains(format!("Access: (0644/-rw-r--r--)")); +} + #[test] #[cfg(target_os = "linux")] fn test_printf() { From 5bcfa88f0ab75c32b5278150531b8affdce53f94 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 3 May 2021 23:09:45 +0200 Subject: [PATCH 243/399] stat: fix test to ignore selinux related output --- tests/by-util/test_stat.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 60d735c51..7b7e990f4 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -198,9 +198,16 @@ fn test_terse_normal_format() { let expect = expected_result(&args); println!("actual: {:?}", actual); println!("expect: {:?}", expect); - let v_actual: Vec<&str> = actual.split(' ').collect(); - let v_expect: Vec<&str> = expect.split(' ').collect(); + let v_actual: Vec<&str> = actual.trim().split(' ').collect(); + let mut v_expect: Vec<&str> = expect.trim().split(' ').collect(); assert!(!v_expect.is_empty()); + + // uu_stat does not support selinux + if v_actual.len() == v_expect.len() - 1 && v_expect[v_expect.len() - 1].contains(":") { + // assume last element contains: `SELinux security context string` + v_expect.pop(); + } + // * allow for inequality if `stat` (aka, expect) returns "0" (unknown value) assert!( expect == "0" From 0cafe2b70d463ec7c5a8ec2be6a42cdfd37e58f6 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 3 May 2021 20:52:32 -0400 Subject: [PATCH 244/399] wc: add tests for edge cases for wc on files --- tests/by-util/test_wc.rs | 57 ++++++++++++++ tests/fixtures/wc/emptyfile.txt | 0 tests/fixtures/wc/manyemptylines.txt | 100 ++++++++++++++++++++++++ tests/fixtures/wc/notrailingnewline.txt | 1 + tests/fixtures/wc/onelongemptyline.txt | 1 + tests/fixtures/wc/onelongword.txt | 1 + 6 files changed, 160 insertions(+) create mode 100644 tests/fixtures/wc/emptyfile.txt create mode 100644 tests/fixtures/wc/manyemptylines.txt create mode 100644 tests/fixtures/wc/notrailingnewline.txt create mode 100644 tests/fixtures/wc/onelongemptyline.txt create mode 100644 tests/fixtures/wc/onelongword.txt diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 075878470..a16f1854e 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -112,3 +112,60 @@ fn test_multiple_default() { alice_in_wonderland.txt\n 36 370 2189 total\n", ); } + +/// Test for an empty file. +#[test] +fn test_file_empty() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "emptyfile.txt"]) + .run() + .stdout_is(" 0 0 0 0 0 emptyfile.txt\n"); +} + +/// Test for an file containing a single non-whitespace character +/// *without* a trailing newline. +#[test] +fn test_file_single_line_no_trailing_newline() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "notrailingnewline.txt"]) + .run() + .stdout_is(" 1 1 2 2 1 notrailingnewline.txt\n"); +} + +/// Test for a file that has 100 empty lines (that is, the contents of +/// the file are the newline character repeated one hundred times). +#[test] +fn test_file_many_empty_lines() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "manyemptylines.txt"]) + .run() + .stdout_is(" 100 0 100 100 0 manyemptylines.txt\n"); +} + +/// Test for a file that has one long line comprising only spaces. +#[test] +fn test_file_one_long_line_only_spaces() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "onelongemptyline.txt"]) + .run() + .stdout_is(" 1 0 10001 10001 10000 onelongemptyline.txt\n"); +} + +/// Test for a file that has one long line comprising a single "word". +#[test] +fn test_file_one_long_word() { + // TODO There is a leading space in the output that should be + // removed; see issue #2173. + new_ucmd!() + .args(&["-clmwL", "onelongword.txt"]) + .run() + .stdout_is(" 1 1 10001 10001 10000 onelongword.txt\n"); +} diff --git a/tests/fixtures/wc/emptyfile.txt b/tests/fixtures/wc/emptyfile.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/wc/manyemptylines.txt b/tests/fixtures/wc/manyemptylines.txt new file mode 100644 index 000000000..716f02896 --- /dev/null +++ b/tests/fixtures/wc/manyemptylines.txt @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/wc/notrailingnewline.txt b/tests/fixtures/wc/notrailingnewline.txt new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/tests/fixtures/wc/notrailingnewline.txt @@ -0,0 +1 @@ +a diff --git a/tests/fixtures/wc/onelongemptyline.txt b/tests/fixtures/wc/onelongemptyline.txt new file mode 100644 index 000000000..f93ac3c2c --- /dev/null +++ b/tests/fixtures/wc/onelongemptyline.txt @@ -0,0 +1 @@ + diff --git a/tests/fixtures/wc/onelongword.txt b/tests/fixtures/wc/onelongword.txt new file mode 100644 index 000000000..9d693a7ca --- /dev/null +++ b/tests/fixtures/wc/onelongword.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa From 6dff9f00a35d2893033330cb8f281357d6904fd9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 4 May 2021 10:02:40 +0200 Subject: [PATCH 245/399] refresh cargo.lock with recent updates --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a33ab099..8d740bb9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,9 +1483,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad184cc9470f9117b2ac6817bfe297307418819ba40552f9b3846f05c33d5373" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote 1.0.9", From 231bb7be93639576bdc553ae7a6fa2f7f5568ddc Mon Sep 17 00:00:00 2001 From: rethab Date: Wed, 5 May 2021 22:59:40 +0200 Subject: [PATCH 246/399] Migrate mknod to clap, closes #2051 (#2056) * mknod: add tests for fifo * mknod: add test for character device --- .gitignore | 1 + Cargo.lock | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mknod/src/mknod.rs | 304 +++++++++++++++------------- src/uu/mknod/src/parsemode.rs | 54 +++++ src/uucore/src/lib/features/mode.rs | 33 ++- tests/by-util/test_mknod.rs | 125 +++++++++++- tests/common/util.rs | 16 +- 8 files changed, 371 insertions(+), 166 deletions(-) create mode 100644 src/uu/mknod/src/parsemode.rs diff --git a/.gitignore b/.gitignore index b1ac52506..11f46e13e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ target/ Cargo.lock lib*.a /docs/_build +*.iml diff --git a/Cargo.lock b/Cargo.lock index 6ff3cd5c1..62fa80c2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2116,7 +2116,7 @@ dependencies = [ name = "uu_mknod" version = "0.0.6" dependencies = [ - "getopts", + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 2c3ac8fb9..1320e3546 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -16,7 +16,7 @@ name = "uu_mknod" path = "src/mknod.rs" [dependencies] -getopts = "0.2.18" +clap = "2.33" libc = "^0.2.42" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index fc6fb0870..5b6c2fa8c 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -5,21 +5,41 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO +// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO #[macro_use] extern crate uucore; +use std::ffi::CString; + +use clap::{App, Arg, ArgMatches}; use libc::{dev_t, mode_t}; use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; -use getopts::Options; - -use std::ffi::CString; use uucore::InvalidEncodingHandling; static NAME: &str = "mknod"; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Create the special file NAME of the given TYPE."; +static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]"; +static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too. +-m, --mode=MODE set file permission bits to MODE, not a=rw - umask +--help display this help and exit +--version output version information and exit + +Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they +must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, +it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; +otherwise, as decimal. TYPE may be: + +b create a block (buffered) special file +c, u create a character (unbuffered) special file +p create a FIFO + +NOTE: your shell may have its own version of mknod, which usually supersedes +the version described here. Please refer to your shell's documentation +for details about the options it supports. +"; const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -30,13 +50,35 @@ fn makedev(maj: u64, min: u64) -> dev_t { } #[cfg(windows)] -fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { +fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { panic!("Unsupported for windows platform") } #[cfg(unix)] -fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { - unsafe { libc::mknod(path.as_ptr(), mode, dev) } +fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { + let c_str = CString::new(file_name).expect("Failed to convert to CString"); + + // the user supplied a mode + let set_umask = mode & MODE_RW_UGO != MODE_RW_UGO; + + unsafe { + // store prev umask + let last_umask = if set_umask { libc::umask(0) } else { 0 }; + + let errno = libc::mknod(c_str.as_ptr(), mode, dev); + + // set umask back to original value + if set_umask { + libc::umask(last_umask); + } + + if errno == -1 { + let c_str = CString::new(NAME).expect("Failed to convert to CString"); + // shows the error from the mknod syscall + libc::perror(c_str.as_ptr()); + } + errno + } } #[allow(clippy::cognitive_complexity)] @@ -44,156 +86,136 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - - let mut opts = Options::new(); - // Linux-specific options, not implemented // opts.optflag("Z", "", "set the SELinux security context to default type"); // opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX"); - opts.optopt( - "m", - "mode", - "set file permission bits to MODE, not a=rw - umask", - "MODE", - ); - opts.optflag("", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + let matches = App::new(executable!()) + .version(VERSION) + .usage(USAGE) + .after_help(LONG_HELP) + .about(ABOUT) + .arg( + Arg::with_name("mode") + .short("m") + .long("mode") + .value_name("MODE") + .help("set file permission bits to MODE, not a=rw - umask"), + ) + .arg( + Arg::with_name("name") + .value_name("NAME") + .help("name of the new file") + .required(true) + .index(1), + ) + .arg( + Arg::with_name("type") + .value_name("TYPE") + .help("type of the new file (b, c, u or p)") + .required(true) + .validator(valid_type) + .index(2), + ) + .arg( + Arg::with_name("major") + .value_name("MAJOR") + .help("major file type") + .validator(valid_u64) + .index(3), + ) + .arg( + Arg::with_name("minor") + .value_name("MINOR") + .help("minor file type") + .validator(valid_u64) + .index(4), + ) + .get_matches_from(args); - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME), + let mode = match get_mode(&matches) { + Ok(mode) => mode, + Err(err) => { + show_info!("{}", err); + return 1; + } }; - if matches.opt_present("help") { - println!( - "Usage: {0} [OPTION]... NAME TYPE [MAJOR MINOR] + let file_name = matches.value_of("name").expect("Missing argument 'NAME'"); -Mandatory arguments to long options are mandatory for short options too. - -m, --mode=MODE set file permission bits to MODE, not a=rw - umask - --help display this help and exit - --version output version information and exit + // Only check the first character, to allow mnemonic usage like + // 'mknod /dev/rst0 character 18 0'. + let ch = matches + .value_of("type") + .expect("Missing argument 'TYPE'") + .chars() + .next() + .expect("Failed to get the first char"); -Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they -must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, -it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; -otherwise, as decimal. TYPE may be: - - b create a block (buffered) special file - c, u create a character (unbuffered) special file - p create a FIFO - -NOTE: your shell may have its own version of mknod, which usually supersedes -the version described here. Please refer to your shell's documentation -for details about the options it supports.", - NAME - ); - return 0; - } - - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); - return 0; - } - - let mut last_umask: mode_t = 0; - let mut newmode: mode_t = MODE_RW_UGO; - if matches.opt_present("mode") { - match uucore::mode::parse_mode(matches.opt_str("mode")) { - Ok(parsed) => { - if parsed > 0o777 { - show_info!("mode must specify only file permission bits"); - return 1; - } - newmode = parsed; - } - Err(e) => { - show_info!("{}", e); - return 1; - } + if ch == 'p' { + if matches.is_present("major") || matches.is_present("minor") { + eprintln!("Fifos do not have major and minor device numbers."); + eprintln!("Try '{} --help' for more information.", NAME); + 1 + } else { + _makenod(file_name, S_IFIFO | mode, 0) } - unsafe { - last_umask = libc::umask(0); - } - } + } else { + match (matches.value_of("major"), matches.value_of("minor")) { + (None, None) | (_, None) | (None, _) => { + eprintln!("Special files require major and minor device numbers."); + eprintln!("Try '{} --help' for more information.", NAME); + 1 + } + (Some(major), Some(minor)) => { + let major = major.parse::().expect("validated by clap"); + let minor = minor.parse::().expect("validated by clap"); - let mut ret = 0i32; - match matches.free.len() { - 0 => show_usage_error!("missing operand"), - 1 => show_usage_error!("missing operand after ‘{}’", matches.free[0]), - _ => { - let args = &matches.free; - let c_str = CString::new(args[0].as_str()).expect("Failed to convert to CString"); - - // Only check the first character, to allow mnemonic usage like - // 'mknod /dev/rst0 character 18 0'. - let ch = args[1] - .chars() - .next() - .expect("Failed to get the first char"); - - if ch == 'p' { - if args.len() > 2 { - show_info!("{}: extra operand ‘{}’", NAME, args[2]); - if args.len() == 4 { - eprintln!("Fifos do not have major and minor device numbers."); - } - eprintln!("Try '{} --help' for more information.", NAME); - return 1; - } - - ret = _makenod(c_str, S_IFIFO | newmode, 0); - } else { - if args.len() < 4 { - show_info!("missing operand after ‘{}’", args[args.len() - 1]); - if args.len() == 2 { - eprintln!("Special files require major and minor device numbers."); - } - eprintln!("Try '{} --help' for more information.", NAME); - return 1; - } else if args.len() > 4 { - show_usage_error!("extra operand ‘{}’", args[4]); - return 1; - } else if !"bcu".contains(ch) { - show_usage_error!("invalid device type ‘{}’", args[1]); - return 1; - } - - let maj = args[2].parse::(); - let min = args[3].parse::(); - if maj.is_err() { - show_info!("invalid major device number ‘{}’", args[2]); - return 1; - } else if min.is_err() { - show_info!("invalid minor device number ‘{}’", args[3]); - return 1; - } - - let (maj, min) = (maj.unwrap(), min.unwrap()); - let dev = makedev(maj, min); + let dev = makedev(major, minor); if ch == 'b' { // block special file - ret = _makenod(c_str, S_IFBLK | newmode, dev); - } else { + _makenod(file_name, S_IFBLK | mode, dev) + } else if ch == 'c' || ch == 'u' { // char special file - ret = _makenod(c_str, S_IFCHR | newmode, dev); + _makenod(file_name, S_IFCHR | mode, dev) + } else { + unreachable!("{} was validated to be only b, c or u", ch); } } } } - - if last_umask != 0 { - unsafe { - libc::umask(last_umask); - } - } - if ret == -1 { - let c_str = CString::new(format!("{}: {}", NAME, matches.free[0]).as_str()) - .expect("Failed to convert to CString"); - unsafe { - libc::perror(c_str.as_ptr()); - } - } - - ret +} + +fn get_mode(matches: &ArgMatches) -> Result { + match matches.value_of("mode") { + None => Ok(MODE_RW_UGO), + Some(str_mode) => uucore::mode::parse_mode(str_mode) + .map_err(|e| format!("invalid mode ({})", e)) + .and_then(|mode| { + if mode > 0o777 { + Err("mode must specify only file permission bits".to_string()) + } else { + Ok(mode) + } + }), + } +} + +fn valid_type(tpe: String) -> Result<(), String> { + // Only check the first character, to allow mnemonic usage like + // 'mknod /dev/rst0 character 18 0'. + tpe.chars() + .next() + .ok_or_else(|| "missing device type".to_string()) + .and_then(|first_char| { + if vec!['b', 'c', 'u', 'p'].contains(&first_char) { + Ok(()) + } else { + Err(format!("invalid device type ‘{}’", tpe)) + } + }) +} + +fn valid_u64(num: String) -> Result<(), String> { + num.parse::().map(|_| ()).map_err(|_| num) } diff --git a/src/uu/mknod/src/parsemode.rs b/src/uu/mknod/src/parsemode.rs new file mode 100644 index 000000000..026fc4a56 --- /dev/null +++ b/src/uu/mknod/src/parsemode.rs @@ -0,0 +1,54 @@ +// spell-checker:ignore (ToDO) fperm + +use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; + +use uucore::mode; + +pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + +pub fn parse_mode(mode: &str) -> Result { + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + mode::parse_numeric(MODE_RW_UGO as u32, mode) + } else { + mode::parse_symbolic(MODE_RW_UGO as u32, mode, true) + }; + result.map(|mode| mode as mode_t) +} + +#[cfg(test)] +mod test { + /// Test if the program is running under WSL + // ref: @@ + // ToDO: test on WSL2 which likely doesn't need special handling; plan change to `is_wsl_1()` if WSL2 is less needy + pub fn is_wsl() -> bool { + #[cfg(target_os = "linux")] + { + if let Ok(b) = std::fs::read("/proc/sys/kernel/osrelease") { + if let Ok(s) = std::str::from_utf8(&b) { + let a = s.to_ascii_lowercase(); + return a.contains("microsoft") || a.contains("wsl"); + } + } + } + false + } + + #[test] + fn symbolic_modes() { + assert_eq!(super::parse_mode("u+x").unwrap(), 0o766); + assert_eq!( + super::parse_mode("+x").unwrap(), + if !is_wsl() { 0o777 } else { 0o776 } + ); + assert_eq!(super::parse_mode("a-w").unwrap(), 0o444); + assert_eq!(super::parse_mode("g-r").unwrap(), 0o626); + } + + #[test] + fn numeric_modes() { + assert_eq!(super::parse_mode("644").unwrap(), 0o644); + assert_eq!(super::parse_mode("+100").unwrap(), 0o766); + assert_eq!(super::parse_mode("-4").unwrap(), 0o662); + } +} diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 1bb79ac03..4fb5a6509 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -132,19 +132,15 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) { (srwx, pos) } -pub fn parse_mode(mode: Option) -> Result { +pub fn parse_mode(mode: &str) -> Result { let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - if let Some(mode) = mode { - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { - parse_numeric(fperm as u32, mode.as_str()) - } else { - parse_symbolic(fperm as u32, mode.as_str(), true) - }; - result.map(|mode| mode as mode_t) + let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + let result = if mode.contains(arr) { + parse_numeric(fperm as u32, mode) } else { - Ok(fperm) - } + parse_symbolic(fperm as u32, mode, true) + }; + result.map(|mode| mode as mode_t) } #[cfg(test)] @@ -152,20 +148,19 @@ mod test { #[test] fn symbolic_modes() { - assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); + assert_eq!(super::parse_mode("u+x").unwrap(), 0o766); assert_eq!( - super::parse_mode(Some("+x".to_owned())).unwrap(), + super::parse_mode("+x").unwrap(), if !crate::os::is_wsl_1() { 0o777 } else { 0o776 } ); - assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); - assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); + assert_eq!(super::parse_mode("a-w").unwrap(), 0o444); + assert_eq!(super::parse_mode("g-r").unwrap(), 0o626); } #[test] fn numeric_modes() { - assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644); - assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766); - assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662); - assert_eq!(super::parse_mode(None).unwrap(), 0o666); + assert_eq!(super::parse_mode("644").unwrap(), 0o644); + assert_eq!(super::parse_mode("+100").unwrap(), 0o766); + assert_eq!(super::parse_mode("-4").unwrap(), 0o662); } } diff --git a/tests/by-util/test_mknod.rs b/tests/by-util/test_mknod.rs index 651491045..1d39372ac 100644 --- a/tests/by-util/test_mknod.rs +++ b/tests/by-util/test_mknod.rs @@ -1 +1,124 @@ -// ToDO: add tests +use crate::common::util::*; + +#[cfg(not(windows))] +#[test] +fn test_mknod_help() { + new_ucmd!() + .arg("--help") + .succeeds() + .no_stderr() + .stdout_contains("USAGE:"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_version() { + assert!(new_ucmd!() + .arg("--version") + .succeeds() + .no_stderr() + .stdout_str() + .starts_with("mknod")); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_default_writable() { + let ts = TestScenario::new(util_name!()); + ts.ucmd().arg("test_file").arg("p").succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); + assert!(!ts.fixtures.metadata("test_file").permissions().readonly()); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_mnemonic_usage() { + let ts = TestScenario::new(util_name!()); + ts.ucmd().arg("test_file").arg("pipe").succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_read_only() { + let ts = TestScenario::new(util_name!()); + ts.ucmd() + .arg("-m") + .arg("a=r") + .arg("test_file") + .arg("p") + .succeeds(); + assert!(ts.fixtures.is_fifo("test_file")); + assert!(ts.fixtures.metadata("test_file").permissions().readonly()); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_fifo_invalid_extra_operand() { + new_ucmd!() + .arg("test_file") + .arg("p") + .arg("1") + .arg("2") + .fails() + .stderr_contains(&"Fifos do not have major and minor device numbers"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_character_device_requires_major_and_minor() { + new_ucmd!() + .arg("test_file") + .arg("c") + .fails() + .status_code(1) + .stderr_contains(&"Special files require major and minor device numbers."); + new_ucmd!() + .arg("test_file") + .arg("c") + .arg("1") + .fails() + .status_code(1) + .stderr_contains(&"Special files require major and minor device numbers."); + new_ucmd!() + .arg("test_file") + .arg("c") + .arg("1") + .arg("c") + .fails() + .status_code(1) + .stderr_contains(&"Invalid value for ''"); + new_ucmd!() + .arg("test_file") + .arg("c") + .arg("c") + .arg("1") + .fails() + .status_code(1) + .stderr_contains(&"Invalid value for ''"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_invalid_arg() { + new_ucmd!() + .arg("--foo") + .fails() + .status_code(1) + .no_stdout() + .stderr_contains(&"Found argument '--foo' which wasn't expected"); +} + +#[test] +#[cfg(not(windows))] +fn test_mknod_invalid_mode() { + new_ucmd!() + .arg("--mode") + .arg("rw") + .arg("test_file") + .arg("p") + .fails() + .no_stdout() + .status_code(1) + .stderr_contains(&"invalid mode"); +} diff --git a/tests/common/util.rs b/tests/common/util.rs index 1ade70127..719849afc 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -163,7 +163,7 @@ impl CmdResult { /// asserts that the command's exit code is the same as the given one pub fn status_code(&self, code: i32) -> &CmdResult { - assert!(self.code == Some(code)); + assert_eq!(self.code, Some(code)); self } @@ -295,12 +295,22 @@ impl CmdResult { } pub fn stdout_contains>(&self, cmp: T) -> &CmdResult { - assert!(self.stdout_str().contains(cmp.as_ref())); + assert!( + self.stdout_str().contains(cmp.as_ref()), + "'{}' does not contain '{}'", + self.stdout_str(), + cmp.as_ref() + ); self } pub fn stderr_contains>(&self, cmp: T) -> &CmdResult { - assert!(self.stderr_str().contains(cmp.as_ref())); + assert!( + self.stderr_str().contains(cmp.as_ref()), + "'{}' does not contain '{}'", + self.stderr_str(), + cmp.as_ref() + ); self } From 7d2b051866f77000d253de324ad69e3fbe31ce9d Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Thu, 6 May 2021 02:33:25 +0530 Subject: [PATCH 247/399] Implement Total size feature (#2170) * ls: Implement total size feature - Implement total size reporting that was missing - Fix minor formatting / readability nits * tests: Add tests for ls total sizes feature * ls: Fix MSRV build errors due to unsupported attributes for if blocks * ls: Add windows support for total sizes feature - Add windows support (defaults to file size as block sizes related infromation is not avialable on windows) - Renamed some functions --- Cargo.lock | 2 ++ src/uu/ls/src/ls.rs | 74 ++++++++++++++++++++++++++++------------ tests/by-util/test_ls.rs | 45 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62fa80c2d..3cd0c7cda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1692,6 +1692,7 @@ dependencies = [ name = "uu_basename" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] @@ -2645,6 +2646,7 @@ dependencies = [ name = "uu_who" version = "0.0.6" dependencies = [ + "clap", "uucore", "uucore_procs", ] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 0e2754f07..f24bf513e 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1179,31 +1179,32 @@ impl PathData { } 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 has_failed = false; let mut out = BufWriter::new(stdout()); - for loc in locs { + for loc in &locs { let p = PathBuf::from(&loc); if !p.exists() { show_error!("'{}': {}", &loc, "No such file or directory"); - // We found an error, the return code of ls should not be 0 - // And no need to continue the execution + /* + We found an error, the return code of ls should not be 0 + And no need to continue the execution + */ has_failed = true; continue; } let path_data = PathData::new(p, None, None, &config, true); - let show_dir_contents = if let Some(ft) = path_data.file_type() { - !config.directory && ft.is_dir() - } else { - has_failed = true; - false + let show_dir_contents = match path_data.file_type() { + Some(ft) => !config.directory && ft.is_dir(), + None => { + has_failed = true; + false + } }; if show_dir_contents { @@ -1217,7 +1218,7 @@ fn list(locs: Vec, config: Config) -> i32 { sort_entries(&mut dirs, &config); for dir in dirs { - if number_of_locs > 1 { + if locs.len() > 1 { let _ = writeln!(out, "\n{}:", dir.p_buf.display()); } enter_directory(&dir, &config, &mut out); @@ -1331,7 +1332,7 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { if let Some(md) = entry.md() { ( display_symlink_count(&md).len(), - display_file_size(&md, config).len(), + display_size(md.len(), config).len(), ) } else { (0, 0) @@ -1344,14 +1345,22 @@ fn pad_left(string: String, count: usize) -> String { fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter) { if config.format == Format::Long { - let (mut max_links, mut max_size) = (1, 1); + let (mut max_links, mut max_width) = (1, 1); + let mut total_size = 0; + for item in items { - let (links, size) = display_dir_entry_size(item, config); + let (links, width) = display_dir_entry_size(item, config); max_links = links.max(max_links); - max_size = size.max(max_size); + max_width = width.max(max_width); + total_size += item.md().map_or(0, |md| get_block_size(md, config)); } + + if total_size > 0 { + let _ = writeln!(out, "total {}", display_size(total_size, config)); + } + for item in items { - display_item_long(item, max_links, max_size, config, out); + display_item_long(item, max_links, max_width, config, out); } } else { let names = items.iter().filter_map(|i| display_file_name(&i, config)); @@ -1396,6 +1405,29 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter u64 { + /* GNU ls will display sizes in terms of block size + md.len() will differ from this value when the file has some holes + */ + #[cfg(unix)] + { + // hard-coded for now - enabling setting this remains a TODO + let ls_block_size = 1024; + return match config.size_format { + SizeFormat::Binary => md.blocks() * 512, + SizeFormat::Decimal => md.blocks() * 512, + SizeFormat::Bytes => md.blocks() * 512 / ls_block_size, + }; + } + + #[cfg(not(unix))] + { + let _ = config; + // no way to get block size for windows, fall-back to file size + md.len() + } +} + fn display_grid( names: impl Iterator, width: u16, @@ -1471,7 +1503,7 @@ fn display_item_long( let _ = writeln!( out, " {} {} {}", - pad_left(display_file_size(&md, config), max_size), + pad_left(display_size(md.len(), config), max_size), display_date(&md, config), // unwrap is fine because it fails when metadata is not available // but we already know that it is because it's checked at the @@ -1626,13 +1658,13 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { } } -fn display_file_size(metadata: &Metadata, config: &Config) -> String { +fn display_size(len: u64, config: &Config) -> String { // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. match config.size_format { - SizeFormat::Binary => format_prefixed(NumberPrefix::binary(metadata.len() as f64)), - SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(metadata.len() as f64)), - SizeFormat::Bytes => metadata.len().to_string(), + SizeFormat::Binary => format_prefixed(NumberPrefix::binary(len as f64)), + SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(len as f64)), + SizeFormat::Bytes => len.to_string(), } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 110764aa5..0985ba719 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -5,6 +5,7 @@ use crate::common::util::*; extern crate regex; use self::regex::Regex; +use std::collections::HashMap; use std::path::Path; use std::thread::sleep; use std::time::Duration; @@ -308,6 +309,50 @@ fn test_ls_long() { } } +#[test] +fn test_ls_long_total_size() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch(&at.plus_as_string("test-long")); + at.append("test-long", "1"); + at.touch(&at.plus_as_string("test-long2")); + at.append("test-long2", "2"); + + let expected_prints: HashMap<_, _> = if cfg!(unix) { + [ + ("long_vanilla", "total 8"), + ("long_human_readable", "total 8.0K"), + ("long_si", "total 8.2k"), + ] + .iter() + .cloned() + .collect() + } else { + [ + ("long_vanilla", "total 2"), + ("long_human_readable", "total 2"), + ("long_si", "total 2"), + ] + .iter() + .cloned() + .collect() + }; + + for arg in &["-l", "--long", "--format=long", "--format=verbose"] { + let result = scene.ucmd().arg(arg).succeeds(); + result.stdout_contains(expected_prints["long_vanilla"]); + + for arg2 in &["-h", "--human-readable", "--si"] { + let result = scene.ucmd().arg(arg).arg(arg2).succeeds(); + result.stdout_contains(if *arg2 == "--si" { + expected_prints["long_si"] + } else { + expected_prints["long_human_readable"] + }); + } + } +} + #[test] fn test_ls_long_formats() { let scene = TestScenario::new(util_name!()); From a2658250fc12f7b1d55978afa3774dd8263955c9 Mon Sep 17 00:00:00 2001 From: jaggededgedjustice Date: Wed, 5 May 2021 22:12:17 +0100 Subject: [PATCH 248/399] Fix fmt crashing on subtracting unsigned numbers (#2178) --- src/uu/fmt/src/linebreak.rs | 2 +- tests/by-util/test_fmt.rs | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index 50cb6f77f..fe9f8568e 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -296,7 +296,7 @@ fn find_kp_breakpoints<'a, T: Iterator>>( (0, 0.0) } else { compute_demerits( - (args.opts.goal - tlen) as isize, + args.opts.goal as isize - tlen as isize, stretch, w.word_nchars as isize, active.prev_rat, diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 21a5f3396..a83fae58e 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -33,18 +33,16 @@ fn test_fmt_w_too_big() { "fmt: error: invalid width: '2501': Numerical result out of range" ); } -/* #[test] - Fails for now, see https://github.com/uutils/coreutils/issues/1501 +#[test] fn test_fmt_w() { let result = new_ucmd!() .arg("-w") .arg("10") .arg("one-word-per-line.txt") .run(); - //.stdout_is_fixture("call_graph.expected"); - assert_eq!(result.stdout_str().trim(), "this is a file with one word per line"); + //.stdout_is_fixture("call_graph.expected"); + assert_eq!( + result.stdout_str().trim(), + "this is\na file\nwith one\nword per\nline" + ); } - - -fmt is pretty broken in general, needs more works to have more tests - */ From 9f9735694db35ac47b4ad1b01b146c3d80f377d7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 5 May 2021 22:52:07 +0200 Subject: [PATCH 249/399] refresh cargo.lock with recent updates --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cd0c7cda..a0169b412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,7 +618,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.7", + "redox_syscall 0.2.8", "winapi 0.3.9", ] @@ -1259,9 +1259,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags", ] @@ -1272,7 +1272,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.7", + "redox_syscall 0.2.8", ] [[package]] @@ -1537,7 +1537,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", - "redox_syscall 0.2.7", + "redox_syscall 0.2.8", "redox_termios", ] From 928fc59845d9854fd16ae324d4ed82882bec99bd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 6 May 2021 10:43:48 +0200 Subject: [PATCH 250/399] Ignore test_lookup until issue #2181 is fixed --- tests/by-util/test_who.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index a5637f23a..8aeecfb55 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -162,6 +162,7 @@ fn test_users() { #[cfg(target_os = "linux")] #[test] +#[ignore] fn test_lookup() { for opt in vec!["--lookup"] { new_ucmd!() From cdd3998a445ea31a14320d27b97f1f8d74d3af4d Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 6 May 2021 14:10:16 +0200 Subject: [PATCH 251/399] gitignore: add ds_store files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 11f46e13e..77e8f717e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ Cargo.lock lib*.a /docs/_build *.iml +### macOS ### +.DS_Store From b24b9d501bfc84702f635669d703a7a7aa0156fc Mon Sep 17 00:00:00 2001 From: Idan Attias Date: Thu, 6 May 2021 10:52:35 +0300 Subject: [PATCH 252/399] logname: replace getopts with clap --- Cargo.lock | 1 + src/uu/logname/Cargo.toml | 1 + src/uu/logname/src/logname.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0169b412..24c008040 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2068,6 +2068,7 @@ dependencies = [ name = "uu_logname" version = "0.0.6" dependencies = [ + "clap", "libc", "uucore", "uucore_procs", diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 416f817d7..4aa4d68f4 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -16,6 +16,7 @@ path = "src/logname.rs" [dependencies] libc = "0.2.42" +clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 8c6a946f5..9f9319e65 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -13,7 +13,8 @@ extern crate uucore; use std::ffi::CStr; -use uucore::InvalidEncodingHandling; + +use clap::App; extern "C" { // POSIX requires using getlogin (or equivalent code) @@ -31,15 +32,14 @@ fn get_userlogin() -> Option { } } -static SYNTAX: &str = ""; static SUMMARY: &str = "Print user's login name"; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); -pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(), - ); +pub fn uumain(_: impl uucore::Args) -> i32 { + let _ = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .get_matches(); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), From 41eb930292ba994b11e03dd700a0180f008c5d9b Mon Sep 17 00:00:00 2001 From: Idan Attias Date: Thu, 6 May 2021 11:06:38 +0300 Subject: [PATCH 253/399] logname: align profile --- src/uu/logname/src/logname.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 9f9319e65..ae0f93533 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -13,6 +13,7 @@ extern crate uucore; use std::ffi::CStr; +use uucore::InvalidEncodingHandling; use clap::App; @@ -35,10 +36,21 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; static VERSION: &str = env!("CARGO_PKG_VERSION"); -pub fn uumain(_: impl uucore::Args) -> i32 { +fn get_usage() -> String { + format!("{0}", executable!()) +} + +pub fn uumain(args: impl uucore::Args) -> i32 { + let _ = args + .collect_str(InvalidEncodingHandling::Ignore) + .accept_any(); + + let usage = get_usage(); + let _ = App::new(executable!()) .version(VERSION) .about(SUMMARY) + .usage(&usage[..]) .get_matches(); match get_userlogin() { From 34b9809223ac5d260fa9931da6206597bf49865a Mon Sep 17 00:00:00 2001 From: Idan Attias Date: Thu, 6 May 2021 11:59:58 +0300 Subject: [PATCH 254/399] logname: fix test & style warning --- src/uu/logname/src/logname.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index ae0f93533..14bf7ef3b 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -37,21 +37,20 @@ static SUMMARY: &str = "Print user's login name"; static VERSION: &str = env!("CARGO_PKG_VERSION"); fn get_usage() -> String { - format!("{0}", executable!()) + String::from(executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let _ = args + let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); let usage = get_usage(); - let _ = App::new(executable!()) .version(VERSION) .about(SUMMARY) .usage(&usage[..]) - .get_matches(); + .get_matches_from(args); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), From 704c6865b1d3f5a3523b354b60ee30f8ee6c59f2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 7 May 2021 09:57:31 +0200 Subject: [PATCH 255/399] refresh cargo.lock with recent updates --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24c008040..2362342d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,9 +1277,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr 2.4.0", @@ -1312,9 +1312,9 @@ dependencies = [ [[package]] name = "retain_mut" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53552c6c49e1e13f1a203ef0080ab3bbef0beb570a528993e83df057a9d9bba1" +checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b" [[package]] name = "rust-ini" From c38373946a6a24afa02050a9dc41d88bc2afcdd7 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 7 May 2021 21:49:44 +0200 Subject: [PATCH 256/399] sort: optimize the Line struct --- Cargo.lock | 9 +-- src/uu/sort/Cargo.toml | 1 - src/uu/sort/src/sort.rs | 118 +++++++++++++++++++++++++--------------- 3 files changed, 74 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2362342d4..13441d4fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1445,12 +1445,6 @@ dependencies = [ "maybe-uninit", ] -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - [[package]] name = "strsim" version = "0.8.0" @@ -1912,7 +1906,7 @@ dependencies = [ "quickcheck", "rand 0.7.3", "rand_chacha", - "smallvec 0.6.14", + "smallvec", "uucore", "uucore_procs", ] @@ -2392,7 +2386,6 @@ dependencies = [ "rand 0.7.3", "rayon", "semver", - "smallvec 1.6.1", "tempdir", "unicode-width", "uucore", diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 3784ccbb0..5221f1f4e 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -21,7 +21,6 @@ clap = "2.33" fnv = "1.0.7" itertools = "0.10.0" semver = "0.9.0" -smallvec = "1.6.1" unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index d8978cb2b..71d912f33 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -20,8 +20,8 @@ mod external_sort; mod numeric_str_cmp; use clap::{App, Arg}; -use external_sort::ext_sort; use custom_str_cmp::custom_str_cmp; +use external_sort::ext_sort; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -29,7 +29,6 @@ use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; -use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; @@ -231,7 +230,6 @@ impl SelectionRange { enum NumCache { AsF64(GeneralF64ParseResult), WithInfo(NumInfo), - None, } impl NumCache { @@ -252,7 +250,7 @@ impl NumCache { #[derive(Clone)] struct Selection { range: SelectionRange, - num_cache: NumCache, + num_cache: Option>, } impl Selection { @@ -266,15 +264,17 @@ type Field = Range; #[derive(Clone)] pub struct Line { - line: String, + line: Box, // The common case is not to specify fields. Let's make this fast. - selections: SmallVec<[Selection; 1]>, + first_selection: Selection, + other_selections: Box<[Selection]>, } impl Line { + /// Estimate the number of bytes that this Line is occupying pub fn estimate_size(&self) -> usize { - self.line.capacity() - + self.selections.capacity() * std::mem::size_of::() + self.line.len() + + self.other_selections.len() * std::mem::size_of::() + std::mem::size_of::() } @@ -290,35 +290,22 @@ impl Line { None }; - let selections: SmallVec<[Selection; 1]> = settings - .selectors - .iter() - .map(|selector| { - let mut range = - SelectionRange::new(selector.get_selection(&line, fields.as_deref())); - let num_cache = if selector.settings.mode == SortMode::Numeric - || selector.settings.mode == SortMode::HumanNumeric - { - let (info, num_range) = NumInfo::parse( - range.get_str(&line), - NumInfoParseSettings { - accept_si_units: selector.settings.mode == SortMode::HumanNumeric, - thousands_separator: Some(THOUSANDS_SEP), - decimal_pt: Some(DECIMAL_PT), - }, - ); - range.shorten(num_range); - NumCache::WithInfo(info) - } else if selector.settings.mode == SortMode::GeneralNumeric { - let str = range.get_str(&line); - NumCache::AsF64(general_f64_parse(&str[get_leading_gen(str)])) - } else { - NumCache::None - }; - Selection { range, num_cache } - }) + let mut selectors = settings.selectors.iter(); + + let first_selection = selectors + .next() + .unwrap() + .get_selection(&line, fields.as_deref()); + + let other_selections: Vec = selectors + .map(|selector| selector.get_selection(&line, fields.as_deref())) .collect(); - Self { line, selections } + + Self { + line: line.into_boxed_str(), + first_selection, + other_selections: other_selections.into_boxed_slice(), + } } /// Writes indicators for the selections this line matched. The original line content is NOT expected @@ -337,7 +324,7 @@ impl Line { let fields = tokenize(&self.line, settings.separator); for selector in settings.selectors.iter() { - let mut selection = selector.get_selection(&self.line, Some(&fields)); + let mut selection = selector.get_range(&self.line, Some(&fields)); match selector.settings.mode { SortMode::Numeric | SortMode::HumanNumeric => { // find out which range is used for numeric comparisons @@ -594,9 +581,35 @@ impl FieldSelector { self.from.field != 1 || self.from.char == 0 || self.to.is_some() } + fn get_selection(&self, line: &str, fields: Option<&[Field]>) -> Selection { + let mut range = SelectionRange::new(self.get_range(&line, fields)); + let num_cache = if self.settings.mode == SortMode::Numeric + || self.settings.mode == SortMode::HumanNumeric + { + let (info, num_range) = NumInfo::parse( + range.get_str(&line), + NumInfoParseSettings { + accept_si_units: self.settings.mode == SortMode::HumanNumeric, + thousands_separator: Some(THOUSANDS_SEP), + decimal_pt: Some(DECIMAL_PT), + }, + ); + range.shorten(num_range); + Some(Box::new(NumCache::WithInfo(info))) + } else if self.settings.mode == SortMode::GeneralNumeric { + let str = range.get_str(&line); + Some(Box::new(NumCache::AsF64(general_f64_parse( + &str[get_leading_gen(str)], + )))) + } else { + None + }; + Selection { range, num_cache } + } + /// Look up the slice that corresponds to this selector for the given line. - /// If needs_fields returned false, fields may be None. - fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Range { + /// If needs_fields returned false, tokens may be None. + fn get_range<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Range { enum Resolution { // The start index of the resolved character, inclusive StartOfChar(usize), @@ -1237,8 +1250,11 @@ fn sort_by(unsorted: &mut Vec, settings: &GlobalSettings) { fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { for (idx, selector) in global_settings.selectors.iter().enumerate() { - let a_selection = &a.selections[idx]; - let b_selection = &b.selections[idx]; + let (a_selection, b_selection) = if idx == 0 { + (&a.first_selection, &b.first_selection) + } else { + (&a.other_selections[idx - 1], &b.other_selections[idx - 1]) + }; let a_str = a_selection.get_str(a); let b_str = b_selection.get_str(b); let settings = &selector.settings; @@ -1248,12 +1264,12 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering } else { match settings.mode { SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( - (a_str, a_selection.num_cache.as_num_info()), - (b_str, b_selection.num_cache.as_num_info()), + (a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()), + (b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()), ), SortMode::GeneralNumeric => general_numeric_compare( - a_selection.num_cache.as_f64(), - b_selection.num_cache.as_f64(), + a_selection.num_cache.as_ref().unwrap().as_f64(), + b_selection.num_cache.as_ref().unwrap().as_f64(), ), SortMode::Month => month_compare(a_str, b_str), SortMode::Version => version_compare(a_str, b_str), @@ -1591,4 +1607,16 @@ mod tests { let line = "..a..a"; assert_eq!(tokenize(line, Some('a')), vec![0..2, 3..5]); } + + #[test] + #[cfg(target_pointer_width = "64")] + fn test_line_size() { + // We should make sure to not regress the size of the Line struct because + // it is unconditional overhead for every line we sort. + assert_eq!(std::mem::size_of::(), 56); + // These are the fields of Line: + assert_eq!(std::mem::size_of::>(), 16); + assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::>(), 16); + } } From 8c9faa16b94c9ef9064b9a2e9d521046619bcc66 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 7 May 2021 21:50:33 +0200 Subject: [PATCH 257/399] sort: improve memory usage for extsort --- src/uu/sort/src/external_sort/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 725b17bbd..662250e1d 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -113,7 +113,7 @@ pub fn ext_sort( chunk.push(seq); - if total_read >= settings.buffer_size { + if total_read + chunk.len() * std::mem::size_of::() >= settings.buffer_size { super::sort_by(&mut chunk, &settings); write_chunk( settings, @@ -136,6 +136,9 @@ pub fn ext_sort( iter.chunks += 1; } + // We manually drop here to not go over our memory limit when we allocate below. + drop(chunk); + // initialize buffers for each chunk // // Having a right sized buffer for each chunk for smallish values seems silly to me? From 3b6c7bc9e97c9d01d31cef2e8fd3641743985bc6 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 8 May 2021 00:50:36 +0200 Subject: [PATCH 258/399] Fix mistakes with merging --- src/uu/ls/src/ls.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 06bbeddea..bacd4176a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1332,7 +1332,7 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { if let Some(md) = entry.md() { ( display_symlink_count(&md).len(), - display_size(md.len(), config).len(), + display_size_or_rdev(&md, config).len(), ) } else { (0, 0) @@ -1503,7 +1503,7 @@ fn display_item_long( let _ = writeln!( out, " {} {} {}", - pad_left(display_size(md.len(), config), max_size), + pad_left(display_size_or_rdev(md, config), max_size), display_date(&md, config), // unwrap is fine because it fails when metadata is not available // but we already know that it is because it's checked at the @@ -1658,7 +1658,7 @@ fn format_prefixed(prefixed: NumberPrefix) -> String { } } -fn display_size(metadata: &Metadata, config: &Config) -> String { +fn display_size_or_rdev(metadata: &Metadata, config: &Config) -> String { #[cfg(unix)] { let ft = metadata.file_type(); @@ -1670,12 +1670,16 @@ fn display_size(metadata: &Metadata, config: &Config) -> String { } } + display_size(metadata.len(), config) +} + +fn display_size(size: u64, config: &Config) -> String { // NOTE: The human-readable behaviour deviates from the GNU ls. // The GNU ls uses binary prefixes by default. match config.size_format { - SizeFormat::Binary => format_prefixed(NumberPrefix::binary(len as f64)), - SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(len as f64)), - SizeFormat::Bytes => len.to_string(), + SizeFormat::Binary => format_prefixed(NumberPrefix::binary(size as f64)), + SizeFormat::Decimal => format_prefixed(NumberPrefix::decimal(size as f64)), + SizeFormat::Bytes => size.to_string(), } } From c0c240f194da65e1cc53d7d878cd89fb00d346bb Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 5 May 2021 19:05:03 +0100 Subject: [PATCH 259/399] du: fix couple of du unit tests for FreeBSD. --- tests/by-util/test_du.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 111f2dc90..c1b7fcb7b 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -53,7 +53,11 @@ fn _du_basics_subdir(s: &str) { fn _du_basics_subdir(s: &str) { assert_eq!(s, "0\tsubdir/deeper\n"); } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] +#[cfg(target_os = "freebsd")] +fn _du_basics_subdir(s: &str) { + assert_eq!(s, "8\tsubdir/deeper\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] fn _du_basics_subdir(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { @@ -100,7 +104,11 @@ fn _du_soft_link(s: &str) { fn _du_soft_link(s: &str) { assert_eq!(s, "8\tsubdir/links\n"); } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] +#[cfg(target_os = "freebsd")] +fn _du_soft_link(s: &str) { + assert_eq!(s, "16\tsubdir/links\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] fn _du_soft_link(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { @@ -141,7 +149,11 @@ fn _du_hard_link(s: &str) { fn _du_hard_link(s: &str) { assert_eq!(s, "8\tsubdir/links\n") } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] +#[cfg(target_os = "freebsd")] +fn _du_hard_link(s: &str) { + assert_eq!(s, "16\tsubdir/links\n") +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] fn _du_hard_link(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { @@ -181,7 +193,11 @@ fn _du_d_flag(s: &str) { fn _du_d_flag(s: &str) { assert_eq!(s, "8\t./subdir\n8\t./\n"); } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] +#[cfg(target_os = "freebsd")] +fn _du_d_flag(s: &str) { + assert_eq!(s, "28\t./subdir\n36\t./\n"); +} +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] fn _du_d_flag(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { From 64c1f164211d6f7bb147cf2be9c14e963aad2cf2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 7 May 2021 23:40:07 +0200 Subject: [PATCH 260/399] sort: allow some functions to be called with OsStr --- src/uu/sort/src/sort.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index d8978cb2b..730be0039 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -20,8 +20,8 @@ mod external_sort; mod numeric_str_cmp; use clap::{App, Arg}; -use external_sort::ext_sort; use custom_str_cmp::custom_str_cmp; +use external_sort::ext_sort; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -33,6 +33,7 @@ use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env; +use std::ffi::OsStr; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; @@ -1109,10 +1110,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exec(files, settings) } -fn file_to_lines_iter<'a>( - file: &str, - settings: &'a GlobalSettings, -) -> Option + 'a> { +fn file_to_lines_iter( + file: impl AsRef, + settings: &'_ GlobalSettings, +) -> Option + '_> { let (reader, _) = match open(file) { Some(x) => x, None => return None, @@ -1177,7 +1178,7 @@ fn exec(files: Vec, settings: GlobalSettings) -> i32 { let mut lines = vec![]; // This is duplicated from fn file_to_lines_iter, but using that function directly results in a performance regression. - for (file, _) in files.iter().map(|file| open(file)).flatten() { + for (file, _) in files.iter().map(open).flatten() { let buf_reader = BufReader::new(file); for line in buf_reader.split(if settings.zero_terminated { b'\0' @@ -1501,7 +1502,8 @@ fn print_sorted>(iter: T, settings: &GlobalSettings) { } // from cat.rs -fn open(path: &str) -> Option<(Box, bool)> { +fn open(path: impl AsRef) -> Option<(Box, bool)> { + let path = path.as_ref(); if path == "-" { let stdin = stdin(); return Some((Box::new(stdin) as Box, is_stdin_interactive())); @@ -1510,7 +1512,7 @@ fn open(path: &str) -> Option<(Box, bool)> { match File::open(Path::new(path)) { Ok(f) => Some((Box::new(f) as Box, false)), Err(e) => { - show_error!("{0}: {1}", path, e.to_string()); + show_error!("{0:?}: {1}", path, e.to_string()); None } } From 38effc93b3d8a34a1136a9911eb9b1e0da7359c7 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 7 May 2021 23:39:00 +0200 Subject: [PATCH 261/399] sort: use FileMerger for extsort merge step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FileMerger is much more efficient than the previous algorithm, which looped over all elements every time to determine the next element. FileMerger uses a BinaryHeap, which should bring the complexity for the merge step down from O(n²) to O(n log n). --- src/uu/sort/src/external_sort/mod.rs | 178 +++++---------------------- 1 file changed, 30 insertions(+), 148 deletions(-) diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs index 725b17bbd..af6902367 100644 --- a/src/uu/sort/src/external_sort/mod.rs +++ b/src/uu/sort/src/external_sort/mod.rs @@ -1,91 +1,33 @@ -use std::cmp::Ordering; -use std::collections::VecDeque; -use std::fs::{File, OpenOptions}; -use std::io::SeekFrom; -use std::io::{BufRead, BufReader, BufWriter, Seek, Write}; +use std::fs::OpenOptions; +use std::io::{BufWriter, Write}; use std::path::Path; use tempdir::TempDir; +use crate::{file_to_lines_iter, FileMerger}; + use super::{GlobalSettings, Line}; /// Iterator that provides sorted `T`s -pub struct ExtSortedIterator { - buffers: Vec>, - chunk_offsets: Vec, - max_per_chunk: usize, - chunks: usize, - tmp_dir: TempDir, - settings: GlobalSettings, - failed: bool, +pub struct ExtSortedIterator<'a> { + file_merger: FileMerger<'a>, + // Keep tmp_dir around, it is deleted when dropped. + _tmp_dir: TempDir, } -impl Iterator for ExtSortedIterator { +impl<'a> Iterator for ExtSortedIterator<'a> { type Item = Line; - - /// # Errors - /// - /// This method can fail due to issues reading intermediate sorted chunks - /// from disk fn next(&mut self) -> Option { - if self.failed { - return None; - } - // fill up any empty buffers - let mut empty = true; - for chunk_num in 0..self.chunks { - if self.buffers[chunk_num as usize].is_empty() { - let mut f = crash_if_err!( - 1, - File::open(self.tmp_dir.path().join(chunk_num.to_string())) - ); - crash_if_err!(1, f.seek(SeekFrom::Start(self.chunk_offsets[chunk_num]))); - let bytes_read = fill_buff( - &mut self.buffers[chunk_num as usize], - f, - self.max_per_chunk, - &self.settings, - ); - self.chunk_offsets[chunk_num as usize] += bytes_read as u64; - if !self.buffers[chunk_num as usize].is_empty() { - empty = false; - } - } else { - empty = false; - } - } - if empty { - return None; - } - - // find the next record to write - // check is_empty() before unwrap()ing - let mut idx = 0; - for chunk_num in 0..self.chunks as usize { - if !self.buffers[chunk_num].is_empty() - && (self.buffers[idx].is_empty() - || super::compare_by( - self.buffers[chunk_num].front().unwrap(), - self.buffers[idx].front().unwrap(), - &self.settings, - ) == Ordering::Less) - { - idx = chunk_num; - } - } - - // unwrap due to checks above - let r = self.buffers[idx].pop_front().unwrap(); - Some(r) + self.file_merger.next() } } /// Sort (based on `compare`) the `T`s provided by `unsorted` and return an /// iterator /// -/// # Errors +/// # Panics /// -/// This method can fail due to issues writing intermediate sorted chunks +/// This method can panic due to issues writing intermediate sorted chunks /// to disk. pub fn ext_sort( unsorted: impl Iterator, @@ -93,19 +35,12 @@ pub fn ext_sort( ) -> ExtSortedIterator { let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); - let mut iter = ExtSortedIterator { - buffers: Vec::new(), - chunk_offsets: Vec::new(), - max_per_chunk: 0, - chunks: 0, - tmp_dir, - settings: settings.clone(), - failed: false, - }; - let mut total_read = 0; let mut chunk = Vec::new(); + let mut chunks_read = 0; + let mut file_merger = FileMerger::new(settings); + // make the initial chunks on disk for seq in unsorted { let seq_size = seq.estimate_size(); @@ -113,62 +48,35 @@ pub fn ext_sort( chunk.push(seq); - if total_read >= settings.buffer_size { + if total_read >= settings.buffer_size && chunk.len() >= 2 { super::sort_by(&mut chunk, &settings); - write_chunk( - settings, - &iter.tmp_dir.path().join(iter.chunks.to_string()), - &mut chunk, - ); + + let file_path = tmp_dir.path().join(chunks_read.to_string()); + write_chunk(settings, &file_path, &mut chunk); chunk.clear(); total_read = 0; - iter.chunks += 1; + chunks_read += 1; + + file_merger.push_file(Box::new(file_to_lines_iter(file_path, settings).unwrap())) } } // write the last chunk if !chunk.is_empty() { super::sort_by(&mut chunk, &settings); + + let file_path = tmp_dir.path().join(chunks_read.to_string()); write_chunk( settings, - &iter.tmp_dir.path().join(iter.chunks.to_string()), + &tmp_dir.path().join(chunks_read.to_string()), &mut chunk, ); - iter.chunks += 1; + + file_merger.push_file(Box::new(file_to_lines_iter(file_path, settings).unwrap())); } - - // initialize buffers for each chunk - // - // Having a right sized buffer for each chunk for smallish values seems silly to me? - // - // We will have to have the entire iter in memory sometime right? - // Set minimum to the size of the writer buffer, ~8K - - const MINIMUM_READBACK_BUFFER: usize = 8200; - let right_sized_buffer = settings - .buffer_size - .checked_div(iter.chunks) - .unwrap_or(settings.buffer_size); - iter.max_per_chunk = if right_sized_buffer > MINIMUM_READBACK_BUFFER { - right_sized_buffer - } else { - MINIMUM_READBACK_BUFFER - }; - iter.buffers = vec![VecDeque::new(); iter.chunks]; - iter.chunk_offsets = vec![0; iter.chunks]; - for chunk_num in 0..iter.chunks { - let offset = fill_buff( - &mut iter.buffers[chunk_num], - crash_if_err!( - 1, - File::open(iter.tmp_dir.path().join(chunk_num.to_string())) - ), - iter.max_per_chunk, - &settings, - ); - iter.chunk_offsets[chunk_num] = offset as u64; + ExtSortedIterator { + file_merger, + _tmp_dir: tmp_dir, } - - iter } fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec) { @@ -183,29 +91,3 @@ fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec) { } crash_if_err!(1, buf_write.flush()); } - -fn fill_buff( - vec: &mut VecDeque, - file: File, - max_bytes: usize, - settings: &GlobalSettings, -) -> usize { - let mut total_read = 0; - let mut bytes_read = 0; - for line in BufReader::new(file).split(if settings.zero_terminated { - b'\0' - } else { - b'\n' - }) { - let line_s = String::from_utf8(crash_if_err!(1, line)).unwrap(); - bytes_read += line_s.len() + 1; - let deserialized = Line::new(line_s, settings); - total_read += deserialized.estimate_size(); - vec.push_back(deserialized); - if total_read > max_bytes { - break; - } - } - - bytes_read -} From a8853765831ae747affb6af22491ae428a1b6d0e Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 7 May 2021 23:36:36 +0200 Subject: [PATCH 262/399] uucore: refactor - reduce duplicate code related to `fs::display_permissions` This is a refactor to reduce duplicate code, it affects chmod/ls/stat. * merge `stat/src/fsext::pretty_access` into `uucore/src/lib/feature/fs::display_permissions_unix` * move tests for `fs::display_permissions` from `test_stat::test_access` to `uucore/src/lib/features/fs::test_display_permissions` * adjust `uu_chmod`, `uu_ls` and `uu_stat` to use `uucore::fs::display_permissions` --- src/uu/chmod/src/chmod.rs | 11 ++-- src/uu/ls/src/ls.rs | 15 +---- src/uu/stat/Cargo.toml | 2 +- src/uu/stat/src/fsext.rs | 66 --------------------- src/uu/stat/src/stat.rs | 4 +- src/uucore/src/lib/features/fs.rs | 97 +++++++++++++++++++++++++++---- tests/by-util/test_stat.rs | 36 ------------ 7 files changed, 97 insertions(+), 134 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d01f0316e..88e3403fe 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -15,6 +15,7 @@ use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::Path; use uucore::fs::display_permissions_unix; +use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; use uucore::InvalidEncodingHandling; @@ -306,7 +307,7 @@ impl Chmoder { "mode of '{}' retained as {:04o} ({})", file.display(), fperm, - display_permissions_unix(fperm), + display_permissions_unix(fperm as mode_t, false), ); } Ok(()) @@ -319,9 +320,9 @@ impl Chmoder { "failed to change mode of file '{}' from {:o} ({}) to {:o} ({})", file.display(), fperm, - display_permissions_unix(fperm), + display_permissions_unix(fperm as mode_t, false), mode, - display_permissions_unix(mode) + display_permissions_unix(mode as mode_t, false) ); } Err(1) @@ -331,9 +332,9 @@ impl Chmoder { "mode of '{}' changed from {:o} ({}) to {:o} ({})", file.display(), fperm, - display_permissions_unix(fperm), + display_permissions_unix(fperm as mode_t, false), mode, - display_permissions_unix(mode) + display_permissions_unix(mode as mode_t, false) ); } Ok(()) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f24bf513e..36f0ad758 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1480,9 +1480,8 @@ fn display_item_long( let _ = write!( out, - "{}{} {}", - display_file_type(md.file_type()), - display_permissions(&md), + "{} {}", + display_permissions(&md, true), pad_left(display_symlink_count(&md), max_links), ); @@ -1668,16 +1667,6 @@ fn display_size(len: u64, config: &Config) -> String { } } -fn display_file_type(file_type: FileType) -> char { - if file_type.is_dir() { - 'd' - } else if file_type.is_symlink() { - 'l' - } else { - '-' - } -} - #[cfg(unix)] fn file_is_executable(md: &Metadata) -> bool { // Mode always returns u32, but the flags might not be, based on the platform diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 96bf63ffe..c325c20db 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -17,7 +17,7 @@ path = "src/stat.rs" [dependencies] clap = "2.33" time = "0.1.40" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc", "fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/stat/src/fsext.rs b/src/uu/stat/src/fsext.rs index d90099892..53280790e 100644 --- a/src/uu/stat/src/fsext.rs +++ b/src/uu/stat/src/fsext.rs @@ -41,13 +41,6 @@ impl BirthTime for Metadata { } } -#[macro_export] -macro_rules! has { - ($mode:expr, $perm:expr) => { - $mode & $perm != 0 - }; -} - pub fn pretty_time(sec: i64, nsec: i64) -> String { // sec == seconds since UNIX_EPOCH // nsec == nanoseconds since (UNIX_EPOCH + sec) @@ -81,65 +74,6 @@ pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str { } } -pub fn pretty_access(mode: mode_t) -> String { - let mut result = String::with_capacity(10); - result.push(match mode & S_IFMT { - S_IFDIR => 'd', - S_IFCHR => 'c', - S_IFBLK => 'b', - S_IFREG => '-', - S_IFIFO => 'p', - S_IFLNK => 'l', - S_IFSOCK => 's', - // TODO: Other file types - _ => '?', - }); - - result.push(if has!(mode, S_IRUSR) { 'r' } else { '-' }); - result.push(if has!(mode, S_IWUSR) { 'w' } else { '-' }); - result.push(if has!(mode, S_ISUID as mode_t) { - if has!(mode, S_IXUSR) { - 's' - } else { - 'S' - } - } else if has!(mode, S_IXUSR) { - 'x' - } else { - '-' - }); - - result.push(if has!(mode, S_IRGRP) { 'r' } else { '-' }); - result.push(if has!(mode, S_IWGRP) { 'w' } else { '-' }); - result.push(if has!(mode, S_ISGID as mode_t) { - if has!(mode, S_IXGRP) { - 's' - } else { - 'S' - } - } else if has!(mode, S_IXGRP) { - 'x' - } else { - '-' - }); - - result.push(if has!(mode, S_IROTH) { 'r' } else { '-' }); - result.push(if has!(mode, S_IWOTH) { 'w' } else { '-' }); - result.push(if has!(mode, S_ISVTX as mode_t) { - if has!(mode, S_IXOTH) { - 't' - } else { - 'T' - } - } else if has!(mode, S_IXOTH) { - 'x' - } else { - '-' - }); - - result -} - use std::borrow::Cow; use std::convert::{AsRef, From}; use std::ffi::CString; diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 5216fb293..d46c54910 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -7,13 +7,13 @@ // spell-checker:ignore (ToDO) mtab fsext showfs otype fmtstr prec ftype blocksize nlink rdev fnodes fsid namelen blksize inodes fstype iosize statfs gnulib NBLOCKSIZE -#[macro_use] mod fsext; pub use crate::fsext::*; #[macro_use] extern crate uucore; use uucore::entries; +use uucore::fs::display_permissions; use clap::{App, Arg, ArgMatches}; use std::borrow::Cow; @@ -575,7 +575,7 @@ impl Stater { } // access rights in human readable form 'A' => { - arg = pretty_access(meta.mode() as mode_t); + arg = display_permissions(&meta, true); otype = OutputType::Str; } // number of blocks allocated (see %B) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index a72d6ea82..040c36e95 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -8,8 +8,9 @@ #[cfg(unix)] use libc::{ - mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, - S_IXGRP, S_IXOTH, S_IXUSR, + mode_t, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRGRP, + S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, + S_IXUSR, }; use std::borrow::Cow; use std::env; @@ -23,9 +24,10 @@ use std::os::unix::fs::MetadataExt; use std::path::{Component, Path, PathBuf}; #[cfg(unix)] +#[macro_export] macro_rules! has { ($mode:expr, $perm:expr) => { - $mode & ($perm as u32) != 0 + $mode & $perm != 0 }; } @@ -240,22 +242,42 @@ pub fn is_stderr_interactive() -> bool { #[cfg(not(unix))] #[allow(unused_variables)] -pub fn display_permissions(metadata: &fs::Metadata) -> String { +pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { + if display_file_type { + return String::from("----------"); + } String::from("---------") } #[cfg(unix)] -pub fn display_permissions(metadata: &fs::Metadata) -> String { +pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { let mode: mode_t = metadata.mode() as mode_t; - display_permissions_unix(mode as u32) + display_permissions_unix(mode, display_file_type) } #[cfg(unix)] -pub fn display_permissions_unix(mode: u32) -> String { - let mut result = String::with_capacity(9); +pub fn display_permissions_unix(mode: mode_t, display_file_type: bool) -> String { + let mut result; + if display_file_type { + result = String::with_capacity(10); + result.push(match mode & S_IFMT { + S_IFDIR => 'd', + S_IFCHR => 'c', + S_IFBLK => 'b', + S_IFREG => '-', + S_IFIFO => 'p', + S_IFLNK => 'l', + S_IFSOCK => 's', + // TODO: Other file types + _ => '?', + }); + } else { + result = String::with_capacity(9); + } + result.push(if has!(mode, S_IRUSR) { 'r' } else { '-' }); result.push(if has!(mode, S_IWUSR) { 'w' } else { '-' }); - result.push(if has!(mode, S_ISUID) { + result.push(if has!(mode, S_ISUID as mode_t) { if has!(mode, S_IXUSR) { 's' } else { @@ -269,7 +291,7 @@ pub fn display_permissions_unix(mode: u32) -> String { result.push(if has!(mode, S_IRGRP) { 'r' } else { '-' }); result.push(if has!(mode, S_IWGRP) { 'w' } else { '-' }); - result.push(if has!(mode, S_ISGID) { + result.push(if has!(mode, S_ISGID as mode_t) { if has!(mode, S_IXGRP) { 's' } else { @@ -283,7 +305,7 @@ pub fn display_permissions_unix(mode: u32) -> String { result.push(if has!(mode, S_IROTH) { 'r' } else { '-' }); result.push(if has!(mode, S_IWOTH) { 'w' } else { '-' }); - result.push(if has!(mode, S_ISVTX) { + result.push(if has!(mode, S_ISVTX as mode_t) { if has!(mode, S_IXOTH) { 't' } else { @@ -355,4 +377,57 @@ mod tests { ); } } + + #[cfg(unix)] + #[test] + fn test_display_permissions() { + assert_eq!( + "drwxr-xr-x", + display_permissions_unix(S_IFDIR | 0o755, true) + ); + assert_eq!( + "rwxr-xr-x", + display_permissions_unix(S_IFDIR | 0o755, false) + ); + assert_eq!( + "-rw-r--r--", + display_permissions_unix(S_IFREG | 0o644, true) + ); + assert_eq!( + "srw-r-----", + display_permissions_unix(S_IFSOCK | 0o640, true) + ); + assert_eq!( + "lrw-r-xr-x", + display_permissions_unix(S_IFLNK | 0o655, true) + ); + assert_eq!("?rw-r-xr-x", display_permissions_unix(0o655, true)); + + assert_eq!( + "brwSr-xr-x", + display_permissions_unix(S_IFBLK | S_ISUID as mode_t | 0o655, true) + ); + assert_eq!( + "brwsr-xr-x", + display_permissions_unix(S_IFBLK | S_ISUID as mode_t | 0o755, true) + ); + + assert_eq!( + "prw---sr--", + display_permissions_unix(S_IFIFO | S_ISGID as mode_t | 0o614, true) + ); + assert_eq!( + "prw---Sr--", + display_permissions_unix(S_IFIFO | S_ISGID as mode_t | 0o604, true) + ); + + assert_eq!( + "c---r-xr-t", + display_permissions_unix(S_IFCHR | S_ISVTX as mode_t | 0o055, true) + ); + assert_eq!( + "c---r-xr-T", + display_permissions_unix(S_IFCHR | S_ISVTX as mode_t | 0o054, true) + ); + } } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 7b7e990f4..5c4e62610 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -9,42 +9,6 @@ pub use self::stat::*; mod test_fsext { use super::*; - #[test] - fn test_access() { - assert_eq!("drwxr-xr-x", pretty_access(S_IFDIR | 0o755)); - assert_eq!("-rw-r--r--", pretty_access(S_IFREG | 0o644)); - assert_eq!("srw-r-----", pretty_access(S_IFSOCK | 0o640)); - assert_eq!("lrw-r-xr-x", pretty_access(S_IFLNK | 0o655)); - assert_eq!("?rw-r-xr-x", pretty_access(0o655)); - - assert_eq!( - "brwSr-xr-x", - pretty_access(S_IFBLK | S_ISUID as mode_t | 0o655) - ); - assert_eq!( - "brwsr-xr-x", - pretty_access(S_IFBLK | S_ISUID as mode_t | 0o755) - ); - - assert_eq!( - "prw---sr--", - pretty_access(S_IFIFO | S_ISGID as mode_t | 0o614) - ); - assert_eq!( - "prw---Sr--", - pretty_access(S_IFIFO | S_ISGID as mode_t | 0o604) - ); - - assert_eq!( - "c---r-xr-t", - pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o055) - ); - assert_eq!( - "c---r-xr-T", - pretty_access(S_IFCHR | S_ISVTX as mode_t | 0o054) - ); - } - #[test] fn test_file_type() { assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); From 525f71badafd5191f202ce7fdf95d4b9ceb2a208 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Wed, 5 May 2021 20:59:37 -0400 Subject: [PATCH 263/399] wc: rm leading space when printing multiple counts Remove the leading space from the output of `wc` when printing two or more types of counts. Fixes #2173. --- src/uu/wc/src/wc.rs | 55 ++++++++++++++++++++++++++++++++-------- tests/by-util/test_wc.rs | 34 +++++++++---------------- 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 3b70856fa..43ce11aa8 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -323,7 +323,12 @@ fn wc(files: Vec, settings: &Settings) -> Result<(), u32> { error_count += 1; WordCount::default() }); - max_width = max(max_width, word_count.bytes.to_string().len() + 1); + // Compute the number of digits needed to display the number + // of bytes in the file. Even if the settings indicate that we + // won't *display* the number of bytes, we still use the + // number of digits in the byte count as the width when + // formatting each count as a string for output. + max_width = max(max_width, word_count.bytes.to_string().len()); total_word_count += word_count; results.push(word_count.with_title(path)); } @@ -364,24 +369,54 @@ fn print_stats( min_width = 0; } + let mut is_first: bool = true; + if settings.show_lines { - write!(stdout_lock, "{:1$}", result.count.lines, min_width)?; + if is_first { + write!(stdout_lock, "{:1$}", result.count.lines, min_width)?; + } else { + write!(stdout_lock, " {:1$}", result.count.lines, min_width)?; + } + is_first = false; } if settings.show_words { - write!(stdout_lock, "{:1$}", result.count.words, min_width)?; + if is_first { + write!(stdout_lock, "{:1$}", result.count.words, min_width)?; + } else { + write!(stdout_lock, " {:1$}", result.count.words, min_width)?; + } + is_first = false; } if settings.show_bytes { - write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; + if is_first { + write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; + } else { + write!(stdout_lock, " {:1$}", result.count.bytes, min_width)?; + } + is_first = false; } if settings.show_chars { - write!(stdout_lock, "{:1$}", result.count.chars, min_width)?; + if is_first { + write!(stdout_lock, "{:1$}", result.count.chars, min_width)?; + } else { + write!(stdout_lock, " {:1$}", result.count.chars, min_width)?; + } + is_first = false; } if settings.show_max_line_length { - write!( - stdout_lock, - "{:1$}", - result.count.max_line_length, min_width - )?; + if is_first { + write!( + stdout_lock, + "{:1$}", + result.count.max_line_length, min_width + )?; + } else { + write!( + stdout_lock, + " {:1$}", + result.count.max_line_length, min_width + )?; + } } if result.title == "-" { diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index a16f1854e..87a86fca4 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -33,7 +33,7 @@ fn test_stdin_default() { new_ucmd!() .pipe_in_fixture("lorem_ipsum.txt") .run() - .stdout_is(" 13 109 772\n"); + .stdout_is(" 13 109 772\n"); } #[test] @@ -42,7 +42,7 @@ fn test_utf8() { .args(&["-lwmcL"]) .pipe_in_fixture("UTF_8_test.txt") .run() - .stdout_is(" 300 4969 22781 22213 79\n"); + .stdout_is(" 300 4969 22781 22213 79\n"); // GNU returns " 300 2086 22219 22781 79" // TODO: we should fix that to match GNU's behavior } @@ -71,7 +71,7 @@ fn test_stdin_all_counts() { .args(&["-c", "-m", "-l", "-L", "-w"]) .pipe_in_fixture("alice_in_wonderland.txt") .run() - .stdout_is(" 5 57 302 302 66\n"); + .stdout_is(" 5 57 302 302 66\n"); } #[test] @@ -79,7 +79,7 @@ fn test_single_default() { new_ucmd!() .arg("moby_dick.txt") .run() - .stdout_is(" 18 204 1115 moby_dick.txt\n"); + .stdout_is(" 18 204 1115 moby_dick.txt\n"); } #[test] @@ -95,7 +95,7 @@ fn test_single_all_counts() { new_ucmd!() .args(&["-c", "-l", "-L", "-m", "-w", "alice_in_wonderland.txt"]) .run() - .stdout_is(" 5 57 302 302 66 alice_in_wonderland.txt\n"); + .stdout_is(" 5 57 302 302 66 alice_in_wonderland.txt\n"); } #[test] @@ -108,64 +108,54 @@ fn test_multiple_default() { ]) .run() .stdout_is( - " 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \ - alice_in_wonderland.txt\n 36 370 2189 total\n", + " 13 109 772 lorem_ipsum.txt\n 18 204 1115 moby_dick.txt\n 5 57 302 \ + alice_in_wonderland.txt\n 36 370 2189 total\n", ); } /// Test for an empty file. #[test] fn test_file_empty() { - // TODO There is a leading space in the output that should be - // removed; see issue #2173. new_ucmd!() .args(&["-clmwL", "emptyfile.txt"]) .run() - .stdout_is(" 0 0 0 0 0 emptyfile.txt\n"); + .stdout_is("0 0 0 0 0 emptyfile.txt\n"); } /// Test for an file containing a single non-whitespace character /// *without* a trailing newline. #[test] fn test_file_single_line_no_trailing_newline() { - // TODO There is a leading space in the output that should be - // removed; see issue #2173. new_ucmd!() .args(&["-clmwL", "notrailingnewline.txt"]) .run() - .stdout_is(" 1 1 2 2 1 notrailingnewline.txt\n"); + .stdout_is("1 1 2 2 1 notrailingnewline.txt\n"); } /// Test for a file that has 100 empty lines (that is, the contents of /// the file are the newline character repeated one hundred times). #[test] fn test_file_many_empty_lines() { - // TODO There is a leading space in the output that should be - // removed; see issue #2173. new_ucmd!() .args(&["-clmwL", "manyemptylines.txt"]) .run() - .stdout_is(" 100 0 100 100 0 manyemptylines.txt\n"); + .stdout_is("100 0 100 100 0 manyemptylines.txt\n"); } /// Test for a file that has one long line comprising only spaces. #[test] fn test_file_one_long_line_only_spaces() { - // TODO There is a leading space in the output that should be - // removed; see issue #2173. new_ucmd!() .args(&["-clmwL", "onelongemptyline.txt"]) .run() - .stdout_is(" 1 0 10001 10001 10000 onelongemptyline.txt\n"); + .stdout_is(" 1 0 10001 10001 10000 onelongemptyline.txt\n"); } /// Test for a file that has one long line comprising a single "word". #[test] fn test_file_one_long_word() { - // TODO There is a leading space in the output that should be - // removed; see issue #2173. new_ucmd!() .args(&["-clmwL", "onelongword.txt"]) .run() - .stdout_is(" 1 1 10001 10001 10000 onelongword.txt\n"); + .stdout_is(" 1 1 10001 10001 10000 onelongword.txt\n"); } From ee43655bdbb836ee6e5d461b75b9dd0a12c65ce5 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 7 May 2021 13:59:31 -0400 Subject: [PATCH 264/399] fixup! wc: rm leading space when printing multiple counts --- src/uu/wc/src/wc.rs | 47 ++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 43ce11aa8..b5f2a273b 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -372,51 +372,42 @@ fn print_stats( let mut is_first: bool = true; if settings.show_lines { - if is_first { - write!(stdout_lock, "{:1$}", result.count.lines, min_width)?; - } else { - write!(stdout_lock, " {:1$}", result.count.lines, min_width)?; + if !is_first { + write!(stdout_lock, " ")?; } + write!(stdout_lock, "{:1$}", result.count.lines, min_width)?; is_first = false; } if settings.show_words { - if is_first { - write!(stdout_lock, "{:1$}", result.count.words, min_width)?; - } else { - write!(stdout_lock, " {:1$}", result.count.words, min_width)?; + if !is_first { + write!(stdout_lock, " ")?; } + write!(stdout_lock, "{:1$}", result.count.words, min_width)?; is_first = false; } if settings.show_bytes { - if is_first { - write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; - } else { - write!(stdout_lock, " {:1$}", result.count.bytes, min_width)?; + if !is_first { + write!(stdout_lock, " ")?; } + write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; is_first = false; } if settings.show_chars { - if is_first { - write!(stdout_lock, "{:1$}", result.count.chars, min_width)?; - } else { - write!(stdout_lock, " {:1$}", result.count.chars, min_width)?; + if !is_first { + write!(stdout_lock, " ")?; } + write!(stdout_lock, "{:1$}", result.count.chars, min_width)?; is_first = false; } if settings.show_max_line_length { - if is_first { - write!( - stdout_lock, - "{:1$}", - result.count.max_line_length, min_width - )?; - } else { - write!( - stdout_lock, - " {:1$}", - result.count.max_line_length, min_width - )?; + if !is_first { + write!(stdout_lock, " ")?; } + write!( + stdout_lock, + "{:1$}", + result.count.max_line_length, min_width + )?; } if result.title == "-" { From a74a729aa8c6f7b0b38b907a1e227b9d5a61bf4d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 8 May 2021 13:13:52 +0200 Subject: [PATCH 265/399] rustfmt the recent change --- tests/by-util/test_df.rs | 2 +- tests/by-util/test_du.rs | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index e3b7141d1..ac3776b96 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -27,7 +27,7 @@ fn test_df_output() { stdout_only("Filesystem Size Used Available Capacity Use% Mounted on \n"); } else { new_ucmd!().arg("-H").arg("-total").succeeds().stdout_only( - "Filesystem Size Used Available Use% Mounted on \n" + "Filesystem Size Used Available Use% Mounted on \n", ); } } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c1b7fcb7b..c72bd02a6 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -57,7 +57,11 @@ fn _du_basics_subdir(s: &str) { fn _du_basics_subdir(s: &str) { assert_eq!(s, "8\tsubdir/deeper\n"); } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] fn _du_basics_subdir(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { @@ -108,7 +112,11 @@ fn _du_soft_link(s: &str) { fn _du_soft_link(s: &str) { assert_eq!(s, "16\tsubdir/links\n"); } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] fn _du_soft_link(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { @@ -153,7 +161,11 @@ fn _du_hard_link(s: &str) { fn _du_hard_link(s: &str) { assert_eq!(s, "16\tsubdir/links\n") } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] fn _du_hard_link(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { @@ -197,7 +209,11 @@ fn _du_d_flag(s: &str) { fn _du_d_flag(s: &str) { assert_eq!(s, "28\t./subdir\n36\t./\n"); } -#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"), not(target_os = "freebsd")))] +#[cfg(all( + not(target_vendor = "apple"), + not(target_os = "windows"), + not(target_os = "freebsd") +))] fn _du_d_flag(s: &str) { // MS-WSL linux has altered expected output if !uucore::os::is_wsl_1() { From 50f4941d4903d13c018a7aeb35eac4f4ebfa103f Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 4 May 2021 19:04:23 -0400 Subject: [PATCH 266/399] wc: refactor WordCount into its own module Move the `WordCount` struct and its implementations into the `wordcount.rs`. --- src/uu/wc/src/wc.rs | 48 ++------------------------------------ src/uu/wc/src/wordcount.rs | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 46 deletions(-) create mode 100644 src/uu/wc/src/wordcount.rs diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index b5f2a273b..8e973ccbd 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -12,8 +12,10 @@ extern crate uucore; mod count_bytes; mod countable; +mod wordcount; use count_bytes::count_bytes_fast; use countable::WordCountable; +use wordcount::{TitledWordCount, WordCount}; use clap::{App, Arg, ArgMatches}; use thiserror::Error; @@ -21,7 +23,6 @@ use thiserror::Error; use std::cmp::max; use std::fs::File; use std::io::{self, Write}; -use std::ops::{Add, AddAssign}; use std::path::Path; use std::str::from_utf8; @@ -82,51 +83,6 @@ impl Settings { } } -#[derive(Debug, Default, Copy, Clone)] -struct WordCount { - bytes: usize, - chars: usize, - lines: usize, - words: usize, - max_line_length: usize, -} - -impl Add for WordCount { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self { - bytes: self.bytes + other.bytes, - chars: self.chars + other.chars, - lines: self.lines + other.lines, - words: self.words + other.words, - max_line_length: max(self.max_line_length, other.max_line_length), - } - } -} - -impl AddAssign for WordCount { - fn add_assign(&mut self, other: Self) { - *self = *self + other - } -} - -impl WordCount { - fn with_title(self, title: &str) -> TitledWordCount { - TitledWordCount { title, count: self } - } -} - -/// This struct supplements the actual word count with a title that is displayed -/// to the user at the end of the program. -/// The reason we don't simply include title in the `WordCount` struct is that -/// it would result in unneccesary copying of `String`. -#[derive(Debug, Default, Clone)] -struct TitledWordCount<'a> { - title: &'a str, - count: WordCount, -} - static ABOUT: &str = "Display newline, word, and byte counts for each FILE, and a total line if more than one FILE is specified."; static VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/uu/wc/src/wordcount.rs b/src/uu/wc/src/wordcount.rs new file mode 100644 index 000000000..38efb216f --- /dev/null +++ b/src/uu/wc/src/wordcount.rs @@ -0,0 +1,47 @@ +use std::cmp::max; +use std::ops::{Add, AddAssign}; + +#[derive(Debug, Default, Copy, Clone)] +pub struct WordCount { + pub bytes: usize, + pub chars: usize, + pub lines: usize, + pub words: usize, + pub max_line_length: usize, +} + +impl Add for WordCount { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + bytes: self.bytes + other.bytes, + chars: self.chars + other.chars, + lines: self.lines + other.lines, + words: self.words + other.words, + max_line_length: max(self.max_line_length, other.max_line_length), + } + } +} + +impl AddAssign for WordCount { + fn add_assign(&mut self, other: Self) { + *self = *self + other + } +} + +impl WordCount { + pub fn with_title(self, title: &str) -> TitledWordCount { + TitledWordCount { title, count: self } + } +} + +/// This struct supplements the actual word count with a title that is displayed +/// to the user at the end of the program. +/// The reason we don't simply include title in the `WordCount` struct is that +/// it would result in unneccesary copying of `String`. +#[derive(Debug, Default, Clone)] +pub struct TitledWordCount<'a> { + pub title: &'a str, + pub count: WordCount, +} From ba8f4ea67041c500a2ca55fc09e87408d8a531f2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 4 May 2021 22:13:28 -0400 Subject: [PATCH 267/399] wc: move counting code into WordCount::from_line() Refactor the counting code from the inner loop of the `wc` program into the `WordCount::from_line()` associated function. This commit also splits that function up into other helper functions that encapsulate decoding characters and finding word boundaries from raw bytes. This commit also implements the `Sum` trait for the `WordCount` struct, so that we can simply call `sum()` on an iterator that yields `WordCount` instances. --- src/uu/wc/src/wc.rs | 73 +++++---------------------------- src/uu/wc/src/wordcount.rs | 84 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 62 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 8e973ccbd..33b2ba5ec 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -24,7 +24,6 @@ use std::cmp::max; use std::fs::File; use std::io::{self, Write}; use std::path::Path; -use std::str::from_utf8; #[derive(Error, Debug)] pub enum WcError { @@ -163,18 +162,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } -const CR: u8 = b'\r'; -const LF: u8 = b'\n'; -const SPACE: u8 = b' '; -const TAB: u8 = b'\t'; -const SYN: u8 = 0x16_u8; -const FF: u8 = 0x0C_u8; - -#[inline(always)] -fn is_word_separator(byte: u8) -> bool { - byte == SPACE || byte == TAB || byte == CR || byte == SYN || byte == FF -} - fn word_count_from_reader( mut reader: T, settings: &Settings, @@ -195,58 +182,20 @@ fn word_count_from_reader( // we do not need to decode the byte stream if we're only counting bytes/newlines let decode_chars = settings.show_chars || settings.show_words || settings.show_max_line_length; - let mut line_count: usize = 0; - let mut word_count: usize = 0; - let mut byte_count: usize = 0; - let mut char_count: usize = 0; - let mut longest_line_length: usize = 0; - let mut ends_lf: bool; - - // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. - // hence the option wrapped in a result here - for line_result in reader.lines() { - let raw_line = match line_result { - Ok(l) => l, + // Sum the WordCount for each line. Show a warning for each line + // that results in an IO error when trying to read it. + let total = reader + .lines() + .filter_map(|res| match res { + Ok(line) => Some(line), Err(e) => { show_warning!("Error while reading {}: {}", path, e); - continue; + None } - }; - - // GNU 'wc' only counts lines that end in LF as lines - ends_lf = *raw_line.last().unwrap() == LF; - line_count += ends_lf as usize; - - byte_count += raw_line.len(); - - if decode_chars { - // try and convert the bytes to UTF-8 first - let current_char_count; - match from_utf8(&raw_line[..]) { - Ok(line) => { - word_count += line.split_whitespace().count(); - current_char_count = line.chars().count(); - } - Err(..) => { - word_count += raw_line.split(|&x| is_word_separator(x)).count(); - current_char_count = raw_line.iter().filter(|c| c.is_ascii()).count() - } - } - char_count += current_char_count; - if current_char_count > longest_line_length { - // -L is a GNU 'wc' extension so same behavior on LF - longest_line_length = current_char_count - (ends_lf as usize); - } - } - } - - Ok(WordCount { - bytes: byte_count, - chars: char_count, - lines: line_count, - words: word_count, - max_line_length: longest_line_length, - }) + }) + .map(|line| WordCount::from_line(&line, decode_chars)) + .sum(); + Ok(total) } fn word_count_from_path(path: &str, settings: &Settings) -> WcResult { diff --git a/src/uu/wc/src/wordcount.rs b/src/uu/wc/src/wordcount.rs index 38efb216f..785e57eff 100644 --- a/src/uu/wc/src/wordcount.rs +++ b/src/uu/wc/src/wordcount.rs @@ -1,5 +1,19 @@ use std::cmp::max; +use std::iter::Sum; use std::ops::{Add, AddAssign}; +use std::str::from_utf8; + +const CR: u8 = b'\r'; +const LF: u8 = b'\n'; +const SPACE: u8 = b' '; +const TAB: u8 = b'\t'; +const SYN: u8 = 0x16_u8; +const FF: u8 = 0x0C_u8; + +#[inline(always)] +fn is_word_separator(byte: u8) -> bool { + byte == SPACE || byte == TAB || byte == CR || byte == SYN || byte == FF +} #[derive(Debug, Default, Copy, Clone)] pub struct WordCount { @@ -30,10 +44,80 @@ impl AddAssign for WordCount { } } +impl Sum for WordCount { + fn sum(iter: I) -> WordCount + where + I: Iterator, + { + iter.fold(WordCount::default(), |acc, x| acc + x) + } +} + impl WordCount { + /// Count the characters and whitespace-separated words in the given bytes. + /// + /// `line` is a slice of bytes that will be decoded as ASCII characters. + fn ascii_word_and_char_count(line: &[u8]) -> (usize, usize) { + let word_count = line.split(|&x| is_word_separator(x)).count(); + let char_count = line.iter().filter(|c| c.is_ascii()).count(); + (word_count, char_count) + } + + /// Create a [`WordCount`] from a sequence of bytes representing a line. + /// + /// If the last byte of `line` encodes a newline character (`\n`), + /// then the [`lines`] field will be set to 1. Otherwise, it will + /// be set to 0. The [`bytes`] field is simply the length of + /// `line`. + /// + /// If `decode_chars` is `false`, the [`chars`] and [`words`] + /// fields will be set to 0. If it is `true`, this function will + /// attempt to decode the bytes first as UTF-8, and failing that, + /// as ASCII. + pub fn from_line(line: &[u8], decode_chars: bool) -> WordCount { + // GNU 'wc' only counts lines that end in LF as lines + let lines = (*line.last().unwrap() == LF) as usize; + let bytes = line.len(); + let (words, chars) = if decode_chars { + WordCount::word_and_char_count(line) + } else { + (0, 0) + }; + // -L is a GNU 'wc' extension so same behavior on LF + let max_line_length = if chars > 0 { chars - lines } else { 0 }; + WordCount { + bytes, + chars, + lines, + words, + max_line_length, + } + } + + /// Count the UTF-8 characters and words in the given string slice. + /// + /// `s` is a string slice that is assumed to be a UTF-8 string. + fn utf8_word_and_char_count(s: &str) -> (usize, usize) { + let word_count = s.split_whitespace().count(); + let char_count = s.chars().count(); + (word_count, char_count) + } + pub fn with_title(self, title: &str) -> TitledWordCount { TitledWordCount { title, count: self } } + + /// Count the characters and words in the given slice of bytes. + /// + /// `line` is a slice of bytes that will be decoded as UTF-8 + /// characters, or if that fails, as ASCII characters. + fn word_and_char_count(line: &[u8]) -> (usize, usize) { + // try and convert the bytes to UTF-8 first + match from_utf8(line) { + Ok(s) => WordCount::utf8_word_and_char_count(s), + Err(..) => WordCount::ascii_word_and_char_count(line), + } + } } /// This struct supplements the actual word count with a title that is displayed From a9ac7af9e14a9baa674a43e4164f85234a2d952b Mon Sep 17 00:00:00 2001 From: Samuel Ainsworth Date: Tue, 4 May 2021 00:21:39 -0700 Subject: [PATCH 268/399] Simplify parsing of --bytes for the split command --- src/uu/split/src/split.rs | 60 +++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4f80e25a3..445c1f205 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -261,31 +261,41 @@ struct ByteSplitter { impl ByteSplitter { fn new(settings: &Settings) -> ByteSplitter { - let mut strategy_param: Vec = settings.strategy_param.chars().collect(); - let suffix = strategy_param.pop().unwrap(); - let multiplier = match suffix { - '0'..='9' => 1usize, - 'b' => 512usize, - 'k' => 1024usize, - 'm' => 1024usize * 1024usize, - _ => crash!(1, "invalid number of bytes"), - }; - let n = if suffix.is_alphabetic() { - match strategy_param - .iter() - .cloned() - .collect::() - .parse::() - { - Ok(a) => a, - Err(e) => crash!(1, "invalid number of bytes: {}", e), - } - } else { - match settings.strategy_param.parse::() { - Ok(a) => a, - Err(e) => crash!(1, "invalid number of bytes: {}", e), - } - }; + // These multipliers are the same as supported by GNU coreutils with the + // exception of zetabytes (2^70) and yottabytes (2^80) as they overflow + // standard machine usize (2^64), so we disable for now. Note however + // that they are supported by the GNU coreutils split. Ignored for now. + let modifiers: Vec<(&str, usize)> = vec![ + ("K", 1024usize), + ("M", 1024 * 1024), + ("G", 1024 * 1024 * 1024), + ("T", 1024 * 1024 * 1024 * 1024), + ("P", 1024 * 1024 * 1024 * 1024 * 1024), + ("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + // ("Z", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + // ("Y", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + ("KB", 1000), + ("MB", 1000 * 1000), + ("GB", 1000 * 1000 * 1000), + ("TB", 1000 * 1000 * 1000 * 1000), + ("PB", 1000 * 1000 * 1000 * 1000 * 1000), + ("EB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000), + // ("ZB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), + // ("YB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), + ]; + + // This sequential find is acceptable since none of the modifiers are + // suffixes of any other modifiers, a la Huffman codes. + let (suffix, multiplier) = modifiers + .iter() + .find(|(suffix, _)| settings.strategy_param.ends_with(suffix)) + .unwrap_or(&("", 1)); + + // Try to parse the actual numeral. + let n = &settings.strategy_param[0..(settings.strategy_param.len() - suffix.len())] + .parse::() + .unwrap_or_else(|_| crash!(1, "invalid number of bytes")); + ByteSplitter { saved_bytes_to_write: n * multiplier, bytes_to_write: n * multiplier, From 7c1395366e151ade7ac3fc3c056e55150d62dedc Mon Sep 17 00:00:00 2001 From: Samuel Ainsworth Date: Tue, 4 May 2021 04:01:01 -0700 Subject: [PATCH 269/399] Fix split's handling of non-UTF-8 files --- src/uu/split/src/split.rs | 191 +++++++++++++++++++----------------- tests/by-util/test_split.rs | 98 +++++++++++++----- 2 files changed, 176 insertions(+), 113 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 445c1f205..128ef73c6 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -13,11 +13,11 @@ extern crate uucore; mod platform; use clap::{App, Arg}; -use std::char; use std::env; use std::fs::File; -use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; +use std::{char, fs::remove_file}; static NAME: &str = "split"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -213,50 +213,65 @@ struct Settings { verbose: bool, } -struct SplitControl { - current_line: String, // Don't touch - request_new_file: bool, // Splitter implementation requests new file -} - trait Splitter { - // Consume the current_line and return the consumed string - fn consume(&mut self, _: &mut SplitControl) -> String; + // Consume as much as possible from `reader` so as to saturate `writer`. + // Equivalent to finishing one of the part files. Returns the number of + // bytes that have been moved. + fn consume( + &mut self, + reader: &mut BufReader>, + writer: &mut BufWriter>, + ) -> usize; } struct LineSplitter { - saved_lines_to_write: usize, - lines_to_write: usize, + lines_per_split: usize, } impl LineSplitter { fn new(settings: &Settings) -> LineSplitter { - let n = match settings.strategy_param.parse() { - Ok(a) => a, - Err(e) => crash!(1, "invalid number of lines: {}", e), - }; LineSplitter { - saved_lines_to_write: n, - lines_to_write: n, + lines_per_split: settings + .strategy_param + .parse() + .unwrap_or_else(|e| crash!(1, "invalid number of lines: {}", e)), } } } impl Splitter for LineSplitter { - fn consume(&mut self, control: &mut SplitControl) -> String { - self.lines_to_write -= 1; - if self.lines_to_write == 0 { - self.lines_to_write = self.saved_lines_to_write; - control.request_new_file = true; + fn consume( + &mut self, + reader: &mut BufReader>, + writer: &mut BufWriter>, + ) -> usize { + let mut bytes_consumed = 0usize; + let mut buffer = String::with_capacity(1024); + for _ in 0..self.lines_per_split { + let bytes_read = reader + .read_line(&mut buffer) + .unwrap_or_else(|_| crash!(1, "error reading bytes from input file")); + // If we ever read 0 bytes then we know we've hit EOF. + if bytes_read == 0 { + return bytes_consumed; + } + + writer + .write_all(buffer.as_bytes()) + .unwrap_or_else(|_| crash!(1, "error writing bytes to output file")); + // Empty out the String buffer since `read_line` appends instead of + // replaces. + buffer.clear(); + + bytes_consumed += bytes_read; } - control.current_line.clone() + + bytes_consumed } } struct ByteSplitter { - saved_bytes_to_write: usize, - bytes_to_write: usize, - break_on_line_end: bool, - require_whole_line: bool, + bytes_per_split: usize, } impl ByteSplitter { @@ -294,36 +309,44 @@ impl ByteSplitter { // Try to parse the actual numeral. let n = &settings.strategy_param[0..(settings.strategy_param.len() - suffix.len())] .parse::() - .unwrap_or_else(|_| crash!(1, "invalid number of bytes")); + .unwrap_or_else(|e| crash!(1, "invalid number of bytes: {}", e)); ByteSplitter { - saved_bytes_to_write: n * multiplier, - bytes_to_write: n * multiplier, - break_on_line_end: settings.strategy == "b", - require_whole_line: false, + bytes_per_split: n * multiplier, } } } impl Splitter for ByteSplitter { - fn consume(&mut self, control: &mut SplitControl) -> String { - let line = control.current_line.clone(); - let n = std::cmp::min(line.chars().count(), self.bytes_to_write); - if self.require_whole_line && n < line.chars().count() { - self.bytes_to_write = self.saved_bytes_to_write; - control.request_new_file = true; - self.require_whole_line = false; - return "".to_owned(); + fn consume( + &mut self, + reader: &mut BufReader>, + writer: &mut BufWriter>, + ) -> usize { + // We buffer reads and writes. We proceed until `bytes_consumed` is + // equal to `self.bytes_per_split` or we reach EOF. + let mut bytes_consumed = 0usize; + const BUFFER_SIZE: usize = 1024; + let mut buffer = [0u8; BUFFER_SIZE]; + while bytes_consumed < self.bytes_per_split { + // Don't overshoot `self.bytes_per_split`! + let bytes_desired = std::cmp::min(BUFFER_SIZE, self.bytes_per_split - bytes_consumed); + let bytes_read = reader + .read(&mut buffer[0..bytes_desired]) + .unwrap_or_else(|_| crash!(1, "error reading bytes from input file")); + // If we ever read 0 bytes then we know we've hit EOF. + if bytes_read == 0 { + return bytes_consumed; + } + + writer + .write_all(&buffer[0..bytes_read]) + .unwrap_or_else(|_| crash!(1, "error writing bytes to output file")); + + bytes_consumed += bytes_read; } - self.bytes_to_write -= n; - if n == 0 { - self.bytes_to_write = self.saved_bytes_to_write; - control.request_new_file = true; - } - if self.break_on_line_end && n == line.chars().count() { - self.require_whole_line = self.break_on_line_end; - } - line[..n].to_owned() + + bytes_consumed } } @@ -363,14 +386,13 @@ fn split(settings: &Settings) -> i32 { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box } else { - let r = match File::open(Path::new(&settings.input)) { - Ok(a) => a, - Err(_) => crash!( + let r = File::open(Path::new(&settings.input)).unwrap_or_else(|_| { + crash!( 1, "cannot open '{}' for reading: No such file or directory", settings.input - ), - }; + ) + }); Box::new(r) as Box }); @@ -380,48 +402,39 @@ fn split(settings: &Settings) -> i32 { a => crash!(1, "strategy {} not supported", a), }; - let mut control = SplitControl { - current_line: "".to_owned(), // Request new line - request_new_file: true, // Request new file - }; - - let mut writer = BufWriter::new(Box::new(stdout()) as Box); let mut fileno = 0; loop { - if control.current_line.chars().count() == 0 { - match reader.read_line(&mut control.current_line) { - Ok(0) | Err(_) => break, - _ => {} + // Get a new part file set up, and construct `writer` for it. + let mut filename = settings.prefix.clone(); + filename.push_str( + if settings.numeric_suffix { + num_prefix(fileno, settings.suffix_length) + } else { + str_prefix(fileno, settings.suffix_length) } - } - if control.request_new_file { - let mut filename = settings.prefix.clone(); - filename.push_str( - if settings.numeric_suffix { - num_prefix(fileno, settings.suffix_length) - } else { - str_prefix(fileno, settings.suffix_length) - } - .as_ref(), - ); - filename.push_str(settings.additional_suffix.as_ref()); + .as_ref(), + ); + filename.push_str(settings.additional_suffix.as_ref()); + let mut writer = platform::instantiate_current_writer(&settings.filter, filename.as_str()); - crash_if_err!(1, writer.flush()); - fileno += 1; - writer = platform::instantiate_current_writer(&settings.filter, filename.as_str()); - control.request_new_file = false; - if settings.verbose { - println!("creating file '{}'", filename); + let bytes_consumed = splitter.consume(&mut reader, &mut writer); + writer + .flush() + .unwrap_or_else(|e| crash!(1, "error flushing to output file: {}", e)); + + // If we didn't write anything we should clean up the empty file, and + // break from the loop. + if bytes_consumed == 0 { + // The output file is only ever created if filter's aren't used. + // Complicated, I know... + if settings.filter.is_none() { + remove_file(filename) + .unwrap_or_else(|e| crash!(1, "error removing empty file: {}", e)); } + break; } - let consumed = splitter.consume(&mut control); - crash_if_err!(1, writer.write_all(consumed.as_bytes())); - - let advance = consumed.chars().count(); - let clone = control.current_line.clone(); - let sl = clone; - control.current_line = sl[advance..sl.chars().count()].to_owned(); + fileno += 1; } 0 } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 521cbbe9a..37856f419 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -4,11 +4,15 @@ extern crate regex; use self::rand::{thread_rng, Rng}; use self::regex::Regex; use crate::common::util::*; +use rand::SeedableRng; #[cfg(not(windows))] use std::env; -use std::fs::{read_dir, File}; use std::io::Write; use std::path::Path; +use std::{ + fs::{read_dir, File}, + io::BufWriter, +}; fn random_chars(n: usize) -> String { thread_rng() @@ -58,7 +62,7 @@ impl Glob { files.sort(); let mut data: Vec = vec![]; for name in &files { - data.extend(self.directory.read(name).into_bytes()); + data.extend(self.directory.read_bytes(name)); } data } @@ -81,20 +85,30 @@ impl RandomFile { } fn add_bytes(&mut self, bytes: usize) { - let chunk_size: usize = if bytes >= 1024 { 1024 } else { bytes }; - let mut n = bytes; - while n > chunk_size { - let _ = write!(self.inner, "{}", random_chars(chunk_size)); - n -= chunk_size; + // Note that just writing random characters isn't enough to cover all + // cases. We need truly random bytes. + let mut writer = BufWriter::new(&self.inner); + + // Seed the rng so as to avoid spurious test failures. + let mut rng = rand::rngs::StdRng::seed_from_u64(123); + let mut buffer = [0; 1024]; + let mut remaining_size = bytes; + + while remaining_size > 0 { + let to_write = std::cmp::min(remaining_size, buffer.len()); + let buf = &mut buffer[..to_write]; + rng.fill(buf); + writer.write(buf).unwrap(); + + remaining_size -= to_write; } - let _ = write!(self.inner, "{}", random_chars(n)); } /// Add n lines each of size `RandomFile::LINESIZE` fn add_lines(&mut self, lines: usize) { let mut n = lines; while n > 0 { - let _ = writeln!(self.inner, "{}", random_chars(RandomFile::LINESIZE)); + writeln!(self.inner, "{}", random_chars(RandomFile::LINESIZE)).unwrap(); n -= 1; } } @@ -104,18 +118,18 @@ impl RandomFile { fn test_split_default() { let (at, mut ucmd) = at_and_ucmd!(); let name = "split_default"; - let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); RandomFile::new(&at, name).add_lines(2000); ucmd.args(&[name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + assert_eq!(glob.collate(), at.read_bytes(name)); } #[test] fn test_split_numeric_prefixed_chunks_by_bytes() { let (at, mut ucmd) = at_and_ucmd!(); let name = "split_num_prefixed_chunks_by_bytes"; - let glob = Glob::new(&at, ".", r"a\d\d$"); RandomFile::new(&at, name).add_bytes(10000); ucmd.args(&[ "-d", // --numeric-suffixes @@ -123,52 +137,86 @@ fn test_split_numeric_prefixed_chunks_by_bytes() { "1000", name, "a", ]) .succeeds(); + + let glob = Glob::new(&at, ".", r"a\d\d$"); assert_eq!(glob.count(), 10); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + for filename in glob.collect() { + assert_eq!(glob.directory.metadata(&filename).len(), 1000); + } + assert_eq!(glob.collate(), at.read_bytes(name)); } #[test] fn test_split_str_prefixed_chunks_by_bytes() { let (at, mut ucmd) = at_and_ucmd!(); let name = "split_str_prefixed_chunks_by_bytes"; - let glob = Glob::new(&at, ".", r"b[[:alpha:]][[:alpha:]]$"); RandomFile::new(&at, name).add_bytes(10000); + // Important that this is less than 1024 since that's our internal buffer + // size. Good to test that we don't overshoot. ucmd.args(&["-b", "1000", name, "b"]).succeeds(); + + let glob = Glob::new(&at, ".", r"b[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 10); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + for filename in glob.collect() { + assert_eq!(glob.directory.metadata(&filename).len(), 1000); + } + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +// This is designed to test what happens when the desired part size is not a +// multiple of the buffer size and we hopefully don't overshoot the desired part +// size. +#[test] +fn test_split_bytes_prime_part_size() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_bytes_prime_part_size"; + RandomFile::new(&at, name).add_bytes(10000); + // 1753 is prime and greater than the buffer size, 1024. + ucmd.args(&["-b", "1753", name, "b"]).succeeds(); + + let glob = Glob::new(&at, ".", r"b[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 6); + for i in 0..5 { + assert_eq!(glob.directory.metadata(&glob.collect()[i]).len(), 1753); + } + assert_eq!(glob.directory.metadata(&glob.collect()[5]).len(), 1235); + assert_eq!(glob.collate(), at.read_bytes(name)); } #[test] fn test_split_num_prefixed_chunks_by_lines() { let (at, mut ucmd) = at_and_ucmd!(); let name = "split_num_prefixed_chunks_by_lines"; - let glob = Glob::new(&at, ".", r"c\d\d$"); RandomFile::new(&at, name).add_lines(10000); ucmd.args(&["-d", "-l", "1000", name, "c"]).succeeds(); + + let glob = Glob::new(&at, ".", r"c\d\d$"); assert_eq!(glob.count(), 10); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + assert_eq!(glob.collate(), at.read_bytes(name)); } #[test] fn test_split_str_prefixed_chunks_by_lines() { let (at, mut ucmd) = at_and_ucmd!(); let name = "split_str_prefixed_chunks_by_lines"; - let glob = Glob::new(&at, ".", r"d[[:alpha:]][[:alpha:]]$"); RandomFile::new(&at, name).add_lines(10000); ucmd.args(&["-l", "1000", name, "d"]).succeeds(); + + let glob = Glob::new(&at, ".", r"d[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 10); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + assert_eq!(glob.collate(), at.read_bytes(name)); } #[test] fn test_split_additional_suffix() { let (at, mut ucmd) = at_and_ucmd!(); let name = "split_additional_suffix"; - let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]].txt$"); RandomFile::new(&at, name).add_lines(2000); ucmd.args(&["--additional-suffix", ".txt", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]].txt$"); assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + assert_eq!(glob.collate(), at.read_bytes(name)); } // note: the test_filter* tests below are unix-only @@ -182,15 +230,16 @@ fn test_filter() { // like `test_split_default()` but run a command before writing let (at, mut ucmd) = at_and_ucmd!(); let name = "filtered"; - let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); let n_lines = 3; RandomFile::new(&at, name).add_lines(n_lines); // change all characters to 'i' ucmd.args(&["--filter=sed s/./i/g > $FILE", name]) .succeeds(); + // assert all characters are 'i' / no character is not 'i' // (assert that command succeded) + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert!( glob.collate().iter().find(|&&c| { // is not i @@ -209,7 +258,6 @@ fn test_filter_with_env_var_set() { // implemented like `test_split_default()` but run a command before writing let (at, mut ucmd) = at_and_ucmd!(); let name = "filtered"; - let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); let n_lines = 3; RandomFile::new(&at, name).add_lines(n_lines); @@ -217,7 +265,9 @@ fn test_filter_with_env_var_set() { env::set_var("FILE", &env_var_value); ucmd.args(&[format!("--filter={}", "cat > $FILE").as_str(), name]) .succeeds(); - assert_eq!(glob.collate(), at.read(name).into_bytes()); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.collate(), at.read_bytes(name)); assert!(env::var("FILE").unwrap_or("var was unset".to_owned()) == env_var_value); } From b8a3a8995f875fb4615fd3bbf51e0820cf6bdc95 Mon Sep 17 00:00:00 2001 From: Samuel Ainsworth Date: Tue, 4 May 2021 15:19:35 -0700 Subject: [PATCH 270/399] Fix test_split_bytes_prime_part_size --- tests/by-util/test_split.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 37856f419..d83de4323 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -176,10 +176,13 @@ fn test_split_bytes_prime_part_size() { let glob = Glob::new(&at, ".", r"b[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 6); + let mut fns = glob.collect(); + // glob.collect() is not guaranteed to return in sorted order, so we sort. + fns.sort(); for i in 0..5 { - assert_eq!(glob.directory.metadata(&glob.collect()[i]).len(), 1753); + assert_eq!(glob.directory.metadata(&fns[i]).len(), 1753); } - assert_eq!(glob.directory.metadata(&glob.collect()[5]).len(), 1235); + assert_eq!(glob.directory.metadata(&fns[5]).len(), 1235); assert_eq!(glob.collate(), at.read_bytes(name)); } From bacad8ed93d00315746fdc5142180a8517f2731c Mon Sep 17 00:00:00 2001 From: Samuel Ainsworth Date: Tue, 4 May 2021 15:21:35 -0700 Subject: [PATCH 271/399] Use u128 instead of usize for large numbers, and consistency across architectures --- src/uu/split/src/split.rs | 47 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 128ef73c6..b2d141b8a 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -221,7 +221,7 @@ trait Splitter { &mut self, reader: &mut BufReader>, writer: &mut BufWriter>, - ) -> usize; + ) -> u128; } struct LineSplitter { @@ -244,8 +244,8 @@ impl Splitter for LineSplitter { &mut self, reader: &mut BufReader>, writer: &mut BufWriter>, - ) -> usize { - let mut bytes_consumed = 0usize; + ) -> u128 { + let mut bytes_consumed = 0u128; let mut buffer = String::with_capacity(1024); for _ in 0..self.lines_per_split { let bytes_read = reader @@ -263,7 +263,7 @@ impl Splitter for LineSplitter { // replaces. buffer.clear(); - bytes_consumed += bytes_read; + bytes_consumed += bytes_read as u128; } bytes_consumed @@ -271,32 +271,29 @@ impl Splitter for LineSplitter { } struct ByteSplitter { - bytes_per_split: usize, + bytes_per_split: u128, } impl ByteSplitter { fn new(settings: &Settings) -> ByteSplitter { - // These multipliers are the same as supported by GNU coreutils with the - // exception of zetabytes (2^70) and yottabytes (2^80) as they overflow - // standard machine usize (2^64), so we disable for now. Note however - // that they are supported by the GNU coreutils split. Ignored for now. - let modifiers: Vec<(&str, usize)> = vec![ - ("K", 1024usize), + // These multipliers are the same as supported by GNU coreutils. + let modifiers: Vec<(&str, u128)> = vec![ + ("K", 1024u128), ("M", 1024 * 1024), ("G", 1024 * 1024 * 1024), ("T", 1024 * 1024 * 1024 * 1024), ("P", 1024 * 1024 * 1024 * 1024 * 1024), ("E", 1024 * 1024 * 1024 * 1024 * 1024 * 1024), - // ("Z", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), - // ("Y", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + ("Z", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), + ("Y", 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), ("KB", 1000), ("MB", 1000 * 1000), ("GB", 1000 * 1000 * 1000), ("TB", 1000 * 1000 * 1000 * 1000), ("PB", 1000 * 1000 * 1000 * 1000 * 1000), ("EB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000), - // ("ZB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), - // ("YB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), + ("ZB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), + ("YB", 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000), ]; // This sequential find is acceptable since none of the modifiers are @@ -308,7 +305,7 @@ impl ByteSplitter { // Try to parse the actual numeral. let n = &settings.strategy_param[0..(settings.strategy_param.len() - suffix.len())] - .parse::() + .parse::() .unwrap_or_else(|e| crash!(1, "invalid number of bytes: {}", e)); ByteSplitter { @@ -322,15 +319,23 @@ impl Splitter for ByteSplitter { &mut self, reader: &mut BufReader>, writer: &mut BufWriter>, - ) -> usize { + ) -> u128 { // We buffer reads and writes. We proceed until `bytes_consumed` is // equal to `self.bytes_per_split` or we reach EOF. - let mut bytes_consumed = 0usize; + let mut bytes_consumed = 0u128; const BUFFER_SIZE: usize = 1024; let mut buffer = [0u8; BUFFER_SIZE]; while bytes_consumed < self.bytes_per_split { - // Don't overshoot `self.bytes_per_split`! - let bytes_desired = std::cmp::min(BUFFER_SIZE, self.bytes_per_split - bytes_consumed); + // Don't overshoot `self.bytes_per_split`! Note: Using std::cmp::min + // doesn't really work since we have to get types to match which + // can't be done in a way that keeps all conversions safe. + let bytes_desired = if (BUFFER_SIZE as u128) <= self.bytes_per_split - bytes_consumed { + BUFFER_SIZE + } else { + // This is a safe conversion since the difference must be less + // than BUFFER_SIZE in this branch. + (self.bytes_per_split - bytes_consumed) as usize + }; let bytes_read = reader .read(&mut buffer[0..bytes_desired]) .unwrap_or_else(|_| crash!(1, "error reading bytes from input file")); @@ -343,7 +348,7 @@ impl Splitter for ByteSplitter { .write_all(&buffer[0..bytes_read]) .unwrap_or_else(|_| crash!(1, "error writing bytes to output file")); - bytes_consumed += bytes_read; + bytes_consumed += bytes_read as u128; } bytes_consumed From 2ff9cc657039739b3fa22c5a2c47f02544faec3a Mon Sep 17 00:00:00 2001 From: Samuel Ainsworth Date: Tue, 4 May 2021 15:28:46 -0700 Subject: [PATCH 272/399] Typo in comment --- src/uu/split/src/split.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index b2d141b8a..726c9b8cd 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -430,7 +430,7 @@ fn split(settings: &Settings) -> i32 { // If we didn't write anything we should clean up the empty file, and // break from the loop. if bytes_consumed == 0 { - // The output file is only ever created if filter's aren't used. + // The output file is only ever created if --filter isn't used. // Complicated, I know... if settings.filter.is_none() { remove_file(filename) From d686f7e48f929c7fccfade292dce0a1dd2ff8eea Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 8 May 2021 22:31:53 +0200 Subject: [PATCH 273/399] sort: improve comments --- src/uu/sort/src/sort.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 02e54baf8..776f71058 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -582,11 +582,14 @@ impl FieldSelector { self.from.field != 1 || self.from.char == 0 || self.to.is_some() } - fn get_selection(&self, line: &str, fields: Option<&[Field]>) -> Selection { - let mut range = SelectionRange::new(self.get_range(&line, fields)); + /// Get the selection that corresponds to this selector for the line. + /// If needs_fields returned false, tokens may be None. + fn get_selection(&self, line: &str, tokens: Option<&[Field]>) -> Selection { + let mut range = SelectionRange::new(self.get_range(&line, tokens)); let num_cache = if self.settings.mode == SortMode::Numeric || self.settings.mode == SortMode::HumanNumeric { + // Parse NumInfo for this number. let (info, num_range) = NumInfo::parse( range.get_str(&line), NumInfoParseSettings { @@ -595,20 +598,23 @@ impl FieldSelector { decimal_pt: Some(DECIMAL_PT), }, ); + // Shorten the range to what we need to pass to numeric_str_cmp later. range.shorten(num_range); Some(Box::new(NumCache::WithInfo(info))) } else if self.settings.mode == SortMode::GeneralNumeric { + // Parse this number as f64, as this is the requirement for general numeric sorting. let str = range.get_str(&line); Some(Box::new(NumCache::AsF64(general_f64_parse( &str[get_leading_gen(str)], )))) } else { + // This is not a numeric sort, so we don't need a NumCache. None }; Selection { range, num_cache } } - /// Look up the slice that corresponds to this selector for the given line. + /// Look up the range in the line that corresponds to this selector. /// If needs_fields returned false, tokens may be None. fn get_range<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Range { enum Resolution { @@ -1356,7 +1362,8 @@ enum GeneralF64ParseResult { Infinity, } -/// Parse the beginning string into an f64, returning -inf instead of NaN on errors. +/// Parse the beginning string into a GeneralF64ParseResult. +/// Using a GeneralF64ParseResult instead of f64 is necessary to correctly order floats. #[inline(always)] fn general_f64_parse(a: &str) -> GeneralF64ParseResult { // The actual behavior here relies on Rust's implementation of parsing floating points. From e0ebf907a4fec88d81bb20b226f0ade6ae932d60 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 8 May 2021 23:06:17 +0200 Subject: [PATCH 274/399] sort: make merging stable When merging files we need to prioritize files that occur earlier in the command line arguments with -m. This also makes the extsort merge step (and thus extsort itself) stable again. --- src/uu/sort/src/sort.rs | 11 ++++++++++- tests/by-util/test_sort.rs | 24 +++++++++++++++++++++++ tests/fixtures/sort/ext_stable.expected | 4 ++++ tests/fixtures/sort/ext_stable.txt | 4 ++++ tests/fixtures/sort/merge_stable.expected | 3 +++ tests/fixtures/sort/merge_stable_1.txt | 2 ++ tests/fixtures/sort/merge_stable_2.txt | 1 + 7 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sort/ext_stable.expected create mode 100644 tests/fixtures/sort/ext_stable.txt create mode 100644 tests/fixtures/sort/merge_stable.expected create mode 100644 tests/fixtures/sort/merge_stable_1.txt create mode 100644 tests/fixtures/sort/merge_stable_2.txt diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 730be0039..d35c62f87 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -686,6 +686,7 @@ struct MergeableFile<'a> { lines: Box + 'a>, current_line: Line, settings: &'a GlobalSettings, + file_index: usize, } // BinaryHeap depends on `Ord`. Note that we want to pop smallest items @@ -693,7 +694,14 @@ struct MergeableFile<'a> { // trick it into the right order by calling reverse() here. impl<'a> Ord for MergeableFile<'a> { fn cmp(&self, other: &MergeableFile) -> Ordering { - compare_by(&self.current_line, &other.current_line, self.settings).reverse() + let comparison = compare_by(&self.current_line, &other.current_line, self.settings); + if comparison == Ordering::Equal { + // If lines are equal, the earlier file takes precedence. + self.file_index.cmp(&other.file_index) + } else { + comparison + } + .reverse() } } @@ -729,6 +737,7 @@ impl<'a> FileMerger<'a> { lines, current_line: next_line, settings: &self.settings, + file_index: self.heap.len(), }; self.heap.push(mergeable_file); } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 4465e861f..bad9d577e 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -51,6 +51,18 @@ fn test_smaller_than_specified_segment() { .stdout_is_fixture("ext_sort.expected"); } +#[test] +fn test_ext_sort_stable() { + new_ucmd!() + .arg("-n") + .arg("--stable") + .arg("-S") + .arg("0M") + .arg("ext_stable.txt") + .succeeds() + .stdout_only_fixture("ext_stable.expected"); +} + #[test] fn test_extsort_zero_terminated() { new_ucmd!() @@ -566,6 +578,18 @@ fn test_merge_unique() { .stdout_only_fixture("merge_ints_interleaved.expected"); } +#[test] +fn test_merge_stable() { + new_ucmd!() + .arg("-m") + .arg("--stable") + .arg("-n") + .arg("merge_stable_1.txt") + .arg("merge_stable_2.txt") + .succeeds() + .stdout_only_fixture("merge_stable.expected"); +} + #[test] fn test_merge_reversed() { new_ucmd!() diff --git a/tests/fixtures/sort/ext_stable.expected b/tests/fixtures/sort/ext_stable.expected new file mode 100644 index 000000000..11ca4deb7 --- /dev/null +++ b/tests/fixtures/sort/ext_stable.expected @@ -0,0 +1,4 @@ +0a +0a +0b +0b diff --git a/tests/fixtures/sort/ext_stable.txt b/tests/fixtures/sort/ext_stable.txt new file mode 100644 index 000000000..11ca4deb7 --- /dev/null +++ b/tests/fixtures/sort/ext_stable.txt @@ -0,0 +1,4 @@ +0a +0a +0b +0b diff --git a/tests/fixtures/sort/merge_stable.expected b/tests/fixtures/sort/merge_stable.expected new file mode 100644 index 000000000..49f57888d --- /dev/null +++ b/tests/fixtures/sort/merge_stable.expected @@ -0,0 +1,3 @@ +0a +0c +0b diff --git a/tests/fixtures/sort/merge_stable_1.txt b/tests/fixtures/sort/merge_stable_1.txt new file mode 100644 index 000000000..20528104f --- /dev/null +++ b/tests/fixtures/sort/merge_stable_1.txt @@ -0,0 +1,2 @@ +0a +0c \ No newline at end of file diff --git a/tests/fixtures/sort/merge_stable_2.txt b/tests/fixtures/sort/merge_stable_2.txt new file mode 100644 index 000000000..d3523d976 --- /dev/null +++ b/tests/fixtures/sort/merge_stable_2.txt @@ -0,0 +1 @@ +0b \ No newline at end of file From 112b04276922a3c10f39abf88907bccf714d6b30 Mon Sep 17 00:00:00 2001 From: Nicolas Thery Date: Sun, 9 May 2021 15:42:55 +0200 Subject: [PATCH 275/399] wc: emit '-' in ouput when set on command-line When stdin is explicitly specified on the command-line with '-', emit it in the output stats to match GNU wc output. Fixes #2188. --- src/uu/wc/src/wc.rs | 100 ++++++++++++++++++++++++++----------- src/uu/wc/src/wordcount.rs | 8 +-- tests/by-util/test_wc.rs | 9 ++++ 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 33b2ba5ec..226608d40 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -104,6 +104,34 @@ fn get_usage() -> String { ) } +enum StdinKind { + /// Stdin specified on command-line with "-". + Explicit, + + /// Stdin implicitly specified on command-line by not passing any positional argument. + Implicit, +} + +/// Supported inputs. +enum Input { + /// A regular file. + Path(String), + + /// Standard input. + Stdin(StdinKind), +} + +impl Input { + /// Converts input to title that appears in stats. + fn to_title(&self) -> Option<&str> { + match self { + Input::Path(path) => Some(path), + Input::Stdin(StdinKind::Explicit) => Some("-"), + Input::Stdin(StdinKind::Implicit) => None, + } + } +} + pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); @@ -144,18 +172,27 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true)) .get_matches_from(args); - let mut files: Vec = matches + let mut inputs: Vec = matches .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) + .map(|v| { + v.map(|i| { + if i == "-" { + Input::Stdin(StdinKind::Explicit) + } else { + Input::Path(ToString::to_string(i)) + } + }) + .collect() + }) .unwrap_or_default(); - if files.is_empty() { - files.push("-".to_owned()); + if inputs.is_empty() { + inputs.push(Input::Stdin(StdinKind::Implicit)); } let settings = Settings::new(&matches); - if wc(files, &settings).is_ok() { + if wc(inputs, &settings).is_ok() { 0 } else { 1 @@ -198,32 +235,35 @@ fn word_count_from_reader( Ok(total) } -fn word_count_from_path(path: &str, settings: &Settings) -> WcResult { - if path == "-" { - let stdin = io::stdin(); - let stdin_lock = stdin.lock(); - word_count_from_reader(stdin_lock, settings, path) - } else { - let path_obj = Path::new(path); - if path_obj.is_dir() { - Err(WcError::IsDirectory(path.to_owned())) - } else { - let file = File::open(path)?; - word_count_from_reader(file, settings, path) +fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { + match input { + Input::Stdin(_) => { + let stdin = io::stdin(); + let stdin_lock = stdin.lock(); + word_count_from_reader(stdin_lock, settings, "-") + } + Input::Path(path) => { + let path_obj = Path::new(path); + if path_obj.is_dir() { + Err(WcError::IsDirectory(path.to_owned())) + } else { + let file = File::open(path)?; + word_count_from_reader(file, settings, path) + } } } } -fn wc(files: Vec, settings: &Settings) -> Result<(), u32> { +fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let mut total_word_count = WordCount::default(); let mut results = vec![]; let mut max_width: usize = 0; let mut error_count = 0; - let num_files = files.len(); + let num_inputs = inputs.len(); - for path in &files { - let word_count = word_count_from_path(&path, settings).unwrap_or_else(|err| { + for input in &inputs { + let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| { show_error!("{}", err); error_count += 1; WordCount::default() @@ -235,18 +275,22 @@ fn wc(files: Vec, settings: &Settings) -> Result<(), u32> { // formatting each count as a string for output. max_width = max(max_width, word_count.bytes.to_string().len()); total_word_count += word_count; - results.push(word_count.with_title(path)); + results.push(word_count.with_title(input.to_title())); } for result in &results { if let Err(err) = print_stats(settings, &result, max_width) { - show_warning!("failed to print result for {}: {}", result.title, err); + show_warning!( + "failed to print result for {}: {}", + result.title.unwrap_or(""), + err + ); error_count += 1; } } - if num_files > 1 { - let total_result = total_word_count.with_title("total"); + if num_inputs > 1 { + let total_result = total_word_count.with_title(Some("total")); if let Err(err) = print_stats(settings, &total_result, max_width) { show_warning!("failed to print total: {}", err); error_count += 1; @@ -315,10 +359,10 @@ fn print_stats( )?; } - if result.title == "-" { - writeln!(stdout_lock)?; + if let Some(title) = result.title { + writeln!(stdout_lock, " {}", title)?; } else { - writeln!(stdout_lock, " {}", result.title)?; + writeln!(stdout_lock)?; } Ok(()) diff --git a/src/uu/wc/src/wordcount.rs b/src/uu/wc/src/wordcount.rs index 785e57eff..9e2a81fca 100644 --- a/src/uu/wc/src/wordcount.rs +++ b/src/uu/wc/src/wordcount.rs @@ -103,7 +103,7 @@ impl WordCount { (word_count, char_count) } - pub fn with_title(self, title: &str) -> TitledWordCount { + pub fn with_title(self, title: Option<&str>) -> TitledWordCount { TitledWordCount { title, count: self } } @@ -120,12 +120,12 @@ impl WordCount { } } -/// This struct supplements the actual word count with a title that is displayed -/// to the user at the end of the program. +/// This struct supplements the actual word count with an optional title that is +/// displayed to the user at the end of the program. /// The reason we don't simply include title in the `WordCount` struct is that /// it would result in unneccesary copying of `String`. #[derive(Debug, Default, Clone)] pub struct TitledWordCount<'a> { - pub title: &'a str, + pub title: Option<&'a str>, pub count: WordCount, } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 87a86fca4..b61d7e3aa 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -36,6 +36,15 @@ fn test_stdin_default() { .stdout_is(" 13 109 772\n"); } +#[test] +fn test_stdin_explicit() { + new_ucmd!() + .pipe_in_fixture("lorem_ipsum.txt") + .arg("-") + .run() + .stdout_is(" 13 109 772 -\n"); +} + #[test] fn test_utf8() { new_ucmd!() From 33206e1adcac4a938d879a815b7804e5ced42d4b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 9 May 2021 18:42:16 +0200 Subject: [PATCH 276/399] Ignore test_domain_socket as it fails too often --- tests/by-util/test_cat.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index c8ae29a9d..67722daa2 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -395,6 +395,7 @@ fn test_dev_full_show_all() { #[test] #[cfg(unix)] +#[ignore] fn test_domain_socket() { use std::io::prelude::*; use std::sync::{Arc, Barrier}; From 8747800697d7d58532c71805cb8f7fbfc32783d6 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sun, 9 May 2021 21:53:03 +0300 Subject: [PATCH 277/399] Switched 'arch' to use clap instead of getopts --- Cargo.lock | 2 ++ src/uu/arch/Cargo.toml | 1 + src/uu/arch/src/arch.rs | 17 ++++++++++------- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13441d4fe..730c53547 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,6 +1658,7 @@ dependencies = [ name = "uu_arch" version = "0.0.6" dependencies = [ + "clap", "platform-info", "uucore", "uucore_procs", @@ -2406,6 +2407,7 @@ name = "uu_stat" version = "0.0.6" dependencies = [ "clap", + "libc", "time", "uucore", "uucore_procs", diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index 0b4359620..b3fe1f8cb 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -16,6 +16,7 @@ path = "src/arch.rs" [dependencies] platform-info = "0.1" +clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index a4c57e282..31278f000 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -10,17 +10,20 @@ extern crate uucore; use platform_info::*; -use uucore::InvalidEncodingHandling; -static SYNTAX: &str = "Display machine architecture"; +use clap::App; + +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Display machine architecture"; static SUMMARY: &str = "Determine architecture name for current machine."; -static LONG_HELP: &str = ""; pub fn uumain(args: impl uucore::Args) -> i32 { - app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(), - ); + App::new(executable!()) + .version(VERSION) + .about(ABOUT) + .after_help(SUMMARY) + .get_matches_from(args); + let uts = return_if_err!(1, PlatformInfo::new()); println!("{}", uts.machine().trim()); 0 From 0cc779c73360199b661246fa343101ef07bfece1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 9 May 2021 21:36:39 -0400 Subject: [PATCH 278/399] tail: simplify unbounded_tail() function Refactor common code out of two branches of the `unbounded_tail()` function into a new `unbounded_tail_collect()` helper function, that collects from an iterator into a `VecDeque` and keeps either the last `n` elements or all but the first `n` elements. This commit also adds a new struct, `RingBuffer`, in a new module, `ringbuffer.rs`, to be responsible for keeping the last `n` elements of an iterator. --- src/uu/tail/src/ringbuffer.rs | 61 ++++++++++++++++++++++ src/uu/tail/src/tail.rs | 98 +++++++++++++---------------------- 2 files changed, 96 insertions(+), 63 deletions(-) create mode 100644 src/uu/tail/src/ringbuffer.rs diff --git a/src/uu/tail/src/ringbuffer.rs b/src/uu/tail/src/ringbuffer.rs new file mode 100644 index 000000000..86483b8ed --- /dev/null +++ b/src/uu/tail/src/ringbuffer.rs @@ -0,0 +1,61 @@ +//! A fixed-size ring buffer. +use std::collections::VecDeque; + +/// A fixed-size ring buffer backed by a `VecDeque`. +/// +/// If the ring buffer is not full, then calling the [`push_back`] +/// method appends elements, as in a [`VecDeque`]. If the ring buffer +/// is full, then calling [`push_back`] removes the element at the +/// front of the buffer (in a first-in, first-out manner) before +/// appending the new element to the back of the buffer. +/// +/// Use [`from_iter`] to take the last `size` elements from an +/// iterator. +/// +/// # Examples +/// +/// After exceeding the size limit, the oldest elements are dropped in +/// favor of the newest element: +/// +/// ```rust,ignore +/// let buffer: RingBuffer = RingBuffer::new(2); +/// buffer.push_back(0); +/// buffer.push_back(1); +/// buffer.push_back(2); +/// assert_eq!(vec![1, 2], buffer.data); +/// ``` +/// +/// Take the last `n` elements from an iterator: +/// +/// ```rust,ignore +/// let iter = vec![0, 1, 2, 3].iter(); +/// assert_eq!(vec![2, 3], RingBuffer::from_iter(iter, 2).data); +/// ``` +pub struct RingBuffer { + pub data: VecDeque, + size: usize, +} + +impl RingBuffer { + pub fn new(size: usize) -> RingBuffer { + RingBuffer { + data: VecDeque::new(), + size, + } + } + + pub fn from_iter(iter: impl Iterator, size: usize) -> RingBuffer { + let mut ringbuf = RingBuffer::new(size); + for value in iter { + ringbuf.push_back(value); + } + ringbuf + } + + pub fn push_back(&mut self, value: T) { + if self.size <= self.data.len() { + self.data.pop_front(); + } + self.data.push_back(value) + } +} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index fec88e841..0a3ff778d 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -16,6 +16,8 @@ extern crate clap; extern crate uucore; mod platform; +mod ringbuffer; +use ringbuffer::RingBuffer; use clap::{App, Arg}; use std::collections::VecDeque; @@ -482,71 +484,46 @@ fn bounded_tail(mut file: &File, settings: &Settings) { } } +/// Collect the last elements of an iterator into a `VecDeque`. +/// +/// This function returns a [`VecDeque`] containing either the last +/// `count` elements of `iter`, an [`Iterator`] over [`Result`] +/// instances, or all but the first `count` elements of `iter`. If +/// `beginning` is `true`, then all but the first `count` elements are +/// returned. +/// +/// # Panics +/// +/// If any element of `iter` is an [`Err`], then this function panics. +fn unbounded_tail_collect( + iter: impl Iterator>, + count: u64, + beginning: bool, +) -> VecDeque +where + E: fmt::Debug, +{ + if beginning { + iter.skip(count as usize).map(|r| r.unwrap()).collect() + } else { + RingBuffer::from_iter(iter.map(|r| r.unwrap()), count as usize).data + } +} + fn unbounded_tail(reader: &mut BufReader, settings: &Settings) { // Read through each line/char and store them in a ringbuffer that always // contains count lines/chars. When reaching the end of file, output the // data in the ringbuf. match settings.mode { - FilterMode::Lines(mut count, _delimiter) => { - let mut ringbuf: VecDeque = VecDeque::new(); - let mut skip = if settings.beginning { - let temp = count; - count = ::std::u64::MAX; - temp - 1 - } else { - 0 - }; - loop { - let mut datum = String::new(); - match reader.read_line(&mut datum) { - Ok(0) => break, - Ok(_) => { - if skip > 0 { - skip -= 1; - } else { - if count <= ringbuf.len() as u64 { - ringbuf.pop_front(); - } - ringbuf.push_back(datum); - } - } - Err(err) => panic!("{}", err), - } - } - let mut stdout = stdout(); - for datum in &ringbuf { - print_string(&mut stdout, datum); + FilterMode::Lines(count, _) => { + for line in unbounded_tail_collect(reader.lines(), count, settings.beginning) { + println!("{}", line); } } - FilterMode::Bytes(mut count) => { - let mut ringbuf: VecDeque = VecDeque::new(); - let mut skip = if settings.beginning { - let temp = count; - count = ::std::u64::MAX; - temp - 1 - } else { - 0 - }; - loop { - let mut datum = [0; 1]; - match reader.read(&mut datum) { - Ok(0) => break, - Ok(_) => { - if skip > 0 { - skip -= 1; - } else { - if count <= ringbuf.len() as u64 { - ringbuf.pop_front(); - } - ringbuf.push_back(datum[0]); - } - } - Err(err) => panic!("{}", err), - } - } - let mut stdout = stdout(); - for datum in &ringbuf { - print_byte(&mut stdout, *datum); + FilterMode::Bytes(count) => { + for byte in unbounded_tail_collect(reader.bytes(), count, settings.beginning) { + let mut stdout = stdout(); + print_byte(&mut stdout, byte); } } } @@ -562,8 +539,3 @@ fn print_byte(stdout: &mut T, ch: u8) { crash!(1, "{}", err); } } - -#[inline] -fn print_string(_: &mut T, s: &str) { - print!("{}", s); -} From 881bbf512ed544142214066d2b60ff6419b27d73 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 10 May 2021 08:59:45 +0200 Subject: [PATCH 279/399] refresh cargo.lock with recent updates --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 13441d4fe..e729bfcd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2406,6 +2406,7 @@ name = "uu_stat" version = "0.0.6" dependencies = [ "clap", + "libc", "time", "uucore", "uucore_procs", From 203ee463c74fd0c7f134c2f1009d9a5accf8fbf9 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 9 May 2021 14:21:15 +0200 Subject: [PATCH 280/399] stat/uucore: refactor - move fsext.rs to uucore --- src/uu/stat/Cargo.toml | 4 +- src/uu/stat/src/stat.rs | 9 ++-- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 2 + .../src => uucore/src/lib/features}/fsext.rs | 43 ++++++++++++++++--- src/uucore/src/lib/lib.rs | 2 + tests/by-util/test_stat.rs | 27 ------------ 7 files changed, 47 insertions(+), 41 deletions(-) rename src/{uu/stat/src => uucore/src/lib/features}/fsext.rs (93%) diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index c51f972a9..86b7da139 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -16,9 +16,7 @@ path = "src/stat.rs" [dependencies] clap = "2.33" -time = "0.1.40" -libc = "0.2" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc", "fs"] } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc", "fs", "fsext"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 905058766..8b148d39d 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -5,15 +5,16 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) mtab fsext showfs otype fmtstr prec ftype blocksize nlink rdev fnodes fsid namelen blksize inodes fstype iosize statfs gnulib NBLOCKSIZE - -mod fsext; -pub use crate::fsext::*; +// spell-checker:ignore (ToDO) showfs otype fmtstr prec ftype blocksize nlink rdev fnodes fsid namelen blksize inodes fstype iosize statfs gnulib NBLOCKSIZE #[macro_use] extern crate uucore; use uucore::entries; use uucore::fs::display_permissions; +use uucore::fsext::{ + pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta, +}; +use uucore::libc::mode_t; use clap::{App, Arg, ArgMatches}; use std::borrow::Cow; diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 291456760..51bb4c66e 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -38,6 +38,7 @@ default = [] encoding = ["data-encoding", "thiserror"] entries = ["libc"] fs = ["libc"] +fsext = ["libc", "time"] mode = ["libc"] parse_time = [] perms = ["libc"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index c26225cb7..0287b9675 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -4,6 +4,8 @@ pub mod encoding; #[cfg(feature = "fs")] pub mod fs; +#[cfg(feature = "fsext")] +pub mod fsext; #[cfg(feature = "parse_time")] pub mod parse_time; #[cfg(feature = "zero-copy")] diff --git a/src/uu/stat/src/fsext.rs b/src/uucore/src/lib/features/fsext.rs similarity index 93% rename from src/uu/stat/src/fsext.rs rename to src/uucore/src/lib/features/fsext.rs index e831a159e..3c95af73e 100644 --- a/src/uu/stat/src/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -9,6 +9,8 @@ extern crate time; +pub use crate::*; // import macros from `../../macros.rs` + #[cfg(target_os = "linux")] static LINUX_MTAB: &str = "/etc/mtab"; #[cfg(target_os = "linux")] @@ -16,12 +18,12 @@ static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo"; static MOUNT_OPT_BIND: &str = "bind"; use self::time::Timespec; -use std::time::UNIX_EPOCH; -pub use uucore::libc::{ +pub use libc::{ c_int, mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, }; +use std::time::UNIX_EPOCH; pub trait BirthTime { fn pretty_birth(&self) -> String; @@ -93,7 +95,7 @@ use std::path::Path; target_os = "android", target_os = "freebsd" ))] -use uucore::libc::statfs as Sstatfs; +use libc::statfs as Sstatfs; #[cfg(any( target_os = "openbsd", target_os = "netbsd", @@ -101,7 +103,7 @@ use uucore::libc::statfs as Sstatfs; target_os = "bitrig", target_os = "dragonfly" ))] -use uucore::libc::statvfs as Sstatfs; +use libc::statvfs as Sstatfs; #[cfg(any( target_os = "linux", @@ -109,7 +111,7 @@ use uucore::libc::statvfs as Sstatfs; target_os = "android", target_os = "freebsd" ))] -use uucore::libc::statfs as statfs_fn; +use libc::statfs as statfs_fn; #[cfg(any( target_os = "openbsd", target_os = "netbsd", @@ -117,7 +119,7 @@ use uucore::libc::statfs as statfs_fn; target_os = "bitrig", target_os = "dragonfly" ))] -use uucore::libc::statvfs as statfs_fn; +use libc::statvfs as statfs_fn; pub trait FsMeta { fn fs_type(&self) -> i64; @@ -184,7 +186,7 @@ impl FsMeta for Sstatfs { #[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux"))] fn fsid(&self) -> u64 { let f_fsid: &[u32; 2] = - unsafe { &*(&self.f_fsid as *const uucore::libc::fsid_t as *const [u32; 2]) }; + unsafe { &*(&self.f_fsid as *const libc::fsid_t as *const [u32; 2]) }; (u64::from(f_fsid[0])) << 32 | u64::from(f_fsid[1]) } #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] @@ -534,3 +536,30 @@ pub fn read_fs_list() -> Vec { .collect::>() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_file_type() { + assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); + assert_eq!("character special file", pretty_filetype(S_IFCHR, 0)); + assert_eq!("regular file", pretty_filetype(S_IFREG, 1)); + assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0)); + assert_eq!("weird file", pretty_filetype(0, 0)); + } + + #[test] + fn test_fs_type() { + assert_eq!("ext2/ext3", pretty_fstype(0xEF53)); + assert_eq!("tmpfs", pretty_fstype(0x01021994)); + assert_eq!("nfs", pretty_fstype(0x6969)); + assert_eq!("btrfs", pretty_fstype(0x9123683e)); + assert_eq!("xfs", pretty_fstype(0x58465342)); + assert_eq!("zfs", pretty_fstype(0x2FC12FC1)); + assert_eq!("ntfs", pretty_fstype(0x5346544e)); + assert_eq!("fat", pretty_fstype(0x4006)); + assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234)); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6dddf8696..f2a4292fb 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -35,6 +35,8 @@ pub use crate::mods::ranges; pub use crate::features::encoding; #[cfg(feature = "fs")] pub use crate::features::fs; +#[cfg(feature = "fsext")] +pub use crate::features::fsext; #[cfg(feature = "parse_time")] pub use crate::features::parse_time; #[cfg(feature = "zero-copy")] diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 569d6873e..308dcb9f5 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -5,33 +5,6 @@ use crate::common::util::*; extern crate stat; pub use self::stat::*; -#[cfg(test)] -mod test_fsext { - use super::*; - - #[test] - fn test_file_type() { - assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); - assert_eq!("character special file", pretty_filetype(S_IFCHR, 0)); - assert_eq!("regular file", pretty_filetype(S_IFREG, 1)); - assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0)); - assert_eq!("weird file", pretty_filetype(0, 0)); - } - - #[test] - fn test_fs_type() { - assert_eq!("ext2/ext3", pretty_fstype(0xEF53)); - assert_eq!("tmpfs", pretty_fstype(0x01021994)); - assert_eq!("nfs", pretty_fstype(0x6969)); - assert_eq!("btrfs", pretty_fstype(0x9123683e)); - assert_eq!("xfs", pretty_fstype(0x58465342)); - assert_eq!("zfs", pretty_fstype(0x2FC12FC1)); - assert_eq!("ntfs", pretty_fstype(0x5346544e)); - assert_eq!("fat", pretty_fstype(0x4006)); - assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234)); - } -} - #[test] fn test_scanutil() { assert_eq!(Some((-5, 2)), "-5zxc".scan_num::()); From 4ac75898c377c433a2cb2698dd09c94dae14540e Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 10 May 2021 13:28:35 +0200 Subject: [PATCH 281/399] fix clippy warnings --- src/uu/factor/src/factor.rs | 3 ++- src/uu/ls/src/ls.rs | 4 ++-- src/uu/test/src/parser.rs | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 138254b51..ebe06a1c5 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -163,6 +163,7 @@ pub fn factor(mut n: u64) -> Factors { let (factors, n) = table::factor(n, factors); + #[allow(clippy::let_and_return)] let r = if n < (1 << 32) { _factor::>(n, factors) } else { @@ -280,6 +281,6 @@ impl std::ops::BitXor for Factors { } debug_assert_eq!(r.product(), self.product().pow(rhs.into())); - return r; + r } } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 16f2ce8ff..c5389295b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1413,11 +1413,11 @@ fn get_block_size(md: &Metadata, config: &Config) -> u64 { { // hard-coded for now - enabling setting this remains a TODO let ls_block_size = 1024; - return match config.size_format { + match config.size_format { SizeFormat::Binary => md.blocks() * 512, SizeFormat::Decimal => md.blocks() * 512, SizeFormat::Bytes => md.blocks() * 512 / ls_block_size, - }; + } } #[cfg(not(unix))] diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index f1ca9dad6..2c9c9db30 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -121,6 +121,8 @@ impl Parser { /// Test if the next token in the stream is a BOOLOP (-a or -o), without /// removing the token from the stream. fn peek_is_boolop(&mut self) -> bool { + // TODO: change to `matches!(self.peek(), Symbol::BoolOp(_))` once MSRV is 1.42 + // #[allow(clippy::match_like_matches_macro)] // needs MSRV 1.43 if let Symbol::BoolOp(_) = self.peek() { true } else { From 381f8dafc6eed456f4a9298baba635ccfd5c826b Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 10 May 2021 10:56:33 +0200 Subject: [PATCH 282/399] df/uucore: refactor - move duplicate code to uucore/fsext.rs --- src/uu/df/Cargo.toml | 6 +- src/uu/df/src/df.rs | 486 +---------------- src/uucore/Cargo.toml | 3 + src/uucore/src/lib/features/fsext.rs | 754 ++++++++++++++++++--------- src/uucore/src/lib/lib.rs | 4 +- 5 files changed, 519 insertions(+), 734 deletions(-) diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 4770cb557..0e65fdb32 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -16,14 +16,10 @@ path = "src/df.rs" [dependencies] clap = "2.33" -libc = "0.2" number_prefix = "0.4" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -[target.'cfg(target_os = "windows")'.dependencies] -winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] } - [[bin]] name = "df" path = "src/main.rs" diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index c917eb2e8..8219b0a27 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -6,22 +6,17 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) mountinfo mtab BLOCKSIZE getmntinfo fobj mptr noatime Iused overmounted -// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statfs statvfs subfs syncreads syncwrites sysfs wcslen +// spell-checker:ignore (ToDO) mountinfo BLOCKSIZE fobj mptr noatime Iused overmounted +// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statvfs subfs syncreads syncwrites sysfs wcslen #[macro_use] extern crate uucore; +#[cfg(unix)] +use uucore::fsext::statfs_fn; +use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; use clap::{App, Arg}; -#[cfg(windows)] -use winapi::um::errhandlingapi::GetLastError; -#[cfg(windows)] -use winapi::um::fileapi::{ - FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW, - GetVolumePathNamesForVolumeNameW, QueryDosDeviceW, -}; - use number_prefix::NumberPrefix; use std::cell::Cell; use std::collections::HashMap; @@ -32,41 +27,11 @@ use std::ffi::CString; #[cfg(unix)] use std::mem; -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -use libc::c_int; -#[cfg(target_vendor = "apple")] -use libc::statfs; -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -use std::ffi::CStr; -#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))] -use std::ptr; -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -use std::slice; - #[cfg(target_os = "freebsd")] -use libc::{c_char, fsid_t, uid_t}; +use uucore::libc::{c_char, fsid_t, uid_t}; -#[cfg(target_os = "linux")] -use std::fs::File; -#[cfg(target_os = "linux")] -use std::io::{BufRead, BufReader}; - -#[cfg(windows)] -use std::ffi::OsString; -#[cfg(windows)] -use std::os::windows::ffi::OsStrExt; -#[cfg(windows)] -use std::os::windows::ffi::OsStringExt; #[cfg(windows)] use std::path::Path; -#[cfg(windows)] -use winapi::shared::minwindef::DWORD; -#[cfg(windows)] -use winapi::um::fileapi::GetDiskFreeSpaceW; -#[cfg(windows)] -use winapi::um::handleapi::INVALID_HANDLE_VALUE; -#[cfg(windows)] -use winapi::um::winbase::DRIVE_REMOTE; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ @@ -75,14 +40,6 @@ static ABOUT: &str = "Show information about the file system on which each FILE static EXIT_OK: i32 = 0; static EXIT_ERR: i32 = 1; -#[cfg(windows)] -const MAX_PATH: usize = 266; - -#[cfg(target_os = "linux")] -static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo"; -#[cfg(target_os = "linux")] -static LINUX_MTAB: &str = "/etc/mtab"; - static OPT_ALL: &str = "all"; static OPT_BLOCKSIZE: &str = "blocksize"; static OPT_DIRECT: &str = "direct"; @@ -101,8 +58,6 @@ static OPT_TYPE: &str = "type"; static OPT_PRINT_TYPE: &str = "print-type"; static OPT_EXCLUDE_TYPE: &str = "exclude-type"; -static MOUNT_OPT_BIND: &str = "bind"; - /// Store names of file systems as a selector. /// Note: `exclude` takes priority over `include`. struct FsSelector { @@ -121,136 +76,16 @@ struct Options { fs_selector: FsSelector, } -#[derive(Debug, Clone)] -struct MountInfo { - // it stores `volume_name` in windows platform and `dev_id` in unix platform - dev_id: String, - dev_name: String, - fs_type: String, - mount_dir: String, - mount_option: String, // we only care "bind" option - mount_root: String, - remote: bool, - dummy: bool, -} - -#[cfg(all( - target_os = "freebsd", - not(all(target_vendor = "apple", target_arch = "x86_64")) -))] -#[repr(C)] -#[derive(Copy, Clone)] -#[allow(non_camel_case_types)] -struct statfs { - f_version: u32, - f_type: u32, - f_flags: u64, - f_bsize: u64, - f_iosize: u64, - f_blocks: u64, - f_bfree: u64, - f_bavail: i64, - f_files: u64, - f_ffree: i64, - f_syncwrites: u64, - f_asyncwrites: u64, - f_syncreads: u64, - f_asyncreads: u64, - f_spare: [u64; 10usize], - f_namemax: u32, - f_owner: uid_t, - f_fsid: fsid_t, - f_charspare: [c_char; 80usize], - f_fstypename: [c_char; 16usize], - f_mntfromname: [c_char; 88usize], - f_mntonname: [c_char; 88usize], -} - -#[derive(Debug, Clone)] -struct FsUsage { - blocksize: u64, - blocks: u64, - bfree: u64, - bavail: u64, - bavail_top_bit_set: bool, - files: u64, - ffree: u64, -} - #[derive(Debug, Clone)] struct Filesystem { mountinfo: MountInfo, usage: FsUsage, } -#[cfg(windows)] -macro_rules! String2LPWSTR { - ($str: expr) => { - OsString::from($str.clone()) - .as_os_str() - .encode_wide() - .chain(Some(0)) - .collect::>() - .as_ptr() - }; -} - -#[cfg(windows)] -#[allow(non_snake_case)] -fn LPWSTR2String(buf: &[u16]) -> String { - let len = unsafe { libc::wcslen(buf.as_ptr()) }; - OsString::from_wide(&buf[..len as usize]) - .into_string() - .unwrap() -} - fn get_usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] -extern "C" { - #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] - #[link_name = "getmntinfo$INODE64"] - fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; - - #[cfg(any( - all(target_os = "freebsd"), - all(target_vendor = "apple", target_arch = "aarch64") - ))] - fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int; -} - -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] -impl From for MountInfo { - fn from(statfs: statfs) -> Self { - let mut info = MountInfo { - dev_id: "".to_string(), - dev_name: unsafe { - CStr::from_ptr(&statfs.f_mntfromname[0]) - .to_string_lossy() - .into_owned() - }, - fs_type: unsafe { - CStr::from_ptr(&statfs.f_fstypename[0]) - .to_string_lossy() - .into_owned() - }, - mount_dir: unsafe { - CStr::from_ptr(&statfs.f_mntonname[0]) - .to_string_lossy() - .into_owned() - }, - mount_root: "".to_string(), - mount_option: "".to_string(), - remote: false, - dummy: false, - }; - info.set_missing_fields(); - info - } -} - impl FsSelector { fn new() -> FsSelector { FsSelector { @@ -295,239 +130,6 @@ impl Options { } } -impl MountInfo { - fn set_missing_fields(&mut self) { - #[cfg(unix)] - { - // We want to keep the dev_id on Windows - // but set dev_id - let path = CString::new(self.mount_dir.clone()).unwrap(); - unsafe { - let mut stat = mem::zeroed(); - if libc::stat(path.as_ptr(), &mut stat) == 0 { - self.dev_id = (stat.st_dev as i32).to_string(); - } else { - self.dev_id = "".to_string(); - } - } - } - // set MountInfo::dummy - match self.fs_type.as_ref() { - "autofs" | "proc" | "subfs" - /* for Linux 2.6/3.x */ - | "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs" - /* FreeBSD, Linux 2.4 */ - | "devfs" - /* for NetBSD 3.0 */ - | "kernfs" - /* for Irix 6.5 */ - | "ignore" => self.dummy = true, - _ => self.dummy = self.fs_type == "none" - && self.mount_option.find(MOUNT_OPT_BIND).is_none(), - } - // set MountInfo::remote - #[cfg(windows)] - { - self.remote = DRIVE_REMOTE == unsafe { GetDriveTypeW(String2LPWSTR!(self.mount_root)) }; - } - #[cfg(unix)] - { - if self.dev_name.find(':').is_some() - || (self.dev_name.starts_with("//") && self.fs_type == "smbfs" - || self.fs_type == "cifs") - || self.dev_name == "-hosts" - { - self.remote = true; - } else { - self.remote = false; - } - } - } - - #[cfg(target_os = "linux")] - fn new(file_name: &str, raw: Vec<&str>) -> Option { - match file_name { - // Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // "man proc" for more details - "/proc/self/mountinfo" => { - let mut m = MountInfo { - dev_id: "".to_string(), - dev_name: raw[9].to_string(), - fs_type: raw[8].to_string(), - mount_root: raw[3].to_string(), - mount_dir: raw[4].to_string(), - mount_option: raw[5].to_string(), - remote: false, - dummy: false, - }; - m.set_missing_fields(); - Some(m) - } - "/etc/mtab" => { - let mut m = MountInfo { - dev_id: "".to_string(), - dev_name: raw[0].to_string(), - fs_type: raw[2].to_string(), - mount_root: "".to_string(), - mount_dir: raw[1].to_string(), - mount_option: raw[3].to_string(), - remote: false, - dummy: false, - }; - m.set_missing_fields(); - Some(m) - } - _ => None, - } - } - #[cfg(windows)] - fn new(mut volume_name: String) -> Option { - let mut dev_name_buf = [0u16; MAX_PATH]; - volume_name.pop(); - unsafe { - QueryDosDeviceW( - OsString::from(volume_name.clone()) - .as_os_str() - .encode_wide() - .chain(Some(0)) - .skip(4) - .collect::>() - .as_ptr(), - dev_name_buf.as_mut_ptr(), - dev_name_buf.len() as DWORD, - ) - }; - volume_name.push('\\'); - let dev_name = LPWSTR2String(&dev_name_buf); - - let mut mount_root_buf = [0u16; MAX_PATH]; - let success = unsafe { - GetVolumePathNamesForVolumeNameW( - String2LPWSTR!(volume_name), - mount_root_buf.as_mut_ptr(), - mount_root_buf.len() as DWORD, - ptr::null_mut(), - ) - }; - if 0 == success { - // TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA` - return None; - } - let mount_root = LPWSTR2String(&mount_root_buf); - - let mut fs_type_buf = [0u16; MAX_PATH]; - let success = unsafe { - GetVolumeInformationW( - String2LPWSTR!(mount_root), - ptr::null_mut(), - 0 as DWORD, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - fs_type_buf.as_mut_ptr(), - fs_type_buf.len() as DWORD, - ) - }; - let fs_type = if 0 != success { - Some(LPWSTR2String(&fs_type_buf)) - } else { - None - }; - let mut mn_info = MountInfo { - dev_id: volume_name, - dev_name, - fs_type: fs_type.unwrap_or_else(|| "".to_string()), - mount_root, - mount_dir: "".to_string(), - mount_option: "".to_string(), - remote: false, - dummy: false, - }; - mn_info.set_missing_fields(); - Some(mn_info) - } -} - -impl FsUsage { - #[cfg(unix)] - fn new(statvfs: libc::statvfs) -> FsUsage { - { - FsUsage { - blocksize: if statvfs.f_frsize != 0 { - statvfs.f_frsize as u64 - } else { - statvfs.f_bsize as u64 - }, - blocks: statvfs.f_blocks as u64, - bfree: statvfs.f_bfree as u64, - bavail: statvfs.f_bavail as u64, - bavail_top_bit_set: ((statvfs.f_bavail as u64) & (1u64.rotate_right(1))) != 0, - files: statvfs.f_files as u64, - ffree: statvfs.f_ffree as u64, - } - } - } - #[cfg(not(unix))] - fn new(path: &Path) -> FsUsage { - let mut root_path = [0u16; MAX_PATH]; - let success = unsafe { - GetVolumePathNamesForVolumeNameW( - //path_utf8.as_ptr(), - String2LPWSTR!(path.as_os_str()), - root_path.as_mut_ptr(), - root_path.len() as DWORD, - ptr::null_mut(), - ) - }; - if 0 == success { - crash!( - EXIT_ERR, - "GetVolumePathNamesForVolumeNameW failed: {}", - unsafe { GetLastError() } - ); - } - - let mut sectors_per_cluster = 0; - let mut bytes_per_sector = 0; - let mut number_of_free_clusters = 0; - let mut total_number_of_clusters = 0; - - let success = unsafe { - GetDiskFreeSpaceW( - String2LPWSTR!(path.as_os_str()), - &mut sectors_per_cluster, - &mut bytes_per_sector, - &mut number_of_free_clusters, - &mut total_number_of_clusters, - ) - }; - if 0 == success { - // Fails in case of CD for example - //crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe { - //GetLastError() - //}); - } - - let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64; - FsUsage { - // f_bsize File system block size. - blocksize: bytes_per_cluster as u64, - // f_blocks - Total number of blocks on the file system, in units of f_frsize. - // frsize = Fundamental file system block size (fragment size). - blocks: total_number_of_clusters as u64, - // Total number of free blocks. - bfree: number_of_free_clusters as u64, - // Total number of free blocks available to non-privileged processes. - bavail: 0 as u64, - bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0, - // Total number of file nodes (inodes) on the file system. - files: 0 as u64, // Not available on windows - // Total number of free file nodes (inodes). - ffree: 4096 as u64, // Meaningless on Windows - } - } -} - impl Filesystem { // TODO: resolve uuid in `mountinfo.dev_name` if exists fn new(mountinfo: MountInfo) -> Option { @@ -548,7 +150,7 @@ impl Filesystem { unsafe { let path = CString::new(_stat_path).unwrap(); let mut statvfs = mem::zeroed(); - if libc::statvfs(path.as_ptr(), &mut statvfs) < 0 { + if statfs_fn(path.as_ptr(), &mut statvfs) < 0 { None } else { Some(Filesystem { @@ -565,80 +167,6 @@ impl Filesystem { } } -/// Read file system list. -fn read_fs_list() -> Vec { - #[cfg(target_os = "linux")] - { - let (file_name, fobj) = File::open(LINUX_MOUNTINFO) - .map(|f| (LINUX_MOUNTINFO, f)) - .or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f))) - .expect("failed to find mount list files"); - let reader = BufReader::new(fobj); - reader - .lines() - .filter_map(|line| line.ok()) - .filter_map(|line| { - let raw_data = line.split_whitespace().collect::>(); - MountInfo::new(file_name, raw_data) - }) - .collect::>() - } - #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] - { - let mut mptr: *mut statfs = ptr::null_mut(); - let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) }; - if len < 0 { - crash!(EXIT_ERR, "getmntinfo failed"); - } - let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; - mounts - .iter() - .map(|m| MountInfo::from(*m)) - .collect::>() - } - #[cfg(windows)] - { - let mut volume_name_buf = [0u16; MAX_PATH]; - // As recommended in the MS documentation, retrieve the first volume before the others - let find_handle = unsafe { - FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD) - }; - if INVALID_HANDLE_VALUE == find_handle { - crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe { - GetLastError() - }); - } - let mut mounts = Vec::::new(); - loop { - let volume_name = LPWSTR2String(&volume_name_buf); - if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') { - show_warning!("A bad path was skipped: {}", volume_name); - continue; - } - if let Some(m) = MountInfo::new(volume_name) { - mounts.push(m); - } - if 0 == unsafe { - FindNextVolumeW( - find_handle, - volume_name_buf.as_mut_ptr(), - volume_name_buf.len() as DWORD, - ) - } { - let err = unsafe { GetLastError() }; - if err != winapi::shared::winerror::ERROR_NO_MORE_FILES { - crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err); - } - break; - } - } - unsafe { - FindVolumeClose(find_handle); - } - mounts - } -} - fn filter_mount_list(vmi: Vec, paths: &[String], opt: &Options) -> Vec { vmi.into_iter() .filter_map(|mi| { diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 51bb4c66e..da51f7ca4 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -29,6 +29,9 @@ time = { version="<= 0.1.43", optional=true } data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0 libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0 +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] } + [target.'cfg(target_os = "redox")'.dependencies] termion = "1.5" diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 3c95af73e..887c31e01 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -1,6 +1,8 @@ // This file is part of the uutils coreutils package. // // (c) Jian Zeng +// (c) Fangxu Hu +// (c) Sylvestre Ledru // // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. @@ -12,19 +14,106 @@ extern crate time; pub use crate::*; // import macros from `../../macros.rs` #[cfg(target_os = "linux")] -static LINUX_MTAB: &str = "/etc/mtab"; +const LINUX_MTAB: &str = "/etc/mtab"; #[cfg(target_os = "linux")] -static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo"; +const LINUX_MOUNTINFO: &str = "/proc/self/mountinfo"; static MOUNT_OPT_BIND: &str = "bind"; +#[cfg(windows)] +const MAX_PATH: usize = 266; +#[cfg(not(unix))] +static EXIT_ERR: i32 = 1; + +#[cfg(windows)] +use std::ffi::OsString; +#[cfg(windows)] +use std::os::windows::ffi::OsStrExt; +#[cfg(windows)] +use std::os::windows::ffi::OsStringExt; +#[cfg(windows)] +use winapi::shared::minwindef::DWORD; +#[cfg(windows)] +use winapi::um::errhandlingapi::GetLastError; +#[cfg(windows)] +use winapi::um::fileapi::GetDiskFreeSpaceW; +#[cfg(windows)] +use winapi::um::fileapi::{ + FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW, + GetVolumePathNamesForVolumeNameW, QueryDosDeviceW, +}; +#[cfg(windows)] +use winapi::um::handleapi::INVALID_HANDLE_VALUE; +#[cfg(windows)] +use winapi::um::winbase::DRIVE_REMOTE; + +#[cfg(windows)] +macro_rules! String2LPWSTR { + ($str: expr) => { + OsString::from($str.clone()) + .as_os_str() + .encode_wide() + .chain(Some(0)) + .collect::>() + .as_ptr() + }; +} + +#[cfg(windows)] +#[allow(non_snake_case)] +fn LPWSTR2String(buf: &[u16]) -> String { + let len = unsafe { libc::wcslen(buf.as_ptr()) }; + OsString::from_wide(&buf[..len as usize]) + .into_string() + .unwrap() +} use self::time::Timespec; -pub use libc::{ - c_int, mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, - S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, - S_IXGRP, S_IXOTH, S_IXUSR, +#[cfg(unix)] +use libc::{ + mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, }; +use std::borrow::Cow; +use std::convert::{AsRef, From}; +#[cfg(unix)] +use std::ffi::CString; +#[cfg(unix)] +use std::io::Error as IOError; +#[cfg(unix)] +use std::mem; +use std::path::Path; use std::time::UNIX_EPOCH; +#[cfg(any( + target_os = "linux", + target_vendor = "apple", + target_os = "android", + target_os = "freebsd" +))] +pub use libc::statfs as Sstatfs; +#[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "bitrig", + target_os = "dragonfly" +))] +pub use libc::statvfs as Sstatfs; + +#[cfg(any( + target_os = "linux", + target_vendor = "apple", + target_os = "android", + target_os = "freebsd" +))] +pub use libc::statfs as statfs_fn; +#[cfg(any( + target_os = "openbsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "bitrig", + target_os = "dragonfly" +))] +pub use libc::statvfs as statfs_fn; + pub trait BirthTime { fn pretty_birth(&self) -> String; fn birth(&self) -> String; @@ -49,78 +138,389 @@ impl BirthTime for Metadata { } } -pub fn pretty_time(sec: i64, nsec: i64) -> String { - // sec == seconds since UNIX_EPOCH - // nsec == nanoseconds since (UNIX_EPOCH + sec) - let tm = time::at(Timespec::new(sec, nsec as i32)); - let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap(); - if res.ends_with(" -0000") { - res.replace(" -0000", " +0000") - } else { - res - } +#[derive(Debug, Clone)] +pub struct MountInfo { + // it stores `volume_name` in windows platform and `dev_id` in unix platform + pub dev_id: String, + pub dev_name: String, + pub fs_type: String, + pub mount_dir: String, + pub mount_option: String, // we only care "bind" option + pub mount_root: String, + pub remote: bool, + pub dummy: bool, } -pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str { - match mode & S_IFMT { - S_IFREG => { - if size != 0 { - "regular file" - } else { - "regular empty file" +impl MountInfo { + fn set_missing_fields(&mut self) { + #[cfg(unix)] + { + // We want to keep the dev_id on Windows + // but set dev_id + let path = CString::new(self.mount_dir.clone()).unwrap(); + unsafe { + let mut stat = mem::zeroed(); + if libc::stat(path.as_ptr(), &mut stat) == 0 { + self.dev_id = (stat.st_dev as i32).to_string(); + } else { + self.dev_id = "".to_string(); + } } } - S_IFDIR => "directory", - S_IFLNK => "symbolic link", - S_IFCHR => "character special file", - S_IFBLK => "block special file", - S_IFIFO => "fifo", - S_IFSOCK => "socket", - // TODO: Other file types - // See coreutils/gnulib/lib/file-type.c - _ => "weird file", + // set MountInfo::dummy + match self.fs_type.as_ref() { + "autofs" | "proc" | "subfs" + /* for Linux 2.6/3.x */ + | "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs" + /* FreeBSD, Linux 2.4 */ + | "devfs" + /* for NetBSD 3.0 */ + | "kernfs" + /* for Irix 6.5 */ + | "ignore" => self.dummy = true, + _ => self.dummy = self.fs_type == "none" + && self.mount_option.find(MOUNT_OPT_BIND).is_none(), + } + // set MountInfo::remote + #[cfg(windows)] + { + self.remote = DRIVE_REMOTE == unsafe { GetDriveTypeW(String2LPWSTR!(self.mount_root)) }; + } + #[cfg(unix)] + { + if self.dev_name.find(':').is_some() + || (self.dev_name.starts_with("//") && self.fs_type == "smbfs" + || self.fs_type == "cifs") + || self.dev_name == "-hosts" + { + self.remote = true; + } else { + self.remote = false; + } + } + } + + #[cfg(target_os = "linux")] + fn new(file_name: &str, raw: Vec<&str>) -> Option { + match file_name { + // Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + // "man proc" for more details + LINUX_MOUNTINFO => { + let mut m = MountInfo { + dev_id: "".to_string(), + dev_name: raw[9].to_string(), + fs_type: raw[8].to_string(), + mount_root: raw[3].to_string(), + mount_dir: raw[4].to_string(), + mount_option: raw[5].to_string(), + remote: false, + dummy: false, + }; + m.set_missing_fields(); + Some(m) + } + LINUX_MTAB => { + let mut m = MountInfo { + dev_id: "".to_string(), + dev_name: raw[0].to_string(), + fs_type: raw[2].to_string(), + mount_root: "".to_string(), + mount_dir: raw[1].to_string(), + mount_option: raw[3].to_string(), + remote: false, + dummy: false, + }; + m.set_missing_fields(); + Some(m) + } + _ => None, + } + } + #[cfg(windows)] + fn new(mut volume_name: String) -> Option { + let mut dev_name_buf = [0u16; MAX_PATH]; + volume_name.pop(); + unsafe { + QueryDosDeviceW( + OsString::from(volume_name.clone()) + .as_os_str() + .encode_wide() + .chain(Some(0)) + .skip(4) + .collect::>() + .as_ptr(), + dev_name_buf.as_mut_ptr(), + dev_name_buf.len() as DWORD, + ) + }; + volume_name.push('\\'); + let dev_name = LPWSTR2String(&dev_name_buf); + + let mut mount_root_buf = [0u16; MAX_PATH]; + let success = unsafe { + GetVolumePathNamesForVolumeNameW( + String2LPWSTR!(volume_name), + mount_root_buf.as_mut_ptr(), + mount_root_buf.len() as DWORD, + ptr::null_mut(), + ) + }; + if 0 == success { + // TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA` + return None; + } + let mount_root = LPWSTR2String(&mount_root_buf); + + let mut fs_type_buf = [0u16; MAX_PATH]; + let success = unsafe { + GetVolumeInformationW( + String2LPWSTR!(mount_root), + ptr::null_mut(), + 0 as DWORD, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + fs_type_buf.as_mut_ptr(), + fs_type_buf.len() as DWORD, + ) + }; + let fs_type = if 0 != success { + Some(LPWSTR2String(&fs_type_buf)) + } else { + None + }; + let mut mn_info = MountInfo { + dev_id: volume_name, + dev_name, + fs_type: fs_type.unwrap_or_else(|| "".to_string()), + mount_root, + mount_dir: "".to_string(), + mount_option: "".to_string(), + remote: false, + dummy: false, + }; + mn_info.set_missing_fields(); + Some(mn_info) } } -use std::borrow::Cow; -use std::convert::{AsRef, From}; -use std::ffi::CString; -use std::io::Error as IOError; -use std::mem; -use std::path::Path; +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +use std::ffi::CStr; +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +impl From for MountInfo { + fn from(statfs: Sstatfs) -> Self { + let mut info = MountInfo { + dev_id: "".to_string(), + dev_name: unsafe { + CStr::from_ptr(&statfs.f_mntfromname[0]) + .to_string_lossy() + .into_owned() + }, + fs_type: unsafe { + CStr::from_ptr(&statfs.f_fstypename[0]) + .to_string_lossy() + .into_owned() + }, + mount_dir: unsafe { + CStr::from_ptr(&statfs.f_mntonname[0]) + .to_string_lossy() + .into_owned() + }, + mount_root: "".to_string(), + mount_option: "".to_string(), + remote: false, + dummy: false, + }; + info.set_missing_fields(); + info + } +} -#[cfg(any( - target_os = "linux", - target_vendor = "apple", - target_os = "android", - target_os = "freebsd" -))] -use libc::statfs as Sstatfs; -#[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "bitrig", - target_os = "dragonfly" -))] -use libc::statvfs as Sstatfs; +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +use libc::c_int; +#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +extern "C" { + #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] + #[link_name = "getmntinfo$INODE64"] + fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int; -#[cfg(any( - target_os = "linux", - target_vendor = "apple", - target_os = "android", - target_os = "freebsd" -))] -use libc::statfs as statfs_fn; -#[cfg(any( - target_os = "openbsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "bitrig", - target_os = "dragonfly" -))] -use libc::statvfs as statfs_fn; + #[cfg(any( + all(target_os = "freebsd"), + all(target_vendor = "apple", target_arch = "aarch64") + ))] + fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int; +} +#[cfg(target_os = "linux")] +use std::fs::File; +#[cfg(target_os = "linux")] +use std::io::{BufRead, BufReader}; +#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))] +use std::ptr; +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +use std::slice; +/// Read file system list. +pub fn read_fs_list() -> Vec { + #[cfg(target_os = "linux")] + { + let (file_name, fobj) = File::open(LINUX_MOUNTINFO) + .map(|f| (LINUX_MOUNTINFO, f)) + .or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f))) + .expect("failed to find mount list files"); + let reader = BufReader::new(fobj); + reader + .lines() + .filter_map(|line| line.ok()) + .filter_map(|line| { + let raw_data = line.split_whitespace().collect::>(); + MountInfo::new(file_name, raw_data) + }) + .collect::>() + } + #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + { + let mut mptr: *mut Sstatfs = ptr::null_mut(); + let len = unsafe { getmntinfo(&mut mptr, 1_i32) }; + if len < 0 { + crash!(1, "getmntinfo failed"); + } + let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; + mounts + .iter() + .map(|m| MountInfo::from(*m)) + .collect::>() + } + #[cfg(windows)] + { + let mut volume_name_buf = [0u16; MAX_PATH]; + // As recommended in the MS documentation, retrieve the first volume before the others + let find_handle = unsafe { + FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD) + }; + if INVALID_HANDLE_VALUE == find_handle { + crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe { + GetLastError() + }); + } + let mut mounts = Vec::::new(); + loop { + let volume_name = LPWSTR2String(&volume_name_buf); + if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') { + show_warning!("A bad path was skipped: {}", volume_name); + continue; + } + if let Some(m) = MountInfo::new(volume_name) { + mounts.push(m); + } + if 0 == unsafe { + FindNextVolumeW( + find_handle, + volume_name_buf.as_mut_ptr(), + volume_name_buf.len() as DWORD, + ) + } { + let err = unsafe { GetLastError() }; + if err != winapi::shared::winerror::ERROR_NO_MORE_FILES { + crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err); + } + break; + } + } + unsafe { + FindVolumeClose(find_handle); + } + mounts + } +} + +#[derive(Debug, Clone)] +pub struct FsUsage { + pub blocksize: u64, + pub blocks: u64, + pub bfree: u64, + pub bavail: u64, + pub bavail_top_bit_set: bool, + pub files: u64, + pub ffree: u64, +} + +impl FsUsage { + #[cfg(unix)] + pub fn new(statvfs: Sstatfs) -> FsUsage { + { + FsUsage { + blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ? + blocks: statvfs.f_blocks as u64, + bfree: statvfs.f_bfree as u64, + bavail: statvfs.f_bavail as u64, + bavail_top_bit_set: ((statvfs.f_bavail as u64) & (1u64.rotate_right(1))) != 0, + files: statvfs.f_files as u64, + ffree: statvfs.f_ffree as u64, + } + } + } + #[cfg(not(unix))] + pub fn new(path: &Path) -> FsUsage { + let mut root_path = [0u16; MAX_PATH]; + let success = unsafe { + GetVolumePathNamesForVolumeNameW( + //path_utf8.as_ptr(), + String2LPWSTR!(path.as_os_str()), + root_path.as_mut_ptr(), + root_path.len() as DWORD, + ptr::null_mut(), + ) + }; + if 0 == success { + crash!( + EXIT_ERR, + "GetVolumePathNamesForVolumeNameW failed: {}", + unsafe { GetLastError() } + ); + } + + let mut sectors_per_cluster = 0; + let mut bytes_per_sector = 0; + let mut number_of_free_clusters = 0; + let mut total_number_of_clusters = 0; + + let success = unsafe { + GetDiskFreeSpaceW( + String2LPWSTR!(path.as_os_str()), + &mut sectors_per_cluster, + &mut bytes_per_sector, + &mut number_of_free_clusters, + &mut total_number_of_clusters, + ) + }; + if 0 == success { + // Fails in case of CD for example + //crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe { + //GetLastError() + //}); + } + + let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64; + FsUsage { + // f_bsize File system block size. + blocksize: bytes_per_cluster as u64, + // f_blocks - Total number of blocks on the file system, in units of f_frsize. + // frsize = Fundamental file system block size (fragment size). + blocks: total_number_of_clusters as u64, + // Total number of free blocks. + bfree: number_of_free_clusters as u64, + // Total number of free blocks available to non-privileged processes. + bavail: 0 as u64, + bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0, + // Total number of file nodes (inodes) on the file system. + files: 0 as u64, // Not available on windows + // Total number of free file nodes (inodes). + ffree: 4096 as u64, // Meaningless on Windows + } + } +} + +#[cfg(unix)] pub trait FsMeta { fn fs_type(&self) -> i64; fn iosize(&self) -> u64; @@ -134,6 +534,7 @@ pub trait FsMeta { fn namelen(&self) -> u64; } +#[cfg(unix)] impl FsMeta for Sstatfs { fn blksize(&self) -> i64 { self.f_bsize as i64 @@ -213,6 +614,7 @@ impl FsMeta for Sstatfs { } } +#[cfg(unix)] pub fn statfs>(path: P) -> Result where Vec: From

, @@ -236,6 +638,40 @@ where } } +pub fn pretty_time(sec: i64, nsec: i64) -> String { + // sec == seconds since UNIX_EPOCH + // nsec == nanoseconds since (UNIX_EPOCH + sec) + let tm = time::at(Timespec::new(sec, nsec as i32)); + let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap(); + if res.ends_with(" -0000") { + res.replace(" -0000", " +0000") + } else { + res + } +} + +#[cfg(unix)] +pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str { + match mode & S_IFMT { + S_IFREG => { + if size != 0 { + "regular file" + } else { + "regular empty file" + } + } + S_IFDIR => "directory", + S_IFLNK => "symbolic link", + S_IFCHR => "character special file", + S_IFBLK => "block special file", + S_IFIFO => "fifo", + S_IFSOCK => "socket", + // TODO: Other file types + // See coreutils/gnulib/lib/file-type.c + _ => "weird file", + } +} + pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { match fstype { 0x6163_6673 => "acfs".into(), @@ -356,192 +792,12 @@ pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> { } } -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] -extern "C" { - #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] - #[link_name = "getmntinfo$INODE64"] - fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int; - - #[cfg(any( - all(target_os = "freebsd"), - all(target_vendor = "apple", target_arch = "aarch64") - ))] - fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int; -} - -#[derive(Debug, Clone)] -pub struct MountInfo { - // it stores `volume_name` in windows platform and `dev_id` in unix platform - dev_id: String, - dev_name: String, - fs_type: String, - pub mount_dir: String, - mount_option: String, // we only care "bind" option - mount_root: String, - remote: bool, - dummy: bool, -} - -impl MountInfo { - fn set_missing_fields(&mut self) { - #[cfg(unix)] - { - // We want to keep the dev_id on Windows - // but set dev_id - let path = CString::new(self.mount_dir.clone()).unwrap(); - unsafe { - let mut stat = mem::zeroed(); - if libc::stat(path.as_ptr(), &mut stat) == 0 { - self.dev_id = (stat.st_dev as i32).to_string(); - } else { - self.dev_id = "".to_string(); - } - } - } - // set MountInfo::dummy - match self.fs_type.as_ref() { - "autofs" | "proc" | "subfs" - /* for Linux 2.6/3.x */ - | "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs" - /* FreeBSD, Linux 2.4 */ - | "devfs" - /* for NetBSD 3.0 */ - | "kernfs" - /* for Irix 6.5 */ - | "ignore" => self.dummy = true, - _ => self.dummy = self.fs_type == "none" - && self.mount_option.find(MOUNT_OPT_BIND).is_none(), - } - // set MountInfo::remote - #[cfg(unix)] - { - if self.dev_name.find(':').is_some() - || (self.dev_name.starts_with("//") && self.fs_type == "smbfs" - || self.fs_type == "cifs") - || self.dev_name == "-hosts" - { - self.remote = true; - } else { - self.remote = false; - } - } - } - - #[cfg(target_os = "linux")] - fn new(file_name: &str, raw: Vec<&str>) -> Option { - match file_name { - // Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - // "man proc" for more details - "/proc/self/mountinfo" => { - let mut m = MountInfo { - dev_id: "".to_string(), - dev_name: raw[9].to_string(), - fs_type: raw[8].to_string(), - mount_root: raw[3].to_string(), - mount_dir: raw[4].to_string(), - mount_option: raw[5].to_string(), - remote: false, - dummy: false, - }; - m.set_missing_fields(); - Some(m) - } - "/etc/mtab" => { - let mut m = MountInfo { - dev_id: "".to_string(), - dev_name: raw[0].to_string(), - fs_type: raw[2].to_string(), - mount_root: "".to_string(), - mount_dir: raw[1].to_string(), - mount_option: raw[3].to_string(), - remote: false, - dummy: false, - }; - m.set_missing_fields(); - Some(m) - } - _ => None, - } - } -} - -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -use std::ffi::CStr; -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] -impl From for MountInfo { - fn from(statfs: Sstatfs) -> Self { - let mut info = MountInfo { - dev_id: "".to_string(), - dev_name: unsafe { - CStr::from_ptr(&statfs.f_mntfromname[0]) - .to_string_lossy() - .into_owned() - }, - fs_type: unsafe { - CStr::from_ptr(&statfs.f_fstypename[0]) - .to_string_lossy() - .into_owned() - }, - mount_dir: unsafe { - CStr::from_ptr(&statfs.f_mntonname[0]) - .to_string_lossy() - .into_owned() - }, - mount_root: "".to_string(), - mount_option: "".to_string(), - remote: false, - dummy: false, - }; - info.set_missing_fields(); - info - } -} - -#[cfg(target_os = "linux")] -use std::fs::File; -#[cfg(target_os = "linux")] -use std::io::{BufRead, BufReader}; -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -use std::ptr; -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -use std::slice; -pub fn read_fs_list() -> Vec { - #[cfg(target_os = "linux")] - { - let (file_name, fobj) = File::open(LINUX_MOUNTINFO) - .map(|f| (LINUX_MOUNTINFO, f)) - .or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f))) - .expect("failed to find mount list files"); - let reader = BufReader::new(fobj); - reader - .lines() - .filter_map(|line| line.ok()) - .filter_map(|line| { - let raw_data = line.split_whitespace().collect::>(); - MountInfo::new(file_name, raw_data) - }) - .collect::>() - } - #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] - { - let mut mptr: *mut Sstatfs = ptr::null_mut(); - let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) }; - if len < 0 { - crash!(1, "getmntinfo failed"); - } - let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) }; - mounts - .iter() - .map(|m| MountInfo::from(*m)) - .collect::>() - } -} - #[cfg(test)] mod tests { use super::*; #[test] + #[cfg(unix)] fn test_file_type() { assert_eq!("block special file", pretty_filetype(S_IFBLK, 0)); assert_eq!("character special file", pretty_filetype(S_IFCHR, 0)); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index f2a4292fb..28bae08cb 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -189,6 +189,7 @@ mod tests { vec.into_iter().collect_str(handling) } + #[cfg(any(unix, target_os = "redox"))] fn test_invalid_utf8_args_lossy(os_str: &OsStr) { //assert our string is invalid utf8 assert!(os_str.to_os_string().into_string().is_err()); @@ -212,6 +213,7 @@ mod tests { ); } + #[cfg(any(unix, target_os = "redox"))] fn test_invalid_utf8_args_ignore(os_str: &OsStr) { //assert our string is invalid utf8 assert!(os_str.to_os_string().into_string().is_err()); @@ -236,7 +238,7 @@ mod tests { //create a vector containing only correct encoding let test_vec = make_os_vec(&OsString::from("test2")); //expect complete conversion without losses, even when lossy conversion is accepted - let _ = collect_os_str(test_vec.clone(), InvalidEncodingHandling::ConvertLossy) + let _ = collect_os_str(test_vec, InvalidEncodingHandling::ConvertLossy) .expect_complete("Lossy conversion not expected in this test"); } From 2ec4bee350bf5d974665d3391fc91c6d90729456 Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Thu, 6 May 2021 08:28:54 -0400 Subject: [PATCH 283/399] test: improve handling of inverted Boolean expressions - add `==` as undocumented alias of `=` - handle negated comparison of `=` as literal - negation generally applies to only the first expression of a Boolean chain, except when combining evaluation of two literal strings --- src/uu/test/src/parser.rs | 88 ++++++++++++++++++++++++++---------- src/uu/test/src/test.rs | 2 +- tests/by-util/test_test.rs | 93 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 25 deletions(-) diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 2c9c9db30..aa44bc5f2 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -33,7 +33,7 @@ impl Symbol { "(" => Symbol::LParen, "!" => Symbol::Bang, "-a" | "-o" => Symbol::BoolOp(s), - "=" | "!=" => Symbol::StringOp(s), + "=" | "==" | "!=" => Symbol::StringOp(s), "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::IntOp(s), "-ef" | "-nt" | "-ot" => Symbol::FileOp(s), "-n" | "-z" => Symbol::StrlenOp(s), @@ -83,7 +83,7 @@ impl Symbol { /// TERM → str OP str /// TERM → str | 𝜖 /// OP → STRINGOP | INTOP | FILEOP -/// STRINGOP → = | != +/// STRINGOP → = | == | != /// INTOP → -eq | -ge | -gt | -le | -lt | -ne /// FILEOP → -ef | -nt | -ot /// STRLEN → -n | -z @@ -163,7 +163,7 @@ impl Parser { match self.peek() { // lparen is a literal when followed by nothing or comparison Symbol::None | Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { - self.literal(Symbol::Literal(OsString::from("("))); + self.literal(Symbol::LParen.into_literal()); } // empty parenthetical Symbol::Literal(s) if s == ")" => {} @@ -183,27 +183,67 @@ impl Parser { /// /// * `! =`: negate the result of the implicit string length test of `=` /// * `! = foo`: compare the literal strings `!` and `foo` - /// * `! `: negate the result of the expression + /// * `! = = str`: negate comparison of literal `=` and `str` + /// * `!`: bang followed by nothing is literal + /// * `! EXPR`: negate the result of the expression + /// + /// Combined Boolean & negation: + /// + /// * `! ( EXPR ) [BOOLOP EXPR]`: negate the parenthesized expression only + /// * `! UOP str BOOLOP EXPR`: negate the unary subexpression + /// * `! str BOOLOP str`: negate the entire Boolean expression + /// * `! str BOOLOP EXPR BOOLOP EXPR`: negate the value of the first `str` term /// fn bang(&mut self) { - if let Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) = self.peek() { - // we need to peek ahead one more token to disambiguate the first - // two cases listed above: case 1 — `! ` — and - // case 2: ` OP str`. - let peek2 = self.tokens.clone().nth(1); + match self.peek() { + Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) | Symbol::BoolOp(_) => { + // we need to peek ahead one more token to disambiguate the first + // three cases listed above + let peek2 = Symbol::new(self.tokens.clone().nth(1)); - if peek2.is_none() { - // op is literal - let op = self.next_token().into_literal(); - self.stack.push(op); - self.stack.push(Symbol::Bang); - } else { - // bang is literal; parsing continues with op - self.literal(Symbol::Literal(OsString::from("!"))); + match peek2 { + // case 1: `! ` + // case 3: `! = OP str` + Symbol::StringOp(_) | Symbol::None => { + // op is literal + let op = self.next_token().into_literal(); + self.literal(op); + self.stack.push(Symbol::Bang); + } + // case 2: ` OP str [BOOLOP EXPR]`. + _ => { + // bang is literal; parsing continues with op + self.literal(Symbol::Bang.into_literal()); + self.maybe_boolop(); + } + } + } + + // bang followed by nothing is literal + Symbol::None => self.stack.push(Symbol::Bang.into_literal()), + + _ => { + // peek ahead up to 4 tokens to determine if we need to negate + // the entire expression or just the first term + let peek4: Vec = self + .tokens + .clone() + .take(4) + .map(|token| Symbol::new(Some(token))) + .collect(); + + match peek4.as_slice() { + // we peeked ahead 4 but there were only 3 tokens left + [Symbol::Literal(_), Symbol::BoolOp(_), Symbol::Literal(_)] => { + self.expr(); + self.stack.push(Symbol::Bang); + } + _ => { + self.term(); + self.stack.push(Symbol::Bang); + } + } } - } else { - self.expr(); - self.stack.push(Symbol::Bang); } } @@ -211,13 +251,14 @@ impl Parser { /// as appropriate. fn maybe_boolop(&mut self) { if self.peek_is_boolop() { - let token = self.tokens.next().unwrap(); // safe because we peeked + let symbol = self.next_token(); // BoolOp by itself interpreted as Literal if let Symbol::None = self.peek() { - self.literal(Symbol::Literal(token)) + self.literal(symbol.into_literal()); } else { - self.boolop(Symbol::BoolOp(token)) + self.boolop(symbol); + self.maybe_boolop(); } } } @@ -231,7 +272,6 @@ impl Parser { if op == Symbol::BoolOp(OsString::from("-a")) { self.term(); self.stack.push(op); - self.maybe_boolop(); } else { self.expr(); self.stack.push(op); diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 3e97af0a6..86950ecc2 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -57,7 +57,7 @@ fn eval(stack: &mut Vec) -> Result { Some(Symbol::StringOp(op)) => { let b = stack.pop(); let a = stack.pop(); - Ok(if op == "=" { a == b } else { a != b }) + Ok(if op == "!=" { a != b } else { a == b }) } Some(Symbol::IntOp(op)) => { let b = pop_literal!(); diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 000013d9c..0dfc0c620 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -122,6 +122,13 @@ fn test_zero_len_not_equals_zero_len_is_false() { new_ucmd!().args(&["", "!=", ""]).run().status_code(1); } +#[test] +fn test_double_equal_is_string_comparison_op() { + // undocumented but part of the GNU test suite + new_ucmd!().args(&["t", "==", "t"]).succeeds(); + new_ucmd!().args(&["t", "==", "f"]).run().status_code(1); +} + #[test] fn test_string_comparison() { let scenario = TestScenario::new(util_name!()); @@ -131,11 +138,22 @@ fn test_string_comparison() { ["(", "=", "("], ["(", "!=", ")"], ["!", "=", "!"], + ["=", "=", "="], ]; for test in &tests { scenario.ucmd().args(&test[..]).succeeds(); } + + // run the inverse of all these tests + for test in &tests { + scenario + .ucmd() + .arg("!") + .args(&test[..]) + .run() + .status_code(1); + } } #[test] @@ -485,6 +503,81 @@ fn test_op_prec_and_or_2_overridden_by_parentheses() { .status_code(1); } +#[test] +fn test_negated_boolean_precedence() { + let scenario = TestScenario::new(util_name!()); + + let tests = [ + vec!["!", "(", "foo", ")", "-o", "bar"], + vec!["!", "", "-o", "", "-a", ""], + vec!["!", "(", "", "-a", "", ")", "-o", ""], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } + + let negative_tests = [ + vec!["!", "-n", "", "-a", ""], + vec!["", "-a", "", "-o", ""], + vec!["!", "", "-a", "", "-o", ""], + vec!["!", "(", "", "-a", "", ")", "-a", ""], + ]; + + for test in &negative_tests { + scenario.ucmd().args(&test[..]).run().status_code(1); + } +} + +#[test] +fn test_bang_boolop_precedence() { + // For a Boolean combination of two literals, bang inverts the entire expression + new_ucmd!().args(&["!", "", "-a", ""]).succeeds(); + new_ucmd!().args(&["!", "", "-o", ""]).succeeds(); + + new_ucmd!() + .args(&["!", "a value", "-o", "another value"]) + .run() + .status_code(1); + + // Introducing a UOP — even one that is equivalent to a bare string — causes + // bang to invert only the first term + new_ucmd!() + .args(&["!", "-n", "", "-a", ""]) + .run() + .status_code(1); + new_ucmd!() + .args(&["!", "", "-a", "-n", ""]) + .run() + .status_code(1); + + // for compound Boolean expressions, bang inverts the _next_ expression + // only, not the entire compound expression + new_ucmd!() + .args(&["!", "", "-a", "", "-a", ""]) + .run() + .status_code(1); + + // parentheses can override this + new_ucmd!() + .args(&["!", "(", "", "-a", "", "-a", "", ")"]) + .succeeds(); +} + +#[test] +fn test_inverted_parenthetical_boolop_precedence() { + // For a Boolean combination of two literals, bang inverts the entire expression + new_ucmd!() + .args(&["!", "a value", "-o", "another value"]) + .run() + .status_code(1); + + // only the parenthetical is inverted, not the entire expression + new_ucmd!() + .args(&["!", "(", "a value", ")", "-o", "another value"]) + .succeeds(); +} + #[test] #[ignore = "fixme: error reporting"] fn test_dangling_parenthesis() { From 6aee792a9318dd47b9c9738a3a6c440e99f6fb85 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 11 May 2021 09:29:46 +0200 Subject: [PATCH 284/399] Remove travis CI * it is redundant with github action * less integrated * fails someone for some unexpected reasons * it is blocking code coverage results ?! --- .travis.yml | 72 ------------------------------------------------- CONTRIBUTING.md | 4 --- README.md | 1 - 3 files changed, 77 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 389ba44b0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,72 +0,0 @@ -language: rust - -rust: - - stable - - beta - -os: - - linux - # - osx - -env: - # sphinx v1.8.0 is bugged & fails for linux builds; so, force specific `sphinx` version - global: FEATURES='' TEST_INSTALL='' SPHINX_VERSIONED='sphinx==1.7.8' - -matrix: - allow_failures: - - rust: beta - - rust: nightly - fast_finish: true - include: - - rust: 1.40.0 - env: FEATURES=unix - # - rust: stable - # os: linux - # env: FEATURES=unix TEST_INSTALL=true - # - rust: stable - # os: osx - # env: FEATURES=macos TEST_INSTALL=true - - rust: nightly - os: linux - env: FEATURES=nightly,unix TEST_INSTALL=true - - rust: nightly - os: osx - env: FEATURES=nightly,macos TEST_INSTALL=true - - rust: nightly - os: linux - env: FEATURES=nightly,feat_os_unix_redox CC=x86_64-unknown-redox-gcc CARGO_ARGS='--no-default-features --target=x86_64-unknown-redox' REDOX=1 - -cache: - directories: - - $HOME/.cargo - -sudo: true - -before_install: - - if [ $REDOX ]; then ./.travis/redox-toolchain.sh; fi - -install: - - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python-pip && sudo pip install $SPHINX_VERSIONED; fi - - | - if [ $TRAVIS_OS_NAME = osx ]; then - brew update - brew upgrade python - pip3 install $SPHINX_VERSIONED - fi - -script: - - cargo build $CARGO_ARGS --features "$FEATURES" - - if [ ! $REDOX ]; then cargo test $CARGO_ARGS -p uucore -p coreutils --features "$FEATURES" --no-fail-fast; fi - - if [ -n "$TEST_INSTALL" ]; then mkdir installdir_test; DESTDIR=installdir_test make install; [ `ls installdir_test/usr/local/bin | wc -l` -gt 0 ]; fi - -addons: - apt: - packages: - - libssl-dev - -after_success: | - if [ "$TRAVIS_OS_NAME" = linux -a "$TRAVIS_RUST_VERSION" = stable ]; then - bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - cargo tarpaulin --out Xml - bash <(curl -s https://codecov.io/bash) - fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bcb1f8fff..3793a0968 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,10 +70,6 @@ lines for non-utility modules include: README: add help ``` -``` -travis: fix build -``` - ``` uucore: add new modules ``` diff --git a/README.md b/README.md index 95dc036fd..7de4419af 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei) [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) -[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils) [![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) From b9d44facb9a8c03b968280ff4a085e863a97e829 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 11 May 2021 10:27:13 +0200 Subject: [PATCH 285/399] refresh cargo.lock with recent updates --- Cargo.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e729bfcd2..a3f870fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,9 +783,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" dependencies = [ "wasm-bindgen", ] @@ -2723,9 +2723,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2733,9 +2733,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" dependencies = [ "bumpalo", "lazy_static", @@ -2748,9 +2748,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" dependencies = [ "quote 1.0.9", "wasm-bindgen-macro-support", @@ -2758,9 +2758,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -2771,15 +2771,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" [[package]] name = "web-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" dependencies = [ "js-sys", "wasm-bindgen", From 8200d399e8d5bc7d7da0d7a65f693620b73e0ed2 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 11 May 2021 23:03:59 +0200 Subject: [PATCH 286/399] date: fix format for nanoseconds --- src/uu/date/src/date.rs | 13 +++++++++---- tests/by-util/test_date.rs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 317fd72d4..577cba460 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -207,11 +207,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .alias(OPT_UNIVERSAL_2) .help("print or set Coordinated Universal Time (UTC)"), ) - .arg(Arg::with_name(OPT_FORMAT).multiple(true)) + .arg(Arg::with_name(OPT_FORMAT).multiple(false)) .get_matches_from(args); let format = if let Some(form) = matches.value_of(OPT_FORMAT) { - let form = form[1..].into(); + if !form.starts_with('+') { + eprintln!("date: invalid date ‘{}’", form); + return 1; + } + // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f` + let form = form[1..].replace("%N", "%f"); Format::Custom(form) } else if let Some(fmt) = matches .values_of(OPT_ISO_8601) @@ -237,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let set_to = match matches.value_of(OPT_SET).map(parse_date) { None => None, Some(Err((input, _err))) => { - eprintln!("date: invalid date '{}'", input); + eprintln!("date: invalid date ‘{}’", input); return 1; } Some(Ok(date)) => Some(date), @@ -301,7 +306,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{}", formatted); } Err((input, _err)) => { - println!("date: invalid date '{}'", input); + println!("date: invalid date ‘{}’", input); } } } diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 0ca0a74ea..464655315 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -104,6 +104,23 @@ fn test_date_format_full_day() { .stdout_matches(&re); } +#[test] +fn test_date_nano_seconds() { + // %N nanoseconds (000000000..999999999) + let re = Regex::new(r"^\d{1,9}$").unwrap(); + new_ucmd!().arg("+%N").succeeds().stdout_matches(&re); +} + +#[test] +fn test_date_format_without_plus() { + // [+FORMAT] + new_ucmd!() + .arg("%s") + .fails() + .stderr_contains("date: invalid date ‘%s’") + .code_is(1); +} + #[test] #[cfg(all(unix, not(target_os = "macos")))] fn test_date_set_valid() { From 12a43d6eb3e1ec5d6a249f548e7b05c2bfb29065 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 12 May 2021 10:21:24 +0200 Subject: [PATCH 287/399] date: fix format literal for nanoseconds --- src/uu/date/src/date.rs | 7 ++++--- tests/by-util/test_date.rs | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 577cba460..1fe80c03f 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -215,8 +215,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { eprintln!("date: invalid date ‘{}’", form); return 1; } - // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f` - let form = form[1..].replace("%N", "%f"); + let form = form[1..].to_string(); Format::Custom(form) } else if let Some(fmt) = matches .values_of(OPT_ISO_8601) @@ -302,7 +301,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for date in dates { match date { Ok(date) => { - let formatted = date.format(format_string); + // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f` + let format_string = &format_string.replace("%N", "%f"); + let formatted = date.format(format_string).to_string().replace("%f", "%N"); println!("{}", formatted); } Err((input, _err)) => { diff --git a/tests/by-util/test_date.rs b/tests/by-util/test_date.rs index 464655315..f4990566a 100644 --- a/tests/by-util/test_date.rs +++ b/tests/by-util/test_date.rs @@ -121,6 +121,12 @@ fn test_date_format_without_plus() { .code_is(1); } +#[test] +fn test_date_format_literal() { + new_ucmd!().arg("+%%s").succeeds().stdout_is("%s\n"); + new_ucmd!().arg("+%%N").succeeds().stdout_is("%N\n"); +} + #[test] #[cfg(all(unix, not(target_os = "macos")))] fn test_date_set_valid() { From 0669c89ef311111f1cd1d81d89032457bb2fd0c8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 12 May 2021 14:47:45 +0200 Subject: [PATCH 288/399] refresh cargo.lock with recent updates --- Cargo.lock | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d942c04d4..7bd97c917 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1820,11 +1820,9 @@ name = "uu_df" version = "0.0.6" dependencies = [ "clap", - "libc", "number_prefix", "uucore", "uucore_procs", - "winapi 0.3.9", ] [[package]] @@ -2407,8 +2405,6 @@ name = "uu_stat" version = "0.0.6" dependencies = [ "clap", - "libc", - "time", "uucore", "uucore_procs", ] @@ -2682,6 +2678,7 @@ dependencies = [ "thiserror", "time", "wild", + "winapi 0.3.9", ] [[package]] From 3114fd77be546db6e72046d57d0abfb956486f20 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 11 May 2021 20:13:38 -0400 Subject: [PATCH 289/399] tail: use &mut File instead of mut file: &File --- src/uu/tail/src/tail.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 0a3ff778d..9246f4f43 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -241,7 +241,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let mut file = File::open(&path).unwrap(); if is_seekable(&mut file) { - bounded_tail(&file, &settings); + bounded_tail(&mut file, &settings); if settings.follow { let reader = BufReader::new(file); readers.push(reader); @@ -400,7 +400,7 @@ fn follow(readers: &mut [BufReader], filenames: &[String], settings: /// true. The `file` is left seek'd to the position just after the byte that /// `should_stop` returned true for. fn backwards_thru_file( - mut file: &File, + file: &mut File, size: u64, buf: &mut Vec, delimiter: u8, @@ -448,14 +448,14 @@ fn backwards_thru_file( /// end of the file, and then read the file "backwards" in blocks of size /// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up /// being a nice performance win for very large files. -fn bounded_tail(mut file: &File, settings: &Settings) { +fn bounded_tail(file: &mut File, settings: &Settings) { let size = file.seek(SeekFrom::End(0)).unwrap(); let mut buf = vec![0; BLOCK_SIZE as usize]; // Find the position in the file to start printing from. match settings.mode { FilterMode::Lines(mut count, delimiter) => { - backwards_thru_file(&file, size, &mut buf, delimiter, &mut |byte| { + backwards_thru_file(file, size, &mut buf, delimiter, &mut |byte| { if byte == delimiter { count -= 1; count == 0 From 2e621759b255391b379ce34fa42e86bc68ed4701 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 11 May 2021 21:10:30 -0400 Subject: [PATCH 290/399] tail: refactor code into ReverseChunks iterator Refactor code from the `backwards_thru_file()` function into a new `ReverseChunks` iterator, and use that iterator to simplify the implementation of the `backwards_thru_file()` function. The `ReverseChunks` iterator yields `Vec` objects, each of which references bytes of a given file. --- src/uu/tail/src/chunks.rs | 83 ++++++++++++++++++++++++++++++++++++ src/uu/tail/src/tail.rs | 89 ++++++++++++++++----------------------- 2 files changed, 120 insertions(+), 52 deletions(-) create mode 100644 src/uu/tail/src/chunks.rs diff --git a/src/uu/tail/src/chunks.rs b/src/uu/tail/src/chunks.rs new file mode 100644 index 000000000..57a26dabf --- /dev/null +++ b/src/uu/tail/src/chunks.rs @@ -0,0 +1,83 @@ +//! Iterating over a file by chunks, starting at the end of the file. +//! +//! Use [`ReverseChunks::new`] to create a new iterator over chunks of +//! bytes from the file. +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; + +/// When reading files in reverse in `bounded_tail`, this is the size of each +/// block read at a time. +pub const BLOCK_SIZE: u64 = 1 << 16; + +/// An iterator over a file in non-overlapping chunks from the end of the file. +/// +/// Each chunk is a [`Vec`]<[`u8`]> of size [`BLOCK_SIZE`] (except +/// possibly the last chunk, which might be smaller). Each call to +/// [`next`] will seek backwards through the given file. +pub struct ReverseChunks<'a> { + /// The file to iterate over, by blocks, from the end to the beginning. + file: &'a File, + + /// The total number of bytes in the file. + size: u64, + + /// The total number of blocks to read. + max_blocks_to_read: usize, + + /// The index of the next block to read. + block_idx: usize, +} + +impl<'a> ReverseChunks<'a> { + pub fn new(file: &'a mut File) -> ReverseChunks<'a> { + let size = file.seek(SeekFrom::End(0)).unwrap(); + let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize; + let block_idx = 0; + ReverseChunks { + file, + size, + max_blocks_to_read, + block_idx, + } + } +} + +impl<'a> Iterator for ReverseChunks<'a> { + type Item = Vec; + + fn next(&mut self) -> Option { + // If there are no more chunks to read, terminate the iterator. + if self.block_idx >= self.max_blocks_to_read { + return None; + } + + // The chunk size is `BLOCK_SIZE` for all but the last chunk + // (that is, the chunk closest to the beginning of the file), + // which contains the remainder of the bytes. + let block_size = if self.block_idx == self.max_blocks_to_read - 1 { + self.size % BLOCK_SIZE + } else { + BLOCK_SIZE + }; + + // Seek backwards by the next chunk, read the full chunk into + // `buf`, and then seek back to the start of the chunk again. + let mut buf = vec![0; BLOCK_SIZE as usize]; + let pos = self + .file + .seek(SeekFrom::Current(-(block_size as i64))) + .unwrap(); + self.file + .read_exact(&mut buf[0..(block_size as usize)]) + .unwrap(); + let pos2 = self + .file + .seek(SeekFrom::Current(-(block_size as i64))) + .unwrap(); + assert_eq!(pos, pos2); + + self.block_idx += 1; + + Some(buf[0..(block_size as usize)].to_vec()) + } +} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 9246f4f43..6dafee184 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -15,8 +15,11 @@ extern crate clap; #[macro_use] extern crate uucore; +mod chunks; mod platform; mod ringbuffer; +use chunks::ReverseChunks; +use chunks::BLOCK_SIZE; use ringbuffer::RingBuffer; use clap::{App, Arg}; @@ -355,10 +358,6 @@ pub fn parse_size(mut size_slice: &str) -> Result { } } -/// When reading files in reverse in `bounded_tail`, this is the size of each -/// block read at a time. -const BLOCK_SIZE: u64 = 1 << 16; - fn follow(readers: &mut [BufReader], filenames: &[String], settings: &Settings) { assert!(settings.follow); let mut last = readers.len() - 1; @@ -396,48 +395,42 @@ fn follow(readers: &mut [BufReader], filenames: &[String], settings: } } -/// Iterate over bytes in the file, in reverse, until `should_stop` returns -/// true. The `file` is left seek'd to the position just after the byte that -/// `should_stop` returned true for. -fn backwards_thru_file( - file: &mut File, - size: u64, - buf: &mut Vec, - delimiter: u8, - should_stop: &mut F, -) where - F: FnMut(u8) -> bool, -{ - assert!(buf.len() >= BLOCK_SIZE as usize); +/// Iterate over bytes in the file, in reverse, until we find the +/// `num_delimiters` instance of `delimiter`. The `file` is left seek'd to the +/// position just after that delimiter. +fn backwards_thru_file(file: &mut File, num_delimiters: usize, delimiter: u8) { + // This variable counts the number of delimiters found in the file + // so far (reading from the end of the file toward the beginning). + let mut counter = 0; - let max_blocks_to_read = (size as f64 / BLOCK_SIZE as f64).ceil() as usize; + for (block_idx, slice) in ReverseChunks::new(file).enumerate() { + // Iterate over each byte in the slice in reverse order. + let mut iter = slice.iter().enumerate().rev(); - for block_idx in 0..max_blocks_to_read { - let block_size = if block_idx == max_blocks_to_read - 1 { - size % BLOCK_SIZE - } else { - BLOCK_SIZE - }; - - // Seek backwards by the next block, read the full block into - // `buf`, and then seek back to the start of the block again. - let pos = file.seek(SeekFrom::Current(-(block_size as i64))).unwrap(); - file.read_exact(&mut buf[0..(block_size as usize)]).unwrap(); - let pos2 = file.seek(SeekFrom::Current(-(block_size as i64))).unwrap(); - assert_eq!(pos, pos2); - - // Iterate backwards through the bytes, calling `should_stop` on each - // one. - let slice = &buf[0..(block_size as usize)]; - for (i, ch) in slice.iter().enumerate().rev() { - // Ignore one trailing newline. - if block_idx == 0 && i as u64 == block_size - 1 && *ch == delimiter { - continue; + // Ignore a trailing newline in the last block, if there is one. + if block_idx == 0 { + if let Some(c) = slice.last() { + if *c == delimiter { + iter.next(); + } } + } - if should_stop(*ch) { - file.seek(SeekFrom::Current((i + 1) as i64)).unwrap(); - return; + // For each byte, increment the count of the number of + // delimiters found. If we have found more than the specified + // number of delimiters, terminate the search and seek to the + // appropriate location in the file. + for (i, ch) in iter { + if *ch == delimiter { + counter += 1; + if counter >= num_delimiters { + // After each iteration of the outer loop, the + // cursor in the file is at the *beginning* of the + // block, so seeking forward by `i + 1` bytes puts + // us right after the found delimiter. + file.seek(SeekFrom::Current((i + 1) as i64)).unwrap(); + return; + } } } } @@ -449,20 +442,12 @@ fn backwards_thru_file( /// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up /// being a nice performance win for very large files. fn bounded_tail(file: &mut File, settings: &Settings) { - let size = file.seek(SeekFrom::End(0)).unwrap(); let mut buf = vec![0; BLOCK_SIZE as usize]; // Find the position in the file to start printing from. match settings.mode { - FilterMode::Lines(mut count, delimiter) => { - backwards_thru_file(file, size, &mut buf, delimiter, &mut |byte| { - if byte == delimiter { - count -= 1; - count == 0 - } else { - false - } - }); + FilterMode::Lines(count, delimiter) => { + backwards_thru_file(file, count as usize, delimiter); } FilterMode::Bytes(count) => { file.seek(SeekFrom::End(-(count as i64))).unwrap(); From a4fc2b5106ad1a9226c6c396b5bb2ea191ac9814 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 13 May 2021 10:17:57 +0200 Subject: [PATCH 291/399] who: fix `--lookup` This closes #2181. `who --lookup` is failing with a runtime panic (double free). Since `crate::dns-lookup` already includes a safe wrapper for `getaddrinfo` I used this crate instead of further debugging the existing code in utmpx::canon_host(). * It was neccessary to remove the version constraint for libc in uucore. --- Cargo.lock | 29 +++++++++++-- src/uu/pinky/src/pinky.rs | 15 ++----- src/uu/who/src/who.rs | 16 ++----- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features/utmpx.rs | 65 +++++++++++++--------------- tests/by-util/test_pinky.rs | 17 ++++++++ tests/by-util/test_who.rs | 1 - 7 files changed, 79 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d942c04d4..77957de80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,6 +576,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dns-lookup" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "socket2", + "winapi 0.3.9", +] + [[package]] name = "dunce" version = "1.0.1" @@ -1445,6 +1457,17 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + [[package]] name = "strsim" version = "0.8.0" @@ -1820,11 +1843,9 @@ name = "uu_df" version = "0.0.6" dependencies = [ "clap", - "libc", "number_prefix", "uucore", "uucore_procs", - "winapi 0.3.9", ] [[package]] @@ -2407,8 +2428,6 @@ name = "uu_stat" version = "0.0.6" dependencies = [ "clap", - "libc", - "time", "uucore", "uucore_procs", ] @@ -2672,6 +2691,7 @@ name = "uucore" version = "0.0.8" dependencies = [ "data-encoding", + "dns-lookup", "dunce", "getopts", "lazy_static", @@ -2682,6 +2702,7 @@ dependencies = [ "thiserror", "time", "wild", + "winapi 0.3.9", ] [[package]] diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index e116a2382..f0ab44e5f 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -286,17 +286,10 @@ impl Pinky { print!(" {}", time_string(&ut)); - if self.include_where && !ut.host().is_empty() { - let ut_host = ut.host(); - let mut res = ut_host.splitn(2, ':'); - let host = match res.next() { - Some(_) => ut.canon_host().unwrap_or_else(|_| ut_host.clone()), - None => ut_host.clone(), - }; - match res.next() { - Some(d) => print!(" {}:{}", host, d), - None => print!(" {}", host), - } + let mut s = ut.host(); + if self.include_where && !s.is_empty() { + s = safe_unwrap!(ut.canon_host()); + print!(" {}", s); } println!(); diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index ba1360eff..aef23b3a2 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -548,20 +548,10 @@ impl Who { " ?".into() }; - let mut buf = vec![]; - let ut_host = ut.host(); - let mut res = ut_host.splitn(2, ':'); - if let Some(h) = res.next() { - if self.do_lookup { - buf.push(ut.canon_host().unwrap_or_else(|_| h.to_owned())); - } else { - buf.push(h.to_owned()); - } + let mut s = ut.host(); + if self.do_lookup { + s = safe_unwrap!(ut.canon_host()); } - if let Some(h) = res.next() { - buf.push(h.to_owned()); - } - let s = buf.join(":"); let hoststr = if s.is_empty() { s } else { format!("({})", s) }; self.print_line( diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index da51f7ca4..85efe0434 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -16,6 +16,7 @@ edition = "2018" path="src/lib/lib.rs" [dependencies] +dns-lookup = "1.0.5" dunce = "1.0.0" getopts = "<= 0.2.21" wild = "2.0.4" diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 0308d8a5e..96db33c35 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -188,47 +188,40 @@ impl Utmpx { /// Canonicalize host name using DNS pub fn canon_host(&self) -> IOResult { - const AI_CANONNAME: libc::c_int = 0x2; let host = self.host(); - let host = host.split(':').next().unwrap(); - let hints = libc::addrinfo { - ai_flags: AI_CANONNAME, - ai_family: 0, - ai_socktype: 0, - ai_protocol: 0, - ai_addrlen: 0, - ai_addr: ptr::null_mut(), - ai_canonname: ptr::null_mut(), - ai_next: ptr::null_mut(), - }; - let c_host = CString::new(host).unwrap(); - let mut res = ptr::null_mut(); - let status = unsafe { - libc::getaddrinfo( - c_host.as_ptr(), - ptr::null(), - &hints as *const _, - &mut res as *mut _, - ) - }; - if status == 0 { - let info: libc::addrinfo = unsafe { ptr::read(res as *const _) }; - // http://lists.gnu.org/archive/html/bug-coreutils/2006-09/msg00300.html - // says Darwin 7.9.0 getaddrinfo returns 0 but sets - // res->ai_canonname to NULL. - let ret = if info.ai_canonname.is_null() { - Ok(String::from(host)) - } else { - Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() }) + + // TODO: change to use `split_once` when MSRV hits 1.52.0 + // let (hostname, display) = host.split_once(':').unwrap_or((&host, "")); + let mut h = host.split(':'); + let hostname = h.next().unwrap_or(&host); + let display = h.next().unwrap_or(""); + + if !hostname.is_empty() { + extern crate dns_lookup; + use dns_lookup::{getaddrinfo, AddrInfoHints}; + + const AI_CANONNAME: i32 = 0x2; + let hints = AddrInfoHints { + flags: AI_CANONNAME, + ..AddrInfoHints::default() }; - unsafe { - libc::freeaddrinfo(res); + let sockets = getaddrinfo(Some(&hostname), None, Some(hints)) + .unwrap() + .collect::>>()?; + for socket in sockets { + if let Some(ai_canonname) = socket.canonname { + return Ok(if display.is_empty() { + ai_canonname + } else { + format!("{}:{}", ai_canonname, display) + }); + } } - ret - } else { - Err(IOError::last_os_error()) } + + Ok(host.to_string()) } + pub fn iter_all_records() -> UtmpxIter { UtmpxIter } diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 1a7ef8b61..904a05f93 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -98,6 +98,23 @@ fn test_short_format_q() { assert_eq!(v_actual, v_expect); } +#[cfg(target_os = "linux")] +#[test] +fn test_no_flag() { + let scene = TestScenario::new(util_name!()); + + let actual = scene.ucmd().succeeds().stdout_move_str(); + let expect = scene + .cmd_keepenv(util_name!()) + .env("LANGUAGE", "C") + .succeeds() + .stdout_move_str(); + + let v_actual: Vec<&str> = actual.split_whitespace().collect(); + let v_expect: Vec<&str> = expect.split_whitespace().collect(); + assert_eq!(v_actual, v_expect); +} + #[cfg(target_os = "linux")] fn expected_result(args: &[&str]) -> String { TestScenario::new(util_name!()) diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 8aeecfb55..a5637f23a 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -162,7 +162,6 @@ fn test_users() { #[cfg(target_os = "linux")] #[test] -#[ignore] fn test_lookup() { for opt in vec!["--lookup"] { new_ucmd!() From e8d911d9d5ccf1c92d53a02ed8cc8fc729950b36 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 15 May 2021 10:32:03 -0400 Subject: [PATCH 292/399] wc: correct some error messages for invalid inputs Change the error messages that get printed to `stderr` for compatibility with GNU `wc` when an input is a directory and when an input does not exist. Fixes #2211. --- src/uu/wc/src/wc.rs | 27 +++++++++++++++++++++++++-- tests/by-util/test_wc.rs | 23 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 226608d40..5670508f4 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -22,7 +22,7 @@ use thiserror::Error; use std::cmp::max; use std::fs::File; -use std::io::{self, Write}; +use std::io::{self, ErrorKind, Write}; use std::path::Path; #[derive(Error, Debug)] @@ -254,6 +254,29 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { + show_error_custom_description!(path, "Is a directory"); + } + (Input::Path(path), WcError::Io(e)) if e.kind() == ErrorKind::NotFound => { + show_error_custom_description!(path, "No such file or directory"); + } + (_, e) => { + show_error!("{}", e); + } + }; +} + fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let mut total_word_count = WordCount::default(); let mut results = vec![]; @@ -264,7 +287,7 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { for input in &inputs { let word_count = word_count_from_input(&input, settings).unwrap_or_else(|err| { - show_error!("{}", err); + show_error(&input, err); error_count += 1; WordCount::default() }); diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index b61d7e3aa..8036d0eaa 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -168,3 +168,26 @@ fn test_file_one_long_word() { .run() .stdout_is(" 1 1 10001 10001 10000 onelongword.txt\n"); } + +/// Test that getting counts from a directory is an error. +#[test] +fn test_read_from_directory_error() { + // TODO To match GNU `wc`, the `stdout` should be: + // + // " 0 0 0 .\n" + // + new_ucmd!() + .args(&["."]) + .fails() + .stderr_contains(".: Is a directory\n") + .stdout_is("0 0 0 .\n"); +} + +/// Test that getting counts from nonexistent file is an error. +#[test] +fn test_read_from_nonexistent_file() { + new_ucmd!() + .args(&["bogusfile"]) + .fails() + .stderr_contains("bogusfile: No such file or directory\n"); +} From 97a49c7c95ec51839650b3b95ebff7d44ea401db Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 7 May 2021 15:07:17 -0400 Subject: [PATCH 293/399] wc: compute min width to format counts up front Fix two issues with the string formatting width for counts displayed by `wc`. First, the output was previously not using the default minimum width (seven characters) when reading from `stdin`. This commit corrects this behavior to match GNU `wc`. For example, $ cat alice_in_wonderland.txt | wc 5 57 302 Second, if at least 10^7 bytes were read from `stdin` *after* reading from a smaller regular file, then every output row would have width 8. This disagrees with GNU `wc`, in which only the `stdin` row and the total row would have width 8. This commit corrects this behavior to match GNU `wc`. For example, $ printf "%.0s0" {1..10000000} | wc emptyfile.txt - 0 0 0 emptyfile.txt 0 1 10000000 0 1 10000000 total Fixes #2186. --- src/uu/wc/src/wc.rs | 106 +++++++++++++++++++++++++++++++++++---- tests/by-util/test_wc.rs | 38 +++++++++++--- 2 files changed, 128 insertions(+), 16 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 5670508f4..b323f7261 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -20,11 +20,13 @@ use wordcount::{TitledWordCount, WordCount}; use clap::{App, Arg, ArgMatches}; use thiserror::Error; -use std::cmp::max; -use std::fs::File; +use std::fs::{self, File}; use std::io::{self, ErrorKind, Write}; use std::path::Path; +/// The minimum character width for formatting counts when reading from stdin. +const MINIMUM_WIDTH: usize = 7; + #[derive(Error, Debug)] pub enum WcError { #[error("{0}")] @@ -277,11 +279,101 @@ fn show_error(input: &Input, err: WcError) { }; } +/// Compute the number of digits needed to represent any count for this input. +/// +/// If `input` is [`Input::Stdin`], then this function returns +/// [`MINIMUM_WIDTH`]. Otherwise, if metadata could not be read from +/// `input` then this function returns 1. +/// +/// # Errors +/// +/// This function will return an error if `input` is a [`Input::Path`] +/// and there is a problem accessing the metadata of the given `input`. +/// +/// # Examples +/// +/// A [`Input::Stdin`] gets a default minimum width: +/// +/// ```rust,ignore +/// let input = Input::Stdin(StdinKind::Explicit); +/// assert_eq!(7, digit_width(input)); +/// ``` +fn digit_width(input: &Input) -> WcResult> { + match input { + Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)), + Input::Path(filename) => { + let path = Path::new(filename); + let metadata = fs::metadata(path)?; + if metadata.is_file() { + // TODO We are now computing the number of bytes in a file + // twice: once here and once in `WordCount::from_line()` (or + // in `count_bytes_fast()` if that function is called + // instead). See GitHub issue #2201. + let num_bytes = metadata.len(); + let num_digits = num_bytes.to_string().len(); + Ok(Some(num_digits)) + } else { + Ok(None) + } + } + } +} + +/// Compute the number of digits needed to represent all counts in all inputs. +/// +/// `inputs` may include zero or more [`Input::Stdin`] entries, each of +/// which represents reading from `stdin`. The presence of any such +/// entry causes this function to return a width that is at least +/// [`MINIMUM_WIDTH`]. +/// +/// If `input` is empty, then this function returns 1. If file metadata +/// could not be read from any of the [`Input::Path`] inputs and there +/// are no [`Input::Stdin`] inputs, then this function returns 1. +/// +/// If there is a problem accessing the metadata, this function will +/// silently ignore the error and assume that the number of digits +/// needed to display the counts for that file is 1. +/// +/// # Examples +/// +/// An empty slice implies a width of 1: +/// +/// ```rust,ignore +/// assert_eq!(1, max_width(&vec![])); +/// ``` +/// +/// The presence of [`Input::Stdin`] implies a minimum width: +/// +/// ```rust,ignore +/// let inputs = vec![Input::Stdin(StdinKind::Explicit)]; +/// assert_eq!(7, max_width(&inputs)); +/// ``` +fn max_width(inputs: &[Input]) -> usize { + let mut result = 1; + for input in inputs { + match digit_width(input) { + Ok(maybe_n) => { + if let Some(n) = maybe_n { + result = result.max(n); + } + } + Err(_) => continue, + } + } + result +} + fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { + // Compute the width, in digits, to use when formatting counts. + // + // The width is the number of digits needed to print the number of + // bytes in the largest file. This is true regardless of whether + // the `settings` indicate that the bytes will be displayed. + let mut error_count = 0; + let max_width = max_width(&inputs); + let mut total_word_count = WordCount::default(); let mut results = vec![]; - let mut max_width: usize = 0; - let mut error_count = 0; let num_inputs = inputs.len(); @@ -291,12 +383,6 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { error_count += 1; WordCount::default() }); - // Compute the number of digits needed to display the number - // of bytes in the file. Even if the settings indicate that we - // won't *display* the number of bytes, we still use the - // number of digits in the byte count as the width when - // formatting each count as a string for output. - max_width = max(max_width, word_count.bytes.to_string().len()); total_word_count += word_count; results.push(word_count.with_title(input.to_title())); } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 8036d0eaa..1203c0b1d 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -33,7 +33,7 @@ fn test_stdin_default() { new_ucmd!() .pipe_in_fixture("lorem_ipsum.txt") .run() - .stdout_is(" 13 109 772\n"); + .stdout_is(" 13 109 772\n"); } #[test] @@ -42,7 +42,7 @@ fn test_stdin_explicit() { .pipe_in_fixture("lorem_ipsum.txt") .arg("-") .run() - .stdout_is(" 13 109 772 -\n"); + .stdout_is(" 13 109 772 -\n"); } #[test] @@ -51,9 +51,11 @@ fn test_utf8() { .args(&["-lwmcL"]) .pipe_in_fixture("UTF_8_test.txt") .run() - .stdout_is(" 300 4969 22781 22213 79\n"); - // GNU returns " 300 2086 22219 22781 79" - // TODO: we should fix that to match GNU's behavior + .stdout_is(" 300 4969 22781 22213 79\n"); + // GNU returns " 300 2086 22219 22781 79" + // + // TODO: we should fix the word, character, and byte count to + // match the behavior of GNU wc } #[test] @@ -80,7 +82,7 @@ fn test_stdin_all_counts() { .args(&["-c", "-m", "-l", "-L", "-w"]) .pipe_in_fixture("alice_in_wonderland.txt") .run() - .stdout_is(" 5 57 302 302 66\n"); + .stdout_is(" 5 57 302 302 66\n"); } #[test] @@ -169,6 +171,30 @@ fn test_file_one_long_word() { .stdout_is(" 1 1 10001 10001 10000 onelongword.txt\n"); } +/// Test that the number of bytes in the file dictate the display width. +/// +/// The width in digits of any count is the width in digits of the +/// number of bytes in the file, regardless of whether the number of +/// bytes are displayed. +#[test] +fn test_file_bytes_dictate_width() { + // This file has 10,001 bytes. Five digits are required to + // represent that. Even though the number of lines is 1 and the + // number of words is 0, each of those counts is formatted with + // five characters, filled with whitespace. + new_ucmd!() + .args(&["-lw", "onelongemptyline.txt"]) + .run() + .stdout_is(" 1 0 onelongemptyline.txt\n"); + + // This file has zero bytes. Only one digit is required to + // represent that. + new_ucmd!() + .args(&["-lw", "emptyfile.txt"]) + .run() + .stdout_is("0 0 emptyfile.txt\n"); +} + /// Test that getting counts from a directory is an error. #[test] fn test_read_from_directory_error() { From 733d347fa86839a84d5cc153f0428dacd3547632 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 11 May 2021 23:18:32 -0400 Subject: [PATCH 294/399] head: simplify rbuf_n_bytes() in head.rs Simplify the code in `rbuf_n_bytes()` to use existing abstractions provided by the standard library. --- src/uu/head/src/head.rs | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 807d04314..e050d26f6 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -1,7 +1,7 @@ use clap::{App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; -use std::io::{ErrorKind, Read, Seek, SeekFrom, Write}; +use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use uucore::{crash, executable, show_error}; const EXIT_FAILURE: i32 = 1; @@ -206,38 +206,20 @@ impl Default for HeadOptions { } } -fn rbuf_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> { - if n == 0 { - return Ok(()); - } - let mut readbuf = [0u8; BUF_SIZE]; - let mut i = 0usize; +fn rbuf_n_bytes(input: R, n: usize) -> std::io::Result<()> +where + R: Read, +{ + // Read the first `n` bytes from the `input` reader. + let mut reader = input.take(n as u64); + // Write those bytes to `stdout`. let stdout = std::io::stdout(); let mut stdout = stdout.lock(); - loop { - let read = loop { - match input.read(&mut readbuf) { - Ok(n) => break n, - Err(e) => match e.kind() { - ErrorKind::Interrupted => {} - _ => return Err(e), - }, - } - }; - if read == 0 { - // might be unexpected if - // we haven't read `n` bytes - // but this mirrors GNU's behavior - return Ok(()); - } - stdout.write_all(&readbuf[..read.min(n - i)])?; - i += read.min(n - i); - if i == n { - return Ok(()); - } - } + io::copy(&mut reader, &mut stdout)?; + + Ok(()) } fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> { From 659bf58a4c80201db406e4d7c2b3e8cef56931a3 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 16 May 2021 11:30:10 -0400 Subject: [PATCH 295/399] head: print headings when reading multiple files Fix a bug in which `head` failed to print headings for `stdin` inputs when reading from multiple files, and fix another bug in which `head` failed to print a blank line between the contents of a file and the heading for the next file when reading multiple files. The output now matches that of GNU `head`. --- src/uu/head/src/head.rs | 5 ++++- tests/by-util/test_head.rs | 25 +++++++++++++++++++++++++ tests/fixtures/head/emptyfile.txt | 0 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/head/emptyfile.txt diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index e050d26f6..faaeedd3f 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -405,7 +405,7 @@ fn uu_head(options: &HeadOptions) { for fname in &options.files { let res = match fname.as_str() { "-" => { - if options.verbose { + if (options.files.len() > 1 && !options.quiet) || options.verbose { if !first { println!(); } @@ -459,6 +459,9 @@ fn uu_head(options: &HeadOptions) { }, }; if (options.files.len() > 1 && !options.quiet) || options.verbose { + if !first { + println!(); + } println!("==> {} <==", name) } head_file(&mut file, options) diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 4f009c800..2aedbdcbe 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -196,3 +196,28 @@ fn test_obsolete_extras() { .succeeds() .stdout_is("==> standard input <==\n1\02\03\04\05\0"); } + +#[test] +fn test_multiple_files() { + new_ucmd!() + .args(&["emptyfile.txt", "emptyfile.txt"]) + .succeeds() + .stdout_is("==> emptyfile.txt <==\n\n==> emptyfile.txt <==\n"); +} + +#[test] +fn test_multiple_files_with_stdin() { + new_ucmd!() + .args(&["emptyfile.txt", "-", "emptyfile.txt"]) + .pipe_in("hello\n") + .succeeds() + .stdout_is( + "==> emptyfile.txt <== + +==> standard input <== +hello + +==> emptyfile.txt <== +", + ); +} diff --git a/tests/fixtures/head/emptyfile.txt b/tests/fixtures/head/emptyfile.txt new file mode 100644 index 000000000..e69de29bb From fcd48813e01b279604f286e87f7dcbacafe9759b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 16 May 2021 21:13:37 +0200 Subject: [PATCH 296/399] sort: read files as chunks, off-thread Instead of using a BufReader and reading each line separately, allocating a String for each one, we read to a chunk. Lines are references to this chunk. This makes the allocator's job much easier and yields performance improvements. Chunks are read on a separate thread to further improve performance. --- Cargo.lock | 141 +++++++- src/uu/sort/BENCHMARKING.md | 15 +- src/uu/sort/Cargo.toml | 10 +- src/uu/sort/src/check.rs | 102 ++++++ src/uu/sort/src/chunks.rs | 202 +++++++++++ src/uu/sort/src/ext_sort.rs | 160 +++++++++ src/uu/sort/src/external_sort/LICENSE | 19 - src/uu/sort/src/external_sort/mod.rs | 93 ----- src/uu/sort/src/merge.rs | 223 ++++++++++++ src/uu/sort/src/sort.rs | 486 +++++++++----------------- tests/by-util/test_sort.rs | 4 +- 11 files changed, 1003 insertions(+), 452 deletions(-) create mode 100644 src/uu/sort/src/check.rs create mode 100644 src/uu/sort/src/chunks.rs create mode 100644 src/uu/sort/src/ext_sort.rs delete mode 100644 src/uu/sort/src/external_sort/LICENSE delete mode 100644 src/uu/sort/src/external_sort/mod.rs create mode 100644 src/uu/sort/src/merge.rs diff --git a/Cargo.lock b/Cargo.lock index 77957de80..feda68de5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + [[package]] name = "advapi32-sys" version = "0.2.0" @@ -63,6 +69,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "binary-heap-plus" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f068638f8ff9e118a9361e66a411eff410e7fb3ecaa23bf9272324f8fc606d7" +dependencies = [ + "compare", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -136,9 +151,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75" +checksum = "57cdfa5d50aad6cb4d44dcab6101a7f79925bd59d82ca42f38a9856a28865374" dependencies = [ "rustc_version", ] @@ -198,6 +213,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "compare" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -999,6 +1020,29 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "ouroboros" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f52300b81ac4eeeb6c00c20f7e86556c427d9fb2d92b68fc73c22f331cd15" +dependencies = [ + "ouroboros_macro", + "stable_deref_trait", +] + +[[package]] +name = "ouroboros_macro" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41db02c8f8731cdd7a72b433c7900cce4bf245465b452c364bfd21f4566ab055" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote 1.0.9", + "syn", +] + [[package]] name = "output_vt100" version = "0.1.2" @@ -1027,6 +1071,15 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "pkg-config" version = "0.3.19" @@ -1089,6 +1142,30 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote 1.0.9", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote 1.0.9", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1336,11 +1413,11 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rustc_version" -version = "0.2.3" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ - "semver", + "semver 0.11.0", ] [[package]] @@ -1370,7 +1447,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -1380,10 +1466,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] -name = "serde" -version = "1.0.125" +name = "semver-parser" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" [[package]] name = "serde_cbor" @@ -1397,9 +1492,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -1468,6 +1563,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.8.0" @@ -1627,6 +1728,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -2402,12 +2509,16 @@ dependencies = [ name = "uu_sort" version = "0.0.6" dependencies = [ + "binary-heap-plus", "clap", + "compare", "fnv", "itertools 0.10.0", + "memchr 2.4.0", + "ouroboros", "rand 0.7.3", "rayon", - "semver", + "semver 0.9.0", "tempdir", "unicode-width", "uucore", @@ -2720,6 +2831,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "void" version = "1.0.2" diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 71c331105..52866719d 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -75,7 +75,20 @@ Try running commands with the `-S` option set to an amount of memory to be used, huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). Example: Run `hyperfine './target/release/coreutils sort shuffled_wordlist.txt -S 1M' 'sort shuffled_wordlist.txt -S 1M'` -` + +## Merging + +"Merge" sort merges already sorted files. It is a sub-step of external sorting, so benchmarking it separately may be helpful. + +- Splitting `shuffled_wordlist.txt` can be achieved by running `split shuffled_wordlist.txt shuffled_wordlist_slice_ --additional-suffix=.txt` +- Sort each part by running `for f in shuffled_wordlist_slice_*; do sort $f -o $f; done` +- Benchmark merging by running `hyperfine "target/release/coreutils sort -m shuffled_wordlist_slice_*"` + +## Check + +When invoked with -c, we simply check if the input is already ordered. The input for benchmarking should be an already sorted file. + +- Benchmark checking by running `hyperfine "target/release/coreutils sort -c sorted_wordlist.txt"` ## Stdout and stdin performance diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 5221f1f4e..724744dc4 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -15,16 +15,20 @@ edition = "2018" path = "src/sort.rs" [dependencies] -rayon = "1.5" -rand = "0.7" +binary-heap-plus = "0.4.1" clap = "2.33" +compare = "0.1.0" fnv = "1.0.7" itertools = "0.10.0" +memchr = "2.4.0" +ouroboros = "0.9.3" +rand = "0.7" +rayon = "1.5" semver = "0.9.0" +tempdir = "0.3.7" unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } -tempdir = "0.3.7" [[bin]] name = "sort" diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs new file mode 100644 index 000000000..fe815b624 --- /dev/null +++ b/src/uu/sort/src/check.rs @@ -0,0 +1,102 @@ +// * This file is part of the uutils coreutils package. +// * +// * (c) Michael Debertol +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +//! Check if a file is ordered + +use crate::{ + chunks::{self, Chunk}, + compare_by, open, GlobalSettings, +}; +use itertools::Itertools; +use std::{ + cmp::Ordering, + io::Read, + iter, + sync::mpsc::{sync_channel, Receiver, SyncSender}, + thread, +}; + +/// Check if the file at `path` is ordered. +/// +/// # Returns +/// +/// The code we should exit with. +pub fn check(path: &str, settings: &GlobalSettings) -> i32 { + let file = open(path).expect("failed to open input file"); + let (recycled_sender, recycled_receiver) = sync_channel(2); + let (loaded_sender, loaded_receiver) = sync_channel(2); + thread::spawn({ + let settings = settings.clone(); + move || reader(file, recycled_receiver, loaded_sender, &settings) + }); + for _ in 0..2 { + recycled_sender + .send(Chunk::new(vec![0; 100 * 1024], |_| Vec::new())) + .unwrap(); + } + + let mut prev_chunk: Option = None; + let mut line_idx = 0; + for chunk in loaded_receiver.iter() { + line_idx += 1; + if let Some(prev_chunk) = prev_chunk.take() { + // Check if the first element of the new chunk is greater than the last + // element from the previous chunk + let prev_last = prev_chunk.borrow_lines().last().unwrap(); + let new_first = chunk.borrow_lines().first().unwrap(); + + if compare_by(prev_last, new_first, &settings) == Ordering::Greater { + if !settings.check_silent { + println!("sort: {}:{}: disorder: {}", path, line_idx, new_first.line); + } + return 1; + } + recycled_sender.send(prev_chunk).ok(); + } + + for (a, b) in chunk.borrow_lines().iter().tuple_windows() { + line_idx += 1; + if compare_by(a, b, &settings) == Ordering::Greater { + if !settings.check_silent { + println!("sort: {}:{}: disorder: {}", path, line_idx, b.line); + } + return 1; + } + } + + prev_chunk = Some(chunk); + } + 0 +} + +/// The function running on the reader thread. +fn reader( + mut file: Box, + receiver: Receiver, + sender: SyncSender, + settings: &GlobalSettings, +) { + let mut sender = Some(sender); + let mut carry_over = vec![]; + for chunk in receiver.iter() { + let (recycled_lines, recycled_buffer) = chunk.recycle(); + chunks::read( + &mut sender, + recycled_buffer, + &mut carry_over, + &mut file, + &mut iter::empty(), + if settings.zero_terminated { + b'\0' + } else { + b'\n' + }, + recycled_lines, + settings, + ) + } +} diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs new file mode 100644 index 000000000..c679980ec --- /dev/null +++ b/src/uu/sort/src/chunks.rs @@ -0,0 +1,202 @@ +// * This file is part of the uutils coreutils package. +// * +// * (c) Michael Debertol +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +//! Utilities for reading files as chunks. + +use std::{ + io::{ErrorKind, Read}, + sync::mpsc::SyncSender, +}; + +use memchr::memchr_iter; +use ouroboros::self_referencing; + +use crate::{GlobalSettings, Line}; + +/// The chunk that is passed around between threads. +/// `lines` consist of slices into `buffer`. +#[self_referencing(pub_extras)] +#[derive(Debug)] +pub struct Chunk { + pub buffer: Vec, + #[borrows(buffer)] + #[covariant] + pub lines: Vec>, +} + +impl Chunk { + /// Destroy this chunk and return its components to be reused. + /// + /// # Returns + /// + /// * The `lines` vector, emptied + /// * The `buffer` vector, **not** emptied + pub fn recycle(mut self) -> (Vec>, Vec) { + let recycled_lines = self.with_lines_mut(|lines| { + lines.clear(); + unsafe { + // SAFETY: It is safe to (temporarily) transmute to a vector of lines with a longer lifetime, + // because the vector is empty. + // Transmuting is necessary to make recycling possible. See https://github.com/rust-lang/rfcs/pull/2802 + // for a rfc to make this unnecessary. Its example is similar to the code here. + std::mem::transmute::>, Vec>>(std::mem::take(lines)) + } + }); + (recycled_lines, self.into_heads().buffer) + } +} + +/// Read a chunk, parse lines and send them. +/// +/// No empty chunk will be sent. +/// +/// # Arguments +/// +/// * `sender_option`: The sender to send the lines to the sorter. If `None`, does nothing. +/// * `buffer`: The recycled buffer. All contents will be overwritten, but it must already be filled. +/// (i.e. `buffer.len()` should be equal to `buffer.capacity()`) +/// * `carry_over`: The bytes that must be carried over in between invocations. +/// * `file`: The current file. +/// * `next_files`: What `file` should be updated to next. +/// * `separator`: The line separator. +/// * `lines`: The recycled vector to fill with lines. Must be empty. +/// * `settings`: The global settings. +#[allow(clippy::too_many_arguments)] +pub fn read( + sender_option: &mut Option>, + mut buffer: Vec, + carry_over: &mut Vec, + file: &mut Box, + next_files: &mut impl Iterator>, + separator: u8, + lines: Vec>, + settings: &GlobalSettings, +) { + assert!(lines.is_empty()); + if let Some(sender) = sender_option { + if buffer.len() < carry_over.len() { + buffer.resize(carry_over.len() + 10 * 1024, 0); + } + buffer[..carry_over.len()].copy_from_slice(&carry_over); + let (read, should_continue) = + read_to_buffer(file, next_files, &mut buffer, carry_over.len(), separator); + carry_over.clear(); + carry_over.extend_from_slice(&buffer[read..]); + + let payload = Chunk::new(buffer, |buf| { + let mut lines = unsafe { + // SAFETY: It is safe to transmute to a vector of lines with shorter lifetime, + // because it was only temporarily transmuted to a Vec> to make recycling possible. + std::mem::transmute::>, Vec>>(lines) + }; + let read = crash_if_err!(1, std::str::from_utf8(&buf[..read])); + parse_lines(read, &mut lines, separator, &settings); + lines + }); + if !payload.borrow_lines().is_empty() { + sender.send(payload).unwrap(); + } + if !should_continue { + *sender_option = None; + } + } +} + +/// Split `read` into `Line`s, and add them to `lines`. +fn parse_lines<'a>( + mut read: &'a str, + lines: &mut Vec>, + separator: u8, + settings: &GlobalSettings, +) { + // Strip a trailing separator. TODO: Once our MinRustV is 1.45 or above, use strip_suffix() instead. + if read.ends_with(separator as char) { + read = &read[..read.len() - 1]; + } + + lines.extend( + read.split(separator as char) + .map(|line| Line::create(line, settings)), + ); +} + +/// Read from `file` into `buffer`. +/// +/// This function makes sure that at least two lines are read (unless we reach EOF and there's no next file), +/// growing the buffer if necessary. +/// The last line is likely to not have been fully read into the buffer. Its bytes must be copied to +/// the front of the buffer for the next invocation so that it can be continued to be read +/// (see the return values and `start_offset`). +/// +/// # Arguments +/// +/// * `file`: The file to start reading from. +/// * `next_files`: When `file` reaches EOF, it is updated to `next_files.next()` if that is `Some`, +/// and this function continues reading. +/// * `buffer`: The buffer that is filled with bytes. Its contents will mostly be overwritten (see `start_offset` +/// as well). It will not be grown by default, unless that is necessary to read at least two lines. +/// * `start_offset`: The amount of bytes at the start of `buffer` that were carried over +/// from the previous read and should not be overwritten. +/// * `separator`: The byte that separates lines. +/// +/// # Returns +/// +/// * The amount of bytes in `buffer` that can now be interpreted as lines. +/// The remaining bytes must be copied to the start of the buffer for the next invocation, +/// if another invocation is necessary, which is determined by the other return value. +/// * Whether this function should be called again. +fn read_to_buffer( + file: &mut Box, + next_files: &mut impl Iterator>, + buffer: &mut Vec, + start_offset: usize, + separator: u8, +) -> (usize, bool) { + let mut read_target = &mut buffer[start_offset..]; + loop { + match file.read(read_target) { + Ok(0) => { + if read_target.is_empty() { + // chunk is full + let mut sep_iter = memchr_iter(separator, &buffer).rev(); + let last_line_end = sep_iter.next(); + if sep_iter.next().is_some() { + // We read enough lines. + let end = last_line_end.unwrap(); + // We want to include the separator here, because it shouldn't be carried over. + return (end + 1, true); + } else { + // We need to read more lines + let len = buffer.len(); + // resize the vector to 10 KB more + buffer.resize(len + 1024 * 10, 0); + read_target = &mut buffer[len..]; + } + } else { + // This file is empty. + if let Some(next_file) = next_files.next() { + // There is another file. + *file = next_file; + } else { + // This was the last file. + let leftover_len = read_target.len(); + return (buffer.len() - leftover_len, false); + } + } + } + Ok(n) => { + read_target = &mut read_target[n..]; + } + Err(e) if e.kind() == ErrorKind::Interrupted => { + // retry + } + Err(e) => { + crash!(1, "{}", e) + } + } + } +} diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs new file mode 100644 index 000000000..629ebb714 --- /dev/null +++ b/src/uu/sort/src/ext_sort.rs @@ -0,0 +1,160 @@ +// * This file is part of the uutils coreutils package. +// * +// * (c) Michael Debertol +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +//! Sort big files by using files for storing intermediate chunks. +//! +//! Files are read into chunks of memory which are then sorted individually and +//! written to temporary files. There are two threads: One sorter, and one reader/writer. +//! The buffers for the individual chunks are recycled. There are two buffers. + +use std::io::{BufWriter, Write}; +use std::path::Path; +use std::{ + fs::OpenOptions, + io::Read, + sync::mpsc::{Receiver, SyncSender}, + thread, +}; + +use tempdir::TempDir; + +use crate::{ + chunks::{self, Chunk}, + merge::{self, FileMerger}, + sort_by, GlobalSettings, +}; + +/// Iterator that wraps the +pub struct ExtSortedMerger<'a> { + pub file_merger: FileMerger<'a>, + // Keep _tmp_dir around, as it is deleted when dropped. + _tmp_dir: TempDir, +} + +/// Sort big files by using files for storing intermediate chunks. +/// +/// # Returns +/// +/// An iterator that merges intermediate files back together. +pub fn ext_sort<'a>( + files: &mut impl Iterator>, + settings: &'a GlobalSettings, +) -> ExtSortedMerger<'a> { + let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); + let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1); + let (recycled_sender, recycled_receiver) = std::sync::mpsc::sync_channel(1); + thread::spawn({ + let settings = settings.clone(); + move || sorter(recycled_receiver, sorted_sender, settings) + }); + let chunks_read = reader_writer( + files, + &tmp_dir, + if settings.zero_terminated { + b'\0' + } else { + b'\n' + }, + // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly + // around settings.buffer_size as a whole. + settings.buffer_size / 10, + settings.clone(), + sorted_receiver, + recycled_sender, + ); + let files = (0..chunks_read) + .map(|chunk_num| tmp_dir.path().join(chunk_num.to_string())) + .collect::>(); + + ExtSortedMerger { + file_merger: merge::merge(&files, settings), + _tmp_dir: tmp_dir, + } +} + +/// The function that is executed on the sorter thread. +fn sorter(receiver: Receiver, sender: SyncSender, settings: GlobalSettings) { + while let Ok(mut payload) = receiver.recv() { + payload.with_lines_mut(|lines| sort_by(lines, &settings)); + sender.send(payload).unwrap(); + } +} + +/// The function that is executed on the reader/writer thread. +/// +/// # Returns +/// * The number of chunks read. +fn reader_writer( + mut files: impl Iterator>, + tmp_dir: &TempDir, + separator: u8, + buffer_size: usize, + settings: GlobalSettings, + receiver: Receiver, + sender: SyncSender, +) -> usize { + let mut sender_option = Some(sender); + + let mut file = files.next().unwrap(); + + let mut carry_over = vec![]; + // kick things off with two reads + for _ in 0..2 { + chunks::read( + &mut sender_option, + vec![0; buffer_size], + &mut carry_over, + &mut file, + &mut files, + separator, + Vec::new(), + &settings, + ) + } + + let mut file_number = 0; + loop { + let mut chunk = match receiver.recv() { + Ok(it) => it, + _ => return file_number, + }; + + write( + &mut chunk, + &tmp_dir.path().join(file_number.to_string()), + separator, + ); + + let (recycled_lines, recycled_buffer) = chunk.recycle(); + + file_number += 1; + + chunks::read( + &mut sender_option, + recycled_buffer, + &mut carry_over, + &mut file, + &mut files, + separator, + recycled_lines, + &settings, + ); + } +} + +/// Write the lines in `chunk` to `file`, separated by `separator`. +fn write(chunk: &mut Chunk, file: &Path, separator: u8) { + chunk.with_lines_mut(|lines| { + // Write the lines to the file + let file = crash_if_err!(1, OpenOptions::new().create(true).write(true).open(file)); + let mut writer = BufWriter::new(file); + for s in lines.iter() { + crash_if_err!(1, writer.write_all(s.line.as_bytes())); + crash_if_err!(1, writer.write_all(&[separator])); + } + }); +} diff --git a/src/uu/sort/src/external_sort/LICENSE b/src/uu/sort/src/external_sort/LICENSE deleted file mode 100644 index e26c89c9f..000000000 --- a/src/uu/sort/src/external_sort/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2018 Battelle Memorial Institute - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/src/uu/sort/src/external_sort/mod.rs b/src/uu/sort/src/external_sort/mod.rs deleted file mode 100644 index af6902367..000000000 --- a/src/uu/sort/src/external_sort/mod.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::fs::OpenOptions; -use std::io::{BufWriter, Write}; -use std::path::Path; - -use tempdir::TempDir; - -use crate::{file_to_lines_iter, FileMerger}; - -use super::{GlobalSettings, Line}; - -/// Iterator that provides sorted `T`s -pub struct ExtSortedIterator<'a> { - file_merger: FileMerger<'a>, - // Keep tmp_dir around, it is deleted when dropped. - _tmp_dir: TempDir, -} - -impl<'a> Iterator for ExtSortedIterator<'a> { - type Item = Line; - fn next(&mut self) -> Option { - self.file_merger.next() - } -} - -/// Sort (based on `compare`) the `T`s provided by `unsorted` and return an -/// iterator -/// -/// # Panics -/// -/// This method can panic due to issues writing intermediate sorted chunks -/// to disk. -pub fn ext_sort( - unsorted: impl Iterator, - settings: &GlobalSettings, -) -> ExtSortedIterator { - let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); - - let mut total_read = 0; - let mut chunk = Vec::new(); - - let mut chunks_read = 0; - let mut file_merger = FileMerger::new(settings); - - // make the initial chunks on disk - for seq in unsorted { - let seq_size = seq.estimate_size(); - total_read += seq_size; - - chunk.push(seq); - - if total_read >= settings.buffer_size && chunk.len() >= 2 { - super::sort_by(&mut chunk, &settings); - - let file_path = tmp_dir.path().join(chunks_read.to_string()); - write_chunk(settings, &file_path, &mut chunk); - chunk.clear(); - total_read = 0; - chunks_read += 1; - - file_merger.push_file(Box::new(file_to_lines_iter(file_path, settings).unwrap())) - } - } - // write the last chunk - if !chunk.is_empty() { - super::sort_by(&mut chunk, &settings); - - let file_path = tmp_dir.path().join(chunks_read.to_string()); - write_chunk( - settings, - &tmp_dir.path().join(chunks_read.to_string()), - &mut chunk, - ); - - file_merger.push_file(Box::new(file_to_lines_iter(file_path, settings).unwrap())); - } - ExtSortedIterator { - file_merger, - _tmp_dir: tmp_dir, - } -} - -fn write_chunk(settings: &GlobalSettings, file: &Path, chunk: &mut Vec) { - let new_file = crash_if_err!(1, OpenOptions::new().create(true).append(true).open(file)); - let mut buf_write = BufWriter::new(new_file); - for s in chunk { - crash_if_err!(1, buf_write.write_all(s.line.as_bytes())); - crash_if_err!( - 1, - buf_write.write_all(if settings.zero_terminated { "\0" } else { "\n" }.as_bytes(),) - ); - } - crash_if_err!(1, buf_write.flush()); -} diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs new file mode 100644 index 000000000..6f7cdfed7 --- /dev/null +++ b/src/uu/sort/src/merge.rs @@ -0,0 +1,223 @@ +//! Merge already sorted files. +//! +//! We achieve performance by splitting the tasks of sorting and writing, and reading and parsing between two threads. +//! The threads communicate over channels. There's one channel per file in the direction reader -> sorter, but only +//! one channel from the sorter back to the reader. The channels to the sorter are used to send the read chunks. +//! The sorter reads the next chunk from the channel whenever it needs the next chunk after running out of lines +//! from the previous read of the file. The channel back from the sorter to the reader has two purposes: To allow the reader +//! to reuse memory allocations and to tell the reader which file to read from next. + +use std::{ + cmp::Ordering, + ffi::OsStr, + io::{Read, Write}, + iter, + rc::Rc, + sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}, + thread, +}; + +use compare::Compare; + +use crate::{ + chunks::{self, Chunk}, + compare_by, open, GlobalSettings, +}; + +// Merge already sorted files. +pub fn merge<'a>(files: &[impl AsRef], settings: &'a GlobalSettings) -> FileMerger<'a> { + let (request_sender, request_receiver) = channel(); + let mut reader_files = Vec::with_capacity(files.len()); + let mut loaded_receivers = Vec::with_capacity(files.len()); + for (file_number, file) in files.iter().filter_map(open).enumerate() { + let (sender, receiver) = sync_channel(2); + loaded_receivers.push(receiver); + reader_files.push(ReaderFile { + file, + sender: Some(sender), + carry_over: vec![], + }); + request_sender + .send((file_number, Chunk::new(vec![0; 8 * 1024], |_| Vec::new()))) + .unwrap(); + } + + for file_number in 0..reader_files.len() { + request_sender + .send((file_number, Chunk::new(vec![0; 8 * 1024], |_| Vec::new()))) + .unwrap(); + } + + thread::spawn({ + let settings = settings.clone(); + move || { + reader( + request_receiver, + &mut reader_files, + &settings, + if settings.zero_terminated { + b'\0' + } else { + b'\n' + }, + ) + } + }); + + let mut mergeable_files = vec![]; + + for (file_number, receiver) in loaded_receivers.into_iter().enumerate() { + mergeable_files.push(MergeableFile { + current_chunk: Rc::new(receiver.recv().unwrap()), + file_number, + line_idx: 0, + receiver, + }) + } + + FileMerger { + heap: binary_heap_plus::BinaryHeap::from_vec_cmp( + mergeable_files, + FileComparator { settings }, + ), + request_sender, + prev: None, + } +} +/// The struct on the reader thread representing an input file +struct ReaderFile { + file: Box, + sender: Option>, + carry_over: Vec, +} + +/// The function running on the reader thread. +fn reader( + recycled_receiver: Receiver<(usize, Chunk)>, + files: &mut [ReaderFile], + settings: &GlobalSettings, + separator: u8, +) { + for (file_idx, chunk) in recycled_receiver.iter() { + let (recycled_lines, recycled_buffer) = chunk.recycle(); + let ReaderFile { + file, + sender, + carry_over, + } = &mut files[file_idx]; + chunks::read( + sender, + recycled_buffer, + carry_over, + file, + &mut iter::empty(), + separator, + recycled_lines, + settings, + ); + } +} +/// The struct on the main thread representing an input file +pub struct MergeableFile { + current_chunk: Rc, + line_idx: usize, + receiver: Receiver, + file_number: usize, +} + +/// A struct to keep track of the previous line we encountered. +/// +/// This is required for deduplication purposes. +struct PreviousLine { + chunk: Rc, + line_idx: usize, + file_number: usize, +} + +/// Merges files together. This is **not** an iterator because of lifetime problems. +pub struct FileMerger<'a> { + heap: binary_heap_plus::BinaryHeap>, + request_sender: Sender<(usize, Chunk)>, + prev: Option, +} + +impl<'a> FileMerger<'a> { + /// Write the merged contents to the output file. + pub fn write_all(&mut self, settings: &GlobalSettings) { + let mut out = settings.out_writer(); + while self.write_next(settings, &mut out) {} + } + + fn write_next(&mut self, settings: &GlobalSettings, out: &mut impl Write) -> bool { + if let Some(file) = self.heap.peek() { + let prev = self.prev.replace(PreviousLine { + chunk: file.current_chunk.clone(), + line_idx: file.line_idx, + file_number: file.file_number, + }); + + file.current_chunk.with_lines(|lines| { + let current_line = &lines[file.line_idx]; + if settings.unique { + if let Some(prev) = &prev { + let cmp = compare_by( + &prev.chunk.borrow_lines()[prev.line_idx], + current_line, + settings, + ); + if cmp == Ordering::Equal { + return; + } + } + } + current_line.print(out, settings); + }); + + let was_last_line_for_file = + file.current_chunk.borrow_lines().len() == file.line_idx + 1; + + if was_last_line_for_file { + if let Ok(next_chunk) = file.receiver.recv() { + let mut file = self.heap.peek_mut().unwrap(); + file.current_chunk = Rc::new(next_chunk); + file.line_idx = 0; + } else { + self.heap.pop(); + } + } else { + self.heap.peek_mut().unwrap().line_idx += 1; + } + + if let Some(prev) = prev { + if let Ok(prev_chunk) = Rc::try_unwrap(prev.chunk) { + self.request_sender + .send((prev.file_number, prev_chunk)) + .ok(); + } + } + } + !self.heap.is_empty() + } +} + +/// Compares files by their current line. +struct FileComparator<'a> { + settings: &'a GlobalSettings, +} + +impl<'a> Compare for FileComparator<'a> { + fn compare(&self, a: &MergeableFile, b: &MergeableFile) -> Ordering { + let mut cmp = compare_by( + &a.current_chunk.borrow_lines()[a.line_idx], + &b.current_chunk.borrow_lines()[b.line_idx], + self.settings, + ); + if cmp == Ordering::Equal { + // To make sorting stable, we need to consider the file number as well, + // as lines from a file with a lower number are to be considered "earlier". + cmp = a.file_number.cmp(&b.file_number); + } + // Our BinaryHeap is a max heap. We use it as a min heap, so we need to reverse the ordering. + cmp.reverse() + } +} diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 2697d7df4..b6ab5a2b1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -15,13 +15,16 @@ #[macro_use] extern crate uucore; +mod check; +mod chunks; mod custom_str_cmp; -mod external_sort; +mod ext_sort; +mod merge; mod numeric_str_cmp; use clap::{App, Arg}; use custom_str_cmp::custom_str_cmp; -use external_sort::ext_sort; +use ext_sort::ext_sort; use fnv::FnvHasher; use itertools::Itertools; use numeric_str_cmp::{numeric_str_cmp, NumInfo, NumInfoParseSettings}; @@ -30,18 +33,15 @@ use rand::{thread_rng, Rng}; use rayon::prelude::*; use semver::Version; use std::cmp::Ordering; -use std::collections::BinaryHeap; use std::env; use std::ffi::OsStr; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; -use std::mem::replace; use std::ops::Range; use std::path::Path; use std::path::PathBuf; use unicode_width::UnicodeWidthStr; -use uucore::fs::is_stdin_interactive; // for Iterator::dedup() use uucore::InvalidEncodingHandling; static NAME: &str = "sort"; @@ -150,6 +150,19 @@ impl GlobalSettings { }; num_usize * suf_usize } + + fn out_writer(&self) -> BufWriter> { + match self.outfile { + Some(ref filename) => match File::create(Path::new(&filename)) { + Ok(f) => BufWriter::new(Box::new(f) as Box), + Err(e) => { + show_error!("{0}: {1}", filename, e.to_string()); + panic!("Could not open output file"); + } + }, + None => BufWriter::new(Box::new(stdout()) as Box), + } + } } impl Default for GlobalSettings { @@ -205,29 +218,7 @@ impl From<&GlobalSettings> for KeySettings { } } -#[derive(Debug, Clone)] -/// Represents the string selected by a FieldSelector. -struct SelectionRange { - range: Range, -} - -impl SelectionRange { - fn new(range: Range) -> Self { - Self { range } - } - - /// Gets the actual string slice represented by this Selection. - fn get_str<'a>(&self, line: &'a str) -> &'a str { - &line[self.range.to_owned()] - } - - fn shorten(&mut self, new_range: Range) { - self.range.end = self.range.start + new_range.end; - self.range.start += new_range.start; - } -} - -#[derive(Clone)] +#[derive(Clone, Debug)] enum NumCache { AsF64(GeneralF64ParseResult), WithInfo(NumInfo), @@ -248,64 +239,53 @@ impl NumCache { } } -#[derive(Clone)] -struct Selection { - range: SelectionRange, +#[derive(Clone, Debug)] +struct Selection<'a> { + slice: &'a str, num_cache: Option>, } -impl Selection { - /// Gets the actual string slice represented by this Selection. - fn get_str<'a>(&'a self, line: &'a Line) -> &'a str { - self.range.get_str(&line.line) - } -} - type Field = Range; -#[derive(Clone)] -pub struct Line { - line: Box, - // The common case is not to specify fields. Let's make this fast. - first_selection: Selection, - other_selections: Box<[Selection]>, +#[derive(Clone, Debug)] +pub struct Line<'a> { + line: &'a str, + selections: Box<[Selection<'a>]>, } -impl Line { - /// Estimate the number of bytes that this Line is occupying - pub fn estimate_size(&self) -> usize { - self.line.len() - + self.other_selections.len() * std::mem::size_of::() - + std::mem::size_of::() - } - - pub fn new(line: String, settings: &GlobalSettings) -> Self { +impl<'a> Line<'a> { + fn create(string: &'a str, settings: &GlobalSettings) -> Self { let fields = if settings .selectors .iter() - .any(|selector| selector.needs_tokens()) + .any(|selector| selector.needs_tokens) { // Only tokenize if we will need tokens. - Some(tokenize(&line, settings.separator)) + Some(tokenize(string, settings.separator)) } else { None }; - let mut selectors = settings.selectors.iter(); + Line { + line: string, + selections: settings + .selectors + .iter() + .filter(|selector| !selector.is_default_selection) + .map(|selector| selector.get_selection(string, fields.as_deref())) + .collect(), + } + } - let first_selection = selectors - .next() - .unwrap() - .get_selection(&line, fields.as_deref()); - - let other_selections: Vec = selectors - .map(|selector| selector.get_selection(&line, fields.as_deref())) - .collect(); - - Self { - line: line.into_boxed_str(), - first_selection, - other_selections: other_selections.into_boxed_slice(), + fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { + if settings.zero_terminated && !settings.debug { + crash_if_err!(1, writer.write_all(self.line.as_bytes())); + crash_if_err!(1, writer.write_all("\0".as_bytes())); + } else if !settings.debug { + crash_if_err!(1, writer.write_all(self.line.as_bytes())); + crash_if_err!(1, writer.write_all("\n".as_bytes())); + } else { + crash_if_err!(1, self.print_debug(settings, writer)); } } @@ -314,7 +294,7 @@ impl Line { fn print_debug( &self, settings: &GlobalSettings, - writer: &mut dyn Write, + writer: &mut impl Write, ) -> std::io::Result<()> { // We do not consider this function performance critical, as debug output is only useful for small files, // which are not a performance problem in any case. Therefore there aren't any special performance @@ -575,23 +555,39 @@ struct FieldSelector { from: KeyPosition, to: Option, settings: KeySettings, + needs_tokens: bool, + // Whether the selection for each line is going to be the whole line with no NumCache + is_default_selection: bool, } impl FieldSelector { - fn needs_tokens(&self) -> bool { - self.from.field != 1 || self.from.char == 0 || self.to.is_some() + fn new(from: KeyPosition, to: Option, settings: KeySettings) -> Self { + Self { + is_default_selection: from.field == 1 + && from.char == 1 + && to.is_none() + // TODO: Once our MinRustV is 1.42 or higher, change this to the matches! macro + && match settings.mode { + SortMode::Numeric | SortMode::GeneralNumeric | SortMode::HumanNumeric => false, + _ => true, + }, + needs_tokens: from.field != 1 || from.char == 0 || to.is_some(), + from, + to, + settings, + } } /// Get the selection that corresponds to this selector for the line. /// If needs_fields returned false, tokens may be None. - fn get_selection(&self, line: &str, tokens: Option<&[Field]>) -> Selection { - let mut range = SelectionRange::new(self.get_range(&line, tokens)); + fn get_selection<'a>(&self, line: &'a str, tokens: Option<&[Field]>) -> Selection<'a> { + let mut range = &line[self.get_range(&line, tokens)]; let num_cache = if self.settings.mode == SortMode::Numeric || self.settings.mode == SortMode::HumanNumeric { // Parse NumInfo for this number. let (info, num_range) = NumInfo::parse( - range.get_str(&line), + range, NumInfoParseSettings { accept_si_units: self.settings.mode == SortMode::HumanNumeric, thousands_separator: Some(THOUSANDS_SEP), @@ -599,19 +595,21 @@ impl FieldSelector { }, ); // Shorten the range to what we need to pass to numeric_str_cmp later. - range.shorten(num_range); + range = &range[num_range]; Some(Box::new(NumCache::WithInfo(info))) } else if self.settings.mode == SortMode::GeneralNumeric { // Parse this number as f64, as this is the requirement for general numeric sorting. - let str = range.get_str(&line); Some(Box::new(NumCache::AsF64(general_f64_parse( - &str[get_leading_gen(str)], + &range[get_leading_gen(range)], )))) } else { // This is not a numeric sort, so we don't need a NumCache. None }; - Selection { range, num_cache } + Selection { + slice: range, + num_cache, + } } /// Look up the range in the line that corresponds to this selector. @@ -701,91 +699,6 @@ impl FieldSelector { } } -struct MergeableFile<'a> { - lines: Box + 'a>, - current_line: Line, - settings: &'a GlobalSettings, - file_index: usize, -} - -// BinaryHeap depends on `Ord`. Note that we want to pop smallest items -// from the heap first, and BinaryHeap.pop() returns the largest, so we -// trick it into the right order by calling reverse() here. -impl<'a> Ord for MergeableFile<'a> { - fn cmp(&self, other: &MergeableFile) -> Ordering { - let comparison = compare_by(&self.current_line, &other.current_line, self.settings); - if comparison == Ordering::Equal { - // If lines are equal, the earlier file takes precedence. - self.file_index.cmp(&other.file_index) - } else { - comparison - } - .reverse() - } -} - -impl<'a> PartialOrd for MergeableFile<'a> { - fn partial_cmp(&self, other: &MergeableFile) -> Option { - Some(self.cmp(other)) - } -} - -impl<'a> PartialEq for MergeableFile<'a> { - fn eq(&self, other: &MergeableFile) -> bool { - Ordering::Equal == self.cmp(other) - } -} - -impl<'a> Eq for MergeableFile<'a> {} - -struct FileMerger<'a> { - heap: BinaryHeap>, - settings: &'a GlobalSettings, -} - -impl<'a> FileMerger<'a> { - fn new(settings: &'a GlobalSettings) -> FileMerger<'a> { - FileMerger { - heap: BinaryHeap::new(), - settings, - } - } - fn push_file(&mut self, mut lines: Box + 'a>) { - if let Some(next_line) = lines.next() { - let mergeable_file = MergeableFile { - lines, - current_line: next_line, - settings: &self.settings, - file_index: self.heap.len(), - }; - self.heap.push(mergeable_file); - } - } -} - -impl<'a> Iterator for FileMerger<'a> { - type Item = Line; - fn next(&mut self) -> Option { - match self.heap.pop() { - Some(mut current) => { - match current.lines.next() { - Some(next_line) => { - let ret = replace(&mut current.current_line, next_line); - self.heap.push(current); - Some(ret) - } - _ => { - // Don't put it back in the heap (it's empty/erroring) - // but its first line is still valid. - Some(current.current_line) - } - } - } - None => None, - } - } -} - fn get_usage() -> String { format!( "{0} {1} @@ -985,7 +898,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut files = Vec::new(); for path in &files0_from { - let (reader, _) = open(path.as_str()).expect("Could not read from file specified."); + let reader = open(path.as_str()).expect("Could not read from file specified."); let buf_reader = BufReader::new(reader); for line in buf_reader.split(b'\0').flatten() { files.push( @@ -1112,11 +1025,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let to = from_to .next() .map(|to| KeyPosition::parse(to, 0, &mut key_settings)); - let field_selector = FieldSelector { - from, - to, - settings: key_settings, - }; + let field_selector = FieldSelector::new(from, to, key_settings); settings.selectors.push(field_selector); } } @@ -1124,48 +1033,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !settings.stable || !matches.is_present(OPT_KEY) { // add a default selector matching the whole line let key_settings = KeySettings::from(&settings); - settings.selectors.push(FieldSelector { - from: KeyPosition { + settings.selectors.push(FieldSelector::new( + KeyPosition { field: 1, char: 1, ignore_blanks: key_settings.ignore_blanks, }, - to: None, - settings: key_settings, - }); + None, + key_settings, + )); } - exec(files, settings) + exec(&files, &settings) } -fn file_to_lines_iter( - file: impl AsRef, - settings: &'_ GlobalSettings, -) -> Option + '_> { - let (reader, _) = match open(file) { - Some(x) => x, - None => return None, - }; - - let buf_reader = BufReader::new(reader); - - Some( - buf_reader - .split(if settings.zero_terminated { - b'\0' - } else { - b'\n' - }) - .map(move |line| { - Line::new( - crash_if_err!(1, String::from_utf8(crash_if_err!(1, line))), - settings, - ) - }), - ) -} - -fn output_sorted_lines(iter: impl Iterator, settings: &GlobalSettings) { +fn output_sorted_lines<'a>(iter: impl Iterator>, settings: &GlobalSettings) { if settings.unique { print_sorted( iter.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), @@ -1176,87 +1058,48 @@ fn output_sorted_lines(iter: impl Iterator, settings: &GlobalSettin } } -fn exec(files: Vec, settings: GlobalSettings) -> i32 { +fn exec(files: &[String], settings: &GlobalSettings) -> i32 { if settings.merge { - let mut file_merger = FileMerger::new(&settings); - for lines in files - .iter() - .filter_map(|file| file_to_lines_iter(file, &settings)) - { - file_merger.push_file(Box::new(lines)); + let mut file_merger = merge::merge(files, settings); + file_merger.write_all(settings); + } else if settings.check { + if files.len() > 1 { + crash!(1, "only one file allowed with -c"); } - output_sorted_lines(file_merger, &settings); + return check::check(files.first().unwrap(), settings); + } else if settings.ext_sort { + let mut lines = files.iter().filter_map(open); + + let mut sorted = ext_sort(&mut lines, &settings); + sorted.file_merger.write_all(settings); } else { - let lines = files - .iter() - .filter_map(|file| file_to_lines_iter(file, &settings)) - .flatten(); + let separator = if settings.zero_terminated { '\0' } else { '\n' }; + let mut lines = vec![]; + let mut full_string = String::new(); - if settings.check { - return exec_check_file(lines, &settings); - } + for mut file in files.iter().filter_map(open) { + crash_if_err!(1, file.read_to_string(&mut full_string)); - // Only use ext_sorter when we need to. - // Probably faster that we don't create - // an owned value each run - if settings.ext_sort { - let sorted_lines = ext_sort(lines, &settings); - output_sorted_lines(sorted_lines, &settings); - } else { - let mut lines = vec![]; - - // This is duplicated from fn file_to_lines_iter, but using that function directly results in a performance regression. - for (file, _) in files.iter().map(open).flatten() { - let buf_reader = BufReader::new(file); - for line in buf_reader.split(if settings.zero_terminated { - b'\0' - } else { - b'\n' - }) { - let string = crash_if_err!(1, String::from_utf8(crash_if_err!(1, line))); - lines.push(Line::new(string, &settings)); - } + if !full_string.ends_with(separator) { + full_string.push(separator); } - - sort_by(&mut lines, &settings); - output_sorted_lines(lines.into_iter(), &settings); } - } + if full_string.ends_with(separator) { + full_string.pop(); + } + + for line in full_string.split(if settings.zero_terminated { '\0' } else { '\n' }) { + lines.push(Line::create(line, &settings)); + } + + sort_by(&mut lines, &settings); + output_sorted_lines(lines.into_iter(), &settings); + } 0 } -fn exec_check_file(unwrapped_lines: impl Iterator, settings: &GlobalSettings) -> i32 { - // errors yields the line before each disorder, - // plus the last line (quirk of .coalesce()) - let mut errors = unwrapped_lines - .enumerate() - .coalesce(|(last_i, last_line), (i, line)| { - if compare_by(&last_line, &line, &settings) == Ordering::Greater { - Err(((last_i, last_line), (i, line))) - } else { - Ok((i, line)) - } - }); - if let Some((first_error_index, _line)) = errors.next() { - // Check for a second "error", as .coalesce() always returns the last - // line, no matter what our merging function does. - if let Some(_last_line_or_next_error) = errors.next() { - if !settings.check_silent { - println!("sort: disorder in line {}", first_error_index); - }; - 1 - } else { - // first "error" was actually the last line. - 0 - } - } else { - // unwrapped_lines was empty. Empty files are defined to be sorted. - 0 - } -} - -fn sort_by(unsorted: &mut Vec, settings: &GlobalSettings) { +fn sort_by<'a>(unsorted: &mut Vec>, settings: &GlobalSettings) { if settings.stable || settings.unique { unsorted.par_sort_by(|a, b| compare_by(a, b, &settings)) } else { @@ -1264,19 +1107,39 @@ fn sort_by(unsorted: &mut Vec, settings: &GlobalSettings) { } } -fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering { - for (idx, selector) in global_settings.selectors.iter().enumerate() { - let (a_selection, b_selection) = if idx == 0 { - (&a.first_selection, &b.first_selection) +fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings) -> Ordering { + let mut idx = 0; + for selector in &global_settings.selectors { + let mut _selections = None; + let (a_selection, b_selection) = if selector.is_default_selection { + // We can select the whole line. + // We have to store the selections outside of the if-block so that they live long enough. + _selections = Some(( + Selection { + slice: a.line, + num_cache: None, + }, + Selection { + slice: b.line, + num_cache: None, + }, + )); + // Unwrap the selections again, and return references to them. + ( + &_selections.as_ref().unwrap().0, + &_selections.as_ref().unwrap().1, + ) } else { - (&a.other_selections[idx - 1], &b.other_selections[idx - 1]) + let selections = (&a.selections[idx], &b.selections[idx]); + idx += 1; + selections }; - let a_str = a_selection.get_str(a); - let b_str = b_selection.get_str(b); + let a_str = a_selection.slice; + let b_str = b_selection.slice; let settings = &selector.settings; let cmp: Ordering = if settings.random { - random_shuffle(a_str, b_str, global_settings.salt.clone()) + random_shuffle(a_str, b_str, &global_settings.salt) } else { match settings.mode { SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( @@ -1307,7 +1170,7 @@ fn compare_by(a: &Line, b: &Line, global_settings: &GlobalSettings) -> Ordering let cmp = if global_settings.random || global_settings.stable || global_settings.unique { Ordering::Equal } else { - a.line.cmp(&b.line) + a.line.cmp(b.line) }; if global_settings.reverse { @@ -1362,7 +1225,7 @@ fn get_leading_gen(input: &str) -> Range { leading_whitespace_len..input.len() } -#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] enum GeneralF64ParseResult { Invalid, NaN, @@ -1408,12 +1271,11 @@ fn get_hash(t: &T) -> u64 { s.finish() } -fn random_shuffle(a: &str, b: &str, x: String) -> Ordering { +fn random_shuffle(a: &str, b: &str, salt: &str) -> Ordering { #![allow(clippy::comparison_chain)] - let salt_slice = x.as_str(); - let da = get_hash(&[a, salt_slice].concat()); - let db = get_hash(&[b, salt_slice].concat()); + let da = get_hash(&[a, salt].concat()); + let db = get_hash(&[b, salt].concat()); da.cmp(&db) } @@ -1504,45 +1366,23 @@ fn version_compare(a: &str, b: &str) -> Ordering { } } -fn print_sorted>(iter: T, settings: &GlobalSettings) { - let mut file: Box = match settings.outfile { - Some(ref filename) => match File::create(Path::new(&filename)) { - Ok(f) => Box::new(BufWriter::new(f)) as Box, - Err(e) => { - show_error!("{0}: {1}", filename, e.to_string()); - panic!("Could not open output file"); - } - }, - None => Box::new(BufWriter::new(stdout())) as Box, - }; - if settings.zero_terminated && !settings.debug { - for line in iter { - crash_if_err!(1, file.write_all(line.line.as_bytes())); - crash_if_err!(1, file.write_all("\0".as_bytes())); - } - } else { - for line in iter { - if !settings.debug { - crash_if_err!(1, file.write_all(line.line.as_bytes())); - crash_if_err!(1, file.write_all("\n".as_bytes())); - } else { - crash_if_err!(1, line.print_debug(settings, &mut file)); - } - } +fn print_sorted<'a, T: Iterator>>(iter: T, settings: &GlobalSettings) { + let mut writer = settings.out_writer(); + for line in iter { + line.print(&mut writer, settings); } - crash_if_err!(1, file.flush()); } // from cat.rs -fn open(path: impl AsRef) -> Option<(Box, bool)> { +fn open(path: impl AsRef) -> Option> { let path = path.as_ref(); if path == "-" { let stdin = stdin(); - return Some((Box::new(stdin) as Box, is_stdin_interactive())); + return Some(Box::new(stdin) as Box); } match File::open(Path::new(path)) { - Ok(f) => Some((Box::new(f) as Box, false)), + Ok(f) => Some(Box::new(f) as Box), Err(e) => { show_error!("{0:?}: {1}", path, e.to_string()); None @@ -1568,7 +1408,7 @@ mod tests { let b = "Ted"; let c = get_rand_string(); - assert_eq!(Ordering::Equal, random_shuffle(a, b, c)); + assert_eq!(Ordering::Equal, random_shuffle(a, b, &c)); } #[test] @@ -1592,7 +1432,7 @@ mod tests { let b = "9"; let c = get_rand_string(); - assert_eq!(Ordering::Equal, random_shuffle(a, b, c)); + assert_eq!(Ordering::Equal, random_shuffle(a, b, &c)); } #[test] @@ -1631,10 +1471,12 @@ mod tests { fn test_line_size() { // We should make sure to not regress the size of the Line struct because // it is unconditional overhead for every line we sort. - assert_eq!(std::mem::size_of::(), 56); + assert_eq!(std::mem::size_of::(), 32); // These are the fields of Line: - assert_eq!(std::mem::size_of::>(), 16); - assert_eq!(std::mem::size_of::(), 24); + assert_eq!(std::mem::size_of::<&str>(), 16); assert_eq!(std::mem::size_of::>(), 16); + + // How big is a selection? Constant cost all lines pay when we need selections. + assert_eq!(std::mem::size_of::(), 24); } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index bad9d577e..e89d18054 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -122,7 +122,7 @@ fn test_check_zero_terminated_failure() { .arg("-c") .arg("zero-terminated.txt") .fails() - .stdout_is("sort: disorder in line 0\n"); + .stdout_is("sort: zero-terminated.txt:2: disorder: ../../fixtures/du\n"); } #[test] @@ -621,7 +621,7 @@ fn test_check() { .arg("-c") .arg("check_fail.txt") .fails() - .stdout_is("sort: disorder in line 4\n"); + .stdout_is("sort: check_fail.txt:6: disorder: 5\n"); new_ucmd!() .arg("-c") From 2f84f59573128927b5f8ffd9cad6a09ef895ffad Mon Sep 17 00:00:00 2001 From: Chad Brewbaker Date: Sun, 16 May 2021 19:43:53 -0500 Subject: [PATCH 297/399] fixing regex to take negative time offsets --- tests/by-util/test_ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 0985ba719..a57525e4b 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -685,7 +685,7 @@ fn test_ls_styles() { at.touch("test"); let re_full = Regex::new( - r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d* \+\d{4} test\n", + r"[a-z-]* \d* \w* \w* \d* \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d* (\+|\-)\d{4} test\n", ) .unwrap(); let re_long = From eeef8290df9e5ea24eb8ca4ae5fb3e0fc40576b3 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 16 May 2021 21:21:20 -0400 Subject: [PATCH 298/399] head: display errors for each input file Change the behavior of `head` to display an error for each problematic file, instead of displaying an error message for the first problematic file and terminating immediately at that point. This change now matches the behavior of GNU `head`. Before this commit, the first error caused the program to terminate immediately: $ head a b c head: error: head: cannot open 'a' for reading: No such file or directory After this commit: $ head a b c head: cannot open 'a' for reading: No such file or directory head: cannot open 'b' for reading: No such file or directory head: cannot open 'c' for reading: No such file or directory --- src/uu/head/src/head.rs | 73 ++++++++++++++++++-------------------- tests/by-util/test_head.rs | 12 +++++++ tests/common/util.rs | 7 +++- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index faaeedd3f..0c8b3bc88 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -2,7 +2,7 @@ use clap::{App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; -use uucore::{crash, executable, show_error}; +use uucore::{crash, executable, show_error, show_error_custom_description}; const EXIT_FAILURE: i32 = 1; const EXIT_SUCCESS: i32 = 0; @@ -400,7 +400,8 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul } } -fn uu_head(options: &HeadOptions) { +fn uu_head(options: &HeadOptions) -> Result<(), u32> { + let mut error_count = 0; let mut first = true; for fname in &options.files { let res = match fname.as_str() { @@ -433,30 +434,22 @@ fn uu_head(options: &HeadOptions) { name => { let mut file = match std::fs::File::open(name) { Ok(f) => f, - Err(err) => match err.kind() { - ErrorKind::NotFound => { - crash!( - EXIT_FAILURE, - "head: cannot open '{}' for reading: No such file or directory", - name - ); + Err(err) => { + let prefix = format!("cannot open '{}' for reading", name); + match err.kind() { + ErrorKind::NotFound => { + show_error_custom_description!(prefix, "No such file or directory"); + } + ErrorKind::PermissionDenied => { + show_error_custom_description!(prefix, "Permission denied"); + } + _ => { + show_error_custom_description!(prefix, "{}", err); + } } - ErrorKind::PermissionDenied => { - crash!( - EXIT_FAILURE, - "head: cannot open '{}' for reading: Permission denied", - name - ); - } - _ => { - crash!( - EXIT_FAILURE, - "head: cannot open '{}' for reading: {}", - name, - err - ); - } - }, + error_count += 1; + continue; + } }; if (options.files.len() > 1 && !options.quiet) || options.verbose { if !first { @@ -468,21 +461,22 @@ fn uu_head(options: &HeadOptions) { } }; if res.is_err() { - if fname.as_str() == "-" { - crash!( - EXIT_FAILURE, - "head: error reading standard input: Input/output error" - ); + let name = if fname.as_str() == "-" { + "standard input" } else { - crash!( - EXIT_FAILURE, - "head: error reading {}: Input/output error", - fname - ); - } + fname + }; + let prefix = format!("error reading {}", name); + show_error_custom_description!(prefix, "Input/output error"); + error_count += 1; } first = false; } + if error_count > 0 { + Err(error_count) + } else { + Ok(()) + } } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -492,9 +486,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(EXIT_FAILURE, "head: {}", s); } }; - uu_head(&args); - - EXIT_SUCCESS + match uu_head(&args) { + Ok(_) => EXIT_SUCCESS, + Err(_) => EXIT_FAILURE, + } } #[cfg(test)] diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 2aedbdcbe..88df1f068 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -162,6 +162,18 @@ fn test_no_such_file_or_directory() { .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory"); } +/// Test that each non-existent files gets its own error message printed. +#[test] +fn test_multiple_nonexistent_files() { + new_ucmd!() + .args(&["bogusfile1", "bogusfile2"]) + .fails() + .stdout_does_not_contain("==> bogusfile1 <==") + .stderr_contains("cannot open 'bogusfile1' for reading: No such file or directory") + .stdout_does_not_contain("==> bogusfile2 <==") + .stderr_contains("cannot open 'bogusfile2' for reading: No such file or directory"); +} + // there was a bug not caught by previous tests // where for negative n > 3, the total amount of lines // was correct, but it would eat from the second line diff --git a/tests/common/util.rs b/tests/common/util.rs index 719849afc..611baadd4 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -315,7 +315,12 @@ impl CmdResult { } pub fn stdout_does_not_contain>(&self, cmp: T) -> &CmdResult { - assert!(!self.stdout_str().contains(cmp.as_ref())); + assert!( + !self.stdout_str().contains(cmp.as_ref()), + "'{}' contains '{}' but should not", + self.stdout_str(), + cmp.as_ref(), + ); self } From c68c83c6ddc8b629d178b47ba6d7a6f987f54817 Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 29 Apr 2021 13:50:31 +0200 Subject: [PATCH 299/399] factor::table: Take mutable refs This will be easier to adapt to working with multiple numbers to process at once. --- src/uu/factor/src/factor.rs | 2 +- src/uu/factor/src/table.rs | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index ebe06a1c5..f53abd772 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -161,7 +161,7 @@ pub fn factor(mut n: u64) -> Factors { return factors; } - let (factors, n) = table::factor(n, factors); + table::factor(&mut n, &mut factors); #[allow(clippy::let_and_return)] let r = if n < (1 << 32) { diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 94ad6df4c..cbd4af5e4 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -8,15 +8,13 @@ // spell-checker: ignore (ToDO) INVS -use std::num::Wrapping; - use crate::Factors; include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); -pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { +pub(crate) fn factor(num: &mut u64, factors: &mut Factors) { for &(prime, inv, ceil) in P_INVS_U64 { - if num == 1 { + if *num == 1 { break; } @@ -27,11 +25,11 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { // for a nice explanation. let mut k = 0; loop { - let Wrapping(x) = Wrapping(num) * Wrapping(inv); + let x = num.wrapping_mul(inv); // While prime divides num if x <= ceil { - num = x; + *num = x; k += 1; #[cfg(feature = "coz")] coz::progress!("factor found"); @@ -43,6 +41,4 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { } } } - - (factors, num) } From cd047425aaefbf9ea4bb059651dffddbcbace251 Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 29 Apr 2021 14:15:40 +0200 Subject: [PATCH 300/399] factor::table: Add chunked implementation and microbenchmarks The factor_chunk implementation is a strawman, but getting it in place allows us to set up the microbenchmarking etc. --- src/uu/factor/Cargo.toml | 5 ++++ src/uu/factor/benches/table.rs | 44 ++++++++++++++++++++++++++++++++++ src/uu/factor/src/cli.rs | 4 ++-- src/uu/factor/src/table.rs | 9 ++++++- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/uu/factor/benches/table.rs diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index c4e7e8469..cb77c5d19 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -23,6 +23,7 @@ uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [dev-dependencies] +array-init = "2.0.0" criterion = "0.3" paste = "0.1.18" quickcheck = "0.9.2" @@ -32,6 +33,10 @@ rand_chacha = "0.2.2" name = "gcd" harness = false +[[bench]] +name = "table" +harness = false + [[bin]] name = "factor" path = "src/main.rs" diff --git a/src/uu/factor/benches/table.rs b/src/uu/factor/benches/table.rs new file mode 100644 index 000000000..8fae7cef6 --- /dev/null +++ b/src/uu/factor/benches/table.rs @@ -0,0 +1,44 @@ +use array_init::array_init; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use uu_factor::{table::*, Factors}; + +fn table(c: &mut Criterion) { + let inputs = { + // Deterministic RNG; use an explicitely-named RNG to guarantee stability + use rand::{RngCore, SeedableRng}; + use rand_chacha::ChaCha8Rng; + const SEED: u64 = 0xdead_bebe_ea75_cafe; + let mut rng = ChaCha8Rng::seed_from_u64(SEED); + + std::iter::repeat_with(move || array_init(|_| rng.next_u64())) + }; + + let mut group = c.benchmark_group("table"); + for a in inputs.take(10) { + let a_str = format!("{:?}", a); + group.bench_with_input( + BenchmarkId::from_parameter("chunked_".to_owned() + &a_str), + &a, + |b, &a| { + b.iter(|| factor_chunk(&mut a.clone(), &mut array_init(|_| Factors::one()))); + }, + ); + group.bench_with_input( + BenchmarkId::from_parameter("seq_".to_owned() + &a_str), + &a, + |b, &a| { + b.iter(|| { + let mut n_s = a.clone(); + let mut f_s: [_; CHUNK_SIZE] = array_init(|_| Factors::one()); + for (n, f) in n_s.iter_mut().zip(f_s.iter_mut()) { + factor(n, f) + } + }) + }, + ); + } + group.finish() +} + +criterion_group!(benches, table); +criterion_main!(benches); diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index fb7b3f192..ee4c8a4c4 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -13,13 +13,13 @@ use std::error::Error; use std::io::{self, stdin, stdout, BufRead, Write}; mod factor; -pub(crate) use factor::*; +pub use factor::*; use uucore::InvalidEncodingHandling; mod miller_rabin; pub mod numeric; mod rho; -mod table; +pub mod table; static SYNTAX: &str = "[OPTION] [NUMBER]..."; static SUMMARY: &str = "Print the prime factors of the given number(s). diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index cbd4af5e4..72628054c 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -12,7 +12,7 @@ use crate::Factors; include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); -pub(crate) fn factor(num: &mut u64, factors: &mut Factors) { +pub fn factor(num: &mut u64, factors: &mut Factors) { for &(prime, inv, ceil) in P_INVS_U64 { if *num == 1 { break; @@ -42,3 +42,10 @@ pub(crate) fn factor(num: &mut u64, factors: &mut Factors) { } } } + +pub const CHUNK_SIZE: usize = 4; +pub fn factor_chunk(n_s: &mut [u64; CHUNK_SIZE], f_s: &mut [Factors; CHUNK_SIZE]) { + for (n, s) in n_s.iter_mut().zip(f_s.iter_mut()) { + factor(n, s); + } +} From 1fd5f9da25d9ce7be5206e5b1eabc0364064cdfb Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 29 Apr 2021 14:29:59 +0200 Subject: [PATCH 301/399] factor::table::factor_chunk: Turn loop inside-out This keeps the traversal of `P_INVS_U64` (a large table) to a single pass in-order, rather than `CHUNK_SIZE` passes. --- src/uu/factor/src/table.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 72628054c..45464ac27 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -45,7 +45,30 @@ pub fn factor(num: &mut u64, factors: &mut Factors) { pub const CHUNK_SIZE: usize = 4; pub fn factor_chunk(n_s: &mut [u64; CHUNK_SIZE], f_s: &mut [Factors; CHUNK_SIZE]) { - for (n, s) in n_s.iter_mut().zip(f_s.iter_mut()) { - factor(n, s); + for &(prime, inv, ceil) in P_INVS_U64 { + if n_s[0] == 1 && n_s[1] == 1 && n_s[2] == 1 && n_s[3] == 1 { + break; + } + + for (num, factors) in n_s.iter_mut().zip(f_s.iter_mut()) { + if *num == 1 { + continue; + } + let mut k = 0; + loop { + let x = num.wrapping_mul(inv); + + // While prime divides num + if x <= ceil { + *num = x; + k += 1; + } else { + if k > 0 { + factors.add(prime, k); + } + break; + } + } + } } } From 7c287542c7cd436520b3b07f124c6dcdf69e9f36 Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 29 Apr 2021 15:45:04 +0200 Subject: [PATCH 302/399] factor::table: Fixup microbenchmark Previous version would perform an amount of work proportional to `CHUNK_SIZE`, so this wasn't a valid way to benchmark at multiple values of that constant. The `TryInto` implementation for `&mut [T]` to `&mut [T; N]` relies on `const` generics, and is available in (stable) Rust v1.51 and later. --- src/uu/factor/benches/table.rs | 20 +++++++++++++++++--- src/uu/factor/src/table.rs | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/uu/factor/benches/table.rs b/src/uu/factor/benches/table.rs index 8fae7cef6..ad8036d67 100644 --- a/src/uu/factor/benches/table.rs +++ b/src/uu/factor/benches/table.rs @@ -1,8 +1,16 @@ +use std::convert::TryInto; use array_init::array_init; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use uu_factor::{table::*, Factors}; fn table(c: &mut Criterion) { + const INPUT_SIZE: usize = 128; + assert!( + INPUT_SIZE % CHUNK_SIZE == 0, + "INPUT_SIZE ({}) is not divisible by CHUNK_SIZE ({})", + INPUT_SIZE, + CHUNK_SIZE + ); let inputs = { // Deterministic RNG; use an explicitely-named RNG to guarantee stability use rand::{RngCore, SeedableRng}; @@ -10,7 +18,7 @@ fn table(c: &mut Criterion) { const SEED: u64 = 0xdead_bebe_ea75_cafe; let mut rng = ChaCha8Rng::seed_from_u64(SEED); - std::iter::repeat_with(move || array_init(|_| rng.next_u64())) + std::iter::repeat_with(move || array_init::<_, _, INPUT_SIZE>(|_| rng.next_u64())) }; let mut group = c.benchmark_group("table"); @@ -20,7 +28,13 @@ fn table(c: &mut Criterion) { BenchmarkId::from_parameter("chunked_".to_owned() + &a_str), &a, |b, &a| { - b.iter(|| factor_chunk(&mut a.clone(), &mut array_init(|_| Factors::one()))); + b.iter(|| { + let mut n_s = a.clone(); + let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); + for (n_s, f_s) in n_s.chunks_mut(CHUNK_SIZE).zip(f_s.chunks_mut(CHUNK_SIZE)) { + factor_chunk(n_s.try_into().unwrap(), f_s.try_into().unwrap()) + } + }) }, ); group.bench_with_input( @@ -29,7 +43,7 @@ fn table(c: &mut Criterion) { |b, &a| { b.iter(|| { let mut n_s = a.clone(); - let mut f_s: [_; CHUNK_SIZE] = array_init(|_| Factors::one()); + let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); for (n, f) in n_s.iter_mut().zip(f_s.iter_mut()) { factor(n, f) } diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 45464ac27..db2698e4b 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -43,7 +43,7 @@ pub fn factor(num: &mut u64, factors: &mut Factors) { } } -pub const CHUNK_SIZE: usize = 4; +pub const CHUNK_SIZE: usize = 8; pub fn factor_chunk(n_s: &mut [u64; CHUNK_SIZE], f_s: &mut [Factors; CHUNK_SIZE]) { for &(prime, inv, ceil) in P_INVS_U64 { if n_s[0] == 1 && n_s[1] == 1 && n_s[2] == 1 && n_s[3] == 1 { From 12efaa6add6d5ceab0c9c73459088faa806ec82e Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 3 May 2021 12:26:05 +0200 Subject: [PATCH 303/399] factor: Add BENCHMARKING.md --- src/uu/factor/BENCHMARKING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/uu/factor/BENCHMARKING.md diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md new file mode 100644 index 000000000..e93bed95e --- /dev/null +++ b/src/uu/factor/BENCHMARKING.md @@ -0,0 +1,12 @@ +# Benchmarking `factor` + +## Microbenchmarking deterministic functions + +We currently use [`criterion`] to benchmark deterministic functions, +such as `gcd` and `table::factor`. + +Those benchmarks can be simply executed with `cargo bench` as usual, +but may require a recent version of Rust, *i.e.* the project's minimum +supported version of Rust does not apply to the benchmarks. + +[`criterion`]: https://bheisler.github.io/criterion.rs/book/index.html From ae15bf16a83328c08fd77f31da40af192eeedb5e Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 3 May 2021 14:42:26 +0200 Subject: [PATCH 304/399] factor::benches::table: Report throughput (in numbers/s) --- src/uu/factor/benches/table.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/factor/benches/table.rs b/src/uu/factor/benches/table.rs index ad8036d67..44ea1c863 100644 --- a/src/uu/factor/benches/table.rs +++ b/src/uu/factor/benches/table.rs @@ -1,6 +1,6 @@ -use std::convert::TryInto; use array_init::array_init; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use std::convert::TryInto; use uu_factor::{table::*, Factors}; fn table(c: &mut Criterion) { @@ -22,6 +22,7 @@ fn table(c: &mut Criterion) { }; let mut group = c.benchmark_group("table"); + group.throughput(Throughput::Elements(INPUT_SIZE as _)); for a in inputs.take(10) { let a_str = format!("{:?}", a); group.bench_with_input( From e9f8194266125f72d8bf99ab83e8562a21c9d048 Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 3 May 2021 14:45:00 +0200 Subject: [PATCH 305/399] =?UTF-8?q?factor::benchmarking(doc):=20Add=20guid?= =?UTF-8?q?ance=20on=20running=20=C2=B5benches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/uu/factor/BENCHMARKING.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md index e93bed95e..e87c965c2 100644 --- a/src/uu/factor/BENCHMARKING.md +++ b/src/uu/factor/BENCHMARKING.md @@ -9,4 +9,26 @@ Those benchmarks can be simply executed with `cargo bench` as usual, but may require a recent version of Rust, *i.e.* the project's minimum supported version of Rust does not apply to the benchmarks. + +However, µbenchmarks are by nature unstable: not only are they specific to +the hardware, operating system version, etc., but they are noisy and affected +by other tasks on the system (browser, compile jobs, etc.), which can cause +`criterion` to report spurious performance improvements and regressions. + +This can be mitigated by getting as close to [idealised conditions][lemire] +as possible: +- minimize the amount of computation and I/O running concurrently to the + benchmark, *i.e.* close your browser and IM clients, don't compile at the + same time, etc. ; +- ensure the CPU's [frequency stays constant] during the benchmark ; +- [isolate a **physical** core], set it to `nohz_full`, and pin the benchmark + to it, so it won't be preempted in the middle of a measurement ; +- disable ASLR by running `setarch -R cargo bench`, so we can compare results + across multiple executions. + **TODO**: check this propagates to the benchmark process + + [`criterion`]: https://bheisler.github.io/criterion.rs/book/index.html +[lemire]: https://lemire.me/blog/2018/01/16/microbenchmarking-calls-for-idealized-conditions/ +[isolate a **physical** core]: https://pyperf.readthedocs.io/en/latest/system.html#isolate-cpus-on-linux +[frequency stays constant]: XXXTODO From 1d75f09743a8fbe436da4d84701200f844cfae9f Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 3 May 2021 14:45:24 +0200 Subject: [PATCH 306/399] =?UTF-8?q?factor::benchmarking(doc):=20Add=20guid?= =?UTF-8?q?ance=20on=20writing=20=C2=B5benches?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/uu/factor/BENCHMARKING.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md index e87c965c2..c629252b8 100644 --- a/src/uu/factor/BENCHMARKING.md +++ b/src/uu/factor/BENCHMARKING.md @@ -32,3 +32,34 @@ as possible: [lemire]: https://lemire.me/blog/2018/01/16/microbenchmarking-calls-for-idealized-conditions/ [isolate a **physical** core]: https://pyperf.readthedocs.io/en/latest/system.html#isolate-cpus-on-linux [frequency stays constant]: XXXTODO + + +### Guidance for designing µbenchmarks + +*Note:* this guidance is specific to `factor` and takes its application domain +into account; do not expect it to generalise to other projects. It is based +on Daniel Lemire's [*Microbenchmarking calls for idealized conditions*][lemire], +which I recommend reading if you want to add benchmarks to `factor`. + +1. Select a small, self-contained, deterministic component + `gcd` and `table::factor` are good example of such: + - no I/O or access to external data structures ; + - no call into other components ; + - behaviour is deterministic: no RNG, no concurrency, ... ; + - the test's body is *fast* (~100ns for `gcd`, ~10µs for `factor::table`), + so each sample takes a very short time, minimizing variability and + maximizing the numbers of samples we can take in a given time. + +2. Benchmarks are immutable (once merged in `uutils`) + Modifying a benchmark means previously-collected values cannot meaningfully + be compared, silently giving nonsensical results. If you must modify an + existing benchmark, rename it. + +3. Test common cases + We are interested in overall performance, rather than specific edge-cases; + use **reproducibly-randomised inputs**, sampling from either all possible + input values or some subset of interest. + +4. Use [`criterion`], `criterion::black_box`, ... + `criterion` isn't perfect, but it is also much better than ad-hoc + solutions in each benchmark. From ddfcd2eb14d8046c6246c753b00e2c0466e43c17 Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 3 May 2021 15:04:06 +0200 Subject: [PATCH 307/399] factor::benchmarking: Add wishlist / planned work --- src/uu/factor/BENCHMARKING.md | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md index c629252b8..3ad038c15 100644 --- a/src/uu/factor/BENCHMARKING.md +++ b/src/uu/factor/BENCHMARKING.md @@ -63,3 +63,52 @@ which I recommend reading if you want to add benchmarks to `factor`. 4. Use [`criterion`], `criterion::black_box`, ... `criterion` isn't perfect, but it is also much better than ad-hoc solutions in each benchmark. + + +## Wishlist + +### Configurable statistical estimators + +`criterion` always uses the arithmetic average as estimator; in µbenchmarks, +where the code under test is fully deterministic and the measurements are +subject to additive, positive noise, [the minimum is more appropriate][lemire]. + + +### CI & reproducible performance testing + +Measuring performance on real hardware is important, as it relates directly +to what users of `factor` experience; however, such measurements are subject +to the constraints of the real-world, and aren't perfectly reproducible. +Moreover, the mitigations for it (described above) aren't achievable in +virtualized, multi-tenant environments such as CI. + +Instead, we could run the µbenchmarks in a simulated CPU with [`cachegrind`], +measure execution “time” in that model (in CI), and use it to detect and report +performance improvements and regressions. + +[`iai`] is an implementation of this idea for Rust. + +[`cachegrind`]: https://www.valgrind.org/docs/manual/cg-manual.html +[`iai`]: https://bheisler.github.io/criterion.rs/book/iai/iai.html + + +### Comparing randomised implementations across multiple inputs + +`factor` is a challenging target for system benchmarks as it combines two +characteristics: + +1. integer factoring algorithms are randomised, with large variance in + execution time ; + +2. various inputs also have large differences in factoring time, that + corresponds to no natural, linear ordering of the inputs. + + +If (1) was untrue (i.e. if execution time wasn't random), we could faithfully +compare 2 implementations (2 successive versions, or `uutils` and GNU) using +a scatter plot, where each axis corresponds to the perf. of one implementation. + +Similarly, without (2) we could plot numbers on the X axis and their factoring +time on the Y axis, using multiple lines for various quantiles. The large +differences in factoring times for successive numbers, mean that such a plot +would be unreadable. From 7c649bc74ecd425bdf15f5376979eeac973821c0 Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 3 May 2021 16:14:38 +0200 Subject: [PATCH 308/399] factor::benches: Add check against ASLR --- src/uu/factor/BENCHMARKING.md | 1 - src/uu/factor/benches/table.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md index 3ad038c15..cf3bb35d0 100644 --- a/src/uu/factor/BENCHMARKING.md +++ b/src/uu/factor/BENCHMARKING.md @@ -25,7 +25,6 @@ as possible: to it, so it won't be preempted in the middle of a measurement ; - disable ASLR by running `setarch -R cargo bench`, so we can compare results across multiple executions. - **TODO**: check this propagates to the benchmark process [`criterion`]: https://bheisler.github.io/criterion.rs/book/index.html diff --git a/src/uu/factor/benches/table.rs b/src/uu/factor/benches/table.rs index 44ea1c863..232e59053 100644 --- a/src/uu/factor/benches/table.rs +++ b/src/uu/factor/benches/table.rs @@ -4,6 +4,9 @@ use std::convert::TryInto; use uu_factor::{table::*, Factors}; fn table(c: &mut Criterion) { + #[cfg(target_os = "linux")] + check_personality(); + const INPUT_SIZE: usize = 128; assert!( INPUT_SIZE % CHUNK_SIZE == 0, @@ -55,5 +58,29 @@ fn table(c: &mut Criterion) { group.finish() } +#[cfg(target_os = "linux")] +fn check_personality() { + use std::fs; + const ADDR_NO_RANDOMIZE: u64 = 0x0040000; + const PERSONALITY_PATH: &'static str = "/proc/self/personality"; + + let p_string = fs::read_to_string(PERSONALITY_PATH) + .expect(&format!("Couldn't read '{}'", PERSONALITY_PATH)) + .strip_suffix("\n") + .unwrap() + .to_owned(); + + let personality = u64::from_str_radix(&p_string, 16).expect(&format!( + "Expected a hex value for personality, got '{:?}'", + p_string + )); + if personality & ADDR_NO_RANDOMIZE == 0 { + eprintln!( + "WARNING: Benchmarking with ASLR enabled (personality is {:x}), results might not be reproducible.", + personality + ); + } +} + criterion_group!(benches, table); criterion_main!(benches); From 1cd001f529c80484f52175370972015abef425b9 Mon Sep 17 00:00:00 2001 From: nicoo Date: Sat, 8 May 2021 17:56:18 +0200 Subject: [PATCH 309/399] factor::benches::table: Match BenchmarkId w/ criterion's conventions See https://bheisler.github.io/criterion.rs/book/user_guide/comparing_functions.html --- src/uu/factor/benches/table.rs | 44 ++++++++++++++-------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/uu/factor/benches/table.rs b/src/uu/factor/benches/table.rs index 232e59053..0b31b2b4c 100644 --- a/src/uu/factor/benches/table.rs +++ b/src/uu/factor/benches/table.rs @@ -28,32 +28,24 @@ fn table(c: &mut Criterion) { group.throughput(Throughput::Elements(INPUT_SIZE as _)); for a in inputs.take(10) { let a_str = format!("{:?}", a); - group.bench_with_input( - BenchmarkId::from_parameter("chunked_".to_owned() + &a_str), - &a, - |b, &a| { - b.iter(|| { - let mut n_s = a.clone(); - let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); - for (n_s, f_s) in n_s.chunks_mut(CHUNK_SIZE).zip(f_s.chunks_mut(CHUNK_SIZE)) { - factor_chunk(n_s.try_into().unwrap(), f_s.try_into().unwrap()) - } - }) - }, - ); - group.bench_with_input( - BenchmarkId::from_parameter("seq_".to_owned() + &a_str), - &a, - |b, &a| { - b.iter(|| { - let mut n_s = a.clone(); - let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); - for (n, f) in n_s.iter_mut().zip(f_s.iter_mut()) { - factor(n, f) - } - }) - }, - ); + group.bench_with_input(BenchmarkId::new("factor_chunk", &a_str), &a, |b, &a| { + b.iter(|| { + let mut n_s = a.clone(); + let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); + for (n_s, f_s) in n_s.chunks_mut(CHUNK_SIZE).zip(f_s.chunks_mut(CHUNK_SIZE)) { + factor_chunk(n_s.try_into().unwrap(), f_s.try_into().unwrap()) + } + }) + }); + group.bench_with_input(BenchmarkId::new("factor", &a_str), &a, |b, &a| { + b.iter(|| { + let mut n_s = a.clone(); + let mut f_s: [_; INPUT_SIZE] = array_init(|_| Factors::one()); + for (n, f) in n_s.iter_mut().zip(f_s.iter_mut()) { + factor(n, f) + } + }) + }); } group.finish() } From 00322b986bfe9311985f64f3401112db12600813 Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 17 May 2021 19:22:56 +0200 Subject: [PATCH 310/399] factor: Move benchmarks out-of-crate --- Cargo.toml | 3 +++ src/uu/factor/BENCHMARKING.md | 13 ++++++---- src/uu/factor/Cargo.toml | 18 +++---------- tests/benches/factor/Cargo.toml | 26 +++++++++++++++++++ .../benches}/factor/benches/gcd.rs | 0 .../benches}/factor/benches/table.rs | 0 6 files changed, 41 insertions(+), 19 deletions(-) create mode 100644 tests/benches/factor/Cargo.toml rename {src/uu => tests/benches}/factor/benches/gcd.rs (100%) rename {src/uu => tests/benches}/factor/benches/table.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 7c1a771fd..745393260 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -324,6 +324,9 @@ wc = { optional=true, version="0.0.6", package="uu_wc", path="src/uu/wc" } who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" } whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" } yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" } + +factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } + # # * pinned transitive dependencies # Not needed for now. Keep as examples: diff --git a/src/uu/factor/BENCHMARKING.md b/src/uu/factor/BENCHMARKING.md index cf3bb35d0..e174d62b7 100644 --- a/src/uu/factor/BENCHMARKING.md +++ b/src/uu/factor/BENCHMARKING.md @@ -1,15 +1,18 @@ # Benchmarking `factor` +The benchmarks for `factor` are located under `tests/benches/factor` +and can be invoked with `cargo bench` in that directory. + +They are located outside the `uu_factor` crate, as they do not comply +with the project's minimum supported Rust version, *i.e.* may require +a newer version of `rustc`. + + ## Microbenchmarking deterministic functions We currently use [`criterion`] to benchmark deterministic functions, such as `gcd` and `table::factor`. -Those benchmarks can be simply executed with `cargo bench` as usual, -but may require a recent version of Rust, *i.e.* the project's minimum -supported version of Rust does not apply to the benchmarks. - - However, µbenchmarks are by nature unstable: not only are they specific to the hardware, operating system version, etc., but they are noisy and affected by other tasks on the system (browser, compile jobs, etc.), which can cause diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index cb77c5d19..eb34519f1 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -17,25 +17,15 @@ num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs [dependencies] coz = { version = "0.1.3", optional = true } num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" -rand = { version="0.7", features=["small_rng"] } -smallvec = { version="0.6.14, < 1.0" } -uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } -uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +rand = { version = "0.7", features = ["small_rng"] } +smallvec = { version = "0.6.14, < 1.0" } +uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } +uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } [dev-dependencies] -array-init = "2.0.0" -criterion = "0.3" paste = "0.1.18" quickcheck = "0.9.2" -rand_chacha = "0.2.2" -[[bench]] -name = "gcd" -harness = false - -[[bench]] -name = "table" -harness = false [[bin]] name = "factor" diff --git a/tests/benches/factor/Cargo.toml b/tests/benches/factor/Cargo.toml new file mode 100644 index 000000000..b3b718477 --- /dev/null +++ b/tests/benches/factor/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "uu_factor_benches" +version = "0.0.0" +authors = ["nicoo "] +license = "MIT" +description = "Benchmarks for the uu_factor integer factorization tool" +homepage = "https://github.com/uutils/coreutils" +edition = "2018" + +[dependencies] +uu_factor = { path = "../../../src/uu/factor" } + +[dev-dependencies] +array-init = "2.0.0" +criterion = "0.3" +rand = "0.7" +rand_chacha = "0.2.2" + + +[[bench]] +name = "gcd" +harness = false + +[[bench]] +name = "table" +harness = false diff --git a/src/uu/factor/benches/gcd.rs b/tests/benches/factor/benches/gcd.rs similarity index 100% rename from src/uu/factor/benches/gcd.rs rename to tests/benches/factor/benches/gcd.rs diff --git a/src/uu/factor/benches/table.rs b/tests/benches/factor/benches/table.rs similarity index 100% rename from src/uu/factor/benches/table.rs rename to tests/benches/factor/benches/table.rs From 9afed1f25f244305a1e24fc17aae061d0bd58c75 Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 17 May 2021 19:41:32 +0200 Subject: [PATCH 311/399] Update Cargo.lock Adding array-init v2.0.0 Updating cast v0.2.5 -> v0.2.6 Adding pest v2.1.3 Updating rustc_version v0.2.3 -> v0.3.3 Adding semver v0.11.0 Adding semver-parser v0.10.2 Updating serde v1.0.125 -> v1.0.126 Updating serde_derive v1.0.125 -> v1.0.126 Adding ucd-trie v0.1.3 Adding uu_factor_benches v0.0.0 (#tests/benches/factor) --- Cargo.lock | 77 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77957de80..34e918b45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "array-init" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6945cc5422176fc5e602e590c2878d2c2acd9a4fe20a4baa7c28022521698ec6" + [[package]] name = "arrayvec" version = "0.4.12" @@ -136,9 +142,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75" +checksum = "57cdfa5d50aad6cb4d44dcab6101a7f79925bd59d82ca42f38a9856a28865374" dependencies = [ "rustc_version", ] @@ -258,6 +264,7 @@ dependencies = [ "uu_expand", "uu_expr", "uu_factor", + "uu_factor_benches", "uu_false", "uu_fmt", "uu_fold", @@ -1027,6 +1034,15 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "pkg-config" version = "0.3.19" @@ -1336,11 +1352,11 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rustc_version" -version = "0.2.3" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ - "semver", + "semver 0.11.0", ] [[package]] @@ -1370,7 +1386,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -1380,10 +1405,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] -name = "serde" -version = "1.0.125" +name = "semver-parser" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" [[package]] name = "serde_cbor" @@ -1397,9 +1431,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote 1.0.9", @@ -1627,6 +1661,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-segmentation" version = "1.7.1" @@ -1922,17 +1962,26 @@ name = "uu_factor" version = "0.0.6" dependencies = [ "coz", - "criterion", "num-traits", "paste", "quickcheck", "rand 0.7.3", - "rand_chacha", "smallvec", "uucore", "uucore_procs", ] +[[package]] +name = "uu_factor_benches" +version = "0.0.0" +dependencies = [ + "array-init", + "criterion", + "rand 0.7.3", + "rand_chacha", + "uu_factor", +] + [[package]] name = "uu_false" version = "0.0.6" @@ -2407,7 +2456,7 @@ dependencies = [ "itertools 0.10.0", "rand 0.7.3", "rayon", - "semver", + "semver 0.9.0", "tempdir", "unicode-width", "uucore", From f46b119493bfe419437e3bd97638f8553eb5ad8a Mon Sep 17 00:00:00 2001 From: nicoo Date: Mon, 17 May 2021 20:37:26 +0200 Subject: [PATCH 312/399] CI: Stabilise the version of GNU tests used in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The “GNU tests” task is routinely broken on `master`. Broken CI is worse than no CI, as it teaches people to ignore errors. This PR pins the versions of the GNU testsuite (and GNUlib) used, to current stable versions, so this task stops breaking unexpectedly. Presumably, someone will update `GNU.yml` when a new stable version of the GNU coreutils is released, but I'm not volunteering. --- .github/workflows/GNU.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index a68f0a083..d721eb8b1 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -12,16 +12,18 @@ jobs: uses: actions/checkout@v2 with: path: 'uutils' - - name: Chechout GNU coreutils + - name: Checkout GNU coreutils uses: actions/checkout@v2 with: repository: 'coreutils/coreutils' path: 'gnu' - - name: Chechout GNU corelib + ref: v8.32 + - name: Checkout GNU corelib uses: actions/checkout@v2 with: repository: 'coreutils/gnulib' path: 'gnulib' + ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 From 047d775e5ee44dddfe77a98f019bda920458d554 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 17 May 2021 21:24:58 +0200 Subject: [PATCH 313/399] gh action: fix the GNU testsuite job --- .github/workflows/GNU.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index a68f0a083..f976b4633 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -92,7 +92,7 @@ jobs: sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh - sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh init.cfg + sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh init.cfg sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh @@ -104,7 +104,6 @@ jobs: #Add specific timeout to tests that currently hang to limit time spent waiting sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh - test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" - name: Run GNU tests From dc93f29fe3532318059f9d5b7e39c1048f6bbc81 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 17 May 2021 22:22:18 +0200 Subject: [PATCH 314/399] CICD: install GNU coreutils on macOS --- .github/workflows/CICD.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index cc0972bf9..a42d2f335 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -235,6 +235,9 @@ jobs: arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; esac + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac - name: Initialize workflow variables id: vars shell: bash From fea1026669ec4f01b121eb1b2c027ad00595cc5c Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 17 May 2021 18:15:39 -0400 Subject: [PATCH 315/399] tail: use std::io::copy() to write bytes to stdout --- src/uu/tail/src/tail.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 6dafee184..371f0e2ed 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -19,7 +19,6 @@ mod chunks; mod platform; mod ringbuffer; use chunks::ReverseChunks; -use chunks::BLOCK_SIZE; use ringbuffer::RingBuffer; use clap::{App, Arg}; @@ -442,8 +441,6 @@ fn backwards_thru_file(file: &mut File, num_delimiters: usize, delimiter: u8) { /// `BLOCK_SIZE` until we find the location of the first line/byte. This ends up /// being a nice performance win for very large files. fn bounded_tail(file: &mut File, settings: &Settings) { - let mut buf = vec![0; BLOCK_SIZE as usize]; - // Find the position in the file to start printing from. match settings.mode { FilterMode::Lines(count, delimiter) => { @@ -455,18 +452,9 @@ fn bounded_tail(file: &mut File, settings: &Settings) { } // Print the target section of the file. - loop { - let bytes_read = file.read(&mut buf).unwrap(); - - let mut stdout = stdout(); - for b in &buf[0..bytes_read] { - print_byte(&mut stdout, *b); - } - - if bytes_read == 0 { - break; - } - } + let stdout = stdout(); + let mut stdout = stdout.lock(); + std::io::copy(file, &mut stdout).unwrap(); } /// Collect the last elements of an iterator into a `VecDeque`. From bc296455316a418ede9238f0b0a3745e261a0b46 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 17 May 2021 19:33:49 -0400 Subject: [PATCH 316/399] tail: fix off-by-one issue for +NUM args Fix an off-by-one issue for `tail -c +NUM` and `tail -n +NUM` command line options. --- src/uu/tail/src/tail.rs | 5 ++++- tests/by-util/test_tail.rs | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 6dafee184..6af6d4b97 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -489,7 +489,10 @@ where E: fmt::Debug, { if beginning { - iter.skip(count as usize).map(|r| r.unwrap()).collect() + // GNU `tail` seems to index bytes and lines starting at 1, not + // at 0. It seems to treat `+0` and `+1` as the same thing. + let i = count.max(1) - 1; + iter.skip(i as usize).map(|r| r.unwrap()).collect() } else { RingBuffer::from_iter(iter.map(|r| r.unwrap()), count as usize).data } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 1c025cf4c..dddbb9c31 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -348,3 +348,47 @@ fn test_negative_indexing() { fn test_sleep_interval() { new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds(); } + + +/// Test for reading all but the first NUM bytes: `tail -c +3`. +#[test] +fn test_positive_bytes() { + new_ucmd!() + .args(&["-c", "+3"]) + .pipe_in("abcde") + .succeeds() + .stdout_is("cde"); +} + + +/// Test for reading all bytes, specified by `tail -c +0`. +#[test] +fn test_positive_zero_bytes() { + new_ucmd!() + .args(&["-c", "+0"]) + .pipe_in("abcde") + .succeeds() + .stdout_is("abcde"); +} + + +/// Test for reading all but the first NUM lines: `tail -n +3`. +#[test] +fn test_positive_lines() { + new_ucmd!() + .args(&["-n", "+3"]) + .pipe_in("a\nb\nc\nd\ne\n") + .succeeds() + .stdout_is("c\nd\ne\n"); +} + + +/// Test for reading all lines, specified by `tail -n +0`. +#[test] +fn test_positive_zero_lines() { + new_ucmd!() + .args(&["-n", "+0"]) + .pipe_in("a\nb\nc\nd\ne\n") + .succeeds() + .stdout_is("a\nb\nc\nd\ne\n"); +} From 7c7d622d540449cc77e9adf84ee6c32dcb73e30b Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 18 May 2021 02:00:16 +0200 Subject: [PATCH 317/399] tests: add test for issue #2223 --- tests/by-util/test_ls.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index a57525e4b..8d32172b0 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1966,3 +1966,38 @@ fn test_ls_sort_extension() { expected, ); } + +// This tests for the open issue described in #2223 +#[cfg_attr(not(feature = "test_unimplemented"), ignore)] +#[test] +fn test_ls_path() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file1 = "file1"; + let file2 = "file2"; + let dir = "dir"; + let path = &format!("{}/{}", dir, file2); + + at.mkdir(dir); + at.touch(file1); + at.touch(path); + + let expected_stdout = &format!("{}\n", path); + scene.ucmd().arg(path).run().stdout_is(expected_stdout); + + let expected_stdout = &format!("./{}\n", path); + scene.ucmd().arg(path).run().stdout_is(expected_stdout); + + let abs_path = format!("{}/{}\n", at.as_string(), path); + println!(":{}", abs_path); + scene.ucmd().arg(&abs_path).run().stdout_is(&abs_path); + + let expected_stdout = &format!("{}\n{}\n", file1, path); + scene + .ucmd() + .arg(file1) + .arg(path) + .run() + .stdout_is(expected_stdout); +} From ce5b852a3121bf8069c1d8b2948fa8c3b31f6134 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Tue, 18 May 2021 19:58:33 +0200 Subject: [PATCH 318/399] stat: remove unused/duplicate tests --- src/uu/stat/src/test_stat.rs | 76 ------------------------------------ 1 file changed, 76 deletions(-) delete mode 100644 src/uu/stat/src/test_stat.rs diff --git a/src/uu/stat/src/test_stat.rs b/src/uu/stat/src/test_stat.rs deleted file mode 100644 index 05e91fb84..000000000 --- a/src/uu/stat/src/test_stat.rs +++ /dev/null @@ -1,76 +0,0 @@ -// spell-checker:ignore (ToDO) scanutil qzxc dqzxc - -pub use super::*; - -#[test] -fn test_scanutil() { - assert_eq!(Some((-5, 2)), "-5zxc".scan_num::()); - assert_eq!(Some((51, 2)), "51zxc".scan_num::()); - assert_eq!(Some((192, 4)), "+192zxc".scan_num::()); - assert_eq!(None, "z192zxc".scan_num::()); - - assert_eq!(Some(('a', 3)), "141zxc".scan_char(8)); - assert_eq!(Some(('\n', 2)), "12qzxc".scan_char(8)); - assert_eq!(Some(('\r', 1)), "dqzxc".scan_char(16)); - assert_eq!(None, "z2qzxc".scan_char(8)); -} - -#[cfg(test)] -mod test_generate_tokens { - use super::*; - - #[test] - fn test_normal_format() { - let s = "%10.2ac%-5.w\n"; - let expected = vec![ - Token::Directive { - flag: 0, - width: 10, - precision: 2, - format: 'a', - }, - Token::Char('c'), - Token::Directive { - flag: F_LEFT, - width: 5, - precision: 0, - format: 'w', - }, - Token::Char('\n'), - ]; - assert_eq!(&expected, &Stater::generate_tokens(s, false).unwrap()); - } - - #[test] - fn test_printf_format() { - let s = "%-# 15a\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.-23w\\x12\\167\\132\\112\\n"; - let expected = vec![ - Token::Directive { - flag: F_LEFT | F_ALTER | F_SPACE, - width: 15, - precision: -1, - format: 'a', - }, - Token::Char('\r'), - Token::Char('"'), - Token::Char('\\'), - Token::Char('\x07'), - Token::Char('\x08'), - Token::Char('\x1B'), - Token::Char('\x0C'), - Token::Char('\x0B'), - Token::Directive { - flag: F_SIGN | F_ZERO, - width: 20, - precision: -1, - format: 'w', - }, - Token::Char('\x12'), - Token::Char('w'), - Token::Char('Z'), - Token::Char('J'), - Token::Char('\n'), - ]; - assert_eq!(&expected, &Stater::generate_tokens(s, true).unwrap()); - } -} From c60d3866c33243d2c186ba15e314eca791053f02 Mon Sep 17 00:00:00 2001 From: Chad Brewbaker Date: Tue, 18 May 2021 15:10:51 -0500 Subject: [PATCH 319/399] dev random blocks on linux --- tests/by-util/test_cat.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 67722daa2..6ec021ffa 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -347,10 +347,18 @@ fn test_squeeze_blank_before_numbering() { #[cfg(unix)] fn test_dev_random() { let mut buf = [0; 2048]; - let mut proc = new_ucmd!().args(&["/dev/random"]).run_no_wait(); + #[cfg(target_os = "linux")] + fn rand_gen() -> &'static str { "/dev/urandom"} + + #[cfg(not(target_os = "linux"))] + fn rand_gen() -> &'static str { "/dev/random"} + + let mut proc = new_ucmd!().args(&[rand_gen()]).run_no_wait(); let mut proc_stdout = proc.stdout.take().unwrap(); + println!("I got to 1"); proc_stdout.read_exact(&mut buf).unwrap(); + println!("I got to 3"); let num_zeroes = buf.iter().fold(0, |mut acc, &n| { if n == 0 { acc += 1; From a69cb11de9de0d51e679a78f4a1055d199d2915c Mon Sep 17 00:00:00 2001 From: Chad Brewbaker Date: Tue, 18 May 2021 15:17:07 -0500 Subject: [PATCH 320/399] Removing debug code --- tests/by-util/test_cat.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 6ec021ffa..997a7964c 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -355,10 +355,8 @@ fn test_dev_random() { let mut proc = new_ucmd!().args(&[rand_gen()]).run_no_wait(); let mut proc_stdout = proc.stdout.take().unwrap(); - println!("I got to 1"); proc_stdout.read_exact(&mut buf).unwrap(); - println!("I got to 3"); let num_zeroes = buf.iter().fold(0, |mut acc, &n| { if n == 0 { acc += 1; From 7bf342fa52300fdbe17fdb86cd630b40b267598e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 18 May 2021 21:31:55 +0200 Subject: [PATCH 321/399] publish the results of the gnu testsuite as a json file too --- .github/workflows/GNU.yml | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index f976b4633..3fd39aa15 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -34,7 +34,7 @@ jobs: shell: bash run: | sudo apt-get update - sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx + sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq pushd uutils make PROFILE=release BUILDDIR="$PWD/target/release/" @@ -117,15 +117,24 @@ jobs: - name: Extract tests info shell: bash run: | - if test -f gnu/tests/test-suite.log + LOG_FILE=gnu/tests/test-suite.log + if test -f "$LOG_FILE" then - TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-) - PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) - SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-) - FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-) - XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) - ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-) - echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR" + TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) + echo "::warning ::GNU testsuite = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" + jq -n \ + --arg total "$TOTAL" \ + --arg pass "$PASS" \ + --arg skip "$SKIP" \ + --arg fail "$FAIL" \ + --arg xpass "$XPASS" \ + --arg error "$ERROR" \ + '{total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }' > gnu-result.json else echo "::error ::Failed to get summary of test results" fi @@ -134,3 +143,8 @@ jobs: with: name: test-report path: gnu/tests/**/*.log + + - uses: actions/upload-artifact@v2 + with: + name: gnu-result + path: gnu-result.json From 8032c6d750f4a97b0d5880fc0180554f10360d8f Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 19 May 2021 01:04:24 +0200 Subject: [PATCH 322/399] fix clippy warnings --- src/uucore/src/lib/features/fsext.rs | 8 ++++---- tests/by-util/test_basename.rs | 2 ++ tests/by-util/test_relpath.rs | 6 +++--- tests/by-util/test_uname.rs | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 887c31e01..19c634b0b 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -276,7 +276,7 @@ impl MountInfo { GetVolumeInformationW( String2LPWSTR!(mount_root), ptr::null_mut(), - 0 as DWORD, + 0, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), @@ -510,12 +510,12 @@ impl FsUsage { // Total number of free blocks. bfree: number_of_free_clusters as u64, // Total number of free blocks available to non-privileged processes. - bavail: 0 as u64, + bavail: 0, bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0, // Total number of file nodes (inodes) on the file system. - files: 0 as u64, // Not available on windows + files: 0, // Not available on windows // Total number of free file nodes (inodes). - ffree: 4096 as u64, // Meaningless on Windows + ffree: 4096, // Meaningless on Windows } } } diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 2a40ba4b9..baf15f78a 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -1,4 +1,5 @@ use crate::common::util::*; +#[cfg(any(unix, target_os = "redox"))] use std::ffi::OsStr; #[test] @@ -123,6 +124,7 @@ fn test_too_many_args_output() { ); } +#[cfg(any(unix, target_os = "redox"))] fn test_invalid_utf8_args(os_str: &OsStr) { let test_vec = vec![os_str.to_os_string()]; new_ucmd!().args(&test_vec).succeeds().stdout_is("fo�o\n"); diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs index 5094d25a8..70d9f2a5d 100644 --- a/tests/by-util/test_relpath.rs +++ b/tests/by-util/test_relpath.rs @@ -155,7 +155,7 @@ fn test_relpath_no_from_with_d() { at.mkdir_all(to); // d is part of subpath -> expect relative path - let mut result_stdout = scene + let _result_stdout = scene .ucmd() .arg(to) .arg(&format!("-d{}", pwd)) @@ -163,10 +163,10 @@ fn test_relpath_no_from_with_d() { .stdout_move_str(); // relax rules for windows test environment #[cfg(not(windows))] - assert!(Path::new(&result_stdout).is_relative()); + assert!(Path::new(&_result_stdout).is_relative()); // d is not part of subpath -> expect absolut path - result_stdout = scene + let result_stdout = scene .ucmd() .arg(to) .arg("-dnon_existing") diff --git a/tests/by-util/test_uname.rs b/tests/by-util/test_uname.rs index da901d985..d878ed7ac 100644 --- a/tests/by-util/test_uname.rs +++ b/tests/by-util/test_uname.rs @@ -43,5 +43,5 @@ fn test_uname_kernel() { } #[cfg(not(target_os = "linux"))] - let result = ucmd.arg("-o").succeeds(); + ucmd.arg("-o").succeeds(); } From 9167a4128da23f14831819bd2b6d89f624ab9cfb Mon Sep 17 00:00:00 2001 From: Chad Brewbaker Date: Wed, 19 May 2021 04:06:46 -0500 Subject: [PATCH 323/399] Update test_cat.rs Refactored to constants --- tests/by-util/test_cat.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 997a7964c..4bb673b95 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -348,12 +348,12 @@ fn test_squeeze_blank_before_numbering() { fn test_dev_random() { let mut buf = [0; 2048]; #[cfg(target_os = "linux")] - fn rand_gen() -> &'static str { "/dev/urandom"} + const DEV_RANDOM: &str = "/dev/urandom"; #[cfg(not(target_os = "linux"))] - fn rand_gen() -> &'static str { "/dev/random"} + const DEV_RANDOM: &str = "/dev/random"; - let mut proc = new_ucmd!().args(&[rand_gen()]).run_no_wait(); + let mut proc = new_ucmd!().args(&[DEV_RANDOM]).run_no_wait(); let mut proc_stdout = proc.stdout.take().unwrap(); proc_stdout.read_exact(&mut buf).unwrap(); From 0c6a84831452b4c00216a498be053cda0ecc3912 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 19 May 2021 20:33:28 +0200 Subject: [PATCH 324/399] gnu results: also add the date (#2236) --- .github/workflows/GNU.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 3fd39aa15..213137acc 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -128,13 +128,14 @@ jobs: ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1) echo "::warning ::GNU testsuite = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" jq -n \ + --arg date "$(date --rfc-email)" \ --arg total "$TOTAL" \ --arg pass "$PASS" \ --arg skip "$SKIP" \ --arg fail "$FAIL" \ --arg xpass "$XPASS" \ --arg error "$ERROR" \ - '{total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }' > gnu-result.json + '{($date): { total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json else echo "::error ::Failed to get summary of test results" fi From 63b496eaa8a87a696fd9ea21eb3bffc86fe0a971 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Wed, 19 May 2021 22:23:28 -0400 Subject: [PATCH 325/399] truncate: refactor parse_size() function Change the interface provided by the `parse_size()` function to reduce its responsibilities to just a single task: parsing a number of bytes from a string of the form '123KB', etc. Previously, the function was also responsible for deciding which mode truncate would operate in. Furthermore, this commit simplifies the code for parsing the number and unit to be less verbose and use less mutable state. Finally, this commit adds some unit tests for the `parse_size()` function. --- src/uu/truncate/src/truncate.rs | 171 +++++++++++++++++++++----------- tests/by-util/test_truncate.rs | 10 ++ 2 files changed, 122 insertions(+), 59 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 91f705bd1..3190e6ad4 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -133,7 +133,35 @@ fn truncate( filenames: Vec, ) { let (modsize, mode) = match size { - Some(size_string) => parse_size(&size_string), + Some(size_string) => { + // Trim any whitespace. + let size_string = size_string.trim(); + + // Get the modifier character from the size string, if any. For + // example, if the argument is "+123", then the modifier is '+'. + let c = size_string.chars().next().unwrap(); + + let mode = match c { + '+' => TruncateMode::Extend, + '-' => TruncateMode::Reduce, + '<' => TruncateMode::AtMost, + '>' => TruncateMode::AtLeast, + '/' => TruncateMode::RoundDown, + '*' => TruncateMode::RoundUp, + _ => TruncateMode::Absolute, /* assume that the size is just a number */ + }; + + // If there was a modifier character, strip it. + let size_string = match mode { + TruncateMode::Absolute => size_string, + _ => &size_string[1..], + }; + let num_bytes = match parse_size(size_string) { + Ok(b) => b, + Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + }; + (num_bytes, mode) + } None => (0, TruncateMode::Reference), }; @@ -208,64 +236,89 @@ fn truncate( } } -fn parse_size(size: &str) -> (u64, TruncateMode) { - let clean_size = size.replace(" ", ""); - let mode = match clean_size.chars().next().unwrap() { - '+' => TruncateMode::Extend, - '-' => TruncateMode::Reduce, - '<' => TruncateMode::AtMost, - '>' => TruncateMode::AtLeast, - '/' => TruncateMode::RoundDown, - '*' => TruncateMode::RoundUp, - _ => TruncateMode::Absolute, /* assume that the size is just a number */ +/// Parse a size string into a number of bytes. +/// +/// A size string comprises an integer and an optional unit. The unit +/// may be K, M, G, T, P, E, Z, or Y (powers of 1024) or KB, MB, +/// etc. (powers of 1000). +/// +/// # Errors +/// +/// This function returns an error if the string does not begin with a +/// numeral, or if the unit is not one of the supported units described +/// in the preceding section. +/// +/// # Examples +/// +/// ```rust,ignore +/// assert_eq!(parse_size("123").unwrap(), 123); +/// assert_eq!(parse_size("123K").unwrap(), 123 * 1024); +/// assert_eq!(parse_size("123KB").unwrap(), 123 * 1000); +/// ``` +fn parse_size(size: &str) -> Result { + // Get the numeric part of the size argument. For example, if the + // argument is "123K", then the numeric part is "123". + let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect(); + let number: u64 = match numeric_string.parse() { + Ok(n) => n, + Err(_) => return Err(()), }; - let bytes = { - let mut slice = if mode == TruncateMode::Absolute { - &clean_size - } else { - &clean_size[1..] - }; - if slice.chars().last().unwrap().is_alphabetic() { - slice = &slice[..slice.len() - 1]; - if !slice.is_empty() && slice.chars().last().unwrap().is_alphabetic() { - slice = &slice[..slice.len() - 1]; - } - } - slice - } - .to_owned(); - let mut number: u64 = match bytes.parse() { - Ok(num) => num, - Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e), + + // Get the alphabetic units part of the size argument and compute + // the factor it represents. For example, if the argument is "123K", + // then the unit part is "K" and the factor is 1024. This may be the + // empty string, in which case, the factor is 1. + let n = numeric_string.len(); + let (base, exponent): (u64, u32) = match &size[n..] { + "" => (1, 0), + "K" | "k" => (1024, 1), + "M" | "m" => (1024, 2), + "G" | "g" => (1024, 3), + "T" | "t" => (1024, 4), + "P" | "p" => (1024, 5), + "E" | "e" => (1024, 6), + "Z" | "z" => (1024, 7), + "Y" | "y" => (1024, 8), + "KB" | "kB" => (1000, 1), + "MB" | "mB" => (1000, 2), + "GB" | "gB" => (1000, 3), + "TB" | "tB" => (1000, 4), + "PB" | "pB" => (1000, 5), + "EB" | "eB" => (1000, 6), + "ZB" | "zB" => (1000, 7), + "YB" | "yB" => (1000, 8), + _ => return Err(()), }; - if clean_size.chars().last().unwrap().is_alphabetic() { - number *= match clean_size.chars().last().unwrap().to_ascii_uppercase() { - 'B' => match clean_size - .chars() - .nth(clean_size.len() - 2) - .unwrap() - .to_ascii_uppercase() - { - 'K' => 1000u64, - 'M' => 1000u64.pow(2), - 'G' => 1000u64.pow(3), - 'T' => 1000u64.pow(4), - 'P' => 1000u64.pow(5), - 'E' => 1000u64.pow(6), - 'Z' => 1000u64.pow(7), - 'Y' => 1000u64.pow(8), - letter => crash!(1, "'{}B' is not a valid suffix.", letter), - }, - 'K' => 1024u64, - 'M' => 1024u64.pow(2), - 'G' => 1024u64.pow(3), - 'T' => 1024u64.pow(4), - 'P' => 1024u64.pow(5), - 'E' => 1024u64.pow(6), - 'Z' => 1024u64.pow(7), - 'Y' => 1024u64.pow(8), - letter => crash!(1, "'{}' is not a valid suffix.", letter), - }; - } - (number, mode) + let factor = base.pow(exponent); + Ok(number * factor) +} + +#[cfg(test)] +mod tests { + use crate::parse_size; + + #[test] + fn test_parse_size_zero() { + assert_eq!(parse_size("0").unwrap(), 0); + assert_eq!(parse_size("0K").unwrap(), 0); + assert_eq!(parse_size("0KB").unwrap(), 0); + } + + #[test] + fn test_parse_size_without_factor() { + assert_eq!(parse_size("123").unwrap(), 123); + } + + #[test] + fn test_parse_size_kilobytes() { + assert_eq!(parse_size("123K").unwrap(), 123 * 1024); + assert_eq!(parse_size("123KB").unwrap(), 123 * 1000); + } + + #[test] + fn test_parse_size_megabytes() { + assert_eq!(parse_size("123").unwrap(), 123); + assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024); + assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000); + } } diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 8f88f4c74..b1f806f82 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -235,3 +235,13 @@ fn test_size_and_reference() { actual ); } + +#[test] +fn test_invalid_numbers() { + // TODO For compatibility with GNU, `truncate -s 0X` should cause + // the same error as `truncate -s 0X file`, but currently it returns + // a different error. + new_ucmd!().args(&["-s", "0X", "file"]).fails().stderr_contains("Invalid number: ‘0X’"); + new_ucmd!().args(&["-s", "0XB", "file"]).fails().stderr_contains("Invalid number: ‘0XB’"); + new_ucmd!().args(&["-s", "0B", "file"]).fails().stderr_contains("Invalid number: ‘0B’"); +} From 998b3c11d3af933afdd714f1678e2080740cbb07 Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 20 May 2021 17:00:49 +0200 Subject: [PATCH 326/399] factor: Make random Factors instance generatable for tests --- src/uu/factor/src/factor.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index f53abd772..b279de7fc 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -239,9 +239,13 @@ mod tests { } #[cfg(test)] -impl quickcheck::Arbitrary for Factors { - fn arbitrary(gen: &mut G) -> Self { - use rand::Rng; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; +#[cfg(test)] +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Factors { let mut f = Factors::one(); let mut g = 1u64; let mut n = u64::MAX; @@ -252,7 +256,7 @@ impl quickcheck::Arbitrary for Factors { // See Generating Random Factored Numbers, Easily, J. Cryptology (2003) 'attempt: loop { while n > 1 { - n = gen.gen_range(1, n); + n = rng.gen_range(1, n); if miller_rabin::is_prime(n) { if let Some(h) = g.checked_mul(n) { f.push(n); @@ -269,6 +273,13 @@ impl quickcheck::Arbitrary for Factors { } } +#[cfg(test)] +impl quickcheck::Arbitrary for Factors { + fn arbitrary(g: &mut G) -> Self { + g.gen() + } +} + #[cfg(test)] impl std::ops::BitXor for Factors { type Output = Self; From a0a103b15e52b20f63d36aee93083c81b007326d Mon Sep 17 00:00:00 2001 From: nicoo Date: Thu, 20 May 2021 17:01:33 +0200 Subject: [PATCH 327/399] factor::table::chunked: Add test (equivalent to the single-number version) --- src/uu/factor/src/table.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index db2698e4b..518d4f241 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -72,3 +72,30 @@ pub fn factor_chunk(n_s: &mut [u64; CHUNK_SIZE], f_s: &mut [Factors; CHUNK_SIZE] } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Factors; + use quickcheck::quickcheck; + use rand::{rngs::SmallRng, Rng, SeedableRng}; + + quickcheck! { + fn chunk_vs_iter(seed: u64) -> () { + let mut rng = SmallRng::seed_from_u64(seed); + let mut n_c: [u64; CHUNK_SIZE] = rng.gen(); + let mut f_c: [Factors; CHUNK_SIZE] = rng.gen(); + + let mut n_i = n_c.clone(); + let mut f_i = f_c.clone(); + for (n, f) in n_i.iter_mut().zip(f_i.iter_mut()) { + factor(n, f); + } + + factor_chunk(&mut n_c, &mut f_c); + + assert_eq!(n_i, n_c); + assert_eq!(f_i, f_c); + } + } +} From d30393089f4d9f02f492933fd8bdd283724e401f Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 20 May 2021 20:57:28 -0400 Subject: [PATCH 328/399] truncate: rustfmt test_truncate.rs file --- tests/by-util/test_truncate.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index b1f806f82..fc302dcba 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -241,7 +241,16 @@ fn test_invalid_numbers() { // TODO For compatibility with GNU, `truncate -s 0X` should cause // the same error as `truncate -s 0X file`, but currently it returns // a different error. - new_ucmd!().args(&["-s", "0X", "file"]).fails().stderr_contains("Invalid number: ‘0X’"); - new_ucmd!().args(&["-s", "0XB", "file"]).fails().stderr_contains("Invalid number: ‘0XB’"); - new_ucmd!().args(&["-s", "0B", "file"]).fails().stderr_contains("Invalid number: ‘0B’"); + new_ucmd!() + .args(&["-s", "0X", "file"]) + .fails() + .stderr_contains("Invalid number: ‘0X’"); + new_ucmd!() + .args(&["-s", "0XB", "file"]) + .fails() + .stderr_contains("Invalid number: ‘0XB’"); + new_ucmd!() + .args(&["-s", "0B", "file"]) + .fails() + .stderr_contains("Invalid number: ‘0B’"); } From fc29846b45e20956ed3ebb37b67aa172290767e2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 20 May 2021 20:55:11 -0400 Subject: [PATCH 329/399] truncate: fix error message for file not found Change the error message for when the reference file (the `-r` argument) is not found to match GNU coreutils. This commit also eliminates a redundant call to `File::open`; the file need not be opened because the size in bytes can be read from the result of `std::fs::metadata()`. --- src/uu/truncate/src/truncate.rs | 14 ++++++++------ tests/by-util/test_truncate.rs | 8 ++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 3190e6ad4..c74171373 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -11,7 +11,8 @@ extern crate uucore; use clap::{App, Arg}; -use std::fs::{metadata, File, OpenOptions}; +use std::fs::{metadata, OpenOptions}; +use std::io::ErrorKind; use std::path::Path; #[derive(Eq, PartialEq)] @@ -174,13 +175,14 @@ fn truncate( TruncateMode::Reduce => (), _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"), }; - let _ = match File::open(Path::new(rfilename)) { - Ok(m) => m, - Err(f) => crash!(1, "{}", f.to_string()), - }; match metadata(rfilename) { Ok(meta) => meta.len(), - Err(f) => crash!(1, "{}", f.to_string()), + Err(f) => match f.kind() { + ErrorKind::NotFound => { + crash!(1, "cannot stat '{}': No such file or directory", rfilename) + } + _ => crash!(1, "{}", f.to_string()), + }, } } None => 0, diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index b1f806f82..e14836fcf 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -245,3 +245,11 @@ fn test_invalid_numbers() { new_ucmd!().args(&["-s", "0XB", "file"]).fails().stderr_contains("Invalid number: ‘0XB’"); new_ucmd!().args(&["-s", "0B", "file"]).fails().stderr_contains("Invalid number: ‘0B’"); } + +#[test] +fn test_reference_file_not_found() { + new_ucmd!() + .args(&["-r", "a", "b"]) + .fails() + .stderr_contains("cannot stat 'a': No such file or directory"); +} From 17b95246cdcb4df0807af0aebcac19590837df57 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 20 May 2021 21:24:43 -0400 Subject: [PATCH 330/399] truncate: use min() and max() instead of if stmts --- src/uu/truncate/src/truncate.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 3190e6ad4..3fb1f2663 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -209,20 +209,8 @@ fn truncate( TruncateMode::Reference => fsize, TruncateMode::Extend => fsize + modsize, TruncateMode::Reduce => fsize - modsize, - TruncateMode::AtMost => { - if fsize > modsize { - modsize - } else { - fsize - } - } - TruncateMode::AtLeast => { - if fsize < modsize { - modsize - } else { - fsize - } - } + TruncateMode::AtMost => fsize.min(modsize), + TruncateMode::AtLeast => fsize.max(modsize), TruncateMode::RoundDown => fsize - fsize % modsize, TruncateMode::RoundUp => fsize + fsize % modsize, }; From a23555e857e89f1cd7a9c08429886b102e95f01a Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 20 May 2021 23:19:58 -0400 Subject: [PATCH 331/399] truncate: fix character used to indicate round up Fix a bug in which the incorrect character was being used to indicate "round up to the nearest multiple" mode. The character was "*" but it should be "%". This commit corrects that. --- src/uu/truncate/src/truncate.rs | 2 +- tests/by-util/test_truncate.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 3190e6ad4..7537987e0 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -147,7 +147,7 @@ fn truncate( '<' => TruncateMode::AtMost, '>' => TruncateMode::AtLeast, '/' => TruncateMode::RoundDown, - '*' => TruncateMode::RoundUp, + '%' => TruncateMode::RoundUp, _ => TruncateMode::Absolute, /* assume that the size is just a number */ }; diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index b1f806f82..7fa3df98c 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -206,7 +206,7 @@ fn test_round_up() { 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(); + ucmd.args(&["--size", "%4", TFILE2]).succeeds(); file.seek(SeekFrom::End(0)).unwrap(); let actual = file.seek(SeekFrom::Current(0)).unwrap(); assert!( From 007e0a4e7f915989298278f0a777a63ab2af1185 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Thu, 20 May 2021 23:11:40 +0200 Subject: [PATCH 332/399] who/stat/pinky: adjust tests to be compatible with running on macOS A lot of tests depend on GNU's coreutils to be installed in order to obtain reference values during testing. In these cases testing is limited to `target_os = linux`. This PR installs GNU's coreutils on "github actions" and adjusts the tests for `who`, `stat` and `pinky` in order to be compatible with macOS. * `brew install coreutils` (prefix is 'g', e.g. `gwho`, `gstat`, etc. * switch paths for testing to something that's available on both OSs, e.g. `/boot` -> `/bin`, etc. * switch paths for testing to the macOS equivalent, e.g. `/dev/pts/ptmx` -> `/dev/ptmx`, etc. * exclude paths when no equivalent is available, e.g. `/proc`, `/etc/fstab`, etc. * refactor tests to make better use of the testing API * fix a warning in utmpx.rs to print to stderr instead of stdout * fix long_usage text in `who` * fix minor output formatting in `stat` * the `expected_result` function should be refactored to reduce duplicate code * more tests should be adjusted to not only run on `target_os = linux` --- src/uu/stat/src/stat.rs | 2 +- src/uu/who/src/who.rs | 7 +- src/uucore/src/lib/features/utmpx.rs | 4 +- tests/by-util/test_pinky.rs | 95 +++++++-------- tests/by-util/test_stat.rs | 127 +++++++++++--------- tests/by-util/test_who.rs | 168 +++++++++++++-------------- 6 files changed, 207 insertions(+), 196 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 8b148d39d..5bb0e5f12 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -657,7 +657,7 @@ impl Stater { dst.to_string_lossy() ); } else { - arg = format!("`{}'", file); + arg = file.to_string(); } otype = OutputType::Str; } diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index aef23b3a2..1ae4f1c8d 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -46,9 +46,10 @@ fn get_usage() -> String { } fn get_long_usage() -> String { - String::from( - "If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common.\n\ -If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.", + format!( + "If FILE is not specified, use {}. /var/log/wtmp as FILE is common.\n\ + If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.", + utmpx::DEFAULT_FILE, ) } diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 96db33c35..826831ba6 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -54,6 +54,8 @@ pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { 0 } +pub use crate::*; // import macros from `../../macros.rs` + // In case the c_char array doesn't end with NULL macro_rules! chars2string { ($arr:expr) => { @@ -240,7 +242,7 @@ impl UtmpxIter { utmpxname(cstr.as_ptr()) }; if res != 0 { - println!("Warning: {}", IOError::last_os_error()); + show_warning!("utmpxname: {}", IOError::last_os_error()); } unsafe { setutxent(); diff --git a/tests/by-util/test_pinky.rs b/tests/by-util/test_pinky.rs index 904a05f93..ccabb7345 100644 --- a/tests/by-util/test_pinky.rs +++ b/tests/by-util/test_pinky.rs @@ -20,42 +20,37 @@ fn test_long_format() { let ulogin = "root"; let pw: Passwd = Passwd::locate(ulogin).unwrap(); let real_name = pw.user_info().replace("&", &pw.name().capitalize()); - new_ucmd!().arg("-l").arg(ulogin).run().stdout_is(format!( - "Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n", - ulogin, - real_name, - pw.user_dir(), - pw.user_shell() - )); + new_ucmd!() + .arg("-l") + .arg(ulogin) + .succeeds() + .stdout_is(format!( + "Login name: {:<28}In real life: {}\nDirectory: {:<29}Shell: {}\n\n", + ulogin, + real_name, + pw.user_dir(), + pw.user_shell() + )); - new_ucmd!().arg("-lb").arg(ulogin).run().stdout_is(format!( - "Login name: {:<28}In real life: {1}\n\n", - ulogin, real_name - )); + new_ucmd!() + .arg("-lb") + .arg(ulogin) + .succeeds() + .stdout_is(format!( + "Login name: {:<28}In real life: {1}\n\n", + ulogin, real_name + )); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_long_format_multiple_users() { - let scene = TestScenario::new(util_name!()); + let args = ["-l", "root", "root", "root"]; - let expected = scene - .cmd_keepenv(util_name!()) - .env("LANGUAGE", "C") - .arg("-l") - .arg("root") - .arg("root") - .arg("root") - .succeeds(); - - scene - .ucmd() - .arg("-l") - .arg("root") - .arg("root") - .arg("root") + new_ucmd!() + .args(&args) .succeeds() - .stdout_is(expected.stdout_str()); + .stdout_is(expected_result(&args)); } #[test] @@ -64,63 +59,53 @@ fn test_long_format_wo_user() { new_ucmd!().arg("-l").fails().code_is(1); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_short_format_i() { // allow whitespace variation // * minor whitespace differences occur between platform built-in outputs; specifically, the number of trailing TABs may be variant let args = ["-i"]; - let actual = TestScenario::new(util_name!()) - .ucmd() - .args(&args) - .succeeds() - .stdout_move_str(); + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_short_format_q() { // allow whitespace variation // * minor whitespace differences occur between platform built-in outputs; specifically, the number of trailing TABs may be variant let args = ["-q"]; - let actual = TestScenario::new(util_name!()) - .ucmd() - .args(&args) - .succeeds() - .stdout_move_str(); + let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_no_flag() { - let scene = TestScenario::new(util_name!()); - - let actual = scene.ucmd().succeeds().stdout_move_str(); - let expect = scene - .cmd_keepenv(util_name!()) - .env("LANGUAGE", "C") - .succeeds() - .stdout_move_str(); - + let actual = new_ucmd!().succeeds().stdout_move_str(); + let expect = expected_result(&[]); let v_actual: Vec<&str> = actual.split_whitespace().collect(); let v_expect: Vec<&str> = expect.split_whitespace().collect(); assert_eq!(v_actual, v_expect); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] fn expected_result(args: &[&str]) -> String { - TestScenario::new(util_name!()) - .cmd_keepenv(util_name!()) + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + TestScenario::new(&util_name) + .cmd_keepenv(util_name) .env("LANGUAGE", "C") .args(args) - .run() + .succeeds() .stdout_move_str() } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 308dcb9f5..44bce9cd8 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -96,10 +96,10 @@ fn test_invalid_option() { new_ucmd!().arg("-w").arg("-q").arg("/").fails(); } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_vendor = "apple"))] const NORMAL_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %x %X %y %Y %z %Z"; // avoid "%w %W" (birth/creation) due to `stat` limitations and linux kernel & rust version capability variations -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux"))] const DEV_FMTSTR: &'static str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (%t/%T) %u %U %w %W %x %X %y %Y %z %Z"; #[cfg(target_os = "linux")] @@ -125,8 +125,8 @@ fn test_fs_format() { .stdout_is(expected_result(&args)); } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] fn test_terse_normal_format() { // note: contains birth/creation date which increases test fragility // * results may vary due to built-in `stat` limitations as well as linux kernel and rust version capability variations @@ -156,10 +156,10 @@ fn test_terse_normal_format() { ); } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] fn test_format_created_time() { - let args = ["-c", "%w", "/boot"]; + let args = ["-c", "%w", "/bin"]; let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); println!("actual: {:?}", actual); @@ -180,10 +180,10 @@ fn test_format_created_time() { ); } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] fn test_format_created_seconds() { - let args = ["-c", "%W", "/boot"]; + let args = ["-c", "%W", "/bin"]; let actual = new_ucmd!().args(&args).succeeds().stdout_move_str(); let expect = expected_result(&args); println!("actual: {:?}", actual); @@ -204,79 +204,97 @@ fn test_format_created_seconds() { ); } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] fn test_normal_format() { - let args = ["-c", NORMAL_FMTSTR, "/boot"]; + let args = ["-c", NORMAL_FMTSTR, "/bin"]; new_ucmd!() .args(&args) - .run() + .succeeds() .stdout_is(expected_result(&args)); } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] -fn test_follow_symlink() { - let args = ["-L", "-c", DEV_FMTSTR, "/dev/cdrom"]; - new_ucmd!() - .args(&args) - .run() - .stdout_is(expected_result(&args)); +fn test_symlinks() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let mut tested: bool = false; + // arbitrarily chosen symlinks with hope that the CI environment provides at least one of them + for file in vec![ + "/bin/sh", + "/bin/sudoedit", + "/usr/bin/ex", + "/etc/localtime", + "/etc/aliases", + ] { + if at.file_exists(file) && at.is_symlink(file) { + tested = true; + let args = ["-c", NORMAL_FMTSTR, file]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_is(expected_result(&args)); + // -L, --dereference follow links + let args = ["-L", "-c", NORMAL_FMTSTR, file]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_is(expected_result(&args)); + } + } + if !tested { + panic!("No symlink found to test in this environment"); + } } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] -fn test_symlink() { - let args = ["-c", DEV_FMTSTR, "/dev/cdrom"]; - new_ucmd!() - .args(&args) - .run() - .stdout_is(expected_result(&args)); -} - -#[test] -#[cfg(target_os = "linux")] fn test_char() { - let args = ["-c", DEV_FMTSTR, "/dev/pts/ptmx"]; + // TODO: "(%t) (%x) (%w)" deviate from GNU stat for `character special file` on macOS + // Diff < left / right > : + // <"(f0000) (2021-05-20 23:08:03.442555000 +0200) (1970-01-01 01:00:00.000000000 +0100)\n" + // >"(f) (2021-05-20 23:08:03.455598000 +0200) (-)\n" + let args = [ + "-c", + #[cfg(target_os = "linux")] + DEV_FMTSTR, + #[cfg(target_os = "linux")] + "/dev/pts/ptmx", + #[cfg(any(target_vendor = "apple"))] + "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s (/%T) %u %U %W %X %y %Y %z %Z", + #[cfg(any(target_vendor = "apple"))] + "/dev/ptmx", + ]; new_ucmd!() .args(&args) - .run() + .succeeds() .stdout_is(expected_result(&args)); } +#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[test] -#[cfg(target_os = "linux")] fn test_multi_files() { let args = [ "-c", NORMAL_FMTSTR, "/dev", "/usr/lib", + #[cfg(target_os = "linux")] "/etc/fstab", "/var", ]; new_ucmd!() .args(&args) - .run() + .succeeds() .stdout_is(expected_result(&args)); } -#[cfg(any(target_os = "linux", target_os = "freebsd", target_vendor = "apple"))] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] -fn test_one_file() { - let (at, mut ucmd) = at_and_ucmd!(); - let file = "TEST_FILE.mp4"; - at.touch(file); - - ucmd.arg(file) - .succeeds() - .stdout_contains(format!("File: `{}'", file)) - .stdout_contains(format!("Size: 0")) - .stdout_contains(format!("Access: (0644/-rw-r--r--)")); -} - -#[test] -#[cfg(target_os = "linux")] fn test_printf() { let args = [ "--printf=123%-# 15q\\r\\\"\\\\\\a\\b\\e\\f\\v%+020.23m\\x12\\167\\132\\112\\n", @@ -284,16 +302,21 @@ fn test_printf() { ]; new_ucmd!() .args(&args) - .run() + .succeeds() .stdout_is(expected_result(&args)); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] fn expected_result(args: &[&str]) -> String { - TestScenario::new(util_name!()) - .cmd_keepenv(util_name!()) + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + TestScenario::new(&util_name) + .cmd_keepenv(util_name) .env("LANGUAGE", "C") .args(args) - .run() + .succeeds() .stdout_move_str() } diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index a5637f23a..725ec0b1e 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -1,28 +1,28 @@ use crate::common::util::*; -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_count() { for opt in vec!["-q", "--count"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_boot() { for opt in vec!["-b", "--boot"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_heading() { for opt in vec!["-H", "--heading"] { @@ -30,7 +30,7 @@ fn test_heading() { // * 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).succeeds().stdout_move_str(); - let expect = expected_result(opt); + let expect = expected_result(&[opt]); println!("actual: {:?}", actual); println!("expect: {:?}", expect); let v_actual: Vec<&str> = actual.split_whitespace().collect(); @@ -39,205 +39,205 @@ fn test_heading() { } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_short() { for opt in vec!["-s", "--short"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_login() { for opt in vec!["-l", "--login"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_m() { for opt in vec!["-m"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_process() { for opt in vec!["-p", "--process"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_runlevel() { for opt in vec!["-r", "--runlevel"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_time() { for opt in vec!["-t", "--time"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_mesg() { - for opt in vec!["-w", "-T", "--users", "--message", "--writable"] { + // -T, -w, --mesg + // add user's message status as +, - or ? + // --message + // same as -T + // --writable + // same as -T + for opt in vec!["-T", "-w", "--mesg", "--message", "--writable"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] #[test] fn test_arg1_arg2() { - let scene = TestScenario::new(util_name!()); + let args = ["am", "i"]; - let expected = scene - .cmd_keepenv(util_name!()) - .env("LANGUAGE", "C") - .arg("am") - .arg("i") - .succeeds(); - - scene - .ucmd() - .arg("am") - .arg("i") + new_ucmd!() + .args(&args) .succeeds() - .stdout_is(expected.stdout_str()); + .stdout_is(expected_result(&args)); } #[test] fn test_too_many_args() { - let expected = + const EXPECTED: &str = "error: The value 'u' was provided to '...', but it wasn't expecting any more values"; - new_ucmd!() - .arg("am") - .arg("i") - .arg("u") - .fails() - .stderr_contains(expected); + let args = ["am", "i", "u"]; + new_ucmd!().args(&args).fails().stderr_contains(EXPECTED); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_users() { for opt in vec!["-u", "--users"] { - new_ucmd!() - .arg(opt) - .succeeds() - .stdout_is(expected_result(opt)); + let actual = new_ucmd!().arg(opt).succeeds().stdout_move_str(); + let expect = expected_result(&[opt]); + println!("actual: {:?}", actual); + println!("expect: {:?}", expect); + + let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); + let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); + + // TODO: `--users` differs from GNU's output on manOS running in CI + // Diff < left / right > : + // <"runner console 2021-05-20 22:03 00:08 196\n" + // >"runner console 2021-05-20 22:03 old 196\n" + if is_ci() && cfg!(target_os = "macos") { + v_actual.remove(4); + v_expect.remove(4); + } + + assert_eq!(v_actual, v_expect); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_lookup() { for opt in vec!["--lookup"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_dead() { for opt in vec!["-d", "--dead"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_all_separately() { + if is_ci() && cfg!(target_os = "macos") { + // TODO: fix `-u`, see: test_users + return; + } + // -a, --all same as -b -d --login -p -r -t -T -u + let args = ["-b", "-d", "--login", "-p", "-r", "-t", "-T", "-u"]; let scene = TestScenario::new(util_name!()); - - let expected = scene - .cmd_keepenv(util_name!()) - .env("LANGUAGE", "C") - .arg("-b") - .arg("-d") - .arg("--login") - .arg("-p") - .arg("-r") - .arg("-t") - .arg("-T") - .arg("-u") - .succeeds(); - scene .ucmd() - .arg("-b") - .arg("-d") - .arg("--login") - .arg("-p") - .arg("-r") - .arg("-t") - .arg("-T") - .arg("-u") + .args(&args) .succeeds() - .stdout_is(expected.stdout_str()); - + .stdout_is(expected_result(&args)); scene .ucmd() .arg("--all") .succeeds() - .stdout_is(expected.stdout_str()); + .stdout_is(expected_result(&args)); } -#[cfg(target_os = "linux")] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_all() { + if is_ci() && cfg!(target_os = "macos") { + // TODO: fix `-u`, see: test_users + return; + } + for opt in vec!["-a", "--all"] { new_ucmd!() .arg(opt) .succeeds() - .stdout_is(expected_result(opt)); + .stdout_is(expected_result(&[opt])); } } -#[cfg(target_os = "linux")] -fn expected_result(arg: &str) -> String { - TestScenario::new(util_name!()) - .cmd_keepenv(util_name!()) +#[cfg(any(target_vendor = "apple", target_os = "linux"))] +fn expected_result(args: &[&str]) -> String { + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); + + TestScenario::new(&util_name) + .cmd_keepenv(util_name) .env("LANGUAGE", "C") - .args(&[arg]) + .args(args) .succeeds() .stdout_move_str() } From 6ed080cf97d7bece6163a190bc07b0179d90f5ac Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 21 May 2021 12:39:48 +0200 Subject: [PATCH 333/399] CICD: install GNU coreutils on macOS (Code Coverage) --- .github/workflows/CICD.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index a42d2f335..bb29355cf 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -489,6 +489,13 @@ jobs: - { os: windows-latest , features: windows } steps: - uses: actions/checkout@v1 + - name: Install/setup prerequisites + shell: bash + run: | + ## install/setup prerequisites + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for testing + esac # - name: Reattach HEAD ## may be needed for accurate code coverage info # run: git checkout ${{ github.head_ref }} - name: Initialize workflow variables From 0dafbfccca27154b6c831fed0338c1c54da63e3d Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 21 May 2021 13:30:24 +0200 Subject: [PATCH 334/399] CI-Trigger From 73b47b8c765284f7cc1586a46358e86eb56ae22a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 21 May 2021 13:27:35 +0200 Subject: [PATCH 335/399] gnu/ci: install the dep into a separate task --- .github/workflows/GNU.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index a72cb0cfc..57730aee7 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -32,11 +32,14 @@ jobs: default: true profile: minimal # minimal component installation (ie, no documentation) components: rustfmt - - name: Build binaries + - name: Install deps shell: bash run: | sudo apt-get update sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq + - name: Build binaries + shell: bash + run: | pushd uutils make PROFILE=release BUILDDIR="$PWD/target/release/" From 414c92eed79d5b8687db332e84c6d67f9b2b2593 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Fri, 21 May 2021 22:04:24 +0530 Subject: [PATCH 336/399] ls: Fix printing paths behavior For any commandline arguments, ls should print the argument as is (and not truncate to just the file name) For any other files it reaches (say through recursive exploration), ls should print just the filename (as path is printed once when we enter the directory) --- src/uu/ls/src/ls.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index c5389295b..d467d431a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1110,7 +1110,7 @@ struct PathData { md: OnceCell>, ft: OnceCell>, // Name of the file - will be empty for . or .. - file_name: String, + display_name: String, // PathBuf that all above data corresponds to p_buf: PathBuf, must_dereference: bool, @@ -1126,14 +1126,18 @@ impl PathData { ) -> Self { // We cannot use `Path::ends_with` or `Path::Components`, because they remove occurrences of '.' // For '..', the filename is None - let name = if let Some(name) = file_name { + let display_name = if let Some(name) = file_name { name } else { - p_buf - .file_name() - .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) - .to_string_lossy() - .into_owned() + let display_osstr = if command_line { + p_buf.as_os_str() + } else { + p_buf + .file_name() + .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) + }; + + display_osstr.to_string_lossy().into_owned() }; let must_dereference = match &config.dereference { Dereference::All => true, @@ -1159,7 +1163,7 @@ impl PathData { Self { md: OnceCell::new(), ft, - file_name: name, + display_name, p_buf, must_dereference, } @@ -1243,7 +1247,7 @@ fn sort_entries(entries: &mut Vec, config: &Config) { 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(|a, b| a.file_name.cmp(&b.file_name)), + Sort::Name => entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)), Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(&a.p_buf, &b.p_buf)), Sort::Extension => entries.sort_by(|a, b| { a.p_buf @@ -1719,7 +1723,7 @@ fn classify_file(path: &PathData) -> Option { } fn display_file_name(path: &PathData, config: &Config) -> Option { - let mut name = escape_name(&path.file_name, &config.quoting_style); + let mut name = escape_name(&path.display_name, &config.quoting_style); #[cfg(unix)] { From 31545258ac6b6ef11ef8c04f2564825e08f7ad50 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Fri, 21 May 2021 22:20:54 +0530 Subject: [PATCH 337/399] tests: Fix test_ls_path --- tests/by-util/test_ls.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 8d32172b0..fc4051039 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1967,8 +1967,6 @@ fn test_ls_sort_extension() { ); } -// This tests for the open issue described in #2223 -#[cfg_attr(not(feature = "test_unimplemented"), ignore)] #[test] fn test_ls_path() { let scene = TestScenario::new(util_name!()); @@ -1987,13 +1985,17 @@ fn test_ls_path() { scene.ucmd().arg(path).run().stdout_is(expected_stdout); let expected_stdout = &format!("./{}\n", path); - scene.ucmd().arg(path).run().stdout_is(expected_stdout); + scene + .ucmd() + .arg(format!("./{}", path)) + .run() + .stdout_is(expected_stdout); - let abs_path = format!("{}/{}\n", at.as_string(), path); - println!(":{}", abs_path); - scene.ucmd().arg(&abs_path).run().stdout_is(&abs_path); + let abs_path = format!("{}/{}", at.as_string(), path); + let expected_stdout = &format!("{}\n", abs_path); + scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout); - let expected_stdout = &format!("{}\n{}\n", file1, path); + let expected_stdout = &format!("{}\n{}\n", path, file1); scene .ucmd() .arg(file1) From e7da8058dc27dc106af66b206318faeec6cf9938 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 21 May 2021 23:00:13 +0200 Subject: [PATCH 338/399] sort: automatically fall back to extsort To make this work we make default sort a special case of external sort. External sorting uses auxiliary files for intermediate chunks. However, when we can keep our intermediate chunks in memory, we don't write them to the file system at all. Only when we notice that we can't keep them in memory they are written to the disk. Additionally, we don't allocate buffers with the capacity of their maximum size anymore. Instead, they start with a capacity of 8kb and are grown only when needed. This makes sorting smaller files about as fast as it was before (I'm seeing a regression of ~3%), and allows us to seamlessly continue with auxiliary files when needed. --- src/uu/sort/BENCHMARKING.md | 3 +- src/uu/sort/src/check.rs | 1 + src/uu/sort/src/chunks.rs | 39 ++++++++++++-- src/uu/sort/src/ext_sort.rs | 105 +++++++++++++++++++++++++----------- src/uu/sort/src/merge.rs | 1 + src/uu/sort/src/sort.rs | 68 ++++++----------------- tests/by-util/test_sort.rs | 33 ++++-------- 7 files changed, 138 insertions(+), 112 deletions(-) diff --git a/src/uu/sort/BENCHMARKING.md b/src/uu/sort/BENCHMARKING.md index 52866719d..fd728c41d 100644 --- a/src/uu/sort/BENCHMARKING.md +++ b/src/uu/sort/BENCHMARKING.md @@ -72,7 +72,8 @@ Run `cargo build --release` before benchmarking after you make a change! ## External sorting Try running commands with the `-S` option set to an amount of memory to be used, such as `1M`. Additionally, you could try sorting -huge files (ideally multiple Gigabytes) with `-S`. Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` +huge files (ideally multiple Gigabytes) with `-S` (or without `-S` to benchmark with our default value). +Creating such a large file can be achieved by running `cat shuffled_wordlist.txt | sort -R >> shuffled_wordlist.txt` multiple times (this will add the contents of `shuffled_wordlist.txt` to itself). Example: Run `hyperfine './target/release/coreutils sort shuffled_wordlist.txt -S 1M' 'sort shuffled_wordlist.txt -S 1M'` diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index fe815b624..01b5a25b5 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -87,6 +87,7 @@ fn reader( chunks::read( &mut sender, recycled_buffer, + None, &mut carry_over, &mut file, &mut iter::empty(), diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index c679980ec..7a7749003 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -52,13 +52,20 @@ impl Chunk { /// Read a chunk, parse lines and send them. /// -/// No empty chunk will be sent. +/// No empty chunk will be sent. If we reach the end of the input, sender_option +/// is set to None. If this function however does not set sender_option to None, +/// it is not guaranteed that there is still input left: If the input fits _exactly_ +/// into a buffer, we will only notice that there's nothing more to read at the next +/// invocation. /// /// # Arguments /// -/// * `sender_option`: The sender to send the lines to the sorter. If `None`, does nothing. +/// (see also `read_to_chunk` for a more detailed documentation) +/// +/// * `sender_option`: The sender to send the lines to the sorter. If `None`, this function does nothing. /// * `buffer`: The recycled buffer. All contents will be overwritten, but it must already be filled. /// (i.e. `buffer.len()` should be equal to `buffer.capacity()`) +/// * `max_buffer_size`: How big `buffer` can be. /// * `carry_over`: The bytes that must be carried over in between invocations. /// * `file`: The current file. /// * `next_files`: What `file` should be updated to next. @@ -69,6 +76,7 @@ impl Chunk { pub fn read( sender_option: &mut Option>, mut buffer: Vec, + max_buffer_size: Option, carry_over: &mut Vec, file: &mut Box, next_files: &mut impl Iterator>, @@ -82,8 +90,14 @@ pub fn read( buffer.resize(carry_over.len() + 10 * 1024, 0); } buffer[..carry_over.len()].copy_from_slice(&carry_over); - let (read, should_continue) = - read_to_buffer(file, next_files, &mut buffer, carry_over.len(), separator); + let (read, should_continue) = read_to_buffer( + file, + next_files, + &mut buffer, + max_buffer_size, + carry_over.len(), + separator, + ); carry_over.clear(); carry_over.extend_from_slice(&buffer[read..]); @@ -138,7 +152,8 @@ fn parse_lines<'a>( /// * `next_files`: When `file` reaches EOF, it is updated to `next_files.next()` if that is `Some`, /// and this function continues reading. /// * `buffer`: The buffer that is filled with bytes. Its contents will mostly be overwritten (see `start_offset` -/// as well). It will not be grown by default, unless that is necessary to read at least two lines. +/// as well). It will be grown up to `max_buffer_size` if necessary, but it will always grow to read at least two lines. +/// * `max_buffer_size`: Grow the buffer to at most this length. If None, the buffer will not grow, unless needed to read at least two lines. /// * `start_offset`: The amount of bytes at the start of `buffer` that were carried over /// from the previous read and should not be overwritten. /// * `separator`: The byte that separates lines. @@ -153,6 +168,7 @@ fn read_to_buffer( file: &mut Box, next_files: &mut impl Iterator>, buffer: &mut Vec, + max_buffer_size: Option, start_offset: usize, separator: u8, ) -> (usize, bool) { @@ -162,6 +178,19 @@ fn read_to_buffer( Ok(0) => { if read_target.is_empty() { // chunk is full + if let Some(max_buffer_size) = max_buffer_size { + if max_buffer_size > buffer.len() { + // we can grow the buffer + let prev_len = buffer.len(); + if buffer.len() < max_buffer_size / 2 { + buffer.resize(buffer.len() * 2, 0); + } else { + buffer.resize(max_buffer_size, 0); + } + read_target = &mut buffer[prev_len..]; + continue; + } + } let mut sep_iter = memchr_iter(separator, &buffer).rev(); let last_line_end = sep_iter.next(); if sep_iter.next().is_some() { diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 629ebb714..a304bf7c0 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -5,12 +5,13 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -//! Sort big files by using files for storing intermediate chunks. +//! Sort big files by using auxiliary files for storing intermediate chunks. //! //! Files are read into chunks of memory which are then sorted individually and //! written to temporary files. There are two threads: One sorter, and one reader/writer. //! The buffers for the individual chunks are recycled. There are two buffers. +use std::cmp::Ordering; use std::io::{BufWriter, Write}; use std::path::Path; use std::{ @@ -20,30 +21,19 @@ use std::{ thread, }; +use itertools::Itertools; + use tempdir::TempDir; use crate::{ chunks::{self, Chunk}, - merge::{self, FileMerger}, - sort_by, GlobalSettings, + compare_by, merge, output_sorted_lines, sort_by, GlobalSettings, }; -/// Iterator that wraps the -pub struct ExtSortedMerger<'a> { - pub file_merger: FileMerger<'a>, - // Keep _tmp_dir around, as it is deleted when dropped. - _tmp_dir: TempDir, -} +const MIN_BUFFER_SIZE: usize = 8_000; -/// Sort big files by using files for storing intermediate chunks. -/// -/// # Returns -/// -/// An iterator that merges intermediate files back together. -pub fn ext_sort<'a>( - files: &mut impl Iterator>, - settings: &'a GlobalSettings, -) -> ExtSortedMerger<'a> { +/// Sort files by using auxiliary files for storing intermediate chunks (if needed), and output the result. +pub fn ext_sort(files: &mut impl Iterator>, settings: &GlobalSettings) { let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1); let (recycled_sender, recycled_receiver) = std::sync::mpsc::sync_channel(1); @@ -51,7 +41,7 @@ pub fn ext_sort<'a>( let settings = settings.clone(); move || sorter(recycled_receiver, sorted_sender, settings) }); - let chunks_read = reader_writer( + let read_result = reader_writer( files, &tmp_dir, if settings.zero_terminated { @@ -66,13 +56,29 @@ pub fn ext_sort<'a>( sorted_receiver, recycled_sender, ); - let files = (0..chunks_read) - .map(|chunk_num| tmp_dir.path().join(chunk_num.to_string())) - .collect::>(); - - ExtSortedMerger { - file_merger: merge::merge(&files, settings), - _tmp_dir: tmp_dir, + match read_result { + ReadResult::WroteChunksToFile { chunks_written } => { + let files = (0..chunks_written) + .map(|chunk_num| tmp_dir.path().join(chunk_num.to_string())) + .collect::>(); + let mut merger = merge::merge(&files, settings); + merger.write_all(settings); + } + ReadResult::SortedSingleChunk(chunk) => { + output_sorted_lines(chunk.borrow_lines().iter(), settings); + } + ReadResult::SortedTwoChunks([a, b]) => { + let merged_iter = a + .borrow_lines() + .iter() + .merge_by(b.borrow_lines().iter(), |line_a, line_b| { + compare_by(line_a, line_b, settings) != Ordering::Greater + }); + output_sorted_lines(merged_iter, settings); + } + ReadResult::EmptyInput => { + // don't output anything + } } } @@ -84,6 +90,21 @@ fn sorter(receiver: Receiver, sender: SyncSender, settings: Global } } +/// Describes how we read the chunks from the input. +enum ReadResult { + /// The input was empty. Nothing was read. + EmptyInput, + /// The input fits into a single Chunk, which was kept in memory. + SortedSingleChunk(Chunk), + /// The input fits into two chunks, which were kept in memory. + SortedTwoChunks([Chunk; 2]), + /// The input was read into multiple chunks, which were written to auxiliary files. + WroteChunksToFile { + /// The number of chunks written to auxiliary files. + chunks_written: usize, + }, +} + /// The function that is executed on the reader/writer thread. /// /// # Returns @@ -96,7 +117,7 @@ fn reader_writer( settings: GlobalSettings, receiver: Receiver, sender: SyncSender, -) -> usize { +) -> ReadResult { let mut sender_option = Some(sender); let mut file = files.next().unwrap(); @@ -106,21 +127,40 @@ fn reader_writer( for _ in 0..2 { chunks::read( &mut sender_option, - vec![0; buffer_size], + vec![0; MIN_BUFFER_SIZE], + Some(buffer_size), &mut carry_over, &mut file, &mut files, separator, Vec::new(), &settings, - ) + ); + if sender_option.is_none() { + // We have already read the whole input. Since we are in our first two reads, + // this means that we can fit the whole input into memory. Bypass writing below and + // handle this case in a more straightforward way. + return if let Ok(first_chunk) = receiver.recv() { + if let Ok(second_chunk) = receiver.recv() { + ReadResult::SortedTwoChunks([first_chunk, second_chunk]) + } else { + ReadResult::SortedSingleChunk(first_chunk) + } + } else { + ReadResult::EmptyInput + }; + } } let mut file_number = 0; loop { let mut chunk = match receiver.recv() { Ok(it) => it, - _ => return file_number, + _ => { + return ReadResult::WroteChunksToFile { + chunks_written: file_number, + } + } }; write( @@ -129,13 +169,14 @@ fn reader_writer( separator, ); - let (recycled_lines, recycled_buffer) = chunk.recycle(); - file_number += 1; + let (recycled_lines, recycled_buffer) = chunk.recycle(); + chunks::read( &mut sender_option, recycled_buffer, + None, &mut carry_over, &mut file, &mut files, diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 6f7cdfed7..48d48ad40 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -108,6 +108,7 @@ fn reader( chunks::read( sender, recycled_buffer, + None, carry_over, file, &mut iter::empty(), diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index b6ab5a2b1..78388a298 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -93,7 +93,10 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; -static DEFAULT_BUF_SIZE: usize = std::usize::MAX; +/// Choosing a higher buffer size does not result in performance improvements +/// (at least not on my machine). TODO: In the future, we should also take the amount of +/// available memory into consideration, instead of relying on this constant only. +static DEFAULT_BUF_SIZE: usize = 1_000_000_000; #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum SortMode { @@ -127,7 +130,6 @@ pub struct GlobalSettings { zero_terminated: bool, buffer_size: usize, tmp_dir: PathBuf, - ext_sort: bool, } impl GlobalSettings { @@ -189,7 +191,6 @@ impl Default for GlobalSettings { zero_terminated: false, buffer_size: DEFAULT_BUF_SIZE, tmp_dir: PathBuf::new(), - ext_sort: false, } } } @@ -941,28 +942,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { env::set_var("RAYON_NUM_THREADS", &settings.threads); } - if matches.is_present(OPT_BUF_SIZE) { - settings.buffer_size = { - let input = matches - .value_of(OPT_BUF_SIZE) - .map(String::from) - .unwrap_or(format!("{}", DEFAULT_BUF_SIZE)); + settings.buffer_size = matches + .value_of(OPT_BUF_SIZE) + .map(GlobalSettings::human_numeric_convert) + .unwrap_or(DEFAULT_BUF_SIZE); - GlobalSettings::human_numeric_convert(&input) - }; - settings.ext_sort = true; - } - - if matches.is_present(OPT_TMP_DIR) { - let result = matches - .value_of(OPT_TMP_DIR) - .map(String::from) - .unwrap_or(format!("{}", env::temp_dir().display())); - settings.tmp_dir = PathBuf::from(result); - settings.ext_sort = true; - } else { - settings.tmp_dir = env::temp_dir(); - } + settings.tmp_dir = matches + .value_of(OPT_TMP_DIR) + .map(PathBuf::from) + .unwrap_or_else(env::temp_dir); settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED); settings.merge = matches.is_present(OPT_MERGE); @@ -1047,7 +1035,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { exec(&files, &settings) } -fn output_sorted_lines<'a>(iter: impl Iterator>, settings: &GlobalSettings) { +fn output_sorted_lines<'a>(iter: impl Iterator>, settings: &GlobalSettings) { if settings.unique { print_sorted( iter.dedup_by(|a, b| compare_by(a, b, &settings) == Ordering::Equal), @@ -1067,34 +1055,10 @@ fn exec(files: &[String], settings: &GlobalSettings) -> i32 { crash!(1, "only one file allowed with -c"); } return check::check(files.first().unwrap(), settings); - } else if settings.ext_sort { + } else { let mut lines = files.iter().filter_map(open); - let mut sorted = ext_sort(&mut lines, &settings); - sorted.file_merger.write_all(settings); - } else { - let separator = if settings.zero_terminated { '\0' } else { '\n' }; - let mut lines = vec![]; - let mut full_string = String::new(); - - for mut file in files.iter().filter_map(open) { - crash_if_err!(1, file.read_to_string(&mut full_string)); - - if !full_string.ends_with(separator) { - full_string.push(separator); - } - } - - if full_string.ends_with(separator) { - full_string.pop(); - } - - for line in full_string.split(if settings.zero_terminated { '\0' } else { '\n' }) { - lines.push(Line::create(line, &settings)); - } - - sort_by(&mut lines, &settings); - output_sorted_lines(lines.into_iter(), &settings); + ext_sort(&mut lines, &settings); } 0 } @@ -1366,7 +1330,7 @@ fn version_compare(a: &str, b: &str) -> Ordering { } } -fn print_sorted<'a, T: Iterator>>(iter: T, settings: &GlobalSettings) { +fn print_sorted<'a, T: Iterator>>(iter: T, settings: &GlobalSettings) { let mut writer = settings.out_writer(); for line in iter { line.print(&mut writer, settings); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index e89d18054..59058d5bc 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -15,29 +15,18 @@ fn test_helper(file_name: &str, args: &str) { .stdout_is_fixture(format!("{}.expected.debug", file_name)); } -// FYI, the initialization size of our Line struct is 96 bytes. -// -// At very small buffer sizes, with that overhead we are certainly going -// to overrun our buffer way, way, way too quickly because of these excess -// bytes for the struct. -// -// For instance, seq 0..20000 > ...text = 108894 bytes -// But overhead is 1920000 + 108894 = 2028894 bytes -// -// Or kjvbible-random.txt = 4332506 bytes, but minimum size of its -// 99817 lines in memory * 96 bytes = 9582432 bytes -// -// Here, we test 108894 bytes with a 50K buffer -// #[test] -fn test_larger_than_specified_segment() { - new_ucmd!() - .arg("-n") - .arg("-S") - .arg("50K") - .arg("ext_sort.txt") - .succeeds() - .stdout_is_fixture("ext_sort.expected"); +fn test_buffer_sizes() { + let buffer_sizes = ["0", "50K", "1M", "1000G"]; + for buffer_size in &buffer_sizes { + new_ucmd!() + .arg("-n") + .arg("-S") + .arg(buffer_size) + .arg("ext_sort.txt") + .succeeds() + .stdout_is_fixture("ext_sort.expected"); + } } #[test] From adaba5402691ee1286abc4ce8be7dd8f66862aa7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 21 May 2021 18:25:23 +0200 Subject: [PATCH 339/399] gnu/ci: move the operations into script to run them locally --- .github/workflows/GNU.yml | 79 ++--------------------------------- util/build-gnu.sh | 86 +++++++++++++++++++++++++++++++++++++++ util/run-gnu-test.sh | 7 ++++ 3 files changed, 96 insertions(+), 76 deletions(-) create mode 100644 util/build-gnu.sh create mode 100644 util/run-gnu-test.sh diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 57730aee7..c94902bbc 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -40,85 +40,12 @@ jobs: - name: Build binaries shell: bash run: | - pushd uutils - make PROFILE=release - BUILDDIR="$PWD/target/release/" - cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target - # Create *sum binaries - for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum - do - sum_path="${BUILDDIR}/${sum}" - test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}" - done - test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/[" - popd - GNULIB_SRCDIR="$PWD/gnulib" - pushd gnu/ - - # Any binaries that aren't built become `false` so their tests fail - for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs) - do - bin_path="${BUILDDIR}/${binary}" - test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; } - done - - ./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" - ./configure --quiet --disable-gcc-warnings - #Add timeout to to protect against hangs - sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver - # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils - sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile - sed -i 's| tr | /usr/bin/tr |' tests/init.sh - make - # Generate the factor tests, so they can be fixed - for i in {00..36} - do - make tests/factor/t${i}.sh - done - grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' - sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh - - # Remove tests checking for --version & --help - # Not really interesting for us and logs are too big - sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ - -e '/tests\/misc\/help-version.sh/ D' \ - -e '/tests\/misc\/help-version-getopt.sh/ D' \ - Makefile - - # printf doesn't limit the values used in its arg, so this produced ~2GB of output - sed -i '/INT_OFLOW/ D' tests/misc/printf.sh - - # Use the system coreutils where the test fails due to error in a util that is not the one being tested - sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh - sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh - sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh - sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout' - sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh - sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh - sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh - sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh - sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh init.cfg - sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh - sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh - sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh - sed -i 's|printf |/usr/bin/printf |' tests/dd/ascii.sh - sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh - sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh - sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh - - #Add specific timeout to tests that currently hang to limit time spent waiting - sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh - sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh - - test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" + cd uutils + bash util/build-gnu.sh - name: Run GNU tests shell: bash run: | - BUILDDIR="${PWD}/uutils/target/release" - GNULIB_DIR="${PWD}/gnulib" - pushd gnu - - timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make + bash uutils/util/run-gnu-test.sh - name: Extract tests info shell: bash run: | diff --git a/util/build-gnu.sh b/util/build-gnu.sh new file mode 100644 index 000000000..667dc8e46 --- /dev/null +++ b/util/build-gnu.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -e +if test ! -d ../gnu; then + echo "Could not find ../gnu" + echo "git clone git@github.com:coreutils/coreutils.git ../gnu" + exit 1 +fi +if test ! -d ../gnulib; then + echo "Could not find ../gnulib" + echo "git clone git@github.com:coreutils/gnulib.git ../gnulib" + exit 1 +fi + + +pushd $(pwd) +make PROFILE=release +BUILDDIR="$PWD/target/release/" +cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target +# Create *sum binaries +for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum +do + sum_path="${BUILDDIR}/${sum}" + test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}" +done +test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/[" +popd +GNULIB_SRCDIR="$PWD/../gnulib" +pushd ../gnu/ + +# Any binaries that aren't built become `false` so their tests fail +for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs) +do + bin_path="${BUILDDIR}/${binary}" + test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; } +done + +./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR" +./configure --quiet --disable-gcc-warnings +#Add timeout to to protect against hangs +sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver +# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils +sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile +sed -i 's| tr | /usr/bin/tr |' tests/init.sh +make +# Generate the factor tests, so they can be fixed +for i in {00..36} +do + make tests/factor/t${i}.sh +done +grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' +sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh + +# Remove tests checking for --version & --help +# Not really interesting for us and logs are too big +sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ + -e '/tests\/misc\/help-version.sh/ D' \ + -e '/tests\/misc\/help-version-getopt.sh/ D' \ + Makefile + +# printf doesn't limit the values used in its arg, so this produced ~2GB of output +sed -i '/INT_OFLOW/ D' tests/misc/printf.sh + +# Use the system coreutils where the test fails due to error in a util that is not the one being tested +sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh +sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh +sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh +sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout' +sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh +sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh +sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh +sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh +sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh init.cfg +sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh +sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh +sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh +sed -i 's|printf |/usr/bin/printf |' tests/dd/ascii.sh +sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh +sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh +sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh + +#Add specific timeout to tests that currently hang to limit time spent waiting +sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh + +test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}" + diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh new file mode 100644 index 000000000..5031863c4 --- /dev/null +++ b/util/run-gnu-test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +BUILDDIR="${PWD}/uutils/target/release" +GNULIB_DIR="${PWD}/gnulib" +pushd gnu + +timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=no RUN_VERY_EXPENSIVE_TESTS=no VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make From ddcd6be37afb0b9a23431415399ad6cf4f881b74 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 21 May 2021 18:31:21 +0200 Subject: [PATCH 340/399] gnu: document how to run the tests --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 7de4419af..6b29fa854 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,16 @@ To pass an argument like "-v" to the busybox test runtime $ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest ``` +## Comparing with GNU + +![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true) + +To run locally: +```bash +$ bash util/build-gnu.sh +$ bash util/run-gnu-test.sh +``` + ## Contribute To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). From 373776e0713580935b3eb8395585655bd492e019 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 22 May 2021 09:40:35 +0200 Subject: [PATCH 341/399] freebsd/circus: workaround the timeout https://github.com/rust-lang/rustup/issues/2774 It is failing currently on: ``` info: installing component 'cargo' error: error: 'sysinfo not supported on this platform' ``` with 1.52.1 --- .cirrus.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.cirrus.yml b/.cirrus.yml index 5d16dce92..fb9b038a8 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,3 +1,10 @@ +env: + # Temporary workaround for error `error: sysinfo not supported on + # this platform` seen on FreeBSD platforms, affecting Rustup + # + # References: https://github.com/rust-lang/rustup/issues/2774 + RUSTUP_IO_THREADS: 1 + task: name: stable x86_64-unknown-freebsd-12 freebsd_instance: From 33fb491c6e89dccb0f6532f1bd2c6bb024c4640d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 22 May 2021 11:05:55 +0200 Subject: [PATCH 342/399] freebsd/circus: update to freebsd 12.2 --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index fb9b038a8..50f8a25b1 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -8,7 +8,7 @@ env: task: name: stable x86_64-unknown-freebsd-12 freebsd_instance: - image: freebsd-12-1-release-amd64 + image: freebsd-12-2-release-amd64 setup_script: - pkg install -y curl gmake - curl https://sh.rustup.rs -sSf --output rustup.sh From 0d1b14ee333bdaae9942cf45f1d5cb7e395f8ab6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 22 May 2021 11:08:03 +0200 Subject: [PATCH 343/399] Bring back the run expensive tests --- util/run-gnu-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 5031863c4..b9948ccd3 100644 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -4,4 +4,4 @@ BUILDDIR="${PWD}/uutils/target/release" GNULIB_DIR="${PWD}/gnulib" pushd gnu -timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=no RUN_VERY_EXPENSIVE_TESTS=no VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make +timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make From 9f88963764ad2e02836b6583ac556503b4f3c687 Mon Sep 17 00:00:00 2001 From: Anup Mahindre Date: Sat, 22 May 2021 14:51:50 +0530 Subject: [PATCH 344/399] tests: fix test_ls_path for windows --- tests/by-util/test_ls.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index fc4051039..6d6c65194 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1992,10 +1992,18 @@ fn test_ls_path() { .stdout_is(expected_stdout); let abs_path = format!("{}/{}", at.as_string(), path); - let expected_stdout = &format!("{}\n", abs_path); + let expected_stdout = if cfg!(windows) { + format!("\'{}\'\n", abs_path) + } else { + format!("{}\n", abs_path) + }; scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout); - let expected_stdout = &format!("{}\n{}\n", path, file1); + let expected_stdout = if cfg!(windows) { + format!("{} {}\n", path, file1) + } else { + format!("{}\n{}\n", path, file1) + }; scene .ucmd() .arg(file1) From 628684af4f8f8fbeed87ba0b8dd592b255f634ab Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 22 May 2021 12:20:13 +0200 Subject: [PATCH 345/399] refresh cargo.lock with recent updates --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index feda68de5..997d52fab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1098,9 +1098,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" dependencies = [ "num-traits", "plotters-backend", @@ -1174,9 +1174,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid 0.2.2", ] @@ -1308,9 +1308,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ "autocfg", "crossbeam-deque", @@ -1320,9 +1320,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel", "crossbeam-deque", From 088443276a6d0229dc93b5a9502ff3d09870d782 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 22 May 2021 14:00:07 +0200 Subject: [PATCH 346/399] sort: improve handling of buffer size cmd arg Instead of overflowing when calculating the buffer size, use saturating_{pow, mul}. When failing to parse the buffer size, we now crash instead of silently ignoring the error. --- src/uu/sort/src/sort.rs | 54 ++++++++++++++++++++++---------------- tests/by-util/test_sort.rs | 24 ++++++++++------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 78388a298..bc3b65492 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -93,10 +93,10 @@ static THOUSANDS_SEP: char = ','; static NEGATIVE: char = '-'; static POSITIVE: char = '+'; -/// Choosing a higher buffer size does not result in performance improvements -/// (at least not on my machine). TODO: In the future, we should also take the amount of -/// available memory into consideration, instead of relying on this constant only. -static DEFAULT_BUF_SIZE: usize = 1_000_000_000; +// Choosing a higher buffer size does not result in performance improvements +// (at least not on my machine). TODO: In the future, we should also take the amount of +// available memory into consideration, instead of relying on this constant only. +static DEFAULT_BUF_SIZE: usize = 1_000_000_000; // 1 GB #[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy)] enum SortMode { @@ -133,24 +133,32 @@ pub struct GlobalSettings { } impl GlobalSettings { - // It's back to do conversions for command line opts! - // Probably want to do through numstrcmp somehow now? - fn human_numeric_convert(a: &str) -> usize { - let num_str = &a[get_leading_gen(a)]; - let (_, suf_str) = a.split_at(num_str.len()); - let num_usize = num_str - .parse::() - .expect("Error parsing buffer size: "); - let suf_usize: usize = match suf_str.to_uppercase().as_str() { - // SI Units - "B" => 1usize, - "K" => 1000usize, - "M" => 1000000usize, - "G" => 1000000000usize, - // GNU regards empty human numeric values as K by default - _ => 1000usize, - }; - num_usize * suf_usize + /// Interpret this `&str` as a number with an optional trailing si unit. + /// + /// If there is no trailing si unit, the implicit unit is K. + /// The suffix B causes the number to be interpreted as a byte count. + fn parse_byte_count(input: &str) -> usize { + const SI_UNITS: &[char] = &['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + + let input = input.trim(); + + let (num_str, si_unit) = + if input.ends_with(|c: char| SI_UNITS.contains(&c.to_ascii_uppercase())) { + let mut chars = input.chars(); + let si_suffix = chars.next_back().unwrap().to_ascii_uppercase(); + let si_unit = SI_UNITS.iter().position(|&c| c == si_suffix).unwrap(); + let num_str = chars.as_str(); + (num_str, si_unit) + } else { + (input, 1) + }; + + let num_usize: usize = num_str + .trim() + .parse() + .unwrap_or_else(|e| crash!(1, "failed to parse buffer size `{}`: {}", num_str, e)); + + num_usize.saturating_mul(1000usize.saturating_pow(si_unit as u32)) } fn out_writer(&self) -> BufWriter> { @@ -944,7 +952,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.buffer_size = matches .value_of(OPT_BUF_SIZE) - .map(GlobalSettings::human_numeric_convert) + .map(GlobalSettings::parse_byte_count) .unwrap_or(DEFAULT_BUF_SIZE); settings.tmp_dir = matches diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 59058d5bc..23705d2ee 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -17,7 +17,9 @@ fn test_helper(file_name: &str, args: &str) { #[test] fn test_buffer_sizes() { - let buffer_sizes = ["0", "50K", "1M", "1000G"]; + let buffer_sizes = [ + "0", "50K", "50k", "1M", "100M", "1000G", "10T", "500E", "1Y", + ]; for buffer_size in &buffer_sizes { new_ucmd!() .arg("-n") @@ -30,14 +32,18 @@ fn test_buffer_sizes() { } #[test] -fn test_smaller_than_specified_segment() { - new_ucmd!() - .arg("-n") - .arg("-S") - .arg("100M") - .arg("ext_sort.txt") - .succeeds() - .stdout_is_fixture("ext_sort.expected"); +fn test_invalid_buffer_size() { + let buffer_sizes = ["asd", "100f"]; + for invalid_buffer_size in &buffer_sizes { + new_ucmd!() + .arg("-S") + .arg(invalid_buffer_size) + .fails() + .stderr_only(format!( + "sort: error: failed to parse buffer size `{}`: invalid digit found in string", + invalid_buffer_size + )); + } } #[test] From bee3b1237c589f7c2e22d94d0c6d2e99bea0b5b5 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 22 May 2021 10:44:50 -0400 Subject: [PATCH 347/399] uucore::fs: don't canonicalize last component Change the behavior of `uucore::fs::canonicalize()` when `can_mode` is `CanonicalizeMode::None` so that it does not attempt to resolve the final component if it is a symbolic link. This matches the behavior of the function for the non-final components of a path when `can_mode` is `None`. --- src/uucore/src/lib/features/fs.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 040c36e95..afaa07af1 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -54,11 +54,19 @@ pub fn resolve_relative_path(path: &Path) -> Cow { result.into() } +/// Controls how symbolic links should be handled when canonicalizing a path. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CanonicalizeMode { + /// Do not resolve any symbolic links. None, + + /// Resolve all symbolic links. Normal, + + /// Resolve symbolic links, ignoring errors on the final component. Existing, + + /// Resolve symbolic links, ignoring errors on the non-final components. Missing, } @@ -125,6 +133,24 @@ fn resolve>(original: P) -> IOResult { Ok(result) } +/// Return the canonical, absolute form of a path. +/// +/// This function is a generalization of [`std::fs::canonicalize`] that +/// allows controlling how symbolic links are resolved and how to deal +/// with missing components. It returns the canonical, absolute form of +/// a path. The `can_mode` parameter controls how symbolic links are +/// resolved: +/// +/// * [`CanonicalizeMode::Normal`] makes this function behave like +/// [`std::fs::canonicalize`], resolving symbolic links and returning +/// an error if the path does not exist. +/// * [`CanonicalizeMode::Missing`] makes this function ignore non-final +/// components of the path that could not be resolved. +/// * [`CanonicalizeMode::Existing`] makes this function return an error +/// if the final component of the path does not exist. +/// * [`CanonicalizeMode::None`] makes this function not try to resolve +/// any symbolic links. +/// pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> IOResult { // Create an absolute path let original = original.as_ref(); @@ -180,6 +206,10 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> result.push(parts.last().unwrap()); + if can_mode == CanonicalizeMode::None { + return Ok(result); + } + match resolve(&result) { Err(e) => { if can_mode == CanonicalizeMode::Existing { From 4b5c3efe85bf3dd735401f40d44eda16dd1c67c2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 22 May 2021 10:50:00 -0400 Subject: [PATCH 348/399] realpath: use uucore::fs::canonicalize() Use the `uucore::fs::canonicalize()` function to simplify the implementation of `realpath`. --- src/uu/realpath/src/realpath.rs | 78 ++++++++++----------------------- 1 file changed, 24 insertions(+), 54 deletions(-) diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 37ff70fb2..937cee5bd 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -11,7 +11,6 @@ extern crate uucore; use clap::{App, Arg}; -use std::fs; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; @@ -75,64 +74,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let quiet = matches.is_present(OPT_QUIET); let mut retcode = 0; for path in &paths { - if !resolve_path(path, strip, zero, quiet) { + if let Err(e) = resolve_path(path, strip, zero) { + if !quiet { + show_error!("{}: {}", e, path.display()); + } retcode = 1 }; } retcode } -fn resolve_path(p: &Path, strip: bool, zero: bool, quiet: bool) -> bool { - let abs = canonicalize(p, CanonicalizeMode::Normal).unwrap(); - - if strip { - if zero { - print!("{}\0", p.display()); - } else { - println!("{}", p.display()) - } - return true; - } - - let mut result = PathBuf::new(); - let mut links_left = 256; - - for part in abs.components() { - result.push(part.as_os_str()); - loop { - if links_left == 0 { - if !quiet { - show_error!("Too many symbolic links: {}", p.display()) - }; - return false; - } - match fs::metadata(result.as_path()) { - Err(_) => break, - Ok(ref m) if !m.file_type().is_symlink() => break, - Ok(_) => { - links_left -= 1; - match fs::read_link(result.as_path()) { - Ok(x) => { - result.pop(); - result.push(x.as_path()); - } - _ => { - if !quiet { - show_error!("Invalid path: {}", p.display()) - }; - return false; - } - } - } - } - } - } - - if zero { - print!("{}\0", result.display()); +/// Resolve a path to an absolute form and print it. +/// +/// If `strip` is `true`, then this function does not attempt to resolve +/// symbolic links in the path. If `zero` is `true`, then this function +/// prints the path followed by the null byte (`'\0'`) instead of a +/// newline character (`'\n'`). +/// +/// # Errors +/// +/// This function returns an error if there is a problem resolving +/// symbolic links. +fn resolve_path(p: &Path, strip: bool, zero: bool) -> std::io::Result<()> { + let mode = if strip { + CanonicalizeMode::None } else { - println!("{}", result.display()); - } - - true + CanonicalizeMode::Normal + }; + let abs = canonicalize(p, mode)?; + let line_ending = if zero { '\0' } else { '\n' }; + print!("{}{}", abs.display(), line_ending); + Ok(()) } From fcb079e20e6cd6008d7146a9a5dc04ddda0e5bdd Mon Sep 17 00:00:00 2001 From: David Carlier Date: Thu, 20 May 2021 20:09:41 +0100 Subject: [PATCH 349/399] who freebsd build fix unsupported RUN_LVL option only for other platforms. --- src/uu/who/src/who.rs | 17 +++++++++++------ tests/by-util/test_who.rs | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 1ae4f1c8d..81fc2a687 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -29,7 +29,7 @@ mod options { pub const ONLY_HOSTNAME_USER: &str = "only_hostname_user"; pub const PROCESS: &str = "process"; pub const COUNT: &str = "count"; - #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] + #[cfg(any(target_os = "linux", target_os = "android"))] pub const RUNLEVEL: &str = "runlevel"; pub const SHORT: &str = "short"; pub const TIME: &str = "time"; @@ -119,11 +119,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("all login names and number of users logged on"), ) .arg( - #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] + #[cfg(any(target_os = "linux", target_os = "android"))] Arg::with_name(options::RUNLEVEL) .long(options::RUNLEVEL) .short("r") .help("print current runlevel"), + #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] + Arg::with_name(""), ) .arg( Arg::with_name(options::SHORT) @@ -265,10 +267,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { assumptions = false; } - if matches.is_present(options::RUNLEVEL) { - need_runlevel = true; - include_idle = true; - assumptions = false; + #[cfg(any(target_os = "linux", target_os = "android"))] + { + if matches.is_present(options::RUNLEVEL) { + need_runlevel = true; + include_idle = true; + assumptions = false; + } } if matches.is_present(options::SHORT) { diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 725ec0b1e..1aa8d604d 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -83,7 +83,7 @@ fn test_process() { } } -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(target_os = "linux")] #[test] fn test_runlevel() { for opt in vec!["-r", "--runlevel"] { @@ -94,6 +94,19 @@ fn test_runlevel() { } } +#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +#[test] +fn test_runlevel() { + let expected = + "error: Found argument"; + for opt in vec!["-r", "--runlevel"] { + new_ucmd!() + .arg(opt) + .fails() + .stderr_contains(expected); + } +} + #[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_time() { @@ -122,6 +135,7 @@ fn test_mesg() { } } +#[cfg(target_os = "linux")] #[test] fn test_arg1_arg2() { let args = ["am", "i"]; @@ -132,6 +146,7 @@ fn test_arg1_arg2() { .stdout_is(expected_result(&args)); } +#[cfg(target_os = "linux")] #[test] fn test_too_many_args() { const EXPECTED: &str = From 4521aa2659c2e44516415913d63da0774e1fd1bc Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 6 May 2021 22:39:39 -0400 Subject: [PATCH 350/399] wc: print counts for each file as soon as computed Change the behavior of `wc` to print the counts for a file as soon as it is computed, instead of waiting to compute the counts for all files before writing any output to `stdout`. The new behavior matches the behavior of GNU `wc`. The old behavior looked like this (the word "hello" is entered on `stdin`): $ wc emptyfile.txt - hello 0 0 0 emptyfile.txt 1 1 6 1 1 6 total The new behavior looks like this: $ wc emptyfile.txt - 0 0 0 emptyfile.txt hello 1 1 6 1 1 6 total --- src/uu/wc/src/wc.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index b323f7261..6e95254ee 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -373,7 +373,6 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let max_width = max_width(&inputs); let mut total_word_count = WordCount::default(); - let mut results = vec![]; let num_inputs = inputs.len(); @@ -384,10 +383,7 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { WordCount::default() }); total_word_count += word_count; - results.push(word_count.with_title(input.to_title())); - } - - for result in &results { + let result = word_count.with_title(input.to_title()); if let Err(err) = print_stats(settings, &result, max_width) { show_warning!( "failed to print result for {}: {}", From 9f0ef3ba54a83e4ed5e5a87bf8f3663eeccfa1c2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 22 May 2021 21:59:54 +0200 Subject: [PATCH 351/399] gnu/ci: also store the hash in the json --- .github/workflows/GNU.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index c94902bbc..1f9250900 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -61,13 +61,14 @@ jobs: echo "::warning ::GNU testsuite = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR" jq -n \ --arg date "$(date --rfc-email)" \ + --arg sha "$GITHUB_SHA" \ --arg total "$TOTAL" \ --arg pass "$PASS" \ --arg skip "$SKIP" \ --arg fail "$FAIL" \ --arg xpass "$XPASS" \ --arg error "$ERROR" \ - '{($date): { total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json + '{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json else echo "::error ::Failed to get summary of test results" fi From c1f67ed775bb33cdf17d563c20ebce8cdf886cde Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 22 May 2021 23:06:30 +0200 Subject: [PATCH 352/399] sort: support --sort flag and check for conflicts `sort` supports three ways to specify the sort mode: a long option (e.g. --numeric-sort), a short option (e.g. -n) and the sort flag (e.g. --sort=numeric). This adds support for the sort flag. Additionally, sort modes now conflict, which means that an error is shown when multiple modes are passed, instead of silently picking a mode. For consistency, I added the `random` sort mode to the `SortMode` enum, instead of it being a bool flag. --- src/uu/sort/src/sort.rs | 178 +++++++++++++++++++++++-------------- tests/by-util/test_sort.rs | 139 +++++++++++++++++++---------- 2 files changed, 202 insertions(+), 115 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bc3b65492..81787ece6 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -64,6 +64,17 @@ static OPT_NUMERIC_SORT: &str = "numeric-sort"; static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort"; static OPT_VERSION_SORT: &str = "version-sort"; +static OPT_SORT: &str = "sort"; + +static ALL_SORT_MODES: &[&str] = &[ + OPT_GENERAL_NUMERIC_SORT, + OPT_HUMAN_NUMERIC_SORT, + OPT_MONTH_SORT, + OPT_NUMERIC_SORT, + OPT_VERSION_SORT, + OPT_RANDOM, +]; + static OPT_DICTIONARY_ORDER: &str = "dictionary-order"; static OPT_MERGE: &str = "merge"; static OPT_CHECK: &str = "check"; @@ -105,6 +116,7 @@ enum SortMode { GeneralNumeric, Month, Version, + Random, Default, } #[derive(Clone)] @@ -122,7 +134,6 @@ pub struct GlobalSettings { unique: bool, check: bool, check_silent: bool, - random: bool, salt: String, selectors: Vec, separator: Option, @@ -191,7 +202,6 @@ impl Default for GlobalSettings { unique: false, check: false, check_silent: false, - random: false, salt: String::new(), selectors: vec![], separator: None, @@ -209,7 +219,6 @@ struct KeySettings { ignore_case: bool, dictionary_order: bool, ignore_non_printing: bool, - random: bool, reverse: bool, } @@ -220,7 +229,6 @@ impl From<&GlobalSettings> for KeySettings { ignore_blanks: settings.ignore_blanks, ignore_case: settings.ignore_case, ignore_non_printing: settings.ignore_non_printing, - random: settings.random, reverse: settings.reverse, dictionary_order: settings.dictionary_order, } @@ -398,7 +406,7 @@ impl<'a> Line<'a> { } } } - if !(settings.random + if !(settings.mode == SortMode::Random || settings.stable || settings.unique || !(settings.dictionary_order @@ -502,7 +510,7 @@ impl KeyPosition { 'h' => settings.mode = SortMode::HumanNumeric, 'i' => settings.ignore_non_printing = true, 'n' => settings.mode = SortMode::Numeric, - 'R' => settings.random = true, + 'R' => settings.mode = SortMode::Random, 'r' => settings.reverse = true, 'V' => settings.mode = SortMode::Version, c => { @@ -526,7 +534,9 @@ impl KeyPosition { | SortMode::GeneralNumeric | SortMode::Month => SortMode::Default, // Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing - m @ SortMode::Default | m @ SortMode::Version => m, + m @ SortMode::Default + | m @ SortMode::Version + | m @ SortMode::Random => m, } } _ => {} @@ -720,6 +730,16 @@ With no FILE, or when FILE is -, read standard input.", ) } +fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> { + let mut arg = Arg::with_name(mode).short(short).long(mode).help(help); + for possible_mode in ALL_SORT_MODES { + if *possible_mode != mode { + arg = arg.conflicts_with(possible_mode); + } + } + arg +} + pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) @@ -732,34 +752,62 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .about(ABOUT) .usage(&usage[..]) .arg( - Arg::with_name(OPT_HUMAN_NUMERIC_SORT) - .short("h") - .long(OPT_HUMAN_NUMERIC_SORT) - .help("compare according to human readable sizes, eg 1M > 100k"), + Arg::with_name(OPT_SORT) + .long(OPT_SORT) + .takes_value(true) + .possible_values( + &[ + "general-numeric", + "human-numeric", + "month", + "numeric", + "version", + "random", + ] + ) + .conflicts_with_all(ALL_SORT_MODES) ) .arg( - Arg::with_name(OPT_MONTH_SORT) - .short("M") - .long(OPT_MONTH_SORT) - .help("compare according to month name abbreviation"), + make_sort_mode_arg( + OPT_HUMAN_NUMERIC_SORT, + "h", + "compare according to human readable sizes, eg 1M > 100k" + ), ) .arg( - Arg::with_name(OPT_NUMERIC_SORT) - .short("n") - .long(OPT_NUMERIC_SORT) - .help("compare according to string numerical value"), + make_sort_mode_arg( + OPT_MONTH_SORT, + "M", + "compare according to month name abbreviation" + ), ) .arg( - Arg::with_name(OPT_GENERAL_NUMERIC_SORT) - .short("g") - .long(OPT_GENERAL_NUMERIC_SORT) - .help("compare according to string general numerical value"), + make_sort_mode_arg( + OPT_NUMERIC_SORT, + "n", + "compare according to string numerical value" + ), ) .arg( - Arg::with_name(OPT_VERSION_SORT) - .short("V") - .long(OPT_VERSION_SORT) - .help("Sort by SemVer version number, eg 1.12.2 > 1.1.2"), + make_sort_mode_arg( + OPT_GENERAL_NUMERIC_SORT, + "g", + "compare according to string general numerical value" + ), + ) + .arg( + make_sort_mode_arg( + OPT_VERSION_SORT, + "V", + "Sort by SemVer version number, eg 1.12.2 > 1.1.2", + ), + ) + .arg( + make_sort_mode_arg( + OPT_RANDOM, + "R", + "shuffle in random order", + ), ) .arg( Arg::with_name(OPT_DICTIONARY_ORDER) @@ -813,12 +861,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .value_name("FILENAME"), ) - .arg( - Arg::with_name(OPT_RANDOM) - .short("R") - .long(OPT_RANDOM) - .help("shuffle in random order"), - ) .arg( Arg::with_name(OPT_REVERSE) .short("r") @@ -925,16 +967,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_default() }; - settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) { + settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) + || matches.value_of(OPT_SORT) == Some("human-numeric") + { SortMode::HumanNumeric - } else if matches.is_present(OPT_MONTH_SORT) { + } else if matches.is_present(OPT_MONTH_SORT) || matches.value_of(OPT_SORT) == Some("month") { SortMode::Month - } else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) { + } else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) + || matches.value_of(OPT_SORT) == Some("general-numeric") + { SortMode::GeneralNumeric - } else if matches.is_present(OPT_NUMERIC_SORT) { + } else if matches.is_present(OPT_NUMERIC_SORT) || matches.value_of(OPT_SORT) == Some("numeric") + { SortMode::Numeric - } else if matches.is_present(OPT_VERSION_SORT) { + } else if matches.is_present(OPT_VERSION_SORT) || matches.value_of(OPT_SORT) == Some("version") + { SortMode::Version + } else if matches.is_present(OPT_RANDOM) || matches.value_of(OPT_SORT) == Some("random") { + settings.salt = get_rand_string(); + SortMode::Random } else { SortMode::Default }; @@ -978,11 +1029,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.stable = matches.is_present(OPT_STABLE); settings.unique = matches.is_present(OPT_UNIQUE); - if matches.is_present(OPT_RANDOM) { - settings.random = matches.is_present(OPT_RANDOM); - settings.salt = get_rand_string(); - } - if files.is_empty() { /* if no file, default to stdin */ files.push("-".to_owned()); @@ -1110,28 +1156,25 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings) let b_str = b_selection.slice; let settings = &selector.settings; - let cmp: Ordering = if settings.random { - random_shuffle(a_str, b_str, &global_settings.salt) - } else { - match settings.mode { - SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( - (a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()), - (b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()), - ), - SortMode::GeneralNumeric => general_numeric_compare( - a_selection.num_cache.as_ref().unwrap().as_f64(), - b_selection.num_cache.as_ref().unwrap().as_f64(), - ), - SortMode::Month => month_compare(a_str, b_str), - SortMode::Version => version_compare(a_str, b_str), - SortMode::Default => custom_str_cmp( - a_str, - b_str, - settings.ignore_non_printing, - settings.dictionary_order, - settings.ignore_case, - ), - } + let cmp: Ordering = match settings.mode { + SortMode::Random => random_shuffle(a_str, b_str, &global_settings.salt), + SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( + (a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()), + (b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()), + ), + SortMode::GeneralNumeric => general_numeric_compare( + a_selection.num_cache.as_ref().unwrap().as_f64(), + b_selection.num_cache.as_ref().unwrap().as_f64(), + ), + SortMode::Month => month_compare(a_str, b_str), + SortMode::Version => version_compare(a_str, b_str), + SortMode::Default => custom_str_cmp( + a_str, + b_str, + settings.ignore_non_printing, + settings.dictionary_order, + settings.ignore_case, + ), }; if cmp != Ordering::Equal { return if settings.reverse { cmp.reverse() } else { cmp }; @@ -1139,7 +1182,10 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings) } // Call "last resort compare" if all selectors returned Equal - let cmp = if global_settings.random || global_settings.stable || global_settings.unique { + let cmp = if global_settings.mode == SortMode::Random + || global_settings.stable + || global_settings.unique + { Ordering::Equal } else { a.line.cmp(b.line) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 23705d2ee..e4676b379 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1,18 +1,20 @@ use crate::common::util::*; -fn test_helper(file_name: &str, args: &str) { - new_ucmd!() - .arg(format!("{}.txt", file_name)) - .args(&args.split(' ').collect::>()) - .succeeds() - .stdout_is_fixture(format!("{}.expected", file_name)); +fn test_helper(file_name: &str, possible_args: &[&str]) { + for args in possible_args { + new_ucmd!() + .arg(format!("{}.txt", file_name)) + .args(&args.split(' ').collect::>()) + .succeeds() + .stdout_is_fixture(format!("{}.expected", file_name)); - new_ucmd!() - .arg(format!("{}.txt", file_name)) - .arg("--debug") - .args(&args.split(' ').collect::>()) - .succeeds() - .stdout_is_fixture(format!("{}.expected.debug", file_name)); + new_ucmd!() + .arg(format!("{}.txt", file_name)) + .arg("--debug") + .args(&args.split(' ').collect::>()) + .succeeds() + .stdout_is_fixture(format!("{}.expected.debug", file_name)); + } } #[test] @@ -71,7 +73,7 @@ fn test_extsort_zero_terminated() { #[test] fn test_months_whitespace() { - test_helper("months-whitespace", "-M"); + test_helper("months-whitespace", &["-M", "--month-sort", "--sort=month"]); } #[test] @@ -85,7 +87,10 @@ fn test_version_empty_lines() { #[test] fn test_human_numeric_whitespace() { - test_helper("human-numeric-whitespace", "-h"); + test_helper( + "human-numeric-whitespace", + &["-h", "--human-numeric-sort", "--sort=human-numeric"], + ); } // This tests where serde often fails when reading back JSON @@ -102,12 +107,18 @@ fn test_extsort_as64_bailout() { #[test] fn test_multiple_decimals_general() { - test_helper("multiple_decimals_general", "-g") + test_helper( + "multiple_decimals_general", + &["-g", "--general-numeric-sort", "--sort=general-numeric"], + ) } #[test] fn test_multiple_decimals_numeric() { - test_helper("multiple_decimals_numeric", "-n") + test_helper( + "multiple_decimals_numeric", + &["-n", "--numeric-sort", "--sort=numeric"], + ) } #[test] @@ -186,72 +197,93 @@ fn test_random_shuffle_contains_two_runs_not_the_same() { #[test] fn test_numeric_floats_and_ints() { - test_helper("numeric_floats_and_ints", "-n"); + test_helper( + "numeric_floats_and_ints", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_numeric_floats() { - test_helper("numeric_floats", "-n"); + test_helper( + "numeric_floats", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_numeric_floats_with_nan() { - test_helper("numeric_floats_with_nan", "-n"); + test_helper( + "numeric_floats_with_nan", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_numeric_unfixed_floats() { - test_helper("numeric_unfixed_floats", "-n"); + test_helper( + "numeric_unfixed_floats", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_numeric_fixed_floats() { - test_helper("numeric_fixed_floats", "-n"); + test_helper( + "numeric_fixed_floats", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_numeric_unsorted_ints() { - test_helper("numeric_unsorted_ints", "-n"); + test_helper( + "numeric_unsorted_ints", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_human_block_sizes() { - test_helper("human_block_sizes", "-h"); + test_helper( + "human_block_sizes", + &["-h", "--human-numeric-sort", "--sort=human-numeric"], + ); } #[test] fn test_month_default() { - test_helper("month_default", "-M"); + test_helper("month_default", &["-M", "--month-sort", "--sort=month"]); } #[test] fn test_month_stable() { - test_helper("month_stable", "-Ms"); + test_helper("month_stable", &["-Ms"]); } #[test] fn test_default_unsorted_ints() { - test_helper("default_unsorted_ints", ""); + test_helper("default_unsorted_ints", &[""]); } #[test] fn test_numeric_unique_ints() { - test_helper("numeric_unsorted_ints_unique", "-nu"); + test_helper("numeric_unsorted_ints_unique", &["-nu"]); } #[test] fn test_version() { - test_helper("version", "-V"); + test_helper("version", &["-V"]); } #[test] fn test_ignore_case() { - test_helper("ignore_case", "-f"); + test_helper("ignore_case", &["-f"]); } #[test] fn test_dictionary_order() { - test_helper("dictionary_order", "-d"); + test_helper("dictionary_order", &["-d"]); } #[test] @@ -278,47 +310,53 @@ fn test_non_printing_chars() { #[test] fn test_exponents_positive_general_fixed() { - test_helper("exponents_general", "-g"); + test_helper("exponents_general", &["-g"]); } #[test] fn test_exponents_positive_numeric() { - test_helper("exponents-positive-numeric", "-n"); + test_helper( + "exponents-positive-numeric", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_months_dedup() { - test_helper("months-dedup", "-Mu"); + test_helper("months-dedup", &["-Mu"]); } #[test] fn test_mixed_floats_ints_chars_numeric() { - test_helper("mixed_floats_ints_chars_numeric", "-n"); + test_helper( + "mixed_floats_ints_chars_numeric", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_mixed_floats_ints_chars_numeric_unique() { - test_helper("mixed_floats_ints_chars_numeric_unique", "-nu"); + test_helper("mixed_floats_ints_chars_numeric_unique", &["-nu"]); } #[test] fn test_words_unique() { - test_helper("words_unique", "-u"); + test_helper("words_unique", &["-u"]); } #[test] fn test_numeric_unique() { - test_helper("numeric_unique", "-nu"); + test_helper("numeric_unique", &["-nu"]); } #[test] fn test_mixed_floats_ints_chars_numeric_reverse() { - test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur"); + test_helper("mixed_floats_ints_chars_numeric_unique_reverse", &["-nur"]); } #[test] fn test_mixed_floats_ints_chars_numeric_stable() { - test_helper("mixed_floats_ints_chars_numeric_stable", "-ns"); + test_helper("mixed_floats_ints_chars_numeric_stable", &["-ns"]); } #[test] @@ -347,12 +385,15 @@ fn test_numeric_floats2() { #[test] fn test_numeric_floats_with_nan2() { - test_helper("numeric-floats-with-nan2", "-n"); + test_helper( + "numeric-floats-with-nan2", + &["-n", "--numeric-sort", "--sort=numeric"], + ); } #[test] fn test_human_block_sizes2() { - for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] { + for human_numeric_sort_param in &["-h", "--human-numeric-sort", "--sort=human-numeric"] { let input = "8981K\n909991M\n-8T\n21G\n0.8M"; new_ucmd!() .arg(human_numeric_sort_param) @@ -364,7 +405,7 @@ fn test_human_block_sizes2() { #[test] fn test_month_default2() { - for month_sort_param in vec!["-M", "--month-sort"] { + for month_sort_param in &["-M", "--month-sort", "--sort=month"] { let input = "JAn\nMAY\n000may\nJun\nFeb"; new_ucmd!() .arg(month_sort_param) @@ -397,32 +438,32 @@ fn test_numeric_unique_ints2() { #[test] fn test_keys_open_ended() { - test_helper("keys_open_ended", "-k 2.3"); + test_helper("keys_open_ended", &["-k 2.3"]); } #[test] fn test_keys_closed_range() { - test_helper("keys_closed_range", "-k 2.2,2.2"); + test_helper("keys_closed_range", &["-k 2.2,2.2"]); } #[test] fn test_keys_multiple_ranges() { - test_helper("keys_multiple_ranges", "-k 2,2 -k 3,3"); + test_helper("keys_multiple_ranges", &["-k 2,2 -k 3,3"]); } #[test] fn test_keys_no_field_match() { - test_helper("keys_no_field_match", "-k 4,4"); + test_helper("keys_no_field_match", &["-k 4,4"]); } #[test] fn test_keys_no_char_match() { - test_helper("keys_no_char_match", "-k 1.2"); + test_helper("keys_no_char_match", &["-k 1.2"]); } #[test] fn test_keys_custom_separator() { - test_helper("keys_custom_separator", "-k 2.2,2.2 -t x"); + test_helper("keys_custom_separator", &["-k 2.2,2.2 -t x"]); } #[test] @@ -534,7 +575,7 @@ aaaa #[test] fn test_zero_terminated() { - test_helper("zero-terminated", "-z"); + test_helper("zero-terminated", &["-z"]); } #[test] From 4aaeede3d8475058531daf740059ed44c3a12850 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 23 May 2021 00:13:53 +0200 Subject: [PATCH 353/399] rustfmt the recent change --- src/uu/pinky/src/pinky.rs | 2 +- src/uu/sort/src/chunks.rs | 4 +--- src/uu/sort/src/sort.rs | 4 +--- src/uu/stdbuf/src/stdbuf.rs | 25 +++++++++++++------------ tests/by-util/test_stdbuf.rs | 12 ++++++------ tests/by-util/test_tail.rs | 4 ---- tests/by-util/test_who.rs | 8 ++------ 7 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index f0ab44e5f..d65775c2d 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -48,7 +48,7 @@ fn get_usage() -> String { fn get_long_usage() -> String { format!( "A lightweight 'finger' program; print user information.\n\ - The utmp file will be {}.", + The utmp file will be {}.", utmpx::DEFAULT_FILE ) } diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 7a7749003..6ec759211 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -223,9 +223,7 @@ fn read_to_buffer( Err(e) if e.kind() == ErrorKind::Interrupted => { // retry } - Err(e) => { - crash!(1, "{}", e) - } + Err(e) => crash!(1, "{}", e), } } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bc3b65492..1bbfdc5c5 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -505,9 +505,7 @@ impl KeyPosition { 'R' => settings.random = true, 'r' => settings.reverse = true, 'V' => settings.mode = SortMode::Version, - c => { - crash!(1, "invalid option for key: `{}`", c) - } + c => crash!(1, "invalid option for key: `{}`", c), } // All numeric sorts and month sort conflict with dictionary_order and ignore_non_printing. // Instad of reporting an error, let them overwrite each other. diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 77f6d9dad..485b3c70e 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -24,18 +24,19 @@ use uucore::InvalidEncodingHandling; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ - Mandatory arguments to long options are mandatory for short options too."; -static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ - This option is invalid with standard input.\n\n\ - If MODE is '0' the corresponding stream will be unbuffered.\n\n\ - Otherwise MODE is a number which may be followed by one of the following:\n\n\ - KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ - In this case the corresponding stream will be fully buffered with the buffer size set to \ - MODE bytes.\n\n\ - NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ - that will override corresponding settings changed by 'stdbuf'.\n\ - Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ - and are thus unaffected by 'stdbuf' settings.\n"; + Mandatory arguments to long options are mandatory for short options too."; +static LONG_HELP: &str = + "If MODE is 'L' the corresponding stream will be line buffered.\n\ + This option is invalid with standard input.\n\n\ + If MODE is '0' the corresponding stream will be unbuffered.\n\n\ + Otherwise MODE is a number which may be followed by one of the following:\n\n\ + KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\ + In this case the corresponding stream will be fully buffered with the buffer size set to \ + MODE bytes.\n\n\ + NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \ + that will override corresponding settings changed by 'stdbuf'.\n\ + Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \ + and are thus unaffected by 'stdbuf' settings.\n"; mod options { pub const INPUT: &str = "input"; diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 808b7382a..4105cb7a2 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -27,12 +27,12 @@ fn test_stdbuf_line_buffered_stdout() { fn test_stdbuf_no_buffer_option_fails() { new_ucmd!().args(&["head"]).fails().stderr_is( "error: The following required arguments were not provided:\n \ - --error \n \ - --input \n \ - --output \n\n\ - USAGE:\n \ - stdbuf OPTION... COMMAND\n\n\ - For more information try --help", + --error \n \ + --input \n \ + --output \n\n\ + USAGE:\n \ + stdbuf OPTION... COMMAND\n\n\ + For more information try --help", ); } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index dddbb9c31..f3c9a7b11 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -349,7 +349,6 @@ fn test_sleep_interval() { new_ucmd!().arg("-s").arg("10").arg(FOOBAR_TXT).succeeds(); } - /// Test for reading all but the first NUM bytes: `tail -c +3`. #[test] fn test_positive_bytes() { @@ -360,7 +359,6 @@ fn test_positive_bytes() { .stdout_is("cde"); } - /// Test for reading all bytes, specified by `tail -c +0`. #[test] fn test_positive_zero_bytes() { @@ -371,7 +369,6 @@ fn test_positive_zero_bytes() { .stdout_is("abcde"); } - /// Test for reading all but the first NUM lines: `tail -n +3`. #[test] fn test_positive_lines() { @@ -382,7 +379,6 @@ fn test_positive_lines() { .stdout_is("c\nd\ne\n"); } - /// Test for reading all lines, specified by `tail -n +0`. #[test] fn test_positive_zero_lines() { diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 1aa8d604d..df023bb0a 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -97,13 +97,9 @@ fn test_runlevel() { #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] #[test] fn test_runlevel() { - let expected = - "error: Found argument"; + let expected = "error: Found argument"; for opt in vec!["-r", "--runlevel"] { - new_ucmd!() - .arg(opt) - .fails() - .stderr_contains(expected); + new_ucmd!().arg(opt).fails().stderr_contains(expected); } } From 95092e64402cf5feadeb4d5f496cc1f8cbdd239e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 23 May 2021 00:33:54 +0200 Subject: [PATCH 354/399] ignore test_should_calculate_implicit_padding_per_free_argument Fails from time to time with ``` ---- test_numfmt::test_should_calculate_implicit_padding_per_free_argument stdout ---- current_directory_resolved: run: /target/x86_64-unknown-linux-musl/debug/coreutils numfmt --from=auto 1Ki 2K thread 'test_numfmt::test_should_calculate_implicit_padding_per_free_argument' panicked at 'failed to write to stdin of child: Broken pipe (os error 32)', tests/common/util.rs:859:21 ``` --- tests/by-util/test_numfmt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index 64fc5360d..b52dbc359 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -281,6 +281,7 @@ fn test_leading_whitespace_in_free_argument_should_imply_padding() { } #[test] +#[ignore] fn test_should_calculate_implicit_padding_per_free_argument() { new_ucmd!() .args(&["--from=auto", " 1Ki", " 2K"]) From 44c033a013a12adf4123d3c112c38700fc462001 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sun, 23 May 2021 02:07:32 +0200 Subject: [PATCH 355/399] who: exclude --runlevel from non Linux targets (fix #2239) --- src/uu/who/src/who.rs | 39 ++++++++++++++++++--------------------- tests/by-util/test_who.rs | 26 +++++++------------------- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 81fc2a687..19ae3addb 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -29,7 +29,6 @@ mod options { pub const ONLY_HOSTNAME_USER: &str = "only_hostname_user"; pub const PROCESS: &str = "process"; pub const COUNT: &str = "count"; - #[cfg(any(target_os = "linux", target_os = "android"))] pub const RUNLEVEL: &str = "runlevel"; pub const SHORT: &str = "short"; pub const TIME: &str = "time"; @@ -41,6 +40,11 @@ mod options { static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Print information about users who are currently logged in."; +#[cfg(any(target_os = "linux"))] +static RUNLEVEL_HELP: &str = "print current runlevel"; +#[cfg(not(target_os = "linux"))] +static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)"; + fn get_usage() -> String { format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", executable!()) } @@ -119,13 +123,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("all login names and number of users logged on"), ) .arg( - #[cfg(any(target_os = "linux", target_os = "android"))] Arg::with_name(options::RUNLEVEL) .long(options::RUNLEVEL) .short("r") - .help("print current runlevel"), - #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] - Arg::with_name(""), + .help(RUNLEVEL_HELP), ) .arg( Arg::with_name(options::SHORT) @@ -267,13 +268,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { assumptions = false; } - #[cfg(any(target_os = "linux", target_os = "android"))] - { - if matches.is_present(options::RUNLEVEL) { - need_runlevel = true; - include_idle = true; - assumptions = false; - } + if matches.is_present(options::RUNLEVEL) { + need_runlevel = true; + include_idle = true; + assumptions = false; } if matches.is_present(options::SHORT) { @@ -389,15 +387,12 @@ fn current_tty() -> String { impl Who { fn exec(&mut self) { - let run_level_chk = |record: i16| { - #[allow(unused_assignments)] - let mut res = false; + let run_level_chk = |_record: i16| { + #[cfg(not(target_os = "linux"))] + return false; - #[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))] - { - res = record == utmpx::RUN_LVL; - } - res + #[cfg(target_os = "linux")] + return _record == utmpx::RUN_LVL; }; let f = if self.args.len() == 1 { @@ -430,7 +425,9 @@ impl Who { if self.need_users && ut.is_user_process() { self.print_user(&ut); } else if self.need_runlevel && run_level_chk(ut.record_type()) { - self.print_runlevel(&ut); + if cfg!(target_os = "linux") { + self.print_runlevel(&ut); + } } else if self.need_boottime && ut.record_type() == utmpx::BOOT_TIME { self.print_boottime(&ut); } else if self.need_clockchange && ut.record_type() == utmpx::NEW_TIME { diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 1aa8d604d..21b5eb93e 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -83,27 +83,17 @@ fn test_process() { } } -#[cfg(target_os = "linux")] #[test] fn test_runlevel() { for opt in vec!["-r", "--runlevel"] { + #[cfg(any(target_vendor = "apple", target_os = "linux"))] new_ucmd!() .arg(opt) .succeeds() .stdout_is(expected_result(&[opt])); - } -} -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] -#[test] -fn test_runlevel() { - let expected = - "error: Found argument"; - for opt in vec!["-r", "--runlevel"] { - new_ucmd!() - .arg(opt) - .fails() - .stderr_contains(expected); + #[cfg(not(target_os = "linux"))] + new_ucmd!().arg(opt).succeeds().stdout_is(""); } } @@ -135,7 +125,6 @@ fn test_mesg() { } } -#[cfg(target_os = "linux")] #[test] fn test_arg1_arg2() { let args = ["am", "i"]; @@ -146,7 +135,6 @@ fn test_arg1_arg2() { .stdout_is(expected_result(&args)); } -#[cfg(target_os = "linux")] #[test] fn test_too_many_args() { const EXPECTED: &str = @@ -168,11 +156,11 @@ fn test_users() { let mut v_actual: Vec<&str> = actual.split_whitespace().collect(); let mut v_expect: Vec<&str> = expect.split_whitespace().collect(); - // TODO: `--users` differs from GNU's output on manOS running in CI + // TODO: `--users` differs from GNU's output on macOS // Diff < left / right > : // <"runner console 2021-05-20 22:03 00:08 196\n" // >"runner console 2021-05-20 22:03 old 196\n" - if is_ci() && cfg!(target_os = "macos") { + if cfg!(target_os = "macos") { v_actual.remove(4); v_expect.remove(4); } @@ -206,7 +194,7 @@ fn test_dead() { #[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_all_separately() { - if is_ci() && cfg!(target_os = "macos") { + if cfg!(target_os = "macos") { // TODO: fix `-u`, see: test_users return; } @@ -229,7 +217,7 @@ fn test_all_separately() { #[cfg(any(target_vendor = "apple", target_os = "linux"))] #[test] fn test_all() { - if is_ci() && cfg!(target_os = "macos") { + if cfg!(target_os = "macos") { // TODO: fix `-u`, see: test_users return; } From a746e37dc7e087159ede20c9c887015d77ff6221 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 21 May 2021 18:20:05 -0400 Subject: [PATCH 356/399] truncate: add test for -r and -s options together Add a test for when the reference file is not found and both `-r` and `-s` options are given on the command-line. --- tests/by-util/test_truncate.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 120982e3c..6323b058f 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -262,3 +262,11 @@ fn test_reference_file_not_found() { .fails() .stderr_contains("cannot stat 'a': No such file or directory"); } + +#[test] +fn test_reference_with_size_file_not_found() { + new_ucmd!() + .args(&["-r", "a", "-s", "+1", "b"]) + .fails() + .stderr_contains("cannot stat 'a': No such file or directory"); +} From 5eb2a5c3e1e72e7d01ce87eed55928f37b14d5ca Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 21 May 2021 18:23:50 -0400 Subject: [PATCH 357/399] truncate: remove read permissions from OpenOptions Remove "read" permissions from the `OpenOptions` when opening a new file just to truncate it. We will never read from the file, only write to it. (Specifically, we will only call `File::set_len()`.) --- src/uu/truncate/src/truncate.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 03b18723c..086e14858 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -189,12 +189,7 @@ fn truncate( }; for filename in &filenames { let path = Path::new(filename); - match OpenOptions::new() - .read(true) - .write(true) - .create(!no_create) - .open(path) - { + match OpenOptions::new().write(true).create(!no_create).open(path) { Ok(file) => { let fsize = match reference { Some(_) => refsize, From 544ae875753b050a5073278e8bcb8af893e31de0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 21 May 2021 21:06:45 -0400 Subject: [PATCH 358/399] truncate: add parse_mode_and_size() helper func Add a helper function to contain the code for parsing the size and the modifier symbol, if any. This commit also changes the `TruncateMode` enum so that the parameter for each "mode" is stored along with the enumeration value. This is because the parameter has a different meaning in each mode. --- src/uu/truncate/src/truncate.rs | 136 ++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 51 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 086e14858..9df775300 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -15,16 +15,16 @@ use std::fs::{metadata, OpenOptions}; use std::io::ErrorKind; use std::path::Path; -#[derive(Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] enum TruncateMode { - Absolute, - Reference, - Extend, - Reduce, - AtMost, - AtLeast, - RoundDown, - RoundUp, + Reference(u64), + Absolute(u64), + Extend(u64), + Reduce(u64), + AtMost(u64), + AtLeast(u64), + RoundDown(u64), + RoundUp(u64), } static ABOUT: &str = "Shrink or extend the size of each file to the specified size."; @@ -133,46 +133,21 @@ fn truncate( size: Option, filenames: Vec, ) { - let (modsize, mode) = match size { - Some(size_string) => { - // Trim any whitespace. - let size_string = size_string.trim(); - - // Get the modifier character from the size string, if any. For - // example, if the argument is "+123", then the modifier is '+'. - let c = size_string.chars().next().unwrap(); - - let mode = match c { - '+' => TruncateMode::Extend, - '-' => TruncateMode::Reduce, - '<' => TruncateMode::AtMost, - '>' => TruncateMode::AtLeast, - '/' => TruncateMode::RoundDown, - '%' => TruncateMode::RoundUp, - _ => TruncateMode::Absolute, /* assume that the size is just a number */ - }; - - // If there was a modifier character, strip it. - let size_string = match mode { - TruncateMode::Absolute => size_string, - _ => &size_string[1..], - }; - let num_bytes = match parse_size(size_string) { - Ok(b) => b, - Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), - }; - (num_bytes, mode) - } - None => (0, TruncateMode::Reference), + let mode = match size { + Some(size_string) => match parse_mode_and_size(&size_string) { + Ok(m) => m, + Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + }, + None => TruncateMode::Reference(0), }; let refsize = match reference { Some(ref rfilename) => { match mode { // Only Some modes work with a reference - TruncateMode::Reference => (), //No --size was given - TruncateMode::Extend => (), - TruncateMode::Reduce => (), + TruncateMode::Reference(_) => (), //No --size was given + TruncateMode::Extend(_) => (), + TruncateMode::Reduce(_) => (), _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"), }; match metadata(rfilename) { @@ -202,14 +177,14 @@ fn truncate( }, }; let tsize: u64 = match mode { - TruncateMode::Absolute => modsize, - TruncateMode::Reference => fsize, - TruncateMode::Extend => fsize + modsize, - TruncateMode::Reduce => fsize - modsize, - TruncateMode::AtMost => fsize.min(modsize), - TruncateMode::AtLeast => fsize.max(modsize), - TruncateMode::RoundDown => fsize - fsize % modsize, - TruncateMode::RoundUp => fsize + fsize % modsize, + TruncateMode::Absolute(modsize) => modsize, + TruncateMode::Reference(_) => fsize, + TruncateMode::Extend(modsize) => fsize + modsize, + TruncateMode::Reduce(modsize) => fsize - modsize, + TruncateMode::AtMost(modsize) => fsize.min(modsize), + TruncateMode::AtLeast(modsize) => fsize.max(modsize), + TruncateMode::RoundDown(modsize) => fsize - fsize % modsize, + TruncateMode::RoundUp(modsize) => fsize + fsize % modsize, }; match file.set_len(tsize) { Ok(_) => {} @@ -221,6 +196,52 @@ fn truncate( } } +/// Decide whether a character is one of the size modifiers, like '+' or '<'. +fn is_modifier(c: char) -> bool { + c == '+' || c == '-' || c == '<' || c == '>' || c == '/' || c == '%' +} + +/// Parse a size string with optional modifier symbol as its first character. +/// +/// A size string is as described in [`parse_size`]. The first character +/// of `size_string` might be a modifier symbol, like `'+'` or +/// `'<'`. The first element of the pair returned by this function +/// indicates which modifier symbol was present, or +/// [`TruncateMode::Absolute`] if none. +/// +/// # Panics +/// +/// If `size_string` is empty, or if no number could be parsed from the +/// given string (for example, if the string were `"abc"`). +/// +/// # Examples +/// +/// ```rust,ignore +/// assert_eq!(parse_mode_and_size("+123"), (TruncateMode::Extend, 123)); +/// ``` +fn parse_mode_and_size(size_string: &str) -> Result { + // Trim any whitespace. + let size_string = size_string.trim(); + + // Get the modifier character from the size string, if any. For + // example, if the argument is "+123", then the modifier is '+'. + let c = size_string.chars().next().unwrap(); + let size_string = if is_modifier(c) { + &size_string[1..] + } else { + size_string + }; + parse_size(size_string).map(match c { + '+' => TruncateMode::Extend, + '-' => TruncateMode::Reduce, + '<' => TruncateMode::AtMost, + '>' => TruncateMode::AtLeast, + '/' => TruncateMode::RoundDown, + '%' => TruncateMode::RoundUp, + _ => TruncateMode::Absolute, + }) +} + /// Parse a size string into a number of bytes. /// /// A size string comprises an integer and an optional unit. The unit @@ -280,7 +301,9 @@ fn parse_size(size: &str) -> Result { #[cfg(test)] mod tests { + use crate::parse_mode_and_size; use crate::parse_size; + use crate::TruncateMode; #[test] fn test_parse_size_zero() { @@ -306,4 +329,15 @@ mod tests { assert_eq!(parse_size("123M").unwrap(), 123 * 1024 * 1024); assert_eq!(parse_size("123MB").unwrap(), 123 * 1000 * 1000); } + + #[test] + fn test_parse_mode_and_size() { + assert_eq!(parse_mode_and_size("10"), Ok(TruncateMode::Absolute(10))); + assert_eq!(parse_mode_and_size("+10"), Ok(TruncateMode::Extend(10))); + assert_eq!(parse_mode_and_size("-10"), Ok(TruncateMode::Reduce(10))); + assert_eq!(parse_mode_and_size("<10"), Ok(TruncateMode::AtMost(10))); + assert_eq!(parse_mode_and_size(">10"), Ok(TruncateMode::AtLeast(10))); + assert_eq!(parse_mode_and_size("/10"), Ok(TruncateMode::RoundDown(10))); + assert_eq!(parse_mode_and_size("%10"), Ok(TruncateMode::RoundUp(10))); + } } From c6d4d0c07d1f1ed5d6dbe58ca0eda5e9c939b2c2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 21 May 2021 21:09:33 -0400 Subject: [PATCH 359/399] truncate: create TruncateMode::to_size() method Create a method that computes the final target size in bytes for the file to truncate, given the reference file size and the parameter to the `TruncateMode`. --- src/uu/truncate/src/truncate.rs | 37 ++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 9df775300..c0f078458 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -27,6 +27,32 @@ enum TruncateMode { RoundUp(u64), } +impl TruncateMode { + /// Compute a target size in bytes for this truncate mode. + /// + /// `fsize` is the size of the reference file, in bytes. + /// + /// # Examples + /// + /// ```rust,ignore + /// let mode = TruncateMode::Extend(5); + /// let fsize = 10; + /// assert_eq!(mode.to_size(fsize), 15); + /// ``` + fn to_size(&self, fsize: u64) -> u64 { + match self { + TruncateMode::Absolute(modsize) => *modsize, + TruncateMode::Reference(_) => fsize, + TruncateMode::Extend(modsize) => fsize + modsize, + TruncateMode::Reduce(modsize) => fsize - modsize, + TruncateMode::AtMost(modsize) => fsize.min(*modsize), + TruncateMode::AtLeast(modsize) => fsize.max(*modsize), + TruncateMode::RoundDown(modsize) => fsize - fsize % modsize, + TruncateMode::RoundUp(modsize) => fsize + fsize % modsize, + } + } +} + static ABOUT: &str = "Shrink or extend the size of each file to the specified size."; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -176,16 +202,7 @@ fn truncate( } }, }; - let tsize: u64 = match mode { - TruncateMode::Absolute(modsize) => modsize, - TruncateMode::Reference(_) => fsize, - TruncateMode::Extend(modsize) => fsize + modsize, - TruncateMode::Reduce(modsize) => fsize - modsize, - TruncateMode::AtMost(modsize) => fsize.min(modsize), - TruncateMode::AtLeast(modsize) => fsize.max(modsize), - TruncateMode::RoundDown(modsize) => fsize - fsize % modsize, - TruncateMode::RoundUp(modsize) => fsize + fsize % modsize, - }; + let tsize = mode.to_size(fsize); match file.set_len(tsize) { Ok(_) => {} Err(f) => crash!(1, "{}", f.to_string()), From 1f1cd3d966cd4317c2eec8e8dfdcfb350f79fcf4 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 21 May 2021 21:12:13 -0400 Subject: [PATCH 360/399] truncate: re-organize into one func for each mode Reorganize the code in `truncate.rs` into three distinct functions representing the three modes of operation of the `truncate` program. The three modes are - `truncate -r RFILE FILE`, which sets the length of `FILE` to match the length of `RFILE`, - `truncate -r RFILE -s NUM FILE`, which sets the length of `FILE` relative to the given `RFILE`, - `truncate -s NUM FILE`, which sets the length of `FILE` either absolutely or relative to its curent length. This organization of the code makes it more concise and easier to follow. --- src/uu/truncate/src/truncate.rs | 196 ++++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 57 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index c0f078458..3a6077b3c 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -17,7 +17,6 @@ use std::path::Path; #[derive(Debug, Eq, PartialEq)] enum TruncateMode { - Reference(u64), Absolute(u64), Extend(u64), Reduce(u64), @@ -42,7 +41,6 @@ impl TruncateMode { fn to_size(&self, fsize: u64) -> u64 { match self { TruncateMode::Absolute(modsize) => *modsize, - TruncateMode::Reference(_) => fsize, TruncateMode::Extend(modsize) => fsize + modsize, TruncateMode::Reduce(modsize) => fsize - modsize, TruncateMode::AtMost(modsize) => fsize.min(*modsize), @@ -142,74 +140,158 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let no_create = matches.is_present(options::NO_CREATE); let reference = matches.value_of(options::REFERENCE).map(String::from); let size = matches.value_of(options::SIZE).map(String::from); - if reference.is_none() && size.is_none() { - crash!(1, "you must specify either --reference or --size"); - } else { - truncate(no_create, io_blocks, reference, size, files); + if let Err(e) = truncate(no_create, io_blocks, reference, size, files) { + match e.kind() { + ErrorKind::NotFound => { + // TODO Improve error-handling so that the error + // returned by `truncate()` provides the necessary + // parameter for formatting the error message. + let reference = matches.value_of(options::REFERENCE).map(String::from); + crash!( + 1, + "cannot stat '{}': No such file or directory", + reference.unwrap() + ); + } + _ => crash!(1, "{}", e.to_string()), + } } } 0 } +/// Truncate the named file to the specified size. +/// +/// If `create` is true, then the file will be created if it does not +/// already exist. If `size` is larger than the number of bytes in the +/// file, then the file will be padded with zeros. If `size` is smaller +/// than the number of bytes in the file, then the file will be +/// truncated and any bytes beyond `size` will be lost. +/// +/// # Errors +/// +/// If the file could not be opened, or there was a problem setting the +/// size of the file. +fn file_truncate(filename: &str, create: bool, size: u64) -> std::io::Result<()> { + let path = Path::new(filename); + let f = OpenOptions::new().write(true).create(create).open(path)?; + f.set_len(size) +} + +/// Truncate files to a size relative to a given file. +/// +/// `rfilename` is the name of the reference file. +/// +/// `size_string` gives the size relative to the reference file to which +/// to set the target files. For example, "+3K" means "set each file to +/// be three kilobytes larger than the size of the reference file". +/// +/// If `create` is true, then each file will be created if it does not +/// already exist. +/// +/// # Errors +/// +/// If the any file could not be opened, or there was a problem setting +/// the size of at least one file. +fn truncate_reference_and_size( + rfilename: &str, + size_string: &str, + filenames: Vec, + create: bool, +) -> std::io::Result<()> { + let mode = match parse_mode_and_size(size_string) { + Ok(m) => match m { + TruncateMode::Absolute(_) => { + crash!(1, "you must specify a relative ‘--size’ with ‘--reference’") + } + _ => m, + }, + Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + }; + let fsize = metadata(rfilename)?.len(); + let tsize = mode.to_size(fsize); + for filename in &filenames { + file_truncate(filename, create, tsize)?; + } + Ok(()) +} + +/// Truncate files to match the size of a given reference file. +/// +/// `rfilename` is the name of the reference file. +/// +/// If `create` is true, then each file will be created if it does not +/// already exist. +/// +/// # Errors +/// +/// If the any file could not be opened, or there was a problem setting +/// the size of at least one file. +fn truncate_reference_file_only( + rfilename: &str, + filenames: Vec, + create: bool, +) -> std::io::Result<()> { + let tsize = metadata(rfilename)?.len(); + for filename in &filenames { + file_truncate(filename, create, tsize)?; + } + Ok(()) +} + +/// Truncate files to a specified size. +/// +/// `size_string` gives either an absolute size or a relative size. A +/// relative size adjusts the size of each file relative to its current +/// size. For example, "3K" means "set each file to be three kilobytes" +/// whereas "+3K" means "set each file to be three kilobytes larger than +/// its current size". +/// +/// If `create` is true, then each file will be created if it does not +/// already exist. +/// +/// # Errors +/// +/// If the any file could not be opened, or there was a problem setting +/// the size of at least one file. +fn truncate_size_only( + size_string: &str, + filenames: Vec, + create: bool, +) -> std::io::Result<()> { + let mode = match parse_mode_and_size(size_string) { + Ok(m) => m, + Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), + }; + for filename in &filenames { + let fsize = metadata(filename).map(|m| m.len()).unwrap_or(0); + let tsize = mode.to_size(fsize); + file_truncate(filename, create, tsize)?; + } + Ok(()) +} + fn truncate( no_create: bool, _: bool, reference: Option, size: Option, filenames: Vec, -) { - let mode = match size { - Some(size_string) => match parse_mode_and_size(&size_string) { - Ok(m) => m, - Err(_) => crash!(1, "Invalid number: ‘{}’", size_string), - }, - None => TruncateMode::Reference(0), - }; - - let refsize = match reference { - Some(ref rfilename) => { - match mode { - // Only Some modes work with a reference - TruncateMode::Reference(_) => (), //No --size was given - TruncateMode::Extend(_) => (), - TruncateMode::Reduce(_) => (), - _ => crash!(1, "you must specify a relative ‘--size’ with ‘--reference’"), - }; - match metadata(rfilename) { - Ok(meta) => meta.len(), - Err(f) => match f.kind() { - ErrorKind::NotFound => { - crash!(1, "cannot stat '{}': No such file or directory", rfilename) - } - _ => crash!(1, "{}", f.to_string()), - }, - } - } - None => 0, - }; - for filename in &filenames { - let path = Path::new(filename); - match OpenOptions::new().write(true).create(!no_create).open(path) { - Ok(file) => { - let fsize = match reference { - Some(_) => refsize, - None => match metadata(filename) { - Ok(meta) => meta.len(), - Err(f) => { - show_warning!("{}", f.to_string()); - continue; - } - }, - }; - let tsize = mode.to_size(fsize); - match file.set_len(tsize) { - Ok(_) => {} - Err(f) => crash!(1, "{}", f.to_string()), - }; - } - Err(f) => crash!(1, "{}", f.to_string()), +) -> std::io::Result<()> { + let create = !no_create; + // There are four possibilities + // - reference file given and size given, + // - reference file given but no size given, + // - no reference file given but size given, + // - no reference file given and no size given, + match (reference, size) { + (Some(rfilename), Some(size_string)) => { + truncate_reference_and_size(&rfilename, &size_string, filenames, create) } + (Some(rfilename), None) => truncate_reference_file_only(&rfilename, filenames, create), + (None, Some(size_string)) => truncate_size_only(&size_string, filenames, create), + (None, None) => crash!(1, "you must specify either --reference or --size"), } } From bc9db289e8edad21de4b2e57a457542d6b1280ee Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 11 May 2021 23:48:06 -0400 Subject: [PATCH 361/399] head: add abstractions for "all but last n lines" Add some abstractions to simplify the `rbuf_but_last_n_lines()` function, which implements the "take all but the last `n` lines" functionality of the `head` program. This commit adds - `RingBuffer`, a fixed-size ring buffer, - `ZLines`, an iterator over zero-terminated "lines", - `TakeAllBut`, an iterator over all but the last `n` elements of an iterator. These three together make the implementation of `rbuf_but_last_n_lines()` concise. --- src/uu/head/Cargo.toml | 2 +- src/uu/head/src/head.rs | 42 +++---- src/uu/head/src/lines.rs | 73 ++++++++++++ src/uu/head/src/take.rs | 93 +++++++++++++++ src/uu/tail/Cargo.toml | 2 +- src/uu/tail/src/ringbuffer.rs | 61 ---------- src/uu/tail/src/tail.rs | 3 +- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 2 + src/uucore/src/lib/features/ringbuffer.rs | 134 ++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 2 + tests/by-util/test_head.rs | 9 ++ 12 files changed, 333 insertions(+), 91 deletions(-) create mode 100644 src/uu/head/src/lines.rs create mode 100644 src/uu/head/src/take.rs delete mode 100644 src/uu/tail/src/ringbuffer.rs create mode 100644 src/uucore/src/lib/features/ringbuffer.rs diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 3c383cb6f..661052f58 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -16,7 +16,7 @@ path = "src/head.rs" [dependencies] clap = "2.33" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } [[bin]] diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 0c8b3bc88..3602b4a73 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -27,8 +27,12 @@ mod options { pub const ZERO_NAME: &str = "ZERO"; pub const FILES_NAME: &str = "FILE"; } +mod lines; mod parse; mod split; +mod take; +use lines::zlines; +use take::take_all_but; fn app<'a>() -> App<'a, 'a> { App::new(executable!()) @@ -293,36 +297,22 @@ fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io } fn rbuf_but_last_n_lines( - input: &mut impl std::io::BufRead, + input: impl std::io::BufRead, n: usize, zero: bool, ) -> std::io::Result<()> { - if n == 0 { - //prints everything - return rbuf_n_bytes(input, std::usize::MAX); + if zero { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + for bytes in take_all_but(zlines(input), n) { + stdout.write_all(&bytes?)?; + } + } else { + for line in take_all_but(input.lines(), n) { + println!("{}", line?); + } } - let mut ringbuf = vec![Vec::new(); n]; - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - let mut line = Vec::new(); - let mut lines = 0usize; - split::walk_lines(input, zero, |e| match e { - split::Event::Data(dat) => { - line.extend_from_slice(dat); - Ok(true) - } - split::Event::Line => { - if lines < n { - ringbuf[lines] = std::mem::replace(&mut line, Vec::new()); - lines += 1; - } else { - stdout.write_all(&ringbuf[0])?; - ringbuf.rotate_left(1); - ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new()); - } - Ok(true) - } - }) + Ok(()) } fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { diff --git a/src/uu/head/src/lines.rs b/src/uu/head/src/lines.rs new file mode 100644 index 000000000..dcae27bc8 --- /dev/null +++ b/src/uu/head/src/lines.rs @@ -0,0 +1,73 @@ +//! Iterate over zero-terminated lines. +use std::io::BufRead; + +/// The zero byte, representing the null character. +const ZERO: u8 = 0; + +/// Returns an iterator over the lines of the given reader. +/// +/// The iterator returned from this function will yield instances of +/// [`io::Result`]<[`Vec`]<[`u8`]>>, representing the bytes of the line +/// *including* the null character (with the possible exception of the +/// last line, which may not have one). +/// +/// # Examples +/// +/// ```rust,ignore +/// use std::io::Cursor; +/// +/// let cursor = Cursor::new(b"x\0y\0z\0"); +/// let mut iter = zlines(cursor).map(|l| l.unwrap()); +/// assert_eq!(iter.next(), Some(b"x\0".to_vec())); +/// assert_eq!(iter.next(), Some(b"y\0".to_vec())); +/// assert_eq!(iter.next(), Some(b"z\0".to_vec())); +/// assert_eq!(iter.next(), None); +/// ``` +pub fn zlines(buf: B) -> ZLines { + ZLines { buf } +} + +/// An iterator over the zero-terminated lines of an instance of `BufRead`. +pub struct ZLines { + buf: B, +} + +impl Iterator for ZLines { + type Item = std::io::Result>; + + fn next(&mut self) -> Option>> { + let mut buf = Vec::new(); + match self.buf.read_until(ZERO, &mut buf) { + Ok(0) => None, + Ok(_) => Some(Ok(buf)), + Err(e) => Some(Err(e)), + } + } +} + +#[cfg(test)] +mod tests { + + use crate::lines::zlines; + use std::io::Cursor; + + #[test] + fn test_null_terminated() { + let cursor = Cursor::new(b"x\0y\0z\0"); + let mut iter = zlines(cursor).map(|l| l.unwrap()); + assert_eq!(iter.next(), Some(b"x\0".to_vec())); + assert_eq!(iter.next(), Some(b"y\0".to_vec())); + assert_eq!(iter.next(), Some(b"z\0".to_vec())); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_not_null_terminated() { + let cursor = Cursor::new(b"x\0y\0z"); + let mut iter = zlines(cursor).map(|l| l.unwrap()); + assert_eq!(iter.next(), Some(b"x\0".to_vec())); + assert_eq!(iter.next(), Some(b"y\0".to_vec())); + assert_eq!(iter.next(), Some(b"z".to_vec())); + assert_eq!(iter.next(), None); + } +} diff --git a/src/uu/head/src/take.rs b/src/uu/head/src/take.rs new file mode 100644 index 000000000..94fa012be --- /dev/null +++ b/src/uu/head/src/take.rs @@ -0,0 +1,93 @@ +//! Take all but the last elements of an iterator. +use uucore::ringbuffer::RingBuffer; + +/// Create an iterator over all but the last `n` elements of `iter`. +/// +/// # Examples +/// +/// ```rust,ignore +/// let data = [1, 2, 3, 4, 5]; +/// let n = 2; +/// let mut iter = take_all_but(data.iter(), n); +/// assert_eq!(Some(4), iter.next()); +/// assert_eq!(Some(5), iter.next()); +/// assert_eq!(None, iter.next()); +/// ``` +pub fn take_all_but(iter: I, n: usize) -> TakeAllBut { + TakeAllBut::new(iter, n) +} + +/// An iterator that only iterates over the last elements of another iterator. +pub struct TakeAllBut { + iter: I, + buf: RingBuffer<::Item>, +} + +impl TakeAllBut { + pub fn new(mut iter: I, n: usize) -> TakeAllBut { + // Create a new ring buffer and fill it up. + // + // If there are fewer than `n` elements in `iter`, then we + // exhaust the iterator so that whenever `TakeAllBut::next()` is + // called, it will return `None`, as expected. + let mut buf = RingBuffer::new(n); + for _ in 0..n { + let value = match iter.next() { + None => { + break; + } + Some(x) => x, + }; + buf.push_back(value); + } + TakeAllBut { iter, buf } + } +} + +impl Iterator for TakeAllBut +where + I: Iterator, +{ + type Item = ::Item; + + fn next(&mut self) -> Option<::Item> { + match self.iter.next() { + Some(value) => self.buf.push_back(value), + None => None, + } + } +} + +#[cfg(test)] +mod tests { + + use crate::take::take_all_but; + + #[test] + fn test_fewer_elements() { + let mut iter = take_all_but([0, 1, 2].iter(), 2); + assert_eq!(Some(&0), iter.next()); + assert_eq!(None, iter.next()); + } + + #[test] + fn test_same_number_of_elements() { + let mut iter = take_all_but([0, 1].iter(), 2); + assert_eq!(None, iter.next()); + } + + #[test] + fn test_more_elements() { + let mut iter = take_all_but([0].iter(), 2); + assert_eq!(None, iter.next()); + } + + #[test] + fn test_zero_elements() { + let mut iter = take_all_but([0, 1, 2].iter(), 0); + assert_eq!(Some(&0), iter.next()); + assert_eq!(Some(&1), iter.next()); + assert_eq!(Some(&2), iter.next()); + assert_eq!(None, iter.next()); + } +} diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index d3f60e09b..273c67bb3 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -17,7 +17,7 @@ path = "src/tail.rs" [dependencies] clap = "2.33" libc = "0.2.42" -uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } diff --git a/src/uu/tail/src/ringbuffer.rs b/src/uu/tail/src/ringbuffer.rs deleted file mode 100644 index 86483b8ed..000000000 --- a/src/uu/tail/src/ringbuffer.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! A fixed-size ring buffer. -use std::collections::VecDeque; - -/// A fixed-size ring buffer backed by a `VecDeque`. -/// -/// If the ring buffer is not full, then calling the [`push_back`] -/// method appends elements, as in a [`VecDeque`]. If the ring buffer -/// is full, then calling [`push_back`] removes the element at the -/// front of the buffer (in a first-in, first-out manner) before -/// appending the new element to the back of the buffer. -/// -/// Use [`from_iter`] to take the last `size` elements from an -/// iterator. -/// -/// # Examples -/// -/// After exceeding the size limit, the oldest elements are dropped in -/// favor of the newest element: -/// -/// ```rust,ignore -/// let buffer: RingBuffer = RingBuffer::new(2); -/// buffer.push_back(0); -/// buffer.push_back(1); -/// buffer.push_back(2); -/// assert_eq!(vec![1, 2], buffer.data); -/// ``` -/// -/// Take the last `n` elements from an iterator: -/// -/// ```rust,ignore -/// let iter = vec![0, 1, 2, 3].iter(); -/// assert_eq!(vec![2, 3], RingBuffer::from_iter(iter, 2).data); -/// ``` -pub struct RingBuffer { - pub data: VecDeque, - size: usize, -} - -impl RingBuffer { - pub fn new(size: usize) -> RingBuffer { - RingBuffer { - data: VecDeque::new(), - size, - } - } - - pub fn from_iter(iter: impl Iterator, size: usize) -> RingBuffer { - let mut ringbuf = RingBuffer::new(size); - for value in iter { - ringbuf.push_back(value); - } - ringbuf - } - - pub fn push_back(&mut self, value: T) { - if self.size <= self.data.len() { - self.data.pop_front(); - } - self.data.push_back(value) - } -} diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 06d0e6fdb..15a819d35 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -17,9 +17,7 @@ extern crate uucore; mod chunks; mod platform; -mod ringbuffer; use chunks::ReverseChunks; -use ringbuffer::RingBuffer; use clap::{App, Arg}; use std::collections::VecDeque; @@ -30,6 +28,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::thread::sleep; use std::time::Duration; +use uucore::ringbuffer::RingBuffer; pub mod options { pub mod verbosity { diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 85efe0434..482252680 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -47,6 +47,7 @@ mode = ["libc"] parse_time = [] perms = ["libc"] process = ["libc"] +ringbuffer = [] signals = [] utf8 = [] utmpx = ["time", "libc"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 0287b9675..310a41fe1 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -8,6 +8,8 @@ pub mod fs; pub mod fsext; #[cfg(feature = "parse_time")] pub mod parse_time; +#[cfg(feature = "ringbuffer")] +pub mod ringbuffer; #[cfg(feature = "zero-copy")] pub mod zero_copy; diff --git a/src/uucore/src/lib/features/ringbuffer.rs b/src/uucore/src/lib/features/ringbuffer.rs new file mode 100644 index 000000000..60847df8f --- /dev/null +++ b/src/uucore/src/lib/features/ringbuffer.rs @@ -0,0 +1,134 @@ +//! A fixed-size ring buffer. +use std::collections::VecDeque; + +/// A fixed-size ring buffer backed by a `VecDeque`. +/// +/// If the ring buffer is not full, then calling the [`push_back`] +/// method appends elements, as in a [`VecDeque`]. If the ring buffer +/// is full, then calling [`push_back`] removes the element at the +/// front of the buffer (in a first-in, first-out manner) before +/// appending the new element to the back of the buffer. +/// +/// Use [`from_iter`] to take the last `size` elements from an +/// iterator. +/// +/// # Examples +/// +/// After exceeding the size limit, the oldest elements are dropped in +/// favor of the newest element: +/// +/// ```rust,ignore +/// let mut buffer: RingBuffer = RingBuffer::new(2); +/// buffer.push_back(0); +/// buffer.push_back(1); +/// buffer.push_back(2); +/// assert_eq!(vec![1, 2], buffer.data); +/// ``` +/// +/// Take the last `n` elements from an iterator: +/// +/// ```rust,ignore +/// let iter = [0, 1, 2].iter(); +/// let actual = RingBuffer::from_iter(iter, 2).data; +/// let expected = VecDeque::from_iter([1, 2].iter()); +/// assert_eq!(expected, actual); +/// ``` +pub struct RingBuffer { + pub data: VecDeque, + size: usize, +} + +impl RingBuffer { + pub fn new(size: usize) -> RingBuffer { + RingBuffer { + data: VecDeque::new(), + size, + } + } + + pub fn from_iter(iter: impl Iterator, size: usize) -> RingBuffer { + let mut ringbuf = RingBuffer::new(size); + for value in iter { + ringbuf.push_back(value); + } + ringbuf + } + + /// Append a value to the end of the ring buffer. + /// + /// If the ring buffer is not full, this method return [`None`]. If + /// the ring buffer is full, appending a new element will cause the + /// oldest element to be evicted. In that case this method returns + /// that element, or `None`. + /// + /// In the special case where the size limit is zero, each call to + /// this method with input `value` returns `Some(value)`, because + /// the input is immediately evicted. + /// + /// # Examples + /// + /// Appending an element when the buffer is full returns the oldest + /// element: + /// + /// ```rust,ignore + /// let mut buf = RingBuffer::new(3); + /// assert_eq!(None, buf.push_back(0)); + /// assert_eq!(None, buf.push_back(1)); + /// assert_eq!(None, buf.push_back(2)); + /// assert_eq!(Some(0), buf.push_back(3)); + /// ``` + /// + /// If the size limit is zero, then this method always returns the + /// input value: + /// + /// ```rust,ignore + /// let mut buf = RingBuffer::new(0); + /// assert_eq!(Some(0), buf.push_back(0)); + /// assert_eq!(Some(1), buf.push_back(1)); + /// assert_eq!(Some(2), buf.push_back(2)); + /// ``` + pub fn push_back(&mut self, value: T) -> Option { + if self.size == 0 { + return Some(value); + } + let result = if self.size <= self.data.len() { + self.data.pop_front() + } else { + None + }; + self.data.push_back(value); + result + } +} + +#[cfg(test)] +mod tests { + + use crate::ringbuffer::RingBuffer; + use std::collections::VecDeque; + use std::iter::FromIterator; + + #[test] + fn test_size_limit_zero() { + let mut buf = RingBuffer::new(0); + assert_eq!(Some(0), buf.push_back(0)); + assert_eq!(Some(1), buf.push_back(1)); + assert_eq!(Some(2), buf.push_back(2)); + } + + #[test] + fn test_evict_oldest() { + let mut buf = RingBuffer::new(2); + assert_eq!(None, buf.push_back(0)); + assert_eq!(None, buf.push_back(1)); + assert_eq!(Some(0), buf.push_back(2)); + } + + #[test] + fn test_from_iter() { + let iter = [0, 1, 2].iter(); + let actual = RingBuffer::from_iter(iter, 2).data; + let expected = VecDeque::from_iter([1, 2].iter()); + assert_eq!(expected, actual); + } +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 28bae08cb..eb630f53a 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -39,6 +39,8 @@ pub use crate::features::fs; pub use crate::features::fsext; #[cfg(feature = "parse_time")] pub use crate::features::parse_time; +#[cfg(feature = "ringbuffer")] +pub use crate::features::ringbuffer; #[cfg(feature = "zero-copy")] pub use crate::features::zero_copy; diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 88df1f068..b2a3cf0cb 100755 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -129,6 +129,15 @@ fn test_zero_terminated_syntax_2() { .stdout_is("x\0y"); } +#[test] +fn test_zero_terminated_negative_lines() { + new_ucmd!() + .args(&["-z", "-n", "-1"]) + .pipe_in("x\0y\0z\0") + .run() + .stdout_is("x\0y\0"); +} + #[test] fn test_negative_byte_syntax() { new_ucmd!() From 1860e61f8344dc7bc7451ea67869190b8e0f21ee Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 23 May 2021 10:19:25 +0200 Subject: [PATCH 362/399] Workaround the Windows CI install issue. Fails trom time to time with: ``` info: installing component 'rustc' memory allocation of 16777216 bytes failed Error: The process 'C:\Rust\.cargo\bin\rustup.exe' failed with exit code 3221226505 ``` on Build (windows-latest, i686-pc-windows-gnu, feat_os_windows) --- .github/workflows/CICD.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index bb29355cf..977a86915 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -363,6 +363,10 @@ jobs: mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg' - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 + env: + # Override auto-detection of RAM for Rustc install. + # https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925 + RUSTUP_UNPACK_RAM: "21474836480" with: toolchain: ${{ steps.vars.outputs.TOOLCHAIN }} target: ${{ matrix.job.target }} From 218f523e1b813e9f1f8fa239d78690e72524b202 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sun, 23 May 2021 21:58:18 +0200 Subject: [PATCH 363/399] expr: make substr infallible Instead of returning an Err it should return the "null string" (in our case that's the empty string) when the offset or length is invalid. --- src/uu/expr/src/syntax_tree.rs | 36 ++++++++++++---------------------- tests/by-util/test_expr.rs | 29 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index c81adf0c8..a75f4c742 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -153,7 +153,7 @@ impl AstNode { ":" | "match" => operator_match(&operand_values), "length" => Ok(prefix_operator_length(&operand_values)), "index" => Ok(prefix_operator_index(&operand_values)), - "substr" => prefix_operator_substr(&operand_values), + "substr" => Ok(prefix_operator_substr(&operand_values)), _ => Err(format!("operation not implemented: {}", op_type)), }, @@ -522,35 +522,23 @@ fn prefix_operator_index(values: &[String]) -> String { "0".to_string() } -fn prefix_operator_substr(values: &[String]) -> Result { +fn prefix_operator_substr(values: &[String]) -> String { assert!(values.len() == 3); let subj = &values[0]; - let mut idx = match values[1].parse::() { - Ok(i) => i, - Err(_) => return Err("expected integer as POS arg to 'substr'".to_string()), + let idx = match values[1] + .parse::() + .ok() + .and_then(|v| v.checked_sub(1)) + { + Some(i) => i, + None => return String::new(), }; - let mut len = match values[2].parse::() { + let len = match values[2].parse::() { Ok(i) => i, - Err(_) => return Err("expected integer as LENGTH arg to 'substr'".to_string()), + Err(_) => return String::new(), }; - if idx <= 0 || len <= 0 { - return Ok("".to_string()); - } - - let mut out_str = String::new(); - for ch in subj.chars() { - idx -= 1; - if idx <= 0 { - if len <= 0 { - break; - } - len -= 1; - - out_str.push(ch); - } - } - Ok(out_str) + subj.chars().skip(idx).take(len).collect() } fn bool_as_int(b: bool) -> i64 { diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index bb0760676..6a969b5e9 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -54,3 +54,32 @@ fn test_and() { new_ucmd!().args(&["", "&", "1"]).run().stdout_is("0\n"); } + +#[test] +fn test_substr() { + new_ucmd!() + .args(&["substr", "abc", "1", "1"]) + .succeeds() + .stdout_only("a\n"); +} + +#[test] +fn test_invalid_substr() { + new_ucmd!() + .args(&["substr", "abc", "0", "1"]) + .fails() + .status_code(1) + .stdout_only("\n"); + + new_ucmd!() + .args(&["substr", "abc", &(std::usize::MAX.to_string() + "0"), "1"]) + .fails() + .status_code(1) + .stdout_only("\n"); + + new_ucmd!() + .args(&["substr", "abc", "0", &(std::usize::MAX.to_string() + "0")]) + .fails() + .status_code(1) + .stdout_only("\n"); +} From 991fcc548cce95318c73629ecbf710deecad4589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Mon, 24 May 2021 21:07:45 +0300 Subject: [PATCH 364/399] fix: log error messages properly on permission errors --- src/uu/mv/src/mv.rs | 33 +++++++++++++++++++++++++-------- tests/by-util/test_mv.rs | 18 ++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index f57178a09..95b2fd423 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -291,12 +291,22 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { return match rename(source, target, &b) { Err(e) => { - show_error!( - "cannot move ‘{}’ to ‘{}’: {}", - source.display(), - target.display(), - e - ); + let error_as_str = e.to_string(); + let is_perm_denied = error_as_str.contains("Permission denied"); + match e.kind() { + _ => { + show_error!( + "cannot move ‘{}’ to ‘{}’: {}", + source.display(), + target.display(), + if is_perm_denied { + "Permission denied".to_string() + } else { + e.to_string() + } + ); + } + } 1 } _ => 0, @@ -357,15 +367,22 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 }; if let Err(e) = rename(sourcepath, &targetpath, b) { + let error_as_str = e.to_string(); + let is_perm_denied = error_as_str.contains("Permission denied"); show_error!( - "mv: cannot move ‘{}’ to ‘{}’: {}", + "cannot move ‘{}’ to ‘{}’: {}", sourcepath.display(), targetpath.display(), - e + if is_perm_denied { + "Permission denied".to_string() + } else { + e.to_string() + } ); all_successful = false; } } + if all_successful { 0 } else { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index e8ba43282..47532e2e5 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -587,6 +587,24 @@ fn test_mv_verbose() { )); } +#[test] +fn test_mv_permission_error() { + let scene = TestScenario::new("mkdir"); + let folder1 = "bar"; + let folder2 = "foo"; + let folder_to_move = "bar/foo"; + scene.ucmd().arg("-m444").arg(folder1).succeeds(); + scene.ucmd().arg("-m777").arg(folder2).succeeds(); + + scene + .cmd_keepenv(util_name!()) + .arg(folder2) + .arg(folder_to_move) + .run() + .stderr_str() + .ends_with("Permission denied"); +} + // Todo: // $ at.touch a b From e5e7ca8dc5cc4f4a2cdb9cdc9d24bb24d24b3fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Mon, 24 May 2021 21:20:59 +0300 Subject: [PATCH 365/399] fix: simplify logic --- src/uu/mv/src/mv.rs | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 95b2fd423..a0ff1bcc6 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -291,22 +291,12 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { return match rename(source, target, &b) { Err(e) => { - let error_as_str = e.to_string(); - let is_perm_denied = error_as_str.contains("Permission denied"); - match e.kind() { - _ => { - show_error!( - "cannot move ‘{}’ to ‘{}’: {}", - source.display(), - target.display(), - if is_perm_denied { - "Permission denied".to_string() - } else { - e.to_string() - } - ); - } - } + show_error!( + "cannot move ‘{}’ to ‘{}’: {}", + source.display(), + target.display(), + e.to_string() + ); 1 } _ => 0, @@ -367,17 +357,11 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 }; if let Err(e) = rename(sourcepath, &targetpath, b) { - let error_as_str = e.to_string(); - let is_perm_denied = error_as_str.contains("Permission denied"); show_error!( "cannot move ‘{}’ to ‘{}’: {}", sourcepath.display(), targetpath.display(), - if is_perm_denied { - "Permission denied".to_string() - } else { - e.to_string() - } + e.to_string() ); all_successful = false; } @@ -469,7 +453,13 @@ fn rename_with_fallback(from: &Path, to: &Path) -> io::Result<()> { ..DirCopyOptions::new() }; if let Err(err) = move_dir(from, to, &options) { - return Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))); + return match err.kind { + fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Permission denied", + )), + _ => Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))), + }; } } else { fs::copy(from, to).and_then(|_| fs::remove_file(from))?; From 962e4198b2a64b827d197b7ee011d986fe2da264 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 24 May 2021 22:32:23 +0200 Subject: [PATCH 366/399] gnu/ci: limit the number of factor runs --- util/build-gnu.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 667dc8e46..c2659ae11 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -43,7 +43,8 @@ sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\ sed -i 's| tr | /usr/bin/tr |' tests/init.sh make # Generate the factor tests, so they can be fixed -for i in {00..36} +# Used to be 36. Reduced to 20 to decrease the log size +for i in {00..20} do make tests/factor/t${i}.sh done From 98f09a6b8b08b705dca590924101fd24e4bc2f76 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 24 May 2021 22:32:48 +0200 Subject: [PATCH 367/399] gnu/ci: don't run seq-precision - logs are too long --- util/build-gnu.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index c2659ae11..e8ab4e44d 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -58,6 +58,10 @@ sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ -e '/tests\/misc\/help-version-getopt.sh/ D' \ Makefile +# logs are clotted because of this test +sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ + Makefile + # printf doesn't limit the values used in its arg, so this produced ~2GB of output sed -i '/INT_OFLOW/ D' tests/misc/printf.sh From 97d15e34d984d3e73b4cc8f6102523c343ac9075 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 25 May 2021 14:58:56 +0200 Subject: [PATCH 368/399] Disable some factor tests --- util/build-gnu.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index e8ab4e44d..9d73450f6 100644 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -48,6 +48,14 @@ for i in {00..20} do make tests/factor/t${i}.sh done + +# strip the long stuff +for i in {21..36} +do + sed -i -e "s/\$(tf)\/t${i}.sh//g" Makefile +done + + grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||' sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh From a77e92cc96ec6f7e93fdf0ef6f8d3ac904323e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Wed, 26 May 2021 01:53:40 +0300 Subject: [PATCH 369/399] chore: delete unused macros --- src/uucore/src/lib/macros.rs | 56 ------------------------------------ 1 file changed, 56 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 637e91f8f..00bdf2939 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -176,13 +176,6 @@ macro_rules! msg_invalid_input { }; } -#[macro_export] -macro_rules! snippet_no_file_at_path { - ($path:expr) => { - format!("nonexistent path {}", $path) - }; -} - // -- message templates : invalid input : flag #[macro_export] @@ -229,55 +222,6 @@ macro_rules! msg_opt_invalid_should_be { }; } -// -- message templates : invalid input : args - -#[macro_export] -macro_rules! msg_arg_invalid_value { - ($expects:expr, $received:expr) => { - msg_invalid_input!(format!( - "expects its argument to be {}, but was provided {}", - $expects, $received - )) - }; -} - -#[macro_export] -macro_rules! msg_args_invalid_value { - ($expects:expr, $received:expr) => { - msg_invalid_input!(format!( - "expects its arguments to be {}, but was provided {}", - $expects, $received - )) - }; - ($msg:expr) => { - msg_invalid_input!($msg) - }; -} - -#[macro_export] -macro_rules! msg_args_nonexistent_file { - ($received:expr) => { - msg_args_invalid_value!("paths to files", snippet_no_file_at_path!($received)) - }; -} - -#[macro_export] -macro_rules! msg_wrong_number_of_arguments { - () => { - msg_args_invalid_value!("wrong number of arguments") - }; - ($min:expr, $max:expr) => { - msg_args_invalid_value!(format!("expects {}-{} arguments", $min, $max)) - }; - ($exact:expr) => { - if $exact == 1 { - msg_args_invalid_value!("expects 1 argument") - } else { - msg_args_invalid_value!(format!("expects {} arguments", $exact)) - } - }; -} - // -- message templates : invalid input : input combinations #[macro_export] From c78a7937f8debc78b4a0016ad4f1cecd759a3437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Wed, 26 May 2021 02:27:10 +0300 Subject: [PATCH 370/399] chore: delete show_info macro and replace with show_error --- src/uu/cat/src/cat.rs | 2 +- src/uu/chgrp/src/chgrp.rs | 24 ++++++++++++------------ src/uu/chmod/src/chmod.rs | 4 ++-- src/uu/chown/src/chown.rs | 26 +++++++++++++------------- src/uu/dircolors/src/dircolors.rs | 6 +++--- src/uu/install/src/install.rs | 20 ++++++++++---------- src/uu/install/src/mode.rs | 2 +- src/uu/mkdir/src/mkdir.rs | 4 ++-- src/uu/mknod/src/mknod.rs | 2 +- src/uu/mktemp/src/mktemp.rs | 6 +++--- src/uu/nohup/src/nohup.rs | 10 +++++----- src/uu/numfmt/src/numfmt.rs | 2 +- src/uu/stat/src/stat.rs | 6 +++--- src/uu/tee/src/tee.rs | 6 +++--- src/uucore/src/lib/macros.rs | 9 --------- 15 files changed, 60 insertions(+), 69 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 8dea096be..69ea902e6 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -347,7 +347,7 @@ fn cat_files(files: Vec, options: &OutputOptions) -> Result<(), u32> { for path in &files { if let Err(err) = cat_path(path, &options, &mut state) { - show_info!("{}: {}", path, err); + show_error!("{}: {}", path, err); error_count += 1; } } diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 2afef7de0..f6afc2805 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -97,7 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if recursive { if bit_flag == FTS_PHYSICAL { if derefer == 1 { - show_info!("-R --dereference requires -H or -L"); + show_error!("-R --dereference requires -H or -L"); return 1; } derefer = 0; @@ -132,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { dest_gid = meta.gid(); } Err(e) => { - show_info!("failed to get attributes of '{}': {}", file, e); + show_error!("failed to get attributes of '{}': {}", file, e); return 1; } } @@ -143,7 +143,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { dest_gid = g; } _ => { - show_info!("invalid group: {}", matches.free[0].as_str()); + show_error!("invalid group: {}", matches.free[0].as_str()); return 1; } } @@ -235,8 +235,8 @@ impl Chgrper { if let Some(p) = may_exist { if p.parent().is_none() || self.is_bind_root(p) { - show_info!("it is dangerous to operate recursively on '/'"); - show_info!("use --no-preserve-root to override this failsafe"); + show_error!("it is dangerous to operate recursively on '/'"); + show_error!("use --no-preserve-root to override this failsafe"); return 1; } } @@ -250,12 +250,12 @@ impl Chgrper { self.verbosity.clone(), ) { Ok(n) => { - show_info!("{}", n); + show_error!("{}", n); 0 } Err(e) => { if self.verbosity != Verbosity::Silent { - show_info!("{}", e); + show_error!("{}", e); } 1 } @@ -275,7 +275,7 @@ impl Chgrper { for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { let entry = unwrap!(entry, e, { ret = 1; - show_info!("{}", e); + show_error!("{}", e); continue; }); let path = entry.path(); @@ -290,13 +290,13 @@ impl Chgrper { ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) { Ok(n) => { if !n.is_empty() { - show_info!("{}", n); + show_error!("{}", n); } 0 } Err(e) => { if self.verbosity != Verbosity::Silent { - show_info!("{}", e); + show_error!("{}", e); } 1 } @@ -313,7 +313,7 @@ impl Chgrper { unwrap!(path.metadata(), e, { match self.verbosity { Silent => (), - _ => show_info!("cannot access '{}': {}", path.display(), e), + _ => show_error!("cannot access '{}': {}", path.display(), e), } return None; }) @@ -321,7 +321,7 @@ impl Chgrper { unwrap!(path.symlink_metadata(), e, { match self.verbosity { Silent => (), - _ => show_info!("cannot dereference '{}': {}", path.display(), e), + _ => show_error!("cannot dereference '{}': {}", path.display(), e), } return None; }) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 88e3403fe..9dea3c842 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -316,7 +316,7 @@ impl Chmoder { show_error!("{}", err); } if self.verbose { - show_info!( + show_error!( "failed to change mode of file '{}' from {:o} ({}) to {:o} ({})", file.display(), fperm, @@ -328,7 +328,7 @@ impl Chmoder { Err(1) } else { if self.verbose || self.changes { - show_info!( + show_error!( "mode of '{}' changed from {:o} ({}) to {:o} ({})", file.display(), fperm, diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index ff9c42dd0..3d0b25814 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -199,7 +199,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if recursive { if bit_flag == FTS_PHYSICAL { if derefer == 1 { - show_info!("-R --dereference requires -H or -L"); + show_error!("-R --dereference requires -H or -L"); return 1; } derefer = 0; @@ -227,7 +227,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), Ok((None, None)) => IfFrom::All, Err(e) => { - show_info!("{}", e); + show_error!("{}", e); return 1; } } @@ -244,7 +244,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { dest_uid = Some(meta.uid()); } Err(e) => { - show_info!("failed to get attributes of '{}': {}", file, e); + show_error!("failed to get attributes of '{}': {}", file, e); return 1; } } @@ -255,7 +255,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { dest_gid = g; } Err(e) => { - show_info!("{}", e); + show_error!("{}", e); return 1; } } @@ -377,8 +377,8 @@ impl Chowner { if let Some(p) = may_exist { if p.parent().is_none() { - show_info!("it is dangerous to operate recursively on '/'"); - show_info!("use --no-preserve-root to override this failsafe"); + show_error!("it is dangerous to operate recursively on '/'"); + show_error!("use --no-preserve-root to override this failsafe"); return 1; } } @@ -395,13 +395,13 @@ impl Chowner { ) { Ok(n) => { if !n.is_empty() { - show_info!("{}", n); + show_error!("{}", n); } 0 } Err(e) => { if self.verbosity != Verbosity::Silent { - show_info!("{}", e); + show_error!("{}", e); } 1 } @@ -424,7 +424,7 @@ impl Chowner { for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { let entry = unwrap!(entry, e, { ret = 1; - show_info!("{}", e); + show_error!("{}", e); continue; }); let path = entry.path(); @@ -450,13 +450,13 @@ impl Chowner { ) { Ok(n) => { if !n.is_empty() { - show_info!("{}", n); + show_error!("{}", n); } 0 } Err(e) => { if self.verbosity != Verbosity::Silent { - show_info!("{}", e); + show_error!("{}", e); } 1 } @@ -472,7 +472,7 @@ impl Chowner { unwrap!(path.metadata(), e, { match self.verbosity { Silent => (), - _ => show_info!("cannot access '{}': {}", path.display(), e), + _ => show_error!("cannot access '{}': {}", path.display(), e), } return None; }) @@ -480,7 +480,7 @@ impl Chowner { unwrap!(path.symlink_metadata(), e, { match self.verbosity { Silent => (), - _ => show_info!("cannot dereference '{}': {}", path.display(), e), + _ => show_error!("cannot dereference '{}': {}", path.display(), e), } return None; }) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index a2d819620..b6942c2d2 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -105,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if out_format == OutputFmt::Unknown { match guess_syntax() { OutputFmt::Unknown => { - show_info!("no SHELL environment variable, and no shell type option given"); + show_error!("no SHELL environment variable, and no shell type option given"); return 1; } fmt => out_format = fmt, @@ -130,7 +130,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) } Err(e) => { - show_info!("{}: {}", matches.free[0], e); + show_error!("{}: {}", matches.free[0], e); return 1; } } @@ -141,7 +141,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 0 } Err(s) => { - show_info!("{}", s); + show_error!("{}", s); 1 } } diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 4ce665b80..bb51a7606 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -370,13 +370,13 @@ fn directory(paths: Vec, b: Behavior) -> i32 { // created ancestor directories will have the default mode. Hence it is safe to use // fs::create_dir_all and then only modify the target's dir mode. if let Err(e) = fs::create_dir_all(path) { - show_info!("{}: {}", path.display(), e); + show_error!("{}: {}", path.display(), e); all_successful = false; continue; } if b.verbose { - show_info!("creating directory '{}'", path.display()); + show_error!("creating directory '{}'", path.display()); } } @@ -461,7 +461,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 let mut all_successful = true; for sourcepath in files.iter() { if !sourcepath.exists() { - show_info!( + show_error!( "cannot stat '{}': No such file or directory", sourcepath.display() ); @@ -471,7 +471,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 } if sourcepath.is_dir() { - show_info!("omitting directory '{}'", sourcepath.display()); + show_error!("omitting directory '{}'", sourcepath.display()); all_successful = false; continue; } @@ -588,10 +588,10 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { ) { Ok(n) => { if !n.is_empty() { - show_info!("{}", n); + show_error!("{}", n); } } - Err(e) => show_info!("{}", e), + Err(e) => show_error!("{}", e), } } @@ -608,10 +608,10 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) { Ok(n) => { if !n.is_empty() { - show_info!("{}", n); + show_error!("{}", n); } } - Err(e) => show_info!("{}", e), + Err(e) => show_error!("{}", e), } } @@ -626,12 +626,12 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { match set_file_times(to, accessed_time, modified_time) { Ok(_) => {} - Err(e) => show_info!("{}", e), + Err(e) => show_error!("{}", e), } } if b.verbose { - show_info!("'{}' -> '{}'", from.display(), to.display()); + show_error!("'{}' -> '{}'", from.display(), to.display()); } Ok(()) diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index a3de40c68..b8d5cd839 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -23,7 +23,7 @@ pub fn parse(mode_string: &str, considering_dir: bool) -> Result { pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> { use std::os::unix::fs::PermissionsExt; fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| { - show_info!("{}: chmod failed with error {}", path.display(), err); + show_error!("{}: chmod failed with error {}", path.display(), err); }) } diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 6b9fd68ea..861ef5075 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -101,7 +101,7 @@ fn exec(dirs: Vec, recursive: bool, mode: u16, verbose: bool) -> i32 { if !recursive { if let Some(parent) = path.parent() { if parent != empty && !parent.exists() { - show_info!( + show_error!( "cannot create directory '{}': No such file or directory", path.display() ); @@ -125,7 +125,7 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 { fs::create_dir }; if let Err(e) = create_dir(path) { - show_info!("{}: {}", path.display(), e.to_string()); + show_error!("{}: {}", path.display(), e.to_string()); return 1; } diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 5b6c2fa8c..e0cf62024 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -136,7 +136,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mode = match get_mode(&matches) { Ok(mode) => mode, Err(err) => { - show_info!("{}", err); + show_error!("{}", err); return 1; } }; diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index ed767ffe0..112c2fb94 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -157,7 +157,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() { - show_info!( + show_error!( "invalid template, ‘{}’; with --tmpdir, it may not be absolute", template ); @@ -229,7 +229,7 @@ fn exec( } Err(e) => { if !quiet { - show_info!("{}: {}", e, tmpdir.display()); + show_error!("{}: {}", e, tmpdir.display()); } return 1; } @@ -244,7 +244,7 @@ fn exec( Ok(f) => f, Err(e) => { if !quiet { - show_info!("failed to create tempfile: {}", e); + show_error!("failed to create tempfile: {}", e); } return 1; } diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 83153ad37..93d9b5e45 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -122,13 +122,13 @@ fn find_stdout() -> File { .open(Path::new(NOHUP_OUT)) { Ok(t) => { - show_info!("ignoring input and appending output to '{}'", NOHUP_OUT); + show_error!("ignoring input and appending output to '{}'", NOHUP_OUT); t } Err(e1) => { let home = match env::var("HOME") { Err(_) => { - show_info!("failed to open '{}': {}", NOHUP_OUT, e1); + show_error!("failed to open '{}': {}", NOHUP_OUT, e1); exit!(internal_failure_code) } Ok(h) => h, @@ -143,12 +143,12 @@ fn find_stdout() -> File { .open(&homeout) { Ok(t) => { - show_info!("ignoring input and appending output to '{}'", homeout_str); + show_error!("ignoring input and appending output to '{}'", homeout_str); t } Err(e2) => { - show_info!("failed to open '{}': {}", NOHUP_OUT, e1); - show_info!("failed to open '{}': {}", homeout_str, e2); + show_error!("failed to open '{}': {}", NOHUP_OUT, e1); + show_error!("failed to open '{}': {}", homeout_str, e2); exit!(internal_failure_code) } } diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index e9a476956..6eba699b2 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -216,7 +216,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match result { Err(e) => { std::io::stdout().flush().expect("error flushing stdout"); - show_info!("{}", e); + show_error!("{}", e); 1 } _ => 0, diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 5bb0e5f12..582d59841 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -749,7 +749,7 @@ impl Stater { } } Err(e) => { - show_info!("cannot stat '{}': {}", file, e); + show_error!("cannot stat '{}': {}", file, e); return 1; } } @@ -842,7 +842,7 @@ impl Stater { } } Err(e) => { - show_info!("cannot read file system information for '{}': {}", file, e); + show_error!("cannot read file system information for '{}': {}", file, e); return 1; } } @@ -1001,7 +1001,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match Stater::new(matches) { Ok(stater) => stater.exec(), Err(e) => { - show_info!("{}", e); + show_error!("{}", e); 1 } } diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 7c6a86b4c..c21559b3b 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -166,7 +166,7 @@ impl Write for MultiWriter { let result = writer.write_all(buf); match result { Err(f) => { - show_info!("{}: {}", writer.name, f.to_string()); + show_error!("{}: {}", writer.name, f.to_string()); false } _ => true, @@ -180,7 +180,7 @@ impl Write for MultiWriter { let result = writer.flush(); match result { Err(f) => { - show_info!("{}: {}", writer.name, f.to_string()); + show_error!("{}: {}", writer.name, f.to_string()); false } _ => true, @@ -213,7 +213,7 @@ impl Read for NamedReader { fn read(&mut self, buf: &mut [u8]) -> Result { match self.inner.read(buf) { Err(f) => { - show_info!("{}: {}", Path::new("stdin").display(), f.to_string()); + show_error!("{}: {}", Path::new("stdin").display(), f.to_string()); Err(f) } okay => okay, diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 637e91f8f..dde059131 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -47,15 +47,6 @@ macro_rules! show_warning( }) ); -/// Show an info message to stderr in a silimar style to GNU coreutils. -#[macro_export] -macro_rules! show_info( - ($($args:tt)+) => ({ - eprint!("{}: ", executable!()); - eprintln!($($args)+); - }) -); - /// Show a bad inocation help message in a similar style to GNU coreutils. #[macro_export] macro_rules! show_usage_error( From 898d2eb48956e38440b5a7328d0589b5d5793281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Wed, 26 May 2021 02:32:02 +0300 Subject: [PATCH 371/399] chore: delete 'error:' prefix on show_error --- src/uucore/src/lib/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index dde059131..616a1b57c 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -25,7 +25,7 @@ macro_rules! executable( #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ - eprint!("{}: error: ", executable!()); + eprint!("{}: ", executable!()); eprintln!($($args)+); }) ); From 7240b1289523a985f6bc9999b0fec4a0cd903557 Mon Sep 17 00:00:00 2001 From: Matt Blessed Date: Tue, 25 May 2021 16:38:34 -0400 Subject: [PATCH 372/399] uucore: implement backup control Most of these changes were sourced from mv's existing backup control implementation. A later commit will update the mv utility to use this new share backup control. --- src/uucore/src/lib/lib.rs | 1 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/backup_control.rs | 97 +++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/uucore/src/lib/mods/backup_control.rs diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index eb630f53a..c17f14516 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -25,6 +25,7 @@ mod features; // feature-gated code modules mod mods; // core cross-platform modules // * cross-platform modules +pub use crate::mods::backup_control; pub use crate::mods::coreopts; pub use crate::mods::os; pub use crate::mods::panic; diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 74725e141..2689361a0 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -1,5 +1,6 @@ // mods ~ cross-platforms modules (core/bundler file) +pub mod backup_control; pub mod coreopts; pub mod os; pub mod panic; diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs new file mode 100644 index 000000000..6004ae84d --- /dev/null +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -0,0 +1,97 @@ +use std::{ + env, + path::{Path, PathBuf}, +}; + +pub static BACKUP_CONTROL_VALUES: &[&str] = &[ + "simple", "never", "numbered", "t", "existing", "nil", "none", "off", +]; + +pub static BACKUP_CONTROL_LONG_HELP: &str = "The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX. Here are the version control values: + +none, off + never make backups (even if --backup is given) + +numbered, t + make numbered backups + +existing, nil + numbered if numbered backups exist, simple otherwise + +simple, never + always make simple backups"; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum BackupMode { + NoBackup, + SimpleBackup, + NumberedBackup, + ExistingBackup, +} + +pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String { + if let Some(suffix) = supplied_suffix { + String::from(suffix) + } else { + env::var("SIMPLE_BACKUP_SUFFIX").unwrap_or("~".to_owned()) + } +} + +pub fn determine_backup_mode(backup_opt_exists: bool, backup_opt: Option<&str>) -> BackupMode { + if backup_opt_exists { + match backup_opt.map(String::from) { + // default is existing, see: + // https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html + None => BackupMode::ExistingBackup, + Some(mode) => match &mode[..] { + "simple" | "never" => BackupMode::SimpleBackup, + "numbered" | "t" => BackupMode::NumberedBackup, + "existing" | "nil" => BackupMode::ExistingBackup, + "none" | "off" => BackupMode::NoBackup, + _ => panic!(), // cannot happen as it is managed by clap + }, + } + } else { + BackupMode::NoBackup + } +} + +pub fn get_backup_path( + backup_mode: BackupMode, + backup_path: &Path, + suffix: &str, +) -> Option { + match backup_mode { + BackupMode::NoBackup => None, + BackupMode::SimpleBackup => Some(simple_backup_path(backup_path, suffix)), + BackupMode::NumberedBackup => Some(numbered_backup_path(backup_path)), + BackupMode::ExistingBackup => Some(existing_backup_path(backup_path, suffix)), + } +} + +pub fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { + let mut p = path.to_string_lossy().into_owned(); + p.push_str(suffix); + PathBuf::from(p) +} + +pub fn numbered_backup_path(path: &Path) -> PathBuf { + for i in 1_u64.. { + let path_str = &format!("{}.~{}~", path.to_string_lossy(), i); + let path = Path::new(path_str); + if !path.exists() { + return path.to_path_buf(); + } + } + panic!("cannot create backup") +} + +pub fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { + let test_path_str = &format!("{}.~1~", path.to_string_lossy()); + let test_path = Path::new(test_path_str); + if test_path.exists() { + numbered_backup_path(path) + } else { + simple_backup_path(path, suffix) + } +} From 071899d24d3d1c21d65d4295292ec3b5600c8da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Wed, 26 May 2021 02:45:53 +0300 Subject: [PATCH 373/399] tests: delete 'error:' prefix from the tests --- tests/by-util/test_base32.rs | 6 +-- tests/by-util/test_base64.rs | 6 +-- tests/by-util/test_basename.rs | 9 ++-- tests/by-util/test_chmod.rs | 2 +- tests/by-util/test_chroot.rs | 8 ++- tests/by-util/test_cksum.rs | 4 +- tests/by-util/test_csplit.rs | 98 +++++++++++++++++----------------- tests/by-util/test_cut.rs | 4 +- tests/by-util/test_du.rs | 2 +- tests/by-util/test_expr.rs | 4 +- tests/by-util/test_fmt.rs | 2 +- tests/by-util/test_id.rs | 2 +- tests/by-util/test_install.rs | 2 +- tests/by-util/test_join.rs | 4 +- tests/by-util/test_link.rs | 4 +- tests/by-util/test_ln.rs | 2 +- tests/by-util/test_logname.rs | 2 +- tests/by-util/test_ls.rs | 6 +-- tests/by-util/test_mkfifo.rs | 6 +-- tests/by-util/test_mktemp.rs | 2 +- tests/by-util/test_mv.rs | 4 +- tests/by-util/test_nice.rs | 2 +- tests/by-util/test_rm.rs | 2 +- tests/by-util/test_rmdir.rs | 8 +-- tests/by-util/test_sort.rs | 15 +++--- tests/by-util/test_stdbuf.rs | 11 ++-- tests/by-util/test_sum.rs | 6 +-- tests/by-util/test_uniq.rs | 2 +- tests/by-util/test_unlink.rs | 6 +-- tests/by-util/test_whoami.rs | 2 +- 30 files changed, 114 insertions(+), 119 deletions(-) diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index fd49aa951..e36c376be 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -98,7 +98,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base32: error: Invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base32: Invalid wrap size: ‘b’: invalid digit found in string\n"); } } @@ -109,7 +109,7 @@ fn test_base32_extra_operand() { .arg("a.txt") .arg("a.txt") .fails() - .stderr_only("base32: error: extra operand ‘a.txt’"); + .stderr_only("base32: extra operand ‘a.txt’"); } #[test] @@ -117,5 +117,5 @@ fn test_base32_file_not_found() { new_ucmd!() .arg("a.txt") .fails() - .stderr_only("base32: error: a.txt: No such file or directory"); + .stderr_only("base32: a.txt: No such file or directory"); } diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index 8d9dc5639..89405d791 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -88,7 +88,7 @@ fn test_wrap_bad_arg() { .arg(wrap_param) .arg("b") .fails() - .stderr_only("base64: error: Invalid wrap size: ‘b’: invalid digit found in string\n"); + .stderr_only("base64: Invalid wrap size: ‘b’: invalid digit found in string\n"); } } @@ -99,7 +99,7 @@ fn test_base64_extra_operand() { .arg("a.txt") .arg("a.txt") .fails() - .stderr_only("base64: error: extra operand ‘a.txt’"); + .stderr_only("base64: extra operand ‘a.txt’"); } #[test] @@ -107,5 +107,5 @@ fn test_base64_file_not_found() { new_ucmd!() .arg("a.txt") .fails() - .stderr_only("base64: error: a.txt: No such file or directory"); + .stderr_only("base64: a.txt: No such file or directory"); } diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index baf15f78a..1d26a922a 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -109,7 +109,7 @@ fn test_no_args() { fn test_no_args_output() { new_ucmd!() .fails() - .stderr_is("basename: error: missing operand\nTry 'basename --help' for more information."); + .stderr_is("basename: missing operand\nTry 'basename --help' for more information."); } #[test] @@ -119,9 +119,10 @@ fn test_too_many_args() { #[test] fn test_too_many_args_output() { - new_ucmd!().args(&["a", "b", "c"]).fails().stderr_is( - "basename: error: extra operand 'c'\nTry 'basename --help' for more information.", - ); + new_ucmd!() + .args(&["a", "b", "c"]) + .fails() + .stderr_is("basename: extra operand 'c'\nTry 'basename --help' for more information."); } #[cfg(any(unix, target_os = "redox"))] diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 3958c0a36..733722b7c 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -338,7 +338,7 @@ fn test_chmod_preserve_root() { .arg("755") .arg("/") .fails() - .stderr_contains(&"chmod: error: it is dangerous to operate recursively on '/'"); + .stderr_contains(&"chmod: it is dangerous to operate recursively on '/'"); } #[test] diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index e2e355e14..0479e7c3a 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -21,7 +21,7 @@ fn test_enter_chroot_fails() { assert!(result .stderr_str() - .starts_with("chroot: error: cannot chroot to jail: Operation not permitted (os error 1)")); + .starts_with("chroot: cannot chroot to jail: Operation not permitted (os error 1)")); } #[test] @@ -32,7 +32,7 @@ fn test_no_such_directory() { ucmd.arg("a") .fails() - .stderr_is("chroot: error: cannot change root directory to `a`: no such directory"); + .stderr_is("chroot: cannot change root directory to `a`: no such directory"); } #[test] @@ -43,9 +43,7 @@ fn test_invalid_user_spec() { let result = ucmd.arg("a").arg("--userspec=ARABA:").fails(); - assert!(result - .stderr_str() - .starts_with("chroot: error: invalid userspec")); + assert!(result.stderr_str().starts_with("chroot: invalid userspec")); } #[test] diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 592e45c58..81ef4c177 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -66,7 +66,7 @@ fn test_invalid_file() { .arg(folder_name) .fails() .no_stdout() - .stderr_contains("cksum: error: 'asdf' No such file or directory"); + .stderr_contains("cksum: 'asdf' No such file or directory"); // Then check when the file is of an invalid type at.mkdir(folder_name); @@ -74,7 +74,7 @@ fn test_invalid_file() { .arg(folder_name) .fails() .no_stdout() - .stderr_contains("cksum: error: 'asdf' Is a directory"); + .stderr_contains("cksum: 'asdf' Is a directory"); } // Make sure crc is correct for files larger than 32 bytes diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 51cab483c..ae0885ff8 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -208,7 +208,7 @@ fn test_up_to_match_repeat_over() { ucmd.args(&["numbers50.txt", "/9$/", "{50}"]) .fails() .stdout_is("16\n29\n30\n30\n30\n6\n") - .stderr_is("csplit: error: '/9$/': match not found on repetition 5"); + .stderr_is("csplit: '/9$/': match not found on repetition 5"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -219,7 +219,7 @@ fn test_up_to_match_repeat_over() { ucmd.args(&["numbers50.txt", "/9$/", "{50}", "-k"]) .fails() .stdout_is("16\n29\n30\n30\n30\n6\n") - .stderr_is("csplit: error: '/9$/': match not found on repetition 5"); + .stderr_is("csplit: '/9$/': match not found on repetition 5"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -365,7 +365,7 @@ fn test_option_keep() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-k", "numbers50.txt", "/20/", "/nope/"]) .fails() - .stderr_is("csplit: error: '/nope/': match not found") + .stderr_is("csplit: '/nope/': match not found") .stdout_is("48\n93\n"); let count = glob(&at.plus_as_string("xx*")) @@ -541,7 +541,7 @@ fn test_up_to_match_context_overflow() { ucmd.args(&["numbers50.txt", "/45/+10"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/45/+10': line number out of range"); + .stderr_is("csplit: '/45/+10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -552,7 +552,7 @@ fn test_up_to_match_context_overflow() { ucmd.args(&["numbers50.txt", "/45/+10", "-k"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/45/+10': line number out of range"); + .stderr_is("csplit: '/45/+10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -567,7 +567,7 @@ fn test_skip_to_match_context_underflow() { ucmd.args(&["numbers50.txt", "%5%-10"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '%5%-10': line number out of range"); + .stderr_is("csplit: '%5%-10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -578,7 +578,7 @@ fn test_skip_to_match_context_underflow() { ucmd.args(&["numbers50.txt", "%5%-10", "-k"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '%5%-10': line number out of range"); + .stderr_is("csplit: '%5%-10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -592,7 +592,7 @@ fn test_skip_to_match_context_overflow() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%45%+10"]) .fails() - .stderr_only("csplit: error: '%45%+10': line number out of range"); + .stderr_only("csplit: '%45%+10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -602,7 +602,7 @@ fn test_skip_to_match_context_overflow() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%45%+10", "-k"]) .fails() - .stderr_only("csplit: error: '%45%+10': line number out of range"); + .stderr_only("csplit: '%45%+10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -616,7 +616,7 @@ fn test_up_to_no_match1() { ucmd.args(&["numbers50.txt", "/4/", "/nope/"]) .fails() .stdout_is("6\n135\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -627,7 +627,7 @@ fn test_up_to_no_match1() { ucmd.args(&["numbers50.txt", "/4/", "/nope/", "-k"]) .fails() .stdout_is("6\n135\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -643,7 +643,7 @@ fn test_up_to_no_match2() { ucmd.args(&["numbers50.txt", "/4/", "/nope/", "{50}"]) .fails() .stdout_is("6\n135\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -654,7 +654,7 @@ fn test_up_to_no_match2() { ucmd.args(&["numbers50.txt", "/4/", "/nope/", "{50}", "-k"]) .fails() .stdout_is("6\n135\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -670,7 +670,7 @@ fn test_up_to_no_match3() { ucmd.args(&["numbers50.txt", "/0$/", "{50}"]) .fails() .stdout_is("18\n30\n30\n30\n30\n3\n") - .stderr_is("csplit: error: '/0$/': match not found on repetition 5"); + .stderr_is("csplit: '/0$/': match not found on repetition 5"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -681,7 +681,7 @@ fn test_up_to_no_match3() { ucmd.args(&["numbers50.txt", "/0$/", "{50}", "-k"]) .fails() .stdout_is("18\n30\n30\n30\n30\n3\n") - .stderr_is("csplit: error: '/0$/': match not found on repetition 5"); + .stderr_is("csplit: '/0$/': match not found on repetition 5"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -701,7 +701,7 @@ fn test_up_to_no_match4() { ucmd.args(&["numbers50.txt", "/nope/", "/4/"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -712,7 +712,7 @@ fn test_up_to_no_match4() { ucmd.args(&["numbers50.txt", "/nope/", "/4/", "-k"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -741,7 +741,7 @@ fn test_up_to_no_match6() { ucmd.args(&["numbers50.txt", "/nope/-5"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/-5': match not found"); + .stderr_is("csplit: '/nope/-5': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -752,7 +752,7 @@ fn test_up_to_no_match6() { ucmd.args(&["numbers50.txt", "/nope/-5", "-k"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/-5': match not found"); + .stderr_is("csplit: '/nope/-5': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -767,7 +767,7 @@ fn test_up_to_no_match7() { ucmd.args(&["numbers50.txt", "/nope/+5"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/+5': match not found"); + .stderr_is("csplit: '/nope/+5': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -778,7 +778,7 @@ fn test_up_to_no_match7() { ucmd.args(&["numbers50.txt", "/nope/+5", "-k"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/+5': match not found"); + .stderr_is("csplit: '/nope/+5': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -792,7 +792,7 @@ fn test_skip_to_no_match1() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%nope%"]) .fails() - .stderr_only("csplit: error: '%nope%': match not found"); + .stderr_only("csplit: '%nope%': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -805,7 +805,7 @@ fn test_skip_to_no_match2() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%nope%", "{50}"]) .fails() - .stderr_only("csplit: error: '%nope%': match not found"); + .stderr_only("csplit: '%nope%': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -818,7 +818,7 @@ fn test_skip_to_no_match3() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%0$%", "{50}"]) .fails() - .stderr_only("csplit: error: '%0$%': match not found on repetition 5"); + .stderr_only("csplit: '%0$%': match not found on repetition 5"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -831,7 +831,7 @@ fn test_skip_to_no_match4() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%nope%", "/4/"]) .fails() - .stderr_only("csplit: error: '%nope%': match not found"); + .stderr_only("csplit: '%nope%': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -858,7 +858,7 @@ fn test_skip_to_no_match6() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%nope%-5"]) .fails() - .stderr_only("csplit: error: '%nope%-5': match not found"); + .stderr_only("csplit: '%nope%-5': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -871,7 +871,7 @@ fn test_skip_to_no_match7() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%nope%+5"]) .fails() - .stderr_only("csplit: error: '%nope%+5': match not found"); + .stderr_only("csplit: '%nope%+5': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -884,7 +884,7 @@ fn test_no_match() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "%nope%"]) .fails() - .stderr_only("csplit: error: '%nope%': match not found"); + .stderr_only("csplit: '%nope%': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -895,7 +895,7 @@ fn test_no_match() { ucmd.args(&["numbers50.txt", "/nope/"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '/nope/': match not found"); + .stderr_is("csplit: '/nope/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -992,7 +992,7 @@ fn test_too_small_linenum_repeat() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "/20/", "10", "{*}"]) .fails() - .stderr_is("csplit: error: '10': line number out of range on repetition 5") + .stderr_is("csplit: '10': line number out of range on repetition 5") .stdout_is("48\n0\n0\n30\n30\n30\n3\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1003,7 +1003,7 @@ fn test_too_small_linenum_repeat() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "/20/", "10", "{*}", "-k"]) .fails() - .stderr_is("csplit: error: '10': line number out of range on repetition 5") + .stderr_is("csplit: '10': line number out of range on repetition 5") .stdout_is("48\n0\n0\n30\n30\n30\n3\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1025,7 +1025,7 @@ fn test_linenum_out_of_range1() { ucmd.args(&["numbers50.txt", "100"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '100': line number out of range"); + .stderr_is("csplit: '100': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1036,7 +1036,7 @@ fn test_linenum_out_of_range1() { ucmd.args(&["numbers50.txt", "100", "-k"]) .fails() .stdout_is("141\n") - .stderr_is("csplit: error: '100': line number out of range"); + .stderr_is("csplit: '100': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1051,7 +1051,7 @@ fn test_linenum_out_of_range2() { ucmd.args(&["numbers50.txt", "10", "100"]) .fails() .stdout_is("18\n123\n") - .stderr_is("csplit: error: '100': line number out of range"); + .stderr_is("csplit: '100': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1062,7 +1062,7 @@ fn test_linenum_out_of_range2() { ucmd.args(&["numbers50.txt", "10", "100", "-k"]) .fails() .stdout_is("18\n123\n") - .stderr_is("csplit: error: '100': line number out of range"); + .stderr_is("csplit: '100': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1078,7 +1078,7 @@ fn test_linenum_out_of_range3() { ucmd.args(&["numbers50.txt", "40", "{2}"]) .fails() .stdout_is("108\n33\n") - .stderr_is("csplit: error: '40': line number out of range on repetition 1"); + .stderr_is("csplit: '40': line number out of range on repetition 1"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1089,7 +1089,7 @@ fn test_linenum_out_of_range3() { ucmd.args(&["numbers50.txt", "40", "{2}", "-k"]) .fails() .stdout_is("108\n33\n") - .stderr_is("csplit: error: '40': line number out of range on repetition 1"); + .stderr_is("csplit: '40': line number out of range on repetition 1"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1105,7 +1105,7 @@ fn test_linenum_out_of_range4() { ucmd.args(&["numbers50.txt", "40", "{*}"]) .fails() .stdout_is("108\n33\n") - .stderr_is("csplit: error: '40': line number out of range on repetition 1"); + .stderr_is("csplit: '40': line number out of range on repetition 1"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1116,7 +1116,7 @@ fn test_linenum_out_of_range4() { ucmd.args(&["numbers50.txt", "40", "{*}", "-k"]) .fails() .stdout_is("108\n33\n") - .stderr_is("csplit: error: '40': line number out of range on repetition 1"); + .stderr_is("csplit: '40': line number out of range on repetition 1"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1132,7 +1132,7 @@ fn test_skip_to_match_negative_offset_before_a_match() { ucmd.args(&["numbers50.txt", "/20/-10", "/15/"]) .fails() .stdout_is("18\n123\n") - .stderr_is("csplit: error: '/15/': match not found"); + .stderr_is("csplit: '/15/': match not found"); let count = glob(&at.plus_as_string("xx*")) .expect("there should be splits created") @@ -1177,7 +1177,7 @@ fn test_corner_case2() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "/10/-5", "/10/"]) .fails() - .stderr_is("csplit: error: '/10/': match not found") + .stderr_is("csplit: '/10/': match not found") .stdout_is("8\n133\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1191,7 +1191,7 @@ fn test_corner_case3() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "/15/-3", "14", "/15/"]) .fails() - .stderr_is("csplit: error: '/15/': match not found") + .stderr_is("csplit: '/15/': match not found") .stdout_is("24\n6\n111\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1223,7 +1223,7 @@ fn test_up_to_match_context_underflow() { ucmd.args(&["numbers50.txt", "/5/-10"]) .fails() .stdout_is("0\n141\n") - .stderr_is("csplit: error: '/5/-10': line number out of range"); + .stderr_is("csplit: '/5/-10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -1234,7 +1234,7 @@ fn test_up_to_match_context_underflow() { ucmd.args(&["numbers50.txt", "/5/-10", "-k"]) .fails() .stdout_is("0\n141\n") - .stderr_is("csplit: error: '/5/-10': line number out of range"); + .stderr_is("csplit: '/5/-10': line number out of range"); let count = glob(&at.plus_as_string("xx*")) .expect("counting splits") @@ -1251,7 +1251,7 @@ fn test_linenum_range_with_up_to_match1() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/12/-5"]) .fails() - .stderr_is("csplit: error: '/12/-5': line number out of range") + .stderr_is("csplit: '/12/-5': line number out of range") .stdout_is("18\n0\n123\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1262,7 +1262,7 @@ fn test_linenum_range_with_up_to_match1() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/12/-5", "-k"]) .fails() - .stderr_is("csplit: error: '/12/-5': line number out of range") + .stderr_is("csplit: '/12/-5': line number out of range") .stdout_is("18\n0\n123\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1281,7 +1281,7 @@ fn test_linenum_range_with_up_to_match2() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/12/-15"]) .fails() - .stderr_is("csplit: error: '/12/-15': line number out of range") + .stderr_is("csplit: '/12/-15': line number out of range") .stdout_is("18\n0\n123\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1292,7 +1292,7 @@ fn test_linenum_range_with_up_to_match2() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/12/-15", "-k"]) .fails() - .stderr_is("csplit: error: '/12/-15': line number out of range") + .stderr_is("csplit: '/12/-15': line number out of range") .stdout_is("18\n0\n123\n"); let count = glob(&at.plus_as_string("xx*")) @@ -1310,7 +1310,7 @@ fn test_linenum_range_with_up_to_match3() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["numbers50.txt", "10", "/10/", "-k"]) .fails() - .stderr_is("csplit: error: '/10/': match not found") + .stderr_is("csplit: '/10/': match not found") .stdout_is("18\n123\n"); let count = glob(&at.plus_as_string("xx*")) diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 875317721..413b73154 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -149,11 +149,11 @@ fn test_directory_and_no_such_file() { ucmd.arg("-b1") .arg("some") .run() - .stderr_is("cut: error: some: Is a directory\n"); + .stderr_is("cut: some: Is a directory\n"); new_ucmd!() .arg("-b1") .arg("some") .run() - .stderr_is("cut: error: some: No such file or directory\n"); + .stderr_is("cut: some: No such file or directory\n"); } diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c72bd02a6..15620be52 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -76,7 +76,7 @@ fn test_du_basics_bad_name() { new_ucmd!() .arg("bad_name") .succeeds() // TODO: replace with ".fails()" once `du` is fixed - .stderr_only("du: error: bad_name: No such file or directory\n"); + .stderr_only("du: bad_name: No such file or directory\n"); } #[test] diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 6a969b5e9..f20739e13 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -17,11 +17,11 @@ fn test_complex_arithmetic() { .args(&["9223372036854775807", "+", "9223372036854775807"]) .run(); run.stdout_is(""); - run.stderr_is("expr: error: +: Numerical result out of range"); + run.stderr_is("expr: +: Numerical result out of range"); let run = new_ucmd!().args(&["9", "/", "0"]).run(); run.stdout_is(""); - run.stderr_is("expr: error: division by zero"); + run.stderr_is("expr: division by zero"); } #[test] diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index a83fae58e..0d6d9bb24 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -30,7 +30,7 @@ fn test_fmt_w_too_big() { //.stdout_is_fixture("call_graph.expected"); assert_eq!( result.stderr_str().trim(), - "fmt: error: invalid width: '2501': Numerical result out of range" + "fmt: invalid width: '2501': Numerical result out of range" ); } #[test] diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index 534736a32..1f8249aab 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -7,7 +7,7 @@ use crate::common::util::*; // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" // stderr: "whoami: cannot find name for user ID 1001" // Maybe: "adduser --uid 1001 username" can put things right? -// stderr = id: error: Could not find uid 1001: No such id: 1001 +// stderr = id: Could not find uid 1001: No such id: 1001 fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { if !result.succeeded() { println!("result.stdout = {}", result.stdout_str()); diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index fc4459072..68cd9700f 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -301,7 +301,7 @@ fn test_install_target_new_file_with_group() { .arg(format!("{}/{}", dir, file)) .run(); - if is_ci() && result.stderr_str().contains("error: no such group:") { + if is_ci() && result.stderr_str().contains("no such group:") { // In the CI, some server are failing to return the group. // As seems to be a configuration issue, ignoring it return; diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index b0311df84..a8f046851 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -148,7 +148,7 @@ fn multitab_character() { .arg("-t") .arg("э") .fails() - .stderr_is("join: error: multi-character tab э"); + .stderr_is("join: multi-character tab э"); } #[test] @@ -211,7 +211,7 @@ fn empty_format() { .arg("-o") .arg("") .fails() - .stderr_is("join: error: invalid file number in field spec: ''"); + .stderr_is("join: invalid file number in field spec: ''"); } #[test] diff --git a/tests/by-util/test_link.rs b/tests/by-util/test_link.rs index 99559a7fe..6ac3f35cc 100644 --- a/tests/by-util/test_link.rs +++ b/tests/by-util/test_link.rs @@ -23,7 +23,7 @@ fn test_link_no_circular() { ucmd.args(&[link, link]) .fails() - .stderr_is("link: error: No such file or directory (os error 2)\n"); + .stderr_is("link: No such file or directory (os error 2)\n"); assert!(!at.file_exists(link)); } @@ -35,7 +35,7 @@ fn test_link_nonexistent_file() { ucmd.args(&[file, link]) .fails() - .stderr_is("link: error: No such file or directory (os error 2)\n"); + .stderr_is("link: No such file or directory (os error 2)\n"); assert!(!at.file_exists(file)); assert!(!at.file_exists(link)); } diff --git a/tests/by-util/test_ln.rs b/tests/by-util/test_ln.rs index 646091b09..f2508ecbf 100644 --- a/tests/by-util/test_ln.rs +++ b/tests/by-util/test_ln.rs @@ -409,7 +409,7 @@ fn test_symlink_missing_destination() { at.touch(file); ucmd.args(&["-s", "-T", file]).fails().stderr_is(format!( - "ln: error: missing destination file operand after '{}'", + "ln: missing destination file operand after '{}'", file )); } diff --git a/tests/by-util/test_logname.rs b/tests/by-util/test_logname.rs index bd9d04a50..0e8125191 100644 --- a/tests/by-util/test_logname.rs +++ b/tests/by-util/test_logname.rs @@ -9,7 +9,7 @@ fn test_normal() { for (key, value) in env::vars() { println!("{}: {}", key, value); } - if (is_ci() || uucore::os::is_wsl_1()) && result.stderr_str().contains("error: no login name") { + if (is_ci() || uucore::os::is_wsl_1()) && result.stderr_str().contains("no login name") { // ToDO: investigate WSL failure // In the CI, some server are failing to return logname. // As seems to be a configuration issue, ignoring it diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 6d6c65194..2ae57ad7f 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -167,7 +167,7 @@ fn test_ls_width() { .ucmd() .args(&option.split(" ").collect::>()) .fails() - .stderr_only("ls: error: invalid line width: ‘1a’"); + .stderr_only("ls: invalid line width: ‘1a’"); } } @@ -875,7 +875,7 @@ fn test_ls_files_dirs() { .ucmd() .arg("doesntexist") .fails() - .stderr_contains(&"error: 'doesntexist': No such file or directory"); + .stderr_contains(&"'doesntexist': No such file or directory"); // One exists, the other doesn't scene @@ -883,7 +883,7 @@ fn test_ls_files_dirs() { .arg("a") .arg("doesntexist") .fails() - .stderr_contains(&"error: 'doesntexist': No such file or directory") + .stderr_contains(&"'doesntexist': No such file or directory") .stdout_contains(&"a:"); } diff --git a/tests/by-util/test_mkfifo.rs b/tests/by-util/test_mkfifo.rs index 23108d976..318a2ea5d 100644 --- a/tests/by-util/test_mkfifo.rs +++ b/tests/by-util/test_mkfifo.rs @@ -2,9 +2,7 @@ use crate::common::util::*; #[test] fn test_create_fifo_missing_operand() { - new_ucmd!() - .fails() - .stderr_is("mkfifo: error: missing operand"); + new_ucmd!().fails().stderr_is("mkfifo: missing operand"); } #[test] @@ -43,5 +41,5 @@ fn test_create_one_fifo_already_exists() { .arg("abcdef") .arg("abcdef") .fails() - .stderr_is("mkfifo: error: cannot create fifo 'abcdef': File exists"); + .stderr_is("mkfifo: cannot create fifo 'abcdef': File exists"); } diff --git a/tests/by-util/test_mktemp.rs b/tests/by-util/test_mktemp.rs index c273c407c..617f0fd06 100644 --- a/tests/by-util/test_mktemp.rs +++ b/tests/by-util/test_mktemp.rs @@ -120,7 +120,7 @@ fn test_mktemp_mktemp_t() { .arg(TEST_TEMPLATE8) .fails() .no_stdout() - .stderr_contains("error: suffix cannot contain any path separators"); + .stderr_contains("suffix cannot contain any path separators"); } #[test] diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 47532e2e5..e0723a479 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -472,7 +472,7 @@ fn test_mv_overwrite_nonempty_dir() { at.touch(dummy); // Not same error as GNU; the error message is a rust builtin // TODO: test (and implement) correct error message (or at least decide whether to do so) - // Current: "mv: error: couldn't rename path (Directory not empty; from=a; to=b)" + // Current: "mv: couldn't rename path (Directory not empty; from=a; to=b)" // GNU: "mv: cannot move ‘a’ to ‘b’: Directory not empty" // Verbose output for the move should not be shown on failure @@ -539,7 +539,7 @@ fn test_mv_errors() { .arg(dir) .fails() .stderr_is(format!( - "mv: error: cannot overwrite directory ‘{}’ with non-directory\n", + "mv: cannot overwrite directory ‘{}’ with non-directory\n", dir )); diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index d3457c686..9e004b98b 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -25,7 +25,7 @@ fn test_adjustment_with_no_command_should_error() { new_ucmd!() .args(&["-n", "19"]) .run() - .stderr_is("nice: error: A command must be given with an adjustment.\nTry \"nice --help\" for more information.\n"); + .stderr_is("nice: A command must be given with an adjustment.\nTry \"nice --help\" for more information.\n"); } #[test] diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 9a068887c..2a87038d5 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -258,7 +258,7 @@ fn test_rm_no_operand() { let mut ucmd = new_ucmd!(); ucmd.fails() - .stderr_is("rm: error: missing an argument\nrm: error: for help, try 'rm --help'\n"); + .stderr_is("rm: missing an argument\nrm: for help, try 'rm --help'\n"); } #[test] diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 34531cf22..eef2d50f5 100644 --- a/tests/by-util/test_rmdir.rs +++ b/tests/by-util/test_rmdir.rs @@ -39,7 +39,7 @@ fn test_rmdir_nonempty_directory_no_parents() { assert!(at.file_exists(file)); ucmd.arg(dir).fails().stderr_is( - "rmdir: error: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \ + "rmdir: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \ empty\n", ); @@ -59,9 +59,9 @@ fn test_rmdir_nonempty_directory_with_parents() { assert!(at.file_exists(file)); ucmd.arg("-p").arg(dir).fails().stderr_is( - "rmdir: error: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \ - empty\nrmdir: error: failed to remove 'test_rmdir_nonempty/with': Directory not \ - empty\nrmdir: error: failed to remove 'test_rmdir_nonempty': Directory not \ + "rmdir: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \ + empty\nrmdir: failed to remove 'test_rmdir_nonempty/with': Directory not \ + empty\nrmdir: failed to remove 'test_rmdir_nonempty': Directory not \ empty\n", ); diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index e4676b379..133dc0028 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -42,7 +42,7 @@ fn test_invalid_buffer_size() { .arg(invalid_buffer_size) .fails() .stderr_only(format!( - "sort: error: failed to parse buffer size `{}`: invalid digit found in string", + "sort: failed to parse buffer size `{}`: invalid digit found in string", invalid_buffer_size )); } @@ -471,7 +471,7 @@ fn test_keys_invalid_field() { new_ucmd!() .args(&["-k", "1."]) .fails() - .stderr_only("sort: error: failed to parse character index for key `1.`: cannot parse integer from empty string"); + .stderr_only("sort: failed to parse character index for key `1.`: cannot parse integer from empty string"); } #[test] @@ -479,7 +479,7 @@ fn test_keys_invalid_field_option() { new_ucmd!() .args(&["-k", "1.1x"]) .fails() - .stderr_only("sort: error: invalid option for key: `x`"); + .stderr_only("sort: invalid option for key: `x`"); } #[test] @@ -487,14 +487,15 @@ fn test_keys_invalid_field_zero() { new_ucmd!() .args(&["-k", "0.1"]) .fails() - .stderr_only("sort: error: field index was 0"); + .stderr_only("sort: field index was 0"); } #[test] fn test_keys_invalid_char_zero() { - new_ucmd!().args(&["-k", "1.0"]).fails().stderr_only( - "sort: error: invalid character index 0 in `1.0` for the start position of a field", - ); + new_ucmd!() + .args(&["-k", "1.0"]) + .fails() + .stderr_only("sort: invalid character index 0 in `1.0` for the start position of a field"); } #[test] diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 4105cb7a2..4732d2def 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -26,7 +26,7 @@ fn test_stdbuf_line_buffered_stdout() { #[test] fn test_stdbuf_no_buffer_option_fails() { new_ucmd!().args(&["head"]).fails().stderr_is( - "error: The following required arguments were not provided:\n \ + "The following required arguments were not provided:\n \ --error \n \ --input \n \ --output \n\n\ @@ -49,10 +49,9 @@ fn test_stdbuf_trailing_var_arg() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_line_buffering_stdin_fails() { - new_ucmd!() - .args(&["-i", "L", "head"]) - .fails() - .stderr_is("stdbuf: error: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information."); + new_ucmd!().args(&["-i", "L", "head"]).fails().stderr_is( + "stdbuf: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information.", + ); } #[cfg(not(target_os = "windows"))] @@ -61,5 +60,5 @@ fn test_stdbuf_invalid_mode_fails() { new_ucmd!() .args(&["-i", "1024R", "head"]) .fails() - .stderr_is("stdbuf: error: invalid mode 1024R\nTry 'stdbuf --help' for more information."); + .stderr_is("stdbuf: invalid mode 1024R\nTry 'stdbuf --help' for more information."); } diff --git a/tests/by-util/test_sum.rs b/tests/by-util/test_sum.rs index d12455749..f09ba9d00 100644 --- a/tests/by-util/test_sum.rs +++ b/tests/by-util/test_sum.rs @@ -59,9 +59,7 @@ fn test_invalid_file() { at.mkdir("a"); - ucmd.arg("a") - .fails() - .stderr_is("sum: error: 'a' Is a directory"); + ucmd.arg("a").fails().stderr_is("sum: 'a' Is a directory"); } #[test] @@ -70,5 +68,5 @@ fn test_invalid_metadata() { ucmd.arg("b") .fails() - .stderr_is("sum: error: 'b' No such file or directory"); + .stderr_is("sum: 'b' No such file or directory"); } diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index c1e53faf3..2645c38ca 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -145,7 +145,7 @@ fn test_invalid_utf8() { .arg("not-utf8-sequence.txt") .run() .failure() - .stderr_only("uniq: error: invalid utf-8 sequence of 1 bytes from index 0"); + .stderr_only("uniq: invalid utf-8 sequence of 1 bytes from index 0"); } #[test] diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index fa8f962c4..1999e965c 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -22,7 +22,7 @@ fn test_unlink_multiple_files() { at.touch(file_b); ucmd.arg(file_a).arg(file_b).fails().stderr_is( - "unlink: error: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \ + "unlink: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \ for more information.\n", ); } @@ -35,7 +35,7 @@ fn test_unlink_directory() { at.mkdir(dir); ucmd.arg(dir).fails().stderr_is( - "unlink: error: cannot unlink 'test_unlink_empty_directory': Not a regular file \ + "unlink: cannot unlink 'test_unlink_empty_directory': Not a regular file \ or symlink\n", ); } @@ -45,7 +45,7 @@ fn test_unlink_nonexistent() { let file = "test_unlink_nonexistent"; new_ucmd!().arg(file).fails().stderr_is( - "unlink: error: Cannot stat 'test_unlink_nonexistent': No such file or directory \ + "unlink: Cannot stat 'test_unlink_nonexistent': No such file or directory \ (os error 2)\n", ); } diff --git a/tests/by-util/test_whoami.rs b/tests/by-util/test_whoami.rs index dc6a1ceed..a98541b2d 100644 --- a/tests/by-util/test_whoami.rs +++ b/tests/by-util/test_whoami.rs @@ -5,7 +5,7 @@ use crate::common::util::*; // considered okay. If we are not inside the CI this calls assert!(result.success). // // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" -// stderr: "whoami: error: failed to get username" +// stderr: "whoami: failed to get username" // Maybe: "adduser --uid 1001 username" can put things right? fn skipping_test_is_okay(result: &CmdResult, needle: &str) -> bool { if !result.succeeded() { From 8fe34c72d2980f39d2d7664b92c776a9c66d1b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Wed, 26 May 2021 03:07:49 +0300 Subject: [PATCH 374/399] test: fix tests --- tests/by-util/test_stdbuf.rs | 2 +- tests/by-util/test_sync.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 4732d2def..2e09601ce 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -26,7 +26,7 @@ fn test_stdbuf_line_buffered_stdout() { #[test] fn test_stdbuf_no_buffer_option_fails() { new_ucmd!().args(&["head"]).fails().stderr_is( - "The following required arguments were not provided:\n \ + "error: The following required arguments were not provided:\n \ --error \n \ --input \n \ --output \n\n\ diff --git a/tests/by-util/test_sync.rs b/tests/by-util/test_sync.rs index 436bfdef3..033651910 100644 --- a/tests/by-util/test_sync.rs +++ b/tests/by-util/test_sync.rs @@ -37,5 +37,5 @@ fn test_sync_no_existing_files() { .arg("--data") .arg("do-no-exist") .fails() - .stderr_contains("error: cannot stat"); + .stderr_contains("cannot stat"); } From 12f207a6d6f002e5c4f0aafc17b95c9d403e75c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ya=C4=9F=C4=B1z=20can=20De=C4=9Firmenci?= Date: Wed, 26 May 2021 03:21:53 +0300 Subject: [PATCH 375/399] test: fix tests --- tests/by-util/test_install.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 68cd9700f..fb79454c1 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -328,7 +328,7 @@ fn test_install_target_new_file_with_owner() { .arg(format!("{}/{}", dir, file)) .run(); - if is_ci() && result.stderr_str().contains("error: no such user:") { + if is_ci() && result.stderr_str().contains("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; From a8a1ec7faf7ef1994366f538f040dc866a2c9686 Mon Sep 17 00:00:00 2001 From: Matt Blessed Date: Tue, 25 May 2021 16:41:07 -0400 Subject: [PATCH 376/399] cp: implement backup control with tests --- src/uu/cp/src/cp.rs | 64 +++++++----- tests/by-util/test_cp.rs | 210 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 247 insertions(+), 27 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 3d6faf66a..7eaa21c11 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -47,6 +47,7 @@ use std::os::windows::ffi::OsStrExt; use std::path::{Path, PathBuf, StripPrefixError}; use std::str::FromStr; use std::string::ToString; +use uucore::backup_control::{self, BackupMode}; use uucore::fs::resolve_relative_path; use uucore::fs::{canonicalize, CanonicalizeMode}; use walkdir::WalkDir; @@ -169,14 +170,6 @@ pub enum TargetType { File, } -#[derive(Clone, Eq, PartialEq)] -pub enum BackupMode { - ExistingBackup, - NoBackup, - NumberedBackup, - SimpleBackup, -} - pub enum CopyMode { Link, SymLink, @@ -201,7 +194,7 @@ pub enum Attribute { #[allow(dead_code)] pub struct Options { attributes_only: bool, - backup: bool, + backup: BackupMode, copy_contents: bool, copy_mode: CopyMode, dereference: bool, @@ -222,6 +215,7 @@ pub struct Options { static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; +static LONG_HELP: &str = ""; static EXIT_OK: i32 = 0; static EXIT_ERR: i32 = 1; @@ -301,6 +295,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) + .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) .usage(&usage[..]) .arg(Arg::with_name(OPT_TARGET_DIRECTORY) .short("t") @@ -364,12 +359,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg(Arg::with_name(OPT_BACKUP) .short("b") .long(OPT_BACKUP) - .help("make a backup of each existing destination file")) + .help("make a backup of each existing destination file") + .takes_value(true) + .require_equals(true) + .min_values(0) + .possible_values(backup_control::BACKUP_CONTROL_VALUES) + .value_name("CONTROL") + ) .arg(Arg::with_name(OPT_SUFFIX) .short("S") .long(OPT_SUFFIX) .takes_value(true) - .default_value("~") .value_name("SUFFIX") .help("override the usual backup suffix")) .arg(Arg::with_name(OPT_UPDATE) @@ -585,7 +585,24 @@ impl Options { || matches.is_present(OPT_RECURSIVE_ALIAS) || matches.is_present(OPT_ARCHIVE); - let backup = matches.is_present(OPT_BACKUP) || (matches.occurrences_of(OPT_SUFFIX) > 0); + let backup_mode = backup_control::determine_backup_mode( + matches.is_present(OPT_BACKUP), + matches.value_of(OPT_BACKUP), + ); + let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); + + let overwrite = OverwriteMode::from_matches(matches); + + if overwrite == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { + show_error!( + "options --backup and --no-clobber are mutually exclusive\n\ + Try '{} --help' for more information.", + executable!() + ); + return Err(Error::Error( + "options --backup and --no-clobber are mutually exclusive".to_owned(), + )); + } // Parse target directory options let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY); @@ -631,9 +648,7 @@ impl Options { || matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) || matches.is_present(OPT_ARCHIVE), one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), - overwrite: OverwriteMode::from_matches(matches), parents: matches.is_present(OPT_PARENTS), - backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(), update: matches.is_present(OPT_UPDATE), verbose: matches.is_present(OPT_VERBOSE), strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), @@ -654,7 +669,9 @@ impl Options { ReflinkMode::Never } }, - backup, + backup: backup_mode, + backup_suffix: backup_suffix, + overwrite: overwrite, no_target_dir, preserve_attributes, recursive, @@ -1090,14 +1107,10 @@ fn context_for(src: &Path, dest: &Path) -> String { format!("'{}' -> '{}'", src.display(), dest.display()) } -/// Implements a relatively naive backup that is not as full featured -/// as GNU cp. No CONTROL version control method argument is taken -/// for backups. -/// TODO: Add version control methods -fn backup_file(path: &Path, suffix: &str) -> CopyResult { - let mut backup_path = path.to_path_buf().into_os_string(); - backup_path.push(suffix); - fs::copy(path, &backup_path)?; +/// Implements a simple backup copy for the destination file. +/// TODO: for the backup, should this function be replaced by `copy_file(...)`? +fn backup_dest(dest: &Path, backup_path: &PathBuf) -> CopyResult { + fs::copy(dest, &backup_path)?; Ok(backup_path.into()) } @@ -1108,8 +1121,9 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe options.overwrite.verify(dest)?; - if options.backup { - backup_file(dest, &options.backup_suffix)?; + let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); + if let Some(backup_path) = backup_path { + backup_dest(dest, &backup_path)?; } match options.overwrite { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1e99da0fb..dddba595c 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -214,8 +214,8 @@ fn test_cp_arg_symlink() { fn test_cp_arg_no_clobber() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg(TEST_HELLO_WORLD_SOURCE) - .arg("--no-clobber") .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg("--no-clobber") .succeeds(); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); @@ -305,7 +305,23 @@ fn test_cp_arg_backup() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg(TEST_HELLO_WORLD_SOURCE) - .arg("--backup") + .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg("-b") + .succeeds(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_arg_backup_arg_first() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup") + .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) .succeeds(); @@ -321,6 +337,7 @@ fn test_cp_arg_suffix() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg("-b") .arg("--suffix") .arg(".bak") .arg(TEST_HOW_ARE_YOU_SOURCE) @@ -333,6 +350,195 @@ fn test_cp_arg_suffix() { ); } +#[test] +fn test_cp_custom_backup_suffix_via_env() { + let (at, mut ucmd) = at_and_ucmd!(); + let suffix = "super-suffix-of-the-century"; + + ucmd.arg("-b") + .env("SIMPLE_BACKUP_SUFFIX", suffix) + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}{}", TEST_HOW_ARE_YOU_SOURCE, suffix)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_numbered_with_t() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=t") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}.~1~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_numbered() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=numbered") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}.~1~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_existing() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=existing") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_nil() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=nil") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_numbered_if_existing_backup_existing() { + let (at, mut ucmd) = at_and_ucmd!(); + let existing_backup = &*format!("{}.~1~", TEST_HOW_ARE_YOU_SOURCE); + at.touch(existing_backup); + + ucmd.arg("--backup=existing") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(TEST_HOW_ARE_YOU_SOURCE)); + assert!(at.file_exists(existing_backup)); + assert_eq!( + at.read(&*format!("{}.~2~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_numbered_if_existing_backup_nil() { + let (at, mut ucmd) = at_and_ucmd!(); + let existing_backup = &*format!("{}.~1~", TEST_HOW_ARE_YOU_SOURCE); + + at.touch(existing_backup); + ucmd.arg("--backup=nil") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(TEST_HOW_ARE_YOU_SOURCE)); + assert!(at.file_exists(existing_backup)); + assert_eq!( + at.read(&*format!("{}.~2~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_simple() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=simple") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_never() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=never") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + +#[test] +fn test_cp_backup_none() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=none") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert!(!at.file_exists(&format!("{}~", TEST_HOW_ARE_YOU_SOURCE))); +} + +#[test] +fn test_cp_backup_off() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup=off") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .succeeds() + .no_stderr(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert!(!at.file_exists(&format!("{}~", TEST_HOW_ARE_YOU_SOURCE))); +} + #[test] fn test_cp_deref_conflicting_options() { new_ucmd!() From eda72b52081c7b92d412735f578645a37bdf6490 Mon Sep 17 00:00:00 2001 From: Syukron Rifail M Date: Sat, 15 May 2021 21:29:45 +0700 Subject: [PATCH 377/399] du: replace getopts with clap --- Cargo.lock | 1 + src/uu/du/Cargo.toml | 1 + src/uu/du/src/du.rs | 371 +++++++++++++++++++++++++++---------------- 3 files changed, 234 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0674d3de0..5e1470c88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1986,6 +1986,7 @@ name = "uu_du" version = "0.0.6" dependencies = [ "chrono", + "clap", "uucore", "uucore_procs", "winapi 0.3.9", diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 3ce9d8361..023c0a021 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/du.rs" [dependencies] +clap = "2.33" chrono = "0.4" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 89dd3f739..6bd4f23e4 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -12,6 +12,7 @@ extern crate uucore; use chrono::prelude::DateTime; use chrono::Local; +use clap::{App, Arg}; use std::collections::HashSet; use std::env; use std::fs; @@ -37,6 +38,27 @@ use winapi::um::winbase::GetFileInformationByHandleEx; #[cfg(windows)] use winapi::um::winnt::{FILE_ID_128, ULONGLONG}; +mod options { + pub const NULL: &str = "0"; + pub const ALL: &str = "all"; + pub const APPARENT_SIZE: &str = "apparent-size"; + pub const BLOCK_SIZE: &str = "B"; + pub const BYTES: &str = "b"; + pub const TOTAL: &str = "c"; + pub const MAX_DEPTH: &str = "d"; + pub const HUMAN_READABLE: &str = "h"; + pub const BLOCK_SIZE_1K: &str = "k"; + pub const COUNT_LINKS: &str = "l"; + pub const BLOCK_SIZE_1M: &str = "m"; + pub const SEPARATE_DIRS: &str = "S"; + pub const SUMMARIZE: &str = "s"; + pub const SI: &str = "si"; + pub const TIME: &str = "time"; + pub const TIME_STYLE: &str = "time-style"; + pub const FILE: &str = "FILE"; +} + +const VERSION: &str = env!("CARGO_PKG_VERSION"); const NAME: &str = "du"; const SUMMARY: &str = "estimate file space usage"; const LONG_HELP: &str = " @@ -220,14 +242,14 @@ fn unit_string_to_number(s: &str) -> Option { Some(number * multiple.pow(unit)) } -fn translate_to_pure_number(s: &Option) -> Option { +fn translate_to_pure_number(s: &Option<&str>) -> Option { match *s { Some(ref s) => unit_string_to_number(s), None => None, } } -fn read_block_size(s: Option) -> u64 { +fn read_block_size(s: Option<&str>) -> u64 { match translate_to_pure_number(&s) { Some(v) => v, None => { @@ -236,7 +258,8 @@ fn read_block_size(s: Option) -> u64 { }; for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { - if let Some(quantity) = translate_to_pure_number(&env::var(env_var).ok()) { + let env_size = env::var(env_var).ok(); + if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) { return quantity; } } @@ -361,126 +384,189 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String { format!("{}", ((size as f64) / (block_size as f64)).ceil()) } +fn get_usage() -> String { + format!( + "{0} [OPTION]... [FILE]... + {0} [OPTION]... --files0-from=F", + executable!() + ) +} + #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let syntax = format!( - "[OPTION]... [FILE]... - {0} [OPTION]... --files0-from=F", - NAME - ); - let matches = app!(&syntax, SUMMARY, LONG_HELP) - // In task - .optflag( - "a", - "all", - " write counts for all files, not just directories", - ) - // In main - .optflag( - "", - "apparent-size", - "print apparent sizes, rather than disk usage - although the apparent size is usually smaller, it may be larger due to holes - in ('sparse') files, internal fragmentation, indirect blocks, and the like", - ) - // In main - .optopt( - "B", - "block-size", - "scale sizes by SIZE before printing them. - E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below.", - "SIZE", - ) - // In main - .optflag( - "b", - "bytes", - "equivalent to '--apparent-size --block-size=1'", - ) - // In main - .optflag("c", "total", "produce a grand total") - // In task - // opts.optflag("D", "dereference-args", "dereference only symlinks that are listed - // on the command line"), - // In main - // opts.optopt("", "files0-from", "summarize disk usage of the NUL-terminated file - // names specified in file F; - // If F is - then read names from standard input", "F"), - // // In task - // opts.optflag("H", "", "equivalent to --dereference-args (-D)"), - // In main - .optflag( - "h", - "human-readable", - "print sizes in human readable format (e.g., 1K 234M 2G)", - ) - // In main - .optflag("", "si", "like -h, but use powers of 1000 not 1024") - // In main - .optflag("k", "", "like --block-size=1K") - // In task - .optflag("l", "count-links", "count sizes many times if hard linked") - // // In main - .optflag("m", "", "like --block-size=1M") - // // In task - // opts.optflag("L", "dereference", "dereference all symbolic links"), - // // In task - // opts.optflag("P", "no-dereference", "don't follow any symbolic links (this is the default)"), - // // In main - .optflag( - "0", - "null", - "end each output line with 0 byte rather than newline", - ) - // In main - .optflag( - "S", - "separate-dirs", - "do not include size of subdirectories", - ) - // In main - .optflag("s", "summarize", "display only a total for each argument") - // // In task - // opts.optflag("x", "one-file-system", "skip directories on different file systems"), - // // In task - // opts.optopt("X", "exclude-from", "exclude files that match any pattern in FILE", "FILE"), - // // In task - // opts.optopt("", "exclude", "exclude files that match PATTERN", "PATTERN"), - // In main - .optopt( - "d", - "max-depth", - "print the total for a directory (or file, with --all) - only if it is N or fewer levels below the command - line argument; --max-depth=0 is the same as --summarize", - "N", - ) - // In main - .optflagopt( - "", - "time", - "show time of the last modification of any file in the - directory, or any of its subdirectories. If WORD is given, show time as WORD instead - of modification time: atime, access, use, ctime or status", - "WORD", - ) - // In main - .optopt( - "", - "time-style", - "show times using style STYLE: - full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'", - "STYLE", - ) - .parse(args); + let usage = get_usage(); - let summarize = matches.opt_present("summarize"); + let matches = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .usage(&usage[..]) + .after_help(LONG_HELP) + .arg( + Arg::with_name(options::ALL) + .short("a") + .long(options::ALL) + .help("write counts for all files, not just directories"), + ) + .arg( + Arg::with_name(options::APPARENT_SIZE) + .long(options::APPARENT_SIZE) + .help( + "print apparent sizes, rather than disk usage \ + although the apparent size is usually smaller, it may be larger due to holes \ + in ('sparse') files, internal fragmentation, indirect blocks, and the like" + ) + ) + .arg( + Arg::with_name(options::BLOCK_SIZE) + .short("B") + .long("block-size") + .value_name("SIZE") + .help( + "scale sizes by SIZE before printing them. \ + E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below." + ) + ) + .arg( + Arg::with_name(options::BYTES) + .short("b") + .long("bytes") + .help("equivalent to '--apparent-size --block-size=1'") + ) + .arg( + Arg::with_name(options::TOTAL) + .long("total") + .short("c") + .help("produce a grand total") + ) + .arg( + Arg::with_name(options::MAX_DEPTH) + .short("d") + .long("max-depth") + .value_name("N") + .help( + "print the total for a directory (or file, with --all) \ + only if it is N or fewer levels below the command \ + line argument; --max-depth=0 is the same as --summarize" + ) + ) + .arg( + Arg::with_name(options::HUMAN_READABLE) + .long("human-readable") + .short("h") + .help("print sizes in human readable format (e.g., 1K 234M 2G)") + ) + .arg( + Arg::with_name("inodes") + .long("inodes") + .help( + "list inode usage information instead of block usage like --block-size=1K" + ) + ) + .arg( + Arg::with_name(options::BLOCK_SIZE_1K) + .short("k") + .help("like --block-size=1K") + ) + .arg( + Arg::with_name(options::COUNT_LINKS) + .short("l") + .long("count-links") + .help("count sizes many times if hard linked") + ) + // .arg( + // Arg::with_name("dereference") + // .short("L") + // .long("dereference") + // .help("dereference all symbolic links") + // ) + // .arg( + // Arg::with_name("no-dereference") + // .short("P") + // .long("no-dereference") + // .help("don't follow any symbolic links (this is the default)") + // ) + .arg( + Arg::with_name(options::BLOCK_SIZE_1M) + .short("m") + .help("like --block-size=1M") + ) + .arg( + Arg::with_name(options::NULL) + .short("0") + .long("null") + .help("end each output line with 0 byte rather than newline") + ) + .arg( + Arg::with_name(options::SEPARATE_DIRS) + .short("S") + .long("separate-dirs") + .help("do not include size of subdirectories") + ) + .arg( + Arg::with_name(options::SUMMARIZE) + .short("s") + .long("summarize") + .help("display only a total for each argument") + ) + .arg( + Arg::with_name(options::SI) + .long(options::SI) + .help("like -h, but use powers of 1000 not 1024") + ) + // .arg( + // Arg::with_name("one-file-system") + // .short("x") + // .long("one-file-system") + // .help("skip directories on different file systems") + // ) + // .arg( + // Arg::with_name("") + // .short("x") + // .long("exclude-from") + // .value_name("FILE") + // .help("exclude files that match any pattern in FILE") + // ) + // .arg( + // Arg::with_name("exclude") + // .long("exclude") + // .value_name("PATTERN") + // .help("exclude files that match PATTERN") + // ) + .arg( + Arg::with_name(options::TIME) + .long(options::TIME) + .value_name("WORD") + .require_equals(true) + .min_values(0) + .help( + "show time of the last modification of any file in the \ + directory, or any of its subdirectories. If WORD is given, show time as WORD instead \ + of modification time: atime, access, use, ctime or status" + ) + ) + .arg( + Arg::with_name(options::TIME_STYLE) + .long(options::TIME_STYLE) + .value_name("STYLE") + .help( + "show times using style STYLE: \ + full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'" + ) + ) + .arg( + Arg::with_name(options::FILE) + .hidden(true) + .multiple(true) + ) + .get_matches_from(args); - let max_depth_str = matches.opt_str("max-depth"); + let summarize = matches.is_present(options::SUMMARIZE); + + let max_depth_str = matches.value_of(options::MAX_DEPTH); let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::().ok()); match (max_depth_str, max_depth) { (Some(ref s), _) if summarize => { @@ -495,34 +581,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let options = Options { - all: matches.opt_present("all"), + all: matches.is_present(options::ALL), program_name: NAME.to_owned(), max_depth, - total: matches.opt_present("total"), - separate_dirs: matches.opt_present("S"), + total: matches.is_present(options::TOTAL), + separate_dirs: matches.is_present(options::SEPARATE_DIRS), }; - let strs = if matches.free.is_empty() { - vec!["./".to_owned()] // TODO: gnu `du` doesn't use trailing "/" here - } else { - matches.free.clone() + let strs = match matches.value_of(options::FILE) { + Some(_) => matches.values_of(options::FILE).unwrap().collect(), + None => { + vec!["./"] // TODO: gnu `du` doesn't use trailing "/" here + } }; - let block_size = read_block_size(matches.opt_str("block-size")); + let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE)); - let multiplier: u64 = if matches.opt_present("si") { + let multiplier: u64 = if matches.is_present(options::SI) { 1000 } else { 1024 }; let convert_size_fn = { - if matches.opt_present("human-readable") || matches.opt_present("si") { + if matches.is_present(options::HUMAN_READABLE) || matches.is_present(options::SI) { convert_size_human - } else if matches.opt_present("b") { + } else if matches.is_present(options::BYTES) { convert_size_b - } else if matches.opt_present("k") { + } else if matches.is_present(options::BLOCK_SIZE_1K) { convert_size_k - } else if matches.opt_present("m") { + } else if matches.is_present(options::BLOCK_SIZE_1M) { convert_size_m } else { convert_size_other @@ -530,8 +617,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let convert_size = |size| convert_size_fn(size, multiplier, block_size); - let time_format_str = match matches.opt_str("time-style") { - Some(s) => match &s[..] { + let time_format_str = match matches.value_of("time-style") { + Some(s) => match s { "full-iso" => "%Y-%m-%d %H:%M:%S.%f %z", "long-iso" => "%Y-%m-%d %H:%M", "iso" => "%Y-%m-%d", @@ -552,7 +639,11 @@ Try '{} --help' for more information.", None => "%Y-%m-%d %H:%M", }; - let line_separator = if matches.opt_present("0") { "\0" } else { "\n" }; + let line_separator = if matches.is_present(options::NULL) { + "\0" + } else { + "\n" + }; let mut grand_total = 0; for path_str in strs { @@ -565,18 +656,20 @@ Try '{} --help' for more information.", let (_, len) = iter.size_hint(); let len = len.unwrap(); for (index, stat) in iter.enumerate() { - let size = if matches.opt_present("apparent-size") || matches.opt_present("b") { + let size = if matches.is_present(options::APPARENT_SIZE) + || matches.is_present(options::BYTES) + { stat.size } else { // C's stat is such that each block is assume to be 512 bytes // See: http://linux.die.net/man/2/stat stat.blocks * 512 }; - if matches.opt_present("time") { + if matches.is_present(options::TIME) { let tm = { let secs = { - match matches.opt_str("time") { - Some(s) => match &s[..] { + match matches.value_of(options::TIME) { + Some(s) => match s { "accessed" => stat.accessed, "created" => stat.created, "modified" => stat.modified, @@ -649,8 +742,8 @@ mod test_du { (Some("900KB".to_string()), Some(900 * 1000)), (Some("BAD_STRING".to_string()), None), ]; - for it in test_data.into_iter() { - assert_eq!(translate_to_pure_number(&it.0), it.1); + for it in test_data.iter() { + assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1); } } @@ -661,8 +754,8 @@ mod test_du { (None, 1024), (Some("BAD_STRING".to_string()), 1024), ]; - for it in test_data.into_iter() { - assert_eq!(read_block_size(it.0.clone()), it.1); + for it in test_data.iter() { + assert_eq!(read_block_size(it.0.as_deref()), it.1); } } } From afb1b9efb4ff31fb934d459555f22cb82d94b483 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 26 May 2021 12:53:11 +0200 Subject: [PATCH 378/399] tests/util: add AtPath::hard_link --- tests/common/util.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 611baadd4..6f9f779ef 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -7,7 +7,7 @@ use std::env; #[cfg(not(windows))] use std::ffi::CString; use std::ffi::OsStr; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, hard_link, File, OpenOptions}; use std::io::{Read, Result, Write}; #[cfg(unix)] use std::os::unix::fs::{symlink as symlink_dir, symlink as symlink_file}; @@ -524,6 +524,14 @@ impl AtPath { } } + pub fn hard_link(&self, src: &str, dst: &str) { + log_info( + "hard_link", + &format!("{},{}", self.plus_as_string(src), self.plus_as_string(dst)), + ); + hard_link(&self.plus(src), &self.plus(dst)).unwrap(); + } + pub fn symlink_file(&self, src: &str, dst: &str) { log_info( "symlink", @@ -680,6 +688,10 @@ impl TestScenario { cmd } + /// Returns builder for invoking any system command. Paths given are treated + /// relative to the environment's unique temporary test directory. + /// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call + /// `Command::env_clear` (Clears the entire environment map for the child process.) pub fn cmd_keepenv>(&self, bin: S) -> UCommand { UCommand::new_from_tmp(bin, self.tmpd.clone(), false) } From 6a70d89e8ca43c979a3b8053ecc43122d8d23bf5 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 26 May 2021 12:55:53 +0200 Subject: [PATCH 379/399] tests/du: replace call to 'ln' with call to 'AtPath::hard_link' --- tests/by-util/test_du.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c72bd02a6..04dbf9f37 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -129,11 +129,9 @@ fn _du_soft_link(s: &str) { #[test] fn test_du_hard_link() { let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; - let result_ln = scene.cmd("ln").arg(SUB_FILE).arg(SUB_LINK).run(); - if !result_ln.succeeded() { - scene.ccmd("ln").arg(SUB_FILE).arg(SUB_LINK).succeeds(); - } + at.hard_link(SUB_FILE, SUB_LINK); let result = scene.ucmd().arg(SUB_DIR_LINKS).succeeds(); From efd5921bdaade152a9a1d07d813f685980d13900 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 26 May 2021 13:07:04 +0200 Subject: [PATCH 380/399] tests/test: replace call to 'ln -s' with call to 'AtPath::symlink_file' --- tests/by-util/test_test.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 0dfc0c620..3a55f772a 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -437,10 +437,9 @@ fn test_not_is_not_empty() { #[cfg(not(windows))] fn test_symlink_is_symlink() { let scenario = TestScenario::new(util_name!()); - let mut ln = scenario.cmd("ln"); + let at = &scenario.fixtures; - // creating symlinks requires admin on Windows - ln.args(&["-s", "regular_file", "symlink"]).succeeds(); + at.symlink_file("regular_file", "symlink"); // FIXME: implement on Windows scenario.ucmd().args(&["-h", "symlink"]).succeeds(); From fe25b51a6658dffb2288c1b409b1c5ca50322d71 Mon Sep 17 00:00:00 2001 From: Dean Li Date: Wed, 26 May 2021 21:34:02 +0800 Subject: [PATCH 381/399] chmod: match GNU error Related to #2260 Signed-off-by: Dean Li --- src/uu/chmod/src/chmod.rs | 4 +++- tests/by-util/test_chmod.rs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 9dea3c842..c4bf309d6 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -262,8 +262,10 @@ impl Chmoder { ); } return Ok(()); + } else if err.kind() == std::io::ErrorKind::PermissionDenied { + show_error!("'{}': Permission denied", file.display()); } else { - show_error!("{}: '{}'", err, file.display()); + show_error!("'{}': {}", file.display(), err); } return Err(1); } diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 733722b7c..f20429a6e 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -282,6 +282,26 @@ fn test_chmod_reference_file() { run_single_test(&tests[0], at, ucmd); } +#[test] +fn test_permission_denied() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d/"); + at.mkdir("d/no-x"); + at.mkdir("d/no-x/y"); + + scene.ucmd().arg("u=rw").arg("d/no-x").succeeds(); + + scene + .ucmd() + .arg("-R") + .arg("o=r") + .arg("d") + .fails() + .stderr_is("chmod: 'd/no-x/y': Permission denied"); +} + #[test] fn test_chmod_recursive() { let _guard = UMASK_MUTEX.lock(); From 25ed5eeb0e470b7d1695f9caebee8a192efecc79 Mon Sep 17 00:00:00 2001 From: Matt Blessed Date: Wed, 26 May 2021 10:50:41 -0400 Subject: [PATCH 382/399] cp: move option check to uumain and use `show_usage_error` - add test for conflicting options `--backup` and `--no-clobber` --- src/uu/cp/src/cp.rs | 17 ++++++----------- tests/by-util/test_cp.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7eaa21c11..7e64a288c 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -463,6 +463,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .get_matches_from(args); let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches)); + + if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup { + show_usage_error!("options --backup and --no-clobber are mutually exclusive"); + return 1; + } + let paths: Vec = matches .values_of(OPT_PATHS) .map(|v| v.map(ToString::to_string).collect()) @@ -593,17 +599,6 @@ impl Options { let overwrite = OverwriteMode::from_matches(matches); - if overwrite == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { - show_error!( - "options --backup and --no-clobber are mutually exclusive\n\ - Try '{} --help' for more information.", - executable!() - ); - return Err(Error::Error( - "options --backup and --no-clobber are mutually exclusive".to_owned(), - )); - } - // Parse target directory options let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY); let target_dir = matches diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index dddba595c..d41d3f6ed 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -539,6 +539,18 @@ fn test_cp_backup_off() { assert!(!at.file_exists(&format!("{}~", TEST_HOW_ARE_YOU_SOURCE))); } +#[test] +fn test_cp_backup_no_clobber_conflicting_options() { + let (_, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup") + .arg("--no-clobber") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .fails() + .stderr_is("cp: options --backup and --no-clobber are mutually exclusive\nTry 'cp --help' for more information."); +} + #[test] fn test_cp_deref_conflicting_options() { new_ucmd!() From 64598d9e26fd39820a03dc793273636928c03d4c Mon Sep 17 00:00:00 2001 From: Mikadore Date: Wed, 26 May 2021 22:15:28 +0200 Subject: [PATCH 383/399] Closing #1916 --- tests/common/util.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 611baadd4..d1c6259b6 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -62,54 +62,54 @@ pub struct CmdResult { /// see [`success`] success: bool, /// captured standard output after running the Command - stdout: String, + stdout: Vec, /// captured standard error after running the Command - stderr: String, + stderr: Vec, } impl CmdResult { /// Returns a reference to the program's standard output as a slice of bytes pub fn stdout(&self) -> &[u8] { - &self.stdout.as_bytes() + &self.stdout } /// Returns the program's standard output as a string slice pub fn stdout_str(&self) -> &str { - &self.stdout + std::str::from_utf8(&self.stdout).unwrap() } /// Returns the program's standard output as a string /// consumes self pub fn stdout_move_str(self) -> String { - self.stdout + String::from_utf8(self.stdout).unwrap() } /// Returns the program's standard output as a vec of bytes /// consumes self pub fn stdout_move_bytes(self) -> Vec { - Vec::from(self.stdout) + self.stdout } /// Returns a reference to the program's standard error as a slice of bytes pub fn stderr(&self) -> &[u8] { - &self.stderr.as_bytes() + &self.stderr } /// Returns the program's standard error as a string slice pub fn stderr_str(&self) -> &str { - &self.stderr + std::str::from_utf8(&self.stderr).unwrap() } /// Returns the program's standard error as a string /// consumes self pub fn stderr_move_str(self) -> String { - self.stderr + String::from_utf8(self.stderr).unwrap() } /// Returns the program's standard error as a vec of bytes /// consumes self pub fn stderr_move_bytes(self) -> Vec { - Vec::from(self.stderr) + self.stderr } /// Returns the program's exit code @@ -202,21 +202,21 @@ impl CmdResult { /// passed in value, trailing whitespace are kept to force strict comparison (#1235) /// stdout_only is a better choice unless stderr may or will be non-empty pub fn stdout_is>(&self, msg: T) -> &CmdResult { - assert_eq!(self.stdout, String::from(msg.as_ref())); + assert_eq!(self.stdout_str(), String::from(msg.as_ref())); self } /// Like `stdout_is` but newlines are normalized to `\n`. pub fn normalized_newlines_stdout_is>(&self, msg: T) -> &CmdResult { let msg = msg.as_ref().replace("\r\n", "\n"); - assert_eq!(self.stdout.replace("\r\n", "\n"), msg); + assert_eq!(self.stdout_str().replace("\r\n", "\n"), msg); self } /// asserts that the command resulted in stdout stream output, /// whose bytes equal those of the passed in slice pub fn stdout_is_bytes>(&self, msg: T) -> &CmdResult { - assert_eq!(self.stdout.as_bytes(), msg.as_ref()); + assert_eq!(self.stdout, msg.as_ref()); self } @@ -231,7 +231,7 @@ impl CmdResult { /// stderr_only is a better choice unless stdout may or will be non-empty pub fn stderr_is>(&self, msg: T) -> &CmdResult { assert_eq!( - self.stderr.trim_end(), + self.stderr_str().trim_end(), String::from(msg.as_ref()).trim_end() ); self @@ -240,7 +240,7 @@ impl CmdResult { /// asserts that the command resulted in stderr stream output, /// whose bytes equal those of the passed in slice pub fn stderr_is_bytes>(&self, msg: T) -> &CmdResult { - assert_eq!(self.stderr.as_bytes(), msg.as_ref()); + assert_eq!(self.stderr, msg.as_ref()); self } @@ -874,8 +874,8 @@ impl UCommand { tmpd: self.tmpd.clone(), code: prog.status.code(), success: prog.status.success(), - stdout: from_utf8(&prog.stdout).unwrap().to_string(), - stderr: from_utf8(&prog.stderr).unwrap().to_string(), + stdout: prog.stdout, + stderr: prog.stderr } } From 5e1d52d4be95d0f8cd09019dbc9f8f1196f002b6 Mon Sep 17 00:00:00 2001 From: Mikadore Date: Wed, 26 May 2021 22:20:16 +0200 Subject: [PATCH 384/399] cargo-fmt :DDD --- tests/common/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index d1c6259b6..94d0df851 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -875,7 +875,7 @@ impl UCommand { code: prog.status.code(), success: prog.status.success(), stdout: prog.stdout, - stderr: prog.stderr + stderr: prog.stderr, } } From f11f5f3abba82be1e4697f4c7a8a0c804b3cfb07 Mon Sep 17 00:00:00 2001 From: Matt Blessed Date: Tue, 25 May 2021 20:34:49 -0400 Subject: [PATCH 385/399] mv: refactor backup logic to use shared uucore backup control - add mv backup tests --- src/uu/mv/src/mv.rs | 95 ++++------------------- tests/by-util/test_mv.rs | 161 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 164 insertions(+), 92 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index a0ff1bcc6..c61c7caf1 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -20,6 +20,7 @@ use std::os::unix; #[cfg(windows)] use std::os::windows; use std::path::{Path, PathBuf}; +use uucore::backup_control::{self, BackupMode}; use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions}; @@ -40,16 +41,9 @@ pub enum OverwriteMode { Force, } -#[derive(Clone, Copy, Eq, PartialEq)] -pub enum BackupMode { - NoBackup, - SimpleBackup, - NumberedBackup, - ExistingBackup, -} - static ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static LONG_HELP: &str = ""; static OPT_BACKUP: &str = "backup"; static OPT_BACKUP_NO_ARG: &str = "b"; @@ -80,20 +74,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) + .after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP)) .usage(&usage[..]) .arg( Arg::with_name(OPT_BACKUP) .long(OPT_BACKUP) .help("make a backup of each existing destination file") .takes_value(true) - .possible_value("simple") - .possible_value("never") - .possible_value("numbered") - .possible_value("t") - .possible_value("existing") - .possible_value("nil") - .possible_value("none") - .possible_value("off") + .require_equals(true) + .min_values(0) + .possible_values(backup_control::BACKUP_CONTROL_VALUES) .value_name("CONTROL") ) .arg( @@ -172,18 +162,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_default(); let overwrite_mode = determine_overwrite_mode(&matches); - let backup_mode = determine_backup_mode(&matches); + let backup_mode = backup_control::determine_backup_mode( + matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), + matches.value_of(OPT_BACKUP), + ); if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { - show_error!( - "options --backup and --no-clobber are mutually exclusive\n\ - Try '{} --help' for more information.", - executable!() - ); + show_usage_error!("options --backup and --no-clobber are mutually exclusive"); return 1; } - let backup_suffix = determine_backup_suffix(backup_mode, &matches); + let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); let behavior = Behavior { overwrite: overwrite_mode, @@ -227,37 +216,6 @@ fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { } } -fn determine_backup_mode(matches: &ArgMatches) -> BackupMode { - if matches.is_present(OPT_BACKUP_NO_ARG) { - BackupMode::SimpleBackup - } else if matches.is_present(OPT_BACKUP) { - match matches.value_of(OPT_BACKUP).map(String::from) { - None => BackupMode::SimpleBackup, - Some(mode) => match &mode[..] { - "simple" | "never" => BackupMode::SimpleBackup, - "numbered" | "t" => BackupMode::NumberedBackup, - "existing" | "nil" => BackupMode::ExistingBackup, - "none" | "off" => BackupMode::NoBackup, - _ => panic!(), // cannot happen as it is managed by clap - }, - } - } else { - BackupMode::NoBackup - } -} - -fn determine_backup_suffix(backup_mode: BackupMode, matches: &ArgMatches) -> String { - if matches.is_present(OPT_SUFFIX) { - matches.value_of(OPT_SUFFIX).map(String::from).unwrap() - } else if let (Ok(s), BackupMode::SimpleBackup) = - (env::var("SIMPLE_BACKUP_SUFFIX"), backup_mode) - { - s - } else { - "~".to_owned() - } -} - fn exec(files: &[PathBuf], b: Behavior) -> i32 { if let Some(ref name) = b.target_dir { return move_files_into_dir(files, &PathBuf::from(name), &b); @@ -389,12 +347,7 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { OverwriteMode::Force => {} }; - backup_path = match b.backup { - BackupMode::NoBackup => None, - BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)), - BackupMode::NumberedBackup => Some(numbered_backup_path(to)), - BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)), - }; + backup_path = backup_control::get_backup_path(b.backup, to, &b.suffix); if let Some(ref backup_path) = backup_path { rename_with_fallback(to, backup_path)?; } @@ -514,28 +467,6 @@ fn read_yes() -> bool { } } -fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf { - let mut p = path.to_string_lossy().into_owned(); - p.push_str(suffix); - PathBuf::from(p) -} - -fn numbered_backup_path(path: &Path) -> PathBuf { - (1_u64..) - .map(|i| path.with_extension(format!("~{}~", i))) - .find(|p| !p.exists()) - .expect("cannot create backup") -} - -fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf { - let test_path = path.with_extension("~1~"); - if test_path.exists() { - numbered_backup_path(path) - } else { - simple_backup_path(path, suffix) - } -} - fn is_empty_dir(path: &Path) -> bool { match fs::read_dir(path) { Ok(contents) => contents.peekable().peek().is_none(), diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index e0723a479..e0bdd9ef3 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -251,6 +251,40 @@ fn test_mv_simple_backup() { assert!(at.file_exists(&format!("{}~", file_b))); } +#[test] +fn test_mv_simple_backup_with_file_extension() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_simple_backup_file_a.txt"; + let file_b = "test_mv_simple_backup_file_b.txt"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("-b") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_mv_arg_backup_arg_first() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_simple_backup_file_a"; + let file_b = "test_mv_simple_backup_file_b"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("--backup").arg(file_a).arg(file_b).succeeds(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + #[test] fn test_mv_custom_backup_suffix() { let (at, mut ucmd) = at_and_ucmd!(); @@ -293,7 +327,7 @@ fn test_mv_custom_backup_suffix_via_env() { } #[test] -fn test_mv_backup_numbering() { +fn test_mv_backup_numbered_with_t() { let (at, mut ucmd) = at_and_ucmd!(); let file_a = "test_mv_backup_numbering_file_a"; let file_b = "test_mv_backup_numbering_file_b"; @@ -311,6 +345,25 @@ fn test_mv_backup_numbering() { assert!(at.file_exists(&format!("{}.~1~", file_b))); } +#[test] +fn test_mv_backup_numbered() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("--backup=numbered") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}.~1~", file_b))); +} + #[test] fn test_mv_backup_existing() { let (at, mut ucmd) = at_and_ucmd!(); @@ -330,6 +383,67 @@ fn test_mv_backup_existing() { assert!(at.file_exists(&format!("{}~", file_b))); } +#[test] +fn test_mv_backup_nil() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("--backup=nil") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_mv_numbered_if_existing_backup_existing() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; + let file_b_backup = "test_mv_backup_numbering_file_b.~1~"; + + at.touch(file_a); + at.touch(file_b); + at.touch(file_b_backup); + ucmd.arg("--backup=existing") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_b)); + assert!(at.file_exists(file_b_backup)); + assert!(at.file_exists(&*format!("{}.~2~", file_b))); +} + +#[test] +fn test_mv_numbered_if_existing_backup_nil() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; + let file_b_backup = "test_mv_backup_numbering_file_b.~1~"; + + at.touch(file_a); + at.touch(file_b); + at.touch(file_b_backup); + ucmd.arg("--backup=nil") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(at.file_exists(file_b)); + assert!(at.file_exists(file_b_backup)); + assert!(at.file_exists(&*format!("{}.~2~", file_b))); +} + #[test] fn test_mv_backup_simple() { let (at, mut ucmd) = at_and_ucmd!(); @@ -349,6 +463,25 @@ fn test_mv_backup_simple() { assert!(at.file_exists(&format!("{}~", file_b))); } +#[test] +fn test_mv_backup_never() { + let (at, mut ucmd) = at_and_ucmd!(); + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; + + at.touch(file_a); + at.touch(file_b); + ucmd.arg("--backup=never") + .arg(file_a) + .arg(file_b) + .succeeds() + .no_stderr(); + + assert!(!at.file_exists(file_a)); + assert!(at.file_exists(file_b)); + assert!(at.file_exists(&format!("{}~", file_b))); +} + #[test] fn test_mv_backup_none() { let (at, mut ucmd) = at_and_ucmd!(); @@ -369,17 +502,14 @@ fn test_mv_backup_none() { } #[test] -fn test_mv_existing_backup() { +fn test_mv_backup_off() { let (at, mut ucmd) = at_and_ucmd!(); - let file_a = "test_mv_existing_backup_file_a"; - let file_b = "test_mv_existing_backup_file_b"; - let file_b_backup = "test_mv_existing_backup_file_b.~1~"; - let resulting_backup = "test_mv_existing_backup_file_b.~2~"; + let file_a = "test_mv_backup_numbering_file_a"; + let file_b = "test_mv_backup_numbering_file_b"; at.touch(file_a); at.touch(file_b); - at.touch(file_b_backup); - ucmd.arg("--backup=nil") + ucmd.arg("--backup=off") .arg(file_a) .arg(file_b) .succeeds() @@ -387,8 +517,19 @@ fn test_mv_existing_backup() { assert!(!at.file_exists(file_a)); assert!(at.file_exists(file_b)); - assert!(at.file_exists(file_b_backup)); - assert!(at.file_exists(resulting_backup)); + assert!(!at.file_exists(&format!("{}~", file_b))); +} + +#[test] +fn test_mv_backup_no_clobber_conflicting_options() { + let (_, mut ucmd) = at_and_ucmd!(); + + ucmd.arg("--backup") + .arg("--no-clobber") + .arg("file1") + .arg("file2") + .fails() + .stderr_is("mv: options --backup and --no-clobber are mutually exclusive\nTry 'mv --help' for more information."); } #[test] From 41bea72f23da42f0b5dded9b1955a3782cd1c063 Mon Sep 17 00:00:00 2001 From: Matt Blessed Date: Wed, 26 May 2021 18:28:17 -0400 Subject: [PATCH 386/399] cp: fix regressed issue with `--backup` and `-b` - add test for regressed issue --- src/uu/cp/src/cp.rs | 8 ++++++-- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7e64a288c..fab1dfec1 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -232,6 +232,7 @@ fn get_usage() -> String { static OPT_ARCHIVE: &str = "archive"; static OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; static OPT_BACKUP: &str = "backup"; +static OPT_BACKUP_NO_ARG: &str = "b"; static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; static OPT_CONTEXT: &str = "context"; static OPT_COPY_CONTENTS: &str = "copy-contents"; @@ -357,7 +358,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .help("remove each existing destination file before attempting to open it \ (contrast with --force). On Windows, current only works for writeable files.")) .arg(Arg::with_name(OPT_BACKUP) - .short("b") .long(OPT_BACKUP) .help("make a backup of each existing destination file") .takes_value(true) @@ -366,6 +366,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .possible_values(backup_control::BACKUP_CONTROL_VALUES) .value_name("CONTROL") ) + .arg(Arg::with_name(OPT_BACKUP_NO_ARG) + .short(OPT_BACKUP_NO_ARG) + .help("like --backup but does not accept an argument") + ) .arg(Arg::with_name(OPT_SUFFIX) .short("S") .long(OPT_SUFFIX) @@ -592,7 +596,7 @@ impl Options { || matches.is_present(OPT_ARCHIVE); let backup_mode = backup_control::determine_backup_mode( - matches.is_present(OPT_BACKUP), + matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP), matches.value_of(OPT_BACKUP), ); let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index d41d3f6ed..d49219b04 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -316,6 +316,22 @@ fn test_cp_arg_backup() { ); } +#[test] +fn test_cp_arg_backup_with_other_args() { + let (at, mut ucmd) = at_and_ucmd!(); + + ucmd.arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_HOW_ARE_YOU_SOURCE) + .arg("-vbL") + .succeeds(); + + assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n"); + assert_eq!( + at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), + "How are you?\n" + ); +} + #[test] fn test_cp_arg_backup_arg_first() { let (at, mut ucmd) = at_and_ucmd!(); From 29f6dd1f3583d954be7921dac9f3fbf8456f7ae5 Mon Sep 17 00:00:00 2001 From: Mikadore Date: Thu, 27 May 2021 16:55:14 +0200 Subject: [PATCH 387/399] Fixed warning --- tests/common/util.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 94d0df851..ba4eed317 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -16,7 +16,6 @@ use std::os::windows::fs::{symlink_dir, symlink_file}; use std::path::{Path, PathBuf}; use std::process::{Child, Command, Stdio}; use std::rc::Rc; -use std::str::from_utf8; use std::thread::sleep; use std::time::Duration; use tempfile::TempDir; From 052ee22ce019faf7a7021f81430512df9c39b901 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 27 May 2021 18:20:15 +0200 Subject: [PATCH 388/399] Bump MSRV to 1.43.1 --- .github/workflows/CICD.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 977a86915..804720bea 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -11,7 +11,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.40.0" ## v1.40.0 + RUST_MIN_SRV: "1.43.1" ## v1.43.0 RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel on: [push, pull_request] diff --git a/README.md b/README.md index 6b29fa854..1365bf7ce 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ to compile anywhere, and this is as good a way as any to try and learn it. ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and nightly. -The current oldest supported version of the Rust compiler is `1.40.0`. +The current oldest supported version of the Rust compiler is `1.43.1`. On both Windows and Redox, only the nightly version is tested currently. From 825476f57314565e306287e72e2f5c690a749567 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 27 May 2021 20:25:24 +0200 Subject: [PATCH 389/399] Update tempfile --- Cargo.lock | 79 ++++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 4 +-- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e1470c88..2060edfa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -707,7 +707,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -1253,14 +1264,26 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc", + "rand_hc 0.2.0", "rand_pcg", ] +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha 0.3.0", + "rand_core 0.6.2", + "rand_hc 0.3.0", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -1271,6 +1294,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.2", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1292,7 +1325,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.3", ] [[package]] @@ -1304,6 +1346,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core 0.6.2", +] + [[package]] name = "rand_pcg" version = "0.2.1" @@ -1623,14 +1674,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", - "rand 0.7.3", - "redox_syscall 0.1.57", + "rand 0.8.3", + "redox_syscall 0.2.8", "remove_dir_all", "winapi 0.3.9", ] @@ -2053,7 +2104,7 @@ dependencies = [ "array-init", "criterion", "rand 0.7.3", - "rand_chacha", + "rand_chacha 0.2.2", "uu_factor", ] @@ -2877,6 +2928,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "wasm-bindgen" version = "0.2.74" diff --git a/Cargo.toml b/Cargo.toml index 745393260..fdf45e484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,9 +342,7 @@ pretty_assertions = "0.7.2" rand = "0.7" regex = "1.0" sha1 = { version="0.6", features=["std"] } -## tempfile 3.2 depends on recent version of rand which depends on getrandom v0.2 which has compiler errors for MinRustV v1.32.0 -## min dep for tempfile = Rustc 1.40 -tempfile = "= 3.1.0" +tempfile = "3.2.0" time = "0.1" unindent = "0.1" uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } From ebe6341ae37dee47ce94861960ddadc012a127bb Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 27 May 2021 22:47:03 +0200 Subject: [PATCH 390/399] chore: replace tempdir with tempfile --- Cargo.lock | 2 +- Cargo.toml | 1 - src/uu/sort/Cargo.toml | 2 +- src/uu/sort/src/ext_sort.rs | 4 ++-- tests/by-util/test_cat.rs | 3 +-- tests/by-util/test_ls.rs | 6 ++---- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e1470c88..547e9bc6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2536,7 +2536,7 @@ dependencies = [ "rand 0.7.3", "rayon", "semver 0.9.0", - "tempdir", + "tempfile", "unicode-width", "uucore", "uucore_procs", diff --git a/Cargo.toml b/Cargo.toml index 745393260..9f499529b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -349,7 +349,6 @@ time = "0.1" unindent = "0.1" uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } walkdir = "2.2" -tempdir = "0.3" [target.'cfg(unix)'.dev-dependencies] rust-users = { version="0.10", package="users" } diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 724744dc4..f06610248 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -25,7 +25,7 @@ ouroboros = "0.9.3" rand = "0.7" rayon = "1.5" semver = "0.9.0" -tempdir = "0.3.7" +tempfile = "3" unicode-width = "0.1.8" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index a304bf7c0..23a55aad0 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -23,7 +23,7 @@ use std::{ use itertools::Itertools; -use tempdir::TempDir; +use tempfile::TempDir; use crate::{ chunks::{self, Chunk}, @@ -34,7 +34,7 @@ const MIN_BUFFER_SIZE: usize = 8_000; /// Sort files by using auxiliary files for storing intermediate chunks (if needed), and output the result. pub fn ext_sort(files: &mut impl Iterator>, settings: &GlobalSettings) { - let tmp_dir = crash_if_err!(1, TempDir::new_in(&settings.tmp_dir, "uutils_sort")); + let tmp_dir = crash_if_err!(1, tempfile::Builder::new().prefix("uutils_sort").tempdir_in(&settings.tmp_dir)); let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1); let (recycled_sender, recycled_receiver) = std::sync::mpsc::sync_channel(1); thread::spawn({ diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 4bb673b95..adda905b3 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -406,10 +406,9 @@ fn test_domain_socket() { use std::io::prelude::*; use std::sync::{Arc, Barrier}; use std::thread; - use tempdir::TempDir; use unix_socket::UnixListener; - let dir = TempDir::new("unix_socket").expect("failed to create dir"); + let dir = tempfile::Builder::new().prefix("unix_socket").tempdir().expect("failed to create dir"); let socket_path = dir.path().join("sock"); let listener = UnixListener::bind(&socket_path).expect("failed to create socket"); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 2ae57ad7f..01c5ab5c4 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -19,9 +19,7 @@ use std::path::PathBuf; #[cfg(not(windows))] use std::sync::Mutex; #[cfg(not(windows))] -extern crate tempdir; -#[cfg(not(windows))] -use self::tempdir::TempDir; +extern crate tempfile; #[cfg(not(windows))] lazy_static! { @@ -1087,7 +1085,7 @@ fn test_ls_indicator_style() { { use self::unix_socket::UnixListener; - let dir = TempDir::new("unix_socket").expect("failed to create dir"); + let dir = tempfile::Builder::new().prefix("unix_socket").tempdir().expect("failed to create dir"); let socket_path = dir.path().join("sock"); let _listener = UnixListener::bind(&socket_path).expect("failed to create socket"); From 835a17d79f59891458d7701daad7c664576448d7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 27 May 2021 22:48:10 +0200 Subject: [PATCH 391/399] mktemp: use tempfile instead of custom tempdir --- Cargo.lock | 33 ---------------- src/uu/mktemp/src/mktemp.rs | 76 ++++++++++++++++++------------------ src/uu/mktemp/src/tempdir.rs | 51 ------------------------ 3 files changed, 39 insertions(+), 121 deletions(-) delete mode 100644 src/uu/mktemp/src/tempdir.rs diff --git a/Cargo.lock b/Cargo.lock index 547e9bc6e..1d4cdca93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,7 +254,6 @@ dependencies = [ "rand 0.7.3", "regex", "sha1", - "tempdir", "tempfile", "textwrap", "time", @@ -1221,19 +1220,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi 0.3.9", -] - [[package]] name = "rand" version = "0.5.6" @@ -1338,15 +1324,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.1.57" @@ -1611,16 +1588,6 @@ dependencies = [ "unicode-xid 0.2.2", ] -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - [[package]] name = "tempfile" version = "3.1.0" diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 112c2fb94..d66dd3d57 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -15,14 +15,11 @@ use clap::{App, Arg}; use std::env; use std::iter; -use std::mem::forget; use std::path::{is_separator, PathBuf}; use rand::Rng; use tempfile::Builder; -mod tempdir; - static ABOUT: &str = "create a temporary file or directory."; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -214,49 +211,54 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> } fn exec( - tmpdir: PathBuf, + dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool, ) -> i32 { - if make_dir { - match tempdir::new_in(&tmpdir, prefix, rand, suffix) { - Ok(ref f) => { - println!("{}", f); - return 0; - } - Err(e) => { - if !quiet { - show_error!("{}: {}", e, tmpdir.display()); + let res = if make_dir { + let tmpdir = Builder::new() + .prefix(prefix) + .rand_bytes(rand) + .suffix(suffix) + .tempdir_in(&dir); + + // `into_path` consumes the TempDir without removing it + tmpdir.map(|d| d.into_path().to_string_lossy().to_string()) + } else { + let tmpfile = Builder::new() + .prefix(prefix) + .rand_bytes(rand) + .suffix(suffix) + .tempfile_in(&dir); + + match tmpfile { + Ok(f) => { + // `keep` ensures that the file is not deleted + match f.keep() { + Ok((_, p)) => Ok(p.to_string_lossy().to_string()), + Err(e) => { + show_error!("'{}': {}", dir.display(), e); + return 1; + } } - return 1; } - } - } - let tmpfile = Builder::new() - .prefix(prefix) - .rand_bytes(rand) - .suffix(suffix) - .tempfile_in(tmpdir); - let tmpfile = match tmpfile { - Ok(f) => f, - Err(e) => { - if !quiet { - show_error!("failed to create tempfile: {}", e); - } - return 1; + Err(x) => Err(x) } }; - let tmpname = tmpfile.path().to_string_lossy().to_string(); - - println!("{}", tmpname); - - // CAUTION: Not to call `drop` of tmpfile, which removes the tempfile, - // I call a dangerous function `forget`. - forget(tmpfile); - - 0 + match res { + Ok(ref f) => { + println!("{}", f); + 0 + } + Err(e) => { + if !quiet { + show_error!("{}: {}", e, dir.display()); + } + 1 + } + } } diff --git a/src/uu/mktemp/src/tempdir.rs b/src/uu/mktemp/src/tempdir.rs deleted file mode 100644 index 1b6c9d7b3..000000000 --- a/src/uu/mktemp/src/tempdir.rs +++ /dev/null @@ -1,51 +0,0 @@ -// spell-checker:ignore (ToDO) tempdir tmpdir - -// Mainly taken from crate `tempdir` - -use rand::distributions::Alphanumeric; -use rand::{thread_rng, Rng}; - -use std::io::Result as IOResult; -use std::io::{Error, ErrorKind}; -use std::path::Path; - -// How many times should we (re)try finding an unused random name? It should be -// enough that an attacker will run out of luck before we run out of patience. -const NUM_RETRIES: u32 = 1 << 31; - -#[cfg(any(unix, target_os = "redox"))] -fn create_dir>(path: P) -> IOResult<()> { - use std::fs::DirBuilder; - use std::os::unix::fs::DirBuilderExt; - - DirBuilder::new().mode(0o700).create(path) -} - -#[cfg(windows)] -fn create_dir>(path: P) -> IOResult<()> { - ::std::fs::create_dir(path) -} - -pub fn new_in>( - tmpdir: P, - prefix: &str, - rand: usize, - suffix: &str, -) -> IOResult { - let mut rng = thread_rng(); - for _ in 0..NUM_RETRIES { - let rand_chars: String = rng.sample_iter(&Alphanumeric).take(rand).collect(); - let leaf = format!("{}{}{}", prefix, rand_chars, suffix); - let path = tmpdir.as_ref().join(&leaf); - match create_dir(&path) { - Ok(_) => return Ok(path.to_string_lossy().into_owned()), - Err(ref e) if e.kind() == ErrorKind::AlreadyExists => {} - Err(e) => return Err(e), - } - } - - Err(Error::new( - ErrorKind::AlreadyExists, - "too many temporary directories already exist", - )) -} From 263b1225404c9e1bef299433093df761a13b1cb3 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 28 May 2021 17:48:48 +0200 Subject: [PATCH 392/399] maint: use the matches! macro when possible --- src/uu/csplit/src/csplit.rs | 7 +------ src/uu/expr/src/tokens.rs | 7 +------ src/uu/fmt/src/parasplit.rs | 16 ++++------------ src/uu/hashsum/src/hashsum.rs | 25 +++++++++++++++++-------- src/uu/od/src/parse_formats.rs | 7 +------ src/uu/rm/src/rm.rs | 7 +------ src/uu/sort/src/numeric_str_cmp.rs | 8 ++++---- src/uu/sort/src/sort.rs | 11 +++++------ src/uu/test/src/parser.rs | 8 +------- 9 files changed, 35 insertions(+), 61 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 9d2f81f43..f67f4958f 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -124,12 +124,7 @@ where // split the file based on patterns for pattern in patterns.into_iter() { let pattern_as_str = pattern.to_string(); - #[allow(clippy::match_like_matches_macro)] - let is_skip = if let patterns::Pattern::SkipToMatch(_, _, _) = pattern { - true - } else { - false - }; + let is_skip = matches!(pattern, patterns::Pattern::SkipToMatch(_, _, _)); match pattern { patterns::Pattern::UpToLine(n, ex) => { let mut up_to_line = n; diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs index b65b0d482..6056e4ba1 100644 --- a/src/uu/expr/src/tokens.rs +++ b/src/uu/expr/src/tokens.rs @@ -63,12 +63,7 @@ impl Token { } } fn is_a_close_paren(&self) -> bool { - #[allow(clippy::match_like_matches_macro)] - // `matches!(...)` macro not stabilized until rust v1.42 - match *self { - Token::ParClose => true, - _ => false, - } + matches!(*self, Token::ParClose) } } diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index 950b3f66d..71b5f62ec 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -264,12 +264,9 @@ impl<'a> ParagraphStream<'a> { return false; } - #[allow(clippy::match_like_matches_macro)] - // `matches!(...)` macro not stabilized until rust v1.42 - l_slice[..colon_posn].chars().all(|x| match x as usize { - y if !(33..=126).contains(&y) => false, - _ => true, - }) + l_slice[..colon_posn] + .chars() + .all(|x| !matches!(x as usize, y if !(33..=126).contains(&y))) } } } @@ -541,12 +538,7 @@ impl<'a> WordSplit<'a> { } fn is_punctuation(c: char) -> bool { - #[allow(clippy::match_like_matches_macro)] - // `matches!(...)` macro not stabilized until rust v1.42 - match c { - '!' | '.' | '?' => true, - _ => false, - } + matches!(c, '!' | '.' | '?') } } diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 2e31ddd25..b1ba3c217 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -51,14 +51,23 @@ struct Options { } fn is_custom_binary(program: &str) -> bool { - #[allow(clippy::match_like_matches_macro)] - // `matches!(...)` macro not stabilized until rust v1.42 - match program { - "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" - | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum" - | "shake128sum" | "shake256sum" | "b2sum" => true, - _ => false, - } + matches!( + program, + "md5sum" + | "sha1sum" + | "sha224sum" + | "sha256sum" + | "sha384sum" + | "sha512sum" + | "sha3sum" + | "sha3-224sum" + | "sha3-256sum" + | "sha3-384sum" + | "sha3-512sum" + | "shake128sum" + | "shake256sum" + | "b2sum" + ) } #[allow(clippy::cognitive_complexity)] diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index 8b32d648c..abf05ea18 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -85,12 +85,7 @@ fn od_format_type(type_char: FormatType, byte_size: u8) -> Option bool { - #[allow(clippy::match_like_matches_macro)] - // `matches!(...)` macro not stabilized until rust v1.42 - match ch { - 'A' | 'j' | 'N' | 'S' | 'w' => true, - _ => false, - } + matches!(ch, 'A' | 'j' | 'N' | 'S' | 'w') } /// Parses format flags from command line diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 94626b4e7..8010988bb 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -386,13 +386,8 @@ fn prompt(msg: &str) -> bool { let stdin = stdin(); let mut stdin = stdin.lock(); - #[allow(clippy::match_like_matches_macro)] - // `matches!(...)` macro not stabilized until rust v1.42 match stdin.read_until(b'\n', &mut buf) { - Ok(x) if x > 0 => match buf[0] { - b'y' | b'Y' => true, - _ => false, - }, + Ok(x) if x > 0 => matches!(buf[0], b'y' | b'Y'), _ => false, } } diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index f8666b701..76dc81aeb 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -68,10 +68,10 @@ impl NumInfo { } first_char = false; - if parse_settings - .thousands_separator - .map_or(false, |c| c == char) - { + if matches!( + parse_settings.thousands_separator, + Some(c) if c == char, + ) { continue; } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 6d79e80fb..cc391af00 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -583,11 +583,10 @@ impl FieldSelector { is_default_selection: from.field == 1 && from.char == 1 && to.is_none() - // TODO: Once our MinRustV is 1.42 or higher, change this to the matches! macro - && match settings.mode { - SortMode::Numeric | SortMode::GeneralNumeric | SortMode::HumanNumeric => false, - _ => true, - }, + && !matches!( + settings.mode, + SortMode::Numeric | SortMode::GeneralNumeric | SortMode::HumanNumeric, + ), needs_tokens: from.field != 1 || from.char == 0 || to.is_some(), from, to, @@ -650,7 +649,7 @@ impl FieldSelector { tokens: Option<&[Field]>, position: &KeyPosition, ) -> Resolution { - if tokens.map_or(false, |fields| fields.len() < position.field) { + if matches!(tokens, Some(tokens) if tokens.len() < position.field) { Resolution::TooHigh } else if position.char == 0 { let end = tokens.unwrap()[position.field - 1].end; diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index aa44bc5f2..0fcb25bd5 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -121,13 +121,7 @@ impl Parser { /// Test if the next token in the stream is a BOOLOP (-a or -o), without /// removing the token from the stream. fn peek_is_boolop(&mut self) -> bool { - // TODO: change to `matches!(self.peek(), Symbol::BoolOp(_))` once MSRV is 1.42 - // #[allow(clippy::match_like_matches_macro)] // needs MSRV 1.43 - if let Symbol::BoolOp(_) = self.peek() { - true - } else { - false - } + matches!(self.peek(), Symbol::BoolOp(_)) } /// Parse an expression. From 59a42f1254a1c8f6f328f4b1ec9f6b4456950ff8 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 28 May 2021 17:49:11 +0200 Subject: [PATCH 393/399] maint: format recent changes --- src/uu/mktemp/src/mktemp.rs | 15 ++++----------- src/uu/sort/src/ext_sort.rs | 7 ++++++- src/uu/stdbuf/src/stdbuf.rs | 3 +-- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index d66dd3d57..f6c244bf2 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -210,21 +210,14 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> 0 } -fn exec( - dir: PathBuf, - prefix: &str, - rand: usize, - suffix: &str, - make_dir: bool, - quiet: bool, -) -> i32 { +fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool) -> i32 { let res = if make_dir { let tmpdir = Builder::new() .prefix(prefix) .rand_bytes(rand) .suffix(suffix) .tempdir_in(&dir); - + // `into_path` consumes the TempDir without removing it tmpdir.map(|d| d.into_path().to_string_lossy().to_string()) } else { @@ -233,7 +226,7 @@ fn exec( .rand_bytes(rand) .suffix(suffix) .tempfile_in(&dir); - + match tmpfile { Ok(f) => { // `keep` ensures that the file is not deleted @@ -245,7 +238,7 @@ fn exec( } } } - Err(x) => Err(x) + Err(x) => Err(x), } }; diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 23a55aad0..9b1845efa 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -34,7 +34,12 @@ const MIN_BUFFER_SIZE: usize = 8_000; /// Sort files by using auxiliary files for storing intermediate chunks (if needed), and output the result. pub fn ext_sort(files: &mut impl Iterator>, settings: &GlobalSettings) { - let tmp_dir = crash_if_err!(1, tempfile::Builder::new().prefix("uutils_sort").tempdir_in(&settings.tmp_dir)); + let tmp_dir = crash_if_err!( + 1, + tempfile::Builder::new() + .prefix("uutils_sort") + .tempdir_in(&settings.tmp_dir) + ); let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1); let (recycled_sender, recycled_receiver) = std::sync::mpsc::sync_channel(1); thread::spawn({ diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 485b3c70e..134247060 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -25,8 +25,7 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ Mandatory arguments to long options are mandatory for short options too."; -static LONG_HELP: &str = - "If MODE is 'L' the corresponding stream will be line buffered.\n\ +static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ This option is invalid with standard input.\n\n\ If MODE is '0' the corresponding stream will be unbuffered.\n\n\ Otherwise MODE is a number which may be followed by one of the following:\n\n\ From a9e0208ee21f35e124ad089e240b933a68b248fb Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 28 May 2021 17:49:28 +0200 Subject: [PATCH 394/399] maint: remove obsolete attributes --- src/uucore_procs/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index 10368a5bd..e0d247c3f 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -1,6 +1,3 @@ -#![allow(dead_code)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0 -#![allow(unused_macros)] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0 - // Copyright (C) ~ Roy Ivy III ; MIT license extern crate proc_macro; @@ -44,7 +41,6 @@ impl syn::parse::Parse for Tokens { } #[proc_macro] -#[cfg(not(test))] // work-around for GH:rust-lang/rust#62127; maint: can be removed when MinSRV >= v1.38.0 pub fn main(stream: proc_macro::TokenStream) -> proc_macro::TokenStream { let Tokens { expr } = syn::parse_macro_input!(stream as Tokens); proc_dbg!(&expr); From 6a9ffee54814b54b5ff367e53f4569b0b70fdded Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Fri, 28 May 2021 18:28:00 +0300 Subject: [PATCH 395/399] Moved factor to use clap Issue: https://github.com/uutils/coreutils/issues/2121 --- Cargo.lock | 1 + src/uu/factor/Cargo.toml | 1 + src/uu/factor/src/cli.rs | 36 ++++++++++++++++++++---------------- tests/by-util/test_factor.rs | 12 ++++++++++++ 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 461ce5487..ef22c052d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2054,6 +2054,7 @@ dependencies = [ name = "uu_factor" version = "0.0.6" dependencies = [ + "clap", "coz", "num-traits", "paste", diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index eb34519f1..eb977760f 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -21,6 +21,7 @@ rand = { version = "0.7", features = ["small_rng"] } smallvec = { version = "0.6.14, < 1.0" } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } +clap = "2.33" [dev-dependencies] paste = "0.1.18" diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index ee4c8a4c4..69a368479 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -13,18 +13,21 @@ use std::error::Error; use std::io::{self, stdin, stdout, BufRead, Write}; mod factor; +use clap::{App, Arg}; pub use factor::*; -use uucore::InvalidEncodingHandling; mod miller_rabin; pub mod numeric; mod rho; pub mod table; -static SYNTAX: &str = "[OPTION] [NUMBER]..."; -static SUMMARY: &str = "Print the prime factors of the given number(s). - If none are specified, read from standard input."; -static LONG_HELP: &str = ""; +static VERSION: &str = env!("CARGO_PKG_VERSION"); +static SUMMARY: &str = "Print the prime factors of the given NUMBER(s). +If none are specified, read from standard input."; + +mod options { + pub static NUMBER: &str = "NUMBER"; +} fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box> { num_str @@ -34,14 +37,21 @@ fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box i32 { - let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse( - args.collect_str(InvalidEncodingHandling::Ignore) - .accept_any(), - ); + let matches = App::new(executable!()) + .version(VERSION) + .about(SUMMARY) + .arg(Arg::with_name(options::NUMBER).multiple(true)) + .get_matches_from(args); let stdout = stdout(); let mut w = io::BufWriter::new(stdout.lock()); - if matches.free.is_empty() { + if let Some(values) = matches.values_of(options::NUMBER) { + for number in values { + if let Err(e) = print_factors_str(number, &mut w) { + show_warning!("{}: {}", number, e); + } + } + } else { let stdin = stdin(); for line in stdin.lock().lines() { @@ -51,12 +61,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } - } else { - for number in &matches.free { - if let Err(e) = print_factors_str(number, &mut w) { - show_warning!("{}: {}", number, e); - } - } } if let Err(e) = w.flush() { diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index af2ff4ddb..7b856d1b8 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -39,6 +39,18 @@ fn test_first_100000_integers() { assert_eq!(hash_check, "4ed2d8403934fa1c76fe4b84c5d4b8850299c359"); } +#[test] +fn test_cli_args() { + // Make sure that factor works with CLI arguments as well. + new_ucmd!().args(&["3"]).succeeds().stdout_contains("3: 3"); + + new_ucmd!() + .args(&["3", "6"]) + .succeeds() + .stdout_contains("3: 3") + .stdout_contains("6: 2 3"); +} + #[test] fn test_random() { use conv::prelude::*; From b5cbd506bcd8516ea790ffd9e01252f8c02b6f15 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 28 May 2021 18:58:06 +0200 Subject: [PATCH 396/399] maint: remove trailing commas from matches Trailing commas are only supported starting from 1.48. --- src/uu/sort/src/numeric_str_cmp.rs | 2 +- src/uu/sort/src/sort.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 76dc81aeb..2935f55e8 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -70,7 +70,7 @@ impl NumInfo { if matches!( parse_settings.thousands_separator, - Some(c) if c == char, + Some(c) if c == char ) { continue; } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index cc391af00..001e59c18 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -585,7 +585,7 @@ impl FieldSelector { && to.is_none() && !matches!( settings.mode, - SortMode::Numeric | SortMode::GeneralNumeric | SortMode::HumanNumeric, + SortMode::Numeric | SortMode::GeneralNumeric | SortMode::HumanNumeric ), needs_tokens: from.field != 1 || from.char == 0 || to.is_some(), from, From e9656a6c32fb84a4f864dc88b34c7a537462769b Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 28 May 2021 22:38:29 +0200 Subject: [PATCH 397/399] sort: make GNU test sort-debug-keys pass (#2269) * sort: disable support for thousand separators In order to be compatible with GNU, we have to disable thousands separators. GNU does not enable them for the C locale, either. Once we add support for locales we can add this feature back. * sort: delete unused fixtures * sort: compare -0 and 0 equal I must have misunderstood this when implementing, but GNU considers -0, 0, and invalid numbers to be equal. * sort: strip blanks before applying the char index * sort: don't crash when key start is after key end * sort: add "no match" for months at the first non-whitespace char We should put the "^ no match for key" indicator at the first non-whitespace character of a field. * sort: improve support for e notation * sort: use maches! macros --- src/uu/sort/src/numeric_str_cmp.rs | 10 +- src/uu/sort/src/sort.rs | 92 ++++++++++++------- tests/by-util/test_sort.rs | 12 +++ .../fixtures/sort/exponents_general.expected | 9 ++ .../sort/exponents_general.expected.debug | 27 ++++++ tests/fixtures/sort/exponents_general.txt | 9 ++ tests/fixtures/sort/keys_blanks.expected | 3 + .../fixtures/sort/keys_blanks.expected.debug | 9 ++ tests/fixtures/sort/keys_blanks.txt | 3 + .../fixtures/sort/keys_negative_size.expected | 1 + .../sort/keys_negative_size.expected.debug | 3 + tests/fixtures/sort/keys_negative_size.txt | 1 + .../mixed_floats_ints_chars_numeric.expected | 4 +- ...d_floats_ints_chars_numeric.expected.debug | 12 +-- ...floats_ints_chars_numeric_reverse.expected | 30 ------ ...ints_chars_numeric_reverse_stable.expected | 30 ------ ...oats_ints_chars_numeric_reverse_stable.txt | 30 ------ ..._floats_ints_chars_numeric_stable.expected | 7 +- ...s_ints_chars_numeric_stable.expected.debug | 14 ++- ...mixed_floats_ints_chars_numeric_stable.txt | 3 + ..._floats_ints_chars_numeric_unique.expected | 3 +- ...s_ints_chars_numeric_unique.expected.debug | 6 +- ...ints_chars_numeric_unique_reverse.expected | 3 +- ...hars_numeric_unique_reverse.expected.debug | 6 +- ...ars_numeric_unique_reverse_stable.expected | 20 ---- ..._ints_chars_numeric_unique_stable.expected | 20 ---- ...loats_ints_chars_numeric_unique_stable.txt | 30 ------ tests/fixtures/sort/month_default.expected | 1 + .../sort/month_default.expected.debug | 3 + tests/fixtures/sort/month_default.txt | 1 + tests/fixtures/sort/month_stable.expected | 1 + .../fixtures/sort/month_stable.expected.debug | 2 + tests/fixtures/sort/month_stable.txt | 1 + .../sort/multiple_decimals_numeric.expected | 4 +- .../multiple_decimals_numeric.expected.debug | 12 +-- 35 files changed, 191 insertions(+), 231 deletions(-) create mode 100644 tests/fixtures/sort/keys_blanks.expected create mode 100644 tests/fixtures/sort/keys_blanks.expected.debug create mode 100644 tests/fixtures/sort/keys_blanks.txt create mode 100644 tests/fixtures/sort/keys_negative_size.expected create mode 100644 tests/fixtures/sort/keys_negative_size.expected.debug create mode 100644 tests/fixtures/sort/keys_negative_size.txt delete mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected delete mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected delete mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt delete mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected delete mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected delete mode 100644 tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 2935f55e8..03806b0c8 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -174,7 +174,11 @@ impl NumInfo { pub fn numeric_str_cmp((a, a_info): (&str, &NumInfo), (b, b_info): (&str, &NumInfo)) -> Ordering { // check for a difference in the sign if a_info.sign != b_info.sign { - return a_info.sign.cmp(&b_info.sign); + return if a.is_empty() && b.is_empty() { + Ordering::Equal + } else { + a_info.sign.cmp(&b_info.sign) + }; } // check for a difference in the exponent @@ -419,8 +423,8 @@ mod tests { #[test] fn minus_zero() { // This matches GNU sort behavior. - test_helper("-0", "0", Ordering::Less); - test_helper("-0x", "0", Ordering::Less); + test_helper("-0", "0", Ordering::Equal); + test_helper("-0x", "0", Ordering::Equal); } #[test] fn double_minus() { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 001e59c18..77be78390 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -99,10 +99,9 @@ static OPT_TMP_DIR: &str = "temporary-directory"; static ARG_FILES: &str = "files"; static DECIMAL_PT: char = '.'; -static THOUSANDS_SEP: char = ','; -static NEGATIVE: char = '-'; -static POSITIVE: char = '+'; +const NEGATIVE: char = '-'; +const POSITIVE: char = '+'; // Choosing a higher buffer size does not result in performance improvements // (at least not on my machine). TODO: In the future, we should also take the amount of @@ -330,8 +329,7 @@ impl<'a> Line<'a> { &self.line[selection.clone()], NumInfoParseSettings { accept_si_units: selector.settings.mode == SortMode::HumanNumeric, - thousands_separator: Some(THOUSANDS_SEP), - decimal_pt: Some(DECIMAL_PT), + ..Default::default() }, ); let initial_selection = selection.clone(); @@ -367,16 +365,24 @@ impl<'a> Line<'a> { SortMode::Month => { let initial_selection = &self.line[selection.clone()]; + let mut month_chars = initial_selection + .char_indices() + .skip_while(|(_, c)| c.is_whitespace()); + let month = if month_parse(initial_selection) == Month::Unknown { // We failed to parse a month, which is equivalent to matching nothing. - 0..0 + // Add the "no match for key" marker to the first non-whitespace character. + let first_non_whitespace = month_chars.next(); + first_non_whitespace.map_or( + initial_selection.len()..initial_selection.len(), + |(idx, _)| idx..idx, + ) } else { - // We parsed a month. Match the three first non-whitespace characters, which must be the month we parsed. - let mut chars = initial_selection - .char_indices() - .skip_while(|(_, c)| c.is_whitespace()); - chars.next().unwrap().0 - ..chars.nth(2).map_or(initial_selection.len(), |(idx, _)| idx) + // We parsed a month. Match the first three non-whitespace characters, which must be the month we parsed. + month_chars.next().unwrap().0 + ..month_chars + .nth(2) + .map_or(initial_selection.len(), |(idx, _)| idx) }; // Shorten selection to month. @@ -606,8 +612,7 @@ impl FieldSelector { range, NumInfoParseSettings { accept_si_units: self.settings.mode == SortMode::HumanNumeric, - thousands_separator: Some(THOUSANDS_SEP), - decimal_pt: Some(DECIMAL_PT), + ..Default::default() }, ); // Shorten the range to what we need to pass to numeric_str_cmp later. @@ -666,22 +671,21 @@ impl FieldSelector { } else { tokens.unwrap()[position.field - 1].start }; + // strip blanks if needed + if position.ignore_blanks { + idx += line[idx..] + .char_indices() + .find(|(_, c)| !c.is_whitespace()) + .map_or(line[idx..].len(), |(idx, _)| idx); + } + // apply the character index idx += line[idx..] .char_indices() .nth(position.char - 1) - .map_or(line.len(), |(idx, _)| idx); + .map_or(line[idx..].len(), |(idx, _)| idx); if idx >= line.len() { Resolution::TooHigh } else { - if position.ignore_blanks { - if let Some((not_whitespace, _)) = - line[idx..].char_indices().find(|(_, c)| !c.is_whitespace()) - { - idx += not_whitespace; - } else { - return Resolution::TooHigh; - } - } Resolution::StartOfChar(idx) } } @@ -691,8 +695,9 @@ impl FieldSelector { Resolution::StartOfChar(from) => { let to = self.to.as_ref().map(|to| resolve_index(line, tokens, &to)); - match to { + let mut range = match to { Some(Resolution::StartOfChar(mut to)) => { + // We need to include the character at `to`. to += line[to..].chars().next().map_or(1, |c| c.len_utf8()); from..to } @@ -703,7 +708,11 @@ impl FieldSelector { // If `to` is before the start of the line, report no match. // This can happen if the line starts with a separator. Some(Resolution::TooLow) => 0..0, + }; + if range.start > range.end { + range.end = range.start; } + range } Resolution::TooLow | Resolution::EndOfChar(_) => { unreachable!("This should only happen if the field start index is 0, but that should already have caused an error.") @@ -1202,6 +1211,8 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings) fn get_leading_gen(input: &str) -> Range { let trimmed = input.trim_start(); let leading_whitespace_len = input.len() - trimmed.len(); + + // check for inf, -inf and nan for allowed_prefix in &["inf", "-inf", "nan"] { if trimmed.is_char_boundary(allowed_prefix.len()) && trimmed[..allowed_prefix.len()].eq_ignore_ascii_case(allowed_prefix) @@ -1210,11 +1221,11 @@ fn get_leading_gen(input: &str) -> Range { } } // Make this iter peekable to see if next char is numeric - let mut char_indices = trimmed.char_indices().peekable(); + let mut char_indices = itertools::peek_nth(trimmed.char_indices()); let first = char_indices.peek(); - if first.map_or(false, |&(_, c)| c == NEGATIVE || c == POSITIVE) { + if matches!(first, Some((_, NEGATIVE)) | Some((_, POSITIVE))) { char_indices.next(); } @@ -1224,16 +1235,29 @@ fn get_leading_gen(input: &str) -> Range { if c.is_ascii_digit() { continue; } - if c == DECIMAL_PT && !had_decimal_pt { + if c == DECIMAL_PT && !had_decimal_pt && !had_e_notation { had_decimal_pt = true; continue; } - let next_char_numeric = char_indices - .peek() - .map_or(false, |(_, c)| c.is_ascii_digit()); - if (c == 'e' || c == 'E') && !had_e_notation && next_char_numeric { - had_e_notation = true; - continue; + if (c == 'e' || c == 'E') && !had_e_notation { + // we can only consume the 'e' if what follow is either a digit, or a sign followed by a digit. + if let Some(&(_, next_char)) = char_indices.peek() { + if (next_char == '+' || next_char == '-') + && matches!( + char_indices.peek_nth(2), + Some((_, c)) if c.is_ascii_digit() + ) + { + // Consume the sign. The following digits will be consumed by the main loop. + char_indices.next(); + had_e_notation = true; + continue; + } + if next_char.is_ascii_digit() { + had_e_notation = true; + continue; + } + } } return leading_whitespace_len..(leading_whitespace_len + idx); } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 133dc0028..624c002d0 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -526,6 +526,11 @@ fn test_keys_with_options_blanks_start() { } } +#[test] +fn test_keys_blanks_with_char_idx() { + test_helper("keys_blanks", &["-k 1.2b"]) +} + #[test] fn test_keys_with_options_blanks_end() { let input = "a b @@ -574,6 +579,13 @@ aaaa .stdout_only(input); } +#[test] +fn test_keys_negative_size_match() { + // If the end of a field is before its start, we should not crash. + // Debug output should report "no match for key" at the start position (i.e. the later position). + test_helper("keys_negative_size", &["-k 3,1"]); +} + #[test] fn test_zero_terminated() { test_helper("zero-terminated", &["-z"]); diff --git a/tests/fixtures/sort/exponents_general.expected b/tests/fixtures/sort/exponents_general.expected index 524b6e67f..308a82e1e 100644 --- a/tests/fixtures/sort/exponents_general.expected +++ b/tests/fixtures/sort/exponents_general.expected @@ -6,8 +6,15 @@ + -12e-5555.5 +0b10 // binary not supported +0x10 // hexadecimal not supported, but it should be +55e-20 +55e-20.10 5.5.5.5 10E +64e+ +99e- 1000EDKLD 10000K78 +100000 @@ -15,5 +22,7 @@ 100E6 100E6 10e10e10e10 +13e+10 +45e+10.5 50e10 50e10 diff --git a/tests/fixtures/sort/exponents_general.expected.debug b/tests/fixtures/sort/exponents_general.expected.debug index 4dea45c39..a7238d10e 100644 --- a/tests/fixtures/sort/exponents_general.expected.debug +++ b/tests/fixtures/sort/exponents_general.expected.debug @@ -22,12 +22,33 @@ ^ no match for key ^ no match for key + > -12e-5555.5 + _________ +______________ +0b10 // binary not supported +_ +____________________________ +0x10 // hexadecimal not supported, but it should be +_ +___________________________________________________ +55e-20 +______ +______ +55e-20.10 +______ +_________ 5.5.5.5 ___ _______ 10E __ ___ +64e+ +__ +____ +99e- +__ +____ 1000EDKLD ____ _________ @@ -49,6 +70,12 @@ _____ 10e10e10e10 _____ ___________ +13e+10 +______ +______ +45e+10.5 +______ +________ 50e10 _____ _____ diff --git a/tests/fixtures/sort/exponents_general.txt b/tests/fixtures/sort/exponents_general.txt index de2a6c31b..4a9bbba2e 100644 --- a/tests/fixtures/sort/exponents_general.txt +++ b/tests/fixtures/sort/exponents_general.txt @@ -4,16 +4,25 @@ +100000 10000K78 +0x10 // hexadecimal not supported, but it should be 10E +0b10 // binary not supported +64e+ +99e- +45e+10.5 1000EDKLD +13e+10 100E6 50e10 + -12e-5555.5 +100000 10e10e10e10 5.5.5.5 +55e-20 +55e-20.10 diff --git a/tests/fixtures/sort/keys_blanks.expected b/tests/fixtures/sort/keys_blanks.expected new file mode 100644 index 000000000..1789659c4 --- /dev/null +++ b/tests/fixtures/sort/keys_blanks.expected @@ -0,0 +1,3 @@ + cab + abc + bca diff --git a/tests/fixtures/sort/keys_blanks.expected.debug b/tests/fixtures/sort/keys_blanks.expected.debug new file mode 100644 index 000000000..bb09ea8a2 --- /dev/null +++ b/tests/fixtures/sort/keys_blanks.expected.debug @@ -0,0 +1,9 @@ +>cab + __ +____ +>abc + __ +____ +>bca + __ +____ diff --git a/tests/fixtures/sort/keys_blanks.txt b/tests/fixtures/sort/keys_blanks.txt new file mode 100644 index 000000000..7c4f313a3 --- /dev/null +++ b/tests/fixtures/sort/keys_blanks.txt @@ -0,0 +1,3 @@ + abc + cab + bca diff --git a/tests/fixtures/sort/keys_negative_size.expected b/tests/fixtures/sort/keys_negative_size.expected new file mode 100644 index 000000000..3774da60e --- /dev/null +++ b/tests/fixtures/sort/keys_negative_size.expected @@ -0,0 +1 @@ +a b c diff --git a/tests/fixtures/sort/keys_negative_size.expected.debug b/tests/fixtures/sort/keys_negative_size.expected.debug new file mode 100644 index 000000000..e392ec7f3 --- /dev/null +++ b/tests/fixtures/sort/keys_negative_size.expected.debug @@ -0,0 +1,3 @@ +a b c + ^ no match for key +_____ diff --git a/tests/fixtures/sort/keys_negative_size.txt b/tests/fixtures/sort/keys_negative_size.txt new file mode 100644 index 000000000..3774da60e --- /dev/null +++ b/tests/fixtures/sort/keys_negative_size.txt @@ -0,0 +1 @@ +a b c diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected index a781a36bb..59541af32 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected @@ -21,10 +21,10 @@ CARAvan 8.013 45 46.89 - 4567. - 37800 576,446.88800000 576,446.890 + 4567. + 37800 4798908.340000000000 4798908.45 4798908.8909800 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug index dbe295a1c..b7b76e589 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected.debug @@ -67,18 +67,18 @@ __ 46.89 _____ _____ +576,446.88800000 +___ +________________ +576,446.890 +___ +___________ 4567. _____ ____________________ >>>>37800 _____ _________ -576,446.88800000 -________________ -________________ -576,446.890 -___________ -___________ 4798908.340000000000 ____________________ ____________________ diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected deleted file mode 100644 index 6b024210b..000000000 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected +++ /dev/null @@ -1,30 +0,0 @@ -4798908.8909800 -4798908.45 -4798908.340000000000 -576,446.890 -576,446.88800000 - 37800 - 4567. -46.89 -45 -8.013 -1.58590 -1.444 -1.040000000 -1 -00000001 -CARAvan -000 - - - - - - - - --.05 --1 --8.90880 --896689 --2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected deleted file mode 100644 index cb1028f0e..000000000 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected +++ /dev/null @@ -1,30 +0,0 @@ -4798908.8909800 -4798908.45 -4798908.340000000000 -576,446.890 -576,446.88800000 - 37800 - 4567. -46.89 -45 -8.013 -1.58590 -1.444 -1.040000000 -1 -00000001 - - - - - -CARAvan - - - -000 --.05 --1 --8.90880 --896689 --2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt deleted file mode 100644 index a5813ea3a..000000000 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt +++ /dev/null @@ -1,30 +0,0 @@ -576,446.890 -576,446.88800000 - - - 4567. -45 -46.89 --1 -1 -00000001 -4798908.340000000000 -4798908.45 -4798908.8909800 - - - 37800 - --2028789030 --896689 -CARAvan - --8.90880 --.05 -1.444 -1.58590 -1.040000000 - -8.013 - -000 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected index 63a3e646d..0ccdd84c0 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected @@ -8,11 +8,14 @@ +-0 CARAvan +-0 000 +-0 1 00000001 1.040000000 @@ -21,10 +24,10 @@ CARAvan 8.013 45 46.89 +576,446.890 +576,446.88800000 4567. 37800 -576,446.88800000 -576,446.890 4798908.340000000000 4798908.45 4798908.8909800 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug index b2782d93d..66a98b208 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected.debug @@ -18,8 +18,12 @@ ____ ^ no match for key ^ no match for key +-0 +__ CARAvan ^ no match for key +-0 +__ ^ no match for key @@ -28,6 +32,8 @@ CARAvan ^ no match for key 000 ___ +-0 +__ 1 _ 00000001 @@ -44,14 +50,14 @@ _____ __ 46.89 _____ +576,446.890 +___ +576,446.88800000 +___ 4567. _____ >>>>37800 _____ -576,446.88800000 -________________ -576,446.890 -___________ 4798908.340000000000 ____________________ 4798908.45 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt index a5813ea3a..44667ef03 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt @@ -17,7 +17,9 @@ -2028789030 -896689 +-0 CARAvan +-0 -8.90880 -.05 @@ -28,3 +30,4 @@ CARAvan 8.013 000 +-0 \ No newline at end of file diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected index cb27c6664..cd4256c5f 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected @@ -11,10 +11,9 @@ 8.013 45 46.89 +576,446.890 4567. 37800 -576,446.88800000 -576,446.890 4798908.340000000000 4798908.45 4798908.8909800 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug index 782a77724..663a4b3a9 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected.debug @@ -24,14 +24,12 @@ _____ __ 46.89 _____ +576,446.890 +___ 4567. _____ >>>>37800 _____ -576,446.88800000 -________________ -576,446.890 -___________ 4798908.340000000000 ____________________ 4798908.45 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected index bbce16934..97e261f14 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected @@ -1,10 +1,9 @@ 4798908.8909800 4798908.45 4798908.340000000000 -576,446.890 -576,446.88800000 37800 4567. +576,446.890 46.89 45 8.013 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug index e0389c1d5..01f7abf5b 100644 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug +++ b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected.debug @@ -4,14 +4,12 @@ _______________ __________ 4798908.340000000000 ____________________ -576,446.890 -___________ -576,446.88800000 -________________ >>>>37800 _____ 4567. _____ +576,446.890 +___ 46.89 _____ 45 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected deleted file mode 100644 index bbce16934..000000000 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse_stable.expected +++ /dev/null @@ -1,20 +0,0 @@ -4798908.8909800 -4798908.45 -4798908.340000000000 -576,446.890 -576,446.88800000 - 37800 - 4567. -46.89 -45 -8.013 -1.58590 -1.444 -1.040000000 -1 - --.05 --1 --8.90880 --896689 --2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected deleted file mode 100644 index bbce16934..000000000 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected +++ /dev/null @@ -1,20 +0,0 @@ -4798908.8909800 -4798908.45 -4798908.340000000000 -576,446.890 -576,446.88800000 - 37800 - 4567. -46.89 -45 -8.013 -1.58590 -1.444 -1.040000000 -1 - --.05 --1 --8.90880 --896689 --2028789030 diff --git a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt b/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt deleted file mode 100644 index a5813ea3a..000000000 --- a/tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt +++ /dev/null @@ -1,30 +0,0 @@ -576,446.890 -576,446.88800000 - - - 4567. -45 -46.89 --1 -1 -00000001 -4798908.340000000000 -4798908.45 -4798908.8909800 - - - 37800 - --2028789030 --896689 -CARAvan - --8.90880 --.05 -1.444 -1.58590 -1.040000000 - -8.013 - -000 diff --git a/tests/fixtures/sort/month_default.expected b/tests/fixtures/sort/month_default.expected index dc51f5d5e..99baa235a 100644 --- a/tests/fixtures/sort/month_default.expected +++ b/tests/fixtures/sort/month_default.expected @@ -1,3 +1,4 @@ + asdf N/A Ut enim ad minim veniam, quis Jan Lorem ipsum dolor sit amet mar laboris nisi ut aliquip ex ea diff --git a/tests/fixtures/sort/month_default.expected.debug b/tests/fixtures/sort/month_default.expected.debug index 2c55a0e2a..f28ba1c48 100644 --- a/tests/fixtures/sort/month_default.expected.debug +++ b/tests/fixtures/sort/month_default.expected.debug @@ -1,3 +1,6 @@ + asdf + ^ no match for key +_____ N/A Ut enim ad minim veniam, quis ^ no match for key _________________________________ diff --git a/tests/fixtures/sort/month_default.txt b/tests/fixtures/sort/month_default.txt index 6d64bf4f8..c96bad300 100644 --- a/tests/fixtures/sort/month_default.txt +++ b/tests/fixtures/sort/month_default.txt @@ -8,3 +8,4 @@ mar laboris nisi ut aliquip ex ea Jul 2 these three lines Jul 1 should remain 2,1,3 Jul 3 if --stable is provided + asdf diff --git a/tests/fixtures/sort/month_stable.expected b/tests/fixtures/sort/month_stable.expected index 868728a18..cb3a8da8f 100644 --- a/tests/fixtures/sort/month_stable.expected +++ b/tests/fixtures/sort/month_stable.expected @@ -1,4 +1,5 @@ N/A Ut enim ad minim veniam, quis + asdf Jan Lorem ipsum dolor sit amet mar laboris nisi ut aliquip ex ea May sed do eiusmod tempor incididunt diff --git a/tests/fixtures/sort/month_stable.expected.debug b/tests/fixtures/sort/month_stable.expected.debug index 4163ba39a..740f9187c 100644 --- a/tests/fixtures/sort/month_stable.expected.debug +++ b/tests/fixtures/sort/month_stable.expected.debug @@ -1,5 +1,7 @@ N/A Ut enim ad minim veniam, quis ^ no match for key + asdf + ^ no match for key Jan Lorem ipsum dolor sit amet ___ mar laboris nisi ut aliquip ex ea diff --git a/tests/fixtures/sort/month_stable.txt b/tests/fixtures/sort/month_stable.txt index 6d64bf4f8..c96bad300 100644 --- a/tests/fixtures/sort/month_stable.txt +++ b/tests/fixtures/sort/month_stable.txt @@ -8,3 +8,4 @@ mar laboris nisi ut aliquip ex ea Jul 2 these three lines Jul 1 should remain 2,1,3 Jul 3 if --stable is provided + asdf diff --git a/tests/fixtures/sort/multiple_decimals_numeric.expected b/tests/fixtures/sort/multiple_decimals_numeric.expected index 3ef4d22e8..8f42e7ce5 100644 --- a/tests/fixtures/sort/multiple_decimals_numeric.expected +++ b/tests/fixtures/sort/multiple_decimals_numeric.expected @@ -21,6 +21,8 @@ CARAvan 8.013 45 46.89 +576,446.88800000 +576,446.890 4567..457 4567. 4567.1 @@ -28,8 +30,6 @@ CARAvan 37800 45670.89079.098 45670.89079.1 -576,446.88800000 -576,446.890 4798908.340000000000 4798908.45 4798908.8909800 diff --git a/tests/fixtures/sort/multiple_decimals_numeric.expected.debug b/tests/fixtures/sort/multiple_decimals_numeric.expected.debug index f40ade9aa..948c4869c 100644 --- a/tests/fixtures/sort/multiple_decimals_numeric.expected.debug +++ b/tests/fixtures/sort/multiple_decimals_numeric.expected.debug @@ -67,6 +67,12 @@ __ 46.89 _____ _____ +576,446.88800000 +___ +________________ +576,446.890 +___ +___________ >>>>>>>>>>4567..457 _____ ___________________ @@ -88,12 +94,6 @@ _____________________ >>>>>>45670.89079.1 ___________ ___________________ -576,446.88800000 -________________ -________________ -576,446.890 -___________ -___________ 4798908.340000000000 ____________________ ____________________ From bb268d1500eabc2bf8efa193de4fc4923a06b66a Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 28 May 2021 22:39:33 +0200 Subject: [PATCH 398/399] sort: crash when failing to open an input file (#2265) * sort: crash when failing to open an input file Instead of ignoring files we fail to open, crash. The error message does not exactly match gnu, but that would require more effort. * use split_whitespace instead of a manual implementation * fix expected error on windows * sort: update expected error message --- src/uu/sort/src/check.rs | 2 +- src/uu/sort/src/merge.rs | 2 +- src/uu/sort/src/sort.rs | 13 ++++++------- tests/by-util/test_sort.rs | 18 ++++++++++++++++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index 01b5a25b5..d3b9d6669 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -26,7 +26,7 @@ use std::{ /// /// The code we should exit with. pub fn check(path: &str, settings: &GlobalSettings) -> i32 { - let file = open(path).expect("failed to open input file"); + let file = open(path); let (recycled_sender, recycled_receiver) = sync_channel(2); let (loaded_sender, loaded_receiver) = sync_channel(2); thread::spawn({ diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 48d48ad40..696353829 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -29,7 +29,7 @@ pub fn merge<'a>(files: &[impl AsRef], settings: &'a GlobalSettings) -> F let (request_sender, request_receiver) = channel(); let mut reader_files = Vec::with_capacity(files.len()); let mut loaded_receivers = Vec::with_capacity(files.len()); - for (file_number, file) in files.iter().filter_map(open).enumerate() { + for (file_number, file) in files.iter().map(open).enumerate() { let (sender, receiver) = sync_channel(2); loaded_receivers.push(receiver); reader_files.push(ReaderFile { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 77be78390..0efce00e6 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -955,7 +955,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut files = Vec::new(); for path in &files0_from { - let reader = open(path.as_str()).expect("Could not read from file specified."); + let reader = open(path.as_str()); let buf_reader = BufReader::new(reader); for line in buf_reader.split(b'\0').flatten() { files.push( @@ -1116,7 +1116,7 @@ fn exec(files: &[String], settings: &GlobalSettings) -> i32 { } return check::check(files.first().unwrap(), settings); } else { - let mut lines = files.iter().filter_map(open); + let mut lines = files.iter().map(open); ext_sort(&mut lines, &settings); } @@ -1413,18 +1413,17 @@ fn print_sorted<'a, T: Iterator>>(iter: T, settings: &Global } // from cat.rs -fn open(path: impl AsRef) -> Option> { +fn open(path: impl AsRef) -> Box { let path = path.as_ref(); if path == "-" { let stdin = stdin(); - return Some(Box::new(stdin) as Box); + return Box::new(stdin) as Box; } match File::open(Path::new(path)) { - Ok(f) => Some(Box::new(f) as Box), + Ok(f) => Box::new(f) as Box, Err(e) => { - show_error!("{0:?}: {1}", path, e.to_string()); - None + crash!(2, "cannot read: {0:?}: {1}", path, e); } } } diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 624c002d0..3c0af259f 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -4,14 +4,14 @@ fn test_helper(file_name: &str, possible_args: &[&str]) { for args in possible_args { new_ucmd!() .arg(format!("{}.txt", file_name)) - .args(&args.split(' ').collect::>()) + .args(&args.split_whitespace().collect::>()) .succeeds() .stdout_is_fixture(format!("{}.expected", file_name)); new_ucmd!() .arg(format!("{}.txt", file_name)) .arg("--debug") - .args(&args.split(' ').collect::>()) + .args(&args.split_whitespace().collect::>()) .succeeds() .stdout_is_fixture(format!("{}.expected.debug", file_name)); } @@ -723,3 +723,17 @@ fn test_trailing_separator() { .succeeds() .stdout_is("aax\naaa\n"); } + +#[test] +fn test_nonexistent_file() { + new_ucmd!() + .arg("nonexistent.txt") + .fails() + .status_code(2) + .stderr_only( + #[cfg(not(windows))] + "sort: cannot read: \"nonexistent.txt\": No such file or directory (os error 2)", + #[cfg(windows)] + "sort: cannot read: \"nonexistent.txt\": The system cannot find the file specified. (os error 2)", + ); +} From 714661774bd04d83f5c92c33ff2eef851f4c78a4 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 29 May 2021 01:51:00 +0200 Subject: [PATCH 399/399] users: fix long_help text and clippy warning --- src/uu/users/src/users.rs | 21 +++++++++++++-------- tests/by-util/test_users.rs | 28 ++++++++++++---------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 4bb628441..99e06c1af 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -6,19 +6,14 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -/* last synced with: whoami (GNU coreutils) 8.22 */ -// Allow dead code here in order to keep all fields, constants here, for consistency. -#![allow(dead_code)] - #[macro_use] extern crate uucore; -use uucore::utmpx::*; - use clap::{App, Arg}; +use uucore::utmpx::{self, Utmpx}; -static ABOUT: &str = "Display who is currently logged in, according to FILE."; static VERSION: &str = env!("CARGO_PKG_VERSION"); +static ABOUT: &str = "Print the user names of users currently logged in to the current host"; static ARG_FILES: &str = "files"; @@ -26,13 +21,23 @@ fn get_usage() -> String { format!("{0} [FILE]", executable!()) } +fn get_long_usage() -> String { + format!( + "Output who is currently logged in according to FILE. +If FILE is not specified, use {}. /var/log/wtmp as FILE is common.", + utmpx::DEFAULT_FILE + ) +} + pub fn uumain(args: impl uucore::Args) -> i32 { let usage = get_usage(); + let after_help = get_long_usage(); let matches = App::new(executable!()) .version(VERSION) .about(ABOUT) .usage(&usage[..]) + .after_help(&after_help[..]) .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) .get_matches_from(args); @@ -44,7 +49,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let filename = if !files.is_empty() { files[0].as_ref() } else { - DEFAULT_FILE + utmpx::DEFAULT_FILE }; let mut users = Utmpx::iter_all_records() diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index 3c5789820..89c3fdd0f 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -1,27 +1,23 @@ use crate::common::util::*; -use std::env; #[test] fn test_users_noarg() { new_ucmd!().succeeds(); } + #[test] +#[cfg(any(target_vendor = "apple", target_os = "linux"))] fn test_users_check_name() { - let result = TestScenario::new(util_name!()).ucmd_keepenv().succeeds(); + #[cfg(target_os = "linux")] + let util_name = util_name!(); + #[cfg(target_vendor = "apple")] + let util_name = format!("g{}", util_name!()); - // Expectation: USER is often set - let key = "USER"; + let expected = TestScenario::new(&util_name) + .cmd_keepenv(util_name) + .env("LANGUAGE", "C") + .succeeds() + .stdout_move_str(); - match env::var(key) { - Err(e) => println!("Key {} isn't set. Found {}", &key, e), - Ok(username) => - // Check if "users" contains the name of the user - { - println!("username found {}", &username); - // println!("result.stdout {}", &result.stdout); - if !result.stdout_str().is_empty() { - result.stdout_contains(&username); - } - } - } + new_ucmd!().succeeds().stdout_is(&expected); }