Merge pull request #273 from sylvestre/mv-uutests

Move to use uutests
This commit is contained in:
Daniel Hofstetter
2025-08-08 11:01:47 +02:00
committed by GitHub
23 changed files with 171 additions and 4175 deletions

17
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,17 @@
repos:
- repo: local
hooks:
- id: rust-linting
name: Rust linting
description: Run cargo fmt on files included in the commit.
entry: cargo +stable fmt --
pass_filenames: true
types: [file, rust]
language: system
- id: rust-clippy
name: Rust clippy
description: Run cargo clippy on files included in the commit.
entry: cargo +stable clippy --workspace --all-targets --all-features --
pass_filenames: false
types: [file, rust]
language: system

53
Cargo.lock generated
View File

@@ -262,6 +262,22 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "ctor"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e9666f4a9a948d4f1dff0c08a4512b0f7c86414b23960104c243c10d79f4c3"
dependencies = [
"ctor-proc-macro",
"dtor",
]
[[package]]
name = "ctor-proc-macro"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.4.0" version = "0.4.0"
@@ -310,6 +326,21 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "dtor"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222ef136a1c687d4aa0395c175f2c4586e379924c352fd02f7870cf7de783c23"
dependencies = [
"dtor-proc-macro",
]
[[package]]
name = "dtor-proc-macro"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055"
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@@ -1198,6 +1229,7 @@ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
"clap_mangen", "clap_mangen",
"ctor",
"dns-lookup", "dns-lookup",
"libc", "libc",
"nix", "nix",
@@ -1231,6 +1263,7 @@ dependencies = [
"uu_uuidgen", "uu_uuidgen",
"uucore", "uucore",
"uuid", "uuid",
"uutests",
"xattr", "xattr",
] ]
@@ -1472,6 +1505,26 @@ dependencies = [
"rand", "rand",
] ]
[[package]]
name = "uutests"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12453ee52c9cffa6bf2c74f9f35ed0c824b846cb0b4bdee829d8150332e7204c"
dependencies = [
"ctor",
"glob",
"libc",
"nix",
"pretty_assertions",
"rand",
"regex",
"rlimit",
"tempfile",
"time",
"uucore",
"xattr",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"

View File

@@ -56,6 +56,7 @@ libmount-sys = "0.1.1"
linux-raw-sys = { version = "0.10.0", features = ["ioctl"] } linux-raw-sys = { version = "0.10.0", features = ["ioctl"] }
md-5 = "0.10.6" md-5 = "0.10.6"
nix = { version = "0.30", default-features = false } nix = { version = "0.30", default-features = false }
parse_datetime = "0.11.0"
phf = "0.12.0" phf = "0.12.0"
phf_codegen = "0.12.1" phf_codegen = "0.12.1"
rand = { version = "0.9.0", features = ["small_rng"] } rand = { version = "0.9.0", features = ["small_rng"] }
@@ -71,21 +72,21 @@ textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "2.0" thiserror = "2.0"
uucore = "0.1.0" uucore = "0.1.0"
uuid = { version = "1.16.0", features = ["rng-rand"] } uuid = { version = "1.16.0", features = ["rng-rand"] }
uutests = "0.1.0"
windows = { version = "0.61.1" } windows = { version = "0.61.1" }
xattr = "1.3.1" xattr = "1.3.1"
parse_datetime = "0.11.0"
[dependencies] [dependencies]
clap = { workspace = true } clap = { workspace = true }
clap_complete = { workspace = true } clap_complete = { workspace = true }
clap_mangen = { workspace = true } clap_mangen = { workspace = true }
dns-lookup = { workspace = true } dns-lookup = { workspace = true }
parse_datetime = {workspace = true}
phf = { workspace = true } phf = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
textwrap = { workspace = true } textwrap = { workspace = true }
uucore = { workspace = true } uucore = { workspace = true }
parse_datetime = {workspace = true}
# #
blockdev = { optional = true, version = "0.0.1", package = "uu_blockdev", path = "src/uu/blockdev" } blockdev = { optional = true, version = "0.0.1", package = "uu_blockdev", path = "src/uu/blockdev" }
@@ -107,6 +108,7 @@ setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src
uuidgen = { optional = true, version = "0.0.1", package = "uu_uuidgen", path ="src/uu/uuidgen" } uuidgen = { optional = true, version = "0.0.1", package = "uu_uuidgen", path ="src/uu/uuidgen" }
[dev-dependencies] [dev-dependencies]
ctor = "0.4.1"
# dmesg test require fixed-boot-time feature turned on. # dmesg test require fixed-boot-time feature turned on.
dmesg = { version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg", features = ["fixed-boot-time"] } dmesg = { version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg", features = ["fixed-boot-time"] }
libc = { workspace = true } libc = { workspace = true }
@@ -114,6 +116,7 @@ pretty_assertions = "1"
rand = { workspace = true } rand = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
tempfile = { workspace = true } tempfile = { workspace = true }
uutests = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals"] } uucore = { workspace = true, features = ["entries", "process", "signals"] }
uuid = { workspace = true } uuid = { workspace = true }

View File

@@ -3,7 +3,9 @@
// 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.
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {
@@ -23,9 +25,12 @@ fn test_report_mutually_exclusive_with_others() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod linux { mod linux {
use crate::common::util::TestScenario;
use regex::Regex; use regex::Regex;
use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_fails_on_first_error() { fn test_fails_on_first_error() {
new_ucmd!() new_ucmd!()
@@ -56,7 +61,9 @@ mod linux {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
mod non_linux { mod non_linux {
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_fails_on_unsupported_platforms() { fn test_fails_on_unsupported_platforms() {

View File

@@ -4,7 +4,11 @@
// file that was distributed with this source code. // file that was distributed with this source code.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use crate::common::util::TestScenario; use uutests::new_ucmd;
#[cfg(target_os = "linux")]
use uutests::util::TestScenario;
#[cfg(target_os = "linux")]
use uutests::util_name;
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View File

@@ -2,7 +2,10 @@
// //
// 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.
use crate::common::util::TestScenario;
use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {

View File

@@ -3,7 +3,9 @@
// 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.
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {
@@ -23,7 +25,10 @@ fn test_operations_mutually_exclusive() {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod linux { mod linux {
use crate::common::util::TestScenario;
use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_fails_on_non_existing_path() { fn test_fails_on_non_existing_path() {
@@ -48,7 +53,9 @@ mod linux {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
mod non_linux { mod non_linux {
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_fails_on_unsupported_platforms() { fn test_fails_on_unsupported_platforms() {

View File

@@ -4,8 +4,13 @@
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (words) symdir somefakedir // spell-checker:ignore (words) symdir somefakedir
use uutests::at_and_ucmd;
#[cfg(unix)] #[cfg(unix)]
use crate::common::util::TestScenario; use uutests::new_ucmd;
#[cfg(unix)]
use uutests::util::TestScenario;
#[cfg(unix)]
use uutests::util_name;
#[cfg(unix)] #[cfg(unix)]
use regex::Regex; use regex::Regex;

View File

@@ -3,7 +3,9 @@
// 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.
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {

View File

@@ -4,7 +4,11 @@
// file that was distributed with this source code. // file that was distributed with this source code.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use crate::common::util::TestScenario; use uutests::new_ucmd;
#[cfg(target_os = "linux")]
use uutests::util::TestScenario;
#[cfg(target_os = "linux")]
use uutests::util_name;
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]

View File

@@ -3,8 +3,10 @@
// 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.
use crate::common::util::TestScenario;
use std::path::Path; use std::path::Path;
use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
fn write_file_content(dir: &Path, name: &str, content: &str) { fn write_file_content(dir: &Path, name: &str, content: &str) {
std::fs::create_dir_all(dir).unwrap(); std::fs::create_dir_all(dir).unwrap();

View File

@@ -7,7 +7,9 @@ use std::io::Write;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {

View File

@@ -3,7 +3,9 @@
// 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.
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_verb() { fn test_invalid_verb() {
@@ -24,7 +26,9 @@ fn test_no_terminal() {
#[cfg(not(target_family = "unix"))] #[cfg(not(target_family = "unix"))]
mod non_unix { mod non_unix {
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_fails_on_unsupported_platforms() { fn test_fails_on_unsupported_platforms() {

View File

@@ -3,8 +3,9 @@
// 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.
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1); new_ucmd!().arg("--definitely-invalid").fails().code_is(1);

View File

@@ -4,7 +4,9 @@
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (words) symdir somefakedir // spell-checker:ignore (words) symdir somefakedir
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {

View File

@@ -3,7 +3,10 @@
// 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.
use crate::common::util::TestScenario; use uutests::at_and_ucmd;
use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {

View File

@@ -5,8 +5,11 @@
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
mod unix { mod unix {
use crate::common::util::{TestScenario, UCommand, TESTS_BINARY}; use uutests::new_ucmd;
use uutests::util::get_tests_binary;
use uutests::util::TestScenario;
use uutests::util::UCommand;
use uutests::util_name;
#[test] #[test]
fn test_invalid_arg() { fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1); new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
@@ -77,8 +80,11 @@ mod unix {
#[test] #[test]
fn unprivileged_user_cannot_steal_controlling_tty() { fn unprivileged_user_cannot_steal_controlling_tty() {
let shell_cmd = let shell_cmd = format!(
format!("{TESTS_BINARY} setsid -w -c {TESTS_BINARY} setsid -w -c /b/usrin/true"); "{} setsid -w -c {} setsid -w -c /b/usrin/true",
get_tests_binary(),
get_tests_binary()
);
UCommand::new() UCommand::new()
.terminal_simulation(true) .terminal_simulation(true)
.arg(&shell_cmd) .arg(&shell_cmd)
@@ -92,7 +98,8 @@ mod unix {
#[test] #[test]
fn unprivileged_user_can_take_new_controlling_tty() { fn unprivileged_user_can_take_new_controlling_tty() {
let shell_cmd = format!( let shell_cmd = format!(
"/usr/bin/cat /proc/self/stat; {TESTS_BINARY} setsid -w -c /usr/bin/cat /proc/self/stat" "/usr/bin/cat /proc/self/stat; {} setsid -w -c /usr/bin/cat /proc/self/stat",
get_tests_binary()
); );
let cmd_result = UCommand::new() let cmd_result = UCommand::new()
@@ -124,7 +131,8 @@ mod unix {
#[test] #[test]
fn setsid_takes_session_leadership() { fn setsid_takes_session_leadership() {
let shell_cmd = format!( let shell_cmd = format!(
"/usr/bin/cat /proc/self/stat; {TESTS_BINARY} setsid /usr/bin/cat /proc/self/stat" "/usr/bin/cat /proc/self/stat; {} setsid /usr/bin/cat /proc/self/stat",
get_tests_binary()
); );
let cmd_result = UCommand::new() let cmd_result = UCommand::new()
@@ -161,7 +169,9 @@ mod unix {
#[cfg(not(target_family = "unix"))] #[cfg(not(target_family = "unix"))]
mod non_unix { mod non_unix {
use crate::common::util::TestScenario; use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
#[test] #[test]
fn unsupported_platforms() { fn unsupported_platforms() {

View File

@@ -4,8 +4,7 @@
// file that was distributed with this source code. // file that was distributed with this source code.
use uuid::Uuid; use uuid::Uuid;
use uutests::{new_ucmd, util::TestScenario, util::UCommand, util_name};
use crate::common::util::{TestScenario, UCommand};
fn assert_ver_eq(cmd: &mut UCommand, ver: uuid::Version) { fn assert_ver_eq(cmd: &mut UCommand, ver: uuid::Version) {
let uuid = Uuid::parse_str( let uuid = Uuid::parse_str(

View File

@@ -1,93 +0,0 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
/// Platform-independent helper for constructing a `PathBuf` from individual elements
#[macro_export]
macro_rules! path_concat {
($e:expr, ..$n:expr) => {{
use std::path::PathBuf;
let n = $n;
let mut pb = PathBuf::new();
for _ in 0..n {
pb.push($e);
}
pb.to_str().unwrap().to_owned()
}};
($($e:expr),*) => {{
use std::path::PathBuf;
let mut pb = PathBuf::new();
$(
pb.push($e);
)*
pb.to_str().unwrap().to_owned()
}};
}
/// Deduce the name of the test binary from the test filename.
///
/// e.g.: `tests/by-util/test_cat.rs` -> `cat`
#[macro_export]
macro_rules! util_name {
() => {
module_path!()
.split("_")
.nth(1)
.and_then(|s| s.split("::").next())
.expect("no test name")
};
}
/// Convenience macro for acquiring a [`UCommand`] builder.
///
/// Returns the following:
/// - a [`UCommand`] builder for invoking the binary to be tested
///
/// This macro is intended for quick, single-call tests. For more complex tests
/// that require multiple invocations of the tested binary, see [`TestScenario`]
///
/// [`UCommand`]: crate::tests::common::util::UCommand
/// [`TestScenario]: crate::tests::common::util::TestScenario
#[macro_export]
macro_rules! new_ucmd {
() => {
TestScenario::new(util_name!()).ucmd()
};
}
/// Convenience macro for acquiring a [`UCommand`] builder and a test path.
///
/// Returns a tuple containing the following:
/// - an [`AtPath`] that points to a unique temporary test directory
/// - a [`UCommand`] builder for invoking the binary to be tested
///
/// This macro is intended for quick, single-call tests. For more complex tests
/// that require multiple invocations of the tested binary, see [`TestScenario`]
///
/// [`UCommand`]: crate::tests::common::util::UCommand
/// [`AtPath`]: crate::tests::common::util::AtPath
/// [`TestScenario]: crate::tests::common::util::TestScenario
#[macro_export]
macro_rules! at_and_ucmd {
() => {{
let ts = TestScenario::new(util_name!());
(ts.fixtures.clone(), ts.ucmd())
}};
}
/// If `common::util::expected_result` returns an error, i.e. the `util` in `$PATH` doesn't
/// include a coreutils version string or the version is too low,
/// this macro can be used to automatically skip the test and print the reason.
#[macro_export]
macro_rules! unwrap_or_return {
( $e:expr ) => {
match $e {
Ok(x) => x,
Err(e) => {
println!("test skipped: {}", e);
return;
}
}
};
}

View File

@@ -1,8 +0,0 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[macro_use]
pub mod macros;
pub mod random;
pub mod util;

View File

@@ -1,340 +0,0 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use rand::distr::{Distribution, Uniform};
use rand::{rng, Rng};
/// Samples alphanumeric characters `[A-Za-z0-9]` including newline `\n`
///
/// # Examples
///
/// ```rust,ignore
/// use rand::{Rng, thread_rng};
///
/// let vec = thread_rng()
/// .sample_iter(AlphanumericNewline)
/// .take(10)
/// .collect::<Vec<u8>>();
/// println!("Random chars: {}", String::from_utf8(vec).unwrap());
/// ```
#[derive(Clone, Copy, Debug)]
pub struct AlphanumericNewline;
impl AlphanumericNewline {
/// The charset to act upon
const CHARSET: &'static [u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n";
/// Generate a random byte from [`Self::CHARSET`] and return it as `u8`.
///
/// # Arguments
///
/// * `rng`: A [`rand::Rng`]
///
/// returns: u8
fn random<R>(rng: &mut R) -> u8
where
R: Rng + ?Sized,
{
let idx = rng.random_range(0..Self::CHARSET.len());
Self::CHARSET[idx]
}
}
impl Distribution<u8> for AlphanumericNewline {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
Self::random(rng)
}
}
/// Generate a random string from a [`Distribution`]
///
/// # Examples
///
/// ```rust,ignore
/// use crate::common::random::{AlphanumericNewline, RandomString};
/// use rand::distributions::Alphanumeric;
///
/// // generates a 100 byte string with characters from AlphanumericNewline
/// let random_string = RandomString::generate(AlphanumericNewline, 100);
/// assert_eq!(100, random_string.len());
///
/// // generates a 100 byte string with 10 newline characters not ending with a newline
/// let string = RandomString::generate_with_delimiter(Alphanumeric, b'\n', 10, false, 100);
/// assert_eq!(100, random_string.len());
/// ```
pub struct RandomString;
impl RandomString {
/// Generate a random string from the given [`Distribution`] with the given `length` in bytes.
///
/// # Arguments
///
/// * `dist`: A u8 [`Distribution`]
/// * `length`: the length of the resulting string in bytes
///
/// returns: String
pub fn generate<D>(dist: D, length: usize) -> String
where
D: Distribution<u8>,
{
rng()
.sample_iter(dist)
.take(length)
.map(|b| b as char)
.collect()
}
/// Generate a random string from the [`Distribution`] with the given `length` in bytes. The
/// function takes a `delimiter`, which is randomly distributed in the string, such that exactly
/// `num_delimiter` amount of `delimiter`s occur. If `end_with_delimiter` is set, then the
/// string ends with the delimiter, else the string does not end with the delimiter.
///
/// # Arguments
///
/// * `dist`: A `u8` [`Distribution`]
/// * `delimiter`: A `u8` delimiter, which does not need to be included in the `Distribution`
/// * `num_delimiter`: The number of `delimiter`s contained in the resulting string
/// * `end_with_delimiter`: If the string shall end with the given delimiter
/// * `length`: the length of the resulting string in bytes
///
/// returns: String
///
/// # Examples
///
/// ```rust,ignore
/// use crate::common::random::{AlphanumericNewline, RandomString};
///
/// // generates a 100 byte string with 10 '\0' byte characters not ending with a '\0' byte
/// let string = RandomString::generate_with_delimiter(AlphanumericNewline, 0, 10, false, 100);
/// assert_eq!(100, random_string.len());
/// assert_eq!(
/// 10,
/// random_string.as_bytes().iter().filter(|p| **p == 0).count()
/// );
/// assert!(!random_string.as_bytes().ends_with(&[0]));
/// ```
pub fn generate_with_delimiter<D>(
dist: D,
delimiter: u8,
num_delimiter: usize,
end_with_delimiter: bool,
length: usize,
) -> String
where
D: Distribution<u8>,
{
if length == 0 {
return String::new();
} else if length == 1 {
return if num_delimiter > 0 {
String::from(delimiter as char)
} else {
String::from(rng().sample(&dist) as char)
};
}
let samples = length - 1;
let mut result: Vec<u8> = rng().sample_iter(&dist).take(samples).collect();
if num_delimiter == 0 {
result.push(rng().sample(&dist));
return String::from_utf8(result).unwrap();
}
let num_delimiter = if end_with_delimiter {
num_delimiter - 1
} else {
num_delimiter
};
// safe to unwrap because samples is at least 1, thus high > low
let between = Uniform::new(0, samples).unwrap();
for _ in 0..num_delimiter {
let mut pos = between.sample(&mut rng());
let turn = pos;
while result[pos] == delimiter {
pos += 1;
if pos >= samples {
pos = 0;
}
if pos == turn {
break;
}
}
result[pos] = delimiter;
}
if end_with_delimiter {
result.push(delimiter);
} else {
result.push(rng().sample(&dist));
}
String::from_utf8(result).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::distr::Alphanumeric;
#[test]
fn test_random_string_generate() {
let random_string = RandomString::generate(AlphanumericNewline, 0);
assert_eq!(0, random_string.len());
let random_string = RandomString::generate(AlphanumericNewline, 1);
assert_eq!(1, random_string.len());
let random_string = RandomString::generate(AlphanumericNewline, 100);
assert_eq!(100, random_string.len());
}
#[test]
fn test_random_string_generate_with_delimiter_when_length_is_zero() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 0, false, 0);
assert_eq!(0, random_string.len());
}
#[test]
fn test_random_string_generate_with_delimiter_when_num_delimiter_is_greater_than_length() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 2, false, 1);
assert_eq!(1, random_string.len());
assert!(random_string.as_bytes().contains(&0));
assert!(random_string.as_bytes().ends_with(&[0]));
}
#[test]
fn test_random_string_generate_with_delimiter_should_end_with_delimiter() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, true, 1);
assert_eq!(1, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, false, 1);
assert_eq!(1, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, true, 2);
assert_eq!(2, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 2, true, 2);
assert_eq!(2, random_string.len());
assert_eq!(
2,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, true, 3);
assert_eq!(3, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
}
#[test]
fn test_random_string_generate_with_delimiter_should_not_end_with_delimiter() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 0, false, 1);
assert_eq!(1, random_string.len());
assert_eq!(
0,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 0, true, 1);
assert_eq!(1, random_string.len());
assert_eq!(
0,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, false, 2);
assert_eq!(2, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, false, 3);
assert_eq!(3, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 2, false, 3);
assert_eq!(3, random_string.len());
assert_eq!(
2,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
}
#[test]
fn test_generate_with_delimiter_with_greater_length() {
let random_string =
RandomString::generate_with_delimiter(Alphanumeric, 0, 100, false, 1000);
assert_eq!(1000, random_string.len());
assert_eq!(
100,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 100, true, 1000);
assert_eq!(1000, random_string.len());
assert_eq!(
100,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
}
/// Originally used to exclude an error within the `random` module. The two
/// affected tests timed out on windows, but only in the ci. These tests are
/// also the source for the concrete numbers. The timed out tests are
/// `test_tail.rs::test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size`
/// `test_tail.rs::test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size`.
#[test]
fn test_generate_random_strings_when_length_is_around_critical_buffer_sizes() {
let length = 8192 * 3;
let random_string = RandomString::generate(AlphanumericNewline, length);
assert_eq!(length, random_string.len());
let length = 8192 * 3 + 1;
let random_string =
RandomString::generate_with_delimiter(Alphanumeric, b'\n', 100, true, length);
assert_eq!(length, random_string.len());
assert_eq!(
100,
random_string
.as_bytes()
.iter()
.filter(|p| **p == b'\n')
.count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,20 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils util-linux package.
// //
// 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.
#[macro_use]
mod common;
use std::env;
pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_util-linux");
// Use the ctor attribute to run this function before any tests
#[ctor::ctor]
fn init() {
unsafe {
// Necessary for uutests to be able to find the binary
std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY);
}
}
#[cfg(feature = "lscpu")] #[cfg(feature = "lscpu")]
#[path = "by-util/test_lscpu.rs"] #[path = "by-util/test_lscpu.rs"]
mod test_lscpu; mod test_lscpu;