1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 20:47:46 +00:00

Merge branch 'master' into sort-disable-dictionary-mode

This commit is contained in:
Sylvestre Ledru 2021-04-24 10:04:23 +02:00 committed by GitHub
commit 513ff4e45f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 732 additions and 948 deletions

44
Cargo.lock generated
View file

@ -28,6 +28,15 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.4.12" version = "0.4.12"
@ -127,9 +136,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.2.3" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" checksum = "cc38c385bfd7e444464011bb24820f40dd1c76bcdfa1b78611cb7c2e5cafab75"
dependencies = [ dependencies = [
"rustc_version", "rustc_version",
] ]
@ -169,7 +178,7 @@ version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [ dependencies = [
"ansi_term", "ansi_term 0.11.0",
"atty", "atty",
"bitflags", "bitflags",
"strsim", "strsim",
@ -452,9 +461,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.0" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"crossbeam-utils", "crossbeam-utils",
@ -580,7 +589,7 @@ checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall 0.2.5", "redox_syscall 0.2.6",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -783,6 +792,15 @@ dependencies = [
"cfg-if 1.0.0", "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]] [[package]]
name = "match_cfg" name = "match_cfg"
version = "0.1.0" version = "0.1.0"
@ -1176,9 +1194,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -1189,7 +1207,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [ dependencies = [
"redox_syscall 0.2.5", "redox_syscall 0.2.6",
] ]
[[package]] [[package]]
@ -1394,9 +1412,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.69" version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb" checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
@ -1454,7 +1472,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [ dependencies = [
"libc", "libc",
"numtoa", "numtoa",
"redox_syscall 0.2.5", "redox_syscall 0.2.6",
"redox_termios", "redox_termios",
] ]
@ -1990,11 +2008,11 @@ dependencies = [
"clap", "clap",
"globset", "globset",
"lazy_static", "lazy_static",
"lscolors",
"number_prefix", "number_prefix",
"term_grid", "term_grid",
"termsize", "termsize",
"time", "time",
"unicode-width",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]

34
src/uu/ls/BENCHMARKING.md Normal file
View file

@ -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`

View file

@ -16,18 +16,18 @@ path = "src/ls.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
lazy_static = "1.0.1"
number_prefix = "0.4" number_prefix = "0.4"
term_grid = "0.1.5" term_grid = "0.1.5"
termsize = "0.1.6" termsize = "0.1.6"
time = "0.1.40" time = "0.1.40"
unicode-width = "0.1.5"
globset = "0.4.6" 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 = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
atty = "0.2"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
atty = "0.2" lazy_static = "1.4.0"
[[bin]] [[bin]]
name = "ls" name = "ls"

View file

@ -7,27 +7,25 @@
// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf // spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf
#[macro_use]
extern crate uucore;
#[cfg(unix)] #[cfg(unix)]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use]
extern crate uucore;
mod quoting_style; mod quoting_style;
mod version_cmp; mod version_cmp;
use clap::{App, Arg}; use clap::{App, Arg};
use globset::{self, Glob, GlobSet, GlobSetBuilder}; use globset::{self, Glob, GlobSet, GlobSetBuilder};
use lscolors::LsColors;
use number_prefix::NumberPrefix; use number_prefix::NumberPrefix;
use quoting_style::{escape_name, QuotingStyle}; use quoting_style::{escape_name, QuotingStyle};
#[cfg(unix)] #[cfg(unix)]
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs::{self, DirEntry, FileType, Metadata};
use std::fs::{DirEntry, FileType, Metadata};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
#[cfg(any(unix, target_os = "redox"))] #[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::{FileTypeExt, MetadataExt};
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -39,9 +37,7 @@ use std::{cmp::Reverse, process::exit};
use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use time::{strftime, Timespec}; use time::{strftime, Timespec};
#[cfg(unix)] #[cfg(unix)]
use unicode_width::UnicodeWidthStr; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
#[cfg(unix)]
use uucore::libc::{mode_t, S_ISGID, S_ISUID, S_ISVTX, S_IWOTH, S_IXGRP, S_IXOTH, S_IXUSR};
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = " static ABOUT: &str = "
@ -54,30 +50,6 @@ fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) 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 options {
pub mod format { pub mod format {
pub static ONELINE: &str = "1"; pub static ONELINE: &str = "1";
@ -212,8 +184,7 @@ struct Config {
time: Time, time: Time,
#[cfg(unix)] #[cfg(unix)]
inode: bool, inode: bool,
#[cfg(unix)] color: Option<LsColors>,
color: bool,
long: LongFormat, long: LongFormat,
width: Option<u16>, width: Option<u16>,
quoting_style: QuotingStyle, quoting_style: QuotingStyle,
@ -337,8 +308,7 @@ impl Config {
Time::Modification Time::Modification
}; };
#[cfg(unix)] let needs_color = match options.value_of(options::COLOR) {
let color = match options.value_of(options::COLOR) {
None => options.is_present(options::COLOR), None => options.is_present(options::COLOR),
Some(val) => match val { Some(val) => match val {
"" | "always" | "yes" | "force" => true, "" | "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) { let size_format = if options.is_present(options::size::HUMAN_READABLE) {
SizeFormat::Binary SizeFormat::Binary
} else if options.is_present(options::size::SI) { } else if options.is_present(options::size::SI) {
@ -520,7 +496,6 @@ impl Config {
size_format, size_format,
directory: options.is_present(options::DIRECTORY), directory: options.is_present(options::DIRECTORY),
time, time,
#[cfg(unix)]
color, color,
#[cfg(unix)] #[cfg(unix)]
inode: options.is_present(options::INODE), inode: options.is_present(options::INODE),
@ -1038,12 +1013,43 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
list(locs, Config::from(matches)) 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<MetaData> got from symlink_metadata() or metadata() based on config
md: std::io::Result<Metadata>,
// 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<String>, config: Config) -> i32 { fn list(locs: Vec<String>, config: Config) -> i32 {
let number_of_locs = locs.len(); let number_of_locs = locs.len();
let mut files = Vec::<PathBuf>::new(); let mut files = Vec::<PathData>::new();
let mut dirs = Vec::<PathBuf>::new(); let mut dirs = Vec::<PathData>::new();
let mut has_failed = false; let mut has_failed = false;
for loc in locs { for loc in locs {
let p = PathBuf::from(&loc); let p = PathBuf::from(&loc);
if !p.exists() { if !p.exists() {
@ -1054,36 +1060,28 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
continue; continue;
} }
let show_dir_contents = if !config.directory { let path_data = PathData::new(p, &config, true);
match config.dereference {
Dereference::None => { let show_dir_contents = if let Ok(md) = path_data.md.as_ref() {
if let Ok(md) = p.symlink_metadata() { !config.directory && md.is_dir()
md.is_dir()
} else {
show_error!("'{}': {}", &loc, "No such file or directory");
has_failed = true;
continue;
}
}
_ => p.is_dir(),
}
} else { } else {
has_failed = true;
false false
}; };
if show_dir_contents { if show_dir_contents {
dirs.push(p); dirs.push(path_data);
} else { } else {
files.push(p); files.push(path_data);
} }
} }
sort_entries(&mut files, &config); sort_entries(&mut files, &config);
display_items(&files, None, &config, true); display_items(&files, None, &config);
sort_entries(&mut dirs, &config); sort_entries(&mut dirs, &config);
for dir in dirs { for dir in dirs {
if number_of_locs > 1 { if number_of_locs > 1 {
println!("\n{}:", dir.to_string_lossy()); println!("\n{}:", dir.lossy_string);
} }
enter_directory(&dir, &config); enter_directory(&dir, &config);
} }
@ -1094,22 +1092,22 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
} }
} }
fn sort_entries(entries: &mut Vec<PathBuf>, config: &Config) { fn sort_entries(entries: &mut Vec<PathData>, config: &Config) {
match config.sort { match config.sort {
Sort::Time => entries.sort_by_key(|k| { Sort::Time => entries.sort_by_key(|k| {
Reverse( Reverse(
get_metadata(k, false) k.md.as_ref()
.ok() .ok()
.and_then(|md| get_system_time(&md, config)) .and_then(|md| get_system_time(&md, config))
.unwrap_or(UNIX_EPOCH), .unwrap_or(UNIX_EPOCH),
) )
}), }),
Sort::Size => { 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 // The default sort in GNU ls is case insensitive
Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()), Sort::Name => entries.sort_by_key(|k| k.file_name.to_lowercase()),
Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)), Sort::Version => entries.sort_by(|k, j| version_cmp::version_cmp(&k.p_buf, &j.p_buf)),
Sort::None => {} Sort::None => {}
} }
@ -1143,32 +1141,57 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
true true
} }
fn enter_directory(dir: &Path, config: &Config) { fn enter_directory(dir: &PathData, config: &Config) {
let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect)); let mut entries: Vec<_> = if config.files == Files::All {
vec![
entries.retain(|e| should_display(e, config)); PathData::new(dir.p_buf.join("."), config, false),
PathData::new(dir.p_buf.join(".."), config, false),
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);
} else { } 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 { if config.recursive {
for e in entries.iter().filter(|p| p.is_dir()) { for e in entries
println!("\n{}:", e.to_string_lossy()); .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); enter_directory(&e, config);
} }
} }
} }
fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> { fn get_metadata(entry: &Path, config: &Config, command_line: bool) -> std::io::Result<Metadata> {
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 { if dereference {
entry.metadata().or_else(|_| entry.symlink_metadata()) entry.metadata().or_else(|_| entry.symlink_metadata())
} else { } else {
@ -1176,8 +1199,8 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
} }
} }
fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) { fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) {
if let Ok(md) = get_metadata(entry, false) { if let Ok(md) = entry.md.as_ref() {
( (
display_symlink_count(&md).len(), display_symlink_count(&md).len(),
display_file_size(&md, config).len(), display_file_size(&md, config).len(),
@ -1191,7 +1214,7 @@ fn pad_left(string: String, count: usize) -> String {
format!("{:>width$}", string, width = count) 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 { if config.format == Format::Long {
let (mut max_links, mut max_size) = (1, 1); let (mut max_links, mut max_size) = (1, 1);
for item in items { for item in items {
@ -1200,18 +1223,18 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, comma
max_size = size.max(max_size); max_size = size.max(max_size);
} }
for item in items { 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 { } else {
let names = items.iter().filter_map(|i| { let names = items.iter().filter_map(|i| {
let md = get_metadata(i, false); let md = i.md.as_ref();
match md { match md {
Err(e) => { Err(e) => {
let filename = get_file_name(i, strip); let filename = get_file_name(&i.p_buf, strip);
show_error!("'{}': {}", filename, e); show_error!("'{}': {}", filename, e);
None 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 +1294,15 @@ fn display_grid(names: impl Iterator<Item = Cell>, width: u16, direction: Direct
use uucore::fs::display_permissions; use uucore::fs::display_permissions;
fn display_item_long( fn display_item_long(
item: &Path, item: &PathData,
strip: Option<&Path>, strip: Option<&Path>,
max_links: usize, max_links: usize,
max_size: usize, max_size: usize,
config: &Config, config: &Config,
command_line: bool,
) { ) {
let dereference = match &config.dereference { let md = match &item.md {
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) => { Err(e) => {
let filename = get_file_name(&item, strip); let filename = get_file_name(&item.p_buf, strip);
show_error!("{}: {}", filename, e); show_error!("{}: {}", filename, e);
return; return;
} }
@ -1336,7 +1341,7 @@ fn display_item_long(
" {} {} {}", " {} {} {}",
pad_left(display_file_size(&md, config), max_size), pad_left(display_file_size(&md, config), max_size),
display_date(&md, config), display_date(&md, config),
display_file_name(&item, strip, &md, config).contents, display_file_name(&item.p_buf, strip, &md, config).contents,
); );
} }
@ -1348,14 +1353,50 @@ fn get_inode(metadata: &Metadata) -> String {
// Currently getpwuid is `linux` target only. If it's broken out into // Currently getpwuid is `linux` target only. If it's broken out into
// a posix-compliant attribute this can be updated... // a posix-compliant attribute this can be updated...
#[cfg(unix)] #[cfg(unix)]
use std::sync::Mutex;
#[cfg(unix)]
use uucore::entries; use uucore::entries;
#[cfg(unix)]
fn cached_uid2usr(uid: u32) -> String {
lazy_static! {
static ref UID_CACHE: Mutex<HashMap<u32, String>> = 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)] #[cfg(unix)]
fn display_uname(metadata: &Metadata, config: &Config) -> String { fn display_uname(metadata: &Metadata, config: &Config) -> String {
if config.long.numeric_uid_gid { if config.long.numeric_uid_gid {
metadata.uid().to_string() metadata.uid().to_string()
} else { } 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<HashMap<u32, String>> = 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 +1405,7 @@ fn display_group(metadata: &Metadata, config: &Config) -> String {
if config.long.numeric_uid_gid { if config.long.numeric_uid_gid {
metadata.gid().to_string() metadata.gid().to_string()
} else { } else {
entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string()) cached_gid2grp(metadata.gid())
} }
} }
@ -1470,140 +1511,63 @@ fn get_file_name(name: &Path, strip: Option<&Path>) -> String {
name.to_string_lossy().into_owned() name.to_string_lossy().into_owned()
} }
#[cfg(not(unix))] #[cfg(unix)]
fn display_file_name( fn file_is_executable(md: &Metadata) -> bool {
path: &Path, // Mode always returns u32, but the flags might not be, based on the platform
strip: Option<&Path>, // e.g. linux has u32, mac has u16.
metadata: &Metadata, // S_IXUSR -> user has execute permission
config: &Config, // S_IXGRP -> group has execute persmission
) -> Cell { // S_IXOTH -> other users have execute permission
let mut name = escape_name(get_file_name(path, strip), &config.quoting_style); md.mode() & ((S_IXUSR | S_IXGRP | S_IXOTH) as u32) != 0
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)] #[allow(clippy::clippy::collapsible_else_if)]
fn color_name(name: String, typ: &str) -> String { fn classify_file(md: &Metadata) -> Option<char> {
let mut typ = typ; let file_type = md.file_type();
if !COLOR_MAP.contains_key(typ) {
if typ == "or" { if file_type.is_dir() {
typ = "ln"; Some('/')
} else if typ == "mi" { } else if file_type.is_symlink() {
typ = "fi"; Some('@')
}
};
if let Some(code) = COLOR_MAP.get(typ) {
format!(
"{}{}{}{}{}{}{}{}",
*LEFT_CODE, code, *RIGHT_CODE, name, *END_CODE, *LEFT_CODE, *RESET_CODE, *RIGHT_CODE,
)
} else { } else {
name #[cfg(unix)]
} {
} if file_type.is_socket() {
Some('=')
#[cfg(unix)] } else if file_type.is_fifo() {
macro_rules! has { Some('|')
($mode:expr, $perm:expr) => { } else if file_type.is_file() && file_is_executable(&md) {
$mode & ($perm as mode_t) != 0
};
}
#[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);
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();
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('*') Some('*')
} else { } else {
None 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);
} }
#[cfg(not(unix))]
None
}
}
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);
#[cfg(unix)]
{
if config.format != Format::Long && config.inode {
name = get_inode(metadata) + " " + &name;
}
}
if let Some(ls_colors) = &config.color {
name = color_name(&ls_colors, path, name, metadata);
}
if config.indicator_style != IndicatorStyle::None {
let sym = classify_file(metadata);
let char_opt = match config.indicator_style { let char_opt = match config.indicator_style {
IndicatorStyle::Classify => sym, IndicatorStyle::Classify => sym,
@ -1626,23 +1590,24 @@ fn display_file_name(
if let Some(c) = char_opt { if let Some(c) = char_opt {
name.push(c); name.push(c);
width += 1;
} }
} }
if config.format == Format::Long && metadata.file_type().is_symlink() { if config.format == Format::Long && metadata.file_type().is_symlink() {
if let Ok(target) = path.read_link() { if let Ok(target) = path.read_link() {
// We don't bother updating width here because it's not used for long listings // We don't bother updating width here because it's not used for long
let code = if target.exists() { "fi" } else { "mi" };
let target_name = color_name(target.to_string_lossy().to_string(), code);
name.push_str(" -> "); name.push_str(" -> ");
name.push_str(&target_name); name.push_str(&target.to_string_lossy());
} }
} }
Cell { name.into()
contents: name, }
width,
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) => style.to_ansi_term_style().paint(name).to_string(),
None => name,
} }
} }

View file

@ -18,18 +18,15 @@ pub fn err_msg(msg: &str) {
// by default stdout only flushes // by default stdout only flushes
// to console when a newline is passed. // to console when a newline is passed.
#[allow(unused_must_use)]
pub fn flush_char(c: char) { pub fn flush_char(c: char) {
print!("{}", c); print!("{}", c);
stdout().flush(); let _ = stdout().flush();
} }
#[allow(unused_must_use)]
pub fn flush_str(s: &str) { pub fn flush_str(s: &str) {
print!("{}", s); print!("{}", s);
stdout().flush(); let _ = stdout().flush();
} }
#[allow(unused_must_use)]
pub fn flush_bytes(bslice: &[u8]) { pub fn flush_bytes(bslice: &[u8]) {
stdout().write(bslice); let _ = stdout().write(bslice);
stdout().flush(); let _ = stdout().flush();
} }

View file

@ -1,5 +1,4 @@
#![allow(dead_code)] #![allow(dead_code)]
// spell-checker:ignore (change!) each's // spell-checker:ignore (change!) each's
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
@ -9,7 +8,6 @@ mod tokenize;
static NAME: &str = "printf"; static NAME: &str = "printf";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static SHORT_USAGE: &str = "printf: usage: printf [-v var] format [arguments]";
static LONGHELP_LEAD: &str = "printf static LONGHELP_LEAD: &str = "printf
USAGE: printf FORMATSTRING [ARGUMENT]... USAGE: printf FORMATSTRING [ARGUMENT]...

View file

@ -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<u8> = ret_rev.into_iter().rev().collect();
let ret: Vec<u8> = ret_rev.iter().rev().map(|x| *x).collect();
ret ret
} }
@ -102,70 +101,6 @@ pub fn arrnum_int_div_step(
remainder: rem_out, remainder: rem_out,
} }
} }
// pub struct ArrFloat {
// pub leading_zeros: u8,
// pub values: Vec<u8>,
// 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<u8>::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<u8> { pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<u8> {
let mut carry: u16 = u16::from(base_ten_int_term); let mut carry: u16 = u16::from(base_ten_int_term);
let mut rem: u16; let mut rem: u16;
@ -193,8 +128,7 @@ pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<
} }
} }
} }
#[allow(clippy::map_clone)] let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
let ret: Vec<u8> = ret_rev.iter().rev().map(|x| *x).collect();
ret ret
} }
@ -219,8 +153,7 @@ pub fn unsigned_to_arrnum(src: u16) -> Vec<u8> {
} }
// temporary needs-improvement-function // 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 // it would require a lot of addl code
// to implement this for arbitrary string input. // to implement this for arbitrary string input.
// until then, the below operates as an outline // until then, the below operates as an outline
@ -267,7 +200,6 @@ pub fn arrnum_to_str(src: &[u8], radix_def_dest: &dyn RadixDef) -> String {
str_out str_out
} }
#[allow(unused_variables)]
pub fn base_conv_str( pub fn base_conv_str(
src: &str, src: &str,
radix_def_src: &dyn RadixDef, radix_def_src: &dyn RadixDef,

View file

@ -43,45 +43,15 @@ 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) // 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. // 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( fn get_primitive_hex(
inprefix: &InPrefix, inprefix: &InPrefix,
str_in: &str, _str_in: &str,
analysis: &FloatAnalysis, _analysis: &FloatAnalysis,
last_dec_place: usize, _last_dec_place: usize,
capitalized: bool, capitalized: bool,
) -> FormatPrimitive { ) -> FormatPrimitive {
let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" })); 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. // TODO actual conversion, make sure to get back mantissa.
// for hex to hex, it's really just a matter of moving the // for hex to hex, it's really just a matter of moving the
// decimal point and calculating the mantissa by its initial // decimal point and calculating the mantissa by its initial

View file

@ -22,12 +22,11 @@ fn get_len_fprim(fprim: &FormatPrimitive) -> usize {
len len
} }
pub struct Decf { pub struct Decf;
as_num: f64,
}
impl Decf { impl Decf {
pub fn new() -> Decf { pub fn new() -> Decf {
Decf { as_num: 0.0 } Decf
} }
} }
impl Formatter for Decf { impl Formatter for Decf {

View file

@ -5,12 +5,10 @@ use super::super::format_field::FormatField;
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
pub struct Floatf { pub struct Floatf;
as_num: f64,
}
impl Floatf { impl Floatf {
pub fn new() -> Floatf { pub fn new() -> Floatf {
Floatf { as_num: 0.0 } Floatf
} }
} }
impl Formatter for Floatf { impl Formatter for Floatf {

View file

@ -11,7 +11,7 @@ use std::i64;
use std::u64; use std::u64;
pub struct Intf { pub struct Intf {
a: u32, _a: u32,
} }
// see the Intf::analyze() function below // see the Intf::analyze() function below
@ -24,7 +24,7 @@ struct IntAnalysis {
impl Intf { impl Intf {
pub fn new() -> Intf { pub fn new() -> Intf {
Intf { a: 0 } Intf { _a: 0 }
} }
// take a ref to argument string, and basic information // take a ref to argument string, and basic information
// about prefix (offset, radix, sign), and analyze string // about prefix (offset, radix, sign), and analyze string

View file

@ -5,12 +5,11 @@ use super::super::format_field::FormatField;
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
pub struct Scif { pub struct Scif;
as_num: f64,
}
impl Scif { impl Scif {
pub fn new() -> Scif { pub fn new() -> Scif {
Scif { as_num: 0.0 } Scif
} }
} }
impl Formatter for Scif { impl Formatter for Scif {

View file

@ -242,18 +242,16 @@ impl UnescapedText {
} }
} }
} }
#[allow(unused_variables)]
impl token::Tokenizer for UnescapedText { impl token::Tokenizer for UnescapedText {
fn from_it( fn from_it(
it: &mut PutBackN<Chars>, it: &mut PutBackN<Chars>,
args: &mut Peekable<Iter<String>>, _: &mut Peekable<Iter<String>>,
) -> Option<Box<dyn token::Token>> { ) -> Option<Box<dyn token::Token>> {
UnescapedText::from_it_core(it, false) UnescapedText::from_it_core(it, false)
} }
} }
#[allow(unused_variables)]
impl token::Token for UnescapedText { impl token::Token for UnescapedText {
fn print(&self, pf_args_it: &mut Peekable<Iter<String>>) { fn print(&self, _: &mut Peekable<Iter<String>>) {
cli::flush_bytes(&self.0[..]); cli::flush_bytes(&self.0[..]);
} }
} }

View file

@ -351,20 +351,18 @@ fn tokenize_default(line: &str) -> Vec<Field> {
/// Split between separators. These separators are not included in fields. /// Split between separators. These separators are not included in fields.
fn tokenize_with_separator(line: &str, separator: char) -> Vec<Field> { fn tokenize_with_separator(line: &str, separator: char) -> Vec<Field> {
let mut tokens = vec![0..0]; let mut tokens = vec![];
let mut previous_was_separator = false; let separator_indices =
for (idx, char) in line.char_indices() { line.char_indices()
if previous_was_separator { .filter_map(|(i, c)| if c == separator { Some(i) } else { None });
tokens.push(idx..0); let mut start = 0;
} for sep_idx in separator_indices {
if char == separator { tokens.push(start..sep_idx);
tokens.last_mut().unwrap().end = idx; start = sep_idx + 1;
previous_was_separator = true; }
} else { if start < line.len() {
previous_was_separator = false; tokens.push(start..line.len());
}
} }
tokens.last_mut().unwrap().end = line.len();
tokens tokens
} }
@ -1407,4 +1405,14 @@ mod tests {
vec![0..0, 1..1, 2..2, 3..9, 10..18,] 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]);
}
} }

View file

@ -91,10 +91,15 @@ fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) -> i32 {
} else { } else {
let path = Path::new(filename); let path = Path::new(filename);
if path.is_dir() || path.metadata().is_err() { if path.is_dir() || path.metadata().is_err() {
show_error!( if path.is_dir() {
"failed to open '{}' for reading: No such file or directory", show_error!("dir: read error: Invalid argument");
filename } else {
); show_error!(
"failed to open '{}' for reading: No such file or directory",
filename
);
}
exit_code = 1;
continue; continue;
} }
match File::open(path) { match File::open(path) {

View file

@ -117,6 +117,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg( .arg(
Arg::with_name(options::SLEEP_INT) Arg::with_name(options::SLEEP_INT)
.short("s") .short("s")
.takes_value(true)
.long(options::SLEEP_INT) .long(options::SLEEP_INT)
.help("Number or seconds to sleep between polling the file when running with -f"), .help("Number or seconds to sleep between polling the file when running with -f"),
) )

View file

@ -192,7 +192,8 @@ fn truncate(
} }
fn parse_size(size: &str) -> (u64, TruncateMode) { 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::Extend,
'-' => TruncateMode::Reduce, '-' => TruncateMode::Reduce,
'<' => TruncateMode::AtMost, '<' => TruncateMode::AtMost,
@ -203,9 +204,9 @@ fn parse_size(size: &str) -> (u64, TruncateMode) {
}; };
let bytes = { let bytes = {
let mut slice = if mode == TruncateMode::Reference { let mut slice = if mode == TruncateMode::Reference {
size &clean_size
} else { } else {
&size[1..] &clean_size[1..]
}; };
if slice.chars().last().unwrap().is_alphabetic() { if slice.chars().last().unwrap().is_alphabetic() {
slice = &slice[..slice.len() - 1]; slice = &slice[..slice.len() - 1];
@ -220,11 +221,11 @@ fn parse_size(size: &str) -> (u64, TruncateMode) {
Ok(num) => num, Ok(num) => num,
Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e), Err(e) => crash!(1, "'{}' is not a valid number: {}", size, e),
}; };
if size.chars().last().unwrap().is_alphabetic() { if clean_size.chars().last().unwrap().is_alphabetic() {
number *= match size.chars().last().unwrap().to_ascii_uppercase() { number *= match clean_size.chars().last().unwrap().to_ascii_uppercase() {
'B' => match size 'B' => match clean_size
.chars() .chars()
.nth(size.len() - 2) .nth(clean_size.len() - 2)
.unwrap() .unwrap()
.to_ascii_uppercase() .to_ascii_uppercase()
{ {

View file

@ -400,22 +400,29 @@ fn test_domain_socket() {
use std::thread; use std::thread;
use tempdir::TempDir; use tempdir::TempDir;
use unix_socket::UnixListener; use unix_socket::UnixListener;
use std::sync::{Barrier, Arc};
let dir = TempDir::new("unix_socket").expect("failed to create dir"); let dir = TempDir::new("unix_socket").expect("failed to create dir");
let socket_path = dir.path().join("sock"); let socket_path = dir.path().join("sock");
let listener = UnixListener::bind(&socket_path).expect("failed to create socket"); 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 thread = thread::spawn(move || {
let mut stream = listener.accept().expect("failed to accept connection").0; let mut stream = listener.accept().expect("failed to accept connection").0;
barrier2.wait();
stream stream
.write_all(b"a\tb") .write_all(b"a\tb")
.expect("failed to write test data"); .expect("failed to write test data");
}); });
new_ucmd!() let child = new_ucmd!().args(&[socket_path]).run_no_wait();
.args(&[socket_path]) barrier.wait();
.succeeds() let stdout = &child.wait_with_output().unwrap().stdout.clone();
.stdout_only("a\tb"); let output = String::from_utf8_lossy(&stdout);
assert_eq!("a\tb", output);
thread.join().unwrap(); thread.join().unwrap();
} }

View file

@ -47,7 +47,7 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) {
ucmd.arg(arg); ucmd.arg(arg);
} }
let r = ucmd.run(); let r = ucmd.run();
if !r.success { if !r.succeeded() {
println!("{}", r.stderr_str()); println!("{}", r.stderr_str());
panic!("{:?}: failed", ucmd.raw); panic!("{:?}: failed", ucmd.raw);
} }

View file

@ -4,14 +4,11 @@ use crate::common::util::*;
fn test_missing_operand() { fn test_missing_operand() {
let result = new_ucmd!().run(); let result = new_ucmd!().run();
assert_eq!( assert!(result
true, .stderr_str()
result .starts_with("error: The following required arguments were not provided"));
.stderr
.starts_with("error: The following required arguments were not provided")
);
assert_eq!(true, result.stderr.contains("<newroot>")); assert!(result.stderr_str().contains("<newroot>"));
} }
#[test] #[test]
@ -20,14 +17,11 @@ fn test_enter_chroot_fails() {
at.mkdir("jail"); at.mkdir("jail");
let result = ucmd.arg("jail").run(); let result = ucmd.arg("jail").fails();
assert_eq!( assert!(result
true, .stderr_str()
result.stderr.starts_with( .starts_with("chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"));
"chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"
)
)
} }
#[test] #[test]
@ -47,19 +41,18 @@ fn test_invalid_user_spec() {
at.mkdir("a"); at.mkdir("a");
let result = ucmd.arg("a").arg("--userspec=ARABA:").run(); let result = ucmd.arg("a").arg("--userspec=ARABA:").fails();
assert_eq!( assert!(result
true, .stderr_str()
result.stderr.starts_with("chroot: error: invalid userspec") .starts_with("chroot: error: invalid userspec"));
);
} }
#[test] #[test]
fn test_preference_of_userspec() { fn test_preference_of_userspec() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let result = scene.cmd("whoami").run(); 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. // In the CI, some server are failing to return whoami.
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; return;
@ -73,7 +66,7 @@ fn test_preference_of_userspec() {
println!("result.stdout = {}", result.stdout_str()); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_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. // In the CI, some server are failing to return id.
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; return;

View file

@ -42,13 +42,9 @@ static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt";
fn test_cp_cp() { fn test_cp_cp() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
// Invoke our binary to make the copy. // Invoke our binary to make the copy.
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .succeeds();
// Check that the exit code represents a successful copy.
assert!(result.success);
// Check the content of the destination file that was copied. // Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
@ -57,12 +53,9 @@ fn test_cp_cp() {
#[test] #[test]
fn test_cp_existing_target() { fn test_cp_existing_target() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE) .arg(TEST_EXISTING_FILE)
.run(); .succeeds();
assert!(result.success);
// Check the content of the destination file // Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
@ -74,52 +67,41 @@ fn test_cp_existing_target() {
#[test] #[test]
fn test_cp_duplicate_files() { fn test_cp_duplicate_files() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.run(); .succeeds()
.stderr_contains("specified more than once");
assert!(result.success);
assert!(result.stderr.contains("specified more than once"));
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
} }
#[test] #[test]
fn test_cp_multiple_files_target_is_file() { fn test_cp_multiple_files_target_is_file() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE) .arg(TEST_EXISTING_FILE)
.run(); .fails()
.stderr_contains("not a directory");
assert!(!result.success);
assert!(result.stderr.contains("not a directory"));
} }
#[test] #[test]
fn test_cp_directory_not_recursive() { fn test_cp_directory_not_recursive() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .fails()
.stderr_contains("omitting directory");
assert!(!result.success);
assert!(result.stderr.contains("omitting directory"));
} }
#[test] #[test]
fn test_cp_multiple_files() { fn test_cp_multiple_files() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_COPY_TO_FOLDER) .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_COPY_TO_FOLDER_FILE), "Hello, World!\n");
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\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))] #[cfg(not(macos))]
fn test_cp_recurse() { fn test_cp_recurse() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-r")
let result = ucmd
.arg("-r")
.arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .succeeds();
assert!(result.success);
// Check the content of the destination file that was copied. // Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
} }
@ -144,14 +123,10 @@ fn test_cp_recurse() {
#[test] #[test]
fn test_cp_with_dirs_t() { fn test_cp_with_dirs_t() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-t")
//using -t option
let result_to_dir_t = ucmd
.arg("-t")
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.run(); .succeeds();
assert!(result_to_dir_t.success);
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); 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 scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
//using -t option scene
let result_to_dir = scene
.ucmd() .ucmd()
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.run(); .succeeds();
assert!(result_to_dir.success);
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
let result_from_dir = scene scene
.ucmd() .ucmd()
.arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .succeeds();
assert!(result_from_dir.success);
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
} }
#[test] #[test]
fn test_cp_arg_target_directory() { fn test_cp_arg_target_directory() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("-t") .arg("-t")
.arg(TEST_COPY_TO_FOLDER) .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_COPY_TO_FOLDER_FILE), "Hello, World!\n");
} }
#[test] #[test]
fn test_cp_arg_no_target_directory() { fn test_cp_arg_no_target_directory() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg("-v") .arg("-v")
.arg("-T") .arg("-T")
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.run(); .fails()
.stderr_contains("cannot overwrite directory");
assert!(!result.success);
assert!(result.stderr.contains("cannot overwrite directory"));
} }
#[test] #[test]
fn test_cp_arg_interactive() { fn test_cp_arg_interactive() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("-i") .arg("-i")
.pipe_in("N\n") .pipe_in("N\n")
.run(); .succeeds()
.stderr_contains("Not overwriting");
assert!(result.success);
assert!(result.stderr.contains("Not overwriting"));
} }
#[test] #[test]
@ -227,39 +191,33 @@ fn test_cp_arg_link() {
use std::os::linux::fs::MetadataExt; use std::os::linux::fs::MetadataExt;
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--link") .arg("--link")
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .succeeds();
assert!(result.success);
assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2); assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2);
} }
#[test] #[test]
fn test_cp_arg_symlink() { fn test_cp_arg_symlink() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--symbolic-link") .arg("--symbolic-link")
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .succeeds();
assert!(result.success);
assert!(at.is_symlink(TEST_HELLO_WORLD_DEST)); assert!(at.is_symlink(TEST_HELLO_WORLD_DEST));
} }
#[test] #[test]
fn test_cp_arg_no_clobber() { fn test_cp_arg_no_clobber() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--no-clobber") .arg("--no-clobber")
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.run(); .succeeds();
assert!(result.success);
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); 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() { fn test_cp_arg_no_clobber_twice() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
at.touch("source.txt"); at.touch("source.txt");
let result = scene scene
.ucmd() .ucmd()
.arg("--no-clobber") .arg("--no-clobber")
.arg("source.txt") .arg("source.txt")
.arg("dest.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"), ""); assert_eq!(at.read("source.txt"), "");
at.append("source.txt", "some-content"); at.append("source.txt", "some-content");
let result = scene scene
.ucmd() .ucmd()
.arg("--no-clobber") .arg("--no-clobber")
.arg("source.txt") .arg("source.txt")
.arg("dest.txt") .arg("dest.txt")
.run(); .succeeds()
.stdout_does_not_contain("Not overwriting");
assert!(result.success);
assert_eq!(at.read("source.txt"), "some-content"); assert_eq!(at.read("source.txt"), "some-content");
// Should be empty as the "no-clobber" should keep // Should be empty as the "no-clobber" should keep
// the previous version // the previous version
assert_eq!(at.read("dest.txt"), ""); assert_eq!(at.read("dest.txt"), "");
assert!(!result.stderr.contains("Not overwriting"));
} }
#[test] #[test]
@ -311,16 +266,11 @@ fn test_cp_arg_force() {
permissions.set_readonly(true); permissions.set_readonly(true);
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap(); set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--force") .arg("--force")
.arg(TEST_HELLO_WORLD_DEST) .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"); 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); permissions.set_readonly(true);
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap(); set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--remove-destination") .arg("--remove-destination")
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .succeeds();
assert!(result.success);
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); 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() { fn test_cp_arg_backup() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--backup") .arg("--backup")
.arg(TEST_HOW_ARE_YOU_SOURCE) .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(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!( assert_eq!(
at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)), at.read(&*format!("{}~", TEST_HOW_ARE_YOU_SOURCE)),
@ -374,14 +320,12 @@ fn test_cp_arg_backup() {
fn test_cp_arg_suffix() { fn test_cp_arg_suffix() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--suffix") .arg("--suffix")
.arg(".bak") .arg(".bak")
.arg(TEST_HOW_ARE_YOU_SOURCE) .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(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!( assert_eq!(
at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)), at.read(&*format!("{}.bak", TEST_HOW_ARE_YOU_SOURCE)),
@ -391,9 +335,8 @@ fn test_cp_arg_suffix() {
#[test] #[test]
fn test_cp_deref_conflicting_options() { fn test_cp_deref_conflicting_options() {
let (_at, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("-LP")
ucmd.arg("-LP")
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.fails(); .fails();
@ -401,8 +344,7 @@ fn test_cp_deref_conflicting_options() {
#[test] #[test]
fn test_cp_deref() { fn test_cp_deref() {
let scene = TestScenario::new(util_name!()); let (at, mut ucmd) = at_and_ucmd!();
let at = &scene.fixtures;
#[cfg(not(windows))] #[cfg(not(windows))]
let _r = fs::symlink( let _r = fs::symlink(
@ -415,16 +357,12 @@ fn test_cp_deref() {
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
); );
//using -L option //using -L option
let result = scene ucmd.arg("-L")
.ucmd()
.arg("-L")
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK) .arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.arg(TEST_COPY_TO_FOLDER) .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 let path_to_new_symlink = at
.subdir .subdir
.join(TEST_COPY_TO_FOLDER) .join(TEST_COPY_TO_FOLDER)
@ -444,8 +382,7 @@ fn test_cp_deref() {
} }
#[test] #[test]
fn test_cp_no_deref() { fn test_cp_no_deref() {
let scene = TestScenario::new(util_name!()); let (at, mut ucmd) = at_and_ucmd!();
let at = &scene.fixtures;
#[cfg(not(windows))] #[cfg(not(windows))]
let _r = fs::symlink( let _r = fs::symlink(
@ -458,16 +395,12 @@ fn test_cp_no_deref() {
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
); );
//using -P option //using -P option
let result = scene ucmd.arg("-P")
.ucmd()
.arg("-P")
.arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK) .arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.arg(TEST_COPY_TO_FOLDER) .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 let path_to_new_symlink = at
.subdir .subdir
.join(TEST_COPY_TO_FOLDER) .join(TEST_COPY_TO_FOLDER)
@ -490,14 +423,10 @@ fn test_cp_strip_trailing_slashes() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
//using --strip-trailing-slashes option //using --strip-trailing-slashes option
let result = ucmd ucmd.arg("--strip-trailing-slashes")
.arg("--strip-trailing-slashes")
.arg(format!("{}/", TEST_HELLO_WORLD_SOURCE)) .arg(format!("{}/", TEST_HELLO_WORLD_SOURCE))
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .succeeds();
// Check that the exit code represents a successful copy.
assert!(result.success);
// Check the content of the destination file that was copied. // Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); 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() { fn test_cp_parents() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg("--parents")
.arg("--parents")
.arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.run(); .succeeds();
assert!(result.success);
// Check the content of the destination file that was copied.
assert_eq!( assert_eq!(
at.read(&format!( at.read(&format!(
"{}/{}", "{}/{}",
@ -528,14 +454,12 @@ fn test_cp_parents() {
fn test_cp_parents_multiple_files() { fn test_cp_parents_multiple_files() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd ucmd.arg("--parents")
.arg("--parents")
.arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.run(); .succeeds();
assert!(result.success);
assert_eq!( assert_eq!(
at.read(&format!( at.read(&format!(
"{}/{}", "{}/{}",
@ -554,20 +478,12 @@ fn test_cp_parents_multiple_files() {
#[test] #[test]
fn test_cp_parents_dest_not_directory() { fn test_cp_parents_dest_not_directory() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg("--parents") .arg("--parents")
.arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .fails()
println!("{:?}", result); .stderr_contains("with --parents, the destination must be a directory");
// Check that we did not succeed in copying.
assert!(!result.success);
assert!(result
.stderr
.contains("with --parents, the destination must be a directory"));
} }
#[test] #[test]
@ -594,18 +510,14 @@ fn test_cp_deref_folder_to_folder() {
assert!(env::set_current_dir(&cwd).is_ok()); assert!(env::set_current_dir(&cwd).is_ok());
//using -P -R option //using -P -R option
let result = scene scene
.ucmd() .ucmd()
.arg("-L") .arg("-L")
.arg("-R") .arg("-R")
.arg("-v") .arg("-v")
.arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .succeeds();
println!("cp output {}", result.stdout_str());
// Check that the exit code represents a successful copy.
assert!(result.success);
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
@ -698,18 +610,14 @@ fn test_cp_no_deref_folder_to_folder() {
assert!(env::set_current_dir(&cwd).is_ok()); assert!(env::set_current_dir(&cwd).is_ok());
//using -P -R option //using -P -R option
let result = scene scene
.ucmd() .ucmd()
.arg("-P") .arg("-P")
.arg("-R") .arg("-R")
.arg("-v") .arg("-v")
.arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .succeeds();
println!("cp output {}", result.stdout_str());
// Check that the exit code represents a successful copy.
assert!(result.success);
#[cfg(not(windows))] #[cfg(not(windows))]
{ {
@ -791,13 +699,11 @@ fn test_cp_archive() {
previous, previous,
) )
.unwrap(); .unwrap();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--archive") .arg("--archive")
.arg(TEST_HOW_ARE_YOU_SOURCE) .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(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap(); 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 creation2 = metadata2.modified().unwrap();
let scene2 = TestScenario::new("ls"); 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()); println!("ls dest {}", result.stdout_str());
assert_eq!(creation, creation2); assert_eq!(creation, creation2);
assert!(result.success);
} }
#[test] #[test]
@ -850,11 +755,10 @@ fn test_cp_archive_recursive() {
// Back to the initial cwd (breaks the other tests) // Back to the initial cwd (breaks the other tests)
assert!(env::set_current_dir(&cwd).is_ok()); assert!(env::set_current_dir(&cwd).is_ok());
let resultg = ucmd ucmd.arg("--archive")
.arg("--archive")
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .fails(); // fails for now
let scene2 = TestScenario::new("ls"); let scene2 = TestScenario::new("ls");
let result = scene2 let result = scene2
@ -865,7 +769,6 @@ fn test_cp_archive_recursive() {
println!("ls dest {}", result.stdout_str()); println!("ls dest {}", result.stdout_str());
let scene2 = TestScenario::new("ls");
let result = scene2 let result = scene2
.cmd("ls") .cmd("ls")
.arg("-al") .arg("-al")
@ -910,9 +813,6 @@ fn test_cp_archive_recursive() {
.join("2.link") .join("2.link")
.to_string_lossy() .to_string_lossy()
)); ));
// fails for now
assert!(resultg.success);
} }
#[test] #[test]
@ -928,13 +828,11 @@ fn test_cp_preserve_timestamps() {
previous, previous,
) )
.unwrap(); .unwrap();
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--preserve=timestamps") .arg("--preserve=timestamps")
.arg(TEST_HOW_ARE_YOU_SOURCE) .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(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap(); 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()); println!("ls dest {}", result.stdout_str());
assert_eq!(creation, creation2); assert_eq!(creation, creation2);
assert!(result.success);
} }
#[test] #[test]
@ -966,13 +863,11 @@ fn test_cp_dont_preserve_timestamps() {
.unwrap(); .unwrap();
sleep(Duration::from_secs(3)); sleep(Duration::from_secs(3));
let result = ucmd ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--no-preserve=timestamps") .arg("--no-preserve=timestamps")
.arg(TEST_HOW_ARE_YOU_SOURCE) .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(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap(); 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 // Some margins with time check
assert!(res.as_secs() > 3595); assert!(res.as_secs() > 3595);
assert!(res.as_secs() < 3605); assert!(res.as_secs() < 3605);
assert!(result.success);
} }
#[test] #[test]
@ -1017,7 +911,7 @@ fn test_cp_one_file_system() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
// Test must be run as root (or with `sudo -E`) // 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; return;
} }
@ -1042,17 +936,16 @@ fn test_cp_one_file_system() {
at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE); at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE);
// Begin testing -x flag // Begin testing -x flag
let result = scene scene
.ucmd() .ucmd()
.arg("-rx") .arg("-rx")
.arg(TEST_MOUNT_COPY_FROM_FOLDER) .arg(TEST_MOUNT_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .succeeds();
// Ditch the mount before the asserts // Ditch the mount before the asserts
scene.cmd("umount").arg(mountpoint_path).succeeds(); scene.cmd("umount").arg(mountpoint_path).succeeds();
assert!(result.success);
assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE)); assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE));
// Check if the other files were copied from the source folder hirerarchy // Check if the other files were copied from the source folder hirerarchy
for entry in WalkDir::new(at_src.as_string()) { for entry in WalkDir::new(at_src.as_string()) {

View file

@ -7,174 +7,147 @@ use rust_users::*;
#[test] #[test]
fn test_date_email() { fn test_date_email() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("--rfc-email").succeeds();
let result = ucmd.arg("--rfc-email").run();
assert!(result.success);
} }
#[test] #[test]
fn test_date_email2() { fn test_date_email2() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("-R").succeeds();
let result = ucmd.arg("-R").run();
assert!(result.success);
} }
#[test] #[test]
fn test_date_rfc_3339() { fn test_date_rfc_3339() {
let scene = TestScenario::new(util_name!()); 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 // 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])))"; scene
let re = Regex::new(rfc_regexp).unwrap(); .ucmd()
assert!(re.is_match(&result.stdout_str().trim())); .arg("--rfc-3339=ns")
.succeeds()
.stdout_matches(&re);
result = scene.ucmd().arg("--rfc-3339=seconds").succeeds(); scene
.ucmd()
// Check that the output matches the regexp .arg("--rfc-3339=seconds")
let re = Regex::new(rfc_regexp).unwrap(); .succeeds()
assert!(re.is_match(&result.stdout_str().trim())); .stdout_matches(&re);
} }
#[test] #[test]
fn test_date_rfc_8601() { fn test_date_rfc_8601() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("--iso-8601=ns").succeeds();
let result = ucmd.arg("--iso-8601=ns").run();
assert!(result.success);
} }
#[test] #[test]
fn test_date_rfc_8601_second() { fn test_date_rfc_8601_second() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("--iso-8601=second").succeeds();
let result = ucmd.arg("--iso-8601=second").run();
assert!(result.success);
} }
#[test] #[test]
fn test_date_utc() { fn test_date_utc() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("--utc").succeeds();
let result = ucmd.arg("--utc").run();
assert!(result.success);
} }
#[test] #[test]
fn test_date_universal() { fn test_date_universal() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("--universal").succeeds();
let result = ucmd.arg("--universal").run();
assert!(result.success);
} }
#[test] #[test]
fn test_date_format_y() { fn test_date_format_y() {
let scene = TestScenario::new(util_name!()); 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(); 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(); re = Regex::new(r"^\d{2}$").unwrap();
assert!(re.is_match(&result.stdout_str().trim())); scene.ucmd().arg("+%y").succeeds().stdout_matches(&re);
} }
#[test] #[test]
fn test_date_format_m() { fn test_date_format_m() {
let scene = TestScenario::new(util_name!()); 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(); 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(); re = Regex::new(r"^\d{2}$").unwrap();
assert!(re.is_match(&result.stdout_str().trim())); scene.ucmd().arg("+%m").succeeds().stdout_matches(&re);
} }
#[test] #[test]
fn test_date_format_day() { fn test_date_format_day() {
let scene = TestScenario::new(util_name!()); 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(); let mut 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("+%A").succeeds();
assert!(result.success);
re = Regex::new(r"\S+").unwrap(); 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(); re = Regex::new(r"^\d{1}$").unwrap();
assert!(re.is_match(&result.stdout_str().trim())); scene.ucmd().arg("+%u").succeeds().stdout_matches(&re);
} }
#[test] #[test]
fn test_date_format_full_day() { 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(); 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] #[test]
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(all(unix, not(target_os = "macos")))]
fn test_date_set_valid() { fn test_date_set_valid() {
if get_effective_uid() == 0 { if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg("--set") .arg("--set")
.arg("2020-03-12 13:30:00+08:00") .arg("2020-03-12 13:30:00+08:00")
.succeeds(); .succeeds()
result.no_stdout().no_stderr(); .no_stdout()
.no_stderr();
} }
} }
#[test] #[test]
#[cfg(any(windows, all(unix, not(target_os = "macos"))))] #[cfg(any(windows, all(unix, not(target_os = "macos"))))]
fn test_date_set_invalid() { fn test_date_set_invalid() {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg("--set").arg("123abcd").fails();
let result = ucmd.arg("--set").arg("123abcd").fails(); result.no_stdout();
let result = result.no_stdout(); assert!(result.stderr_str().starts_with("date: invalid date "));
assert!(result.stderr.starts_with("date: invalid date "));
} }
#[test] #[test]
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(all(unix, not(target_os = "macos")))]
fn test_date_set_permissions_error() { fn test_date_set_permissions_error() {
if !(get_effective_uid() == 0 || is_wsl()) { if !(get_effective_uid() == 0 || is_wsl()) {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!()
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); .arg("--set")
let result = result.no_stdout(); .arg("2020-03-11 21:45:00+08:00")
assert!(result.stderr.starts_with("date: cannot set date: ")); .fails();
result.no_stdout();
assert!(result.stderr_str().starts_with("date: cannot set date: "));
} }
} }
#[test] #[test]
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn test_date_set_mac_unavailable() { fn test_date_set_mac_unavailable() {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!()
let result = ucmd.arg("--set").arg("2020-03-11 21:45:00+08:00").fails(); .arg("--set")
let result = result.no_stdout(); .arg("2020-03-11 21:45:00+08:00")
.fails();
result.no_stdout();
assert!(result assert!(result
.stderr .stderr_str()
.starts_with("date: setting the date is not supported by macOS")); .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. /// TODO: expected to fail currently; change to succeeds() when required.
fn test_date_set_valid_2() { fn test_date_set_valid_2() {
if get_effective_uid() == 0 { if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!()
let result = ucmd
.arg("--set") .arg("--set")
.arg("Sat 20 Mar 2021 14:53:01 AWST") .arg("Sat 20 Mar 2021 14:53:01 AWST")
.fails(); .fails();
let result = result.no_stdout(); result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date ")); 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. /// TODO: expected to fail currently; change to succeeds() when required.
fn test_date_set_valid_3() { fn test_date_set_valid_3() {
if get_effective_uid() == 0 { if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!()
let result = ucmd
.arg("--set") .arg("--set")
.arg("Sat 20 Mar 2021 14:53:01") // Local timezone .arg("Sat 20 Mar 2021 14:53:01") // Local timezone
.fails(); .fails();
let result = result.no_stdout(); result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date ")); 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. /// TODO: expected to fail currently; change to succeeds() when required.
fn test_date_set_valid_4() { fn test_date_set_valid_4() {
if get_effective_uid() == 0 { if get_effective_uid() == 0 {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!()
let result = ucmd
.arg("--set") .arg("--set")
.arg("2020-03-11 21:45:00") // Local timezone .arg("2020-03-11 21:45:00") // Local timezone
.fails(); .fails();
let result = result.no_stdout(); result.no_stdout();
assert!(result.stderr.starts_with("date: invalid date ")); assert!(result.stderr_str().starts_with("date: invalid date "));
} }
} }

View file

@ -2,30 +2,22 @@ use crate::common::util::*;
#[test] #[test]
fn test_df_compatible_no_size_arg() { fn test_df_compatible_no_size_arg() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("-a").succeeds();
let result = ucmd.arg("-a").run();
assert!(result.success);
} }
#[test] #[test]
fn test_df_compatible() { fn test_df_compatible() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("-ah").succeeds();
let result = ucmd.arg("-ah").run();
assert!(result.success);
} }
#[test] #[test]
fn test_df_compatible_type() { fn test_df_compatible_type() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("-aT").succeeds();
let result = ucmd.arg("-aT").run();
assert!(result.success);
} }
#[test] #[test]
fn test_df_compatible_si() { fn test_df_compatible_si() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg("-aH").succeeds();
let result = ucmd.arg("-aH").run();
assert!(result.success);
} }
// ToDO: more tests... // ToDO: more tests...

View file

@ -220,26 +220,36 @@ fn test_du_time() {
.arg("date_test") .arg("date_test")
.succeeds() .succeeds()
.stdout_only("0\t2015-05-15 00:00\tdate_test\n"); .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(not(target_os = "windows"))]
#[cfg(feature = "chmod")] #[cfg(feature = "chmod")]
#[test] #[test]
fn test_du_no_permission() { 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(); at.mkdir_all(SUB_DIR_LINKS);
let result = ts.ucmd().arg(SUB_DIR_LINKS).succeeds();
ts.ccmd("chmod").arg("+r").arg(SUB_DIR_LINKS).run(); scene.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds();
assert_eq!( let result = scene.ucmd().arg(SUB_DIR_LINKS).run(); // TODO: replace with ".fails()" once `du` is fixed
result.stderr_str(), result.stderr_contains(
"du: cannot read directory subdir/links: Permission denied (os error 13)\n" "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()); _du_no_permission(result.stdout_str());
} }

View file

@ -140,8 +140,11 @@ fn test_unset_variable() {
#[test] #[test]
fn test_fail_null_with_program() { fn test_fail_null_with_program() {
let out = new_ucmd!().arg("--null").arg("cd").fails().stderr; new_ucmd!()
assert!(out.contains("cannot specify --null (-0) with command")); .arg("--null")
.arg("cd")
.fails()
.stderr_contains("cannot specify --null (-0) with command");
} }
#[cfg(not(windows))] #[cfg(not(windows))]

View file

@ -29,7 +29,7 @@ fn test_fmt_w_too_big() {
.run(); .run();
//.stdout_is_fixture("call_graph.expected"); //.stdout_is_fixture("call_graph.expected");
assert_eq!( assert_eq!(
result.stderr.trim(), result.stderr_str().trim(),
"fmt: error: invalid width: '2501': Numerical result out of range" "fmt: error: invalid width: '2501': Numerical result out of range"
); );
} }

View file

@ -10,7 +10,7 @@ fn test_groups() {
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; return;
} }
assert!(result.success); result.success();
assert!(!result.stdout_str().trim().is_empty()); assert!(!result.stdout_str().trim().is_empty());
} }
@ -30,16 +30,12 @@ fn test_groups_arg() {
println!("result.stdout = {}", result.stdout_str()); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str()); println!("result.stderr = {}", result.stderr_str());
assert!(result.success); result.success();
assert!(!result.stdout_str().is_empty()); assert!(!result.stdout_str().is_empty());
let username = result.stdout_str().trim(); let username = result.stdout_str().trim();
// call groups with the user name to check that we // call groups with the user name to check that we
// are getting something // are getting something
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().arg(username).succeeds();
let result = ucmd.arg(username).run();
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
assert!(result.success);
assert!(!result.stdout_str().is_empty()); assert!(!result.stdout_str().is_empty());
} }

View file

@ -156,14 +156,10 @@ fn test_negative_zero_bytes() {
} }
#[test] #[test]
fn test_no_such_file_or_directory() { fn test_no_such_file_or_directory() {
let result = new_ucmd!().arg("no_such_file.toml").run(); new_ucmd!()
.arg("no_such_file.toml")
assert_eq!( .fails()
true, .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory");
result
.stderr
.contains("cannot open 'no_such_file.toml' for reading: No such file or directory")
)
} }
// there was a bug not caught by previous tests // there was a bug not caught by previous tests

View file

@ -11,12 +11,10 @@ use std::thread::sleep;
fn test_install_help() { fn test_install_help() {
let (_, mut ucmd) = at_and_ucmd!(); let (_, mut ucmd) = at_and_ucmd!();
assert!(ucmd ucmd.arg("--help")
.arg("--help")
.succeeds() .succeeds()
.no_stderr() .no_stderr()
.stdout .stdout_contains("FLAGS:");
.contains("FLAGS:"));
} }
#[test] #[test]
@ -59,13 +57,11 @@ fn test_install_failing_not_dir() {
at.touch(file1); at.touch(file1);
at.touch(file2); at.touch(file2);
at.touch(file3); at.touch(file3);
assert!(ucmd ucmd.arg(file1)
.arg(file1)
.arg(file2) .arg(file2)
.arg(file3) .arg(file3)
.fails() .fails()
.stderr .stderr_contains("not a directory");
.contains("not a directory"));
} }
#[test] #[test]
@ -77,13 +73,11 @@ fn test_install_unimplemented_arg() {
at.touch(file); at.touch(file);
at.mkdir(dir); at.mkdir(dir);
assert!(ucmd ucmd.arg(context_arg)
.arg(context_arg)
.arg(file) .arg(file)
.arg(dir) .arg(dir)
.fails() .fails()
.stderr .stderr_contains("Unimplemented");
.contains("Unimplemented"));
assert!(!at.file_exists(&format!("{}/{}", dir, file))); assert!(!at.file_exists(&format!("{}/{}", dir, file)));
} }
@ -231,13 +225,11 @@ fn test_install_mode_failing() {
at.touch(file); at.touch(file);
at.mkdir(dir); at.mkdir(dir);
assert!(ucmd ucmd.arg(file)
.arg(file)
.arg(dir) .arg(dir)
.arg(mode_arg) .arg(mode_arg)
.fails() .fails()
.stderr .stderr_contains("Invalid mode string: invalid digit found in string");
.contains("Invalid mode string: invalid digit found in string"));
let dest_file = &format!("{}/{}", dir, file); let dest_file = &format!("{}/{}", dir, file);
assert!(at.file_exists(file)); assert!(at.file_exists(file));
@ -336,7 +328,7 @@ fn test_install_target_new_file_with_owner() {
.arg(format!("{}/{}", dir, file)) .arg(format!("{}/{}", dir, file))
.run(); .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. // In the CI, some server are failing to return the user id.
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; return;
@ -619,35 +611,27 @@ fn test_install_and_strip_with_program() {
#[test] #[test]
#[cfg(not(windows))] #[cfg(not(windows))]
fn test_install_and_strip_with_invalid_program() { fn test_install_and_strip_with_invalid_program() {
let scene = TestScenario::new(util_name!()); new_ucmd!()
let stderr = scene
.ucmd()
.arg("-s") .arg("-s")
.arg("--strip-program") .arg("--strip-program")
.arg("/bin/date") .arg("/bin/date")
.arg(strip_source_file()) .arg(strip_source_file())
.arg(STRIP_TARGET_FILE) .arg(STRIP_TARGET_FILE)
.fails() .fails()
.stderr; .stderr_contains("strip program failed");
assert!(stderr.contains("strip program failed"));
} }
#[test] #[test]
#[cfg(not(windows))] #[cfg(not(windows))]
fn test_install_and_strip_with_non_existent_program() { fn test_install_and_strip_with_non_existent_program() {
let scene = TestScenario::new(util_name!()); new_ucmd!()
let stderr = scene
.ucmd()
.arg("-s") .arg("-s")
.arg("--strip-program") .arg("--strip-program")
.arg("/usr/bin/non_existent_program") .arg("/usr/bin/non_existent_program")
.arg(strip_source_file()) .arg(strip_source_file())
.arg(STRIP_TARGET_FILE) .arg(STRIP_TARGET_FILE)
.fails() .fails()
.stderr; .stderr_contains("No such file or directory");
assert!(stderr.contains("No such file or directory"));
} }
#[test] #[test]

View file

@ -299,13 +299,11 @@ fn test_symlink_overwrite_dir_fail() {
at.touch(path_a); at.touch(path_a);
at.mkdir(path_b); at.mkdir(path_b);
assert!( assert!(!ucmd
ucmd.args(&["-s", "-T", path_a, path_b]) .args(&["-s", "-T", path_a, path_b])
.fails() .fails()
.stderr .stderr_str()
.len() .is_empty());
> 0
);
} }
#[test] #[test]
@ -358,7 +356,11 @@ fn test_symlink_target_only() {
at.mkdir(dir); 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] #[test]

View file

@ -103,6 +103,14 @@ fn test_ls_width() {
.succeeds() .succeeds()
.stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); .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::<Vec<_>>())
.fails()
.stderr_only("ls: error: invalid line width: 1a");
}
} }
#[test] #[test]
@ -621,20 +629,27 @@ fn test_ls_recursive() {
result.stdout_contains(&"a\\b:\nb"); result.stdout_contains(&"a\\b:\nb");
} }
#[cfg(unix)]
#[test] #[test]
fn test_ls_ls_color() { fn test_ls_color() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
at.mkdir("a"); 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.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"); at.touch("test-color");
let a_with_colors = "\x1b[01;34ma\x1b[0m"; let a_with_colors = "\x1b[1;34ma\x1b[0m";
let z_with_colors = "\x1b[01;34mz\x1b[0m"; let z_with_colors = "\x1b[1;34mz\x1b[0m";
let nested_dir_with_colors = "\x1b[01;34mnested_dir\x1b[0m"; let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m";
// Color is disabled by default // Color is disabled by default
let result = scene.ucmd().succeeds(); let result = scene.ucmd().succeeds();
@ -670,14 +685,6 @@ fn test_ls_ls_color() {
.succeeds() .succeeds()
.stdout_contains(nested_dir_with_colors); .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 // No output
scene scene
.ucmd() .ucmd()
@ -817,7 +824,7 @@ fn test_ls_indicator_style() {
let options = vec!["classify", "file-type", "slash"]; let options = vec!["classify", "file-type", "slash"];
for opt in options { for opt in options {
// Verify that classify and file-type both contain indicators for symlinks. // Verify that classify and file-type both contain indicators for symlinks.
let result = scene scene
.ucmd() .ucmd()
.arg(format!("--indicator-style={}", opt)) .arg(format!("--indicator-style={}", opt))
.succeeds() .succeeds()
@ -827,7 +834,7 @@ fn test_ls_indicator_style() {
// Same test as above, but with the alternate flags. // Same test as above, but with the alternate flags.
let options = vec!["--classify", "--file-type", "-p"]; let options = vec!["--classify", "--file-type", "-p"];
for opt in options { for opt in options {
let result = scene scene
.ucmd() .ucmd()
.arg(format!("{}", opt)) .arg(format!("{}", opt))
.succeeds() .succeeds()
@ -838,7 +845,7 @@ fn test_ls_indicator_style() {
let options = vec!["classify", "file-type"]; let options = vec!["classify", "file-type"];
for opt in options { for opt in options {
// Verify that classify and file-type both contain indicators for symlinks. // Verify that classify and file-type both contain indicators for symlinks.
let result = scene scene
.ucmd() .ucmd()
.arg(format!("--indicator-style={}", opt)) .arg(format!("--indicator-style={}", opt))
.succeeds() .succeeds()
@ -962,7 +969,7 @@ fn test_ls_hidden_windows() {
let result = scene.ucmd().succeeds(); let result = scene.ucmd().succeeds();
assert!(!result.stdout_str().contains(file)); 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] #[test]

View file

@ -19,8 +19,7 @@ fn test_create_one_fifo_with_invalid_mode() {
.arg("-m") .arg("-m")
.arg("invalid") .arg("invalid")
.fails() .fails()
.stderr .stderr_contains("invalid mode");
.contains("invalid mode");
} }
#[test] #[test]

View file

@ -2,18 +2,15 @@ use crate::common::util::*;
#[test] #[test]
fn test_more_no_arg() { fn test_more_no_arg() {
let (_, mut ucmd) = at_and_ucmd!(); // stderr = more: Reading from stdin isn't supported yet.
let result = ucmd.run(); new_ucmd!().fails();
assert!(!result.success);
} }
#[test] #[test]
fn test_more_dir_arg() { fn test_more_dir_arg() {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!().arg(".").run();
ucmd.arg("."); result.failure();
let result = ucmd.run();
assert!(!result.success);
const EXPECTED_ERROR_MESSAGE: &str = const EXPECTED_ERROR_MESSAGE: &str =
"more: '.' is a directory.\nTry 'more --help' for more information."; "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);
} }

View file

@ -476,16 +476,9 @@ fn test_mv_overwrite_nonempty_dir() {
// GNU: "mv: cannot move a to b: Directory not empty" // GNU: "mv: cannot move a to b: Directory not empty"
// Verbose output for the move should not be shown on failure // Verbose output for the move should not be shown on failure
assert!( let result = ucmd.arg("-vT").arg(dir_a).arg(dir_b).fails();
ucmd.arg("-vT") result.no_stdout();
.arg(dir_a) assert!(!result.stderr_str().is_empty());
.arg(dir_b)
.fails()
.no_stdout()
.stderr
.len()
> 0
);
assert!(at.dir_exists(dir_a)); assert!(at.dir_exists(dir_a));
assert!(at.dir_exists(dir_b)); assert!(at.dir_exists(dir_b));
@ -526,15 +519,15 @@ fn test_mv_errors() {
// $ mv -T -t a b // $ mv -T -t a b
// mv: cannot combine --target-directory (-t) and --no-target-directory (-T) // mv: cannot combine --target-directory (-t) and --no-target-directory (-T)
let result = scene scene
.ucmd() .ucmd()
.arg("-T") .arg("-T")
.arg("-t") .arg("-t")
.arg(dir) .arg(dir)
.arg(file_a) .arg(file_a)
.arg(file_b) .arg(file_b)
.fails(); .fails()
assert!(result.stderr.contains("cannot be used with")); .stderr_contains("cannot be used with");
// $ at.touch file && at.mkdir dir // $ at.touch file && at.mkdir dir
// $ mv -T file dir // $ mv -T file dir
@ -553,7 +546,13 @@ fn test_mv_errors() {
// $ at.mkdir dir && at.touch file // $ at.mkdir dir && at.touch file
// $ mv dir file // $ mv dir file
// err == mv: cannot overwrite non-directory file with directory dir // 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] #[test]

View file

@ -16,7 +16,7 @@ fn test_negative_adjustment() {
let res = new_ucmd!().args(&["-n", "-1", "true"]).run(); let res = new_ucmd!().args(&["-n", "-1", "true"]).run();
assert!(res assert!(res
.stderr .stderr_str()
.starts_with("nice: warning: setpriority: Permission denied")); .starts_with("nice: warning: setpriority: Permission denied"));
} }

View file

@ -9,35 +9,28 @@ fn test_output_is_random_permutation() {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let result = new_ucmd!() let result = new_ucmd!().pipe_in(input.as_bytes()).succeeds();
.pipe_in(input.as_bytes()) result.no_stderr();
.succeeds()
.no_stderr()
.stdout
.clone();
let mut result_seq: Vec<i32> = result let mut result_seq: Vec<i32> = result
.stdout_str()
.split("\n") .split("\n")
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.parse().unwrap()) .map(|x| x.parse().unwrap())
.collect(); .collect();
result_seq.sort(); 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"); assert_eq!(result_seq, input_seq, "Output is not a permutation");
} }
#[test] #[test]
fn test_zero_termination() { fn test_zero_termination() {
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result = new_ucmd!() let result = new_ucmd!().arg("-z").arg("-i1-10").succeeds();
.arg("-z") result.no_stderr();
.arg("-i1-10")
.succeeds()
.no_stderr()
.stdout
.clone();
let mut result_seq: Vec<i32> = result let mut result_seq: Vec<i32> = result
.stdout_str()
.split("\0") .split("\0")
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.parse().unwrap()) .map(|x| x.parse().unwrap())
@ -57,12 +50,11 @@ fn test_echo() {
.map(|x| x.to_string()) .map(|x| x.to_string())
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
) )
.succeeds() .succeeds();
.no_stderr() result.no_stderr();
.stdout
.clone();
let mut result_seq: Vec<i32> = result let mut result_seq: Vec<i32> = result
.stdout_str()
.split("\n") .split("\n")
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.parse().unwrap()) .map(|x| x.parse().unwrap())
@ -84,12 +76,11 @@ fn test_head_count() {
let result = new_ucmd!() let result = new_ucmd!()
.args(&["-n", &repeat_limit.to_string()]) .args(&["-n", &repeat_limit.to_string()])
.pipe_in(input.as_bytes()) .pipe_in(input.as_bytes())
.succeeds() .succeeds();
.no_stderr() result.no_stderr();
.stdout
.clone();
let mut result_seq: Vec<i32> = result let mut result_seq: Vec<i32> = result
.stdout_str()
.split("\n") .split("\n")
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.parse().unwrap()) .map(|x| x.parse().unwrap())
@ -99,7 +90,7 @@ fn test_head_count() {
assert!( assert!(
result_seq.iter().all(|x| input_seq.contains(x)), result_seq.iter().all(|x| input_seq.contains(x)),
"Output includes element not from input: {}", "Output includes element not from input: {}",
result result.stdout_str()
) )
} }
@ -117,12 +108,11 @@ fn test_repeat() {
.arg("-r") .arg("-r")
.args(&["-n", &repeat_limit.to_string()]) .args(&["-n", &repeat_limit.to_string()])
.pipe_in(input.as_bytes()) .pipe_in(input.as_bytes())
.succeeds() .succeeds();
.no_stderr() result.no_stderr();
.stdout
.clone();
let result_seq: Vec<i32> = result let result_seq: Vec<i32> = result
.stdout_str()
.split("\n") .split("\n")
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.parse().unwrap()) .map(|x| x.parse().unwrap())
@ -146,14 +136,11 @@ fn test_repeat() {
fn test_file_input() { fn test_file_input() {
let expected_seq = vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; let expected_seq = vec![11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
let result = new_ucmd!() let result = new_ucmd!().arg("file_input.txt").succeeds();
.arg("file_input.txt") result.no_stderr();
.succeeds()
.no_stderr()
.stdout
.clone();
let mut result_seq: Vec<i32> = result let mut result_seq: Vec<i32> = result
.stdout_str()
.split("\n") .split("\n")
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.parse().unwrap()) .map(|x| x.parse().unwrap())
@ -164,52 +151,50 @@ fn test_file_input() {
#[test] #[test]
fn test_shuf_echo_and_input_range_not_allowed() { fn test_shuf_echo_and_input_range_not_allowed() {
let result = new_ucmd!().args(&["-e", "0", "-i", "0-2"]).run(); new_ucmd!()
.args(&["-e", "0", "-i", "0-2"])
assert!(!result.success); .fails()
assert!(result .stderr_contains(
.stderr "The argument '--input-range <LO-HI>' cannot be used with '--echo <ARG>...'",
.contains("The argument '--input-range <LO-HI>' cannot be used with '--echo <ARG>...'")); );
} }
#[test] #[test]
fn test_shuf_input_range_and_file_not_allowed() { fn test_shuf_input_range_and_file_not_allowed() {
let result = new_ucmd!().args(&["-i", "0-9", "file"]).run(); new_ucmd!()
.args(&["-i", "0-9", "file"])
assert!(!result.success); .fails()
assert!(result .stderr_contains("The argument '<file>' cannot be used with '--input-range <LO-HI>'");
.stderr
.contains("The argument '<file>' cannot be used with '--input-range <LO-HI>'"));
} }
#[test] #[test]
fn test_shuf_invalid_input_range_one() { fn test_shuf_invalid_input_range_one() {
let result = new_ucmd!().args(&["-i", "0"]).run(); new_ucmd!()
.args(&["-i", "0"])
assert!(!result.success); .fails()
assert!(result.stderr.contains("invalid input range")); .stderr_contains("invalid input range");
} }
#[test] #[test]
fn test_shuf_invalid_input_range_two() { fn test_shuf_invalid_input_range_two() {
let result = new_ucmd!().args(&["-i", "a-9"]).run(); new_ucmd!()
.args(&["-i", "a-9"])
assert!(!result.success); .fails()
assert!(result.stderr.contains("invalid input range: 'a'")); .stderr_contains("invalid input range: 'a'");
} }
#[test] #[test]
fn test_shuf_invalid_input_range_three() { fn test_shuf_invalid_input_range_three() {
let result = new_ucmd!().args(&["-i", "0-b"]).run(); new_ucmd!()
.args(&["-i", "0-b"])
assert!(!result.success); .fails()
assert!(result.stderr.contains("invalid input range: 'b'")); .stderr_contains("invalid input range: 'b'");
} }
#[test] #[test]
fn test_shuf_invalid_input_line_count() { fn test_shuf_invalid_input_line_count() {
let result = new_ucmd!().args(&["-n", "a"]).run(); new_ucmd!()
.args(&["-n", "a"])
assert!(!result.success); .fails()
assert!(result.stderr.contains("invalid line count: 'a'")); .stderr_contains("invalid line count: 'a'");
} }

View file

@ -608,3 +608,12 @@ fn test_dictionary_and_nonprinting_conflicts() {
} }
} }
} }
#[test]
fn test_trailing_separator() {
new_ucmd!()
.args(&["-t", "x", "-k", "1,1"])
.pipe_in("aax\naaa\n")
.succeeds()
.stdout_is("aax\naaa\n");
}

View file

@ -337,5 +337,5 @@ fn expected_result(args: &[&str]) -> String {
.env("LANGUAGE", "C") .env("LANGUAGE", "C")
.args(args) .args(args)
.run() .run()
.stdout .stdout_move_str()
} }

View file

@ -52,18 +52,19 @@ fn test_single_non_newline_separator_before() {
#[test] #[test]
fn test_invalid_input() { fn test_invalid_input() {
let (_, mut ucmd) = at_and_ucmd!(); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
ucmd.arg("b") scene
.run() .ucmd()
.stderr .arg("b")
.contains("tac: error: failed to open 'b' for reading"); .fails()
.stderr_contains("failed to open 'b' for reading: No such file or directory");
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a"); at.mkdir("a");
ucmd.arg("a") scene
.run() .ucmd()
.stderr .arg("a")
.contains("tac: error: failed to read 'a'"); .fails()
.stderr_contains("dir: read error: Invalid argument");
} }

View file

@ -343,3 +343,12 @@ fn test_negative_indexing() {
assert_eq!(positive_lines_index.stdout(), negative_lines_index.stdout()); assert_eq!(positive_lines_index.stdout(), negative_lines_index.stdout());
assert_eq!(positive_bytes_index.stdout(), negative_bytes_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();
}

View file

@ -120,19 +120,15 @@ fn test_truncate_with_set1_shorter_than_set2() {
#[test] #[test]
fn missing_args_fails() { fn missing_args_fails() {
let (_, mut ucmd) = at_and_ucmd!(); let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd.run(); ucmd.fails().stderr_contains("missing operand");
assert!(!result.success);
assert!(result.stderr.contains("missing operand"));
} }
#[test] #[test]
fn missing_required_second_arg_fails() { fn missing_required_second_arg_fails() {
let (_, mut ucmd) = at_and_ucmd!(); let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd.args(&["foo"]).run(); ucmd.args(&["foo"])
.fails()
assert!(!result.success); .stderr_contains("missing operand after");
assert!(result.stderr.contains("missing operand after"));
} }
#[test] #[test]

View file

@ -53,6 +53,16 @@ fn test_decrease_file_size() {
assert!(file.seek(SeekFrom::Current(0)).unwrap() == 6); 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] #[test]
fn test_failed() { fn test_failed() {
new_ucmd!().fails(); new_ucmd!().fails();
@ -69,3 +79,4 @@ fn test_failed_incorrect_arg() {
let (_at, mut ucmd) = at_and_ucmd!(); let (_at, mut ucmd) = at_and_ucmd!();
ucmd.args(&["-s", "+5A", TFILE1]).fails(); ucmd.args(&["-s", "+5A", TFILE1]).fails();
} }

View file

@ -18,33 +18,35 @@ fn test_sort_self_loop() {
#[test] #[test]
fn test_no_such_file() { fn test_no_such_file() {
let result = new_ucmd!().arg("invalid_file_txt").run(); new_ucmd!()
.arg("invalid_file_txt")
assert_eq!(true, result.stderr.contains("No such file or directory")); .fails()
.stderr_contains("No such file or directory");
} }
#[test] #[test]
fn test_version_flag() { fn test_version_flag() {
let version_short = new_ucmd!().arg("-V").run(); let version_short = new_ucmd!().arg("-V").succeeds();
let version_long = new_ucmd!().arg("--version").run(); 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] #[test]
fn test_help_flag() { fn test_help_flag() {
let help_short = new_ucmd!().arg("-h").run(); let help_short = new_ucmd!().arg("-h").succeeds();
let help_long = new_ucmd!().arg("--help").run(); 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] #[test]
fn test_multiple_arguments() { fn test_multiple_arguments() {
let result = new_ucmd!() new_ucmd!()
.arg("call_graph.txt") .arg("call_graph.txt")
.arg("invalid_file.txt") .arg("invalid_file")
.run(); .fails()
.stderr_contains(
assert_eq!(true, result.stderr.contains("error: Found argument 'invalid_file.txt' which wasn't expected, or isn't valid in this context")) "Found argument 'invalid_file' which wasn't expected, or isn't valid in this context",
);
} }

View file

@ -23,7 +23,7 @@ fn test_heading() {
for opt in vec!["-H"] { for opt in vec!["-H"] {
// allow whitespace variation // allow whitespace variation
// * minor whitespace differences occur between platform built-in outputs; specifically number of TABs between "TIME" and "COMMENT" may be variant // * 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); let expect = expected_result(opt);
println!("actual: {:?}", actual); println!("actual: {:?}", actual);
println!("expect: {:?}", expect); println!("expect: {:?}", expect);
@ -80,5 +80,5 @@ fn expected_result(arg: &str) -> String {
.env("LANGUAGE", "C") .env("LANGUAGE", "C")
.args(&[arg]) .args(&[arg])
.run() .run()
.stdout .stdout_move_str()
} }

View file

@ -74,11 +74,11 @@ pub struct CmdResult {
code: Option<i32>, code: Option<i32>,
/// zero-exit from running the Command? /// zero-exit from running the Command?
/// see [`success`] /// see [`success`]
pub success: bool, success: bool,
/// captured standard output after running the Command /// captured standard output after running the Command
pub stdout: String, stdout: String,
/// captured standard error after running the Command /// captured standard error after running the Command
pub stderr: String, stderr: String,
} }
impl CmdResult { impl CmdResult {
@ -329,14 +329,14 @@ impl CmdResult {
} }
pub fn stdout_matches(&self, regex: &regex::Regex) -> &CmdResult { pub fn stdout_matches(&self, regex: &regex::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()) panic!("Stdout does not match regex:\n{}", self.stdout_str())
} }
self self
} }
pub fn stdout_does_not_match(&self, regex: &regex::Regex) -> &CmdResult { pub fn stdout_does_not_match(&self, regex: &regex::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()) panic!("Stdout matches regex:\n{}", self.stdout_str())
} }
self self