Merge branch 'unittests-in-ci' of github.com:tertsdiepraam/coreutils into unittests-in-ci

This commit is contained in:
Terts Diepraam 2022-03-19 10:58:59 +01:00
commit 76edba0e1e
12 changed files with 227 additions and 89 deletions

View file

@ -20,6 +20,13 @@ env:
on: [push, pull_request]
jobs:
cargo-deny:
name: Style/cargo-deny
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: EmbarkStudios/cargo-deny-action@v1
style_deps:
## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard
name: Style/deps

View file

@ -137,6 +137,8 @@ jobs:
echo "::error ::Failed to find summary of test results (missing '${SUITE_LOG_FILE}'); failing early"
exit 1
fi
# Compress logs before upload (fails otherwise)
gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }}
- name: Reserve SHA1/ID of 'test-summary'
uses: actions/upload-artifact@v2
with:

View file

@ -94,6 +94,16 @@ uutils: add new utility
gitignore: add temporary files
```
## cargo-deny
This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to
detect duplicate dependencies, checks licenses, etc. To run it locally, first
install it and then run with:
```
cargo deny --all-features check all
```
## Licensing
uutils is distributed under the terms of the MIT License; see the `LICENSE` file

29
Cargo.lock generated
View file

@ -877,7 +877,7 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37978dab2ca789938a83b2f8bc1ef32db6633af9051a6cd409eff72cbaaa79a"
dependencies = [
"paste 1.0.6",
"paste",
]
[[package]]
@ -1431,31 +1431,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "paste"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
dependencies = [
"paste-impl",
"proc-macro-hack",
]
[[package]]
name = "paste"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5"
[[package]]
name = "paste-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
dependencies = [
"proc-macro-hack",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@ -1574,12 +1555,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.36"
@ -2559,7 +2534,7 @@ dependencies = [
"clap 3.0.10",
"coz",
"num-traits",
"paste 0.1.18",
"paste",
"quickcheck",
"rand",
"smallvec",

95
deny.toml Normal file
View file

@ -0,0 +1,95 @@
# spell-checker:ignore SSLeay RUSTSEC
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
vulnerability = "warn"
unmaintained = "warn"
yanked = "warn"
notice = "warn"
ignore = [
#"RUSTSEC-0000-0000",
]
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
unlicensed = "deny"
allow = [
"MIT",
"Apache-2.0",
"ISC",
"BSD-2-Clause",
"BSD-2-Clause-FreeBSD",
"BSD-3-Clause",
"CC0-1.0",
"MPL-2.0", # XXX considered copyleft?
]
copyleft = "deny"
allow-osi-fsf-free = "neither"
default = "deny"
confidence-threshold = 0.8
exceptions = [
{ allow = ["OpenSSL"], name = "ring" },
]
[[licenses.clarify]]
name = "ring"
# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses
# https://spdx.org/licenses/OpenSSL.html
# ISC - Both BoringSSL and ring use this for their new files
# MIT - "Files in third_party/ have their own licenses, as described therein. The MIT
# license, for third_party/fiat, which, unlike other third_party directories, is
# compiled into non-test libraries, is included below."
# OpenSSL - Obviously
expression = "ISC AND MIT AND OpenSSL"
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
multiple-versions = "deny"
wildcards = "allow"
highlight = "all"
# For each duplicate dependency, indicate the name of the dependency which
# introduces it.
# spell-checker: disable
skip = [
# blake2d_simd
{ name = "arrayvec", version = "=0.7.2" },
# flimit/unix_socket
{ name = "cfg-if", version = "=0.1.10" },
# ordered-multimap
{ name = "hashbrown", version = "=0.9.1" },
# kernel32-sys
{ name = "winapi", version = "=0.2.8" },
# bindgen 0.59.2
{ name = "clap", version = "=2.34.0" },
{ name = "strsim", version = "=0.8.0" },
{ name = "textwrap", version = "=0.11.0" },
{ name = "cpp_common", version = "=0.4.0" },
# quickcheck
{ name = "env_logger", version = "=0.8.4" },
# cpp_*
{ name = "memchr", version = "=1.0.2" },
{ name = "quote", version = "=0.3.15" },
{ name = "unicode-xid", version = "=0.0.4" },
# exacl
{ name = "nix", version = "=0.21.0" },
]
# spell-checker: enable
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
unknown-registry = "warn"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []

View file

@ -23,7 +23,7 @@ smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or late
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
[dev-dependencies]
paste = "0.1.18"
paste = "1.0.6"
quickcheck = "1.0.3"
[[bin]]

View file

@ -63,7 +63,7 @@ fn sleep(args: &[&str]) -> UResult<()> {
args.iter().try_fold(
Duration::new(0, 0),
|result, arg| match uucore::parse_time::from_str(&arg[..]) {
Ok(m) => Ok(m + result),
Ok(m) => Ok(m.saturating_add(result)),
Err(f) => Err(USimpleError::new(1, f)),
},
)?;

View file

@ -1162,46 +1162,6 @@ where
Ok(())
}
fn split_into_n_chunks_by_line_round_robin<R>(
settings: &Settings,
reader: &mut R,
num_chunks: u64,
) -> UResult<()>
where
R: BufRead,
{
// This object is responsible for creating the filename for each chunk.
let mut filename_iterator = FilenameIterator::new(
&settings.prefix,
&settings.additional_suffix,
settings.suffix_length,
settings.suffix_type,
);
// Create one writer for each chunk. This will create each
// of the underlying files (if not in `--filter` mode).
let mut writers = vec![];
for _ in 0..num_chunks {
let filename = filename_iterator
.next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
let writer = platform::instantiate_current_writer(&settings.filter, filename.as_str());
writers.push(writer);
}
let num_chunks: usize = num_chunks.try_into().unwrap();
for (i, line_result) in reader.lines().enumerate() {
let line = line_result.unwrap();
let maybe_writer = writers.get_mut(i % num_chunks);
let writer = maybe_writer.unwrap();
let bytes = line.as_bytes();
writer.write_all(bytes)?;
writer.write_all(b"\n")?;
}
Ok(())
}
fn split(settings: &Settings) -> UResult<()> {
let mut reader = BufReader::new(if settings.input == "-" {
Box::new(stdin()) as Box<dyn Read>
@ -1228,9 +1188,6 @@ fn split(settings: &Settings) -> UResult<()> {
let chunk_number = chunk_number - 1;
kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks)
}
Strategy::Number(NumberType::RoundRobin(num_chunks)) => {
split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks)
}
Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")),
Strategy::Lines(chunk_size) => {
let mut writer = LineChunkWriter::new(chunk_size, settings)

View file

@ -6,11 +6,39 @@
// file that was distributed with this source code.
// spell-checker:ignore (vars) NANOS numstr
//! Parsing a duration from a string.
//!
//! Use the [`from_str`] function to parse a [`Duration`] from a string.
use std::time::Duration;
use crate::display::Quotable;
/// Parse a duration from a string.
///
/// The string may contain only a number, like "123" or "4.5", or it
/// may contain a number with a unit specifier, like "123s" meaning
/// one hundred twenty three seconds or "4.5d" meaning four and a half
/// days. If no unit is specified, the unit is assumed to be seconds.
///
/// This function uses [`Duration::saturating_mul`] to compute the
/// number of seconds, so it does not overflow. If overflow would have
/// occurred, [`Duration::MAX`] is returned instead.
///
/// # Errors
///
/// This function returns an error if the input string is empty, the
/// input is not a valid number, or the unit specifier is invalid or
/// unknown.
///
/// # Examples
///
/// ```rust
/// use std::time::Duration;
/// use uucore::parse_time::from_str;
/// assert_eq!(from_str("123"), Ok(Duration::from_secs(123)));
/// assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
/// ```
pub fn from_str(string: &str) -> Result<Duration, String> {
let len = string.len();
if len == 0 {
@ -39,5 +67,42 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
let whole_secs = num.trunc();
let nanos = (num.fract() * (NANOS_PER_SEC as f64)).trunc();
let duration = Duration::new(whole_secs as u64, nanos as u32);
Ok(duration * times)
Ok(duration.saturating_mul(times))
}
#[cfg(test)]
mod tests {
use crate::parse_time::from_str;
use std::time::Duration;
#[test]
fn test_no_units() {
assert_eq!(from_str("123"), Ok(Duration::from_secs(123)));
}
#[test]
fn test_units() {
assert_eq!(from_str("2d"), Ok(Duration::from_secs(60 * 60 * 24 * 2)));
}
#[test]
fn test_saturating_mul() {
assert_eq!(from_str("9223372036854775808d"), Ok(Duration::MAX));
}
#[test]
fn test_error_empty() {
assert!(from_str("").is_err());
}
#[test]
fn test_error_invalid_unit() {
assert!(from_str("123X").is_err());
}
#[test]
fn test_error_invalid_magnitude() {
assert!(from_str("12abc3s").is_err());
}
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore dont
use crate::common::util::*;
use std::time::{Duration, Instant};
@ -115,3 +116,28 @@ fn test_sleep_sum_duration_many() {
fn test_sleep_wrong_time() {
new_ucmd!().args(&["0.1s", "abc"]).fails();
}
// TODO These tests would obviously block for a very long time. We
// only want to verify that there is no error here, so we could just
// figure out a way to terminate the child process after a short
// period of time.
// #[test]
#[allow(dead_code)]
fn test_dont_overflow() {
new_ucmd!()
.arg("9223372036854775808d")
.succeeds()
.no_stderr()
.no_stdout();
}
// #[test]
#[allow(dead_code)]
fn test_sum_overflow() {
new_ucmd!()
.args(&["100000000000000d", "100000000000000d", "100000000000000d"])
.succeeds()
.no_stderr()
.no_stdout();
}

View file

@ -605,19 +605,3 @@ fn test_line_bytes() {
assert_eq!(at.read("xac"), "cccc\ndd\n");
assert_eq!(at.read("xad"), "ee\n");
}
#[test]
fn test_round_robin() {
let (at, mut ucmd) = at_and_ucmd!();
let file_read = |f| {
let mut s = String::new();
at.open(f).read_to_string(&mut s).unwrap();
s
};
ucmd.args(&["-n", "r/2", "fivelines.txt"]).succeeds();
assert_eq!(file_read("xaa"), "1\n3\n5\n");
assert_eq!(file_read("xab"), "2\n4\n");
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore dont
use crate::common::util::*;
// FIXME: this depends on the system having true and false in PATH
@ -64,3 +65,19 @@ fn test_preserve_status() {
.no_stderr()
.no_stdout();
}
#[test]
fn test_dont_overflow() {
new_ucmd!()
.args(&["9223372036854775808d", "sleep", "0"])
.succeeds()
.code_is(0)
.no_stderr()
.no_stdout();
new_ucmd!()
.args(&["-k", "9223372036854775808d", "10", "sleep", "0"])
.succeeds()
.code_is(0)
.no_stderr()
.no_stdout();
}