1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'master' into split-wsl-detection

This commit is contained in:
Sylvestre Ledru 2021-04-17 10:22:54 +02:00 committed by GitHub
commit fc057b816b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
147 changed files with 5210 additions and 2169 deletions

View file

@ -22,6 +22,7 @@ search the issues to make sure no one else is working on it.
1. Make sure that the code coverage is covering all of the cases, including errors. 1. Make sure that the code coverage is covering all of the cases, including errors.
1. The code must be clippy-warning-free and rustfmt-compliant. 1. The code must be clippy-warning-free and rustfmt-compliant.
1. Don't hesitate to move common functions into uucore if they can be reused by other binaries. 1. Don't hesitate to move common functions into uucore if they can be reused by other binaries.
1. Unsafe code should be documented with Safety comments.
## Commit messages ## Commit messages

40
Cargo.lock generated
View file

@ -1363,10 +1363,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "static_assertions" name = "smallvec"
version = "1.1.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "strsim" name = "strsim"
@ -1394,9 +1394,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.68" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
@ -1522,17 +1522,6 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[package]]
name = "twox-hash"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
dependencies = [
"cfg-if 0.1.10",
"rand 0.7.3",
"static_assertions",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.13.0" version = "1.13.0"
@ -1627,7 +1616,8 @@ name = "uu_cat"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"quick-error", "nix 0.20.0",
"thiserror",
"unix_socket", "unix_socket",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
@ -1777,7 +1767,7 @@ dependencies = [
name = "uu_du" name = "uu_du"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"time", "chrono",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"winapi 0.3.9", "winapi 0.3.9",
@ -1833,7 +1823,7 @@ dependencies = [
"quickcheck", "quickcheck",
"rand 0.7.3", "rand 0.7.3",
"rand_chacha", "rand_chacha",
"smallvec", "smallvec 0.6.14",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -1861,6 +1851,7 @@ dependencies = [
name = "uu_fold" name = "uu_fold"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2300,10 +2291,12 @@ name = "uu_sort"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap", "clap",
"itertools 0.8.2", "fnv",
"itertools 0.10.0",
"rand 0.7.3", "rand 0.7.3",
"rayon",
"semver", "semver",
"twox-hash", "smallvec 1.6.1",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2331,7 +2324,7 @@ dependencies = [
name = "uu_stdbuf" name = "uu_stdbuf"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"getopts", "clap",
"tempfile", "tempfile",
"uu_stdbuf_libstdbuf", "uu_stdbuf_libstdbuf",
"uucore", "uucore",
@ -2415,6 +2408,7 @@ dependencies = [
name = "uu_timeout" name = "uu_timeout"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"clap",
"getopts", "getopts",
"libc", "libc",
"uucore", "uucore",
@ -2514,7 +2508,7 @@ dependencies = [
name = "uu_unlink" name = "uu_unlink"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"getopts", "clap",
"libc", "libc",
"uucore", "uucore",
"uucore_procs", "uucore_procs",

138
README.md
View file

@ -1,5 +1,4 @@
uutils coreutils # uutils coreutils
================
[![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils) [![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ) [![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
@ -9,16 +8,18 @@ uutils coreutils
[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils) [![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils)
[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master) [![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master)
[![codecov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
----------------------------------------------- -----------------------------------------------
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
<!-- spell-checker:ignore markdownlint ; (jargon) multicall ; (misc) aarch riscv uutil uutils ; (names/acronyms) BusyBox BusyBox's BusyTest MSVC NixOS PowerPC WASI WASM ; (options) DESTDIR RUNTEST UTILNAME -->
uutils is an attempt at writing universal (as in cross-platform) CLI uutils is an attempt at writing universal (as in cross-platform) CLI
utilities in [Rust](http://www.rust-lang.org). This repository is intended to utilities in [Rust](http://www.rust-lang.org). This repository is intended to
aggregate GNU coreutils rewrites. aggregate GNU coreutils rewrites.
Why? ## Why?
----
Many GNU, Linux and other utilities are useful, and obviously Many GNU, Linux and other utilities are useful, and obviously
[some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net) [some](http://gnuwin32.sourceforge.net) [effort](http://unxutils.sourceforge.net)
@ -29,265 +30,300 @@ have other issues.
Rust provides a good, platform-agnostic way of writing systems utilities that are easy Rust provides a good, platform-agnostic way of writing systems utilities that are easy
to compile anywhere, and this is as good a way as any to try and learn it. to compile anywhere, and this is as good a way as any to try and learn it.
Requirements ## Requirements
------------
* Rust (`cargo`, `rustc`) * Rust (`cargo`, `rustc`)
* GNU Make (required to build documentation) * GNU Make (required to build documentation)
* [Sphinx](http://www.sphinx-doc.org/) (for documentation) * [Sphinx](http://www.sphinx-doc.org/) (for documentation)
* gzip (for installing documentation) * gzip (for installing documentation)
### Rust Version ### ### Rust Version
uutils follows Rust's release channels and is tested against stable, beta and nightly. uutils follows Rust's release channels and is tested against stable, beta and nightly.
The current oldest supported version of the Rust compiler is `1.40.0`. The current oldest supported version of the Rust compiler is `1.40.0`.
On both Windows and Redox, only the nightly version is tested currently. On both Windows and Redox, only the nightly version is tested currently.
Build Instructions ## Build Instructions
------------------
There are currently two methods to build uutils: GNU Make and Cargo. However, There are currently two methods to build the uutils binaries: either Cargo
while there may be two methods, both systems are required to build on Unix or GNU Make.
(only Cargo is required on Windows).
> Building the full package, including all documentation, requires both Cargo
> and Gnu Make on a Unix platform.
For either method, we first need to fetch the repository:
First, for both methods, we need to fetch the repository:
```bash ```bash
$ git clone https://github.com/uutils/coreutils $ git clone https://github.com/uutils/coreutils
$ cd coreutils $ cd coreutils
``` ```
### Cargo ### ### Cargo
Building uutils using Cargo is easy because the process is the same as for Building uutils using Cargo is easy because the process is the same as for
every other Rust program: every other Rust program:
```bash ```bash
# to keep debug information, compile without --release
$ cargo build --release $ cargo build --release
``` ```
Because the above command attempts to build utilities that only work on This command builds the most portable common core set of uutils into a multicall
Unix-like platforms at the moment, to build on Windows, you must do the (BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms.
following:
Additional platform-specific uutils are often available. Building these
expanded sets of uutils for a platform (on that platform) is as simple as
specifying it as a feature:
```bash ```bash
# to keep debug information, compile without --release $ cargo build --release --features macos
$ cargo build --release --no-default-features --features windows # or ...
$ cargo build --release --features windows
# or ...
$ cargo build --release --features unix
``` ```
If you don't want to build every utility available on your platform into the If you don't want to build every utility available on your platform into the
multicall binary (the Busybox-esque binary), you can also specify which ones final binary, you can also specify which ones you want to build manually.
you want to build manually. For example: For example:
```bash ```bash
$ cargo build --features "base32 cat echo rm" --no-default-features $ cargo build --features "base32 cat echo rm" --no-default-features
``` ```
If you don't even want to build the multicall binary and would prefer to just If you don't want to build the multicall binary and would prefer to build
build the utilities as individual binaries, that is possible too. For example: the utilities as individual binaries, that is also possible. Each utility
is contained in it's own package within the main repository, named
"uu_UTILNAME". To build individual utilities, use cargo to build just the
specific packages (using the `--package` [aka `-p`] option). For example:
```bash ```bash
$ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm $ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
``` ```
### GNU Make ### ### GNU Make
Building using `make` is a simple process as well. Building using `make` is a simple process as well.
To simply build all available utilities: To simply build all available utilities:
```bash ```bash
$ make $ make
``` ```
To build all but a few of the available utilities: To build all but a few of the available utilities:
```bash ```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' $ make SKIP_UTILS='UTILITY_1 UTILITY_2'
``` ```
To build only a few of the available utilities: To build only a few of the available utilities:
```bash ```bash
$ make UTILS='UTILITY_1 UTILITY_2' $ make UTILS='UTILITY_1 UTILITY_2'
``` ```
Installation Instructions ## Installation Instructions
-------------------------
### Cargo ### ### Cargo
Likewise, installing can simply be done using: Likewise, installing can simply be done using:
```bash ```bash
$ cargo install --path . $ cargo install --path .
``` ```
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`). This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
### GNU Make ### ### GNU Make
To install all available utilities: To install all available utilities:
```bash ```bash
$ make install $ make install
``` ```
To install using `sudo` switch `-E` must be used: To install using `sudo` switch `-E` must be used:
```bash ```bash
$ sudo -E make install $ sudo -E make install
``` ```
To install all but a few of the available utilities: To install all but a few of the available utilities:
```bash ```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install $ make SKIP_UTILS='UTILITY_1 UTILITY_2' install
``` ```
To install only a few of the available utilities: To install only a few of the available utilities:
```bash ```bash
$ make UTILS='UTILITY_1 UTILITY_2' install $ make UTILS='UTILITY_1 UTILITY_2' install
``` ```
To install every program with a prefix (e.g. uu-echo uu-cat): To install every program with a prefix (e.g. uu-echo uu-cat):
```bash ```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE install $ make PROG_PREFIX=PREFIX_GOES_HERE install
``` ```
To install the multicall binary: To install the multicall binary:
```bash ```bash
$ make MULTICALL=y install $ make MULTICALL=y install
``` ```
Set install parent directory (default value is /usr/local): Set install parent directory (default value is /usr/local):
```bash ```bash
# DESTDIR is also supported # DESTDIR is also supported
$ make PREFIX=/my/path install $ make PREFIX=/my/path install
``` ```
### NixOS ### ### NixOS
The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/) The [standard package set](https://nixos.org/nixpkgs/manual/) of [NixOS](https://nixos.org/)
provides this package out of the box since 18.03: provides this package out of the box since 18.03:
``` ```shell
nix-env -iA nixos.uutils-coreutils $ nix-env -iA nixos.uutils-coreutils
``` ```
Uninstallation Instructions ## Un-installation Instructions
---------------------------
Uninstallation differs depending on how you have installed uutils. If you used Un-installation differs depending on how you have installed uutils. If you used
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
Make to uninstall. Make to uninstall.
### Cargo ### ### Cargo
To uninstall uutils: To uninstall uutils:
```bash ```bash
$ cargo uninstall uutils $ cargo uninstall uutils
``` ```
### GNU Make ### ### GNU Make
To uninstall all utilities: To uninstall all utilities:
```bash ```bash
$ make uninstall $ make uninstall
``` ```
To uninstall every program with a set prefix: To uninstall every program with a set prefix:
```bash ```bash
$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall $ make PROG_PREFIX=PREFIX_GOES_HERE uninstall
``` ```
To uninstall the multicall binary: To uninstall the multicall binary:
```bash ```bash
$ make MULTICALL=y uninstall $ make MULTICALL=y uninstall
``` ```
To uninstall from a custom parent directory: To uninstall from a custom parent directory:
```bash ```bash
# DESTDIR is also supported # DESTDIR is also supported
$ make PREFIX=/my/path uninstall $ make PREFIX=/my/path uninstall
``` ```
Test Instructions ## Test Instructions
-----------------
Testing can be done using either Cargo or `make`. Testing can be done using either Cargo or `make`.
### Cargo ### ### Cargo
Just like with building, we follow the standard procedure for testing using Just like with building, we follow the standard procedure for testing using
Cargo: Cargo:
```bash ```bash
$ cargo test $ cargo test
``` ```
By default, `cargo test` only runs the common programs. To run also platform By default, `cargo test` only runs the common programs. To run also platform
specific tests, run: specific tests, run:
```bash ```bash
$ cargo test --features unix $ cargo test --features unix
``` ```
If you would prefer to test a select few utilities: If you would prefer to test a select few utilities:
```bash ```bash
$ cargo test --features "chmod mv tail" --no-default-features $ cargo test --features "chmod mv tail" --no-default-features
``` ```
If you also want to test the core utilities: If you also want to test the core utilities:
```bash ```bash
$ cargo test -p uucore -p coreutils $ cargo test -p uucore -p coreutils
``` ```
To debug: To debug:
```bash ```bash
$ gdb --args target/debug/coreutils ls $ gdb --args target/debug/coreutils ls
(gdb) b ls.rs:79 (gdb) b ls.rs:79
(gdb) run (gdb) run
``` ```
### GNU Make ### ### GNU Make
To simply test all available utilities: To simply test all available utilities:
```bash ```bash
$ make test $ make test
``` ```
To test all but a few of the available utilities: To test all but a few of the available utilities:
```bash ```bash
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test $ make SKIP_UTILS='UTILITY_1 UTILITY_2' test
``` ```
To test only a few of the available utilities: To test only a few of the available utilities:
```bash ```bash
$ make UTILS='UTILITY_1 UTILITY_2' test $ make UTILS='UTILITY_1 UTILITY_2' test
``` ```
To include tests for unimplemented behavior: To include tests for unimplemented behavior:
```bash ```bash
$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test $ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
``` ```
Run Busybox Tests ## Run Busybox Tests
-----------------
This testing functionality is only available on *nix operating systems and This testing functionality is only available on *nix operating systems and
requires `make`. requires `make`.
To run busybox's tests for all utilities for which busybox has tests To run busybox's tests for all utilities for which busybox has tests
```bash ```bash
$ make busytest $ make busytest
``` ```
To run busybox's tests for a few of the available utilities To run busybox's tests for a few of the available utilities
```bash ```bash
$ make UTILS='UTILITY_1 UTILITY_2' busytest $ make UTILS='UTILITY_1 UTILITY_2' busytest
``` ```
To pass an argument like "-v" to the busybox test runtime To pass an argument like "-v" to the busybox test runtime
```bash ```bash
$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest $ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
``` ```
Contribute ## Contribute
----------
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
Utilities ## Utilities
---------
| Done | Semi-Done | To Do | | Done | Semi-Done | To Do |
|-----------|-----------|--------| |-----------|-----------|--------|
@ -377,8 +413,7 @@ Utilities
| whoami | | | | whoami | | |
| yes | | | | yes | | |
Targets that compile ## Targets that compile
-------
This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass. This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass.
@ -405,8 +440,7 @@ This is an auto-generated table showing which binaries compile for each target-t
|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
License ## License
-------
uutils is licensed under the MIT License - see the `LICENSE` file for details uutils is licensed under the MIT License - see the `LICENSE` file for details

View file

@ -111,6 +111,7 @@ fn basename(fullname: &str, suffix: &str) -> String {
} }
} }
#[allow(clippy::manual_strip)] // can be replaced with strip_suffix once the minimum rust version is 1.45
fn strip_suffix(name: &str, suffix: &str) -> String { fn strip_suffix(name: &str, suffix: &str) -> String {
if name == suffix { if name == suffix {
return name.to_owned(); return name.to_owned();

View file

@ -16,13 +16,16 @@ path = "src/cat.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
quick-error = "1.2.3" thiserror = "1.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["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" }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0" unix_socket = "0.5.0"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = "0.20"
[[bin]] [[bin]]
name = "cat" name = "cat"
path = "src/main.rs" path = "src/main.rs"

View file

@ -3,14 +3,13 @@
// (c) Jordi Boggiano <j.boggiano@seld.be> // (c) Jordi Boggiano <j.boggiano@seld.be>
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com> // (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
// (c) Joshua S. Miller <jsmiller@uchicago.edu> // (c) Joshua S. Miller <jsmiller@uchicago.edu>
// (c) Árni Dagur <arni@dagur.eu>
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) nonprint nonblank nonprinting // spell-checker:ignore (ToDO) nonprint nonblank nonprinting
#[macro_use]
extern crate quick_error;
#[cfg(unix)] #[cfg(unix)]
extern crate unix_socket; extern crate unix_socket;
#[macro_use] #[macro_use]
@ -18,9 +17,9 @@ extern crate uucore;
// last synced with: cat (GNU coreutils) 8.13 // last synced with: cat (GNU coreutils) 8.13
use clap::{App, Arg}; use clap::{App, Arg};
use quick_error::ResultExt;
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write}; use std::io::{self, Read, Write};
use thiserror::Error;
use uucore::fs::is_stdin_interactive; use uucore::fs::is_stdin_interactive;
/// Unix domain socket support /// Unix domain socket support
@ -31,12 +30,41 @@ use std::os::unix::fs::FileTypeExt;
#[cfg(unix)] #[cfg(unix)]
use unix_socket::UnixStream; use unix_socket::UnixStream;
/// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::fcntl::{splice, SpliceFFlags};
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::unistd::pipe;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::{AsRawFd, RawFd};
static NAME: &str = "cat"; static NAME: &str = "cat";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str = "[OPTION]... [FILE]..."; static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output
With no FILE, or when FILE is -, read standard input."; With no FILE, or when FILE is -, read standard input.";
#[derive(Error, Debug)]
enum CatError {
/// Wrapper around `io::Error`
#[error("{0}")]
Io(#[from] io::Error),
/// Wrapper around `nix::Error`
#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("{0}")]
Nix(#[from] nix::Error),
/// Unknown file type; it's not a regular file, socket, etc.
#[error("unknown filetype: {}", ft_debug)]
UnknownFiletype {
/// A debug print of the file type
ft_debug: String,
},
#[error("Is a directory")]
IsDirectory,
}
type CatResult<T> = Result<T, CatError>;
#[derive(PartialEq)] #[derive(PartialEq)]
enum NumberingMode { enum NumberingMode {
None, None,
@ -44,39 +72,6 @@ enum NumberingMode {
All, All,
} }
quick_error! {
#[derive(Debug)]
enum CatError {
/// Wrapper for io::Error with path context
Input(err: io::Error, path: String) {
display("cat: {0}: {1}", path, err)
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
cause(err)
}
/// Wrapper for io::Error with no context
Output(err: io::Error) {
display("cat: {0}", err) from()
cause(err)
}
/// Unknown Filetype classification
UnknownFiletype(path: String) {
display("cat: {0}: unknown filetype", path)
}
/// At least one error was encountered in reading or writing
EncounteredErrors(count: usize) {
display("cat: encountered {0} errors", count)
}
/// Denotes an error caused by trying to `cat` a directory
IsDirectory(path: String) {
display("cat: {0}: Is a directory", path)
}
}
}
struct OutputOptions { struct OutputOptions {
/// Line numbering mode /// Line numbering mode
number: NumberingMode, number: NumberingMode,
@ -87,21 +82,56 @@ struct OutputOptions {
/// display TAB characters as `tab` /// display TAB characters as `tab`
show_tabs: bool, show_tabs: bool,
/// If `show_tabs == true`, this string will be printed in the /// Show end of lines
/// place of tabs show_ends: bool,
tab: String,
/// Can be set to show characters other than '\n' a the end of
/// each line, e.g. $
end_of_line: String,
/// use ^ and M- notation, except for LF (\\n) and TAB (\\t) /// use ^ and M- notation, except for LF (\\n) and TAB (\\t)
show_nonprint: bool, show_nonprint: bool,
} }
impl OutputOptions {
fn tab(&self) -> &'static str {
if self.show_tabs {
"^I"
} else {
"\t"
}
}
fn end_of_line(&self) -> &'static str {
if self.show_ends {
"$\n"
} else {
"\n"
}
}
/// We can write fast if we can simply copy the contents of the file to
/// stdout, without augmenting the output with e.g. line numbers.
fn can_write_fast(&self) -> bool {
!(self.show_tabs
|| self.show_nonprint
|| self.show_ends
|| self.squeeze_blank
|| self.number != NumberingMode::None)
}
}
/// State that persists between output of each file. This struct is only used
/// when we can't write fast.
struct OutputState {
/// The current line number
line_number: usize,
/// Whether the output cursor is at the beginning of a new line
at_line_start: bool,
}
/// Represents an open file handle, stream, or other device /// Represents an open file handle, stream, or other device
struct InputHandle { struct InputHandle<R: Read> {
reader: Box<dyn Read>, #[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: RawFd,
reader: R,
is_interactive: bool, is_interactive: bool,
} }
@ -124,8 +154,6 @@ enum InputType {
Socket, Socket,
} }
type CatResult<T> = Result<T, CatError>;
mod options { mod options {
pub static FILE: &str = "file"; pub static FILE: &str = "file";
pub static SHOW_ALL: &str = "show-all"; pub static SHOW_ALL: &str = "show-all";
@ -243,30 +271,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
None => vec!["-".to_owned()], None => vec!["-".to_owned()],
}; };
let can_write_fast = !(show_tabs
|| show_nonprint
|| show_ends
|| squeeze_blank
|| number_mode != NumberingMode::None);
let success = if can_write_fast {
write_fast(files).is_ok()
} else {
let tab = if show_tabs { "^I" } else { "\t" }.to_owned();
let end_of_line = if show_ends { "$\n" } else { "\n" }.to_owned();
let options = OutputOptions { let options = OutputOptions {
end_of_line, show_ends,
number: number_mode, number: number_mode,
show_nonprint, show_nonprint,
show_tabs, show_tabs,
squeeze_blank, squeeze_blank,
tab,
};
write_lines(files, &options).is_ok()
}; };
let success = cat_files(files, &options).is_ok();
if success { if success {
0 0
@ -275,6 +287,76 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
fn cat_handle<R: Read>(
handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
if options.can_write_fast() {
write_fast(handle)
} else {
write_lines(handle, &options, state)
}
}
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
if path == "-" {
let stdin = io::stdin();
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(),
reader: stdin,
is_interactive: is_stdin_interactive(),
};
return cat_handle(&mut handle, &options, state);
}
match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory),
#[cfg(unix)]
InputType::Socket => {
let socket = UnixStream::connect(path)?;
socket.shutdown(Shutdown::Write)?;
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: socket.as_raw_fd(),
reader: socket,
is_interactive: false,
};
cat_handle(&mut handle, &options, state)
}
_ => {
let file = File::open(path)?;
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(),
reader: file,
is_interactive: false,
};
cat_handle(&mut handle, &options, state)
}
}
}
fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
let mut error_count = 0;
let mut state = OutputState {
line_number: 1,
at_line_start: true,
};
for path in &files {
if let Err(err) = cat_path(path, &options, &mut state) {
show_info!("{}: {}", path, err);
error_count += 1;
}
}
if error_count == 0 {
Ok(())
} else {
Err(error_count)
}
}
/// Classifies the `InputType` of file at `path` if possible /// Classifies the `InputType` of file at `path` if possible
/// ///
/// # Arguments /// # Arguments
@ -285,7 +367,8 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
return Ok(InputType::StdIn); return Ok(InputType::StdIn);
} }
match metadata(path).context(path)?.file_type() { let ft = metadata(path)?.file_type();
match ft {
#[cfg(unix)] #[cfg(unix)]
ft if ft.is_block_device() => Ok(InputType::BlockDevice), ft if ft.is_block_device() => Ok(InputType::BlockDevice),
#[cfg(unix)] #[cfg(unix)]
@ -297,125 +380,116 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
ft if ft.is_dir() => Ok(InputType::Directory), ft if ft.is_dir() => Ok(InputType::Directory),
ft if ft.is_file() => Ok(InputType::File), ft if ft.is_file() => Ok(InputType::File),
ft if ft.is_symlink() => Ok(InputType::SymLink), ft if ft.is_symlink() => Ok(InputType::SymLink),
_ => Err(CatError::UnknownFiletype(path.to_owned())), _ => Err(CatError::UnknownFiletype {
ft_debug: format!("{:?}", ft),
}),
} }
} }
/// Returns an InputHandle from which a Reader can be accessed or an /// Writes handle to stdout with no configuration. This allows a
/// error /// simple memory copy.
/// fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
/// # Arguments let stdout = io::stdout();
/// let mut stdout_lock = stdout.lock();
/// * `path` - `InputHandler` will wrap a reader from this file path #[cfg(any(target_os = "linux", target_os = "android"))]
fn open(path: &str) -> CatResult<InputHandle> { {
if path == "-" { // If we're on Linux or Android, try to use the splice() system call
let stdin = stdin(); // for faster writing. If it works, we're done.
return Ok(InputHandle { if !write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
reader: Box::new(stdin) as Box<dyn Read>, return Ok(());
is_interactive: is_stdin_interactive(),
});
}
match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory(path.to_owned())),
#[cfg(unix)]
InputType::Socket => {
let socket = UnixStream::connect(path).context(path)?;
socket.shutdown(Shutdown::Write).context(path)?;
Ok(InputHandle {
reader: Box::new(socket) as Box<dyn Read>,
is_interactive: false,
})
}
_ => {
let file = File::open(path).context(path)?;
Ok(InputHandle {
reader: Box::new(file) as Box<dyn Read>,
is_interactive: false,
})
} }
} }
} // If we're not on Linux or Android, or the splice() call failed,
// fall back on slower writing.
/// Writes files to stdout with no configuration. This allows a let mut buf = [0; 1024 * 64];
/// simple memory copy. Returns `Ok(())` if no errors were while let Ok(n) = handle.reader.read(&mut buf) {
/// encountered, or an error with the number of errors encountered.
///
/// # Arguments
///
/// * `files` - There is no short circuit when encountering an error
/// reading a file in this vector
fn write_fast(files: Vec<String>) -> CatResult<()> {
let mut writer = stdout();
let mut in_buf = [0; 1024 * 64];
let mut error_count = 0;
for file in files {
match open(&file[..]) {
Ok(mut handle) => {
while let Ok(n) = handle.reader.read(&mut in_buf) {
if n == 0 { if n == 0 {
break; break;
} }
writer.write_all(&in_buf[..n]).context(&file[..])?; stdout_lock.write_all(&buf[..n])?;
}
}
Err(error) => {
writeln!(&mut stderr(), "{}", error)?;
error_count += 1;
}
}
}
match error_count {
0 => Ok(()),
_ => Err(CatError::EncounteredErrors(error_count)),
} }
Ok(())
} }
/// State that persists between output of each file /// This function is called from `write_fast()` on Linux and Android. The
struct OutputState { /// function `splice()` is used to move data between two file descriptors
/// The current line number /// without copying between kernel- and userspace. This results in a large
line_number: usize, /// speedup.
///
/// The `bool` in the result value indicates if we need to fall back to normal
/// copying or not. False means we don't have to.
#[cfg(any(target_os = "linux", target_os = "android"))]
#[inline]
fn write_fast_using_splice<R: Read>(handle: &mut InputHandle<R>, writer: RawFd) -> CatResult<bool> {
const BUF_SIZE: usize = 1024 * 16;
/// Whether the output cursor is at the beginning of a new line let (pipe_rd, pipe_wr) = pipe()?;
at_line_start: bool,
// We only fall back if splice fails on the first call.
match splice(
handle.file_descriptor,
None,
pipe_wr,
None,
BUF_SIZE,
SpliceFFlags::empty(),
) {
Ok(n) => {
if n == 0 {
return Ok(false);
}
splice_exact(pipe_rd, writer, n)?;
}
Err(_) => {
return Ok(true);
}
}
loop {
let n = splice(
handle.file_descriptor,
None,
pipe_wr,
None,
BUF_SIZE,
SpliceFFlags::empty(),
)?;
if n == 0 {
// We read 0 bytes from the input,
// which means we're done copying.
break;
}
splice_exact(pipe_rd, writer, n)?;
}
Ok(false)
} }
/// Writes files to stdout with `options` as configuration. Returns /// Splice wrapper which handles short writes
/// `Ok(())` if no errors were encountered, or an error with the #[cfg(any(target_os = "linux", target_os = "android"))]
/// number of errors encountered. #[inline]
/// fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
/// # Arguments let mut left = num_bytes;
/// loop {
/// * `files` - There is no short circuit when encountering an error let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
/// reading a file in this vector left -= written;
fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> { if left == 0 {
let mut error_count = 0; break;
let mut state = OutputState {
line_number: 1,
at_line_start: true,
};
for file in files {
if let Err(error) = write_file_lines(&file, options, &mut state) {
writeln!(&mut stderr(), "{}", error).context(&file[..])?;
error_count += 1;
} }
} }
Ok(())
match error_count {
0 => Ok(()),
_ => Err(CatError::EncounteredErrors(error_count)),
}
} }
/// Outputs file contents to stdout in a line-by-line fashion, /// Outputs file contents to stdout in a line-by-line fashion,
/// propagating any errors that might occur. /// propagating any errors that might occur.
fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { fn write_lines<R: Read>(
let mut handle = open(file)?; handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
let mut in_buf = [0; 1024 * 31]; let mut in_buf = [0; 1024 * 31];
let mut writer = BufWriter::with_capacity(1024 * 64, stdout()); let stdout = io::stdout();
let mut writer = stdout.lock();
let mut one_blank_kept = false; let mut one_blank_kept = false;
while let Ok(n) = handle.reader.read(&mut in_buf) { while let Ok(n) = handle.reader.read(&mut in_buf) {
@ -433,9 +507,9 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
write!(&mut writer, "{0:6}\t", state.line_number)?; write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1; state.line_number += 1;
} }
writer.write_all(options.end_of_line.as_bytes())?; writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive { if handle.is_interactive {
writer.flush().context(file)?; writer.flush()?;
} }
} }
state.at_line_start = true; state.at_line_start = true;
@ -450,7 +524,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
// print to end of line or end of buffer // print to end of line or end of buffer
let offset = if options.show_nonprint { let offset = if options.show_nonprint {
write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab.as_bytes()) write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab().as_bytes())
} else if options.show_tabs { } else if options.show_tabs {
write_tab_to_end(&in_buf[pos..], &mut writer) write_tab_to_end(&in_buf[pos..], &mut writer)
} else { } else {
@ -462,7 +536,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
break; break;
} }
// print suitable end of line // print suitable end of line
writer.write_all(options.end_of_line.as_bytes())?; writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive { if handle.is_interactive {
writer.flush()?; writer.flush()?;
} }

View file

@ -286,7 +286,7 @@ impl Chgrper {
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) { ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
Ok(n) => { Ok(n) => {
if n != "" { if !n.is_empty() {
show_info!("{}", n); show_info!("{}", n);
} }
0 0

View file

@ -171,13 +171,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// of a prefix '-' if it's associated with MODE // of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE" // e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool { pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for i in 0..args.len() { for arg in args {
if args[i].starts_with("-") { if arg.starts_with('-') {
if let Some(second) = args[i].chars().nth(1) { if let Some(second) = arg.chars().nth(1) {
match second { match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0 // TODO: use strip_prefix() once minimum rust version reaches 1.45.0
args[i] = args[i][1..args[i].len()].to_string(); *arg = arg[1..arg.len()].to_string();
return true; return true;
} }
_ => {} _ => {}

View file

@ -391,7 +391,7 @@ impl Chowner {
self.verbosity.clone(), self.verbosity.clone(),
) { ) {
Ok(n) => { Ok(n) => {
if n != "" { if !n.is_empty() {
show_info!("{}", n); show_info!("{}", n);
} }
0 0
@ -446,7 +446,7 @@ impl Chowner {
self.verbosity.clone(), self.verbosity.clone(),
) { ) {
Ok(n) => { Ok(n) => {
if n != "" { if !n.is_empty() {
show_info!("{}", n); show_info!("{}", n);
} }
0 0

View file

@ -104,7 +104,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
_ => { _ => {
let mut vector: Vec<&str> = Vec::new(); let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() { for (&k, v) in matches.args.iter() {
vector.push(k.clone()); vector.push(k);
vector.push(&v.vals[0].to_str().unwrap()); vector.push(&v.vals[0].to_str().unwrap());
} }
vector vector
@ -133,7 +133,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec = match userspec_str { let userspec = match userspec_str {
Some(ref u) => { Some(ref u) => {
let s: Vec<&str> = u.split(':').collect(); let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 || s.iter().any(|&spec| spec == "") { if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u) crash!(1, "invalid userspec: `{}`", u)
}; };
s s
@ -142,16 +142,16 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
}; };
let (user, group) = if userspec.is_empty() { let (user, group) = if userspec.is_empty() {
(&user_str[..], &group_str[..]) (user_str, group_str)
} else { } else {
(&userspec[0][..], &userspec[1][..]) (userspec[0], userspec[1])
}; };
enter_chroot(root); enter_chroot(root);
set_groups_from_str(&groups_str[..]); set_groups_from_str(groups_str);
set_main_group(&group[..]); set_main_group(group);
set_user(&user[..]); set_user(user);
} }
fn enter_chroot(root: &Path) { fn enter_chroot(root: &Path) {

View file

@ -132,7 +132,9 @@ macro_rules! prompt_yes(
pub type CopyResult<T> = Result<T, Error>; pub type CopyResult<T> = Result<T, Error>;
pub type Source = PathBuf; pub type Source = PathBuf;
pub type SourceSlice = Path;
pub type Target = PathBuf; pub type Target = PathBuf;
pub type TargetSlice = Path;
/// Specifies whether when overwrite files /// Specifies whether when overwrite files
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
@ -547,14 +549,13 @@ impl FromStr for Attribute {
} }
fn add_all_attributes() -> Vec<Attribute> { fn add_all_attributes() -> Vec<Attribute> {
let mut attr = Vec::new(); use Attribute::*;
let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links];
#[cfg(unix)] #[cfg(unix)]
attr.push(Attribute::Mode); attr.insert(0, Mode);
attr.push(Attribute::Ownership);
attr.push(Attribute::Timestamps);
attr.push(Attribute::Context);
attr.push(Attribute::Xattr);
attr.push(Attribute::Links);
attr attr
} }
@ -665,7 +666,7 @@ impl TargetType {
/// ///
/// Treat target as a dir if we have multiple sources or the target /// Treat target as a dir if we have multiple sources or the target
/// exists and already is a directory /// exists and already is a directory
fn determine(sources: &[Source], target: &Target) -> TargetType { fn determine(sources: &[Source], target: &TargetSlice) -> TargetType {
if sources.len() > 1 || target.is_dir() { if sources.len() > 1 || target.is_dir() {
TargetType::Directory TargetType::Directory
} else { } else {
@ -714,7 +715,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
fn preserve_hardlinks( fn preserve_hardlinks(
hard_links: &mut Vec<(String, u64)>, hard_links: &mut Vec<(String, u64)>,
source: &std::path::PathBuf, source: &std::path::Path,
dest: std::path::PathBuf, dest: std::path::PathBuf,
found_hard_link: &mut bool, found_hard_link: &mut bool,
) -> CopyResult<()> { ) -> CopyResult<()> {
@ -788,7 +789,7 @@ fn preserve_hardlinks(
/// Behavior depends on `options`, see [`Options`] for details. /// Behavior depends on `options`, see [`Options`] for details.
/// ///
/// [`Options`]: ./struct.Options.html /// [`Options`]: ./struct.Options.html
fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()> { fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> {
let target_type = TargetType::determine(sources, target); let target_type = TargetType::determine(sources, target);
verify_target_type(target, &target_type)?; verify_target_type(target, &target_type)?;
@ -840,7 +841,7 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
fn construct_dest_path( fn construct_dest_path(
source_path: &Path, source_path: &Path,
target: &Target, target: &TargetSlice,
target_type: &TargetType, target_type: &TargetType,
options: &Options, options: &Options,
) -> CopyResult<PathBuf> { ) -> CopyResult<PathBuf> {
@ -870,8 +871,8 @@ fn construct_dest_path(
} }
fn copy_source( fn copy_source(
source: &Source, source: &SourceSlice,
target: &Target, target: &TargetSlice,
target_type: &TargetType, target_type: &TargetType,
options: &Options, options: &Options,
) -> CopyResult<()> { ) -> CopyResult<()> {
@ -912,7 +913,7 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
/// ///
/// Any errors encountered copying files in the tree will be logged but /// Any errors encountered copying files in the tree will be logged but
/// will not cause a short-circuit. /// will not cause a short-circuit.
fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult<()> { fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> {
if !options.recursive { if !options.recursive {
return Err(format!("omitting directory '{}'", root.display()).into()); return Err(format!("omitting directory '{}'", root.display()).into());
} }
@ -1068,6 +1069,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
} }
#[cfg(not(windows))] #[cfg(not(windows))]
#[allow(clippy::unnecessary_wraps)] // needed for windows version
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
match std::os::unix::fs::symlink(source, dest).context(context) { match std::os::unix::fs::symlink(source, dest).context(context) {
Ok(_) => Ok(()), Ok(_) => Ok(()),

View file

@ -406,7 +406,7 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
continue; continue;
} }
if !path.metadata().is_ok() { if path.metadata().is_err() {
show_error!("{}: No such file or directory", filename); show_error!("{}: No such file or directory", filename);
continue; continue;
} }
@ -535,8 +535,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
matches.value_of(options::CHARACTERS), matches.value_of(options::CHARACTERS),
matches.value_of(options::FIELDS), matches.value_of(options::FIELDS),
) { ) {
(Some(byte_ranges), None, None) => { (Some(byte_ranges), None, None) => list_to_ranges(byte_ranges, complement).map(|ranges| {
list_to_ranges(&byte_ranges[..], complement).map(|ranges| {
Mode::Bytes( Mode::Bytes(
ranges, ranges,
Options { Options {
@ -549,10 +548,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
zero_terminated: matches.is_present(options::ZERO_TERMINATED), zero_terminated: matches.is_present(options::ZERO_TERMINATED),
}, },
) )
}) }),
} (None, Some(char_ranges), None) => list_to_ranges(char_ranges, complement).map(|ranges| {
(None, Some(char_ranges), None) => {
list_to_ranges(&char_ranges[..], complement).map(|ranges| {
Mode::Characters( Mode::Characters(
ranges, ranges,
Options { Options {
@ -565,10 +562,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
zero_terminated: matches.is_present(options::ZERO_TERMINATED), zero_terminated: matches.is_present(options::ZERO_TERMINATED),
}, },
) )
}) }),
}
(None, None, Some(field_ranges)) => { (None, None, Some(field_ranges)) => {
list_to_ranges(&field_ranges[..], complement).and_then(|ranges| { list_to_ranges(field_ranges, complement).and_then(|ranges| {
let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) { let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) {
Some(s) => { Some(s) => {
if s.is_empty() { if s.is_empty() {

View file

@ -116,7 +116,6 @@ struct Options {
show_listed_fs: bool, show_listed_fs: bool,
show_fs_type: bool, show_fs_type: bool,
show_inode_instead: bool, show_inode_instead: bool,
print_grand_total: bool,
// block_size: usize, // block_size: usize,
human_readable_base: i64, human_readable_base: i64,
fs_selector: FsSelector, fs_selector: FsSelector,
@ -286,7 +285,6 @@ impl Options {
show_listed_fs: false, show_listed_fs: false,
show_fs_type: false, show_fs_type: false,
show_inode_instead: false, show_inode_instead: false,
print_grand_total: false,
// block_size: match env::var("BLOCKSIZE") { // block_size: match env::var("BLOCKSIZE") {
// Ok(size) => size.parse().unwrap(), // Ok(size) => size.parse().unwrap(),
// Err(_) => 512, // Err(_) => 512,
@ -871,9 +869,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if matches.is_present(OPT_ALL) { if matches.is_present(OPT_ALL) {
opt.show_all_fs = true; opt.show_all_fs = true;
} }
if matches.is_present(OPT_TOTAL) {
opt.print_grand_total = true;
}
if matches.is_present(OPT_INODES) { if matches.is_present(OPT_INODES) {
opt.show_inode_instead = true; opt.show_inode_instead = true;
} }

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/du.rs" path = "src/du.rs"
[dependencies] [dependencies]
time = "0.1.40" chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
winapi = { version="0.3", features=[] } winapi = { version="0.3", features=[] }

View file

@ -10,6 +10,8 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use chrono::prelude::DateTime;
use chrono::Local;
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::fs; use std::fs;
@ -22,7 +24,7 @@ use std::os::windows::fs::MetadataExt;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::io::AsRawHandle; use std::os::windows::io::AsRawHandle;
use std::path::PathBuf; use std::path::PathBuf;
use time::Timespec; use std::time::{Duration, UNIX_EPOCH};
#[cfg(windows)] #[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID}; use winapi::shared::minwindef::{DWORD, LPVOID};
#[cfg(windows)] #[cfg(windows)]
@ -118,7 +120,7 @@ impl Stat {
// https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.creation_time // https://doc.rust-lang.org/std/os/windows/fs/trait.MetadataExt.html#tymethod.creation_time
// "The returned 64-bit value [...] which represents the number of 100-nanosecond intervals since January 1, 1601 (UTC)." // "The returned 64-bit value [...] which represents the number of 100-nanosecond intervals since January 1, 1601 (UTC)."
fn windows_time_to_unix_time(win_time: u64) -> u64 { fn windows_time_to_unix_time(win_time: u64) -> u64 {
win_time / 10_000 - 11_644_473_600_000 win_time / 10_000_000 - 11_644_473_600
} }
#[cfg(windows)] #[cfg(windows)]
@ -320,7 +322,7 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
} }
} }
if size == 0 { if size == 0 {
return format!("0"); return "0".to_string();
} }
format!("{}B", size) format!("{}B", size)
} }
@ -555,8 +557,8 @@ Try '{} --help' for more information.",
}; };
if matches.opt_present("time") { if matches.opt_present("time") {
let tm = { let tm = {
let (secs, nsecs) = { let secs = {
let time = match matches.opt_str("time") { match matches.opt_str("time") {
Some(s) => match &s[..] { Some(s) => match &s[..] {
"accessed" => stat.accessed, "accessed" => stat.accessed,
"created" => stat.created, "created" => stat.created,
@ -573,13 +575,12 @@ Try '{} --help' for more information.",
} }
}, },
None => stat.modified, None => stat.modified,
}
}; };
((time / 1000) as i64, (time % 1000 * 1_000_000) as i32) DateTime::<Local>::from(UNIX_EPOCH + Duration::from_secs(secs))
};
time::at(Timespec::new(secs, nsecs))
}; };
if !summarize || index == len - 1 { if !summarize || index == len - 1 {
let time_str = tm.strftime(time_format_str).unwrap(); let time_str = tm.format(time_format_str).to_string();
print!( print!(
"{}\t{}\t{}{}", "{}\t{}\t{}{}",
convert_size(size), convert_size(size),

View file

@ -51,7 +51,7 @@ fn print_expr_error(expr_error: &str) -> ! {
crash!(2, "{}", expr_error) crash!(2, "{}", expr_error)
} }
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::ASTNode>, String>) -> Result<String, String> { fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
if maybe_ast.is_err() { if maybe_ast.is_err() {
Err(maybe_ast.err().unwrap()) Err(maybe_ast.err().unwrap())
} else { } else {

View file

@ -17,10 +17,10 @@ use onig::{Regex, RegexOptions, Syntax};
use crate::tokens::Token; use crate::tokens::Token;
type TokenStack = Vec<(usize, Token)>; type TokenStack = Vec<(usize, Token)>;
pub type OperandsList = Vec<Box<ASTNode>>; pub type OperandsList = Vec<Box<AstNode>>;
#[derive(Debug)] #[derive(Debug)]
pub enum ASTNode { pub enum AstNode {
Leaf { Leaf {
token_idx: usize, token_idx: usize,
value: String, value: String,
@ -31,7 +31,7 @@ pub enum ASTNode {
operands: OperandsList, operands: OperandsList,
}, },
} }
impl ASTNode { impl AstNode {
fn debug_dump(&self) { fn debug_dump(&self) {
self.debug_dump_impl(1); self.debug_dump_impl(1);
} }
@ -40,7 +40,7 @@ impl ASTNode {
print!("\t",); print!("\t",);
} }
match *self { match *self {
ASTNode::Leaf { AstNode::Leaf {
ref token_idx, ref token_idx,
ref value, ref value,
} => println!( } => println!(
@ -49,7 +49,7 @@ impl ASTNode {
token_idx, token_idx,
self.evaluate() self.evaluate()
), ),
ASTNode::Node { AstNode::Node {
ref token_idx, ref token_idx,
ref op_type, ref op_type,
ref operands, ref operands,
@ -67,23 +67,23 @@ impl ASTNode {
} }
} }
fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box<ASTNode> { fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box<AstNode> {
Box::new(ASTNode::Node { Box::new(AstNode::Node {
token_idx, token_idx,
op_type: op_type.into(), op_type: op_type.into(),
operands, operands,
}) })
} }
fn new_leaf(token_idx: usize, value: &str) -> Box<ASTNode> { fn new_leaf(token_idx: usize, value: &str) -> Box<AstNode> {
Box::new(ASTNode::Leaf { Box::new(AstNode::Leaf {
token_idx, token_idx,
value: value.into(), value: value.into(),
}) })
} }
pub fn evaluate(&self) -> Result<String, String> { pub fn evaluate(&self) -> Result<String, String> {
match *self { match *self {
ASTNode::Leaf { ref value, .. } => Ok(value.clone()), AstNode::Leaf { ref value, .. } => Ok(value.clone()),
ASTNode::Node { ref op_type, .. } => match self.operand_values() { AstNode::Node { ref op_type, .. } => match self.operand_values() {
Err(reason) => Err(reason), Err(reason) => Err(reason),
Ok(operand_values) => match op_type.as_ref() { Ok(operand_values) => match op_type.as_ref() {
"+" => infix_operator_two_ints( "+" => infix_operator_two_ints(
@ -161,7 +161,7 @@ impl ASTNode {
} }
} }
pub fn operand_values(&self) -> Result<Vec<String>, String> { pub fn operand_values(&self) -> Result<Vec<String>, String> {
if let ASTNode::Node { ref operands, .. } = *self { if let AstNode::Node { ref operands, .. } = *self {
let mut out = Vec::with_capacity(operands.len()); let mut out = Vec::with_capacity(operands.len());
for operand in operands { for operand in operands {
match operand.evaluate() { match operand.evaluate() {
@ -178,7 +178,7 @@ impl ASTNode {
pub fn tokens_to_ast( pub fn tokens_to_ast(
maybe_tokens: Result<Vec<(usize, Token)>, String>, maybe_tokens: Result<Vec<(usize, Token)>, String>,
) -> Result<Box<ASTNode>, String> { ) -> Result<Box<AstNode>, String> {
if maybe_tokens.is_err() { if maybe_tokens.is_err() {
Err(maybe_tokens.err().unwrap()) Err(maybe_tokens.err().unwrap())
} else { } else {
@ -212,7 +212,7 @@ pub fn tokens_to_ast(
} }
} }
fn maybe_dump_ast(result: &Result<Box<ASTNode>, String>) { fn maybe_dump_ast(result: &Result<Box<AstNode>, String>) {
use std::env; use std::env;
if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") { if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") {
if debug_var == "1" { if debug_var == "1" {
@ -238,11 +238,11 @@ fn maybe_dump_rpn(rpn: &TokenStack) {
} }
} }
fn ast_from_rpn(rpn: &mut TokenStack) -> Result<Box<ASTNode>, String> { fn ast_from_rpn(rpn: &mut TokenStack) -> Result<Box<AstNode>, String> {
match rpn.pop() { match rpn.pop() {
None => Err("syntax error (premature end of expression)".to_owned()), None => Err("syntax error (premature end of expression)".to_owned()),
Some((token_idx, Token::Value { value })) => Ok(ASTNode::new_leaf(token_idx, &value)), Some((token_idx, Token::Value { value })) => Ok(AstNode::new_leaf(token_idx, &value)),
Some((token_idx, Token::InfixOp { value, .. })) => { Some((token_idx, Token::InfixOp { value, .. })) => {
maybe_ast_node(token_idx, &value, 2, rpn) maybe_ast_node(token_idx, &value, 2, rpn)
@ -262,7 +262,7 @@ fn maybe_ast_node(
op_type: &str, op_type: &str,
arity: usize, arity: usize,
rpn: &mut TokenStack, rpn: &mut TokenStack,
) -> Result<Box<ASTNode>, String> { ) -> Result<Box<AstNode>, String> {
let mut operands = Vec::with_capacity(arity); let mut operands = Vec::with_capacity(arity);
for _ in 0..arity { for _ in 0..arity {
match ast_from_rpn(rpn) { match ast_from_rpn(rpn) {
@ -271,7 +271,7 @@ fn maybe_ast_node(
} }
} }
operands.reverse(); operands.reverse();
Ok(ASTNode::new_node(token_idx, op_type, operands)) Ok(AstNode::new_node(token_idx, op_type, operands))
} }
fn move_rest_of_ops_to_out( fn move_rest_of_ops_to_out(

View file

@ -267,7 +267,7 @@ impl<'a> ParagraphStream<'a> {
#[allow(clippy::match_like_matches_macro)] #[allow(clippy::match_like_matches_macro)]
// `matches!(...)` macro not stabilized until rust v1.42 // `matches!(...)` macro not stabilized until rust v1.42
l_slice[..colon_posn].chars().all(|x| match x as usize { l_slice[..colon_posn].chars().all(|x| match x as usize {
y if y < 33 || y > 126 => false, y if !(33..=126).contains(&y) => false,
_ => true, _ => true,
}) })
} }

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/fold.rs" path = "src/fold.rs"
[dependencies] [dependencies]
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,48 +10,71 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read}; use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path; use std::path::Path;
const TAB_WIDTH: usize = 8; const TAB_WIDTH: usize = 8;
static NAME: &str = "fold";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str = "[OPTION]... [FILE]..."; static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Writes each file (or standard input if no files are given) static SUMMARY: &str = "Writes each file (or standard input if no files are given)
to standard output whilst breaking long lines"; to standard output whilst breaking long lines";
static LONG_HELP: &str = "";
mod options {
pub const BYTES: &str = "bytes";
pub const SPACES: &str = "spaces";
pub const WIDTH: &str = "width";
pub const FILE: &str = "file";
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let (args, obs_width) = handle_obsolete(&args[..]); let (args, obs_width) = handle_obsolete(&args[..]);
let matches = app!(SYNTAX, SUMMARY, LONG_HELP) let matches = App::new(executable!())
.optflag( .name(NAME)
"b", .version(VERSION)
"bytes", .usage(SYNTAX)
.about(SUMMARY)
.arg(
Arg::with_name(options::BYTES)
.long(options::BYTES)
.short("b")
.help(
"count using bytes rather than columns (meaning control characters \ "count using bytes rather than columns (meaning control characters \
such as newline are not treated specially)", such as newline are not treated specially)",
) )
.optflag( .takes_value(false),
"s",
"spaces",
"break lines at word boundaries rather than a hard cut-off",
) )
.optopt( .arg(
"w", Arg::with_name(options::SPACES)
"width", .long(options::SPACES)
"set WIDTH as the maximum line width rather than 80", .short("s")
"WIDTH", .help("break lines at word boundaries rather than a hard cut-off")
.takes_value(false),
) )
.parse(args); .arg(
Arg::with_name(options::WIDTH)
.long(options::WIDTH)
.short("w")
.help("set WIDTH as the maximum line width rather than 80")
.value_name("WIDTH")
.allow_hyphen_values(true)
.takes_value(true),
)
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args);
let bytes = matches.opt_present("b"); let bytes = matches.is_present(options::BYTES);
let spaces = matches.opt_present("s"); let spaces = matches.is_present(options::SPACES);
let poss_width = if matches.opt_present("w") { let poss_width = match matches.value_of(options::WIDTH) {
matches.opt_str("w") Some(v) => Some(v.to_owned()),
} else { None => obs_width,
obs_width
}; };
let width = match poss_width { let width = match poss_width {
Some(inp_width) => match inp_width.parse::<usize>() { Some(inp_width) => match inp_width.parse::<usize>() {
Ok(width) => width, Ok(width) => width,
@ -59,11 +82,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}, },
None => 80, None => 80,
}; };
let files = if matches.free.is_empty() {
vec!["-".to_owned()] let files = match matches.values_of(options::FILE) {
} else { Some(v) => v.map(|v| v.to_owned()).collect(),
matches.free None => vec!["-".to_owned()],
}; };
fold(files, bytes, spaces, width); fold(files, bytes, spaces, width);
0 0
@ -132,7 +156,7 @@ fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usiz
let slice = { let slice = {
let slice = &line[i..i + width]; let slice = &line[i..i + width];
if spaces && i + width < len { if spaces && i + width < len {
match slice.rfind(char::is_whitespace) { match slice.rfind(|c: char| c.is_whitespace() && c != '\r') {
Some(m) => &slice[..=m], Some(m) => &slice[..=m],
None => slice, None => slice,
} }
@ -175,7 +199,6 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
let mut line = String::new(); let mut line = String::new();
let mut output = String::new(); let mut output = String::new();
let mut col_count = 0; let mut col_count = 0;
let mut char_count = 0;
let mut last_space = None; let mut last_space = None;
/// Print the output line, resetting the column and character counts. /// Print the output line, resetting the column and character counts.
@ -192,11 +215,10 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
println!("{}", &output[..consume]); println!("{}", &output[..consume]);
output.replace_range(..consume, ""); output.replace_range(..consume, "");
char_count = output.len();
// we know there are no tabs left in output, so each char counts // we know there are no tabs left in output, so each char counts
// as 1 column // as 1 column
col_count = char_count; col_count = output.len();
last_space = None; last_space = None;
}; };
@ -221,6 +243,7 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
} }
match ch { match ch {
'\r' => col_count = 0,
'\t' => { '\t' => {
let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH; let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH;
@ -229,36 +252,24 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) {
} }
col_count = next_tab_stop; col_count = next_tab_stop;
last_space = if spaces { Some(char_count) } else { None }; last_space = if spaces { Some(output.len()) } else { None };
} }
'\x08' => { '\x08' => {
// FIXME: does not match GNU's handling of backspace
if col_count > 0 { if col_count > 0 {
col_count -= 1; col_count -= 1;
char_count -= 1;
output.truncate(char_count);
} }
continue;
}
'\r' => {
// FIXME: does not match GNU's handling of carriage return
output.truncate(0);
col_count = 0;
char_count = 0;
continue;
} }
_ if spaces && ch.is_whitespace() => { _ if spaces && ch.is_whitespace() => {
last_space = Some(char_count); last_space = Some(output.len());
col_count += 1 col_count += 1;
} }
_ => col_count += 1, _ => col_count += 1,
}; };
output.push(ch); output.push(ch);
char_count += 1;
} }
if col_count > 0 { if !output.is_empty() {
print!("{}", output); print!("{}", output);
output.truncate(0); output.truncate(0);
} }

View file

@ -78,7 +78,7 @@ fn detect_algo<'a>(
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512), "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
"b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512), "b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512),
"sha3sum" => match matches.value_of("bits") { "sha3sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(224) => ( Ok(224) => (
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -128,7 +128,7 @@ fn detect_algo<'a>(
512, 512,
), ),
"shake128sum" => match matches.value_of("bits") { "shake128sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE128", "SHAKE128",
Box::new(Shake128::new()) as Box<dyn Digest>, Box::new(Shake128::new()) as Box<dyn Digest>,
@ -139,7 +139,7 @@ fn detect_algo<'a>(
None => crash!(1, "--bits required for SHAKE-128"), None => crash!(1, "--bits required for SHAKE-128"),
}, },
"shake256sum" => match matches.value_of("bits") { "shake256sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE256", "SHAKE256",
Box::new(Shake256::new()) as Box<dyn Digest>, Box::new(Shake256::new()) as Box<dyn Digest>,
@ -182,7 +182,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("sha3") { if matches.is_present("sha3") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(224) => set_or_crash( Ok(224) => set_or_crash(
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -226,7 +226,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake128") { if matches.is_present("shake128") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -235,7 +235,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake256") { if matches.is_present("shake256") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -253,7 +253,7 @@ fn detect_algo<'a>(
// TODO: return custom error type // TODO: return custom error type
fn parse_bit_num(arg: &str) -> Result<usize, ParseIntError> { fn parse_bit_num(arg: &str) -> Result<usize, ParseIntError> {
usize::from_str_radix(arg, 10) arg.parse()
} }
fn is_valid_bit_num(arg: String) -> Result<(), String> { fn is_valid_bit_num(arg: String) -> Result<(), String> {

View file

@ -625,7 +625,7 @@ mod tests {
assert_eq!(arg_outputs("head"), Ok("head".to_owned())); assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
} }
#[test] #[test]
#[cfg(linux)] #[cfg(target_os = "linux")]
fn test_arg_iterate_bad_encoding() { fn test_arg_iterate_bad_encoding() {
let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") }; let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") };
// this arises from a conversion from OsString to &str // this arises from a conversion from OsString to &str

View file

@ -23,9 +23,11 @@ use std::fs;
use std::fs::File; use std::fs::File;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result; use std::result::Result;
const DEFAULT_MODE: u32 = 0o755; const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip";
#[allow(dead_code)] #[allow(dead_code)]
pub struct Behavior { pub struct Behavior {
@ -37,6 +39,8 @@ pub struct Behavior {
verbose: bool, verbose: bool,
preserve_timestamps: bool, preserve_timestamps: bool,
compare: bool, compare: bool,
strip: bool,
strip_program: String,
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
@ -164,17 +168,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("apply access/modification times of SOURCE files to corresponding destination files") .help("apply access/modification times of SOURCE files to corresponding destination files")
) )
.arg( .arg(
// TODO implement flag
Arg::with_name(OPT_STRIP) Arg::with_name(OPT_STRIP)
.short("s") .short("s")
.long(OPT_STRIP) .long(OPT_STRIP)
.help("(unimplemented) strip symbol tables") .help("strip symbol tables (no action Windows)")
) )
.arg( .arg(
// TODO implement flag
Arg::with_name(OPT_STRIP_PROGRAM) Arg::with_name(OPT_STRIP_PROGRAM)
.long(OPT_STRIP_PROGRAM) .long(OPT_STRIP_PROGRAM)
.help("(unimplemented) program used to strip binaries") .help("program used to strip binaries (no action Windows)")
.value_name("PROGRAM") .value_name("PROGRAM")
) )
.arg( .arg(
@ -266,10 +268,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
Err("-b") Err("-b")
} else if matches.is_present(OPT_CREATED) { } else if matches.is_present(OPT_CREATED) {
Err("-D") Err("-D")
} else if matches.is_present(OPT_STRIP) {
Err("--strip, -s")
} else if matches.is_present(OPT_STRIP_PROGRAM) {
Err("--strip-program")
} else if matches.is_present(OPT_SUFFIX) { } else if matches.is_present(OPT_SUFFIX) {
Err("--suffix, -S") Err("--suffix, -S")
} else if matches.is_present(OPT_TARGET_DIRECTORY) { } else if matches.is_present(OPT_TARGET_DIRECTORY) {
@ -304,7 +302,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) { let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
match matches.value_of(OPT_MODE) { match matches.value_of(OPT_MODE) {
Some(x) => match mode::parse(&x[..], considering_dir) { Some(x) => match mode::parse(x, considering_dir) {
Ok(y) => Some(y), Ok(y) => Some(y),
Err(err) => { Err(err) => {
show_error!("Invalid mode string: {}", err); show_error!("Invalid mode string: {}", err);
@ -339,6 +337,12 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS), preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS),
compare: matches.is_present(OPT_COMPARE), compare: matches.is_present(OPT_COMPARE),
strip: matches.is_present(OPT_STRIP),
strip_program: String::from(
matches
.value_of(OPT_STRIP_PROGRAM)
.unwrap_or(DEFAULT_STRIP_PROGRAM),
),
}) })
} }
@ -425,7 +429,7 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
/// _files_ must all exist as non-directories. /// _files_ must all exist as non-directories.
/// _target_dir_ must be a directory. /// _target_dir_ must be a directory.
/// ///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 { fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target '{}' is not a directory", target_dir.display()); show_error!("target '{}' is not a directory", target_dir.display());
return 1; return 1;
@ -449,7 +453,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
continue; continue;
} }
let mut targetpath = target_dir.clone().to_path_buf(); let mut targetpath = target_dir.to_path_buf();
let filename = sourcepath.components().last().unwrap(); let filename = sourcepath.components().last().unwrap();
targetpath.push(filename); targetpath.push(filename);
@ -474,7 +478,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
/// _file_ must exist as a non-directory. /// _file_ must exist as a non-directory.
/// _target_ must be a non-directory /// _target_ must be a non-directory
/// ///
fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 { fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
if copy(file, &target, b).is_err() { if copy(file, &target, b).is_err() {
1 1
} else { } else {
@ -493,7 +497,7 @@ fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 {
/// ///
/// If the copy system call fails, we print a verbose error and return an empty error value. /// If the copy system call fails, we print a verbose error and return an empty error value.
/// ///
fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) { if b.compare && !need_copy(from, to, b) {
return Ok(()); return Ok(());
} }
@ -521,6 +525,21 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
return Err(()); return Err(());
} }
if b.strip && cfg!(not(windows)) {
match Command::new(&b.strip_program).arg(to).output() {
Ok(o) => {
if !o.status.success() {
crash!(
1,
"strip program failed: {}",
String::from_utf8(o.stderr).unwrap_or_default()
);
}
}
Err(e) => crash!(1, "strip program execution failed: {}", e),
}
}
if mode::chmod(&to, b.mode()).is_err() { if mode::chmod(&to, b.mode()).is_err() {
return Err(()); return Err(());
} }
@ -537,7 +556,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
}; };
let gid = meta.gid(); let gid = meta.gid();
match wrap_chown( match wrap_chown(
to.as_path(), to,
&meta, &meta,
Some(owner_id), Some(owner_id),
Some(gid), Some(gid),
@ -563,7 +582,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
Ok(g) => g, Ok(g) => g,
_ => crash!(1, "no such group: {}", b.group), _ => crash!(1, "no such group: {}", b.group),
}; };
match wrap_chgrp(to.as_path(), &meta, group_id, false, Verbosity::Normal) { match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) {
Ok(n) => { Ok(n) => {
if !n.is_empty() { if !n.is_empty() {
show_info!("{}", n); show_info!("{}", n);
@ -582,7 +601,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
let modified_time = FileTime::from_last_modification_time(&meta); let modified_time = FileTime::from_last_modification_time(&meta);
let accessed_time = FileTime::from_last_access_time(&meta); let accessed_time = FileTime::from_last_access_time(&meta);
match set_file_times(to.as_path(), accessed_time, modified_time) { match set_file_times(to, accessed_time, modified_time) {
Ok(_) => {} Ok(_) => {}
Err(e) => show_info!("{}", e), Err(e) => show_info!("{}", e),
} }
@ -611,7 +630,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
/// ///
/// Crashes the program if a nonexistent owner or group is specified in _b_. /// Crashes the program if a nonexistent owner or group is specified in _b_.
/// ///
fn need_copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> bool { fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
let from_meta = match fs::metadata(from) { let from_meta = match fs::metadata(from) {
Ok(meta) => meta, Ok(meta) => meta,
Err(_) => return true, Err(_) => return true,

View file

@ -303,7 +303,7 @@ fn exec(files: &[PathBuf], settings: &Settings) -> i32 {
} }
} }
fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Settings) -> i32 { fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> i32 {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target '{}' is not a directory", target_dir.display()); show_error!("target '{}' is not a directory", target_dir.display());
return 1; return 1;
@ -329,7 +329,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting
}; };
} }
} }
target_dir.clone() target_dir.to_path_buf()
} else { } else {
match srcpath.as_os_str().to_str() { match srcpath.as_os_str().to_str() {
Some(name) => { Some(name) => {
@ -370,7 +370,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting
} }
} }
fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result<Cow<'a, Path>> { fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let abssrc = canonicalize(src, CanonicalizeMode::Normal)?; let abssrc = canonicalize(src, CanonicalizeMode::Normal)?;
let absdst = canonicalize(dst, CanonicalizeMode::Normal)?; let absdst = canonicalize(dst, CanonicalizeMode::Normal)?;
let suffix_pos = abssrc let suffix_pos = abssrc
@ -390,7 +390,7 @@ fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result<Cow<'a, Path>> {
Ok(result.into()) Ok(result.into())
} }
fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> { fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
let mut backup_path = None; let mut backup_path = None;
let source: Cow<'_, Path> = if settings.relative { let source: Cow<'_, Path> = if settings.relative {
relative_path(&src, dst)? relative_path(&src, dst)?
@ -453,13 +453,13 @@ fn read_yes() -> bool {
} }
} }
fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf {
let mut p = path.as_os_str().to_str().unwrap().to_owned(); let mut p = path.as_os_str().to_str().unwrap().to_owned();
p.push_str(suffix); p.push_str(suffix);
PathBuf::from(p) PathBuf::from(p)
} }
fn numbered_backup_path(path: &PathBuf) -> PathBuf { fn numbered_backup_path(path: &Path) -> PathBuf {
let mut i: u64 = 1; let mut i: u64 = 1;
loop { loop {
let new_path = simple_backup_path(path, &format!(".~{}~", i)); let new_path = simple_backup_path(path, &format!(".~{}~", i));
@ -470,7 +470,7 @@ fn numbered_backup_path(path: &PathBuf) -> PathBuf {
} }
} }
fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
let test_path = simple_backup_path(path, &".~1~".to_owned()); let test_path = simple_backup_path(path, &".~1~".to_owned());
if test_path.exists() { if test_path.exists() {
return numbered_backup_path(path); return numbered_backup_path(path);

View file

@ -370,6 +370,7 @@ impl Config {
}) })
.or_else(|| termsize::get().map(|s| s.cols)); .or_else(|| termsize::get().map(|s| s.cols));
#[allow(clippy::needless_bool)]
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
false false
} else if options.is_present(options::SHOW_CONTROL_CHARS) { } else if options.is_present(options::SHOW_CONTROL_CHARS) {
@ -1042,7 +1043,7 @@ fn sort_entries(entries: &mut Vec<PathBuf>, config: &Config) {
.sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))), .sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))),
// The default sort in GNU ls is case insensitive // 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.to_string_lossy().to_lowercase()),
Sort::Version => entries.sort_by(version_cmp::version_cmp), Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)),
Sort::None => {} Sort::None => {}
} }
@ -1076,7 +1077,7 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
true true
} }
fn enter_directory(dir: &PathBuf, config: &Config) { fn enter_directory(dir: &Path, config: &Config) {
let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect)); let mut entries: Vec<_> = safe_unwrap!(fs::read_dir(dir).and_then(Iterator::collect));
entries.retain(|e| should_display(e, config)); entries.retain(|e| should_display(e, config));
@ -1101,7 +1102,7 @@ fn enter_directory(dir: &PathBuf, config: &Config) {
} }
} }
fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result<Metadata> { fn get_metadata(entry: &Path, config: &Config) -> std::io::Result<Metadata> {
if config.dereference { if config.dereference {
entry.metadata().or_else(|_| entry.symlink_metadata()) entry.metadata().or_else(|_| entry.symlink_metadata())
} else { } else {
@ -1109,7 +1110,7 @@ fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result<Metadata> {
} }
} }
fn display_dir_entry_size(entry: &PathBuf, config: &Config) -> (usize, usize) { fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) {
if let Ok(md) = get_metadata(entry, config) { if let Ok(md) = get_metadata(entry, config) {
( (
display_symlink_count(&md).len(), display_symlink_count(&md).len(),
@ -1204,7 +1205,7 @@ 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: &PathBuf, item: &Path,
strip: Option<&Path>, strip: Option<&Path>,
max_links: usize, max_links: usize,
max_size: usize, max_size: usize,

View file

@ -1,8 +1,9 @@
use std::{cmp::Ordering, path::PathBuf}; use std::cmp::Ordering;
use std::path::Path;
/// Compare pathbufs in a way that matches the GNU version sort, meaning that /// Compare paths in a way that matches the GNU version sort, meaning that
/// numbers get sorted in a natural way. /// numbers get sorted in a natural way.
pub(crate) fn version_cmp(a: &PathBuf, b: &PathBuf) -> Ordering { pub(crate) fn version_cmp(a: &Path, b: &Path) -> Ordering {
let a_string = a.to_string_lossy(); let a_string = a.to_string_lossy();
let b_string = b.to_string_lossy(); let b_string = b.to_string_lossy();
let mut a = a_string.chars().peekable(); let mut a = a_string.chars().peekable();

View file

@ -335,7 +335,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 {
0 0
} }
fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 { fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target {} is not a directory", target_dir.display()); show_error!("target {} is not a directory", target_dir.display());
return 1; return 1;
@ -373,7 +373,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
} }
} }
fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> { fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> {
let mut backup_path = None; let mut backup_path = None;
if to.exists() { if to.exists() {
@ -429,7 +429,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> {
/// A wrapper around `fs::rename`, so that if it fails, we try falling back on /// A wrapper around `fs::rename`, so that if it fails, we try falling back on
/// copying and removing. /// copying and removing.
fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { fn rename_with_fallback(from: &Path, to: &Path) -> io::Result<()> {
if fs::rename(from, to).is_err() { if fs::rename(from, to).is_err() {
// Get metadata without following symlinks // Get metadata without following symlinks
let metadata = from.symlink_metadata()?; let metadata = from.symlink_metadata()?;
@ -464,7 +464,7 @@ fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> {
/// Move the given symlink to the given destination. On Windows, dangling /// Move the given symlink to the given destination. On Windows, dangling
/// symlinks return an error. /// symlinks return an error.
#[inline] #[inline]
fn rename_symlink_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
let path_symlink_points_to = fs::read_link(from)?; let path_symlink_points_to = fs::read_link(from)?;
#[cfg(unix)] #[cfg(unix)]
{ {
@ -507,20 +507,20 @@ fn read_yes() -> bool {
} }
} }
fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf {
let mut p = path.to_string_lossy().into_owned(); let mut p = path.to_string_lossy().into_owned();
p.push_str(suffix); p.push_str(suffix);
PathBuf::from(p) PathBuf::from(p)
} }
fn numbered_backup_path(path: &PathBuf) -> PathBuf { fn numbered_backup_path(path: &Path) -> PathBuf {
(1_u64..) (1_u64..)
.map(|i| path.with_extension(format!("~{}~", i))) .map(|i| path.with_extension(format!("~{}~", i)))
.find(|p| !p.exists()) .find(|p| !p.exists())
.expect("cannot create backup") .expect("cannot create backup")
} }
fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
let test_path = path.with_extension("~1~"); let test_path = path.with_extension("~1~");
if test_path.exists() { if test_path.exists() {
numbered_backup_path(path) numbered_backup_path(path)
@ -529,7 +529,7 @@ fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
} }
} }
fn is_empty_dir(path: &PathBuf) -> bool { fn is_empty_dir(path: &Path) -> bool {
match fs::read_dir(path) { match fs::read_dir(path) {
Ok(contents) => contents.peekable().peek().is_none(), Ok(contents) => contents.peekable().peek().is_none(),
Err(_e) => false, Err(_e) => false,

View file

@ -166,8 +166,9 @@ mod tests {
let mut input = PeekReader::new(Cursor::new(&data)); let mut input = PeekReader::new(Cursor::new(&data));
let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little); let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little);
match sut.peek_read() { // Peek normal length
Ok(mut mem) => { let mut mem = sut.peek_read().unwrap();
assert_eq!(8, mem.length()); assert_eq!(8, mem.length());
assert_eq!(-2.0, mem.read_float(0, 8)); assert_eq!(-2.0, mem.read_float(0, 8));
@ -185,20 +186,10 @@ mod tests {
mem.zero_out_buffer(7, 8); mem.zero_out_buffer(7, 8);
assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6)); assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6));
}
Err(e) => {
assert!(false, e);
}
}
match sut.peek_read() { // Peek tail
Ok(mem) => { let mem = sut.peek_read().unwrap();
assert_eq!(2, mem.length()); assert_eq!(2, mem.length());
assert_eq!(0xffff, mem.read_uint(0, 2)); assert_eq!(0xffff, mem.read_uint(0, 2));
} }
Err(e) => {
assert!(false, e);
}
}
}
} }

View file

@ -118,7 +118,7 @@ struct OdOptions {
} }
impl OdOptions { impl OdOptions {
fn new<'a>(matches: ArgMatches<'a>, args: Vec<String>) -> Result<OdOptions, String> { fn new(matches: ArgMatches, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.value_of(options::ENDIAN) { let byte_order = match matches.value_of(options::ENDIAN) {
None => ByteOrder::Native, None => ByteOrder::Native,
Some("little") => ByteOrder::Little, Some("little") => ByteOrder::Little,

View file

@ -63,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
} }
if input_strings.len() == 2 { if input_strings.len() == 2 {
return Ok(CommandLineInputs::FileAndOffset(( return Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(), input_strings[0].to_string(),
n, n,
None, None,
))); )));
@ -106,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
Some(m), Some(m),
))), ))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(), input_strings[0].to_string(),
m, m,
None, None,
))), ))),
@ -118,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
let label = parse_offset_operand(&input_strings[2]); let label = parse_offset_operand(&input_strings[2]);
match (offset, label) { match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(), input_strings[0].to_string(),
n, n,
Some(m), Some(m),
))), ))),

View file

@ -15,7 +15,6 @@ use uucore::utmpx::{self, time, Utmpx};
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
use std::io::Result as IOResult;
use std::fs::File; use std::fs::File;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
@ -136,12 +135,8 @@ The utmp file will be {}",
}; };
if do_short_format { if do_short_format {
if let Err(e) = pk.short_pinky() { pk.short_pinky();
show_usage_error!("{}", e);
1
} else {
0 0
}
} else { } else {
pk.long_pinky() pk.long_pinky()
} }
@ -282,7 +277,7 @@ impl Pinky {
println!(); println!();
} }
fn short_pinky(&self) -> IOResult<()> { fn short_pinky(&self) {
if self.include_heading { if self.include_heading {
self.print_heading(); self.print_heading();
} }
@ -295,7 +290,6 @@ impl Pinky {
} }
} }
} }
Ok(())
} }
fn long_pinky(&self) -> i32 { fn long_pinky(&self) -> i32 {

View file

@ -199,8 +199,7 @@ pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<
} }
pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec<u8> { pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec<u8> {
let mut result: Vec<u8> = Vec::new(); let mut result = vec![0];
result.push(0);
for i in src { for i in src {
result = arrnum_int_mult(&result, radix_dest, radix_src); result = arrnum_int_mult(&result, radix_dest, radix_src);
result = arrnum_int_add(&result, radix_dest, *i); result = arrnum_int_add(&result, radix_dest, *i);
@ -226,8 +225,7 @@ pub fn base_conv_float(src: &[u8], radix_src: u8, radix_dest: u8) -> f64 {
// to implement this for arbitrary string input. // to implement this for arbitrary string input.
// until then, the below operates as an outline // until then, the below operates as an outline
// of how it would work. // of how it would work.
let mut result: Vec<u8> = Vec::new(); let result: Vec<u8> = vec![0];
result.push(0);
let mut factor: f64 = 1_f64; let mut factor: f64 = 1_f64;
let radix_src_float: f64 = f64::from(radix_src); let radix_src_float: f64 = f64::from(radix_src);
let mut r: f64 = 0_f64; let mut r: f64 = 0_f64;

View file

@ -263,9 +263,5 @@ pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option<St
}; };
// if we have a formatPrimitive, print its results // if we have a formatPrimitive, print its results
// according to the field-char appropriate Formatter // according to the field-char appropriate Formatter
if let Some(prim) = prim_opt { prim_opt.map(|prim| fmtr.primitive_to_str(&prim, field.clone()))
Some(fmtr.primitive_to_str(&prim, field.clone()))
} else {
None
}
} }

View file

@ -169,22 +169,22 @@ fn get_config(matches: &clap::ArgMatches) -> Config {
.expect(err_msg) .expect(err_msg)
.to_string(); .to_string();
} }
if matches.is_present(options::IGNORE_CASE) { if matches.is_present(options::FLAG_TRUNCATION) {
config.trunc_str = matches config.trunc_str = matches
.value_of(options::IGNORE_CASE) .value_of(options::FLAG_TRUNCATION)
.expect(err_msg) .expect(err_msg)
.to_string(); .to_string();
} }
if matches.is_present(options::WIDTH) { if matches.is_present(options::WIDTH) {
let width_str = matches.value_of(options::WIDTH).expect(err_msg).to_string(); let width_str = matches.value_of(options::WIDTH).expect(err_msg).to_string();
config.line_width = crash_if_err!(1, usize::from_str_radix(&width_str, 10)); config.line_width = crash_if_err!(1, (&width_str).parse::<usize>());
} }
if matches.is_present(options::GAP_SIZE) { if matches.is_present(options::GAP_SIZE) {
let gap_str = matches let gap_str = matches
.value_of(options::GAP_SIZE) .value_of(options::GAP_SIZE)
.expect(err_msg) .expect(err_msg)
.to_string(); .to_string();
config.gap_size = crash_if_err!(1, usize::from_str_radix(&gap_str, 10)); config.gap_size = crash_if_err!(1, (&gap_str).parse::<usize>());
} }
if matches.is_present(options::FORMAT_ROFF) { if matches.is_present(options::FORMAT_ROFF) {
config.format = OutFormat::Roff; config.format = OutFormat::Roff;
@ -195,8 +195,16 @@ fn get_config(matches: &clap::ArgMatches) -> Config {
config config
} }
fn read_input(input_files: &[String], config: &Config) -> HashMap<String, (Vec<String>, usize)> { struct FileContent {
let mut file_map: HashMap<String, (Vec<String>, usize)> = HashMap::new(); lines: Vec<String>,
chars_lines: Vec<Vec<char>>,
offset: usize,
}
type FileMap = HashMap<String, FileContent>;
fn read_input(input_files: &[String], config: &Config) -> FileMap {
let mut file_map: FileMap = HashMap::new();
let mut files = Vec::new(); let mut files = Vec::new();
if input_files.is_empty() { if input_files.is_empty() {
files.push("-"); files.push("-");
@ -207,7 +215,7 @@ fn read_input(input_files: &[String], config: &Config) -> HashMap<String, (Vec<S
} else { } else {
files.push(&input_files[0]); files.push(&input_files[0]);
} }
let mut lines_so_far: usize = 0; let mut offset: usize = 0;
for filename in files { for filename in files {
let reader: BufReader<Box<dyn Read>> = BufReader::new(if filename == "-" { let reader: BufReader<Box<dyn Read>> = BufReader::new(if filename == "-" {
Box::new(stdin()) Box::new(stdin())
@ -216,25 +224,33 @@ fn read_input(input_files: &[String], config: &Config) -> HashMap<String, (Vec<S
Box::new(file) Box::new(file)
}); });
let lines: Vec<String> = reader.lines().map(|x| crash_if_err!(1, x)).collect(); let lines: Vec<String> = reader.lines().map(|x| crash_if_err!(1, x)).collect();
// Indexing UTF-8 string requires walking from the beginning, which can hurts performance badly when the line is long.
// Since we will be jumping around the line a lot, we dump the content into a Vec<char>, which can be indexed in constant time.
let chars_lines: Vec<Vec<char>> = lines.iter().map(|x| x.chars().collect()).collect();
let size = lines.len(); let size = lines.len();
file_map.insert(filename.to_owned(), (lines, lines_so_far)); file_map.insert(
lines_so_far += size filename.to_owned(),
FileContent {
lines,
chars_lines,
offset,
},
);
offset += size
} }
file_map file_map
} }
fn create_word_set( /// Go through every lines in the input files and record each match occurance as a `WordRef`.
config: &Config, fn create_word_set(config: &Config, filter: &WordFilter, file_map: &FileMap) -> BTreeSet<WordRef> {
filter: &WordFilter,
file_map: &HashMap<String, (Vec<String>, usize)>,
) -> BTreeSet<WordRef> {
let reg = Regex::new(&filter.word_regex).unwrap(); let reg = Regex::new(&filter.word_regex).unwrap();
let ref_reg = Regex::new(&config.context_regex).unwrap(); let ref_reg = Regex::new(&config.context_regex).unwrap();
let mut word_set: BTreeSet<WordRef> = BTreeSet::new(); let mut word_set: BTreeSet<WordRef> = BTreeSet::new();
for (file, lines) in file_map.iter() { for (file, lines) in file_map.iter() {
let mut count: usize = 0; let mut count: usize = 0;
let offs = lines.1; let offs = lines.offset;
for line in &lines.0 { for line in &lines.lines {
// if -r, exclude reference from word set // if -r, exclude reference from word set
let (ref_beg, ref_end) = match ref_reg.find(line) { let (ref_beg, ref_end) = match ref_reg.find(line) {
Some(x) => (x.start(), x.end()), Some(x) => (x.start(), x.end()),
@ -271,12 +287,11 @@ fn create_word_set(
word_set word_set
} }
fn get_reference(config: &Config, word_ref: &WordRef, line: &str) -> String { fn get_reference(config: &Config, word_ref: &WordRef, line: &str, context_reg: &Regex) -> String {
if config.auto_ref { if config.auto_ref {
format!("{}:{}", word_ref.filename, word_ref.local_line_nr + 1) format!("{}:{}", word_ref.filename, word_ref.local_line_nr + 1)
} else if config.input_ref { } else if config.input_ref {
let reg = Regex::new(&config.context_regex).unwrap(); let (beg, end) = match context_reg.find(line) {
let (beg, end) = match reg.find(line) {
Some(x) => (x.start(), x.end()), Some(x) => (x.start(), x.end()),
None => (0, 0), None => (0, 0),
}; };
@ -329,57 +344,107 @@ fn trim_idx(s: &[char], beg: usize, end: usize) -> (usize, usize) {
} }
fn get_output_chunks( fn get_output_chunks(
all_before: &str, all_before: &[char],
keyword: &str, keyword: &str,
all_after: &str, all_after: &[char],
config: &Config, config: &Config,
) -> (String, String, String, String) { ) -> (String, String, String, String) {
assert_eq!(all_before.trim(), all_before); // Chunk size logics are mostly copied from the GNU ptx source.
assert_eq!(keyword.trim(), keyword); // https://github.com/MaiZure/coreutils-8.3/blob/master/src/ptx.c#L1234
assert_eq!(all_after.trim(), all_after); let half_line_size = (config.line_width / 2) as usize;
let mut head = String::new(); let max_before_size = cmp::max(half_line_size as isize - config.gap_size as isize, 0) as usize;
let mut before = String::new(); let max_after_size = cmp::max(
let mut after = String::new(); half_line_size as isize
let mut tail = String::new(); - (2 * config.trunc_str.len()) as isize
- keyword.len() as isize
let half_line_size = cmp::max( - 1,
(config.line_width / 2) as isize - (2 * config.trunc_str.len()) as isize,
0, 0,
) as usize; ) as usize;
let max_after_size = cmp::max(half_line_size as isize - keyword.len() as isize - 1, 0) as usize;
let max_before_size = half_line_size;
let all_before_vec: Vec<char> = all_before.chars().collect();
let all_after_vec: Vec<char> = all_after.chars().collect();
// get before // Allocate plenty space for all the chunks.
let mut bb_tmp = cmp::max(all_before.len() as isize - max_before_size as isize, 0) as usize; let mut head = String::with_capacity(half_line_size);
bb_tmp = trim_broken_word_left(&all_before_vec, bb_tmp, all_before.len()); let mut before = String::with_capacity(half_line_size);
let (before_beg, before_end) = trim_idx(&all_before_vec, bb_tmp, all_before.len()); let mut after = String::with_capacity(half_line_size);
before.push_str(&all_before[before_beg..before_end]); let mut tail = String::with_capacity(half_line_size);
// the before chunk
// trim whitespace away from all_before to get the index where the before chunk should end.
let (_, before_end) = trim_idx(all_before, 0, all_before.len());
// the minimum possible begin index of the before_chunk is the end index minus the length.
let before_beg = cmp::max(before_end as isize - max_before_size as isize, 0) as usize;
// in case that falls in the middle of a word, trim away the word.
let before_beg = trim_broken_word_left(all_before, before_beg, before_end);
// trim away white space.
let (before_beg, before_end) = trim_idx(all_before, before_beg, before_end);
// and get the string.
let before_str: String = all_before[before_beg..before_end].iter().collect();
before.push_str(&before_str);
assert!(max_before_size >= before.len()); assert!(max_before_size >= before.len());
// get after // the after chunk
let mut ae_tmp = cmp::min(max_after_size, all_after.len());
ae_tmp = trim_broken_word_right(&all_after_vec, 0, ae_tmp); // must be no longer than the minimum between the max size and the total available string.
let (after_beg, after_end) = trim_idx(&all_after_vec, 0, ae_tmp); let after_end = cmp::min(max_after_size, all_after.len());
after.push_str(&all_after[after_beg..after_end]); // in case that falls in the middle of a word, trim away the word.
let after_end = trim_broken_word_right(all_after, 0, after_end);
// trim away white space.
let (_, after_end) = trim_idx(all_after, 0, after_end);
// and get the string
let after_str: String = all_after[0..after_end].iter().collect();
after.push_str(&after_str);
assert!(max_after_size >= after.len()); assert!(max_after_size >= after.len());
// get tail // the tail chunk
let max_tail_size = max_before_size - before.len();
let (tb, _) = trim_idx(&all_after_vec, after_end, all_after.len());
let mut te_tmp = cmp::min(tb + max_tail_size, all_after.len());
te_tmp = trim_broken_word_right(&all_after_vec, tb, te_tmp);
let (tail_beg, tail_end) = trim_idx(&all_after_vec, tb, te_tmp);
tail.push_str(&all_after[tail_beg..tail_end]);
// get head // max size of the tail chunk = max size of left half - space taken by before chunk - gap size.
let max_head_size = max_after_size - after.len(); let max_tail_size = cmp::max(
let (_, he) = trim_idx(&all_before_vec, 0, before_beg); max_before_size as isize - before.len() as isize - config.gap_size as isize,
let mut hb_tmp = cmp::max(he as isize - max_head_size as isize, 0) as usize; 0,
hb_tmp = trim_broken_word_left(&all_before_vec, hb_tmp, he); ) as usize;
let (head_beg, head_end) = trim_idx(&all_before_vec, hb_tmp, he);
head.push_str(&all_before[head_beg..head_end]); // the tail chunk takes text starting from where the after chunk ends (with whitespaces trimmed).
let (tail_beg, _) = trim_idx(all_after, after_end, all_after.len());
// end = begin + max length
let tail_end = cmp::min(all_after.len(), tail_beg + max_tail_size) as usize;
// in case that falls in the middle of a word, trim away the word.
let tail_end = trim_broken_word_right(all_after, tail_beg, tail_end);
// trim away whitespace again.
let (tail_beg, tail_end) = trim_idx(all_after, tail_beg, tail_end);
// and get the string
let tail_str: String = all_after[tail_beg..tail_end].iter().collect();
tail.push_str(&tail_str);
// the head chunk
// max size of the head chunk = max size of right half - space taken by after chunk - gap size.
let max_head_size = cmp::max(
max_after_size as isize - after.len() as isize - config.gap_size as isize,
0,
) as usize;
// the head chunk takes text from before the before chunk
let (_, head_end) = trim_idx(all_before, 0, before_beg);
// begin = end - max length
let head_beg = cmp::max(head_end as isize - max_head_size as isize, 0) as usize;
// in case that falls in the middle of a word, trim away the word.
let head_beg = trim_broken_word_left(all_before, head_beg, head_end);
// trim away white space again.
let (head_beg, head_end) = trim_idx(all_before, head_beg, head_end);
// and get the string.
let head_str: String = all_before[head_beg..head_end].iter().collect();
head.push_str(&head_str);
// put right context truncation string if needed // put right context truncation string if needed
if after_end != all_after.len() && tail_beg == tail_end { if after_end != all_after.len() && tail_beg == tail_end {
@ -395,11 +460,6 @@ fn get_output_chunks(
head = format!("{}{}", config.trunc_str, head); head = format!("{}{}", config.trunc_str, head);
} }
// add space before "after" if needed
if !after.is_empty() {
after = format!(" {}", after);
}
(tail, before, after, head) (tail, before, after, head)
} }
@ -412,70 +472,95 @@ fn tex_mapper(x: char) -> String {
} }
} }
fn adjust_tex_str(context: &str) -> String { /// Escape special characters for TeX.
let ws_reg = Regex::new(r"[\t\n\v\f\r ]").unwrap(); fn format_tex_field(s: &str) -> String {
let mut fix: String = ws_reg.replace_all(context, " ").trim().to_owned(); let mapped_chunks: Vec<String> = s.chars().map(tex_mapper).collect();
let mapped_chunks: Vec<String> = fix.chars().map(tex_mapper).collect(); mapped_chunks.join("")
fix = mapped_chunks.join("");
fix
} }
fn format_tex_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String { fn format_tex_line(
config: &Config,
word_ref: &WordRef,
line: &str,
chars_line: &[char],
reference: &str,
) -> String {
let mut output = String::new(); let mut output = String::new();
output.push_str(&format!("\\{} ", config.macro_name)); output.push_str(&format!("\\{} ", config.macro_name));
let all_before = if config.input_ref { let all_before = if config.input_ref {
let before = &line[0..word_ref.position]; let before = &line[0..word_ref.position];
adjust_tex_str(before.trim().trim_start_matches(reference)) let before_start_trimoff =
word_ref.position - before.trim_start_matches(reference).trim_start().len();
let before_end_index = before.len();
&chars_line[before_start_trimoff..cmp::max(before_end_index, before_start_trimoff)]
} else { } else {
adjust_tex_str(&line[0..word_ref.position]) let before_chars_trim_idx = (0, word_ref.position);
&chars_line[before_chars_trim_idx.0..before_chars_trim_idx.1]
}; };
let keyword = adjust_tex_str(&line[word_ref.position..word_ref.position_end]); let keyword = &line[word_ref.position..word_ref.position_end];
let all_after = adjust_tex_str(&line[word_ref.position_end..line.len()]); let after_chars_trim_idx = (word_ref.position_end, chars_line.len());
let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1];
let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config);
output.push_str(&format!( output.push_str(&format!(
"{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}", "{5}{0}{6}{5}{1}{6}{5}{2}{6}{5}{3}{6}{5}{4}{6}",
tail, before, keyword, after, head, "{", "}" format_tex_field(&tail),
format_tex_field(&before),
format_tex_field(keyword),
format_tex_field(&after),
format_tex_field(&head),
"{",
"}"
)); ));
if config.auto_ref || config.input_ref { if config.auto_ref || config.input_ref {
output.push_str(&format!("{}{}{}", "{", adjust_tex_str(&reference), "}")); output.push_str(&format!("{}{}{}", "{", format_tex_field(&reference), "}"));
} }
output output
} }
fn adjust_roff_str(context: &str) -> String { fn format_roff_field(s: &str) -> String {
let ws_reg = Regex::new(r"[\t\n\v\f\r]").unwrap(); s.replace("\"", "\"\"")
ws_reg
.replace_all(context, " ")
.replace("\"", "\"\"")
.trim()
.to_owned()
} }
fn format_roff_line(config: &Config, word_ref: &WordRef, line: &str, reference: &str) -> String { fn format_roff_line(
config: &Config,
word_ref: &WordRef,
line: &str,
chars_line: &[char],
reference: &str,
) -> String {
let mut output = String::new(); let mut output = String::new();
output.push_str(&format!(".{}", config.macro_name)); output.push_str(&format!(".{}", config.macro_name));
let all_before = if config.input_ref { let all_before = if config.input_ref {
let before = &line[0..word_ref.position]; let before = &line[0..word_ref.position];
adjust_roff_str(before.trim().trim_start_matches(reference)) let before_start_trimoff =
word_ref.position - before.trim_start_matches(reference).trim_start().len();
let before_end_index = before.len();
&chars_line[before_start_trimoff..cmp::max(before_end_index, before_start_trimoff)]
} else { } else {
adjust_roff_str(&line[0..word_ref.position]) let before_chars_trim_idx = (0, word_ref.position);
&chars_line[before_chars_trim_idx.0..before_chars_trim_idx.1]
}; };
let keyword = adjust_roff_str(&line[word_ref.position..word_ref.position_end]); let keyword = &line[word_ref.position..word_ref.position_end];
let all_after = adjust_roff_str(&line[word_ref.position_end..line.len()]); let after_chars_trim_idx = (word_ref.position_end, chars_line.len());
let all_after = &chars_line[after_chars_trim_idx.0..after_chars_trim_idx.1];
let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config); let (tail, before, after, head) = get_output_chunks(&all_before, &keyword, &all_after, &config);
output.push_str(&format!( output.push_str(&format!(
" \"{}\" \"{}\" \"{}{}\" \"{}\"", " \"{}\" \"{}\" \"{}{}\" \"{}\"",
tail, before, keyword, after, head format_roff_field(&tail),
format_roff_field(&before),
format_roff_field(keyword),
format_roff_field(&after),
format_roff_field(&head)
)); ));
if config.auto_ref || config.input_ref { if config.auto_ref || config.input_ref {
output.push_str(&format!(" \"{}\"", adjust_roff_str(&reference))); output.push_str(&format!(" \"{}\"", format_roff_field(&reference)));
} }
output output
} }
fn write_traditional_output( fn write_traditional_output(
config: &Config, config: &Config,
file_map: &HashMap<String, (Vec<String>, usize)>, file_map: &FileMap,
words: &BTreeSet<WordRef>, words: &BTreeSet<WordRef>,
output_filename: &str, output_filename: &str,
) { ) {
@ -485,19 +570,39 @@ fn write_traditional_output(
let file = crash_if_err!(1, File::create(output_filename)); let file = crash_if_err!(1, File::create(output_filename));
Box::new(file) Box::new(file)
}); });
let context_reg = Regex::new(&config.context_regex).unwrap();
for word_ref in words.iter() { for word_ref in words.iter() {
let file_map_value: &(Vec<String>, usize) = file_map let file_map_value: &FileContent = file_map
.get(&(word_ref.filename)) .get(&(word_ref.filename))
.expect("Missing file in file map"); .expect("Missing file in file map");
let (ref lines, _) = *(file_map_value); let FileContent {
let reference = get_reference(config, word_ref, &lines[word_ref.local_line_nr]); ref lines,
ref chars_lines,
offset: _,
} = *(file_map_value);
let reference = get_reference(
config,
word_ref,
&lines[word_ref.local_line_nr],
&context_reg,
);
let output_line: String = match config.format { let output_line: String = match config.format {
OutFormat::Tex => { OutFormat::Tex => format_tex_line(
format_tex_line(config, word_ref, &lines[word_ref.local_line_nr], &reference) config,
} word_ref,
OutFormat::Roff => { &lines[word_ref.local_line_nr],
format_roff_line(config, word_ref, &lines[word_ref.local_line_nr], &reference) &chars_lines[word_ref.local_line_nr],
} &reference,
),
OutFormat::Roff => format_roff_line(
config,
word_ref,
&lines[word_ref.local_line_nr],
&chars_lines[word_ref.local_line_nr],
&reference,
),
OutFormat::Dumb => crash!(1, "There is no dumb format with GNU extensions disabled"), OutFormat::Dumb => crash!(1, "There is no dumb format with GNU extensions disabled"),
}; };
crash_if_err!(1, writeln!(writer, "{}", output_line)); crash_if_err!(1, writeln!(writer, "{}", output_line));

View file

@ -13,7 +13,7 @@ extern crate uucore;
use clap::{App, Arg}; use clap::{App, Arg};
use std::fs; use std::fs;
use std::io::{stdout, Write}; use std::io::{stdout, Write};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
const NAME: &str = "readlink"; const NAME: &str = "readlink";
@ -160,8 +160,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
fn show(path: &PathBuf, no_newline: bool, use_zero: bool) { fn show(path: &Path, no_newline: bool, use_zero: bool) {
let path = path.as_path().to_str().unwrap(); let path = path.to_str().unwrap();
if use_zero { if use_zero {
print!("{}\0", path); print!("{}\0", path);
} else if no_newline { } else if no_newline {

View file

@ -12,7 +12,7 @@ extern crate uucore;
use clap::{App, Arg}; use clap::{App, Arg};
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
static ABOUT: &str = "print the resolved path"; static ABOUT: &str = "print the resolved path";
@ -82,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
retcode retcode
} }
fn resolve_path(p: &PathBuf, strip: bool, zero: bool, quiet: bool) -> bool { fn resolve_path(p: &Path, strip: bool, zero: bool, quiet: bool) -> bool {
let abs = canonicalize(p, CanonicalizeMode::Normal).unwrap(); let abs = canonicalize(p, CanonicalizeMode::Normal).unwrap();
if strip { if strip {

View file

@ -176,7 +176,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} else if matches.is_present(OPT_PROMPT_MORE) { } else if matches.is_present(OPT_PROMPT_MORE) {
InteractiveMode::Once InteractiveMode::Once
} else if matches.is_present(OPT_INTERACTIVE) { } else if matches.is_present(OPT_INTERACTIVE) {
match &matches.value_of(OPT_INTERACTIVE).unwrap()[..] { match matches.value_of(OPT_INTERACTIVE).unwrap() {
"none" => InteractiveMode::None, "none" => InteractiveMode::None,
"once" => InteractiveMode::Once, "once" => InteractiveMode::Once,
"always" => InteractiveMode::Always, "always" => InteractiveMode::Always,

View file

@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mut largest_dec = 0; let mut largest_dec = 0;
let mut padding = 0; let mut padding = 0;
let first = if numbers.len() > 1 { let first = if numbers.len() > 1 {
let slice = &numbers[0][..]; let slice = numbers[0];
let len = slice.len(); let len = slice.len();
let dec = slice.find('.').unwrap_or(len); let dec = slice.find('.').unwrap_or(len);
largest_dec = len - dec; largest_dec = len - dec;
@ -118,7 +118,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
1.0 1.0
}; };
let increment = if numbers.len() > 2 { let increment = if numbers.len() > 2 {
let slice = &numbers[1][..]; let slice = numbers[1];
let len = slice.len(); let len = slice.len();
let dec = slice.find('.').unwrap_or(len); let dec = slice.find('.').unwrap_or(len);
largest_dec = cmp::max(largest_dec, len - dec); largest_dec = cmp::max(largest_dec, len - dec);
@ -134,11 +134,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
1.0 1.0
}; };
if increment == 0.0 { if increment == 0.0 {
show_error!("increment value: '{}'", &numbers[1][..]); show_error!("increment value: '{}'", numbers[1]);
return 1; return 1;
} }
let last = { let last = {
let slice = &numbers[numbers.len() - 1][..]; let slice = numbers[numbers.len() - 1];
padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len()));
match parse_float(slice) { match parse_float(slice) {
Ok(n) => n, Ok(n) => n,

View file

@ -259,6 +259,7 @@ static AFTER_HELP: &str =
"; ";
pub mod options { pub mod options {
pub const FORCE: &str = "force";
pub const FILE: &str = "file"; pub const FILE: &str = "file";
pub const ITERATIONS: &str = "iterations"; pub const ITERATIONS: &str = "iterations";
pub const SIZE: &str = "size"; pub const SIZE: &str = "size";
@ -278,6 +279,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.about(ABOUT) .about(ABOUT)
.after_help(AFTER_HELP) .after_help(AFTER_HELP)
.usage(&usage[..]) .usage(&usage[..])
.arg(
Arg::with_name(options::FORCE)
.long(options::FORCE)
.short("f")
.help("change permissions to allow writing if necessary"),
)
.arg( .arg(
Arg::with_name(options::ITERATIONS) Arg::with_name(options::ITERATIONS)
.long(options::ITERATIONS) .long(options::ITERATIONS)
@ -354,13 +361,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// TODO: implement --random-source // TODO: implement --random-source
// TODO: implement --force let force = matches.is_present(options::FORCE);
let remove = matches.is_present(options::REMOVE); let remove = matches.is_present(options::REMOVE);
let size_arg = match matches.value_of(options::SIZE) { let size_arg = matches.value_of(options::SIZE).map(|s| s.to_string());
Some(s) => Some(s.to_string()),
None => None,
};
let size = get_size(size_arg); let size = get_size(size_arg);
let exact = matches.is_present(options::EXACT) && size.is_none(); // if -s is given, ignore -x let exact = matches.is_present(options::EXACT) && size.is_none(); // if -s is given, ignore -x
let zero = matches.is_present(options::ZERO); let zero = matches.is_present(options::ZERO);
@ -375,7 +378,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
for path_str in matches.values_of(options::FILE).unwrap() { for path_str in matches.values_of(options::FILE).unwrap() {
wipe_file(&path_str, iterations, remove, size, exact, zero, verbose); wipe_file(
&path_str, iterations, remove, size, exact, zero, verbose, force,
);
} }
0 0
@ -431,6 +436,7 @@ fn pass_name(pass_type: PassType) -> String {
} }
} }
#[allow(clippy::too_many_arguments)]
fn wipe_file( fn wipe_file(
path_str: &str, path_str: &str,
n_passes: usize, n_passes: usize,
@ -439,18 +445,37 @@ fn wipe_file(
exact: bool, exact: bool,
zero: bool, zero: bool,
verbose: bool, verbose: bool,
force: bool,
) { ) {
// Get these potential errors out of the way first // Get these potential errors out of the way first
let path: &Path = Path::new(path_str); let path: &Path = Path::new(path_str);
if !path.exists() { if !path.exists() {
println!("{}: {}: No such file or directory", NAME, path.display()); show_error!("{}: No such file or directory", path.display());
return; return;
} }
if !path.is_file() { if !path.is_file() {
println!("{}: {}: Not a file", NAME, path.display()); show_error!("{}: Not a file", path.display());
return; return;
} }
// If force is true, set file permissions to not-readonly.
if force {
let metadata = match fs::metadata(path) {
Ok(m) => m,
Err(e) => {
show_error!("{}", e);
return;
}
};
let mut perms = metadata.permissions();
perms.set_readonly(false);
if let Err(e) = fs::set_permissions(path, perms) {
show_error!("{}", e);
return;
}
}
// Fill up our pass sequence // Fill up our pass sequence
let mut pass_sequence: Vec<PassType> = Vec::new(); let mut pass_sequence: Vec<PassType> = Vec::new();
@ -489,11 +514,13 @@ fn wipe_file(
{ {
let total_passes: usize = pass_sequence.len(); let total_passes: usize = pass_sequence.len();
let mut file: File = OpenOptions::new() let mut file: File = match OpenOptions::new().write(true).truncate(false).open(path) {
.write(true) Ok(f) => f,
.truncate(false) Err(e) => {
.open(path) show_error!("{}: failed to open for writing: {}", path.display(), e);
.expect("Failed to open file for writing"); return;
}
};
// NOTE: it does not really matter what we set for total_bytes and gen_type here, so just // NOTE: it does not really matter what we set for total_bytes and gen_type here, so just
// use bogus values // use bogus values
@ -523,14 +550,23 @@ fn wipe_file(
} }
} }
// size is an optional argument for exactly how many bytes we want to shred // size is an optional argument for exactly how many bytes we want to shred
do_pass(&mut file, path, &mut generator, *pass_type, size) match do_pass(&mut file, path, &mut generator, *pass_type, size) {
.expect("File write pass failed"); Ok(_) => {}
Err(e) => {
show_error!("{}: File write pass failed: {}", path.display(), e);
}
}
// Ignore failed writes; just keep trying // Ignore failed writes; just keep trying
} }
} }
if remove { if remove {
do_remove(path, path_str, verbose).expect("Failed to remove file"); match do_remove(path, path_str, verbose) {
Ok(_) => {}
Err(e) => {
show_error!("{}: failed to remove file: {}", path.display(), e);
}
}
} }
} }

View file

@ -0,0 +1,33 @@
# Benchmarking sort
Most of the time when sorting is spent comparing lines. The comparison functions however differ based
on which arguments are passed to `sort`, therefore it is important to always benchmark multiple scenarios.
This is an overwiew over what was benchmarked, and if you make changes to `sort`, you are encouraged to check
how performance was affected for the workloads listed below. Feel free to add other workloads to the
list that we should improve / make sure not to regress.
Run `cargo build --release` before benchmarking after you make a change!
## Sorting a wordlist
- Get a wordlist, for example with [words](https://en.wikipedia.org/wiki/Words_(Unix)) on Linux. The exact wordlist
doesn't matter for performance comparisons. In this example I'm using `/usr/share/dict/american-english` as the wordlist.
- Shuffle the wordlist by running `sort -R /usr/share/dict/american-english > shuffled_wordlist.txt`.
- Benchmark sorting the wordlist with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -o output.txt"`.
## Sorting a wordlist with ignore_case
- Same wordlist as above
- Benchmark sorting the wordlist ignoring the case with hyperfine: `hyperfine "target/release/coreutils sort shuffled_wordlist.txt -f -o output.txt"`.
## Sorting numbers
- Generate a list of numbers: `seq 0 100000 | sort -R > shuffled_numbers.txt`.
- Benchmark numeric sorting with hyperfine: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"`.
## Stdout and stdin performance
Try to run the above benchmarks by piping the input through stdin (standard input) and redirect the
output through stdout (standard output):
- Remove the input file from the arguments and add `cat [inputfile] | ` at the beginning.
- Remove `-o output.txt` and add `> output.txt` at the end.
Example: `hyperfine "target/release/coreutils sort shuffled_numbers.txt -n -o output.txt"` becomes
`hyperfine "cat shuffled_numbers.txt | target/release/coreutils sort -n > output.txt`
- Check that performance is similar to the original benchmark.

View file

@ -15,11 +15,13 @@ edition = "2018"
path = "src/sort.rs" path = "src/sort.rs"
[dependencies] [dependencies]
rayon = "1.5"
rand = "0.7" rand = "0.7"
clap = "2.33" clap = "2.33"
twox-hash = "1.6.0" fnv = "1.0.7"
itertools = "0.8.0" itertools = "0.10.0"
semver = "0.9.0" semver = "0.9.0"
smallvec = "1.6.1"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["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" }

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/stdbuf.rs" path = "src/stdbuf.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
tempfile = "3.1" tempfile = "3.1"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -35,8 +35,8 @@ extern "C" {
fn set_buffer(stream: *mut FILE, value: &str) { fn set_buffer(stream: *mut FILE, value: &str) {
let (mode, size): (c_int, size_t) = match value { let (mode, size): (c_int, size_t) = match value {
"0" => (_IONBF, 0 as size_t), "0" => (_IONBF, 0_usize),
"L" => (_IOLBF, 0 as size_t), "L" => (_IOLBF, 0_usize),
input => { input => {
let buff_size: usize = match input.parse() { let buff_size: usize = match input.parse() {
Ok(num) => num, Ok(num) => num,

View file

@ -10,7 +10,8 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use getopts::{Matches, Options}; use clap::{App, AppSettings, Arg, ArgMatches};
use std::convert::TryFrom;
use std::fs::File; use std::fs::File;
use std::io::{self, Write}; use std::io::{self, Write};
use std::os::unix::process::ExitStatusExt; use std::os::unix::process::ExitStatusExt;
@ -19,8 +20,35 @@ use std::process::Command;
use tempfile::tempdir; use tempfile::tempdir;
use tempfile::TempDir; use tempfile::TempDir;
static NAME: &str = "stdbuf";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str =
"Run COMMAND, with modified buffering operations for its standard streams.\n\n\
Mandatory arguments to long options are mandatory for short options too.";
static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\
This option is invalid with standard input.\n\n\
If MODE is '0' the corresponding stream will be unbuffered.\n\n\
Otherwise MODE is a number which may be followed by one of the following:\n\n\
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\
In this case the corresponding stream will be fully buffered with the buffer size set to \
MODE bytes.\n\n\
NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \
that will override corresponding settings changed by 'stdbuf'.\n\
Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \
and are thus unaffected by 'stdbuf' settings.\n";
mod options {
pub const INPUT: &str = "input";
pub const INPUT_SHORT: &str = "i";
pub const OUTPUT: &str = "output";
pub const OUTPUT_SHORT: &str = "o";
pub const ERROR: &str = "error";
pub const ERROR_SHORT: &str = "e";
pub const COMMAND: &str = "command";
}
fn get_usage() -> String {
format!("{0} OPTION... COMMAND", executable!())
}
const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so"));
@ -36,16 +64,19 @@ struct ProgramOptions {
stderr: BufferType, stderr: BufferType,
} }
enum ErrMsg { impl<'a> TryFrom<&ArgMatches<'a>> for ProgramOptions {
Retry, type Error = ProgramOptionsError;
Fatal,
fn try_from(matches: &ArgMatches) -> Result<Self, Self::Error> {
Ok(ProgramOptions {
stdin: check_option(&matches, options::INPUT)?,
stdout: check_option(&matches, options::OUTPUT)?,
stderr: check_option(&matches, options::ERROR)?,
})
}
} }
enum OkMsg { struct ProgramOptionsError(String);
Buffering,
Help,
Version,
}
#[cfg(any( #[cfg(any(
target_os = "linux", target_os = "linux",
@ -73,31 +104,6 @@ fn preload_strings() -> (&'static str, &'static str) {
crash!(1, "Command not supported for this operating system!") crash!(1, "Command not supported for this operating system!")
} }
fn print_version() {
println!("{} {}", NAME, VERSION);
}
fn print_usage(opts: &Options) {
let brief = "Run COMMAND, with modified buffering operations for its standard streams\n \
Mandatory arguments to long options are mandatory for short options too.";
let explanation = "If MODE is 'L' the corresponding stream will be line buffered.\n \
This option is invalid with standard input.\n\n \
If MODE is '0' the corresponding stream will be unbuffered.\n\n \
Otherwise MODE is a number which may be followed by one of the following:\n\n \
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n \
In this case the corresponding stream will be fully buffered with the buffer size set to \
MODE bytes.\n\n \
NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for e.g.) then \
that will override corresponding settings changed by 'stdbuf'.\n \
Also some filters (like 'dd' and 'cat' etc.) don't use streams for I/O, \
and are thus unaffected by 'stdbuf' settings.\n";
println!("{} {}", NAME, VERSION);
println!();
println!("Usage: stdbuf OPTION... COMMAND");
println!();
println!("{}\n{}", opts.usage(brief), explanation);
}
fn parse_size(size: &str) -> Option<u64> { fn parse_size(size: &str) -> Option<u64> {
let ext = size.trim_start_matches(|c: char| c.is_digit(10)); let ext = size.trim_start_matches(|c: char| c.is_digit(10));
let num = size.trim_end_matches(char::is_alphabetic); let num = size.trim_end_matches(char::is_alphabetic);
@ -133,63 +139,26 @@ fn parse_size(size: &str) -> Option<u64> {
Some(buf_size * base.pow(power)) Some(buf_size * base.pow(power))
} }
fn check_option(matches: &Matches, name: &str, modified: &mut bool) -> Option<BufferType> { fn check_option(matches: &ArgMatches, name: &str) -> Result<BufferType, ProgramOptionsError> {
match matches.opt_str(name) { match matches.value_of(name) {
Some(value) => { Some(value) => match value {
*modified = true;
match &value[..] {
"L" => { "L" => {
if name == "input" { if name == options::INPUT {
show_info!("line buffering stdin is meaningless"); Err(ProgramOptionsError("line buffering stdin is meaningless".to_string()))
None
} else { } else {
Some(BufferType::Line) Ok(BufferType::Line)
} }
} }
x => { x => {
let size = match parse_size(x) { let size = match parse_size(x) {
Some(m) => m, Some(m) => m,
None => { None => return Err(ProgramOptionsError(format!("invalid mode {}", x))),
show_error!("Invalid mode {}", x);
return None;
}
}; };
Some(BufferType::Size(size)) Ok(BufferType::Size(size))
} }
},
None => Ok(BufferType::Default),
} }
}
None => Some(BufferType::Default),
}
}
fn parse_options(
args: &[String],
options: &mut ProgramOptions,
optgrps: &Options,
) -> Result<OkMsg, ErrMsg> {
let matches = match optgrps.parse(args) {
Ok(m) => m,
Err(_) => return Err(ErrMsg::Retry),
};
if matches.opt_present("help") {
return Ok(OkMsg::Help);
}
if matches.opt_present("version") {
return Ok(OkMsg::Version);
}
let mut modified = false;
options.stdin = check_option(&matches, "input", &mut modified).ok_or(ErrMsg::Fatal)?;
options.stdout = check_option(&matches, "output", &mut modified).ok_or(ErrMsg::Fatal)?;
options.stderr = check_option(&matches, "error", &mut modified).ok_or(ErrMsg::Fatal)?;
if matches.free.len() != 1 {
return Err(ErrMsg::Retry);
}
if !modified {
show_error!("you must specify a buffering mode option");
return Err(ErrMsg::Fatal);
}
Ok(OkMsg::Buffering)
} }
fn set_command_env(command: &mut Command, buffer_name: &str, buffer_type: BufferType) { fn set_command_env(command: &mut Command, buffer_name: &str, buffer_type: BufferType) {
@ -215,72 +184,62 @@ fn get_preload_env(tmp_dir: &mut TempDir) -> io::Result<(String, PathBuf)> {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let usage = get_usage();
let mut opts = Options::new(); let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(LONG_HELP)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(options::INPUT)
.long(options::INPUT)
.short(options::INPUT_SHORT)
.help("adjust standard input stream buffering")
.value_name("MODE")
.required_unless_one(&[options::OUTPUT, options::ERROR]),
)
.arg(
Arg::with_name(options::OUTPUT)
.long(options::OUTPUT)
.short(options::OUTPUT_SHORT)
.help("adjust standard output stream buffering")
.value_name("MODE")
.required_unless_one(&[options::INPUT, options::ERROR]),
)
.arg(
Arg::with_name(options::ERROR)
.long(options::ERROR)
.short(options::ERROR_SHORT)
.help("adjust standard error stream buffering")
.value_name("MODE")
.required_unless_one(&[options::INPUT, options::OUTPUT]),
)
.arg(
Arg::with_name(options::COMMAND)
.multiple(true)
.takes_value(true)
.hidden(true)
.required(true),
)
.get_matches_from(args);
opts.optopt( let options = ProgramOptions::try_from(&matches)
"i", .unwrap_or_else(|e| crash!(125, "{}\nTry 'stdbuf --help' for more information.", e.0));
"input",
"adjust standard input stream buffering",
"MODE",
);
opts.optopt(
"o",
"output",
"adjust standard output stream buffering",
"MODE",
);
opts.optopt(
"e",
"error",
"adjust standard error stream buffering",
"MODE",
);
opts.optflag("", "help", "display this help and exit");
opts.optflag("", "version", "output version information and exit");
let mut options = ProgramOptions { let mut command_values = matches.values_of::<&str>(options::COMMAND).unwrap();
stdin: BufferType::Default, let mut command = Command::new(command_values.next().unwrap());
stdout: BufferType::Default, let command_params: Vec<&str> = command_values.collect();
stderr: BufferType::Default,
};
let mut command_idx: i32 = -1;
for i in 1..=args.len() {
match parse_options(&args[1..i], &mut options, &opts) {
Ok(OkMsg::Buffering) => {
command_idx = (i as i32) - 1;
break;
}
Ok(OkMsg::Help) => {
print_usage(&opts);
return 0;
}
Ok(OkMsg::Version) => {
print_version();
return 0;
}
Err(ErrMsg::Fatal) => break,
Err(ErrMsg::Retry) => continue,
}
}
if command_idx == -1 {
crash!(
125,
"Invalid options\nTry 'stdbuf --help' for more information."
);
}
let command_name = &args[command_idx as usize];
let mut command = Command::new(command_name);
let mut tmp_dir = tempdir().unwrap(); let mut tmp_dir = tempdir().unwrap();
let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir)); let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir));
command command.env(preload_env, libstdbuf);
.args(&args[(command_idx as usize) + 1..])
.env(preload_env, libstdbuf);
set_command_env(&mut command, "_STDBUF_I", options.stdin); set_command_env(&mut command, "_STDBUF_I", options.stdin);
set_command_env(&mut command, "_STDBUF_O", options.stdout); set_command_env(&mut command, "_STDBUF_O", options.stdout);
set_command_env(&mut command, "_STDBUF_E", options.stderr); set_command_env(&mut command, "_STDBUF_E", options.stderr);
command.args(command_params);
let mut process = match command.spawn() { let mut process = match command.spawn() {
Ok(p) => p, Ok(p) => p,
Err(e) => crash!(1, "failed to execute process: {}", e), Err(e) => crash!(1, "failed to execute process: {}", e),

View file

@ -75,7 +75,7 @@ fn open(name: &str) -> Result<Box<dyn Read>> {
"Is a directory", "Is a directory",
)); ));
}; };
if !path.metadata().is_ok() { if path.metadata().is_err() {
return Err(std::io::Error::new( return Err(std::io::Error::new(
std::io::ErrorKind::NotFound, std::io::ErrorKind::NotFound,
"No such file or directory", "No such file or directory",

View file

@ -90,7 +90,7 @@ fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) -> i32 {
Box::new(stdin()) as Box<dyn Read> Box::new(stdin()) as Box<dyn Read>
} else { } else {
let path = Path::new(filename); let path = Path::new(filename);
if path.is_dir() || !path.metadata().is_ok() { if path.is_dir() || path.metadata().is_err() {
show_error!( show_error!(
"failed to open '{}' for reading: No such file or directory", "failed to open '{}' for reading: No such file or directory",
filename filename

View file

@ -55,16 +55,16 @@ fn two(args: &[&[u8]], error: &mut bool) -> bool {
b"-d" => path(args[1], PathCondition::Directory), b"-d" => path(args[1], PathCondition::Directory),
b"-e" => path(args[1], PathCondition::Exists), b"-e" => path(args[1], PathCondition::Exists),
b"-f" => path(args[1], PathCondition::Regular), b"-f" => path(args[1], PathCondition::Regular),
b"-g" => path(args[1], PathCondition::GroupIDFlag), b"-g" => path(args[1], PathCondition::GroupIdFlag),
b"-h" => path(args[1], PathCondition::SymLink), b"-h" => path(args[1], PathCondition::SymLink),
b"-L" => path(args[1], PathCondition::SymLink), b"-L" => path(args[1], PathCondition::SymLink),
b"-n" => one(&args[1..]), b"-n" => one(&args[1..]),
b"-p" => path(args[1], PathCondition::FIFO), b"-p" => path(args[1], PathCondition::Fifo),
b"-r" => path(args[1], PathCondition::Readable), b"-r" => path(args[1], PathCondition::Readable),
b"-S" => path(args[1], PathCondition::Socket), b"-S" => path(args[1], PathCondition::Socket),
b"-s" => path(args[1], PathCondition::NonEmpty), b"-s" => path(args[1], PathCondition::NonEmpty),
b"-t" => isatty(args[1]), b"-t" => isatty(args[1]),
b"-u" => path(args[1], PathCondition::UserIDFlag), b"-u" => path(args[1], PathCondition::UserIdFlag),
b"-w" => path(args[1], PathCondition::Writable), b"-w" => path(args[1], PathCondition::Writable),
b"-x" => path(args[1], PathCondition::Executable), b"-x" => path(args[1], PathCondition::Executable),
b"-z" => !one(&args[1..]), b"-z" => !one(&args[1..]),
@ -322,13 +322,13 @@ enum PathCondition {
Directory, Directory,
Exists, Exists,
Regular, Regular,
GroupIDFlag, GroupIdFlag,
SymLink, SymLink,
FIFO, Fifo,
Readable, Readable,
Socket, Socket,
NonEmpty, NonEmpty,
UserIDFlag, UserIdFlag,
Writable, Writable,
Executable, Executable,
} }
@ -390,13 +390,13 @@ fn path(path: &[u8], cond: PathCondition) -> bool {
PathCondition::Directory => file_type.is_dir(), PathCondition::Directory => file_type.is_dir(),
PathCondition::Exists => true, PathCondition::Exists => true,
PathCondition::Regular => file_type.is_file(), PathCondition::Regular => file_type.is_file(),
PathCondition::GroupIDFlag => metadata.mode() & S_ISGID != 0, PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
PathCondition::SymLink => metadata.file_type().is_symlink(), PathCondition::SymLink => metadata.file_type().is_symlink(),
PathCondition::FIFO => file_type.is_fifo(), PathCondition::Fifo => file_type.is_fifo(),
PathCondition::Readable => perm(metadata, Permission::Read), PathCondition::Readable => perm(metadata, Permission::Read),
PathCondition::Socket => file_type.is_socket(), PathCondition::Socket => file_type.is_socket(),
PathCondition::NonEmpty => metadata.size() > 0, PathCondition::NonEmpty => metadata.size() > 0,
PathCondition::UserIDFlag => metadata.mode() & S_ISUID != 0, PathCondition::UserIdFlag => metadata.mode() & S_ISUID != 0,
PathCondition::Writable => perm(metadata, Permission::Write), PathCondition::Writable => perm(metadata, Permission::Write),
PathCondition::Executable => perm(metadata, Permission::Execute), PathCondition::Executable => perm(metadata, Permission::Execute),
} }
@ -416,13 +416,13 @@ fn path(path: &[u8], cond: PathCondition) -> bool {
PathCondition::Directory => stat.is_dir(), PathCondition::Directory => stat.is_dir(),
PathCondition::Exists => true, PathCondition::Exists => true,
PathCondition::Regular => stat.is_file(), PathCondition::Regular => stat.is_file(),
PathCondition::GroupIDFlag => false, PathCondition::GroupIdFlag => false,
PathCondition::SymLink => false, PathCondition::SymLink => false,
PathCondition::FIFO => false, PathCondition::Fifo => false,
PathCondition::Readable => false, // TODO PathCondition::Readable => false, // TODO
PathCondition::Socket => false, PathCondition::Socket => false,
PathCondition::NonEmpty => stat.len() > 0, PathCondition::NonEmpty => stat.len() > 0,
PathCondition::UserIDFlag => false, PathCondition::UserIdFlag => false,
PathCondition::Writable => false, // TODO PathCondition::Writable => false, // TODO
PathCondition::Executable => false, // TODO PathCondition::Executable => false, // TODO
} }

View file

@ -15,11 +15,13 @@ edition = "2018"
path = "src/timeout.rs" path = "src/timeout.rs"
[dependencies] [dependencies]
clap = "2.33"
getopts = "0.2.18" getopts = "0.2.18"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["parse_time", "process", "signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]
name = "timeout" name = "timeout"
path = "src/main.rs" path = "src/main.rs"

View file

@ -10,99 +10,154 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
extern crate clap;
use clap::{App, AppSettings, Arg};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use std::time::Duration; use std::time::Duration;
use uucore::process::ChildExt; use uucore::process::ChildExt;
use uucore::signals::signal_by_name_or_value;
static NAME: &str = "timeout";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION.";
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
const ERR_EXIT_STATUS: i32 = 125; const ERR_EXIT_STATUS: i32 = 125;
pub fn uumain(args: impl uucore::Args) -> i32 { pub mod options {
let args = args.collect_str(); pub static FOREGROUND: &str = "foreground";
pub static KILL_AFTER: &str = "kill-after";
pub static SIGNAL: &str = "signal";
pub static VERSION: &str = "version";
pub static PRESERVE_STATUS: &str = "preserve-status";
let program = args[0].clone(); // Positional args.
pub static DURATION: &str = "duration";
pub static COMMAND: &str = "command";
pub static ARGS: &str = "args";
}
let mut opts = getopts::Options::new(); struct Config {
opts.optflag( foreground: bool,
"", kill_after: Duration,
"preserve-status", signal: usize,
"exit with the same status as COMMAND, even when the command times out", duration: Duration,
); preserve_status: bool,
opts.optflag("", "foreground", "when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out");
opts.optopt("k", "kill-after", "also send a KILL signal if COMMAND is still running this long after the initial signal was sent", "DURATION");
opts.optflag("s", "signal", "specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals");
opts.optflag("h", "help", "display this help and exit");
opts.optflag("V", "version", "output version information and exit");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => crash!(ERR_EXIT_STATUS, "{}", f),
};
if matches.opt_present("help") {
print!(
"{} {}
Usage: command: String,
{} [OPTION] DURATION COMMAND [ARG]... command_args: Vec<String>,
}
{}", impl Config {
NAME, fn from(options: clap::ArgMatches) -> Config {
VERSION, let signal = match options.value_of(options::SIGNAL) {
program, Some(signal_) => {
&opts.usage("Start COMMAND, and kill it if still running after DURATION.") let signal_result = signal_by_name_or_value(&signal_);
); match signal_result {
} else if matches.opt_present("version") { None => {
println!("{} {}", NAME, VERSION); unreachable!("invalid signal '{}'", signal_);
} else if matches.free.len() < 2 {
show_error!("missing an argument");
show_error!("for help, try '{0} --help'", program);
return ERR_EXIT_STATUS;
} else {
let status = matches.opt_present("preserve-status");
let foreground = matches.opt_present("foreground");
let kill_after = match matches.opt_str("kill-after") {
Some(tstr) => match uucore::parse_time::from_str(&tstr) {
Ok(time) => time,
Err(f) => {
show_error!("{}", f);
return ERR_EXIT_STATUS;
} }
}, Some(signal_value) => signal_value,
}
}
_ => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
};
let kill_after: Duration = match options.value_of(options::KILL_AFTER) {
Some(time) => uucore::parse_time::from_str(&time).unwrap(),
None => Duration::new(0, 0), None => Duration::new(0, 0),
}; };
let signal = match matches.opt_str("signal") {
Some(sigstr) => match uucore::signals::signal_by_name_or_value(&sigstr) {
Some(sig) => sig,
None => {
show_error!("invalid signal '{}'", sigstr);
return ERR_EXIT_STATUS;
}
},
None => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
};
let duration = match uucore::parse_time::from_str(&matches.free[0]) {
Ok(time) => time,
Err(f) => {
show_error!("{}", f);
return ERR_EXIT_STATUS;
}
};
return timeout(
&matches.free[1],
&matches.free[2..],
duration,
signal,
kill_after,
foreground,
status,
);
}
0 let duration: Duration =
uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap();
let preserve_status: bool = options.is_present(options::PRESERVE_STATUS);
let foreground = options.is_present(options::FOREGROUND);
let command: String = options.value_of(options::COMMAND).unwrap().to_string();
let command_args: Vec<String> = match options.values_of(options::ARGS) {
Some(values) => values.map(|x| x.to_owned()).collect(),
None => vec![],
};
Config {
foreground,
kill_after,
signal,
duration,
preserve_status,
command,
command_args,
}
}
} }
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let usage = get_usage();
let app = App::new("timeout")
.version(VERSION)
.usage(&usage[..])
.about(ABOUT)
.arg(
Arg::with_name(options::FOREGROUND)
.long(options::FOREGROUND)
.help("when not running timeout directly from a shell prompt, allow COMMAND to read from the TTY and get TTY signals; in this mode, children of COMMAND will not be timed out")
)
.arg(
Arg::with_name(options::KILL_AFTER)
.short("k")
.takes_value(true))
.arg(
Arg::with_name(options::PRESERVE_STATUS)
.long(options::PRESERVE_STATUS)
.help("exit with the same status as COMMAND, even when the command times out")
)
.arg(
Arg::with_name(options::SIGNAL)
.short("s")
.long(options::SIGNAL)
.help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals")
.takes_value(true)
)
.arg(
Arg::with_name(options::DURATION)
.index(1)
.required(true)
)
.arg(
Arg::with_name(options::COMMAND)
.index(2)
.required(true)
)
.arg(
Arg::with_name(options::ARGS).multiple(true)
)
.setting(AppSettings::TrailingVarArg);
let matches = app.get_matches_from(args);
let config = Config::from(matches);
timeout(
&config.command,
&config.command_args,
config.duration,
config.signal,
config.kill_after,
config.foreground,
config.preserve_status,
)
}
/// TODO: Improve exit codes, and make them consistent with the GNU Coreutil
/// exit codes.
fn timeout( fn timeout(
cmdname: &str, cmdname: &str,
args: &[String], args: &[String],
@ -126,10 +181,10 @@ fn timeout(
Err(err) => { Err(err) => {
show_error!("failed to execute process: {}", err); show_error!("failed to execute process: {}", err);
if err.kind() == ErrorKind::NotFound { if err.kind() == ErrorKind::NotFound {
// XXX: not sure which to use // FIXME: not sure which to use
return 127; return 127;
} else { } else {
// XXX: this may not be 100% correct... // FIXME: this may not be 100% correct...
return 126; return 126;
} }
} }

View file

@ -18,6 +18,7 @@ use filetime::*;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::Error; use std::io::Error;
use std::path::Path; use std::path::Path;
use std::process;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
@ -137,7 +138,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) { let (mut atime, mut mtime) = if matches.is_present(options::sources::REFERENCE) {
stat( stat(
&matches.value_of(options::sources::REFERENCE).unwrap()[..], matches.value_of(options::sources::REFERENCE).unwrap(),
!matches.is_present(options::NO_DEREF), !matches.is_present(options::NO_DEREF),
) )
} else if matches.is_present(options::sources::DATE) } else if matches.is_present(options::sources::DATE)
@ -261,7 +262,27 @@ fn parse_timestamp(s: &str) -> FileTime {
}; };
match time::strptime(&ts, format) { match time::strptime(&ts, format) {
Ok(tm) => local_tm_to_filetime(to_local(tm)), Ok(tm) => {
let mut local = to_local(tm);
local.tm_isdst = -1;
let ft = local_tm_to_filetime(local);
// We have to check that ft is valid time. Due to daylight saving
// time switch, local time can jump from 1:59 AM to 3:00 AM,
// in which case any time between 2:00 AM and 2:59 AM is not valid.
// Convert back to local time and see if we got the same value back.
let ts = time::Timespec {
sec: ft.unix_seconds(),
nsec: 0,
};
let tm2 = time::at(ts);
if tm.tm_hour != tm2.tm_hour {
show_error!("invalid date format {}", s);
process::exit(1);
}
ft
}
Err(e) => panic!("Unable to parse timestamp\n{}", e), Err(e) => panic!("Unable to parse timestamp\n{}", e),
} }
} }

View file

@ -24,7 +24,7 @@ use std::ops::RangeInclusive;
fn parse_sequence(s: &str) -> (char, usize) { fn parse_sequence(s: &str) -> (char, usize) {
let c = s.chars().next().expect("invalid escape: empty string"); let c = s.chars().next().expect("invalid escape: empty string");
if '0' <= c && c <= '7' { if ('0'..='7').contains(&c) {
let mut v = c.to_digit(8).unwrap(); let mut v = c.to_digit(8).unwrap();
let mut consumed = 1; let mut consumed = 1;
let bits_per_digit = 3; let bits_per_digit = 3;

View file

@ -32,13 +32,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.version(VERSION) .version(VERSION)
.usage(USAGE) .usage(USAGE)
.about(SUMMARY) .about(SUMMARY)
.arg(Arg::with_name(options::FILE).hidden(true)) .arg(
Arg::with_name(options::FILE)
.default_value("-")
.hidden(true),
)
.get_matches_from(args); .get_matches_from(args);
let input = match matches.value_of(options::FILE) { let input = matches
Some(v) => v, .value_of(options::FILE)
None => "-", .expect("Value is required by clap");
};
let mut stdin_buf; let mut stdin_buf;
let mut file_buf; let mut file_buf;

View file

@ -65,9 +65,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
return if is_stdin_interactive() { if is_stdin_interactive() {
libc::EXIT_SUCCESS libc::EXIT_SUCCESS
} else { } else {
libc::EXIT_FAILURE libc::EXIT_FAILURE
}; }
} }

View file

@ -149,10 +149,8 @@ fn next_tabstop(tabstops: &[usize], col: usize) -> Option<usize> {
Some(tabstops[0] - col % tabstops[0]) Some(tabstops[0] - col % tabstops[0])
} else { } else {
// find next larger tab // find next larger tab
match tabstops.iter().find(|&&t| t > col) { // if there isn't one in the list, tab becomes a single space
Some(t) => Some(t - col), tabstops.iter().find(|&&t| t > col).map(|t| t-col)
None => None, // if there isn't one in the list, tab becomes a single space
}
} }
} }

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/unlink.rs" path = "src/unlink.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -12,59 +12,53 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use getopts::Options; use clap::{App, Arg};
use libc::{lstat, stat, unlink}; use libc::{lstat, stat, unlink};
use libc::{S_IFLNK, S_IFMT, S_IFREG}; use libc::{S_IFLNK, S_IFMT, S_IFREG};
use std::ffi::CString; use std::ffi::CString;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
static NAME: &str = "unlink";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Unlink the file at [FILE].";
static OPT_PATH: &str = "FILE";
fn get_usage() -> String {
format!("{} [OPTION]... FILE", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let mut opts = Options::new(); let usage = get_usage();
opts.optflag("h", "help", "display this help and exit"); let matches = App::new(executable!())
opts.optflag("V", "version", "output version information and exit"); .version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true))
.get_matches_from(args);
let matches = match opts.parse(&args[1..]) { let paths: Vec<String> = matches
Ok(m) => m, .values_of(OPT_PATH)
Err(f) => crash!(1, "invalid options\n{}", f), .map(|v| v.map(ToString::to_string).collect())
}; .unwrap_or_default();
if matches.opt_present("help") { if paths.is_empty() {
println!("{} {}", NAME, VERSION);
println!();
println!("Usage:");
println!(" {} [FILE]... [OPTION]...", NAME);
println!();
println!("{}", opts.usage("Unlink the file at [FILE]."));
return 0;
}
if matches.opt_present("version") {
println!("{} {}", NAME, VERSION);
return 0;
}
if matches.free.is_empty() {
crash!( crash!(
1, 1,
"missing operand\nTry '{0} --help' for more information.", "missing operand\nTry '{0} --help' for more information.",
NAME executable!()
); );
} else if matches.free.len() > 1 { } else if paths.len() > 1 {
crash!( crash!(
1, 1,
"extra operand: '{1}'\nTry '{0} --help' for more information.", "extra operand: '{1}'\nTry '{0} --help' for more information.",
NAME, executable!(),
matches.free[1] paths[1]
); );
} }
let c_string = CString::new(matches.free[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0. let c_string = CString::new(paths[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0.
let st_mode = { let st_mode = {
#[allow(deprecated)] #[allow(deprecated)]
@ -72,12 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let result = unsafe { lstat(c_string.as_ptr(), &mut buf as *mut stat) }; let result = unsafe { lstat(c_string.as_ptr(), &mut buf as *mut stat) };
if result < 0 { if result < 0 {
crash!( crash!(1, "Cannot stat '{}': {}", paths[0], Error::last_os_error());
1,
"Cannot stat '{}': {}",
matches.free[0],
Error::last_os_error()
);
} }
buf.st_mode & S_IFMT buf.st_mode & S_IFMT
@ -101,7 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match result { match result {
Ok(_) => (), Ok(_) => (),
Err(e) => { Err(e) => {
crash!(1, "cannot unlink '{0}': {1}", matches.free[0], e); crash!(1, "cannot unlink '{0}': {1}", paths[0], e);
} }
} }

View file

@ -20,6 +20,21 @@ use nix::unistd::pipe;
const BUF_SIZE: usize = 16384; const BUF_SIZE: usize = 16384;
/// Splice wrapper which handles short writes
#[cfg(any(target_os = "linux", target_os = "android"))]
#[inline]
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
let mut left = num_bytes;
loop {
let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
left -= written;
if left == 0 {
break;
}
}
Ok(())
}
/// This is a Linux-specific function to count the number of bytes using the /// This is a Linux-specific function to count the number of bytes using the
/// `splice` system call, which is faster than using `read`. /// `splice` system call, which is faster than using `read`.
#[inline] #[inline]
@ -39,7 +54,7 @@ fn count_bytes_using_splice(fd: RawFd) -> nix::Result<usize> {
break; break;
} }
byte_count += res; byte_count += res;
splice(pipe_rd, None, null, None, res, SpliceFFlags::empty())?; splice_exact(pipe_rd, null, res)?;
} }
Ok(byte_count) Ok(byte_count)
@ -57,8 +72,7 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usi
#[cfg(unix)] #[cfg(unix)]
{ {
let fd = handle.as_raw_fd(); let fd = handle.as_raw_fd();
match fstat(fd) { if let Ok(stat) = fstat(fd) {
Ok(stat) => {
// If the file is regular, then the `st_size` should hold // If the file is regular, then the `st_size` should hold
// the file's size in bytes. // the file's size in bytes.
if (stat.st_mode & S_IFREG) != 0 { if (stat.st_mode & S_IFREG) != 0 {
@ -75,12 +89,10 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> WcResult<usi
} }
} }
} }
_ => {}
}
} }
// Fall back on `read`, but without the overhead of counting words and lines. // Fall back on `read`, but without the overhead of counting words and lines.
let mut buf = [0 as u8; BUF_SIZE]; let mut buf = [0_u8; BUF_SIZE];
let mut byte_count = 0; let mut byte_count = 0;
loop { loop {
match handle.read(&mut buf) { match handle.read(&mut buf) {

View file

@ -138,11 +138,8 @@ impl AddAssign for WordCount {
} }
impl WordCount { impl WordCount {
fn with_title<'a>(self, title: &'a str) -> TitledWordCount<'a> { fn with_title(self, title: &str) -> TitledWordCount {
return TitledWordCount { TitledWordCount { title, count: self }
title: title,
count: self,
};
} }
} }
@ -251,7 +248,7 @@ fn is_word_separator(byte: u8) -> bool {
fn word_count_from_reader<T: WordCountable>( fn word_count_from_reader<T: WordCountable>(
mut reader: T, mut reader: T,
settings: &Settings, settings: &Settings,
path: &String, path: &str,
) -> WcResult<WordCount> { ) -> WcResult<WordCount> {
let only_count_bytes = settings.show_bytes let only_count_bytes = settings.show_bytes
&& (!(settings.show_chars && (!(settings.show_chars
@ -333,18 +330,18 @@ fn word_count_from_reader<T: WordCountable>(
}) })
} }
fn word_count_from_path(path: &String, settings: &Settings) -> WcResult<WordCount> { fn word_count_from_path(path: &str, settings: &Settings) -> WcResult<WordCount> {
if path == "-" { if path == "-" {
let stdin = io::stdin(); let stdin = io::stdin();
let stdin_lock = stdin.lock(); let stdin_lock = stdin.lock();
return Ok(word_count_from_reader(stdin_lock, settings, path)?); word_count_from_reader(stdin_lock, settings, path)
} else { } else {
let path_obj = Path::new(path); let path_obj = Path::new(path);
if path_obj.is_dir() { if path_obj.is_dir() {
return Err(WcError::IsDirectory(path.clone())); Err(WcError::IsDirectory(path.to_owned()))
} else { } else {
let file = File::open(path)?; let file = File::open(path)?;
return Ok(word_count_from_reader(file, settings, path)?); word_count_from_reader(file, settings, path)
} }
} }
} }
@ -425,7 +422,7 @@ fn print_stats(
} }
if result.title == "-" { if result.title == "-" {
writeln!(stdout_lock, "")?; writeln!(stdout_lock)?;
} else { } else {
writeln!(stdout_lock, " {}", result.title)?; writeln!(stdout_lock, " {}", result.title)?;
} }

View file

@ -222,7 +222,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
need_runlevel, need_runlevel,
need_users, need_users,
my_line_only, my_line_only,
has_records: false,
args: matches.free, args: matches.free,
}; };
@ -247,7 +246,6 @@ struct Who {
need_runlevel: bool, need_runlevel: bool,
need_users: bool, need_users: bool,
my_line_only: bool, my_line_only: bool,
has_records: bool,
args: Vec<String>, args: Vec<String>,
} }
@ -321,8 +319,7 @@ impl Who {
println!("{}", users.join(" ")); println!("{}", users.join(" "));
println!("# users={}", users.len()); println!("# users={}", users.len());
} else { } else {
let mut records = Utmpx::iter_all_records().read_from(f).peekable(); let records = Utmpx::iter_all_records().read_from(f).peekable();
self.has_records = records.peek().is_some();
if self.include_heading { if self.include_heading {
self.print_heading() self.print_heading()

View file

@ -31,9 +31,9 @@ fn chgrp<P: AsRef<Path>>(path: P, dgid: gid_t, follow: bool) -> IOResult<()> {
let s = CString::new(path.as_os_str().as_bytes()).unwrap(); let s = CString::new(path.as_os_str().as_bytes()).unwrap();
let ret = unsafe { let ret = unsafe {
if follow { if follow {
libc::chown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) libc::chown(s.as_ptr(), 0_u32.wrapping_sub(1), dgid)
} else { } else {
lchown(s.as_ptr(), (0 as gid_t).wrapping_sub(1), dgid) lchown(s.as_ptr(), 0_u32.wrapping_sub(1), dgid)
} }
}; };
if ret == 0 { if ret == 0 {

View file

@ -108,7 +108,7 @@ macro_rules! safe_write(
($fd:expr, $($args:tt)+) => ( ($fd:expr, $($args:tt)+) => (
match write!($fd, $($args)+) { match write!($fd, $($args)+) {
Ok(_) => {} Ok(_) => {}
Err(f) => panic!(f.to_string()) Err(f) => panic!("{}", f)
} }
) )
); );
@ -118,7 +118,7 @@ macro_rules! safe_writeln(
($fd:expr, $($args:tt)+) => ( ($fd:expr, $($args:tt)+) => (
match writeln!($fd, $($args)+) { match writeln!($fd, $($args)+) {
Ok(_) => {} Ok(_) => {}
Err(f) => panic!(f.to_string()) Err(f) => panic!("{}", f)
} }
) )
); );

View file

@ -2,17 +2,13 @@ use crate::common::util::*;
#[test] #[test]
fn test_arch() { fn test_arch() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!().succeeds();
let result = ucmd.run();
assert!(result.success);
} }
#[test] #[test]
fn test_arch_help() { fn test_arch_help() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("--help")
let result = ucmd.arg("--help").run(); .succeeds()
assert!(result.success); .stdout_contains("architecture name");
assert!(result.stdout.contains("architecture name"));
} }

View file

@ -66,7 +66,7 @@ fn test_zero_param() {
} }
fn expect_error(input: Vec<&str>) { fn expect_error(input: Vec<&str>) {
assert!(new_ucmd!().args(&input).fails().no_stdout().stderr.len() > 0); assert!(new_ucmd!().args(&input).fails().no_stdout().stderr().len() > 0);
} }
#[test] #[test]

View file

@ -1,7 +1,5 @@
#[cfg(unix)]
extern crate unix_socket;
use crate::common::util::*; use crate::common::util::*;
use std::io::Read;
#[test] #[test]
fn test_output_simple() { fn test_output_simple() {
@ -11,6 +9,131 @@ fn test_output_simple() {
.stdout_only("abcde\nfghij\nklmno\npqrst\nuvwxyz\n"); .stdout_only("abcde\nfghij\nklmno\npqrst\nuvwxyz\n");
} }
#[test]
fn test_no_options() {
for fixture in &["empty.txt", "alpha.txt", "nonewline.txt"] {
// Give fixture through command line file argument
new_ucmd!()
.args(&[fixture])
.succeeds()
.stdout_is_fixture(fixture);
// Give fixture through stdin
new_ucmd!()
.pipe_in_fixture(fixture)
.succeeds()
.stdout_is_fixture(fixture);
}
}
#[test]
#[cfg(unix)]
fn test_no_options_big_input() {
for &n in &[
0,
1,
42,
16 * 1024 - 7,
16 * 1024 - 1,
16 * 1024,
16 * 1024 + 1,
16 * 1024 + 3,
32 * 1024,
64 * 1024,
80 * 1024,
96 * 1024,
112 * 1024,
128 * 1024,
] {
let data = vec_of_size(n);
let data2 = data.clone();
assert_eq!(data.len(), data2.len());
new_ucmd!().pipe_in(data).succeeds().stdout_is_bytes(&data2);
}
}
#[test]
#[cfg(unix)]
fn test_fifo_symlink() {
use std::fs::OpenOptions;
use std::io::Write;
use std::thread;
let s = TestScenario::new(util_name!());
s.fixtures.mkdir("dir");
s.fixtures.mkfifo("dir/pipe");
assert!(s.fixtures.is_fifo("dir/pipe"));
// Make cat read the pipe through a symlink
s.fixtures.symlink_file("dir/pipe", "sympipe");
let proc = s.ucmd().args(&["sympipe"]).run_no_wait();
let data = vec_of_size(128 * 1024);
let data2 = data.clone();
let pipe_path = s.fixtures.plus("dir/pipe");
let thread = thread::spawn(move || {
let mut pipe = OpenOptions::new()
.write(true)
.create(false)
.open(pipe_path)
.unwrap();
pipe.write_all(&data).unwrap();
});
let output = proc.wait_with_output().unwrap();
assert_eq!(&output.stdout, &data2);
thread.join().unwrap();
}
#[test]
fn test_directory() {
let s = TestScenario::new(util_name!());
s.fixtures.mkdir("test_directory");
s.ucmd()
.args(&["test_directory"])
.fails()
.stderr_is("cat: test_directory: Is a directory");
}
#[test]
fn test_directory_and_file() {
let s = TestScenario::new(util_name!());
s.fixtures.mkdir("test_directory2");
for fixture in &["empty.txt", "alpha.txt", "nonewline.txt"] {
s.ucmd()
.args(&["test_directory2", fixture])
.fails()
.stderr_is("cat: test_directory2: Is a directory")
.stdout_is_fixture(fixture);
}
}
#[test]
#[cfg(unix)]
fn test_three_directories_and_file_and_stdin() {
let s = TestScenario::new(util_name!());
s.fixtures.mkdir("test_directory3");
s.fixtures.mkdir("test_directory3/test_directory4");
s.fixtures.mkdir("test_directory3/test_directory5");
s.ucmd()
.args(&[
"test_directory3/test_directory4",
"alpha.txt",
"-",
"filewhichdoesnotexist.txt",
"nonewline.txt",
"test_directory3/test_directory5",
"test_directory3/../test_directory3/test_directory5",
"test_directory3",
])
.pipe_in("stdout bytes")
.fails()
.stderr_is_fixture("three_directories_and_file_and_stdin.stderr.expected")
.stdout_is(
"abcde\nfghij\nklmno\npqrst\nuvwxyz\nstdout bytestext without a trailing newline",
);
}
#[test] #[test]
fn test_output_multi_files_print_all_chars() { fn test_output_multi_files_print_all_chars() {
new_ucmd!() new_ucmd!()
@ -149,13 +272,64 @@ fn test_squeeze_blank_before_numbering() {
} }
} }
/// This tests reading from Unix character devices
#[test] #[test]
#[cfg(foo)] #[cfg(unix)]
fn test_dev_random() {
let mut buf = [0; 2048];
let mut proc = new_ucmd!().args(&["/dev/random"]).run_no_wait();
let mut proc_stdout = proc.stdout.take().unwrap();
proc_stdout.read_exact(&mut buf).unwrap();
let num_zeroes = buf.iter().fold(0, |mut acc, &n| {
if n == 0 {
acc += 1;
}
acc
});
// The probability of more than 512 zero bytes is essentially zero if the
// output is truly random.
assert!(num_zeroes < 512);
proc.kill().unwrap();
}
/// Reading from /dev/full should return an infinite amount of zero bytes.
/// Wikipedia says there is support on Linux, FreeBSD, and NetBSD.
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
fn test_dev_full() {
let mut buf = [0; 2048];
let mut proc = new_ucmd!().args(&["/dev/full"]).run_no_wait();
let mut proc_stdout = proc.stdout.take().unwrap();
let expected = [0; 2048];
proc_stdout.read_exact(&mut buf).unwrap();
assert_eq!(&buf[..], &expected[..]);
proc.kill().unwrap();
}
#[test]
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
fn test_dev_full_show_all() {
let mut buf = [0; 2048];
let mut proc = new_ucmd!().args(&["-A", "/dev/full"]).run_no_wait();
let mut proc_stdout = proc.stdout.take().unwrap();
proc_stdout.read_exact(&mut buf).unwrap();
let expected: Vec<u8> = (0..buf.len())
.map(|n| if n & 1 == 0 { b'^' } else { b'@' })
.collect();
assert_eq!(&buf[..], &expected[..]);
proc.kill().unwrap();
}
#[test]
#[cfg(unix)]
fn test_domain_socket() { fn test_domain_socket() {
use self::tempdir::TempDir;
use self::unix_socket::UnixListener;
use std::io::prelude::*; use std::io::prelude::*;
use std::thread; use std::thread;
use tempdir::TempDir;
use unix_socket::UnixListener;
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");

View file

@ -149,7 +149,7 @@ fn test_big_h() {
.arg("bin") .arg("bin")
.arg("/proc/self/fd") .arg("/proc/self/fd")
.fails() .fails()
.stderr .stderr_str()
.lines() .lines()
.fold(0, |acc, _| acc + 1) .fold(0, |acc, _| acc + 1)
> 1 > 1

View file

@ -48,7 +48,7 @@ fn run_single_test(test: &TestCase, at: AtPath, mut ucmd: UCommand) {
} }
let r = ucmd.run(); let r = ucmd.run();
if !r.success { if !r.success {
println!("{}", r.stderr); println!("{}", r.stderr_str());
panic!("{:?}: failed", ucmd.raw); panic!("{:?}: failed", ucmd.raw);
} }
@ -297,13 +297,14 @@ fn test_chmod_recursive() {
mkfile(&at.plus_as_string("a/b/c/c"), 0o100444); mkfile(&at.plus_as_string("a/b/c/c"), 0o100444);
mkfile(&at.plus_as_string("z/y"), 0o100444); mkfile(&at.plus_as_string("z/y"), 0o100444);
let result = ucmd ucmd.arg("-R")
.arg("-R")
.arg("--verbose") .arg("--verbose")
.arg("-r,a+w") .arg("-r,a+w")
.arg("a") .arg("a")
.arg("z") .arg("z")
.succeeds(); .succeeds()
.stderr_contains(&"to 333 (-wx-wx-wx)")
.stderr_contains(&"to 222 (-w--w--w-)");
assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222); assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222);
assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222); assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222);
@ -312,8 +313,6 @@ fn test_chmod_recursive() {
println!("mode {:o}", at.metadata("a").permissions().mode()); println!("mode {:o}", at.metadata("a").permissions().mode());
assert_eq!(at.metadata("a").permissions().mode(), 0o40333); assert_eq!(at.metadata("a").permissions().mode(), 0o40333);
assert_eq!(at.metadata("z").permissions().mode(), 0o40333); assert_eq!(at.metadata("z").permissions().mode(), 0o40333);
assert!(result.stderr.contains("to 333 (-wx-wx-wx)"));
assert!(result.stderr.contains("to 222 (-w--w--w-)"));
unsafe { unsafe {
umask(original_umask); umask(original_umask);
@ -322,30 +321,24 @@ fn test_chmod_recursive() {
#[test] #[test]
fn test_chmod_non_existing_file() { fn test_chmod_non_existing_file() {
let (_at, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg("-R") .arg("-R")
.arg("--verbose") .arg("--verbose")
.arg("-r,a+w") .arg("-r,a+w")
.arg("dont-exist") .arg("dont-exist")
.fails(); .fails()
assert!(result .stderr_contains(&"cannot access 'dont-exist': No such file or directory");
.stderr
.contains("cannot access 'dont-exist': No such file or directory"));
} }
#[test] #[test]
fn test_chmod_preserve_root() { fn test_chmod_preserve_root() {
let (_at, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd
.arg("-R") .arg("-R")
.arg("--preserve-root") .arg("--preserve-root")
.arg("755") .arg("755")
.arg("/") .arg("/")
.fails(); .fails()
assert!(result .stderr_contains(&"chmod: error: it is dangerous to operate recursively on '/'");
.stderr
.contains("chmod: error: it is dangerous to operate recursively on '/'"));
} }
#[test] #[test]
@ -362,33 +355,27 @@ fn test_chmod_symlink_non_existing_file() {
let expected_stderr = &format!("cannot operate on dangling symlink '{}'", test_symlink); let expected_stderr = &format!("cannot operate on dangling symlink '{}'", test_symlink);
at.symlink_file(non_existing, test_symlink); at.symlink_file(non_existing, test_symlink);
let mut result;
// this cannot succeed since the symbolic link dangles // this cannot succeed since the symbolic link dangles
result = scene.ucmd().arg("755").arg("-v").arg(test_symlink).fails(); scene.ucmd()
.arg("755")
println!("stdout = {:?}", result.stdout); .arg("-v")
println!("stderr = {:?}", result.stderr); .arg(test_symlink)
.fails()
assert!(result.stdout.contains(expected_stdout)); .code_is(1)
assert!(result.stderr.contains(expected_stderr)); .stdout_contains(expected_stdout)
assert_eq!(result.code, Some(1)); .stderr_contains(expected_stderr);
// this should be the same than with just '-v' but without stderr // this should be the same than with just '-v' but without stderr
result = scene scene.ucmd()
.ucmd()
.arg("755") .arg("755")
.arg("-v") .arg("-v")
.arg("-f") .arg("-f")
.arg(test_symlink) .arg(test_symlink)
.fails(); .run()
.code_is(1)
println!("stdout = {:?}", result.stdout); .no_stderr()
println!("stderr = {:?}", result.stderr); .stdout_contains(expected_stdout);
assert!(result.stdout.contains(expected_stdout));
assert!(result.stderr.is_empty());
assert_eq!(result.code, Some(1));
} }
#[test] #[test]
@ -405,18 +392,15 @@ fn test_chmod_symlink_non_existing_file_recursive() {
non_existing, non_existing,
&format!("{}/{}", test_directory, test_symlink), &format!("{}/{}", test_directory, test_symlink),
); );
let mut result;
// this should succeed // this should succeed
result = scene scene.ucmd()
.ucmd()
.arg("-R") .arg("-R")
.arg("755") .arg("755")
.arg(test_directory) .arg(test_directory)
.succeeds(); .succeeds()
assert_eq!(result.code, Some(0)); .no_stderr()
assert!(result.stdout.is_empty()); .no_stdout();
assert!(result.stderr.is_empty());
let expected_stdout = &format!( let expected_stdout = &format!(
"mode of '{}' retained as 0755 (rwxr-xr-x)\nneither symbolic link '{}/{}' nor referent has been changed", "mode of '{}' retained as 0755 (rwxr-xr-x)\nneither symbolic link '{}/{}' nor referent has been changed",
@ -424,37 +408,25 @@ fn test_chmod_symlink_non_existing_file_recursive() {
); );
// '-v': this should succeed without stderr // '-v': this should succeed without stderr
result = scene scene.ucmd()
.ucmd()
.arg("-R") .arg("-R")
.arg("-v") .arg("-v")
.arg("755") .arg("755")
.arg(test_directory) .arg(test_directory)
.succeeds(); .succeeds()
.stdout_contains(expected_stdout)
println!("stdout = {:?}", result.stdout); .no_stderr();
println!("stderr = {:?}", result.stderr);
assert!(result.stdout.contains(expected_stdout));
assert!(result.stderr.is_empty());
assert_eq!(result.code, Some(0));
// '-vf': this should be the same than with just '-v' // '-vf': this should be the same than with just '-v'
result = scene scene.ucmd()
.ucmd()
.arg("-R") .arg("-R")
.arg("-v") .arg("-v")
.arg("-f") .arg("-f")
.arg("755") .arg("755")
.arg(test_directory) .arg(test_directory)
.succeeds(); .succeeds()
.stdout_contains(expected_stdout)
println!("stdout = {:?}", result.stdout); .no_stderr();
println!("stderr = {:?}", result.stderr);
assert!(result.stdout.contains(expected_stdout));
assert!(result.stderr.is_empty());
assert_eq!(result.code, Some(0));
} }
#[test] #[test]

View file

@ -53,22 +53,22 @@ fn test_chown_myself() {
// test chown username file.txt // test chown username file.txt
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;
} }
println!("results {}", result.stdout); println!("results {}", result.stdout_str());
let username = result.stdout.trim_end(); let username = result.stdout_str().trim_end();
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
at.touch(file1); at.touch(file1);
let result = ucmd.arg(username).arg(file1).run(); let result = ucmd.arg(username).arg(file1).run();
println!("results stdout {}", result.stdout); println!("results stdout {}", result.stdout_str());
println!("results stderr {}", result.stderr); println!("results stderr {}", result.stderr_str());
if is_ci() && result.stderr.contains("invalid user") { if is_ci() && result.stderr_str().contains("invalid user") {
// In the CI, some server are failing to return id. // 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;
@ -81,24 +81,24 @@ fn test_chown_myself_second() {
// test chown username: file.txt // test chown username: file.txt
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;
} }
println!("results {}", result.stdout); println!("results {}", result.stdout_str());
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
at.touch(file1); at.touch(file1);
let result = ucmd let result = ucmd
.arg(result.stdout.trim_end().to_owned() + ":") .arg(result.stdout_str().trim_end().to_owned() + ":")
.arg(file1) .arg(file1)
.run(); .run();
println!("result.stdout = {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
assert!(result.success); assert!(result.success);
} }
@ -107,31 +107,31 @@ fn test_chown_myself_group() {
// test chown username:group file.txt // test chown username:group file.txt
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;
} }
println!("user name = {}", result.stdout); println!("user name = {}", result.stdout_str());
let username = result.stdout.trim_end(); let username = result.stdout_str().trim_end();
let result = scene.cmd("id").arg("-gn").run(); let result = scene.cmd("id").arg("-gn").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("group name = {}", result.stdout); println!("group name = {}", result.stdout_str());
let group = result.stdout.trim_end(); let group = result.stdout_str().trim_end();
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
let perm = username.to_owned() + ":" + group; let perm = username.to_owned() + ":" + group;
at.touch(file1); at.touch(file1);
let result = ucmd.arg(perm).arg(file1).run(); let result = ucmd.arg(perm).arg(file1).run();
println!("result.stdout = {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("chown: invalid group:") { if is_ci() && result.stderr_str().contains("chown: invalid group:") {
// With some Ubuntu into the CI, we can get this answer // With some Ubuntu into the CI, we can get this answer
return; return;
} }
@ -143,27 +143,27 @@ fn test_chown_only_group() {
// test chown :group file.txt // test chown :group file.txt
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;
} }
println!("results {}", result.stdout); println!("results {}", result.stdout_str());
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
let perm = ":".to_owned() + result.stdout.trim_end(); let perm = ":".to_owned() + result.stdout_str().trim_end();
at.touch(file1); at.touch(file1);
let result = ucmd.arg(perm).arg(file1).run(); let result = ucmd.arg(perm).arg(file1).run();
println!("result.stdout = {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("Operation not permitted") { if is_ci() && result.stderr_str().contains("Operation not permitted") {
// With ubuntu with old Rust in the CI, we can get an error // With ubuntu with old Rust in the CI, we can get an error
return; return;
} }
if is_ci() && result.stderr.contains("chown: invalid group:") { if is_ci() && result.stderr_str().contains("chown: invalid group:") {
// With mac into the CI, we can get this answer // With mac into the CI, we can get this answer
return; return;
} }
@ -174,14 +174,14 @@ fn test_chown_only_group() {
fn test_chown_only_id() { fn test_chown_only_id() {
// test chown 1111 file.txt // test chown 1111 file.txt
let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("result.stdout = {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let id = String::from(result.stdout.trim()); let id = String::from(result.stdout_str().trim());
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
@ -189,9 +189,9 @@ fn test_chown_only_id() {
at.touch(file1); at.touch(file1);
let result = ucmd.arg(id).arg(file1).run(); let result = ucmd.arg(id).arg(file1).run();
println!("result.stdout = {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("chown: invalid user:") { if is_ci() && result.stderr_str().contains("chown: invalid user:") {
// With some Ubuntu into the CI, we can get this answer // With some Ubuntu into the CI, we can get this answer
return; return;
} }
@ -202,14 +202,14 @@ fn test_chown_only_id() {
fn test_chown_only_group_id() { fn test_chown_only_group_id() {
// test chown :1111 file.txt // test chown :1111 file.txt
let result = TestScenario::new("id").ucmd_keepenv().arg("-g").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-g").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let id = String::from(result.stdout.trim()); let id = String::from(result.stdout_str().trim());
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
@ -219,9 +219,9 @@ fn test_chown_only_group_id() {
let result = ucmd.arg(perm).arg(file1).run(); let result = ucmd.arg(perm).arg(file1).run();
println!("result.stdout = {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("chown: invalid group:") { if is_ci() && result.stderr_str().contains("chown: invalid group:") {
// With mac into the CI, we can get this answer // With mac into the CI, we can get this answer
return; return;
} }
@ -232,24 +232,24 @@ fn test_chown_only_group_id() {
fn test_chown_both_id() { fn test_chown_both_id() {
// test chown 1111:1111 file.txt // test chown 1111:1111 file.txt
let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let id_user = String::from(result.stdout.trim()); let id_user = String::from(result.stdout_str().trim());
let result = TestScenario::new("id").ucmd_keepenv().arg("-g").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-g").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let id_group = String::from(result.stdout.trim()); let id_group = String::from(result.stdout_str().trim());
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
@ -258,10 +258,10 @@ fn test_chown_both_id() {
let perm = id_user + &":".to_owned() + &id_group; let perm = id_user + &":".to_owned() + &id_group;
let result = ucmd.arg(perm).arg(file1).run(); let result = ucmd.arg(perm).arg(file1).run();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("invalid user") { if is_ci() && result.stderr_str().contains("invalid user") {
// In the CI, some server are failing to return id. // 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;
@ -274,24 +274,24 @@ fn test_chown_both_id() {
fn test_chown_both_mix() { fn test_chown_both_mix() {
// test chown 1111:1111 file.txt // test chown 1111:1111 file.txt
let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-u").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let id_user = String::from(result.stdout.trim()); let id_user = String::from(result.stdout_str().trim());
let result = TestScenario::new("id").ucmd_keepenv().arg("-gn").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-gn").run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr_str().contains("No such user/group") {
// In the CI, some server are failing to return whoami. // 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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let group_name = String::from(result.stdout.trim()); let group_name = String::from(result.stdout_str().trim());
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let file1 = "test_install_target_dir_file_a1"; let file1 = "test_install_target_dir_file_a1";
@ -301,7 +301,7 @@ fn test_chown_both_mix() {
let result = ucmd.arg(perm).arg(file1).run(); let result = ucmd.arg(perm).arg(file1).run();
if is_ci() && result.stderr.contains("invalid user") { if is_ci() && result.stderr_str().contains("invalid user") {
// In the CI, some server are failing to return id. // 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;
@ -313,14 +313,14 @@ fn test_chown_both_mix() {
fn test_chown_recursive() { fn test_chown_recursive() {
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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let username = result.stdout.trim_end(); let username = result.stdout_str().trim_end();
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a"); at.mkdir("a");
@ -339,31 +339,32 @@ fn test_chown_recursive() {
.arg("a") .arg("a")
.arg("z") .arg("z")
.run(); .run();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("invalid user") { if is_ci() && result.stderr_str().contains("invalid user") {
// In the CI, some server are failing to return id. // 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;
} }
assert!(result.stderr.contains("ownership of 'a/a' retained as")); result
assert!(result.stderr.contains("ownership of 'z/y' retained as")); .stderr_contains(&"ownership of 'a/a' retained as")
assert!(result.success); .stderr_contains(&"ownership of 'z/y' retained as")
.success();
} }
#[test] #[test]
fn test_root_preserve() { fn test_root_preserve() {
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;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let username = result.stdout.trim_end(); let username = result.stdout_str().trim_end();
let result = new_ucmd!() let result = new_ucmd!()
.arg("--preserve-root") .arg("--preserve-root")
@ -371,9 +372,9 @@ fn test_root_preserve() {
.arg(username) .arg(username)
.arg("/") .arg("/")
.fails(); .fails();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("invalid user") { if is_ci() && result.stderr_str().contains("invalid user") {
// In the CI, some server are failing to return id. // 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

@ -64,14 +64,14 @@ fn test_preference_of_userspec() {
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; return;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let username = result.stdout.trim_end(); let username = result.stdout_str().trim_end();
let ts = TestScenario::new("id"); let ts = TestScenario::new("id");
let result = ts.cmd("id").arg("-g").arg("-n").run(); let result = ts.cmd("id").arg("-g").arg("-n").run();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
if is_ci() && result.stderr.contains("cannot find name for user ID") { if is_ci() && result.stderr.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.
@ -79,7 +79,7 @@ fn test_preference_of_userspec() {
return; return;
} }
let group_name = result.stdout.trim_end(); let group_name = result.stdout_str().trim_end();
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a"); at.mkdir("a");
@ -93,6 +93,6 @@ fn test_preference_of_userspec() {
.arg(format!("--userspec={}:{}", username, group_name)) .arg(format!("--userspec={}:{}", username, group_name))
.run(); .run();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
} }

View file

@ -41,7 +41,13 @@ fn test_arg_overrides_stdin() {
at.touch("a"); at.touch("a");
let result = ucmd.arg("a").pipe_in(input.as_bytes()).run(); let result = ucmd
.arg("a")
.pipe_in(input.as_bytes())
// the command might have exited before all bytes have been pipe in.
// in that case, we don't care about the error (broken pipe)
.ignore_stdin_write_error()
.run();
println!("{}, {}", result.stdout, result.stderr); println!("{}, {}", result.stdout, result.stderr);

View file

@ -275,8 +275,8 @@ fn test_cp_arg_no_clobber_twice() {
.arg("dest.txt") .arg("dest.txt")
.run(); .run();
println!("stderr = {:?}", result.stderr); println!("stderr = {:?}", result.stderr_str());
println!("stdout = {:?}", result.stdout); println!("stdout = {:?}", result.stdout_str());
assert!(result.success); assert!(result.success);
assert!(result.stderr.is_empty()); assert!(result.stderr.is_empty());
assert_eq!(at.read("source.txt"), ""); assert_eq!(at.read("source.txt"), "");
@ -317,8 +317,8 @@ fn test_cp_arg_force() {
.arg(TEST_HELLO_WORLD_DEST) .arg(TEST_HELLO_WORLD_DEST)
.run(); .run();
println!("{:?}", result.stderr); println!("{:?}", result.stderr_str());
println!("{:?}", result.stdout); println!("{:?}", result.stdout_str());
assert!(result.success); 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");
@ -602,7 +602,7 @@ fn test_cp_deref_folder_to_folder() {
.arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .run();
println!("cp output {}", result.stdout); println!("cp output {}", result.stdout_str());
// Check that the exit code represents a successful copy. // Check that the exit code represents a successful copy.
assert!(result.success); assert!(result.success);
@ -611,12 +611,12 @@ fn test_cp_deref_folder_to_folder() {
{ {
let scene2 = TestScenario::new("ls"); let scene2 = TestScenario::new("ls");
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls source {}", result.stdout); println!("ls source {}", result.stdout_str());
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW); let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
} }
#[cfg(windows)] #[cfg(windows)]
@ -706,7 +706,7 @@ fn test_cp_no_deref_folder_to_folder() {
.arg(TEST_COPY_FROM_FOLDER) .arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.run(); .run();
println!("cp output {}", result.stdout); println!("cp output {}", result.stdout_str());
// Check that the exit code represents a successful copy. // Check that the exit code represents a successful copy.
assert!(result.success); assert!(result.success);
@ -715,12 +715,12 @@ fn test_cp_no_deref_folder_to_folder() {
{ {
let scene2 = TestScenario::new("ls"); let scene2 = TestScenario::new("ls");
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls source {}", result.stdout); println!("ls source {}", result.stdout_str());
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW); let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run(); let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
} }
#[cfg(windows)] #[cfg(windows)]
@ -809,7 +809,7 @@ fn test_cp_archive() {
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).run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
assert_eq!(creation, creation2); assert_eq!(creation, creation2);
assert!(result.success); assert!(result.success);
} }
@ -863,7 +863,7 @@ fn test_cp_archive_recursive() {
.arg(&at.subdir.join(TEST_COPY_TO_FOLDER)) .arg(&at.subdir.join(TEST_COPY_TO_FOLDER))
.run(); .run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
let scene2 = TestScenario::new("ls"); let scene2 = TestScenario::new("ls");
let result = scene2 let result = scene2
@ -872,7 +872,7 @@ fn test_cp_archive_recursive() {
.arg(&at.subdir.join(TEST_COPY_TO_FOLDER_NEW)) .arg(&at.subdir.join(TEST_COPY_TO_FOLDER_NEW))
.run(); .run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
assert!(at.file_exists( assert!(at.file_exists(
&at.subdir &at.subdir
.join(TEST_COPY_TO_FOLDER_NEW) .join(TEST_COPY_TO_FOLDER_NEW)
@ -946,7 +946,7 @@ fn test_cp_preserve_timestamps() {
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).run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
assert_eq!(creation, creation2); assert_eq!(creation, creation2);
assert!(result.success); assert!(result.success);
} }
@ -984,7 +984,7 @@ fn test_cp_dont_preserve_timestamps() {
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).run();
println!("ls dest {}", result.stdout); println!("ls dest {}", result.stdout_str());
println!("creation {:?} / {:?}", creation, creation2); println!("creation {:?} / {:?}", creation, creation2);
assert_ne!(creation, creation2); assert_ne!(creation, creation2);

View file

@ -28,13 +28,13 @@ fn test_date_rfc_3339() {
// 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])))"; let rfc_regexp = r"(\d+)-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d|60)(\.\d+)?(([Zz])|([\+|\-]([01]\d|2[0-3])))";
let re = Regex::new(rfc_regexp).unwrap(); let re = Regex::new(rfc_regexp).unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str().trim()));
result = scene.ucmd().arg("--rfc-3339=seconds").succeeds(); result = scene.ucmd().arg("--rfc-3339=seconds").succeeds();
// Check that the output matches the regexp // Check that the output matches the regexp
let re = Regex::new(rfc_regexp).unwrap(); let re = Regex::new(rfc_regexp).unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str().trim()));
} }
#[test] #[test]
@ -73,13 +73,13 @@ fn test_date_format_y() {
assert!(result.success); 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.trim())); assert!(re.is_match(&result.stdout_str().trim()));
result = scene.ucmd().arg("+%y").succeeds(); result = scene.ucmd().arg("+%y").succeeds();
assert!(result.success); assert!(result.success);
re = Regex::new(r"^\d{2}$").unwrap(); re = Regex::new(r"^\d{2}$").unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str().trim()));
} }
#[test] #[test]
@ -90,13 +90,13 @@ fn test_date_format_m() {
assert!(result.success); 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.trim())); assert!(re.is_match(&result.stdout_str().trim()));
result = scene.ucmd().arg("+%m").succeeds(); result = scene.ucmd().arg("+%m").succeeds();
assert!(result.success); assert!(result.success);
re = Regex::new(r"^\d{2}$").unwrap(); re = Regex::new(r"^\d{2}$").unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str().trim()));
} }
#[test] #[test]
@ -107,20 +107,20 @@ fn test_date_format_day() {
assert!(result.success); 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.trim())); assert!(re.is_match(&result.stdout_str().trim()));
result = scene.ucmd().arg("+%A").succeeds(); result = scene.ucmd().arg("+%A").succeeds();
assert!(result.success); assert!(result.success);
re = Regex::new(r"\S+").unwrap(); re = Regex::new(r"\S+").unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str().trim()));
result = scene.ucmd().arg("+%u").succeeds(); result = scene.ucmd().arg("+%u").succeeds();
assert!(result.success); assert!(result.success);
re = Regex::new(r"^\d{1}$").unwrap(); re = Regex::new(r"^\d{1}$").unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str().trim()));
} }
#[test] #[test]
@ -131,7 +131,7 @@ fn test_date_format_full_day() {
assert!(result.success); 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.trim())); assert!(re.is_match(&result.stdout_str().trim()));
} }
#[test] #[test]

View file

@ -7,10 +7,9 @@ const SUB_LINK: &str = "subdir/links/sublink.txt";
#[test] #[test]
fn test_du_basics() { fn test_du_basics() {
let (_at, mut ucmd) = at_and_ucmd!(); new_ucmd!()
let result = ucmd.run(); .succeeds()
assert!(result.success); .no_stderr();
assert_eq!(result.stderr, "");
} }
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn _du_basics(s: String) { fn _du_basics(s: String) {
@ -22,7 +21,7 @@ fn _du_basics(s: String) {
assert_eq!(s, answer); assert_eq!(s, answer);
} }
#[cfg(not(target_vendor = "apple"))] #[cfg(not(target_vendor = "apple"))]
fn _du_basics(s: String) { fn _du_basics(s: &str) {
let answer = "28\t./subdir let answer = "28\t./subdir
8\t./subdir/deeper 8\t./subdir/deeper
16\t./subdir/links 16\t./subdir/links
@ -38,19 +37,19 @@ fn test_du_basics_subdir() {
let result = ucmd.arg(SUB_DIR).run(); let result = ucmd.arg(SUB_DIR).run();
assert!(result.success); assert!(result.success);
assert_eq!(result.stderr, ""); assert_eq!(result.stderr, "");
_du_basics_subdir(result.stdout); _du_basics_subdir(result.stdout_str());
} }
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn _du_basics_subdir(s: String) { fn _du_basics_subdir(s: &str) {
assert_eq!(s, "4\tsubdir/deeper\n"); assert_eq!(s, "4\tsubdir/deeper\n");
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn _du_basics_subdir(s: String) { fn _du_basics_subdir(s: &str) {
assert_eq!(s, "0\tsubdir/deeper\n"); assert_eq!(s, "0\tsubdir/deeper\n");
} }
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
fn _du_basics_subdir(s: String) { fn _du_basics_subdir(s: &str) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !uucore::os::is_wsl_1() { if !uucore::os::is_wsl_1() {
assert_eq!(s, "8\tsubdir/deeper\n"); assert_eq!(s, "8\tsubdir/deeper\n");
@ -64,7 +63,7 @@ fn test_du_basics_bad_name() {
let (_at, mut ucmd) = at_and_ucmd!(); let (_at, mut ucmd) = at_and_ucmd!();
let result = ucmd.arg("bad_name").run(); let result = ucmd.arg("bad_name").run();
assert_eq!(result.stdout, ""); assert_eq!(result.stdout_str(), "");
assert_eq!( assert_eq!(
result.stderr, result.stderr,
"du: error: bad_name: No such file or directory\n" "du: error: bad_name: No such file or directory\n"
@ -81,20 +80,20 @@ fn test_du_soft_link() {
let result = ts.ucmd().arg(SUB_DIR_LINKS).run(); let result = ts.ucmd().arg(SUB_DIR_LINKS).run();
assert!(result.success); assert!(result.success);
assert_eq!(result.stderr, ""); assert_eq!(result.stderr, "");
_du_soft_link(result.stdout); _du_soft_link(result.stdout_str());
} }
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn _du_soft_link(s: String) { fn _du_soft_link(s: &str) {
// 'macos' host variants may have `du` output variation for soft links // 'macos' host variants may have `du` output variation for soft links
assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n")); assert!((s == "12\tsubdir/links\n") || (s == "16\tsubdir/links\n"));
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn _du_soft_link(s: String) { fn _du_soft_link(s: &str) {
assert_eq!(s, "8\tsubdir/links\n"); assert_eq!(s, "8\tsubdir/links\n");
} }
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
fn _du_soft_link(s: String) { fn _du_soft_link(s: &str) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !uucore::os::is_wsl_1() { if !uucore::os::is_wsl_1() {
assert_eq!(s, "16\tsubdir/links\n"); assert_eq!(s, "16\tsubdir/links\n");
@ -114,19 +113,19 @@ fn test_du_hard_link() {
assert!(result.success); assert!(result.success);
assert_eq!(result.stderr, ""); assert_eq!(result.stderr, "");
// We do not double count hard links as the inodes are identical // We do not double count hard links as the inodes are identical
_du_hard_link(result.stdout); _du_hard_link(result.stdout_str());
} }
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn _du_hard_link(s: String) { fn _du_hard_link(s: &str) {
assert_eq!(s, "12\tsubdir/links\n") assert_eq!(s, "12\tsubdir/links\n")
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn _du_hard_link(s: String) { fn _du_hard_link(s: &str) {
assert_eq!(s, "8\tsubdir/links\n") assert_eq!(s, "8\tsubdir/links\n")
} }
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
fn _du_hard_link(s: String) { fn _du_hard_link(s: &str) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !uucore::os::is_wsl_1() { if !uucore::os::is_wsl_1() {
assert_eq!(s, "16\tsubdir/links\n"); assert_eq!(s, "16\tsubdir/links\n");
@ -142,19 +141,19 @@ fn test_du_d_flag() {
let result = ts.ucmd().arg("-d").arg("1").run(); let result = ts.ucmd().arg("-d").arg("1").run();
assert!(result.success); assert!(result.success);
assert_eq!(result.stderr, ""); assert_eq!(result.stderr, "");
_du_d_flag(result.stdout); _du_d_flag(result.stdout_str());
} }
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
fn _du_d_flag(s: String) { fn _du_d_flag(s: &str) {
assert_eq!(s, "16\t./subdir\n20\t./\n"); assert_eq!(s, "16\t./subdir\n20\t./\n");
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn _du_d_flag(s: String) { fn _du_d_flag(s: &str) {
assert_eq!(s, "8\t./subdir\n8\t./\n"); assert_eq!(s, "8\t./subdir\n8\t./\n");
} }
#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))] #[cfg(all(not(target_vendor = "apple"), not(target_os = "windows")))]
fn _du_d_flag(s: String) { fn _du_d_flag(s: &str) {
// MS-WSL linux has altered expected output // MS-WSL linux has altered expected output
if !uucore::os::is_wsl_1() { if !uucore::os::is_wsl_1() {
assert_eq!(s, "28\t./subdir\n36\t./\n"); assert_eq!(s, "28\t./subdir\n36\t./\n");
@ -167,8 +166,27 @@ fn _du_d_flag(s: String) {
fn test_du_h_flag_empty_file() { fn test_du_h_flag_empty_file() {
let ts = TestScenario::new("du"); let ts = TestScenario::new("du");
let result = ts.ucmd().arg("-h").arg("empty.txt").run(); ts.ucmd()
.arg("-h")
.arg("empty.txt")
.succeeds()
.stdout_only("0\tempty.txt\n");
}
#[cfg(feature = "touch")]
#[test]
fn test_du_time() {
let ts = TestScenario::new("du");
let touch = ts.ccmd("touch").arg("-a").arg("-m").arg("-t").arg("201505150000").arg("date_test").run();
assert!(touch.success);
let result = ts.ucmd().arg("--time").arg("date_test").run();
// cleanup by removing test file
ts.cmd("rm").arg("date_test").run();
assert!(result.success); assert!(result.success);
assert_eq!(result.stderr, ""); assert_eq!(result.stderr, "");
assert_eq!(result.stdout, "0\tempty.txt\n"); assert_eq!(result.stdout, "0\t2015-05-15 00:00\tdate_test\n");
} }

View file

@ -2,22 +2,20 @@ use crate::common::util::*;
#[test] #[test]
fn test_default() { fn test_default() {
//CmdResult.stdout_only(...) trims trailing newlines new_ucmd!()
assert_eq!("hi\n", new_ucmd!().arg("hi").succeeds().no_stderr().stdout); .arg("hi")
.succeeds()
.stdout_only("hi\n");
} }
#[test] #[test]
fn test_no_trailing_newline() { fn test_no_trailing_newline() {
//CmdResult.stdout_only(...) trims trailing newlines
assert_eq!(
"hi",
new_ucmd!() new_ucmd!()
.arg("-n") .arg("-n")
.arg("hi") .arg("hi")
.succeeds() .succeeds()
.no_stderr() .no_stderr()
.stdout .stdout_only("hi");
);
} }
#[test] #[test]
@ -192,39 +190,38 @@ fn test_hyphen_values_inside_string() {
new_ucmd!() new_ucmd!()
.arg("'\"\n'CXXFLAGS=-g -O2'\n\"'") .arg("'\"\n'CXXFLAGS=-g -O2'\n\"'")
.succeeds() .succeeds()
.stdout .stdout_contains("CXXFLAGS");
.contains("CXXFLAGS");
} }
#[test] #[test]
fn test_hyphen_values_at_start() { fn test_hyphen_values_at_start() {
let result = new_ucmd!() new_ucmd!()
.arg("-E") .arg("-E")
.arg("-test") .arg("-test")
.arg("araba") .arg("araba")
.arg("-merci") .arg("-merci")
.run(); .run()
.success()
assert!(result.success); .stdout_does_not_contain("-E")
assert_eq!(false, result.stdout.contains("-E")); .stdout_is("-test araba -merci\n");
assert_eq!(result.stdout, "-test araba -merci\n");
} }
#[test] #[test]
fn test_hyphen_values_between() { fn test_hyphen_values_between() {
let result = new_ucmd!().arg("test").arg("-E").arg("araba").run(); new_ucmd!()
.arg("test")
.arg("-E")
.arg("araba")
.run()
.success()
.stdout_is("test -E araba\n");
assert!(result.success); new_ucmd!()
assert_eq!(result.stdout, "test -E araba\n");
let result = new_ucmd!()
.arg("dumdum ") .arg("dumdum ")
.arg("dum dum dum") .arg("dum dum dum")
.arg("-e") .arg("-e")
.arg("dum") .arg("dum")
.run(); .run()
.success()
assert!(result.success); .stdout_is("dumdum dum dum dum -e dum\n");
assert_eq!(result.stdout, "dumdum dum dum dum -e dum\n");
assert_eq!(true, result.stdout.contains("-e"));
} }

View file

@ -8,45 +8,35 @@ use tempfile::tempdir;
#[test] #[test]
fn test_env_help() { fn test_env_help() {
assert!(new_ucmd!() new_ucmd!()
.arg("--help") .arg("--help")
.succeeds() .succeeds()
.no_stderr() .no_stderr()
.stdout .stdout_contains("OPTIONS:");
.contains("OPTIONS:"));
} }
#[test] #[test]
fn test_env_version() { fn test_env_version() {
assert!(new_ucmd!() new_ucmd!()
.arg("--version") .arg("--version")
.succeeds() .succeeds()
.no_stderr() .no_stderr()
.stdout .stdout_contains(util_name!());
.contains(util_name!()));
} }
#[test] #[test]
fn test_echo() { fn test_echo() {
// assert!(new_ucmd!().arg("printf").arg("FOO-bar").succeeds().no_stderr().stdout.contains("FOO-bar")); let result = new_ucmd!()
let mut cmd = new_ucmd!(); .arg("echo")
cmd.arg("echo").arg("FOO-bar"); .arg("FOO-bar")
println!("cmd={:?}", cmd); .succeeds();
let result = cmd.run(); assert_eq!(result.stdout_str().trim(), "FOO-bar");
println!("success={:?}", result.success);
println!("stdout={:?}", result.stdout);
println!("stderr={:?}", result.stderr);
assert!(result.success);
let out = result.stdout.trim_end();
assert_eq!(out, "FOO-bar");
} }
#[test] #[test]
fn test_file_option() { fn test_file_option() {
let out = new_ucmd!().arg("-f").arg("vars.conf.txt").run().stdout; let out = new_ucmd!().arg("-f").arg("vars.conf.txt").run().stdout_move_str();
assert_eq!( assert_eq!(
out.lines() out.lines()
@ -63,7 +53,7 @@ fn test_combined_file_set() {
.arg("vars.conf.txt") .arg("vars.conf.txt")
.arg("FOO=bar.alt") .arg("FOO=bar.alt")
.run() .run()
.stdout; .stdout_move_str();
assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt").count(), 1); assert_eq!(out.lines().filter(|&line| line == "FOO=bar.alt").count(), 1);
} }
@ -76,8 +66,8 @@ fn test_combined_file_set_unset() {
.arg("-f") .arg("-f")
.arg("vars.conf.txt") .arg("vars.conf.txt")
.arg("FOO=bar.alt") .arg("FOO=bar.alt")
.run() .succeeds()
.stdout; .stdout_move_str();
assert_eq!( assert_eq!(
out.lines() out.lines()
@ -89,17 +79,17 @@ fn test_combined_file_set_unset() {
#[test] #[test]
fn test_single_name_value_pair() { fn test_single_name_value_pair() {
let out = new_ucmd!().arg("FOO=bar").run().stdout; let out = new_ucmd!().arg("FOO=bar").run();
assert!(out.lines().any(|line| line == "FOO=bar")); assert!(out.stdout_str().lines().any(|line| line == "FOO=bar"));
} }
#[test] #[test]
fn test_multiple_name_value_pairs() { fn test_multiple_name_value_pairs() {
let out = new_ucmd!().arg("FOO=bar").arg("ABC=xyz").run().stdout; let out = new_ucmd!().arg("FOO=bar").arg("ABC=xyz").run();
assert_eq!( assert_eq!(
out.lines() out.stdout_str().lines()
.filter(|&line| line == "FOO=bar" || line == "ABC=xyz") .filter(|&line| line == "FOO=bar" || line == "ABC=xyz")
.count(), .count(),
2 2
@ -110,13 +100,8 @@ fn test_multiple_name_value_pairs() {
fn test_ignore_environment() { fn test_ignore_environment() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let out = scene.ucmd().arg("-i").run().stdout; scene.ucmd().arg("-i").run().no_stdout();
scene.ucmd().arg("-").run().no_stdout();
assert_eq!(out, "");
let out = scene.ucmd().arg("-").run().stdout;
assert_eq!(out, "");
} }
#[test] #[test]
@ -126,8 +111,8 @@ fn test_null_delimiter() {
.arg("--null") .arg("--null")
.arg("FOO=bar") .arg("FOO=bar")
.arg("ABC=xyz") .arg("ABC=xyz")
.run() .succeeds()
.stdout; .stdout_move_str();
let mut vars: Vec<_> = out.split('\0').collect(); let mut vars: Vec<_> = out.split('\0').collect();
assert_eq!(vars.len(), 3); assert_eq!(vars.len(), 3);
@ -145,8 +130,8 @@ fn test_unset_variable() {
.ucmd_keepenv() .ucmd_keepenv()
.arg("-u") .arg("-u")
.arg("HOME") .arg("HOME")
.run() .succeeds()
.stdout; .stdout_move_str();
assert_eq!(out.lines().any(|line| line.starts_with("HOME=")), false); assert_eq!(out.lines().any(|line| line.starts_with("HOME=")), false);
} }
@ -173,8 +158,8 @@ fn test_change_directory() {
.arg("--chdir") .arg("--chdir")
.arg(&temporary_path) .arg(&temporary_path)
.arg(pwd) .arg(pwd)
.run() .succeeds()
.stdout; .stdout_move_str();
assert_eq!(out.trim(), temporary_path.as_os_str()) assert_eq!(out.trim(), temporary_path.as_os_str())
} }
@ -193,8 +178,8 @@ fn test_change_directory() {
.ucmd() .ucmd()
.arg("--chdir") .arg("--chdir")
.arg(&temporary_path) .arg(&temporary_path)
.run() .succeeds()
.stdout; .stdout_move_str();
assert_eq!( assert_eq!(
out.lines() out.lines()
.any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())), .any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())),
@ -214,6 +199,6 @@ fn test_fail_change_directory() {
.arg(some_non_existing_path) .arg(some_non_existing_path)
.arg("pwd") .arg("pwd")
.fails() .fails()
.stderr; .stderr_move_str();
assert!(out.contains("env: cannot change directory to ")); assert!(out.contains("env: cannot change directory to "));
} }

View file

@ -2,57 +2,54 @@ use crate::common::util::*;
#[test] #[test]
fn test_with_tab() { fn test_with_tab() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("with-tab.txt")
let result = ucmd.arg("with-tab.txt").run(); .succeeds()
assert!(result.success); .stdout_contains(" ")
assert!(result.stdout.contains(" ")); .stdout_does_not_contain("\t");
assert!(!result.stdout.contains("\t"));
} }
#[test] #[test]
fn test_with_trailing_tab() { fn test_with_trailing_tab() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("with-trailing-tab.txt")
let result = ucmd.arg("with-trailing-tab.txt").run(); .succeeds()
assert!(result.success); .stdout_contains("with tabs=> ")
assert!(result.stdout.contains("with tabs=> ")); .stdout_does_not_contain("\t");
assert!(!result.stdout.contains("\t"));
} }
#[test] #[test]
fn test_with_trailing_tab_i() { fn test_with_trailing_tab_i() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("with-trailing-tab.txt")
let result = ucmd.arg("with-trailing-tab.txt").arg("-i").run(); .arg("-i")
assert!(result.success); .succeeds()
assert!(result.stdout.contains(" // with tabs=>\t")); .stdout_contains(" // with tabs=>\t");
} }
#[test] #[test]
fn test_with_tab_size() { fn test_with_tab_size() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("with-tab.txt")
let result = ucmd.arg("with-tab.txt").arg("--tabs=10").run(); .arg("--tabs=10")
assert!(result.success); .succeeds()
assert!(result.stdout.contains(" ")); .stdout_contains(" ");
} }
#[test] #[test]
fn test_with_space() { fn test_with_space() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("with-spaces.txt")
let result = ucmd.arg("with-spaces.txt").run(); .succeeds()
assert!(result.success); .stdout_contains(" return");
assert!(result.stdout.contains(" return"));
} }
#[test] #[test]
fn test_with_multiple_files() { fn test_with_multiple_files() {
let (_, mut ucmd) = at_and_ucmd!(); new_ucmd!()
.arg("with-spaces.txt")
let result = ucmd.arg("with-spaces.txt").arg("with-tab.txt").run(); .arg("with-tab.txt")
assert!(result.success); .succeeds()
assert!(result.stdout.contains(" return")); .stdout_contains(" return")
assert!(result.stdout.contains(" ")); .stdout_contains(" ");
} }

View file

@ -32,13 +32,10 @@ fn test_first_100000_integers() {
} }
println!("STDIN='{}'", instring); println!("STDIN='{}'", instring);
let result = new_ucmd!().pipe_in(instring.as_bytes()).run(); let result = new_ucmd!().pipe_in(instring.as_bytes()).succeeds();
let stdout = result.stdout;
assert!(result.success);
// `seq 0 100000 | factor | sha1sum` => "4ed2d8403934fa1c76fe4b84c5d4b8850299c359" // `seq 0 100000 | factor | sha1sum` => "4ed2d8403934fa1c76fe4b84c5d4b8850299c359"
let hash_check = sha1::Sha1::from(stdout.as_bytes()).hexdigest(); let hash_check = sha1::Sha1::from(result.stdout()).hexdigest();
assert_eq!(hash_check, "4ed2d8403934fa1c76fe4b84c5d4b8850299c359"); assert_eq!(hash_check, "4ed2d8403934fa1c76fe4b84c5d4b8850299c359");
} }

View file

@ -5,7 +5,7 @@ fn test_fmt() {
let result = new_ucmd!().arg("one-word-per-line.txt").run(); let result = new_ucmd!().arg("one-word-per-line.txt").run();
//.stdout_is_fixture("call_graph.expected"); //.stdout_is_fixture("call_graph.expected");
assert_eq!( assert_eq!(
result.stdout.trim(), result.stdout_str().trim(),
"this is a file with one word per line" "this is a file with one word per line"
); );
} }
@ -15,7 +15,7 @@ fn test_fmt_q() {
let result = new_ucmd!().arg("-q").arg("one-word-per-line.txt").run(); let result = new_ucmd!().arg("-q").arg("one-word-per-line.txt").run();
//.stdout_is_fixture("call_graph.expected"); //.stdout_is_fixture("call_graph.expected");
assert_eq!( assert_eq!(
result.stdout.trim(), result.stdout_str().trim(),
"this is a file with one word per line" "this is a file with one word per line"
); );
} }
@ -42,7 +42,7 @@ fn test_fmt_w() {
.arg("one-word-per-line.txt") .arg("one-word-per-line.txt")
.run(); .run();
//.stdout_is_fixture("call_graph.expected"); //.stdout_is_fixture("call_graph.expected");
assert_eq!(result.stdout.trim(), "this is a file with one word per line"); assert_eq!(result.stdout_str().trim(), "this is a file with one word per line");
} }

View file

@ -248,6 +248,71 @@ fn test_fold_at_word_boundary_only_whitespace_preserve_final_newline() {
.stdout_is(" \n \n"); .stdout_is(" \n \n");
} }
#[test]
fn test_backspace_should_be_preserved() {
new_ucmd!().pipe_in("\x08").succeeds().stdout_is("\x08");
}
#[test]
fn test_backspaced_char_should_be_preserved() {
new_ucmd!().pipe_in("x\x08").succeeds().stdout_is("x\x08");
}
#[test]
fn test_backspace_should_decrease_column_count() {
new_ucmd!()
.arg("-w2")
.pipe_in("1\x08345")
.succeeds()
.stdout_is("1\x0834\n5");
}
#[test]
fn test_backspace_should_not_decrease_column_count_past_zero() {
new_ucmd!()
.arg("-w2")
.pipe_in("1\x08\x083456")
.succeeds()
.stdout_is("1\x08\x0834\n56");
}
#[test]
fn test_backspace_is_not_word_boundary() {
new_ucmd!()
.args(&["-w10", "-s"])
.pipe_in("foobar\x086789abcdef")
.succeeds()
.stdout_is("foobar\x086789a\nbcdef");
}
#[test]
fn test_carriage_return_should_be_preserved() {
new_ucmd!().pipe_in("\r").succeeds().stdout_is("\r");
}
#[test]
fn test_carriage_return_overwrriten_char_should_be_preserved() {
new_ucmd!().pipe_in("x\ry").succeeds().stdout_is("x\ry");
}
#[test]
fn test_carriage_return_should_reset_column_count() {
new_ucmd!()
.arg("-w6")
.pipe_in("12345\r123456789abcdef")
.succeeds()
.stdout_is("12345\r123456\n789abc\ndef");
}
#[test]
fn test_carriage_return_is_not_word_boundary() {
new_ucmd!()
.args(&["-w6", "-s"])
.pipe_in("fizz\rbuzz\rfizzbuzz")
.succeeds()
.stdout_is("fizz\rbuzz\rfizzbu\nzz");
}
// //
// bytewise tests // bytewise tests
@ -397,3 +462,84 @@ fn test_bytewise_fold_at_word_boundary_only_whitespace_preserve_final_newline()
.succeeds() .succeeds()
.stdout_is(" \n \n"); .stdout_is(" \n \n");
} }
#[test]
fn test_bytewise_backspace_should_be_preserved() {
new_ucmd!()
.arg("-b")
.pipe_in("\x08")
.succeeds()
.stdout_is("\x08");
}
#[test]
fn test_bytewise_backspaced_char_should_be_preserved() {
new_ucmd!()
.arg("-b")
.pipe_in("x\x08")
.succeeds()
.stdout_is("x\x08");
}
#[test]
fn test_bytewise_backspace_should_not_decrease_column_count() {
new_ucmd!()
.args(&["-w2", "-b"])
.pipe_in("1\x08345")
.succeeds()
.stdout_is("1\x08\n34\n5");
}
#[test]
fn test_bytewise_backspace_is_not_word_boundary() {
new_ucmd!()
.args(&["-w10", "-s", "-b"])
.pipe_in("foobar\x0889abcdef")
.succeeds()
.stdout_is("foobar\x0889a\nbcdef");
}
#[test]
fn test_bytewise_carriage_return_should_be_preserved() {
new_ucmd!()
.arg("-b")
.pipe_in("\r")
.succeeds()
.stdout_is("\r");
}
#[test]
fn test_bytewise_carriage_return_overwrriten_char_should_be_preserved() {
new_ucmd!()
.arg("-b")
.pipe_in("x\ry")
.succeeds()
.stdout_is("x\ry");
}
#[test]
fn test_bytewise_carriage_return_should_not_reset_column_count() {
new_ucmd!()
.args(&["-w6", "-b"])
.pipe_in("12345\r123456789abcdef")
.succeeds()
.stdout_is("12345\r\n123456\n789abc\ndef");
}
#[test]
fn test_bytewise_carriage_return_is_not_word_boundary() {
new_ucmd!()
.args(&["-w6", "-s", "-b"])
.pipe_in("fizz\rbuzz\rfizzbuzz")
.succeeds()
.stdout_is("fizz\rb\nuzz\rfi\nzzbuzz");
}
#[test]
fn test_obsolete_syntax() {
new_ucmd!()
.arg("-5")
.arg("-s")
.arg("space_separated_words.txt")
.succeeds()
.stdout_is("test1\n \ntest2\n \ntest3\n \ntest4\n \ntest5\n \ntest6\n ");
}

View file

@ -2,26 +2,25 @@ use crate::common::util::*;
#[test] #[test]
fn test_groups() { fn test_groups() {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!().run();
let result = ucmd.run(); println!("result.stdout = {}", result.stdout_str());
println!("result.stdout {}", result.stdout); println!("result.stderr = {}", result.stderr_str());
println!("result.stderr = {}", result.stderr); if is_ci() && result.stdout_str().trim().is_empty() {
if is_ci() && result.stdout.trim().is_empty() {
// In the CI, some server are failing to return the group. // In the CI, some server are failing to return the group.
// As seems to be a configuration issue, ignoring it // As seems to be a configuration issue, ignoring it
return; return;
} }
assert!(result.success); assert!(result.success);
assert!(!result.stdout.trim().is_empty()); assert!(!result.stdout_str().trim().is_empty());
} }
#[test] #[test]
fn test_groups_arg() { fn test_groups_arg() {
// get the username with the "id -un" command // get the username with the "id -un" command
let result = TestScenario::new("id").ucmd_keepenv().arg("-un").run(); let result = TestScenario::new("id").ucmd_keepenv().arg("-un").run();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
let s1 = String::from(result.stdout.trim()); let s1 = String::from(result.stdout_str().trim());
if is_ci() && s1.parse::<f64>().is_ok() { if is_ci() && s1.parse::<f64>().is_ok() {
// In the CI, some server are failing to return id -un. // In the CI, some server are failing to return id -un.
// So, if we are getting a uid, just skip this test // So, if we are getting a uid, just skip this test
@ -29,18 +28,18 @@ fn test_groups_arg() {
return; return;
} }
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
assert!(result.success); assert!(result.success);
assert!(!result.stdout.is_empty()); assert!(!result.stdout_str().is_empty());
let username = result.stdout.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!(); let (_, mut ucmd) = at_and_ucmd!();
let result = ucmd.arg(username).run(); let result = ucmd.arg(username).run();
println!("result.stdout {}", result.stdout); println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr); println!("result.stderr = {}", result.stderr_str());
assert!(result.success); assert!(result.success);
assert!(!result.stdout.is_empty()); assert!(!result.stdout_str().is_empty());
} }

View file

@ -17,14 +17,14 @@ macro_rules! test_digest {
fn test_single_file() { fn test_single_file() {
let ts = TestScenario::new("hashsum"); let ts = TestScenario::new("hashsum");
assert_eq!(ts.fixtures.read(EXPECTED_FILE), assert_eq!(ts.fixtures.read(EXPECTED_FILE),
get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg("input.txt").succeeds().no_stderr().stdout)); get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg("input.txt").succeeds().no_stderr().stdout_str()));
} }
#[test] #[test]
fn test_stdin() { fn test_stdin() {
let ts = TestScenario::new("hashsum"); let ts = TestScenario::new("hashsum");
assert_eq!(ts.fixtures.read(EXPECTED_FILE), assert_eq!(ts.fixtures.read(EXPECTED_FILE),
get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout)); get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout_str()));
} }
} }
)*) )*)

View file

@ -9,5 +9,5 @@ fn test_normal() {
assert!(result.success); assert!(result.success);
let re = Regex::new(r"^[0-9a-f]{8}").unwrap(); let re = Regex::new(r"^[0-9a-f]{8}").unwrap();
assert!(re.is_match(&result.stdout.trim())); assert!(re.is_match(&result.stdout_str()));
} }

View file

@ -6,8 +6,8 @@ fn test_hostname() {
let ls_short_res = new_ucmd!().arg("-s").succeeds(); let ls_short_res = new_ucmd!().arg("-s").succeeds();
let ls_domain_res = new_ucmd!().arg("-d").succeeds(); let ls_domain_res = new_ucmd!().arg("-d").succeeds();
assert!(ls_default_res.stdout.len() >= ls_short_res.stdout.len()); assert!(ls_default_res.stdout().len() >= ls_short_res.stdout().len());
assert!(ls_default_res.stdout.len() >= ls_domain_res.stdout.len()); assert!(ls_default_res.stdout().len() >= ls_domain_res.stdout().len());
} }
// FixME: fails for "MacOS" // FixME: fails for "MacOS"
@ -17,14 +17,14 @@ fn test_hostname_ip() {
let result = new_ucmd!().arg("-i").run(); let result = new_ucmd!().arg("-i").run();
println!("{:#?}", result); println!("{:#?}", result);
assert!(result.success); assert!(result.success);
assert!(!result.stdout.trim().is_empty()); assert!(!result.stdout_str().trim().is_empty());
} }
#[test] #[test]
fn test_hostname_full() { fn test_hostname_full() {
let result = new_ucmd!().arg("-f").succeeds();
assert!(!result.stdout.trim().is_empty());
let ls_short_res = new_ucmd!().arg("-s").succeeds(); let ls_short_res = new_ucmd!().arg("-s").succeeds();
assert!(result.stdout.trim().contains(ls_short_res.stdout.trim())); assert!(!ls_short_res.stdout_str().trim().is_empty());
new_ucmd!().arg("-f").succeeds()
.stdout_contains(ls_short_res.stdout_str().trim());
} }

View file

@ -9,33 +9,29 @@ fn return_whoami_username() -> String {
return String::from(""); return String::from("");
} }
result.stdout.trim().to_string() result.stdout_str().trim().to_string()
} }
#[test] #[test]
fn test_id() { fn test_id() {
let scene = TestScenario::new(util_name!()); let result = new_ucmd!().arg("-u").run();
let mut result = scene.ucmd().arg("-u").run();
if result.stderr.contains("cannot find name for user ID") { if result.stderr.contains("cannot find name for user ID") {
// 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;
} }
assert!(result.success);
let uid = String::from(result.stdout.trim()); let uid = result.success().stdout_str().trim();
result = scene.ucmd().run(); let result = new_ucmd!().run();
if is_ci() && result.stderr.contains("cannot find name for user ID") { if is_ci() && result.stderr.contains("cannot find name for user ID") {
// 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;
} }
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr); if !result.stderr_str().contains("Could not find uid") {
if !result.stderr.contains("Could not find uid") {
// Verify that the id found by --user/-u exists in the list // Verify that the id found by --user/-u exists in the list
assert!(result.stdout.contains(&uid)); result.success().stdout_contains(&uid);
} }
} }
@ -47,88 +43,64 @@ fn test_id_from_name() {
return; return;
} }
let scene = TestScenario::new(util_name!()); let result = new_ucmd!().arg(&username).succeeds();
let result = scene.ucmd().arg(&username).succeeds(); let uid = result.stdout_str().trim();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr); new_ucmd!().succeeds()
assert!(result.success);
let uid = String::from(result.stdout.trim());
let result = scene.ucmd().succeeds();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
// Verify that the id found by --user/-u exists in the list // Verify that the id found by --user/-u exists in the list
assert!(result.stdout.contains(&uid)); .stdout_contains(uid)
// Verify that the username found by whoami exists in the list // Verify that the username found by whoami exists in the list
assert!(result.stdout.contains(&username)); .stdout_contains(username);
} }
#[test] #[test]
fn test_id_name_from_id() { fn test_id_name_from_id() {
let mut scene = TestScenario::new(util_name!()); let result = new_ucmd!().arg("-u").succeeds();
let result = scene.ucmd().arg("-u").run(); let uid = result.stdout_str().trim();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
assert!(result.success);
let uid = String::from(result.stdout.trim());
scene = TestScenario::new(util_name!()); let result = new_ucmd!().arg("-nu").arg(uid).run();
let result = scene.ucmd().arg("-nu").arg(uid).run();
if is_ci() && result.stderr.contains("No such user/group") { if is_ci() && result.stderr.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;
} }
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
assert!(result.success);
let username_id = String::from(result.stdout.trim()); let username_id = result
.success()
.stdout_str()
.trim();
scene = TestScenario::new("whoami"); let scene = TestScenario::new("whoami");
let result = scene.cmd("whoami").run(); let result = scene.cmd("whoami").succeeds();
let username_whoami = result.stdout.trim(); let username_whoami = result.stdout_str().trim();
assert_eq!(username_id, username_whoami); assert_eq!(username_id, username_whoami);
} }
#[test] #[test]
fn test_id_group() { fn test_id_group() {
let scene = TestScenario::new(util_name!()); let mut result = new_ucmd!().arg("-g").succeeds();
let s1 = result.stdout_str().trim();
let mut result = scene.ucmd().arg("-g").succeeds();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
assert!(result.success);
let s1 = String::from(result.stdout.trim());
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<f64>().is_ok());
result = scene.ucmd().arg("--group").succeeds(); result = new_ucmd!().arg("--group").succeeds();
println!("result.stdout = {}", result.stdout); let s1 = result.stdout_str().trim();
println!("result.stderr = {}", result.stderr);
assert!(result.success);
let s1 = String::from(result.stdout.trim());
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<f64>().is_ok());
} }
#[test] #[test]
fn test_id_groups() { fn test_id_groups() {
let scene = TestScenario::new(util_name!()); let result = new_ucmd!().arg("-G").succeeds();
let result = scene.ucmd().arg("-G").succeeds();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
assert!(result.success); assert!(result.success);
let groups = result.stdout.trim().split_whitespace(); let groups = result.stdout_str().trim().split_whitespace();
for s in groups { for s in groups {
assert!(s.parse::<f64>().is_ok()); assert!(s.parse::<f64>().is_ok());
} }
let result = scene.ucmd().arg("--groups").succeeds(); let result = new_ucmd!().arg("--groups").succeeds();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
assert!(result.success); assert!(result.success);
let groups = result.stdout.trim().split_whitespace(); let groups = result.stdout_str().trim().split_whitespace();
for s in groups { for s in groups {
assert!(s.parse::<f64>().is_ok()); assert!(s.parse::<f64>().is_ok());
} }
@ -136,15 +108,12 @@ fn test_id_groups() {
#[test] #[test]
fn test_id_user() { fn test_id_user() {
let scene = TestScenario::new(util_name!()); let mut result = new_ucmd!().arg("-u").succeeds();
let s1 = result.stdout_str().trim();
let mut result = scene.ucmd().arg("-u").succeeds();
assert!(result.success);
let s1 = String::from(result.stdout.trim());
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<f64>().is_ok());
result = scene.ucmd().arg("--user").succeeds();
assert!(result.success); result = new_ucmd!().arg("--user").succeeds();
let s1 = String::from(result.stdout.trim()); let s1 = result.stdout_str().trim();
assert!(s1.parse::<f64>().is_ok()); assert!(s1.parse::<f64>().is_ok());
} }
@ -156,17 +125,13 @@ fn test_id_pretty_print() {
return; return;
} }
let scene = TestScenario::new(util_name!()); let result = new_ucmd!().arg("-p").run();
let result = scene.ucmd().arg("-p").run(); if result.stdout_str().trim() == "" {
if result.stdout.trim() == "" {
// Sometimes, the CI is failing here with // Sometimes, the CI is failing here with
// old rust versions on Linux // old rust versions on Linux
return; return;
} }
println!("result.stdout = {}", result.stdout); result.success().stdout_contains(username);
println!("result.stderr = {}", result.stderr);
assert!(result.success);
assert!(result.stdout.contains(&username));
} }
#[test] #[test]
@ -176,12 +141,7 @@ fn test_id_password_style() {
// Sometimes, the CI is failing here // Sometimes, the CI is failing here
return; return;
} }
let scene = TestScenario::new(util_name!());
let result = scene.ucmd().arg("-P").succeeds(); let result = new_ucmd!().arg("-P").succeeds();
assert!(result.stdout_str().starts_with(&username));
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
assert!(result.success);
assert!(result.stdout.starts_with(&username));
} }

View file

@ -2,6 +2,8 @@ use crate::common::util::*;
use filetime::FileTime; use filetime::FileTime;
use rust_users::*; use rust_users::*;
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
#[cfg(not(windows))]
use std::process::Command;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::thread::sleep; use std::thread::sleep;
@ -193,12 +195,8 @@ fn test_install_mode_numeric() {
let mode_arg = "-m 0333"; let mode_arg = "-m 0333";
at.mkdir(dir2); at.mkdir(dir2);
let result = scene.ucmd().arg(mode_arg).arg(file).arg(dir2).run(); scene.ucmd().arg(mode_arg).arg(file).arg(dir2).succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
let dest_file = &format!("{}/{}", dir2, file); let dest_file = &format!("{}/{}", dir2, file);
assert!(at.file_exists(file)); assert!(at.file_exists(file));
assert!(at.file_exists(dest_file)); assert!(at.file_exists(dest_file));
@ -311,16 +309,13 @@ fn test_install_target_new_file_with_group() {
.arg(format!("{}/{}", dir, file)) .arg(format!("{}/{}", dir, file))
.run(); .run();
println!("stderr = {:?}", result.stderr); if is_ci() && result.stderr_str().contains("error: no such group:") {
println!("stdout = {:?}", result.stdout);
if is_ci() && result.stderr.contains("error: no such group:") {
// In the CI, some server are failing to return the group. // In the CI, some server are failing to return the group.
// 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!(at.file_exists(file)); assert!(at.file_exists(file));
assert!(at.file_exists(&format!("{}/{}", dir, file))); assert!(at.file_exists(&format!("{}/{}", dir, file)));
} }
@ -341,16 +336,13 @@ fn test_install_target_new_file_with_owner() {
.arg(format!("{}/{}", dir, file)) .arg(format!("{}/{}", dir, file))
.run(); .run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
if is_ci() && result.stderr.contains("error: no such user:") { if is_ci() && result.stderr.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;
} }
assert!(result.success); result.success();
assert!(at.file_exists(file)); assert!(at.file_exists(file));
assert!(at.file_exists(&format!("{}/{}", dir, file))); assert!(at.file_exists(&format!("{}/{}", dir, file)));
} }
@ -364,13 +356,10 @@ fn test_install_target_new_file_failing_nonexistent_parent() {
at.touch(file1); at.touch(file1);
let err = ucmd ucmd.arg(file1)
.arg(file1)
.arg(format!("{}/{}", dir, file2)) .arg(format!("{}/{}", dir, file2))
.fails() .fails()
.stderr; .stderr_contains(&"not a directory");
assert!(err.contains("not a directory"))
} }
#[test] #[test]
@ -415,18 +404,12 @@ fn test_install_copy_file() {
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn test_install_target_file_dev_null() { fn test_install_target_file_dev_null() {
let scene = TestScenario::new(util_name!()); let (at, mut ucmd) = at_and_ucmd!();
let at = &scene.fixtures;
let file1 = "/dev/null"; let file1 = "/dev/null";
let file2 = "target_file"; let file2 = "target_file";
let result = scene.ucmd().arg(file1).arg(file2).run(); ucmd.arg(file1).arg(file2).succeeds();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
assert!(result.success);
assert!(at.file_exists(file2)); assert!(at.file_exists(file2));
} }
@ -566,3 +549,97 @@ fn test_install_copy_then_compare_file_with_extra_mode() {
assert!(after_install_sticky != after_install_sticky_again); assert!(after_install_sticky != after_install_sticky_again);
} }
const STRIP_TARGET_FILE: &str = "helloworld_installed";
const SYMBOL_DUMP_PROGRAM: &str = "objdump";
const STRIP_SOURCE_FILE_SYMBOL: &str = "main";
fn strip_source_file() -> &'static str {
if cfg!(target_os = "macos") {
"helloworld_macos"
} else {
"helloworld_linux"
}
}
#[test]
#[cfg(not(windows))]
fn test_install_and_strip() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
scene
.ucmd()
.arg("-s")
.arg(strip_source_file())
.arg(STRIP_TARGET_FILE)
.succeeds()
.no_stderr();
let output = Command::new(SYMBOL_DUMP_PROGRAM)
.arg("-t")
.arg(at.plus(STRIP_TARGET_FILE))
.output()
.unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.contains(STRIP_SOURCE_FILE_SYMBOL));
}
#[test]
#[cfg(not(windows))]
fn test_install_and_strip_with_program() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
scene
.ucmd()
.arg("-s")
.arg("--strip-program")
.arg("/usr/bin/strip")
.arg(strip_source_file())
.arg(STRIP_TARGET_FILE)
.succeeds()
.no_stderr();
let output = Command::new(SYMBOL_DUMP_PROGRAM)
.arg("-t")
.arg(at.plus(STRIP_TARGET_FILE))
.output()
.unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.contains(STRIP_SOURCE_FILE_SYMBOL));
}
#[test]
#[cfg(not(windows))]
fn test_install_and_strip_with_invalid_program() {
let scene = TestScenario::new(util_name!());
let stderr = scene
.ucmd()
.arg("-s")
.arg("--strip-program")
.arg("/bin/date")
.arg(strip_source_file())
.arg(STRIP_TARGET_FILE)
.fails()
.stderr;
assert!(stderr.contains("strip program failed"));
}
#[test]
#[cfg(not(windows))]
fn test_install_and_strip_with_non_existent_program() {
let scene = TestScenario::new(util_name!());
let stderr = scene
.ucmd()
.arg("-s")
.arg("--strip-program")
.arg("/usr/bin/non_existent_program")
.arg(strip_source_file())
.arg(STRIP_TARGET_FILE)
.fails()
.stderr;
assert!(stderr.contains("No such file or directory"));
}

View file

@ -520,10 +520,7 @@ fn test_symlink_no_deref_dir() {
scene.ucmd().args(&["-sn", dir1, link]).fails(); scene.ucmd().args(&["-sn", dir1, link]).fails();
// Try with the no-deref // Try with the no-deref
let result = scene.ucmd().args(&["-sfn", dir1, link]).run(); scene.ucmd().args(&["-sfn", dir1, link]).succeeds();
println!("stdout {}", result.stdout);
println!("stderr {}", result.stderr);
assert!(result.success);
assert!(at.dir_exists(dir1)); assert!(at.dir_exists(dir1));
assert!(at.dir_exists(dir2)); assert!(at.dir_exists(dir2));
assert!(at.is_symlink(link)); assert!(at.is_symlink(link));
@ -566,10 +563,7 @@ fn test_symlink_no_deref_file() {
scene.ucmd().args(&["-sn", file1, link]).fails(); scene.ucmd().args(&["-sn", file1, link]).fails();
// Try with the no-deref // Try with the no-deref
let result = scene.ucmd().args(&["-sfn", file1, link]).run(); scene.ucmd().args(&["-sfn", file1, link]).succeeds();
println!("stdout {}", result.stdout);
println!("stderr {}", result.stderr);
assert!(result.success);
assert!(at.file_exists(file1)); assert!(at.file_exists(file1));
assert!(at.file_exists(file2)); assert!(at.file_exists(file2));
assert!(at.is_symlink(link)); assert!(at.is_symlink(link));

View file

@ -3,23 +3,19 @@ use std::env;
#[test] #[test]
fn test_normal() { fn test_normal() {
let (_, mut ucmd) = at_and_ucmd!(); let result = new_ucmd!().run();
let result = ucmd.run();
println!("result.stdout = {}", result.stdout);
println!("result.stderr = {}", result.stderr);
println!("env::var(CI).is_ok() = {}", env::var("CI").is_ok()); println!("env::var(CI).is_ok() = {}", env::var("CI").is_ok());
for (key, value) in env::vars() { for (key, value) in env::vars() {
println!("{}: {}", key, value); println!("{}: {}", key, value);
} }
if (is_ci() || uucore::os::is_wsl_1()) && result.stderr.contains("error: no login name") { if (is_ci() || uucore::os::is_wsl_1()) && result.stderr_str().contains("error: no login name") {
// ToDO: investigate WSL failure // ToDO: investigate WSL failure
// In the CI, some server are failing to return logname. // In the CI, some server are failing to return logname.
// 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.trim().is_empty()); assert!(!result.stdout_str().trim().is_empty());
} }

File diff suppressed because it is too large Load diff

View file

@ -70,10 +70,10 @@ fn convert_path<'a>(path: &'a str) -> Cow<'a, str> {
#[test] #[test]
fn test_relpath_with_from_no_d() { fn test_relpath_with_from_no_d() {
for test in TESTS.iter() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
for test in TESTS.iter() {
let from: &str = &convert_path(test.from); let from: &str = &convert_path(test.from);
let to: &str = &convert_path(test.to); let to: &str = &convert_path(test.to);
let expected: &str = &convert_path(test.expected); let expected: &str = &convert_path(test.expected);
@ -92,10 +92,10 @@ fn test_relpath_with_from_no_d() {
#[test] #[test]
fn test_relpath_with_from_with_d() { fn test_relpath_with_from_with_d() {
for test in TESTS.iter() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
for test in TESTS.iter() {
let from: &str = &convert_path(test.from); let from: &str = &convert_path(test.from);
let to: &str = &convert_path(test.to); let to: &str = &convert_path(test.to);
let pwd = at.as_string(); let pwd = at.as_string();
@ -103,63 +103,75 @@ fn test_relpath_with_from_with_d() {
at.mkdir_all(from); at.mkdir_all(from);
// d is part of subpath -> expect relative path // d is part of subpath -> expect relative path
let mut result = scene let mut result_stdout = scene
.ucmd() .ucmd()
.arg(to) .arg(to)
.arg(from) .arg(from)
.arg(&format!("-d{}", pwd)) .arg(&format!("-d{}", pwd))
.run(); .succeeds()
assert!(result.success); .stdout_move_str();
// relax rules for windows test environment // relax rules for windows test environment
#[cfg(not(windows))] #[cfg(not(windows))]
assert!(Path::new(&result.stdout).is_relative()); assert!(Path::new(&result_stdout).is_relative());
// d is not part of subpath -> expect absolut path // d is not part of subpath -> expect absolut path
result = scene.ucmd().arg(to).arg(from).arg("-dnon_existing").run(); result_stdout = scene
assert!(result.success); .ucmd()
assert!(Path::new(&result.stdout).is_absolute()); .arg(to)
.arg(from)
.arg("-dnon_existing")
.succeeds()
.stdout_move_str();
assert!(Path::new(&result_stdout).is_absolute());
} }
} }
#[test] #[test]
fn test_relpath_no_from_no_d() { fn test_relpath_no_from_no_d() {
for test in TESTS.iter() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
for test in TESTS.iter() {
let to: &str = &convert_path(test.to); let to: &str = &convert_path(test.to);
at.mkdir_all(to); at.mkdir_all(to);
let result = scene.ucmd().arg(to).run(); let result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str();
assert!(result.success);
#[cfg(not(windows))] #[cfg(not(windows))]
assert_eq!(result.stdout, format!("{}\n", to)); assert_eq!(result_stdout, format!("{}\n", to));
// relax rules for windows test environment // relax rules for windows test environment
#[cfg(windows)] #[cfg(windows)]
assert!(result.stdout.ends_with(&format!("{}\n", to))); assert!(result_stdout.ends_with(&format!("{}\n", to)));
} }
} }
#[test] #[test]
fn test_relpath_no_from_with_d() { fn test_relpath_no_from_with_d() {
for test in TESTS.iter() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; let at = &scene.fixtures;
for test in TESTS.iter() {
let to: &str = &convert_path(test.to); let to: &str = &convert_path(test.to);
let pwd = at.as_string(); let pwd = at.as_string();
at.mkdir_all(to); at.mkdir_all(to);
// d is part of subpath -> expect relative path // d is part of subpath -> expect relative path
let mut result = scene.ucmd().arg(to).arg(&format!("-d{}", pwd)).run(); let mut result_stdout = scene
assert!(result.success); .ucmd()
.arg(to)
.arg(&format!("-d{}", pwd))
.succeeds()
.stdout_move_str();
// relax rules for windows test environment // relax rules for windows test environment
#[cfg(not(windows))] #[cfg(not(windows))]
assert!(Path::new(&result.stdout).is_relative()); assert!(Path::new(&result_stdout).is_relative());
// d is not part of subpath -> expect absolut path // d is not part of subpath -> expect absolut path
result = scene.ucmd().arg(to).arg("-dnon_existing").run(); result_stdout = scene
assert!(result.success); .ucmd()
assert!(Path::new(&result.stdout).is_absolute()); .arg(to)
.arg("-dnon_existing")
.succeeds()
.stdout_move_str();
assert!(Path::new(&result_stdout).is_absolute());
} }
} }

View file

@ -1 +1,51 @@
// ToDO: add tests use crate::common::util::*;
#[test]
fn test_shred_remove() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file_a = "test_shred_remove_a";
let file_b = "test_shred_remove_b";
// Create file_a and file_b.
at.touch(file_a);
at.touch(file_b);
// Shred file_a.
scene.ucmd().arg("-u").arg(file_a).run();
// file_a was deleted, file_b exists.
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
}
#[cfg(not(target_os = "freebsd"))]
#[test]
fn test_shred_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let file = "test_shred_force";
// Create file_a.
at.touch(file);
assert!(at.file_exists(file));
// Make file_a readonly.
at.set_readonly(file);
// Try shred -u.
let result = scene.ucmd().arg("-u").arg(file).run();
println!("stderr = {:?}", result.stderr);
println!("stdout = {:?}", result.stdout);
// file_a was not deleted because it is readonly.
assert!(at.file_exists(file));
// Try shred -u -f.
scene.ucmd().arg("-u").arg("-f").arg(file).run();
// file_a was deleted.
assert!(!at.file_exists(file));
}

View file

@ -1,44 +1,142 @@
use crate::common::util::*; use crate::common::util::*;
fn test_helper(file_name: &str, args: &str) {
new_ucmd!()
.arg(args)
.arg(format!("{}.txt", file_name))
.succeeds()
.stdout_is_fixture(format!("{}.expected", file_name));
}
#[test]
fn test_months_whitespace() {
test_helper("months-whitespace", "-M");
}
#[test]
fn test_version_empty_lines() {
new_ucmd!()
.arg("-V")
.arg("version-empty-lines.txt")
.succeeds()
.stdout_is("\n\n\n\n\n\n\n1.2.3-alpha\n1.2.3-alpha2\n\t\t\t1.12.4\n11.2.3\n");
}
#[test]
fn test_human_numeric_whitespace() {
test_helper("human-numeric-whitespace", "-h");
}
#[test]
fn test_multiple_decimals_general() {
new_ucmd!()
.arg("-g")
.arg("multiple_decimals_general.txt")
.succeeds()
.stdout_is("\n\n\n\n\n\n\n\nCARAvan\n-2028789030\n-896689\n-8.90880\n-1\n-.05\n000\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n576,446.88800000\n576,446.890\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n4798908.340000000000\n4798908.45\n4798908.8909800\n");
}
#[test]
fn test_multiple_decimals_numeric() {
new_ucmd!()
.arg("-n")
.arg("multiple_decimals_numeric.txt")
.succeeds()
.stdout_is("-2028789030\n-896689\n-8.90880\n-1\n-.05\n\n\n\n\n\n\n\n\n000\nCARAvan\n00000001\n1\n1.040000000\n1.444\n1.58590\n8.013\n45\n46.89\n 4567.\n4567.1\n4567.34\n\t\t\t\t\t\t\t\t\t\t4567..457\n\t\t\t\t37800\n\t\t\t\t\t\t45670.89079.098\n\t\t\t\t\t\t45670.89079.1\n576,446.88800000\n576,446.890\n4798908.340000000000\n4798908.45\n4798908.8909800\n");
}
#[test]
fn test_check_zero_terminated_failure() {
new_ucmd!()
.arg("-z")
.arg("-c")
.arg("zero-terminated.txt")
.fails()
.stdout_is("sort: disorder in line 0\n");
}
#[test]
fn test_check_zero_terminated_success() {
new_ucmd!()
.arg("-z")
.arg("-c")
.arg("zero-terminated.expected")
.succeeds();
}
#[test]
fn test_random_shuffle_len() {
// check whether output is the same length as the input
const FILE: &'static str = "default_unsorted_ints.expected";
let (at, _ucmd) = at_and_ucmd!();
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
let expected = at.read(FILE);
assert_ne!(result, expected);
assert_eq!(result.len(), expected.len());
}
#[test]
fn test_random_shuffle_contains_all_lines() {
// check whether lines of input are all in output
const FILE: &'static str = "default_unsorted_ints.expected";
let (at, _ucmd) = at_and_ucmd!();
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
let expected = at.read(FILE);
let result_sorted = new_ucmd!().pipe_in(result.clone()).run().stdout;
assert_ne!(result, expected);
assert_eq!(result_sorted, expected);
}
#[test]
fn test_random_shuffle_two_runs_not_the_same() {
// check to verify that two random shuffles are not equal; this has the
// potential to fail in the very unlikely event that the random order is the same
// as the starting order, or if both random sorts end up having the same order.
const FILE: &'static str = "default_unsorted_ints.expected";
let (at, _ucmd) = at_and_ucmd!();
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
let expected = at.read(FILE);
let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout;
assert_ne!(result, expected);
assert_ne!(result, unexpected);
}
#[test]
fn test_random_shuffle_contains_two_runs_not_the_same() {
// check to verify that two random shuffles are not equal; this has the
// potential to fail in the unlikely event that random order is the same
// as the starting order, or if both random sorts end up having the same order.
const FILE: &'static str = "default_unsorted_ints.expected";
let (at, _ucmd) = at_and_ucmd!();
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
let expected = at.read(FILE);
let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout;
assert_ne!(result, expected);
assert_ne!(result, unexpected);
}
#[test] #[test]
fn test_numeric_floats_and_ints() { fn test_numeric_floats_and_ints() {
for numeric_sort_param in vec!["-n", "--numeric-sort"] { test_helper("numeric_floats_and_ints", "-n");
let input = "1.444\n8.013\n1\n-8\n1.04\n-1";
new_ucmd!()
.arg(numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8\n-1\n1\n1.04\n1.444\n8.013\n");
}
} }
#[test] #[test]
fn test_numeric_floats() { fn test_numeric_floats() {
for numeric_sort_param in vec!["-n", "--numeric-sort"] { test_helper("numeric_floats", "-n");
let input = "1.444\n8.013\n1.58590\n-8.90880\n1.040000000\n-.05";
new_ucmd!()
.arg(numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8.90880\n-.05\n1.040000000\n1.444\n1.58590\n8.013\n");
}
} }
#[test] #[test]
fn test_numeric_floats_with_nan() { fn test_numeric_floats_with_nan() {
for numeric_sort_param in vec!["-n", "--numeric-sort"] { test_helper("numeric_floats_with_nan", "-n");
let input = "1.444\n1.0/0.0\n1.58590\n-8.90880\n1.040000000\n-.05";
new_ucmd!()
.arg(numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8.90880\n-.05\n1.0/0.0\n1.040000000\n1.444\n1.58590\n");
}
} }
#[test] #[test]
fn test_numeric_unfixed_floats() { fn test_numeric_unfixed_floats() {
test_helper("numeric_fixed_floats", "-n"); test_helper("numeric_unfixed_floats", "-n");
} }
#[test] #[test]
@ -53,26 +151,12 @@ fn test_numeric_unsorted_ints() {
#[test] #[test]
fn test_human_block_sizes() { fn test_human_block_sizes() {
for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] { test_helper("human_block_sizes", "-h");
let input = "8981K\n909991M\n-8T\n21G\n0.8M";
new_ucmd!()
.arg(human_numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n");
}
} }
#[test] #[test]
fn test_month_default() { fn test_month_default() {
for month_sort_param in vec!["-M", "--month-sort"] { test_helper("month_default", "-M");
let input = "JAn\nMAY\n000may\nJun\nFeb";
new_ucmd!()
.arg(month_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("000may\nJAn\nFeb\nMAY\nJun\n");
}
} }
#[test] #[test]
@ -82,23 +166,12 @@ fn test_month_stable() {
#[test] #[test]
fn test_default_unsorted_ints() { fn test_default_unsorted_ints() {
let input = "9\n1909888\n000\n1\n2"; test_helper("default_unsorted_ints", "");
new_ucmd!()
.pipe_in(input)
.succeeds()
.stdout_only("000\n1\n1909888\n2\n9\n");
} }
#[test] #[test]
fn test_numeric_unique_ints() { fn test_numeric_unique_ints() {
for numeric_unique_sort_param in vec!["-nu"] { test_helper("numeric_unsorted_ints_unique", "-nu");
let input = "9\n9\n8\n1\n";
new_ucmd!()
.arg(numeric_unique_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("1\n8\n9\n");
}
} }
#[test] #[test]
@ -116,6 +189,318 @@ fn test_dictionary_order() {
test_helper("dictionary_order", "-d"); test_helper("dictionary_order", "-d");
} }
#[test]
fn test_dictionary_order2() {
for non_dictionary_order2_param in vec!["-d"] {
new_ucmd!()
.pipe_in("a👦🏻aa b\naaaa b")
.arg(non_dictionary_order2_param)
.succeeds()
.stdout_only("a👦🏻aa b\naaaa b\n");
}
}
#[test]
fn test_non_printing_chars() {
for non_printing_chars_param in vec!["-i"] {
new_ucmd!()
.pipe_in("a👦🏻aa\naaaa")
.arg(non_printing_chars_param)
.succeeds()
.stdout_only("a👦🏻aa\naaaa\n");
}
}
#[test]
fn test_exponents_positive_general_fixed() {
for exponents_positive_general_param in vec!["-g"] {
new_ucmd!()
.pipe_in("100E6\n\n50e10\n+100000\n\n10000K78\n10E\n\n\n1000EDKLD\n\n\n100E6\n\n50e10\n+100000\n\n")
.arg(exponents_positive_general_param)
.succeeds()
.stdout_only("\n\n\n\n\n\n\n\n10000K78\n1000EDKLD\n10E\n+100000\n+100000\n100E6\n100E6\n50e10\n50e10\n");
}
}
#[test]
fn test_exponents_positive_numeric() {
test_helper("exponents-positive-numeric", "-n");
}
#[test]
fn test_months_dedup() {
test_helper("months-dedup", "-Mu");
}
#[test]
fn test_mixed_floats_ints_chars_numeric() {
test_helper("mixed_floats_ints_chars_numeric", "-n");
}
#[test]
fn test_mixed_floats_ints_chars_numeric_unique() {
test_helper("mixed_floats_ints_chars_numeric_unique", "-nu");
}
#[test]
fn test_words_unique() {
test_helper("words_unique", "-u");
}
#[test]
fn test_numeric_unique() {
test_helper("numeric_unique", "-nu");
}
#[test]
fn test_mixed_floats_ints_chars_numeric_reverse() {
test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur");
}
#[test]
fn test_mixed_floats_ints_chars_numeric_stable() {
test_helper("mixed_floats_ints_chars_numeric_stable", "-ns");
}
#[test]
fn test_numeric_floats_and_ints2() {
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
let input = "1.444\n8.013\n1\n-8\n1.04\n-1";
new_ucmd!()
.arg(numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8\n-1\n1\n1.04\n1.444\n8.013\n");
}
}
#[test]
fn test_numeric_floats2() {
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
let input = "1.444\n8.013\n1.58590\n-8.90880\n1.040000000\n-.05";
new_ucmd!()
.arg(numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8.90880\n-.05\n1.040000000\n1.444\n1.58590\n8.013\n");
}
}
#[test]
fn test_numeric_floats_with_nan2() {
test_helper("numeric-floats-with-nan2", "-n");
}
#[test]
fn test_human_block_sizes2() {
for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] {
let input = "8981K\n909991M\n-8T\n21G\n0.8M";
new_ucmd!()
.arg(human_numeric_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n");
}
}
#[test]
fn test_month_default2() {
for month_sort_param in vec!["-M", "--month-sort"] {
let input = "JAn\nMAY\n000may\nJun\nFeb";
new_ucmd!()
.arg(month_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("000may\nJAn\nFeb\nMAY\nJun\n");
}
}
#[test]
fn test_default_unsorted_ints2() {
let input = "9\n1909888\n000\n1\n2";
new_ucmd!()
.pipe_in(input)
.succeeds()
.stdout_only("000\n1\n1909888\n2\n9\n");
}
#[test]
fn test_numeric_unique_ints2() {
for numeric_unique_sort_param in vec!["-nu"] {
let input = "9\n9\n8\n1\n";
new_ucmd!()
.arg(numeric_unique_sort_param)
.pipe_in(input)
.succeeds()
.stdout_only("1\n8\n9\n");
}
}
#[test]
fn test_keys_open_ended() {
let input = "aa bb cc\ndd aa ff\ngg aa cc\n";
new_ucmd!()
.args(&["-k", "2.2"])
.pipe_in(input)
.succeeds()
.stdout_only("gg aa cc\ndd aa ff\naa bb cc\n");
}
#[test]
fn test_keys_closed_range() {
let input = "aa bb cc\ndd aa ff\ngg aa cc\n";
new_ucmd!()
.args(&["-k", "2.2,2.2"])
.pipe_in(input)
.succeeds()
.stdout_only("dd aa ff\ngg aa cc\naa bb cc\n");
}
#[test]
fn test_keys_multiple_ranges() {
let input = "aa bb cc\ndd aa ff\ngg aa cc\n";
new_ucmd!()
.args(&["-k", "2,2", "-k", "3,3"])
.pipe_in(input)
.succeeds()
.stdout_only("gg aa cc\ndd aa ff\naa bb cc\n");
}
#[test]
fn test_keys_no_field_match() {
let input = "aa aa aa aa\naa bb cc\ndd aa ff\n";
new_ucmd!()
.args(&["-k", "4,4"])
.pipe_in(input)
.succeeds()
.stdout_only("aa bb cc\ndd aa ff\naa aa aa aa\n");
}
#[test]
fn test_keys_no_char_match() {
let input = "aaa\nba\nc\n";
new_ucmd!()
.args(&["-k", "1.2"])
.pipe_in(input)
.succeeds()
.stdout_only("c\nba\naaa\n");
}
#[test]
fn test_keys_custom_separator() {
let input = "aaxbbxcc\nddxaaxff\nggxaaxcc\n";
new_ucmd!()
.args(&["-k", "2.2,2.2", "-t", "x"])
.pipe_in(input)
.succeeds()
.stdout_only("ddxaaxff\nggxaaxcc\naaxbbxcc\n");
}
#[test]
fn test_keys_invalid_field() {
new_ucmd!()
.args(&["-k", "1."])
.fails()
.stderr_only("sort: error: failed to parse character index for key `1.`: cannot parse integer from empty string");
}
#[test]
fn test_keys_invalid_field_option() {
new_ucmd!()
.args(&["-k", "1.1x"])
.fails()
.stderr_only("sort: error: invalid option for key: `x`");
}
#[test]
fn test_keys_invalid_field_zero() {
new_ucmd!()
.args(&["-k", "0.1"])
.fails()
.stderr_only("sort: error: field index was 0");
}
#[test]
fn test_keys_with_options() {
let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n";
for param in &[
&["-k", "2,2n"][..],
&["-k", "2n,2"][..],
&["-k", "2,2", "-n"][..],
] {
new_ucmd!()
.args(param)
.pipe_in(input)
.succeeds()
.stdout_only("dd 1 ff\ngg 2 cc\naa 3 cc\n");
}
}
#[test]
fn test_keys_with_options_blanks_start() {
let input = "aa 3 cc\ndd 1 ff\ngg 2 cc\n";
for param in &[&["-k", "2b,2"][..], &["-k", "2,2", "-b"][..]] {
new_ucmd!()
.args(param)
.pipe_in(input)
.succeeds()
.stdout_only("dd 1 ff\ngg 2 cc\naa 3 cc\n");
}
}
#[test]
fn test_keys_with_options_blanks_end() {
let input = "a b
a b
a b
";
new_ucmd!()
.args(&["-k", "1,2.1b", "-s"])
.pipe_in(input)
.succeeds()
.stdout_only(
"a b
a b
a b
",
);
}
#[test]
fn test_keys_stable() {
let input = "a b
a b
a b
";
new_ucmd!()
.args(&["-k", "1,2.1", "-s"])
.pipe_in(input)
.succeeds()
.stdout_only(
"a b
a b
a b
",
);
}
#[test]
fn test_keys_empty_match() {
let input = "a a a a
aaaa
";
new_ucmd!()
.args(&["-k", "1,1", "-t", "a"])
.pipe_in(input)
.succeeds()
.stdout_only(input);
}
#[test]
fn test_zero_terminated() {
test_helper("zero-terminated", "-z");
}
#[test] #[test]
fn test_multiple_files() { fn test_multiple_files() {
new_ucmd!() new_ucmd!()
@ -192,10 +577,11 @@ fn test_check() {
.stdout_is(""); .stdout_is("");
} }
fn test_helper(file_name: &str, args: &str) { #[test]
fn test_check_silent() {
new_ucmd!() new_ucmd!()
.arg(args) .arg("-C")
.arg(format!("{}{}", file_name, ".txt")) .arg("check_fail.txt")
.succeeds() .fails()
.stdout_is_fixture(format!("{}{}", file_name, ".expected")); .stdout_is("");
} }

View file

@ -1,13 +1,65 @@
#[cfg(not(target_os = "windows"))]
use crate::common::util::*; use crate::common::util::*;
#[cfg(not(target_os = "windows"))]
#[test] #[test]
fn test_stdbuf_unbuffered_stdout() { fn test_stdbuf_unbuffered_stdout() {
if cfg!(target_os = "linux") {
// This is a basic smoke test // This is a basic smoke test
new_ucmd!() new_ucmd!()
.args(&["-o0", "head"]) .args(&["-o0", "head"])
.pipe_in("The quick brown fox jumps over the lazy dog.") .pipe_in("The quick brown fox jumps over the lazy dog.")
.run() .run()
.stdout_is("The quick brown fox jumps over the lazy dog."); .stdout_is("The quick brown fox jumps over the lazy dog.");
} }
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_line_buffered_stdout() {
new_ucmd!()
.args(&["-oL", "head"])
.pipe_in("The quick brown fox jumps over the lazy dog.")
.run()
.stdout_is("The quick brown fox jumps over the lazy dog.");
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_no_buffer_option_fails() {
new_ucmd!().args(&["head"]).fails().stderr_is(
"error: The following required arguments were not provided:\n \
--error <MODE>\n \
--input <MODE>\n \
--output <MODE>\n\n\
USAGE:\n \
stdbuf OPTION... COMMAND\n\n\
For more information try --help",
);
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_trailing_var_arg() {
new_ucmd!()
.args(&["-i", "1024", "tail", "-1"])
.pipe_in("The quick brown fox\njumps over the lazy dog.")
.run()
.stdout_is("jumps over the lazy dog.");
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_line_buffering_stdin_fails() {
new_ucmd!()
.args(&["-i", "L", "head"])
.fails()
.stderr_is("stdbuf: error: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information.");
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_invalid_mode_fails() {
new_ucmd!()
.args(&["-i", "1024R", "head"])
.fails()
.stderr_is("stdbuf: error: invalid mode 1024R\nTry 'stdbuf --help' for more information.");
} }

View file

@ -29,6 +29,7 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) {
fn str_to_filetime(format: &str, s: &str) -> FileTime { fn str_to_filetime(format: &str, s: &str) -> FileTime {
let mut tm = time::strptime(s, format).unwrap(); let mut tm = time::strptime(s, format).unwrap();
tm.tm_utcoff = time::now().tm_utcoff; tm.tm_utcoff = time::now().tm_utcoff;
tm.tm_isdst = -1; // Unknown flag DST
let ts = tm.to_timespec(); let ts = tm.to_timespec();
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
} }
@ -352,3 +353,21 @@ fn test_touch_set_date() {
assert_eq!(atime, start_of_year); assert_eq!(atime, start_of_year);
assert_eq!(mtime, start_of_year); assert_eq!(mtime, start_of_year);
} }
#[test]
fn test_touch_mtime_dst_succeeds() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "test_touch_set_mtime_dst_succeeds";
ucmd.args(&["-m", "-t", "202103140300", file])
.succeeds()
.no_stderr();
assert!(at.file_exists(file));
let target_time = str_to_filetime("%Y%m%d%H%M", "202103140300");
let (_, mtime) = get_file_times(&at, file);
eprintln!("target_time: {:?}", target_time);
eprintln!("mtime: {:?}", mtime);
assert!(target_time == mtime);
}

View file

@ -1,5 +1,33 @@
use crate::common::util::*; use crate::common::util::*;
#[test]
fn test_count_bytes_large_stdin() {
for &n in &[
0,
1,
42,
16 * 1024 - 7,
16 * 1024 - 1,
16 * 1024,
16 * 1024 + 1,
16 * 1024 + 3,
32 * 1024,
64 * 1024,
80 * 1024,
96 * 1024,
112 * 1024,
128 * 1024,
] {
let data = vec_of_size(n);
let expected = format!("{}\n", n);
new_ucmd!()
.args(&["-c"])
.pipe_in(data)
.succeeds()
.stdout_is_bytes(&expected.as_bytes());
}
}
#[test] #[test]
fn test_stdin_default() { fn test_stdin_default() {
new_ucmd!() new_ucmd!()

View file

@ -1,40 +1,3 @@
/// Assertion helper macro for [`CmdResult`] types
///
/// [`CmdResult`]: crate::tests::common::util::CmdResult
#[macro_export]
macro_rules! assert_empty_stderr(
($cond:expr) => (
if $cond.stderr.len() > 0 {
panic!("stderr: {}", $cond.stderr_str())
}
);
);
/// Assertion helper macro for [`CmdResult`] types
///
/// [`CmdResult`]: crate::tests::common::util::CmdResult
#[macro_export]
macro_rules! assert_empty_stdout(
($cond:expr) => (
if $cond.stdout.len() > 0 {
panic!("stdout: {}", $cond.stdout_str())
}
);
);
/// Assertion helper macro for [`CmdResult`] types
///
/// [`CmdResult`]: crate::tests::common::util::CmdResult
#[macro_export]
macro_rules! assert_no_error(
($cond:expr) => (
assert!($cond.success);
if $cond.stderr.len() > 0 {
panic!("stderr: {}", $cond.stderr_str())
}
);
);
/// Platform-independent helper for constructing a PathBuf from individual elements /// Platform-independent helper for constructing a PathBuf from individual elements
#[macro_export] #[macro_export]
macro_rules! path_concat { macro_rules! path_concat {

View file

@ -33,6 +33,8 @@ static ALREADY_RUN: &str = " you have already run this UCommand, if you want to
testing();"; testing();";
static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly."; static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical use case of: provide args and input stream -> spawn process -> block until completion -> return output streams. For verifying that a particular section of the input stream is what causes a particular behavior, use the Command type directly.";
static NO_STDIN_MEANINGLESS: &str = "Setting this flag has no effect if there is no stdin";
/// Test if the program is running under CI /// Test if the program is running under CI
pub fn is_ci() -> bool { pub fn is_ci() -> bool {
std::env::var("CI") std::env::var("CI")
@ -48,7 +50,7 @@ fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_p
/// A command result is the outputs of a command (streams and status code) /// A command result is the outputs of a command (streams and status code)
/// within a struct which has convenience assertion functions about those outputs /// within a struct which has convenience assertion functions about those outputs
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct CmdResult { pub struct CmdResult {
//tmpd is used for convenience functions for asserts against fixtures //tmpd is used for convenience functions for asserts against fixtures
tmpd: Option<Rc<TempDir>>, tmpd: Option<Rc<TempDir>>,
@ -114,6 +116,11 @@ impl CmdResult {
self.code.expect("Program must be run first") self.code.expect("Program must be run first")
} }
pub fn code_is(&self, expected_code: i32) -> &CmdResult {
assert_eq!(self.code(), expected_code);
self
}
/// Returns the program's TempDir /// Returns the program's TempDir
/// Panics if not present /// Panics if not present
pub fn tmpd(&self) -> Rc<TempDir> { pub fn tmpd(&self) -> Rc<TempDir> {
@ -130,13 +137,25 @@ impl CmdResult {
/// asserts that the command resulted in a success (zero) status code /// asserts that the command resulted in a success (zero) status code
pub fn success(&self) -> &CmdResult { pub fn success(&self) -> &CmdResult {
assert!(self.success); if !self.success {
panic!(
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
}
self self
} }
/// asserts that the command resulted in a failure (non-zero) status code /// asserts that the command resulted in a failure (non-zero) status code
pub fn failure(&self) -> &CmdResult { pub fn failure(&self) -> &CmdResult {
assert!(!self.success); if self.success {
panic!(
"Command was expected to fail.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
}
self self
} }
@ -149,10 +168,15 @@ impl CmdResult {
/// asserts that the command resulted in empty (zero-length) stderr stream output /// asserts that the command resulted in empty (zero-length) stderr stream output
/// generally, it's better to use stdout_only() instead, /// generally, it's better to use stdout_only() instead,
/// but you might find yourself using this function if /// but you might find yourself using this function if
/// 1. you can not know exactly what stdout will be /// 1. you can not know exactly what stdout will be or
/// or 2. you know that stdout will also be empty /// 2. you know that stdout will also be empty
pub fn no_stderr(&self) -> &CmdResult { pub fn no_stderr(&self) -> &CmdResult {
assert!(self.stderr.is_empty()); if !self.stderr.is_empty() {
panic!(
"Expected stderr to be empty, but it's:\n{}",
self.stderr_str()
);
}
self self
} }
@ -160,10 +184,15 @@ impl CmdResult {
/// unless asserting there was neither stdout or stderr, stderr_only is usually a better choice /// unless asserting there was neither stdout or stderr, stderr_only is usually a better choice
/// generally, it's better to use stderr_only() instead, /// generally, it's better to use stderr_only() instead,
/// but you might find yourself using this function if /// but you might find yourself using this function if
/// 1. you can not know exactly what stderr will be /// 1. you can not know exactly what stderr will be or
/// or 2. you know that stderr will also be empty /// 2. you know that stderr will also be empty
pub fn no_stdout(&self) -> &CmdResult { pub fn no_stdout(&self) -> &CmdResult {
assert!(self.stdout.is_empty()); if !self.stdout.is_empty() {
panic!(
"Expected stdout to be empty, but it's:\n{}",
self.stderr_str()
);
}
self self
} }
@ -206,10 +235,16 @@ impl CmdResult {
self self
} }
/// Like stdout_is_fixture, but for stderr
pub fn stderr_is_fixture<T: AsRef<OsStr>>(&self, file_rel_path: T) -> &CmdResult {
let contents = read_scenario_fixture(&self.tmpd, file_rel_path);
self.stderr_is_bytes(contents)
}
/// asserts that /// asserts that
/// 1. the command resulted in stdout stream output that equals the /// 1. the command resulted in stdout stream output that equals the
/// passed in value, when both are trimmed of trailing whitespace /// passed in value
/// and 2. the command resulted in empty (zero-length) stderr stream output /// 2. the command resulted in empty (zero-length) stderr stream output
pub fn stdout_only<T: AsRef<str>>(&self, msg: T) -> &CmdResult { pub fn stdout_only<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
self.no_stderr().stdout_is(msg) self.no_stderr().stdout_is(msg)
} }
@ -231,7 +266,7 @@ impl CmdResult {
/// asserts that /// asserts that
/// 1. the command resulted in stderr stream output that equals the /// 1. the command resulted in stderr stream output that equals the
/// passed in value, when both are trimmed of trailing whitespace /// passed in value, when both are trimmed of trailing whitespace
/// and 2. the command resulted in empty (zero-length) stdout stream output /// 2. the command resulted in empty (zero-length) stdout stream output
pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> &CmdResult { pub fn stderr_only<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
self.no_stdout().stderr_is(msg) self.no_stdout().stderr_is(msg)
} }
@ -255,10 +290,34 @@ impl CmdResult {
self self
} }
pub fn stderr_contains<T: AsRef<str>>(&self, cmp: &T) -> &CmdResult { pub fn stderr_contains<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
assert!(self.stderr_str().contains(cmp.as_ref())); assert!(self.stderr_str().contains(cmp.as_ref()));
self self
} }
pub fn stdout_does_not_contain<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
assert!(!self.stdout_str().contains(cmp.as_ref()));
self
}
pub fn stderr_does_not_contain<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
assert!(!self.stderr_str().contains(cmp.as_ref()));
self
}
pub fn stdout_matches(&self, regex: &regex::Regex) -> &CmdResult {
if !regex.is_match(self.stdout_str()) {
panic!("Stdout does not match regex:\n{}", self.stdout_str())
}
self
}
pub fn stdout_does_not_match(&self, regex: &regex::Regex) -> &CmdResult {
if regex.is_match(self.stdout_str()) {
panic!("Stdout matches regex:\n{}", self.stdout_str())
}
self
}
} }
pub fn log_info<T: AsRef<str>, U: AsRef<str>>(msg: T, par: U) { pub fn log_info<T: AsRef<str>, U: AsRef<str>>(msg: T, par: U) {
@ -335,6 +394,13 @@ impl AtPath {
String::from(self.minus(name).to_str().unwrap()) String::from(self.minus(name).to_str().unwrap())
} }
pub fn set_readonly(&self, name: &str) {
let metadata = fs::metadata(self.plus(name)).unwrap();
let mut permissions = metadata.permissions();
permissions.set_readonly(true);
fs::set_permissions(self.plus(name), permissions).unwrap();
}
pub fn open(&self, name: &str) -> File { pub fn open(&self, name: &str) -> File {
log_info("open", self.plus_as_string(name)); log_info("open", self.plus_as_string(name));
File::open(self.plus(name)).unwrap() File::open(self.plus(name)).unwrap()
@ -608,6 +674,7 @@ pub struct UCommand {
tmpd: Option<Rc<TempDir>>, tmpd: Option<Rc<TempDir>>,
has_run: bool, has_run: bool,
stdin: Option<Vec<u8>>, stdin: Option<Vec<u8>>,
ignore_stdin_write_error: bool,
} }
impl UCommand { impl UCommand {
@ -637,6 +704,7 @@ impl UCommand {
}, },
comm_string: String::from(arg.as_ref().to_str().unwrap()), comm_string: String::from(arg.as_ref().to_str().unwrap()),
stdin: None, stdin: None,
ignore_stdin_write_error: false,
} }
} }
@ -689,6 +757,17 @@ impl UCommand {
self.pipe_in(contents) self.pipe_in(contents)
} }
/// Ignores error caused by feeding stdin to the command.
/// This is typically useful to test non-standard workflows
/// like feeding something to a command that does not read it
pub fn ignore_stdin_write_error(&mut self) -> &mut UCommand {
if self.stdin.is_none() {
panic!("{}", NO_STDIN_MEANINGLESS);
}
self.ignore_stdin_write_error = true;
self
}
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut UCommand pub fn env<K, V>(&mut self, key: K, val: V) -> &mut UCommand
where where
K: AsRef<OsStr>, K: AsRef<OsStr>,
@ -709,7 +788,7 @@ impl UCommand {
} }
self.has_run = true; self.has_run = true;
log_info("run", &self.comm_string); log_info("run", &self.comm_string);
let mut result = self let mut child = self
.raw .raw
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
@ -718,15 +797,19 @@ impl UCommand {
.unwrap(); .unwrap();
if let Some(ref input) = self.stdin { if let Some(ref input) = self.stdin {
result let write_result = child
.stdin .stdin
.take() .take()
.unwrap_or_else(|| panic!("Could not take child process stdin")) .unwrap_or_else(|| panic!("Could not take child process stdin"))
.write_all(input) .write_all(input);
.unwrap_or_else(|e| panic!("{}", e)); if !self.ignore_stdin_write_error {
if let Err(e) = write_result {
panic!("failed to write to stdin of child: {}", e)
}
}
} }
result child
} }
/// Spawns the command, feeds the stdin if any, waits for the result /// Spawns the command, feeds the stdin if any, waits for the result
@ -781,3 +864,220 @@ pub fn read_size(child: &mut Child, size: usize) -> String {
.unwrap(); .unwrap();
String::from_utf8(output).unwrap() String::from_utf8(output).unwrap()
} }
pub fn vec_of_size(n: usize) -> Vec<u8> {
let mut result = Vec::new();
for _ in 0..n {
result.push('a' as u8);
}
assert_eq!(result.len(), n);
result
}
/// Sanity checks for test utils
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_code_is() {
let res = CmdResult {
tmpd: None,
code: Some(32),
success: false,
stdout: "".into(),
stderr: "".into(),
};
res.code_is(32);
}
#[test]
#[should_panic]
fn test_code_is_fail() {
let res = CmdResult {
tmpd: None,
code: Some(32),
success: false,
stdout: "".into(),
stderr: "".into(),
};
res.code_is(1);
}
#[test]
fn test_failure() {
let res = CmdResult {
tmpd: None,
code: None,
success: false,
stdout: "".into(),
stderr: "".into(),
};
res.failure();
}
#[test]
#[should_panic]
fn test_failure_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "".into(),
};
res.failure();
}
#[test]
fn test_success() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "".into(),
};
res.success();
}
#[test]
#[should_panic]
fn test_success_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: false,
stdout: "".into(),
stderr: "".into(),
};
res.success();
}
#[test]
fn test_no_std_errout() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "".into(),
};
res.no_stderr();
res.no_stdout();
}
#[test]
#[should_panic]
fn test_no_stderr_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "asdfsadfa".into(),
};
res.no_stderr();
}
#[test]
#[should_panic]
fn test_no_stdout_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "asdfsadfa".into(),
stderr: "".into(),
};
res.no_stdout();
}
#[test]
fn test_std_does_not_contain() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "This is a likely error message\n".into(),
stderr: "This is a likely error message\n".into(),
};
res.stdout_does_not_contain("unlikely");
res.stderr_does_not_contain("unlikely");
}
#[test]
#[should_panic]
fn test_stdout_does_not_contain_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "This is a likely error message\n".into(),
stderr: "".into(),
};
res.stdout_does_not_contain("likely");
}
#[test]
#[should_panic]
fn test_stderr_does_not_contain_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "".into(),
stderr: "This is a likely error message\n".into(),
};
res.stderr_does_not_contain("likely");
}
#[test]
fn test_stdout_matches() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "This is a likely error message\n".into(),
stderr: "This is a likely error message\n".into(),
};
let positive = regex::Regex::new(".*likely.*").unwrap();
let negative = regex::Regex::new(".*unlikely.*").unwrap();
res.stdout_matches(&positive);
res.stdout_does_not_match(&negative);
}
#[test]
#[should_panic]
fn test_stdout_matches_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "This is a likely error message\n".into(),
stderr: "This is a likely error message\n".into(),
};
let negative = regex::Regex::new(".*unlikely.*").unwrap();
res.stdout_matches(&negative);
}
#[test]
#[should_panic]
fn test_stdout_not_matches_fail() {
let res = CmdResult {
tmpd: None,
code: None,
success: true,
stdout: "This is a likely error message\n".into(),
stderr: "This is a likely error message\n".into(),
};
let positive = regex::Regex::new(".*likely.*").unwrap();
res.stdout_does_not_match(&positive);
}
}

0
tests/fixtures/cat/empty.txt vendored Normal file
View file

View file

@ -0,0 +1,5 @@
cat: test_directory3/test_directory4: Is a directory
cat: filewhichdoesnotexist.txt: No such file or directory (os error 2)
cat: test_directory3/test_directory5: Is a directory
cat: test_directory3/../test_directory3/test_directory5: Is a directory
cat: test_directory3: Is a directory

View file

@ -0,0 +1 @@
test1 test2 test3 test4 test5 test6

3
tests/fixtures/install/helloworld.rs vendored Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello World!");
}

Some files were not shown because too many files have changed in this diff Show more