mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-09-16 03:36:18 +00:00
Merge branch 'main' into tail_notify
This commit is contained in:
commit
409878e323
150 changed files with 2505 additions and 863 deletions
|
@ -418,6 +418,29 @@ fn test_chown_only_user_id() {
|
|||
.stderr_contains(&"failed to change");
|
||||
}
|
||||
|
||||
/// Test for setting the owner to a user ID for a user that does not exist.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// $ touch f && chown 12345 f
|
||||
///
|
||||
/// succeeds with exit status 0 and outputs nothing. The owner of the
|
||||
/// file is set to 12345, even though no user with that ID exists.
|
||||
///
|
||||
/// This test must be run as root, because only the root user can
|
||||
/// transfer ownership of a file.
|
||||
#[test]
|
||||
fn test_chown_only_user_id_nonexistent_user() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = ts.fixtures.clone();
|
||||
at.touch("f");
|
||||
if let Ok(result) = run_ucmd_as_root(&ts, &["12345", "f"]) {
|
||||
result.success().no_stdout().no_stderr();
|
||||
} else {
|
||||
print!("Test skipped; requires root user");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
|
@ -461,6 +484,29 @@ fn test_chown_only_group_id() {
|
|||
.stderr_contains(&"failed to change");
|
||||
}
|
||||
|
||||
/// Test for setting the group to a group ID for a group that does not exist.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// $ touch f && chown :12345 f
|
||||
///
|
||||
/// succeeds with exit status 0 and outputs nothing. The group of the
|
||||
/// file is set to 12345, even though no group with that ID exists.
|
||||
///
|
||||
/// This test must be run as root, because only the root user can
|
||||
/// transfer ownership of a file.
|
||||
#[test]
|
||||
fn test_chown_only_group_id_nonexistent_group() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = ts.fixtures.clone();
|
||||
at.touch("f");
|
||||
if let Ok(result) = run_ucmd_as_root(&ts, &[":12345", "f"]) {
|
||||
result.success().no_stdout().no_stderr();
|
||||
} else {
|
||||
print!("Test skipped; requires root user");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chown_owner_group_id() {
|
||||
// test chown 1111:1111 file.txt
|
||||
|
|
|
@ -1032,8 +1032,8 @@ fn test_cp_no_deref_folder_to_folder() {
|
|||
#[cfg(target_os = "linux")]
|
||||
fn test_cp_archive() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ts = time::now().to_timespec();
|
||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond() as u32);
|
||||
// set the file creation/modification an hour ago
|
||||
filetime::set_file_times(
|
||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||
|
@ -1135,8 +1135,8 @@ fn test_cp_archive_recursive() {
|
|||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn test_cp_preserve_timestamps() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ts = time::now().to_timespec();
|
||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
|
||||
// set the file creation/modification an hour ago
|
||||
filetime::set_file_times(
|
||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||
|
@ -1168,8 +1168,8 @@ fn test_cp_preserve_timestamps() {
|
|||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn test_cp_no_preserve_timestamps() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let ts = time::now().to_timespec();
|
||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
|
||||
// set the file creation/modification an hour ago
|
||||
filetime::set_file_times(
|
||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||
|
@ -1506,6 +1506,18 @@ fn test_copy_through_dangling_symlink() {
|
|||
.stderr_only("cp: not writing through dangling symlink 'target'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_through_dangling_symlink_no_dereference() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.symlink_file("no-such-file", "dangle");
|
||||
ucmd.arg("-P")
|
||||
.arg("dangle")
|
||||
.arg("d2")
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.no_stdout();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_archive_on_nonexistent_file() {
|
||||
|
|
|
@ -73,7 +73,7 @@ fn test_df_output() {
|
|||
"Filesystem",
|
||||
"Size",
|
||||
"Used",
|
||||
"Available",
|
||||
"Avail",
|
||||
"Capacity",
|
||||
"Use%",
|
||||
"Mounted",
|
||||
|
@ -84,7 +84,7 @@ fn test_df_output() {
|
|||
"Filesystem",
|
||||
"Size",
|
||||
"Used",
|
||||
"Available",
|
||||
"Avail",
|
||||
"Use%",
|
||||
"Mounted",
|
||||
"on",
|
||||
|
@ -107,7 +107,7 @@ fn test_df_output_overridden() {
|
|||
"Filesystem",
|
||||
"Size",
|
||||
"Used",
|
||||
"Available",
|
||||
"Avail",
|
||||
"Capacity",
|
||||
"Use%",
|
||||
"Mounted",
|
||||
|
@ -118,7 +118,7 @@ fn test_df_output_overridden() {
|
|||
"Filesystem",
|
||||
"Size",
|
||||
"Used",
|
||||
"Available",
|
||||
"Avail",
|
||||
"Use%",
|
||||
"Mounted",
|
||||
"on",
|
||||
|
@ -134,6 +134,46 @@ fn test_df_output_overridden() {
|
|||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_headers() {
|
||||
let expected = if cfg!(target_os = "macos") {
|
||||
vec![
|
||||
"Filesystem",
|
||||
"1K-blocks",
|
||||
"Used",
|
||||
"Available",
|
||||
"Capacity",
|
||||
"Use%",
|
||||
"Mounted",
|
||||
"on",
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
"Filesystem",
|
||||
"1K-blocks",
|
||||
"Used",
|
||||
"Available",
|
||||
"Use%",
|
||||
"Mounted",
|
||||
"on",
|
||||
]
|
||||
};
|
||||
let output = new_ucmd!().succeeds().stdout_move_str();
|
||||
let actual = output.lines().take(1).collect::<Vec<&str>>()[0];
|
||||
let actual = actual.split_whitespace().collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_precedence_of_human_readable_header_over_output_header() {
|
||||
let output = new_ucmd!()
|
||||
.args(&["-H", "--output=size"])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
assert_eq!(header.trim(), "Size");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_total_option_with_single_dash() {
|
||||
// These should fail because `-total` should have two dashes,
|
||||
|
@ -199,7 +239,42 @@ fn test_type_option() {
|
|||
new_ucmd!()
|
||||
.args(&["-t", fs_type, "-t", "nonexisting"])
|
||||
.succeeds();
|
||||
new_ucmd!().args(&["-t", "nonexisting"]).fails();
|
||||
new_ucmd!()
|
||||
.args(&["-t", "nonexisting"])
|
||||
.fails()
|
||||
.stderr_contains("no file systems processed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_option_with_file() {
|
||||
let fs_type = new_ucmd!()
|
||||
.args(&["--output=fstype", "."])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let fs_type = fs_type.lines().nth(1).unwrap().trim();
|
||||
|
||||
new_ucmd!().args(&["-t", fs_type, "."]).succeeds();
|
||||
new_ucmd!()
|
||||
.args(&["-t", "nonexisting", "."])
|
||||
.fails()
|
||||
.stderr_contains("no file systems processed");
|
||||
|
||||
let fs_types = new_ucmd!()
|
||||
.arg("--output=fstype")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let fs_types: Vec<_> = fs_types
|
||||
.lines()
|
||||
.skip(1)
|
||||
.filter(|t| t.trim() != fs_type && t.trim() != "")
|
||||
.collect();
|
||||
|
||||
if !fs_types.is_empty() {
|
||||
new_ucmd!()
|
||||
.args(&["-t", fs_types[0], "."])
|
||||
.fails()
|
||||
.stderr_contains("no file systems processed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -340,22 +415,58 @@ fn test_iuse_percentage() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_block_size() {
|
||||
let output = new_ucmd!()
|
||||
.arg("--output=size")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
|
||||
assert_eq!(header, "1K-blocks");
|
||||
|
||||
let output = new_ucmd!()
|
||||
.arg("--output=size")
|
||||
.env("POSIXLY_CORRECT", "1")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let header = output.lines().next().unwrap().to_string();
|
||||
|
||||
assert_eq!(header, "512B-blocks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_block_size_in_posix_portability_mode() {
|
||||
fn get_header(s: &str) -> String {
|
||||
s.lines()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
let output = new_ucmd!().arg("-P").succeeds().stdout_move_str();
|
||||
assert_eq!(get_header(&output), "1024-blocks");
|
||||
|
||||
let output = new_ucmd!()
|
||||
.arg("-P")
|
||||
.env("POSIXLY_CORRECT", "1")
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
assert_eq!(get_header(&output), "512-blocks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_size_1024() {
|
||||
fn get_header(block_size: u64) -> String {
|
||||
// TODO When #3057 is resolved, we should just use
|
||||
//
|
||||
// new_ucmd!().arg("--output=size").succeeds().stdout_move_str();
|
||||
//
|
||||
// instead of parsing the entire `df` table as a string.
|
||||
let output = new_ucmd!()
|
||||
.args(&["-B", &format!("{}", block_size)])
|
||||
.args(&["-B", &format!("{}", block_size), "--output=size"])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let first_line = output.lines().next().unwrap();
|
||||
let mut column_labels = first_line.split_whitespace();
|
||||
let size_column_label = column_labels.nth(1).unwrap();
|
||||
size_column_label.into()
|
||||
output.lines().next().unwrap().to_string()
|
||||
}
|
||||
|
||||
assert_eq!(get_header(1024), "1K-blocks");
|
||||
|
@ -365,6 +476,94 @@ fn test_block_size_1024() {
|
|||
assert_eq!(get_header(2 * 1024 * 1024), "2M-blocks");
|
||||
assert_eq!(get_header(1024 * 1024 * 1024), "1G-blocks");
|
||||
assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks");
|
||||
|
||||
// multiples of both 1024 and 1000
|
||||
assert_eq!(get_header(128_000), "128kB-blocks");
|
||||
assert_eq!(get_header(1000 * 1024), "1.1MB-blocks");
|
||||
assert_eq!(get_header(1_000_000_000_000), "1TB-blocks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_size_with_suffix() {
|
||||
fn get_header(block_size: &str) -> String {
|
||||
let output = new_ucmd!()
|
||||
.args(&["-B", block_size, "--output=size"])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
output.lines().next().unwrap().to_string()
|
||||
}
|
||||
|
||||
assert_eq!(get_header("K"), "1K-blocks");
|
||||
assert_eq!(get_header("M"), "1M-blocks");
|
||||
assert_eq!(get_header("G"), "1G-blocks");
|
||||
assert_eq!(get_header("1K"), "1K-blocks");
|
||||
assert_eq!(get_header("1M"), "1M-blocks");
|
||||
assert_eq!(get_header("1G"), "1G-blocks");
|
||||
assert_eq!(get_header("1KiB"), "1K-blocks");
|
||||
assert_eq!(get_header("1MiB"), "1M-blocks");
|
||||
assert_eq!(get_header("1GiB"), "1G-blocks");
|
||||
assert_eq!(get_header("1KB"), "1kB-blocks");
|
||||
assert_eq!(get_header("1MB"), "1MB-blocks");
|
||||
assert_eq!(get_header("1GB"), "1GB-blocks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_size_in_posix_portability_mode() {
|
||||
fn get_header(block_size: &str) -> String {
|
||||
let output = new_ucmd!()
|
||||
.args(&["-P", "-B", block_size])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
output
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
assert_eq!(get_header("1024"), "1024-blocks");
|
||||
assert_eq!(get_header("1K"), "1024-blocks");
|
||||
assert_eq!(get_header("1KB"), "1000-blocks");
|
||||
assert_eq!(get_header("1M"), "1048576-blocks");
|
||||
assert_eq!(get_header("1MB"), "1000000-blocks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_too_large_block_size() {
|
||||
fn run_command(size: &str) {
|
||||
new_ucmd!()
|
||||
.arg(format!("--block-size={}", size))
|
||||
.fails()
|
||||
.stderr_contains(format!("--block-size argument '{}' too large", size));
|
||||
}
|
||||
|
||||
let too_large_sizes = vec!["1Y", "1Z"];
|
||||
|
||||
for size in too_large_sizes {
|
||||
run_command(size);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_block_size() {
|
||||
new_ucmd!()
|
||||
.arg("--block-size=x")
|
||||
.fails()
|
||||
.stderr_contains("invalid --block-size argument 'x'");
|
||||
|
||||
new_ucmd!()
|
||||
.arg("--block-size=0")
|
||||
.fails()
|
||||
.stderr_contains("invalid --block-size argument '0'");
|
||||
|
||||
new_ucmd!()
|
||||
.arg("--block-size=0K")
|
||||
.fails()
|
||||
.stderr_contains("invalid --block-size argument '0K'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -373,7 +572,7 @@ fn test_output_selects_columns() {
|
|||
.args(&["--output=source"])
|
||||
.succeeds()
|
||||
.stdout_move_str();
|
||||
assert_eq!(output.lines().next().unwrap(), "Filesystem");
|
||||
assert_eq!(output.lines().next().unwrap().trim_end(), "Filesystem");
|
||||
|
||||
let output = new_ucmd!()
|
||||
.args(&["--output=source,target"])
|
||||
|
@ -432,7 +631,7 @@ fn test_output_file_all_filesystems() {
|
|||
let mut lines = output.lines();
|
||||
assert_eq!(lines.next().unwrap(), "File");
|
||||
for line in lines {
|
||||
assert_eq!(line, "-");
|
||||
assert_eq!(line, "- ");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,7 +650,7 @@ fn test_output_file_specific_files() {
|
|||
.succeeds()
|
||||
.stdout_move_str();
|
||||
let actual: Vec<&str> = output.lines().collect();
|
||||
assert_eq!(actual, vec!["File", "a", "b", "c"]);
|
||||
assert_eq!(actual, vec!["File", "a ", "b ", "c "]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -486,5 +685,5 @@ fn test_nonexistent_file() {
|
|||
.args(&["--output=file", "does-not-exist", "."])
|
||||
.fails()
|
||||
.stderr_is("df: does-not-exist: No such file or directory\n")
|
||||
.stdout_is("File\n.\n");
|
||||
.stdout_is("File\n. \n");
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ fn test_default_output() {
|
|||
scene
|
||||
.ucmd()
|
||||
.succeeds()
|
||||
.stdout_does_not_match(&Regex::new("[rwx][^some-file1]").unwrap());
|
||||
.stdout_does_not_match(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -51,5 +51,5 @@ fn test_long_output() {
|
|||
.ucmd()
|
||||
.arg("-l")
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new("[rwx][^some-file1]").unwrap());
|
||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
||||
}
|
||||
|
|
|
@ -435,9 +435,7 @@ fn test_du_no_permission() {
|
|||
ts.ccmd("chmod").arg("-r").arg(SUB_DIR_LINKS).succeeds();
|
||||
|
||||
let result = ts.ucmd().arg(SUB_DIR_LINKS).fails();
|
||||
result.stderr_contains(
|
||||
"du: cannot read directory 'subdir/links': Permission denied (os error 13)",
|
||||
);
|
||||
result.stderr_contains("du: cannot read directory 'subdir/links': Permission denied");
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
{
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
use crate::common::util::*;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
@ -411,3 +413,86 @@ fn test_mktemp_directory_tmpdir() {
|
|||
result.no_stderr().stdout_contains("apt-key-gpghome.");
|
||||
assert!(PathBuf::from(result.stdout_str().trim()).is_dir());
|
||||
}
|
||||
|
||||
/// Decide whether a string matches a given template.
|
||||
///
|
||||
/// In the template, the character `'X'` is treated as a wildcard,
|
||||
/// that is, it matches anything. All other characters in `template`
|
||||
/// and `s` must match exactly.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # These all match.
|
||||
/// assert!(matches_template("abc", "abc"));
|
||||
/// assert!(matches_template("aXc", "abc"));
|
||||
/// assert!(matches_template("XXX", "abc"));
|
||||
///
|
||||
/// # None of these match
|
||||
/// assert!(matches_template("abc", "abcd"));
|
||||
/// assert!(matches_template("abc", "ab"));
|
||||
/// assert!(matches_template("aXc", "abd"));
|
||||
/// assert!(matches_template("XXX", "abcd"));
|
||||
/// ```
|
||||
///
|
||||
fn matches_template(template: &str, s: &str) -> bool {
|
||||
if template.len() != s.len() {
|
||||
return false;
|
||||
}
|
||||
for (a, b) in template.chars().zip(s.chars()) {
|
||||
if !(a == 'X' || a == b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// An assertion that uses [`matches_template`] and adds a helpful error message.
|
||||
macro_rules! assert_matches_template {
|
||||
($template:expr, $s:expr) => {{
|
||||
assert!(
|
||||
matches_template($template, $s),
|
||||
"\"{}\" != \"{}\"",
|
||||
$template,
|
||||
$s
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
/// Test that the file is created in the directory given by the template.
|
||||
#[test]
|
||||
fn test_respect_template() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let template = "XXX";
|
||||
let result = ucmd.arg(template).succeeds();
|
||||
let filename = result.no_stderr().stdout_str().trim_end();
|
||||
assert_matches_template!(template, filename);
|
||||
assert!(at.file_exists(filename));
|
||||
}
|
||||
|
||||
/// Test that the file is created in the directory given by the template.
|
||||
#[test]
|
||||
fn test_respect_template_directory() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir("d");
|
||||
#[cfg(not(windows))]
|
||||
let template = "d/XXX";
|
||||
#[cfg(windows)]
|
||||
let template = r"d\XXX";
|
||||
let result = ucmd.arg(template).succeeds();
|
||||
let filename = result.no_stderr().stdout_str().trim_end();
|
||||
assert_matches_template!(template, filename);
|
||||
assert!(at.file_exists(filename));
|
||||
}
|
||||
|
||||
/// Test that a template with a path separator is invalid.
|
||||
#[test]
|
||||
fn test_template_path_separator() {
|
||||
new_ucmd!()
|
||||
.args(&["-t", "a/bXXX"])
|
||||
.fails()
|
||||
.stderr_only(format!(
|
||||
"mktemp: invalid template, {}, contains directory separator\n",
|
||||
"a/bXXX".quote()
|
||||
));
|
||||
}
|
||||
|
|
|
@ -595,9 +595,9 @@ fn test_mv_update_option() {
|
|||
|
||||
at.touch(file_a);
|
||||
at.touch(file_b);
|
||||
let ts = time::now().to_timespec();
|
||||
let now = FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32);
|
||||
let later = FileTime::from_unix_time(ts.sec as i64 + 3600, ts.nsec as u32);
|
||||
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||
let now = FileTime::from_unix_time(ts.unix_timestamp(), ts.nanosecond());
|
||||
let later = FileTime::from_unix_time(ts.unix_timestamp() as i64 + 3600, ts.nanosecond() as u32);
|
||||
filetime::set_file_times(at.plus_as_string(file_a), now, now).unwrap();
|
||||
filetime::set_file_times(at.plus_as_string(file_b), now, later).unwrap();
|
||||
|
||||
|
|
|
@ -58,3 +58,8 @@ fn test_command_where_command_takes_n_flag() {
|
|||
.run()
|
||||
.stdout_is("a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_argument() {
|
||||
new_ucmd!().arg("--invalid").fails().code_is(125);
|
||||
}
|
||||
|
|
|
@ -446,6 +446,14 @@ fn sub_any_specifiers_after_period() {
|
|||
.stdout_only("3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unspecified_left_justify_is_1_width() {
|
||||
new_ucmd!()
|
||||
.args(&["%-o"]) //spell-checker:disable-line
|
||||
.succeeds()
|
||||
.stdout_only("0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_any_specifiers_after_second_param() {
|
||||
new_ucmd!()
|
||||
|
|
|
@ -71,3 +71,35 @@ fn gnu_ext_disabled_ignore_and_only_file() {
|
|||
.succeeds()
|
||||
.stdout_only_fixture("gnu_ext_disabled_ignore_and_only_file.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_ext_disabled_output_width_50() {
|
||||
new_ucmd!()
|
||||
.args(&["-G", "-w", "50", "input"])
|
||||
.succeeds()
|
||||
.stdout_only_fixture("gnu_ext_disabled_output_width_50.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_ext_disabled_output_width_70() {
|
||||
new_ucmd!()
|
||||
.args(&["-G", "-w", "70", "input"])
|
||||
.succeeds()
|
||||
.stdout_only_fixture("gnu_ext_disabled_output_width_70.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_ext_disabled_break_file() {
|
||||
new_ucmd!()
|
||||
.args(&["-G", "-b", "break_file", "input"])
|
||||
.succeeds()
|
||||
.stdout_only_fixture("gnu_ext_disabled_break_file.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gnu_ext_disabled_empty_word_regexp_ignores_break_file() {
|
||||
new_ucmd!()
|
||||
.args(&["-G", "-b", "break_file", "-R", "-W", "", "input"])
|
||||
.succeeds()
|
||||
.stdout_only_fixture("gnu_ext_disabled_rightward_no_ref.expected");
|
||||
}
|
||||
|
|
|
@ -283,6 +283,40 @@ fn test_char() {
|
|||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
|
||||
#[test]
|
||||
fn test_date() {
|
||||
// Just test the date for the time 0.3 change
|
||||
let args = [
|
||||
"-c",
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
"%z",
|
||||
#[cfg(target_os = "linux")]
|
||||
"/bin/sh",
|
||||
#[cfg(any(target_vendor = "apple"))]
|
||||
"%z",
|
||||
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
||||
"/bin/sh",
|
||||
];
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
// Just test the date for the time 0.3 change
|
||||
let args = [
|
||||
"-c",
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
"%z",
|
||||
#[cfg(target_os = "linux")]
|
||||
"/dev/ptmx",
|
||||
#[cfg(any(target_vendor = "apple"))]
|
||||
"%z",
|
||||
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
||||
"/dev/ptmx",
|
||||
];
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_multi_files() {
|
||||
|
@ -311,3 +345,75 @@ fn test_printf() {
|
|||
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_pipe_fifo() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkfifo("FIFO");
|
||||
ucmd.arg("FIFO")
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_contains("fifo")
|
||||
.stdout_contains("File: FIFO")
|
||||
.succeeded();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "android")))]
|
||||
fn test_stdin_pipe_fifo1() {
|
||||
// $ echo | stat -
|
||||
// File: -
|
||||
// Size: 0 Blocks: 0 IO Block: 4096 fifo
|
||||
new_ucmd!()
|
||||
.arg("-")
|
||||
.set_stdin(std::process::Stdio::piped())
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_contains("fifo")
|
||||
.stdout_contains("File: -")
|
||||
.succeeded();
|
||||
new_ucmd!()
|
||||
.args(&["-L", "-"])
|
||||
.set_stdin(std::process::Stdio::piped())
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_contains("fifo")
|
||||
.stdout_contains("File: -")
|
||||
.succeeded();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "android")))]
|
||||
fn test_stdin_pipe_fifo2() {
|
||||
// $ stat -
|
||||
// File: -
|
||||
// Size: 0 Blocks: 0 IO Block: 1024 character special file
|
||||
new_ucmd!()
|
||||
.arg("-")
|
||||
.set_stdin(std::process::Stdio::null())
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_contains("character special file")
|
||||
.stdout_contains("File: -")
|
||||
.succeeded();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(any(target_os = "android", target_os = "macos"))))]
|
||||
fn test_stdin_redirect() {
|
||||
// $ touch f && stat - < f
|
||||
// File: -
|
||||
// Size: 0 Blocks: 0 IO Block: 4096 regular empty file
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("f");
|
||||
ts.ucmd()
|
||||
.arg("-")
|
||||
.set_stdin(std::fs::File::open(at.plus("f")).unwrap())
|
||||
.run()
|
||||
.no_stderr()
|
||||
.stdout_contains("regular empty file")
|
||||
.stdout_contains("File: -")
|
||||
.succeeded();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms
|
||||
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime
|
||||
|
||||
// This test relies on
|
||||
// --cfg unsound_local_offset
|
||||
// https://github.com/time-rs/time/blob/deb8161b84f355b31e39ce09e40c4d6ce3fea837/src/sys/local_offset_at/unix.rs#L112-L120=
|
||||
// See https://github.com/time-rs/time/issues/293#issuecomment-946382614=
|
||||
// Defined in .cargo/config
|
||||
|
||||
extern crate touch;
|
||||
use self::touch::filetime::{self, FileTime};
|
||||
|
||||
extern crate time;
|
||||
use time::macros::{datetime, format_description};
|
||||
|
||||
use crate::common::util::*;
|
||||
use std::fs::remove_file;
|
||||
|
@ -32,11 +39,24 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) {
|
|||
|
||||
// Adjusts for local timezone
|
||||
fn str_to_filetime(format: &str, s: &str) -> FileTime {
|
||||
let mut tm = time::strptime(s, format).unwrap();
|
||||
tm.tm_utcoff = time::now().tm_utcoff;
|
||||
tm.tm_isdst = -1; // Unknown flag DST
|
||||
let ts = tm.to_timespec();
|
||||
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
|
||||
let format_description = match format {
|
||||
"%y%m%d%H%M" => format_description!("[year repr:last_two][month][day][hour][minute]"),
|
||||
"%y%m%d%H%M.%S" => {
|
||||
format_description!("[year repr:last_two][month][day][hour][minute].[second]")
|
||||
}
|
||||
"%Y%m%d%H%M" => format_description!("[year][month][day][hour][minute]"),
|
||||
"%Y%m%d%H%M.%S" => format_description!("[year][month][day][hour][minute].[second]"),
|
||||
_ => panic!("unexpected dt format"),
|
||||
};
|
||||
let tm = time::PrimitiveDateTime::parse(s, &format_description).unwrap();
|
||||
let d = match time::OffsetDateTime::now_local() {
|
||||
Ok(now) => now,
|
||||
Err(e) => {
|
||||
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
|
||||
}
|
||||
};
|
||||
let offset_dt = tm.assume_offset(d.offset());
|
||||
FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -83,7 +103,10 @@ fn test_touch_set_mdhm_time() {
|
|||
|
||||
let start_of_year = str_to_filetime(
|
||||
"%Y%m%d%H%M",
|
||||
&format!("{}01010000", 1900 + time::now().tm_year),
|
||||
&format!(
|
||||
"{}01010000",
|
||||
time::OffsetDateTime::now_local().unwrap().year()
|
||||
),
|
||||
);
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
|
@ -104,7 +127,7 @@ fn test_touch_set_mdhms_time() {
|
|||
|
||||
let start_of_year = str_to_filetime(
|
||||
"%Y%m%d%H%M.%S",
|
||||
&format!("{}01010000.00", 1900 + time::now().tm_year),
|
||||
&format!("{}01010000.00", time::OffsetDateTime::now_utc().year()),
|
||||
);
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
|
@ -123,7 +146,7 @@ fn test_touch_set_ymdhm_time() {
|
|||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let start_of_year = str_to_filetime("%y%m%d%H%M", "1501010000");
|
||||
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
|
||||
|
@ -141,7 +164,7 @@ fn test_touch_set_ymdhms_time() {
|
|||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let start_of_year = str_to_filetime("%y%m%d%H%M.%S", "1501010000.00");
|
||||
let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00");
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45296);
|
||||
|
@ -404,6 +427,86 @@ fn test_touch_set_date3() {
|
|||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date4() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "1970-01-01 18:43:33", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let expected = FileTime::from_unix_time(67413, 0);
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date5() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "1970-01-01 18:43:33.023456789", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
// Slightly different result on Windows for nano seconds
|
||||
// TODO: investigate
|
||||
#[cfg(windows)]
|
||||
let expected = FileTime::from_unix_time(67413, 23456700);
|
||||
#[cfg(not(windows))]
|
||||
let expected = FileTime::from_unix_time(67413, 23456789);
|
||||
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date6() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "2000-01-01 00:00", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let expected = FileTime::from_unix_time(946684800, 0);
|
||||
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date7() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_set_date";
|
||||
|
||||
ucmd.args(&["-d", "2004-01-16 12:00 +0000", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let expected = FileTime::from_unix_time(1074254400, 0);
|
||||
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime, expected);
|
||||
assert_eq!(mtime, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_set_date_wrong_format() {
|
||||
let (_at, mut ucmd) = at_and_ucmd!();
|
||||
|
@ -430,18 +533,18 @@ fn test_touch_mtime_dst_succeeds() {
|
|||
assert_eq!(target_time, mtime);
|
||||
}
|
||||
|
||||
// is_dst_switch_hour returns true if timespec ts is just before the switch
|
||||
// to Daylight Saving Time.
|
||||
// For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
|
||||
// for March 8 2020 01:00:00 AM
|
||||
// is just before the switch because on that day clock jumps by 1 hour,
|
||||
// so 1 minute after 01:59:00 is 03:00:00.
|
||||
fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
||||
let ts_after = ts + time::Duration::hours(1);
|
||||
let tm = time::at(ts);
|
||||
let tm_after = time::at(ts_after);
|
||||
tm_after.tm_hour == tm.tm_hour + 2
|
||||
}
|
||||
// // is_dst_switch_hour returns true if timespec ts is just before the switch
|
||||
// // to Daylight Saving Time.
|
||||
// // For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
|
||||
// // for March 8 2020 01:00:00 AM
|
||||
// // is just before the switch because on that day clock jumps by 1 hour,
|
||||
// // so 1 minute after 01:59:00 is 03:00:00.
|
||||
// fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
||||
// let ts_after = ts + time::Duration::hours(1);
|
||||
// let tm = time::at(ts);
|
||||
// let tm_after = time::at(ts_after);
|
||||
// tm_after.tm_hour == tm.tm_hour + 2
|
||||
// }
|
||||
|
||||
// get_dst_switch_hour returns date string for which touch -m -t fails.
|
||||
// For example, in EST (UTC-5), that will be "202003080200" so
|
||||
|
@ -450,23 +553,30 @@ fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
|||
// In other locales it will be a different date/time, and in some locales
|
||||
// it doesn't exist at all, in which case this function will return None.
|
||||
fn get_dst_switch_hour() -> Option<String> {
|
||||
let now = time::now();
|
||||
// Start from January 1, 2020, 00:00.
|
||||
let mut tm = time::strptime("20200101-0000", "%Y%m%d-%H%M").unwrap();
|
||||
tm.tm_isdst = -1;
|
||||
tm.tm_utcoff = now.tm_utcoff;
|
||||
let mut ts = tm.to_timespec();
|
||||
// Loop through all hours in year 2020 until we find the hour just
|
||||
// before the switch to DST.
|
||||
for _i in 0..(366 * 24) {
|
||||
if is_dst_switch_hour(ts) {
|
||||
let mut tm = time::at(ts);
|
||||
tm.tm_hour += 1;
|
||||
let s = time::strftime("%Y%m%d%H%M", &tm).unwrap();
|
||||
return Some(s);
|
||||
//let now = time::OffsetDateTime::now_local().unwrap();
|
||||
let now = match time::OffsetDateTime::now_local() {
|
||||
Ok(now) => now,
|
||||
Err(e) => {
|
||||
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
|
||||
}
|
||||
ts = ts + time::Duration::hours(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Start from January 1, 2020, 00:00.
|
||||
let tm = datetime!(2020-01-01 00:00 UTC);
|
||||
tm.to_offset(now.offset());
|
||||
|
||||
// let mut ts = tm.to_timespec();
|
||||
// // Loop through all hours in year 2020 until we find the hour just
|
||||
// // before the switch to DST.
|
||||
// for _i in 0..(366 * 24) {
|
||||
// // if is_dst_switch_hour(ts) {
|
||||
// // let mut tm = time::at(ts);
|
||||
// // tm.tm_hour += 1;
|
||||
// // let s = time::strftime("%Y%m%d%H%M", &tm).unwrap();
|
||||
// // return Some(s);
|
||||
// // }
|
||||
// ts = ts + time::Duration::hours(1);
|
||||
// }
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -573,3 +683,21 @@ fn test_no_dereference_no_file() {
|
|||
.stderr_contains("setting times of 'not-a-file-1': No such file or directory")
|
||||
.stderr_contains("setting times of 'not-a-file-2': No such file or directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_leap_second() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_touch_leap_sec";
|
||||
|
||||
ucmd.args(&["-t", "197001010000.60", file])
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
|
||||
let epoch = str_to_filetime("%Y%m%d%H%M", "197001010000");
|
||||
let (atime, mtime) = get_file_times(&at, file);
|
||||
assert_eq!(atime, mtime);
|
||||
assert_eq!(atime.unix_seconds() - epoch.unix_seconds(), 60);
|
||||
assert_eq!(mtime.unix_seconds() - epoch.unix_seconds(), 60);
|
||||
}
|
||||
|
|
|
@ -680,6 +680,9 @@ fn gnu_tests() {
|
|||
stderr: None,
|
||||
exit: None,
|
||||
},
|
||||
/*
|
||||
Disable as it fails too often. See:
|
||||
https://github.com/uutils/coreutils/issues/3509
|
||||
TestCase {
|
||||
name: "112",
|
||||
args: &["-D", "-c"],
|
||||
|
@ -687,7 +690,7 @@ fn gnu_tests() {
|
|||
stdout: Some(""),
|
||||
stderr: Some("uniq: printing all duplicated lines and repeat counts is meaningless"),
|
||||
exit: Some(1),
|
||||
},
|
||||
},*/
|
||||
TestCase {
|
||||
name: "113",
|
||||
args: &["--all-repeated=separate"],
|
||||
|
|
|
@ -31,7 +31,7 @@ fn test_default_output() {
|
|||
scene
|
||||
.ucmd()
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new("[rwx][^some-file1]").unwrap());
|
||||
.stdout_matches(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -51,5 +51,5 @@ fn test_column_output() {
|
|||
.ucmd()
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_does_not_match(&Regex::new("[rwx][^some-file1]").unwrap());
|
||||
.stdout_does_not_match(&Regex::new("[rwx-]{10}.*some-file1$").unwrap());
|
||||
}
|
||||
|
|
|
@ -1362,6 +1362,70 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
|
|||
))
|
||||
}
|
||||
|
||||
/// This is a convenience wrapper to run a ucmd with root permissions.
|
||||
/// It can be used to test programs when being root is needed
|
||||
/// This runs 'sudo -E --non-interactive target/debug/coreutils util_name args`
|
||||
/// This is primarily designed to run in an environment where whoami is in $path
|
||||
/// and where non-interactive sudo is possible.
|
||||
/// To check if i) non-interactive sudo is possible and ii) if sudo works, this runs:
|
||||
/// 'sudo -E --non-interactive whoami' first.
|
||||
///
|
||||
/// This return an `Err()` if run inside CICD because there's no 'sudo'.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// ```no_run
|
||||
/// use crate::common::util::*;
|
||||
/// #[test]
|
||||
/// fn test_xyz() {
|
||||
/// let ts = TestScenario::new("whoami");
|
||||
/// let expected = "root\n".to_string();
|
||||
/// if let Ok(result) = run_ucmd_as_root(&ts, &[]) {
|
||||
/// result.stdout_is(expected);
|
||||
/// } else {
|
||||
/// println!("TEST SKIPPED");
|
||||
/// }
|
||||
/// }
|
||||
///```
|
||||
#[cfg(unix)]
|
||||
pub fn run_ucmd_as_root(
|
||||
ts: &TestScenario,
|
||||
args: &[&str],
|
||||
) -> std::result::Result<CmdResult, String> {
|
||||
if !is_ci() {
|
||||
// check if we can run 'sudo'
|
||||
log_info("run", "sudo -E --non-interactive whoami");
|
||||
match Command::new("sudo")
|
||||
.env("LC_ALL", "C")
|
||||
.args(&["-E", "--non-interactive", "whoami"])
|
||||
.output()
|
||||
{
|
||||
Ok(output) if String::from_utf8_lossy(&output.stdout).eq("root\n") => {
|
||||
// we can run sudo and we're root
|
||||
// run ucmd as root:
|
||||
Ok(ts
|
||||
.cmd_keepenv("sudo")
|
||||
.env("LC_ALL", "C")
|
||||
.arg("-E")
|
||||
.arg("--non-interactive")
|
||||
.arg(&ts.bin_path)
|
||||
.arg(&ts.util_name)
|
||||
.args(args)
|
||||
.run())
|
||||
}
|
||||
Ok(output)
|
||||
if String::from_utf8_lossy(&output.stderr).eq("sudo: a password is required\n") =>
|
||||
{
|
||||
Err("Cannot run non-interactive sudo".to_string())
|
||||
}
|
||||
Ok(_output) => Err("\"sudo whoami\" didn't return \"root\"".to_string()),
|
||||
Err(e) => Err(format!("{}: {}", UUTILS_WARNING, e)),
|
||||
}
|
||||
} else {
|
||||
Err(format!("{}: {}", UUTILS_INFO, "cannot run inside CI"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanity checks for test utils
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -1712,4 +1776,32 @@ mod tests {
|
|||
std::assert_eq!(host_name_for("gwho"), "gwho");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "whoami")]
|
||||
fn test_run_ucmd_as_root() {
|
||||
if !is_ci() {
|
||||
// Skip test if we can't guarantee non-interactive `sudo`, or if we're not "root"
|
||||
if let Ok(output) = Command::new("sudo")
|
||||
.env("LC_ALL", "C")
|
||||
.args(&["-E", "--non-interactive", "whoami"])
|
||||
.output()
|
||||
{
|
||||
if output.status.success() && String::from_utf8_lossy(&output.stdout).eq("root\n") {
|
||||
let ts = TestScenario::new("whoami");
|
||||
std::assert_eq!(
|
||||
run_ucmd_as_root(&ts, &[]).unwrap().stdout_str().trim(),
|
||||
"root"
|
||||
);
|
||||
} else {
|
||||
println!("TEST SKIPPED (we're not root)");
|
||||
}
|
||||
} else {
|
||||
println!("TEST SKIPPED (cannot run sudo)");
|
||||
}
|
||||
} else {
|
||||
println!("TEST SKIPPED (cannot run inside CI)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1
tests/fixtures/ptx/break_file
vendored
Normal file
1
tests/fixtures/ptx/break_file
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
abc_e^-]\
|
42
tests/fixtures/ptx/gnu_ext_disabled_break_file.expected
vendored
Normal file
42
tests/fixtures/ptx/gnu_ext_disabled_break_file.expected
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
.xx "" "" """quotes"", for roff" ""
|
||||
.xx "" "and some other like %a, b" "#, c$c" ""
|
||||
.xx "" "and some other like %a, b#, c" "$c" ""
|
||||
.xx "" "and some other like" "%a, b#, c$c" ""
|
||||
.xx "" "and some other like %a" ", b#, c$c" ""
|
||||
.xx "" """quotes""," "for roff" ""
|
||||
.xx "" "{brackets}" "for tex" ""
|
||||
.xx "" "" "hello world!" ""
|
||||
.xx "" "let's c" "heck special characters:" ""
|
||||
.xx "" "let's check special c" "haracters:" ""
|
||||
.xx "" "let's check spec" "ial characters:" ""
|
||||
.xx "" "let's chec" "k special characters:" ""
|
||||
.xx "" "{brac" "kets} for tex" ""
|
||||
.xx "" "oh, and bac" "k\slash" ""
|
||||
.xx "" "" "let's check special characters:" ""
|
||||
.xx "" "let's check specia" "l characters:" ""
|
||||
.xx "" "and some other" "like %a, b#, c$c" ""
|
||||
.xx "" "he" "llo world!" ""
|
||||
.xx "" "maybe a" "lso~or^" ""
|
||||
.xx "" "" "maybe also~or^" ""
|
||||
.xx "" "a" "nd some other like %a, b#, c$c" ""
|
||||
.xx "" "oh, a" "nd back\slash" ""
|
||||
.xx "" "" "oh, and back\slash" ""
|
||||
.xx "" "and some" "other like %a, b#, c$c" ""
|
||||
.xx "" "let's check special cha" "racters:" ""
|
||||
.xx "" "{b" "rackets} for tex" ""
|
||||
.xx "" "and some othe" "r like %a, b#, c$c" ""
|
||||
.xx "" """quotes"", for" "roff" ""
|
||||
.xx "" "let's check special characte" "rs:" ""
|
||||
.xx "" """quote" "s"", for roff" ""
|
||||
.xx "" "oh, and back\sla" "sh" ""
|
||||
.xx "" "oh, and back\" "slash" ""
|
||||
.xx "" "and" "some other like %a, b#, c$c" ""
|
||||
.xx "" "let's check" "special characters:" ""
|
||||
.xx "" "let's check special charac" "ters:" ""
|
||||
.xx "" "{brackets} for" "tex" ""
|
||||
.xx "" "le" "t's check special characters:" ""
|
||||
.xx "" "{bracke" "ts} for tex" ""
|
||||
.xx "" "hello" "world!" ""
|
||||
.xx "" "{brackets} for te" "x" ""
|
||||
.xx "" "ma" "ybe also~or^" ""
|
||||
.xx "" "" "{brackets} for tex" ""
|
24
tests/fixtures/ptx/gnu_ext_disabled_output_width_50.expected
vendored
Normal file
24
tests/fixtures/ptx/gnu_ext_disabled_output_width_50.expected
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
.xx "" "" """quotes"", for roff" ""
|
||||
.xx "" "and some other like" "%a, b#, c$c" ""
|
||||
.xx "" "maybe" "also~or^" ""
|
||||
.xx "%a, b#, c$c" "" "and some other like" ""
|
||||
.xx "" "oh," "and back\slash" ""
|
||||
.xx "" "some other like %a," "b#, c$c" "and"
|
||||
.xx "" "oh, and" "back\slash" ""
|
||||
.xx "" "other like %a, b#," "c$c" "and some"
|
||||
.xx "" "let's check special" "characters:" ""
|
||||
.xx "characters:" "let's" "check special" ""
|
||||
.xx "" """quotes""," "for roff" ""
|
||||
.xx "" "{brackets}" "for tex" ""
|
||||
.xx "" "" "hello world!" ""
|
||||
.xx "characters:" "" "let's check special" ""
|
||||
.xx "" "and some other" "like %a, b#, c$c" ""
|
||||
.xx "" "" "maybe also~or^" ""
|
||||
.xx "" "" "oh, and back\slash" ""
|
||||
.xx "" "and some" "other like %a, b#, c$c" ""
|
||||
.xx "" """quotes"", for" "roff" ""
|
||||
.xx "b#, c$c" "and" "some other like %a," ""
|
||||
.xx "" "let's check" "special characters:" ""
|
||||
.xx "" "{brackets} for" "tex" ""
|
||||
.xx "" "hello" "world!" ""
|
||||
.xx "" "" "{brackets} for tex" ""
|
24
tests/fixtures/ptx/gnu_ext_disabled_output_width_70.expected
vendored
Normal file
24
tests/fixtures/ptx/gnu_ext_disabled_output_width_70.expected
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
.xx "" "" """quotes"", for roff" ""
|
||||
.xx "" "and some other like" "%a, b#, c$c" ""
|
||||
.xx "" "maybe" "also~or^" ""
|
||||
.xx "" "" "and some other like %a, b#, c$c" ""
|
||||
.xx "" "oh," "and back\slash" ""
|
||||
.xx "" "and some other like %a," "b#, c$c" ""
|
||||
.xx "" "oh, and" "back\slash" ""
|
||||
.xx "" "and some other like %a, b#," "c$c" ""
|
||||
.xx "" "let's check special" "characters:" ""
|
||||
.xx "" "let's" "check special characters:" ""
|
||||
.xx "" """quotes""," "for roff" ""
|
||||
.xx "" "{brackets}" "for tex" ""
|
||||
.xx "" "" "hello world!" ""
|
||||
.xx "" "" "let's check special characters:" ""
|
||||
.xx "" "and some other" "like %a, b#, c$c" ""
|
||||
.xx "" "" "maybe also~or^" ""
|
||||
.xx "" "" "oh, and back\slash" ""
|
||||
.xx "" "and some" "other like %a, b#, c$c" ""
|
||||
.xx "" """quotes"", for" "roff" ""
|
||||
.xx "" "and" "some other like %a, b#, c$c" ""
|
||||
.xx "" "let's check" "special characters:" ""
|
||||
.xx "" "{brackets} for" "tex" ""
|
||||
.xx "" "hello" "world!" ""
|
||||
.xx "" "" "{brackets} for tex" ""
|
Loading…
Add table
Add a link
Reference in a new issue