mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
5807760aa2
* du: honor LC_NUMERIC for decimal separator Route the fractional digit in `uucore::format::human::format_prefixed` through `locale_decimal_separator()`, the same helper #11941 added for `numfmt`. Fixes #11956. * du: spell-check: ignore `replacen` Matches the directive in `src/uu/numfmt/src/format.rs` for the same helper introduced by #11941.
2533 lines
68 KiB
Rust
2533 lines
68 KiB
Rust
// 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.
|
|
|
|
// spell-checker:ignore (paths) atim sublink subwords azerty azeaze xcwww azeaz amaz azea qzerty tazerty tsublink testfile1 testfile2 filelist fpath testdir testfile
|
|
// spell-checker:ignore selfref ELOOP smallfile
|
|
|
|
#[cfg(not(windows))]
|
|
use regex::Regex;
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
use uutests::unwrap_or_return;
|
|
use uutests::util::TestScenario;
|
|
#[cfg(not(target_os = "windows"))]
|
|
use uutests::util::expected_result;
|
|
use uutests::{at_and_ucmd, new_ucmd, util_name};
|
|
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
const SUB_DIR: &str = "subdir/deeper";
|
|
const SUB_DEEPER_DIR: &str = "subdir/deeper/deeper_dir";
|
|
const SUB_DIR_LINKS: &str = "subdir/links";
|
|
const SUB_DIR_LINKS_DEEPER_SYM_DIR: &str = "subdir/links/deeper_dir";
|
|
#[cfg(all(not(target_os = "android"), not(target_os = "openbsd")))]
|
|
const SUB_FILE: &str = "subdir/links/subwords.txt";
|
|
#[cfg(all(not(target_os = "android"), not(target_os = "openbsd")))]
|
|
const SUB_LINK: &str = "subdir/links/sublink.txt";
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_basics() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
let result = ts.ucmd().succeeds();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &[]));
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
du_basics(result.stdout_str());
|
|
}
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
fn du_basics(s: &str) {
|
|
let answer = concat!(
|
|
"4\t./subdir/deeper/deeper_dir\n",
|
|
"8\t./subdir/deeper\n",
|
|
"12\t./subdir/links\n",
|
|
"20\t./subdir\n",
|
|
"24\t.\n"
|
|
);
|
|
assert_eq!(s, answer);
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn du_basics(s: &str) {
|
|
let answer = concat!(
|
|
"0\t.\\subdir\\deeper\\deeper_dir\n",
|
|
"0\t.\\subdir\\deeper\n",
|
|
"8\t.\\subdir\\links\n",
|
|
"8\t.\\subdir\n",
|
|
"8\t.\n"
|
|
);
|
|
assert_eq!(s, answer);
|
|
}
|
|
|
|
#[cfg(all(
|
|
not(target_vendor = "apple"),
|
|
not(target_os = "windows"),
|
|
not(target_os = "openbsd")
|
|
))]
|
|
fn du_basics(s: &str) {
|
|
let answer = concat!(
|
|
"8\t./subdir/deeper/deeper_dir\n",
|
|
"16\t./subdir/deeper\n",
|
|
"16\t./subdir/links\n",
|
|
"36\t./subdir\n",
|
|
"44\t.\n"
|
|
);
|
|
assert_eq!(s, answer);
|
|
}
|
|
|
|
#[test]
|
|
fn test_all_summarize() {
|
|
new_ucmd!().arg("-a").arg("-s").fails_with_code(1); //clap provided message
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_arg() {
|
|
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_basics_subdir() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
let result = ts.ucmd().arg(SUB_DIR).succeeds();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR]));
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
du_basics_subdir(result.stdout_str());
|
|
}
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
fn du_basics_subdir(s: &str) {
|
|
assert_eq!(s, "4\tsubdir/deeper/deeper_dir\n8\tsubdir/deeper\n");
|
|
}
|
|
#[cfg(target_os = "windows")]
|
|
fn du_basics_subdir(s: &str) {
|
|
assert_eq!(s, "0\tsubdir/deeper\\deeper_dir\n0\tsubdir/deeper\n");
|
|
}
|
|
#[cfg(target_os = "freebsd")]
|
|
fn du_basics_subdir(s: &str) {
|
|
assert_eq!(s, "8\tsubdir/deeper/deeper_dir\n16\tsubdir/deeper\n");
|
|
}
|
|
#[cfg(all(
|
|
not(target_vendor = "apple"),
|
|
not(target_os = "windows"),
|
|
not(target_os = "freebsd"),
|
|
not(target_os = "openbsd")
|
|
))]
|
|
fn du_basics_subdir(s: &str) {
|
|
// MS-WSL linux has altered expected output
|
|
if uucore::os::is_wsl_1() {
|
|
assert_eq!(s, "0\tsubdir/deeper\n");
|
|
} else {
|
|
assert_eq!(s, "8\tsubdir/deeper\n");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_invalid_size() {
|
|
let args = &["block-size", "threshold"];
|
|
let ts = TestScenario::new(util_name!());
|
|
for s in args {
|
|
ts.ucmd()
|
|
.arg(format!("--{s}=1fb4t"))
|
|
.arg("/tmp")
|
|
.fails_with_code(1)
|
|
.stderr_only(format!("du: invalid suffix in --{s} argument '1fb4t'\n"));
|
|
ts.ucmd()
|
|
.arg(format!("--{s}=x"))
|
|
.arg("/tmp")
|
|
.fails_with_code(1)
|
|
.stderr_only(format!("du: invalid --{s} argument 'x'\n"));
|
|
ts.ucmd()
|
|
.arg(format!("--{s}=1Y"))
|
|
.arg("/tmp")
|
|
.fails_with_code(1)
|
|
.stderr_only(format!("du: --{s} argument '1Y' too large\n"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_with_posixly_correct() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
at.write(&format!("{dir}/file"), "some content");
|
|
|
|
let expected = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=512")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("POSIXLY_CORRECT", "1")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(expected, result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_zero_env_block_size() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
at.write(&format!("{dir}/file"), "some content");
|
|
|
|
let expected = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=1024")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("DU_BLOCK_SIZE", "0")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(expected, result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_zero_env_block_size_hierarchy() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
at.write(&format!("{dir}/file"), "some content");
|
|
|
|
let expected = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=1024")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result1 = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("BLOCK_SIZE", "1")
|
|
.env("DU_BLOCK_SIZE", "0")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result2 = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("BLOCK_SIZE", "1")
|
|
.env("BLOCKSIZE", "1")
|
|
.env("DU_BLOCK_SIZE", "0")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(expected, result1);
|
|
assert_eq!(expected, result2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_env_block_size_hierarchy() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
at.write(&format!("{dir}/file"), "some content");
|
|
|
|
let expected = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=1")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result1 = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("BLOCK_SIZE", "0")
|
|
.env("DU_BLOCK_SIZE", "1")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result2 = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("BLOCK_SIZE", "1")
|
|
.env("BLOCKSIZE", "0")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(expected, result1);
|
|
assert_eq!(expected, result2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_binary_block_size() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
let fpath = at.plus(format!("{dir}/file"));
|
|
std::fs::File::create(&fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000)
|
|
.expect("cannot set file size");
|
|
|
|
let test_cases = [
|
|
("0b1", "1"),
|
|
("0b10100", "20"),
|
|
("0b1000000000", "512"),
|
|
("0b10K", "2K"),
|
|
];
|
|
|
|
for (binary, decimal) in test_cases {
|
|
let decimal = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg(format!("--block-size={decimal}"))
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let binary = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg(format!("--block-size={binary}"))
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(
|
|
decimal, binary,
|
|
"Binary {binary} should equal decimal {decimal}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_binary_env_block_size() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
let fpath = at.plus(format!("{dir}/file"));
|
|
std::fs::File::create(&fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000)
|
|
.expect("cannot set file size");
|
|
|
|
let expected = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=1024")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("DU_BLOCK_SIZE", "0b10000000000")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(expected, result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_invalid_binary_size() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
ts.ucmd()
|
|
.arg("--block-size=0b123")
|
|
.arg("/tmp")
|
|
.fails_with_code(1)
|
|
.stderr_only("du: invalid suffix in --block-size argument '0b123'\n");
|
|
|
|
ts.ucmd()
|
|
.arg("--threshold=0b123")
|
|
.arg("/tmp")
|
|
.fails_with_code(1)
|
|
.stderr_only("du: invalid suffix in --threshold argument '0b123'\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_invalid_env_block_size_stops_lookup() {
|
|
// When DU_BLOCK_SIZE is set but invalid, it should stop the lookup
|
|
// and use the default block size — not fall through to BLOCK_SIZE.
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
at.write(&format!("{dir}/file"), "some content");
|
|
|
|
let default_output = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=1024")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
// Invalid DU_BLOCK_SIZE should use default (1024), not BLOCK_SIZE=1
|
|
let result = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("DU_BLOCK_SIZE", "invalid")
|
|
.env("BLOCK_SIZE", "1")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(default_output, result);
|
|
|
|
// Empty DU_BLOCK_SIZE should also use default
|
|
let result = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("DU_BLOCK_SIZE", "")
|
|
.env("BLOCK_SIZE", "1")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(default_output, result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_posixly_correct_default() {
|
|
// With POSIXLY_CORRECT and no block size env vars, default is 512
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a";
|
|
|
|
at.mkdir(dir);
|
|
at.write(&format!("{dir}/file"), "some content");
|
|
|
|
let expected = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.arg("--block-size=512")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.env("POSIXLY_CORRECT", "1")
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(expected, result);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_binary_edge_cases() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
at.write("foo", "test");
|
|
|
|
ts.ucmd()
|
|
.arg("-B0b")
|
|
.arg("foo")
|
|
.fails()
|
|
.stderr_only("du: invalid --block-size argument '0b'\n");
|
|
|
|
ts.ucmd()
|
|
.arg("-B0B")
|
|
.arg("foo")
|
|
.fails()
|
|
.stderr_only("du: invalid suffix in --block-size argument '0B'\n");
|
|
|
|
ts.ucmd()
|
|
.arg("--block-size=0b1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
|
|
.arg("foo")
|
|
.fails_with_code(1)
|
|
.stderr_contains("too large");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_non_existing_files() {
|
|
new_ucmd!()
|
|
.arg("non_existing_a")
|
|
.arg("non_existing_b")
|
|
.fails()
|
|
.stderr_only(concat!(
|
|
"du: cannot access 'non_existing_a': No such file or directory\n",
|
|
"du: cannot access 'non_existing_b': No such file or directory\n"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_soft_link() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
// Create the directory and file structure explicitly for this test
|
|
at.mkdir_all("subdir/links");
|
|
at.write("subdir/links/subwords.txt", &"hello world\n".repeat(100));
|
|
at.symlink_file("subdir/links/subwords.txt", "subdir/links/sublink.txt");
|
|
|
|
let result = ts.ucmd().arg("subdir/links").succeeds();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &["subdir/links"]));
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
let s = result.stdout_str();
|
|
println!("Output: {s}");
|
|
|
|
// Helper closure to assert output matches one of the valid sizes
|
|
#[cfg(any(target_vendor = "apple", target_os = "windows", target_os = "freebsd"))]
|
|
let assert_valid_size = |output: &str, valid_sizes: &[&str]| {
|
|
assert!(
|
|
valid_sizes.contains(&output),
|
|
"Expected one of {valid_sizes:?}, got {output}"
|
|
);
|
|
};
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
{
|
|
// 'macos' host variants may have `du` output variation for soft links
|
|
let valid_sizes = [
|
|
"8\tsubdir/links\n",
|
|
"12\tsubdir/links\n",
|
|
"16\tsubdir/links\n",
|
|
];
|
|
assert_valid_size(s, &valid_sizes);
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
let valid_sizes = ["4\tsubdir/links\n", "8\tsubdir/links\n"];
|
|
assert_valid_size(s, &valid_sizes);
|
|
}
|
|
|
|
#[cfg(target_os = "freebsd")]
|
|
{
|
|
// FreeBSD may have different block allocations depending on filesystem
|
|
// Accept both common sizes
|
|
let valid_sizes = ["12\tsubdir/links\n", "16\tsubdir/links\n"];
|
|
assert_valid_size(s, &valid_sizes);
|
|
}
|
|
|
|
#[cfg(all(
|
|
not(target_vendor = "apple"),
|
|
not(target_os = "windows"),
|
|
not(target_os = "freebsd")
|
|
))]
|
|
{
|
|
// MS-WSL linux has altered expected output
|
|
if uucore::os::is_wsl_1() {
|
|
assert_eq!(s, "8\tsubdir/links\n");
|
|
} else {
|
|
assert_eq!(s, "16\tsubdir/links\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(all(not(target_os = "android"), not(target_os = "openbsd")))]
|
|
#[test]
|
|
fn test_du_hard_link() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.hard_link(SUB_FILE, SUB_LINK);
|
|
|
|
let result = ts.ucmd().arg(SUB_DIR_LINKS).succeeds();
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR_LINKS]));
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
// We do not double count hard links as the inodes are identical
|
|
du_hard_link(result.stdout_str());
|
|
}
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
fn du_hard_link(s: &str) {
|
|
assert_eq!(s, "12\tsubdir/links\n");
|
|
}
|
|
#[cfg(target_os = "windows")]
|
|
fn du_hard_link(s: &str) {
|
|
assert_eq!(s, "8\tsubdir/links\n");
|
|
}
|
|
#[cfg(target_os = "freebsd")]
|
|
fn du_hard_link(s: &str) {
|
|
assert_eq!(s, "16\tsubdir/links\n");
|
|
}
|
|
#[cfg(all(
|
|
not(target_vendor = "apple"),
|
|
not(target_os = "windows"),
|
|
not(target_os = "freebsd"),
|
|
not(target_os = "openbsd"),
|
|
not(target_os = "android")
|
|
))]
|
|
fn du_hard_link(s: &str) {
|
|
// MS-WSL linux has altered expected output
|
|
if uucore::os::is_wsl_1() {
|
|
assert_eq!(s, "8\tsubdir/links\n");
|
|
} else {
|
|
assert_eq!(s, "16\tsubdir/links\n");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_d_flag() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
let result = ts.ucmd().arg("-d1").succeeds();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &["-d1"]));
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
du_d_flag(result.stdout_str());
|
|
}
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
fn du_d_flag(s: &str) {
|
|
assert_eq!(s, "20\t./subdir\n24\t.\n");
|
|
}
|
|
#[cfg(target_os = "windows")]
|
|
fn du_d_flag(s: &str) {
|
|
assert_eq!(s, "8\t.\\subdir\n8\t.\n");
|
|
}
|
|
#[cfg(target_os = "freebsd")]
|
|
fn du_d_flag(s: &str) {
|
|
assert_eq!(s, "36\t./subdir\n44\t.\n");
|
|
}
|
|
#[cfg(all(
|
|
not(target_vendor = "apple"),
|
|
not(target_os = "windows"),
|
|
not(target_os = "freebsd"),
|
|
not(target_os = "openbsd")
|
|
))]
|
|
fn du_d_flag(s: &str) {
|
|
// MS-WSL linux has altered expected output
|
|
if uucore::os::is_wsl_1() {
|
|
assert_eq!(s, "8\t./subdir\n8\t.\n");
|
|
} else {
|
|
assert_eq!(s, "28\t./subdir\n36\t.\n");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_dereference() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.symlink_dir(SUB_DEEPER_DIR, SUB_DIR_LINKS_DEEPER_SYM_DIR);
|
|
|
|
let result = ts.ucmd().arg("-L").arg(SUB_DIR_LINKS).succeeds();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &["-L", SUB_DIR_LINKS]));
|
|
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
du_dereference(result.stdout_str());
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
#[test]
|
|
fn test_du_dereference_args() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir");
|
|
at.write("dir/file-ignore1", "azeaze");
|
|
at.write("dir/file-ignore2", "amaz?ng");
|
|
at.symlink_dir("dir", "sublink");
|
|
|
|
for arg in ["-D", "-H", "--dereference-args"] {
|
|
let result = ts.ucmd().arg(arg).arg("-s").arg("sublink").succeeds();
|
|
let stdout = result.stdout_str();
|
|
|
|
assert!(!stdout.starts_with('0'));
|
|
assert!(stdout.contains("sublink"));
|
|
}
|
|
|
|
// Without the option
|
|
let result = ts.ucmd().arg("-s").arg("sublink").succeeds();
|
|
result.stdout_contains("0\tsublink\n");
|
|
}
|
|
|
|
#[cfg(target_vendor = "apple")]
|
|
fn du_dereference(s: &str) {
|
|
assert_eq!(s, "4\tsubdir/links/deeper_dir\n16\tsubdir/links\n");
|
|
}
|
|
#[cfg(target_os = "windows")]
|
|
fn du_dereference(s: &str) {
|
|
assert_eq!(s, "0\tsubdir/links\\deeper_dir\n8\tsubdir/links\n");
|
|
}
|
|
#[cfg(target_os = "freebsd")]
|
|
fn du_dereference(s: &str) {
|
|
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
|
|
}
|
|
#[cfg(all(
|
|
not(target_vendor = "apple"),
|
|
not(target_os = "windows"),
|
|
not(target_os = "freebsd"),
|
|
not(target_os = "openbsd")
|
|
))]
|
|
fn du_dereference(s: &str) {
|
|
// MS-WSL linux has altered expected output
|
|
if uucore::os::is_wsl_1() {
|
|
assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n");
|
|
} else {
|
|
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(
|
|
target_os = "windows",
|
|
target_os = "android",
|
|
target_os = "freebsd",
|
|
target_os = "openbsd"
|
|
)))]
|
|
#[test]
|
|
fn test_du_no_dereference() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "a_dir";
|
|
let symlink = "symlink";
|
|
|
|
at.mkdir(dir);
|
|
at.symlink_dir(dir, symlink);
|
|
|
|
for arg in ["-P", "--no-dereference"] {
|
|
ts.ucmd()
|
|
.arg(arg)
|
|
.succeeds()
|
|
.stdout_contains(dir)
|
|
.stdout_does_not_contain(symlink);
|
|
|
|
// ensure no-dereference "wins"
|
|
ts.ucmd()
|
|
.arg("--dereference")
|
|
.arg(arg)
|
|
.succeeds()
|
|
.stdout_contains(dir)
|
|
.stdout_does_not_contain(symlink);
|
|
|
|
// ensure dereference "wins"
|
|
let result = ts.ucmd().arg(arg).arg("--dereference").succeeds();
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &[arg, "--dereference"]));
|
|
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
result.stdout_contains(symlink).stdout_does_not_contain(dir);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_inodes_basic() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let result = ts.ucmd().arg("--inodes").succeeds();
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &["--inodes"]));
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
assert_eq!(
|
|
result.stdout_str(),
|
|
concat!(
|
|
"2\t.\\subdir\\deeper\\deeper_dir\n",
|
|
"4\t.\\subdir\\deeper\n",
|
|
"3\t.\\subdir\\links\n",
|
|
"8\t.\\subdir\n",
|
|
"11\t.\n",
|
|
)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_inodes() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
ts.ucmd()
|
|
.arg("--summarize")
|
|
.arg("--inodes")
|
|
.succeeds()
|
|
.stdout_only("11\t.\n");
|
|
|
|
let result = ts.ucmd().arg("--separate-dirs").arg("--inodes").succeeds();
|
|
|
|
#[cfg(target_os = "windows")]
|
|
result.stdout_contains("3\t.\\subdir\\links\n");
|
|
#[cfg(not(target_os = "windows"))]
|
|
result.stdout_contains("3\t./subdir/links\n");
|
|
result.stdout_contains("3\t.\n");
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference =
|
|
unwrap_or_return!(expected_result(&ts, &["--separate-dirs", "--inodes"]));
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "android"))]
|
|
#[test]
|
|
fn test_du_inodes_with_count_links() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir");
|
|
at.touch("dir/file");
|
|
at.hard_link("dir/file", "dir/hard_link_a");
|
|
at.hard_link("dir/file", "dir/hard_link_b");
|
|
|
|
// ensure the hard links are not counted without --count-links
|
|
ts.ucmd()
|
|
.arg("--inodes")
|
|
.arg("dir")
|
|
.succeeds()
|
|
.stdout_is("2\tdir\n");
|
|
|
|
for arg in ["-l", "--count-links"] {
|
|
ts.ucmd()
|
|
.arg("--inodes")
|
|
.arg(arg)
|
|
.arg("dir")
|
|
.succeeds()
|
|
.stdout_is("4\tdir\n");
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "android"))]
|
|
#[test]
|
|
fn test_du_inodes_with_count_links_all() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("d");
|
|
at.mkdir("d/d");
|
|
at.touch("d/f");
|
|
at.hard_link("d/f", "d/h");
|
|
|
|
let result = ts.ucmd().arg("--inodes").arg("-al").arg("d").succeeds();
|
|
result.no_stderr();
|
|
|
|
let mut result_seq: Vec<String> = result
|
|
.stdout_str()
|
|
.split('\n')
|
|
.filter(|x| !x.is_empty())
|
|
.map(|x| x.parse().unwrap())
|
|
.collect();
|
|
result_seq.sort_unstable();
|
|
#[cfg(windows)]
|
|
assert_eq!(result_seq, ["1\td\\d", "1\td\\f", "1\td\\h", "4\td"]);
|
|
#[cfg(not(windows))]
|
|
assert_eq!(result_seq, ["1\td/d", "1\td/f", "1\td/h", "4\td"]);
|
|
}
|
|
|
|
#[cfg(not(target_os = "android"))]
|
|
#[test]
|
|
fn test_du_count_links_hardlinks_separately() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir");
|
|
at.touch("dir/file");
|
|
at.hard_link("dir/file", "dir/hard_link");
|
|
|
|
let result_without_l = ts.ucmd().arg("-b").arg("dir").succeeds();
|
|
let size_without_l: u64 = result_without_l
|
|
.stdout_str()
|
|
.split('\t')
|
|
.next()
|
|
.unwrap()
|
|
.trim()
|
|
.parse()
|
|
.unwrap();
|
|
|
|
for arg in ["-l", "--count-links"] {
|
|
let result_with_l = ts.ucmd().arg("-b").arg(arg).arg("dir").succeeds();
|
|
let size_with_l: u64 = result_with_l
|
|
.stdout_str()
|
|
.split('\t')
|
|
.next()
|
|
.unwrap()
|
|
.trim()
|
|
.parse()
|
|
.unwrap();
|
|
|
|
assert!(
|
|
size_with_l >= size_without_l,
|
|
"With {arg}, size ({size_with_l}) should be >= size without -l ({size_without_l})"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_h_flag_empty_file() {
|
|
new_ucmd!()
|
|
.arg("-h")
|
|
.arg("empty.txt")
|
|
.succeeds()
|
|
.stdout_only("0\tempty.txt\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_h_precision() {
|
|
let test_cases = [
|
|
(133_456_345, "128M"),
|
|
(12 * 1024 * 1024, "12M"),
|
|
(8500, "8.4K"),
|
|
];
|
|
|
|
for &(test_len, expected_output) in &test_cases {
|
|
let (at, mut ucmd) = at_and_ucmd!();
|
|
|
|
let fpath = at.plus("test.txt");
|
|
std::fs::File::create(&fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(test_len)
|
|
.expect("cannot truncate test len to size");
|
|
ucmd.arg("-h")
|
|
.arg("--apparent-size")
|
|
.arg(&fpath)
|
|
.succeeds()
|
|
.stdout_only(format!("{expected_output}\t{}\n", fpath.to_string_lossy()));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(wasi_runner, ignore = "WASI: locale env vars not propagated")]
|
|
fn test_du_h_locale_decimal_separator() {
|
|
for (locale, expected) in [("fr_FR.UTF-8", "8,4K"), ("C", "8.4K")] {
|
|
let (at, mut ucmd) = at_and_ucmd!();
|
|
|
|
let fpath = at.plus("test.txt");
|
|
std::fs::File::create(&fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(8500)
|
|
.expect("cannot truncate test len to size");
|
|
ucmd.env("LC_ALL", locale)
|
|
.arg("-h")
|
|
.arg("--apparent-size")
|
|
.arg(&fpath)
|
|
.succeeds()
|
|
.stdout_only(format!("{expected}\t{}\n", fpath.to_string_lossy()));
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_lines)]
|
|
#[cfg(feature = "touch")]
|
|
#[test]
|
|
fn test_du_time() {
|
|
use regex::Regex;
|
|
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
// du --time formats the timestamp according to the local timezone. We set the TZ
|
|
// environment variable to UTC in the commands below to ensure consistent outputs
|
|
// and test results regardless of the timezone of the machine this test runs in.
|
|
|
|
ts.ccmd("touch")
|
|
.env("TZ", "UTC")
|
|
.arg("-a")
|
|
.arg("-t")
|
|
.arg("201505150000")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
|
|
ts.ccmd("touch")
|
|
.env("TZ", "UTC")
|
|
.arg("-m")
|
|
.arg("-t")
|
|
.arg("201606160000")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
|
|
|
// long-iso (same as default)
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("--time-style=long-iso")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
|
|
|
// full-iso
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("--time-style=full-iso")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
|
|
|
// iso
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("--time-style=iso")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16\tdate_test\n");
|
|
|
|
// custom +FORMAT
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("--time-style=+%Y__%H")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016__00\tdate_test\n");
|
|
|
|
// ls has special handling for new line in format, du doesn't.
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("--time-style=+%Y_\n_%H")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016_\n_00\tdate_test\n");
|
|
|
|
// Time style can also be setup from environment
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.env("TIME_STYLE", "full-iso")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
|
|
|
// For compatibility reason, we also allow posix- prefix.
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.env("TIME_STYLE", "posix-full-iso")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
|
|
|
// ... and we strip content after a new line
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.env("TIME_STYLE", "+XXX\nYYY")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\tXXX\tdate_test\n");
|
|
|
|
// ... and we ignore "locale", fall back to full-iso.
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.env("TIME_STYLE", "locale")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
|
|
|
// Command line option takes precedence
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.env("TIME_STYLE", "full-iso")
|
|
.arg("--time")
|
|
.arg("--time-style=iso")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16\tdate_test\n");
|
|
|
|
for argument in ["--time=atime", "--time=atim", "--time=a"] {
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg(argument)
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2015-05-15 00:00\tdate_test\n");
|
|
}
|
|
|
|
// Change (and birth) times can't be easily modified, so we just do a regex
|
|
let re_change_birth =
|
|
Regex::new(r"0\t[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}\tdate_test").unwrap();
|
|
let result = ts.ucmd().arg("--time=ctime").arg("date_test").succeeds();
|
|
#[cfg(windows)]
|
|
result.stdout_only("0\t???\tdate_test\n"); // ctime not supported on Windows
|
|
#[cfg(not(windows))]
|
|
result.stdout_matches(&re_change_birth);
|
|
|
|
if birth_supported() {
|
|
let result = ts.ucmd().arg("--time=birth").arg("date_test").succeeds();
|
|
result.stdout_matches(&re_change_birth);
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "touch")]
|
|
fn birth_supported() -> bool {
|
|
let ts = TestScenario::new(util_name!());
|
|
let m = match std::fs::metadata(&ts.fixtures.subdir) {
|
|
Ok(m) => m,
|
|
Err(e) => panic!("{e}"),
|
|
};
|
|
m.created().is_ok()
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "windows", target_os = "openbsd")))]
|
|
#[cfg(feature = "chmod")]
|
|
#[test]
|
|
fn test_du_no_permission() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all(SUB_DIR_LINKS);
|
|
|
|
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");
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &[SUB_DIR_LINKS]));
|
|
if result_reference
|
|
.stderr_str()
|
|
.contains("du: cannot read directory 'subdir/links': Permission denied")
|
|
{
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
#[cfg(not(target_vendor = "apple"))]
|
|
assert_eq!(result.stdout_str(), "4\tsubdir/links\n");
|
|
#[cfg(target_vendor = "apple")]
|
|
assert_eq!(result.stdout_str(), "0\tsubdir/links\n");
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
#[cfg(feature = "chmod")]
|
|
#[test]
|
|
fn test_du_no_exec_permission() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("d/no-x/y");
|
|
|
|
ts.ccmd("chmod").arg("u=rw").arg("d/no-x").succeeds();
|
|
|
|
let result = ts.ucmd().arg("d/no-x").fails();
|
|
result.stderr_contains("du: cannot access 'd/no-x/y': Permission denied");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_one_file_system() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
// Create the directory structure explicitly for this test
|
|
at.mkdir_all("subdir/deeper/deeper_dir");
|
|
at.write("subdir/deeper/deeper_dir/deeper_words.txt", "hello world");
|
|
at.write("subdir/deeper/words.txt", "world");
|
|
|
|
let result = ts.ucmd().arg("-x").arg("subdir/deeper").succeeds();
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
let result_reference = unwrap_or_return!(expected_result(&ts, &["-x", "subdir/deeper"]));
|
|
if result_reference.succeeded() {
|
|
assert_eq!(result.stdout_str(), result_reference.stdout_str());
|
|
return;
|
|
}
|
|
}
|
|
du_basics_subdir(result.stdout_str());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_threshold() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
// Create the directory structure explicitly for this test
|
|
at.mkdir_all("subdir/links");
|
|
at.mkdir_all("subdir/deeper/deeper_dir");
|
|
// Create files with specific sizes to test threshold
|
|
at.write("subdir/links/bigfile.txt", &"x".repeat(10000)); // ~10K file
|
|
at.write("subdir/deeper/deeper_dir/smallfile.txt", "small"); // small file
|
|
|
|
let threshold = "10K";
|
|
|
|
ts.ucmd()
|
|
.arg("--apparent-size")
|
|
.arg(format!("--threshold={threshold}"))
|
|
.succeeds()
|
|
.stdout_contains("links")
|
|
.stdout_does_not_contain("deeper_dir");
|
|
|
|
ts.ucmd()
|
|
.arg("--apparent-size")
|
|
.arg(format!("--threshold=-{threshold}"))
|
|
.succeeds()
|
|
.stdout_does_not_contain("links")
|
|
.stdout_contains("deeper_dir");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
fn test_du_binary_threshold() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("subdir/links");
|
|
at.mkdir_all("subdir/deeper/deeper_dir");
|
|
at.write("subdir/links/bigfile.txt", &"x".repeat(10000));
|
|
at.write("subdir/deeper/deeper_dir/smallfile.txt", "small");
|
|
|
|
let threshold_bin = "0b10011100010000";
|
|
|
|
ts.ucmd()
|
|
.arg("--apparent-size")
|
|
.arg(format!("--threshold={threshold_bin}"))
|
|
.succeeds()
|
|
.stdout_contains("links")
|
|
.stdout_does_not_contain("deeper_dir");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_invalid_threshold() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
let threshold = "-0";
|
|
|
|
ts.ucmd().arg(format!("--threshold={threshold}")).fails();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_threshold_error_handling() {
|
|
// Test missing threshold value - the specific case from GNU test
|
|
new_ucmd!()
|
|
.arg("--threshold")
|
|
.fails()
|
|
.stderr_contains(
|
|
"error: a value is required for '--threshold <SIZE>' but none was supplied",
|
|
)
|
|
.stderr_contains("For more information, try '--help'.");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_apparent_size() {
|
|
let (at, mut ucmd) = at_and_ucmd!();
|
|
|
|
at.mkdir_all("a/b");
|
|
|
|
at.write("a/b/file1", "foo");
|
|
at.write("a/b/file2", "foobar");
|
|
|
|
let result = ucmd.args(&["--apparent-size", "--all", "a"]).succeeds();
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
{
|
|
result.stdout_contains_line("1\ta/b/file2");
|
|
result.stdout_contains_line("1\ta/b/file1");
|
|
result.stdout_contains_line("1\ta/b");
|
|
result.stdout_contains_line("1\ta");
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
result.stdout_contains_line("1\ta\\b\\file2");
|
|
result.stdout_contains_line("1\ta\\b\\file1");
|
|
result.stdout_contains_line("1\ta\\b");
|
|
result.stdout_contains_line("1\ta");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_bytes() {
|
|
let (at, mut ucmd) = at_and_ucmd!();
|
|
|
|
at.mkdir_all("a/b");
|
|
|
|
at.write("a/b/file1", "foo");
|
|
at.write("a/b/file2", "foobar");
|
|
|
|
let result = ucmd.args(&["--bytes", "--all", "a"]).succeeds();
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
{
|
|
result.stdout_contains_line("6\ta/b/file2");
|
|
result.stdout_contains_line("3\ta/b/file1");
|
|
result.stdout_contains_line("9\ta/b");
|
|
result.stdout_contains_line("9\ta");
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
result.stdout_contains_line("6\ta\\b\\file2");
|
|
result.stdout_contains_line("3\ta\\b\\file1");
|
|
result.stdout_contains_line("9\ta\\b");
|
|
result.stdout_contains_line("9\ta");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_exclude() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.symlink_dir(SUB_DEEPER_DIR, SUB_DIR_LINKS_DEEPER_SYM_DIR);
|
|
at.mkdir_all(SUB_DIR_LINKS);
|
|
|
|
ts.ucmd()
|
|
.arg("--exclude=subdir")
|
|
.arg(SUB_DEEPER_DIR)
|
|
.succeeds()
|
|
.stdout_contains("subdir/deeper/deeper_dir");
|
|
ts.ucmd()
|
|
.arg("--exclude=subdir")
|
|
.arg("subdir")
|
|
.succeeds()
|
|
.stdout_is("");
|
|
ts.ucmd()
|
|
.arg("--exclude=subdir")
|
|
.arg("--verbose")
|
|
.arg("subdir")
|
|
.succeeds()
|
|
.stdout_contains("'subdir' ignored");
|
|
}
|
|
|
|
#[test]
|
|
// Disable on Windows because we are looking for /
|
|
// And the tests would be more complex if we have to support \ too
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn test_du_exclude_2() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("azerty/xcwww/azeaze");
|
|
|
|
let result = ts.ucmd().arg("azerty").succeeds();
|
|
|
|
let path_regexp = r"(.*)azerty/xcwww/azeaze(.*)azerty/xcwww(.*)azerty";
|
|
let re = Regex::new(path_regexp).unwrap();
|
|
assert!(re.is_match(result.stdout_str().replace('\n', "").trim()));
|
|
|
|
// Exact match
|
|
ts.ucmd()
|
|
.arg("--exclude=azeaze")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_does_not_contain("azerty/xcwww/azeaze");
|
|
// Partial match and NOT a glob
|
|
ts.ucmd()
|
|
.arg("--exclude=azeaz")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_contains("azerty/xcwww/azeaze");
|
|
// Partial match and a various glob
|
|
ts.ucmd()
|
|
.arg("--exclude=azea?")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_contains("azerty/xcwww/azeaze");
|
|
ts.ucmd()
|
|
.arg("--exclude=azea{z,b}")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_contains("azerty/xcwww/azeaze");
|
|
ts.ucmd()
|
|
.arg("--exclude=azea*")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_does_not_contain("azerty/xcwww/azeaze");
|
|
ts.ucmd()
|
|
.arg("--exclude=azeaz?")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_does_not_contain("azerty/xcwww/azeaze");
|
|
}
|
|
|
|
#[test]
|
|
// Disable on Windows because we are looking for /
|
|
// And the tests would be more complex if we have to support \ too
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn test_du_exclude_mix() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.write("file-ignore1", "azeaze");
|
|
at.write("file-ignore2", "amaz?ng");
|
|
|
|
at.mkdir_all("azerty/xcwww/azeaze");
|
|
at.mkdir_all("azerty/xcwww/qzerty");
|
|
at.mkdir_all("azerty/xcwww/amazing");
|
|
|
|
ts.ucmd()
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_contains("azerty/xcwww/azeaze");
|
|
ts.ucmd()
|
|
.arg("--exclude=azeaze")
|
|
.arg("azerty")
|
|
.succeeds()
|
|
.stdout_does_not_contain("azerty/xcwww/azeaze");
|
|
|
|
// Just exclude one file name
|
|
let result = ts.ucmd().arg("--exclude=qzerty").arg("azerty").succeeds();
|
|
assert!(!result.stdout_str().contains("qzerty"));
|
|
assert!(result.stdout_str().contains("azerty"));
|
|
assert!(result.stdout_str().contains("xcwww"));
|
|
|
|
// Exclude from file
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--exclude-from=file-ignore1")
|
|
.arg("azerty")
|
|
.succeeds();
|
|
assert!(!result.stdout_str().contains("azeaze"));
|
|
assert!(result.stdout_str().contains("qzerty"));
|
|
assert!(result.stdout_str().contains("xcwww"));
|
|
|
|
// Mix two files and string
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--exclude=qzerty")
|
|
.arg("--exclude-from=file-ignore1")
|
|
.arg("--exclude-from=file-ignore2")
|
|
.arg("azerty")
|
|
.succeeds();
|
|
assert!(!result.stdout_str().contains("amazing"));
|
|
assert!(!result.stdout_str().contains("qzerty"));
|
|
assert!(!result.stdout_str().contains("azeaze"));
|
|
assert!(result.stdout_str().contains("xcwww"));
|
|
}
|
|
|
|
#[test]
|
|
// Disable on Windows because we are looking for /
|
|
// And the tests would be more complex if we have to support \ too
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn test_du_complex_exclude_patterns() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("azerty/xcwww/azeaze");
|
|
at.mkdir_all("azerty/xcwww/qzerty");
|
|
at.mkdir_all("azerty/xcwww/amazing");
|
|
|
|
// Negation in glob should work with both ^ and !
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--exclude=azerty/*/[^q]*")
|
|
.arg("azerty")
|
|
.succeeds();
|
|
assert!(!result.stdout_str().contains("amazing"));
|
|
assert!(result.stdout_str().contains("qzerty"));
|
|
assert!(!result.stdout_str().contains("azeaze"));
|
|
assert!(result.stdout_str().contains("xcwww"));
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--exclude=azerty/*/[!q]*")
|
|
.arg("azerty")
|
|
.succeeds();
|
|
assert!(!result.stdout_str().contains("amazing"));
|
|
assert!(result.stdout_str().contains("qzerty"));
|
|
assert!(!result.stdout_str().contains("azeaze"));
|
|
assert!(result.stdout_str().contains("xcwww"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_exclude_several_components() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("a/b/c");
|
|
at.mkdir_all("a/x/y");
|
|
at.mkdir_all("a/u/y");
|
|
|
|
// Exact match
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--exclude=a/u")
|
|
.arg("--exclude=a/b")
|
|
.arg("a")
|
|
.succeeds();
|
|
assert!(!result.stdout_str().contains("a/u"));
|
|
assert!(!result.stdout_str().contains("a/b"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_exclude_invalid_syntax() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("azerty/xcwww/azeaze");
|
|
|
|
ts.ucmd()
|
|
.arg("--exclude=a[ze")
|
|
.arg("azerty")
|
|
.fails()
|
|
.stderr_contains("du: Invalid exclude syntax");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_exclude_from_nonexistent_file() {
|
|
new_ucmd!()
|
|
.arg("--exclude-from=nonexistent-file")
|
|
.fails()
|
|
.stderr_contains("du: No such file or directory");
|
|
}
|
|
|
|
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
|
|
#[test]
|
|
fn test_du_exclude_from_read_error() {
|
|
new_ucmd!()
|
|
.arg("--exclude-from=/proc/self/mem")
|
|
.fails()
|
|
.stderr_contains("du: Input/output error");
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
#[test]
|
|
fn test_du_symlink_fail() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.symlink_file("non-existing.txt", "target.txt");
|
|
|
|
ts.ucmd().arg("-L").arg("target.txt").fails_with_code(1);
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
#[cfg(not(target_os = "openbsd"))]
|
|
#[test]
|
|
fn test_du_symlink_multiple_fail() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.symlink_file("non-existing.txt", "target.txt");
|
|
at.write("file1", "azeaze");
|
|
|
|
let result = ts.ucmd().arg("-L").arg("target.txt").arg("file1").fails();
|
|
assert_eq!(result.code(), 1);
|
|
result.stdout_contains("4\tfile1\n");
|
|
}
|
|
|
|
#[test]
|
|
// Disable on Windows because of different path separators and handling of null characters
|
|
#[cfg(not(target_os = "windows"))]
|
|
fn test_du_files0_from() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.write("testfile1", "content1");
|
|
at.write("testfile2", "content2");
|
|
|
|
at.mkdir("testdir");
|
|
at.write("testdir/testfile3", "content3");
|
|
|
|
at.write("filelist", "testfile1\0testfile2\0testdir\0");
|
|
|
|
ts.ucmd()
|
|
.arg("--files0-from=filelist")
|
|
.succeeds()
|
|
.stdout_contains("testfile1")
|
|
.stdout_contains("testfile2")
|
|
.stdout_contains("testdir");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_ignore_duplicate_file_names() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let file = "testfile";
|
|
|
|
at.touch(file);
|
|
at.write("filelist", &format!("{file}\0{file}\0"));
|
|
|
|
ts.ucmd()
|
|
.arg("--files0-from=filelist")
|
|
.succeeds()
|
|
.stdout_is(format!("0\t{file}\n"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_with_invalid_zero_length_file_names() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.touch("testfile");
|
|
|
|
at.write("filelist", "\0testfile\0\0");
|
|
|
|
ts.ucmd()
|
|
.arg("--files0-from=filelist")
|
|
.fails_with_code(1)
|
|
.stdout_contains("testfile")
|
|
.stderr_contains("filelist:1: invalid zero-length file name")
|
|
.stderr_contains("filelist:3: invalid zero-length file name");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_stdin() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.write("testfile1", "content1");
|
|
at.write("testfile2", "content2");
|
|
|
|
let input = "testfile1\0testfile2\0";
|
|
|
|
ts.ucmd()
|
|
.arg("--files0-from=-")
|
|
.pipe_in(input)
|
|
.succeeds()
|
|
.stdout_contains("testfile1")
|
|
.stdout_contains("testfile2");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_stdin_ignore_duplicate_file_names() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let file = "testfile";
|
|
|
|
at.touch(file);
|
|
|
|
let input = format!("{file}\0{file}");
|
|
|
|
ts.ucmd()
|
|
.arg("--files0-from=-")
|
|
.pipe_in(input)
|
|
.succeeds()
|
|
.stdout_is(format!("0\t{file}\n"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_stdin_with_invalid_zero_length_file_names() {
|
|
new_ucmd!()
|
|
.arg("--files0-from=-")
|
|
.pipe_in("\0\0")
|
|
.fails_with_code(1)
|
|
.stderr_contains("-:1: invalid zero-length file name")
|
|
.stderr_contains("-:2: invalid zero-length file name");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_stdin_with_stdin_as_input() {
|
|
new_ucmd!()
|
|
.arg("--files0-from=-")
|
|
.pipe_in("-")
|
|
.fails_with_code(1)
|
|
.stderr_is(
|
|
"du: when reading file names from standard input, no file name of '-' allowed\n",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_dir() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir");
|
|
|
|
let result = ts.ucmd().arg("--files0-from=dir").fails();
|
|
assert_eq!(result.stderr_str(), "du: dir: read error: Is a directory\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_files0_from_combined() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir");
|
|
|
|
let result = ts.ucmd().arg("--files0-from=-").arg("foo").fails();
|
|
let stderr = result.stderr_str();
|
|
|
|
assert!(stderr.contains("file operands cannot be combined with --files0-from"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_time_style() {
|
|
let ts = TestScenario::new(util_name!());
|
|
ts.ucmd()
|
|
.arg("-s")
|
|
.arg("--time-style=banana")
|
|
.succeeds()
|
|
.stdout_does_not_contain("du: invalid argument 'banana' for 'time style'");
|
|
}
|
|
|
|
#[test]
|
|
fn test_human_size() {
|
|
use std::fs::File;
|
|
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = at.plus_as_string("d");
|
|
at.mkdir(&dir);
|
|
|
|
for i in 1..=1023 {
|
|
let file_path = format!("{dir}/file{i}");
|
|
File::create(&file_path).expect("Failed to create file");
|
|
}
|
|
|
|
ts.ucmd()
|
|
.arg("--inodes")
|
|
.arg("-h")
|
|
.arg(&dir)
|
|
.succeeds()
|
|
.stdout_contains(format!("1.0K {dir}"));
|
|
}
|
|
|
|
#[cfg(not(target_os = "android"))]
|
|
#[test]
|
|
fn test_du_deduplicated_input_args() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("d");
|
|
at.mkdir("d/d");
|
|
at.touch("d/f");
|
|
at.hard_link("d/f", "d/h");
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--inodes")
|
|
.arg("d")
|
|
.arg("d")
|
|
.arg("d")
|
|
.succeeds();
|
|
result.no_stderr();
|
|
|
|
let result_seq: Vec<String> = result
|
|
.stdout_str()
|
|
.lines()
|
|
.map(|x| x.parse().unwrap())
|
|
.collect();
|
|
#[cfg(windows)]
|
|
assert_eq!(result_seq, ["1\td\\d", "3\td"]);
|
|
#[cfg(not(windows))]
|
|
assert_eq!(result_seq, ["1\td/d", "3\td"]);
|
|
}
|
|
|
|
#[cfg(not(target_os = "android"))]
|
|
#[test]
|
|
fn test_du_no_deduplicated_input_args() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("d");
|
|
at.touch("d/d");
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.arg("--inodes")
|
|
.arg("-l")
|
|
.arg("d")
|
|
.arg("d")
|
|
.arg("d")
|
|
.succeeds();
|
|
result.no_stderr();
|
|
|
|
let result_seq: Vec<String> = result
|
|
.stdout_str()
|
|
.lines()
|
|
.map(|x| x.parse().unwrap())
|
|
.collect();
|
|
assert_eq!(result_seq, ["2\td", "2\td", "2\td"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_blocksize_zero_do_not_panic() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
at.write("foo", "some content");
|
|
for block_size in ["0", "00", "000", "0x0", "0b0"] {
|
|
ts.ucmd()
|
|
.arg(format!("-B{block_size}"))
|
|
.arg("foo")
|
|
.fails()
|
|
.stderr_only(format!(
|
|
"du: invalid --block-size argument '{block_size}'\n"
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_inodes_blocksize_ineffective() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let fpath = at.plus("test.txt");
|
|
at.touch(fpath);
|
|
for method in ["-B3", "--block-size=3"] {
|
|
// No warning
|
|
ts.ucmd()
|
|
.arg(method)
|
|
.arg("--inodes")
|
|
.arg("test.txt")
|
|
.succeeds()
|
|
.stdout_only("1\ttest.txt\n");
|
|
}
|
|
for method in ["--apparent-size", "-b"] {
|
|
// A warning appears!
|
|
ts.ucmd()
|
|
.arg(method)
|
|
.arg("--inodes")
|
|
.arg("test.txt")
|
|
.succeeds()
|
|
.stdout_is("1\ttest.txt\n")
|
|
.stderr_is(
|
|
"du: warning: options --apparent-size and -b are ineffective with --inodes\n",
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_inodes_total_text() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("d/d");
|
|
|
|
let result = ts.ucmd().arg("--inodes").arg("-c").arg("d").succeeds();
|
|
|
|
let lines: Vec<&str> = result.stdout_str().lines().collect();
|
|
|
|
assert_eq!(lines.len(), 3);
|
|
|
|
let total_line = lines.last().unwrap();
|
|
assert!(total_line.contains("total"));
|
|
|
|
let parts: Vec<&str> = total_line.split('\t').collect();
|
|
assert_eq!(parts.len(), 2);
|
|
|
|
assert!(parts[0].parse::<u64>().is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_threshold_no_suggested_values() {
|
|
// tested by tests/du/threshold
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
let result = ts.ucmd().arg("--threshold").fails();
|
|
assert!(!result.stderr_str().contains("[possible values: ]"));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_du_long_path_safe_traversal() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
let mut deep_path = String::from("long_path_test");
|
|
at.mkdir(&deep_path);
|
|
|
|
for i in 0..15 {
|
|
let long_dir_name = format!("{}{i}", "a".repeat(100));
|
|
deep_path = format!("{deep_path}/{long_dir_name}");
|
|
at.mkdir_all(&deep_path);
|
|
}
|
|
|
|
let test_file = format!("{deep_path}/test.txt");
|
|
at.write(&test_file, "test content");
|
|
|
|
let result = ts.ucmd().arg("-s").arg("long_path_test").succeeds();
|
|
assert!(result.stdout_str().contains("long_path_test"));
|
|
|
|
let result = ts.ucmd().arg("long_path_test").succeeds();
|
|
let lines: Vec<&str> = result.stdout_str().trim().lines().collect();
|
|
assert!(lines.len() >= 15);
|
|
}
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_du_very_deep_directory() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
let mut current_path = String::from("x");
|
|
at.mkdir(¤t_path);
|
|
|
|
for _ in 0..10 {
|
|
current_path = format!("{current_path}/x");
|
|
at.mkdir_all(¤t_path);
|
|
}
|
|
|
|
at.write(&format!("{current_path}/file.txt"), "deep file");
|
|
|
|
let result = ts.ucmd().arg("-s").arg("x").succeeds();
|
|
assert!(result.stdout_str().contains('x'));
|
|
|
|
let result = ts.ucmd().arg("-a").arg("x").succeeds();
|
|
let output = result.stdout_str();
|
|
assert!(output.contains("file.txt"));
|
|
}
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_du_safe_traversal_with_symlinks() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
let mut deep_path = String::from("symlink_test");
|
|
at.mkdir(&deep_path);
|
|
|
|
for i in 0..8 {
|
|
let dir_name = format!("{}{i}", "b".repeat(50));
|
|
deep_path = format!("{deep_path}/{dir_name}");
|
|
at.mkdir_all(&deep_path);
|
|
}
|
|
|
|
at.write(&format!("{deep_path}/target.txt"), "target content");
|
|
|
|
at.symlink_file(&format!("{deep_path}/target.txt"), "shallow_link.txt");
|
|
|
|
let result = ts.ucmd().arg("-L").arg("shallow_link.txt").succeeds();
|
|
assert!(!result.stdout_str().is_empty());
|
|
|
|
let result = ts.ucmd().arg("shallow_link.txt").succeeds();
|
|
assert!(!result.stdout_str().is_empty());
|
|
}
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_du_inaccessible_directory() {
|
|
// tested by tests/du/no-x
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = ts.fixtures.clone();
|
|
|
|
at.mkdir("d");
|
|
at.mkdir("d/no-x");
|
|
at.mkdir("d/no-x/y");
|
|
|
|
at.set_mode("d/no-x", 0o600);
|
|
|
|
let result = ts.ucmd().arg("d").fails();
|
|
result.stderr_contains("du: cannot access 'd/no-x/y': Permission denied");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_du_symlink_self_reference() {
|
|
// Test symlink that points to its own directory
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("selfref");
|
|
at.symlink_dir("selfref", "selfref/self");
|
|
|
|
let result = ts.ucmd().arg("-L").arg("selfref").succeeds();
|
|
|
|
result.stdout_contains("selfref");
|
|
// Should not show the self-referencing symlink to avoid infinite recursion
|
|
result.stdout_does_not_contain("selfref/self");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_du_long_symlink_chain() {
|
|
// Test that very long symlink chains are handled gracefully
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
// Create a simple structure that tests symlink depth limits
|
|
// Instead of trying to create a chain that causes ELOOP, test that reasonable chains work
|
|
at.mkdir_all("deep/level1/level2/level3/level4/level5");
|
|
at.write(
|
|
"deep/level1/level2/level3/level4/level5/file.txt",
|
|
"content",
|
|
);
|
|
|
|
at.symlink_dir("deep/level1", "link1");
|
|
at.symlink_dir("link1/level2", "link2");
|
|
at.symlink_dir("link2/level3", "link3");
|
|
|
|
let result = ts.ucmd().arg("-L").arg("link3").succeeds();
|
|
result.stdout_contains("link3");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(all(unix, not(target_os = "macos")))]
|
|
fn test_du_bind_mount_simulation() {
|
|
// Simulate bind mount scenario using hard links where possible
|
|
// Note: This test simulates what bind mounts do - making the same directory
|
|
// appear in multiple places with the same inode
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("mount_test/subdir");
|
|
at.write("mount_test/file1.txt", "content1");
|
|
at.write("mount_test/subdir/file2.txt", "content2");
|
|
|
|
// On systems where we can't create actual bind mounts,
|
|
// we test that cycle detection works with symlinks that would create similar cycles
|
|
at.symlink_dir("../mount_test", "mount_test/subdir/cycle_link");
|
|
|
|
let result = ts.ucmd().arg("mount_test").succeeds();
|
|
|
|
result.stdout_contains("mount_test/subdir");
|
|
result.stdout_contains("mount_test");
|
|
|
|
result.stdout_does_not_contain("mount_test/subdir/cycle_link");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn test_du_symlink_depth_tracking() {
|
|
// Test that du can handle reasonable symlink chains without hitting depth limits
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir_all("chain/dir1/dir2/dir3");
|
|
at.write("chain/dir1/dir2/dir3/file.txt", "content");
|
|
|
|
at.symlink_dir("chain/dir1/dir2", "shortcut");
|
|
|
|
let result = ts.ucmd().arg("-L").arg("shortcut").succeeds();
|
|
result.stdout_contains("shortcut/dir3");
|
|
result.stdout_contains("shortcut");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_du_long_path_from_unreadable() {
|
|
// Test the specific scenario from GNU's long-from-unreadable.sh test
|
|
// This verifies that du can handle very long paths when the current directory is unreadable
|
|
use std::env;
|
|
use std::fs;
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
// Create a deep hierarchy similar to the GNU test
|
|
// Use a more reasonable depth for unit tests
|
|
let dir_name = "x".repeat(200);
|
|
let mut current_path = String::new();
|
|
|
|
for i in 0..20 {
|
|
if i == 0 {
|
|
current_path = dir_name.clone();
|
|
} else {
|
|
current_path = format!("{current_path}/{dir_name}");
|
|
}
|
|
at.mkdir_all(¤t_path);
|
|
}
|
|
|
|
at.write(&format!("{current_path}/test.txt"), "test content");
|
|
|
|
at.mkdir("inaccessible");
|
|
|
|
let original_cwd = env::current_dir().unwrap();
|
|
|
|
let inaccessible_path = at.plus("inaccessible");
|
|
env::set_current_dir(&inaccessible_path).unwrap();
|
|
|
|
// Remove read permission from the directory
|
|
let mut perms = fs::metadata(&inaccessible_path).unwrap().permissions();
|
|
perms.set_mode(0o000);
|
|
fs::set_permissions(&inaccessible_path, perms).unwrap();
|
|
|
|
// Try to run du on the long path from the unreadable directory
|
|
let target_path = at.plus(&dir_name);
|
|
let result = ts.ucmd().arg("-s").arg(&target_path).succeeds(); // Should succeed with safe traversal
|
|
|
|
assert!(!result.stdout_str().is_empty());
|
|
let output = result.stdout_str().trim();
|
|
let parts: Vec<&str> = output.split_whitespace().collect();
|
|
assert_eq!(parts.len(), 2);
|
|
|
|
assert!(parts[0].parse::<u64>().is_ok());
|
|
assert!(parts[1].contains(&dir_name[..50])); // Check first part of the long name
|
|
|
|
env::set_current_dir(&original_cwd).unwrap();
|
|
|
|
// Restore permissions so the directory can be cleaned up
|
|
let mut perms = fs::metadata(&inaccessible_path).unwrap().permissions();
|
|
perms.set_mode(0o755);
|
|
fs::set_permissions(&inaccessible_path, perms).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_du_hard_links_multiple_dirs_in_args() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir1");
|
|
at.mkdir("dir2");
|
|
at.write("dir1/file", "hello world");
|
|
at.hard_link("dir1/file", "dir2/link");
|
|
|
|
let result = ts.ucmd().args(&["dir1", "dir2"]).succeeds();
|
|
let lines: Vec<&str> = result.stdout_str().lines().collect();
|
|
let size = |i: usize| lines[i].split_once('\t').unwrap().0.parse::<u64>().unwrap();
|
|
assert!(size(0) > size(1));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_du_hard_links_multiple_links_in_args() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir1");
|
|
at.write("dir1/file", "hello world");
|
|
at.hard_link("dir1/file", "dir1/link");
|
|
|
|
let result = ts.ucmd().args(&["dir1/file", "dir1/link"]).succeeds();
|
|
result.stdout_contains("dir1/file");
|
|
result.stdout_does_not_contain("dir1/link");
|
|
|
|
let result = ts.ucmd().args(&["-L", "dir1/file", "dir1/link"]).succeeds();
|
|
result.stdout_contains("dir1/file");
|
|
result.stdout_does_not_contain("dir1/link");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "linux")]
|
|
fn test_du_symlinks_multiple_links_in_args() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
at.mkdir("dir1");
|
|
at.write("dir1/file", "hello world");
|
|
at.symlink_file("dir1/file", "dir1/link");
|
|
|
|
let result = ts.ucmd().args(&["dir1/file", "dir1/link"]).succeeds();
|
|
result.stdout_contains("dir1/file");
|
|
result.stdout_contains("dir1/link");
|
|
|
|
let result = ts.ucmd().args(&["-L", "dir1/file", "dir1/link"]).succeeds();
|
|
result.stdout_contains("dir1/file");
|
|
result.stdout_does_not_contain("dir1/link");
|
|
}
|
|
|
|
#[test]
|
|
fn test_block_size_args_override() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "override_args_dir";
|
|
let nested_dir = "override_args_dir/nested_dir";
|
|
let nested_dir_2 = "override_args_dir/nested_dir_2";
|
|
|
|
at.mkdir_all(nested_dir);
|
|
at.mkdir_all(nested_dir_2);
|
|
let fpath = at.plus(format!("{nested_dir}/file"));
|
|
std::fs::File::create(fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000_000)
|
|
.expect("cannot set file size");
|
|
|
|
let fpath2 = at.plus(format!("{nested_dir}/file_2"));
|
|
std::fs::File::create(fpath2)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000_000)
|
|
.expect("cannot set file size");
|
|
|
|
let fpath = at.plus(format!("{nested_dir_2}/file_3"));
|
|
std::fs::File::create(fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000)
|
|
.expect("cannot set file size");
|
|
|
|
let fpath2 = at.plus(format!("{nested_dir_2}/file_4"));
|
|
std::fs::File::create(fpath2)
|
|
.expect("cannot create test file")
|
|
.set_len(100)
|
|
.expect("cannot set file size");
|
|
|
|
let test_cases = [
|
|
(["-sk", "-m"], vec!["-sm"]),
|
|
(["-sk", "-b"], vec!["-sb"]),
|
|
(["-sm", "-k"], vec!["-sk"]),
|
|
(["-sk", "--si"], vec!["-s", "--si"]),
|
|
(["-sk", "-h"], vec!["-s", "-h"]),
|
|
(["-sm", "--block-size=128"], vec!["-s", "--block-size=128"]),
|
|
(["--block-size=128", "-b"], vec!["-b"]),
|
|
(["--si", "-b"], vec!["-b"]),
|
|
(["-h", "-b"], vec!["-b"]),
|
|
];
|
|
|
|
for (idx, (overwriting_args, expected)) in test_cases.into_iter().enumerate() {
|
|
let overridden_args = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.args(&overwriting_args)
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let single_args = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.args(&expected)
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(
|
|
overridden_args, single_args,
|
|
"The last argument of m, k and b should overwrite. Run: {idx}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_block_override_b_still_has_apparent_size() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
let dir = "override_args_dir";
|
|
let nested_dir = "override_args_dir/nested_dir";
|
|
|
|
at.mkdir_all(nested_dir);
|
|
let fpath = at.plus(format!("{nested_dir}/file"));
|
|
std::fs::File::create(fpath)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000_000)
|
|
.expect("cannot set file size");
|
|
|
|
let fpath2 = at.plus(format!("{nested_dir}/file_2"));
|
|
std::fs::File::create(fpath2)
|
|
.expect("cannot create test file")
|
|
.set_len(100_000_000)
|
|
.expect("cannot set file size");
|
|
|
|
let test_cases = [
|
|
(["-b", "-m"], ["-m", "--apparent-size"]),
|
|
(["-b", "-k"], ["-k", "--apparent-size"]),
|
|
(["-b", "--si"], ["--si", "--apparent-size"]),
|
|
(["-b", "-h"], ["-h", "--apparent-size"]),
|
|
(
|
|
["-b", "--block-size=128"],
|
|
["--block-size=128", "--apparent-size"],
|
|
),
|
|
];
|
|
|
|
for (idx, (overwriting_args, expected)) in test_cases.into_iter().enumerate() {
|
|
let overridden_args = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.args(&overwriting_args)
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
let single_args = ts
|
|
.ucmd()
|
|
.arg(dir)
|
|
.args(&expected)
|
|
.succeeds()
|
|
.stdout_move_str();
|
|
|
|
assert_eq!(
|
|
overridden_args, single_args,
|
|
"Overwriting the b flag should still leave --apparent-size active. Run: {idx}"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_overriding_block_size_arg_with_invalid_value_still_errors() {
|
|
new_ucmd!()
|
|
.args(&["--block-size=abc", "-m"])
|
|
.fails_with_code(1)
|
|
.stderr_contains("invalid --block-size argument 'abc'");
|
|
new_ucmd!()
|
|
.args(&["--block-size=abc", "-k"])
|
|
.fails_with_code(1)
|
|
.stderr_contains("invalid --block-size argument 'abc'");
|
|
new_ucmd!()
|
|
.args(&["--block-size=abc", "-b"])
|
|
.fails_with_code(1)
|
|
.stderr_contains("invalid --block-size argument 'abc'");
|
|
new_ucmd!()
|
|
.args(&["--block-size=abc", "-h"])
|
|
.fails_with_code(1)
|
|
.stderr_contains("invalid --block-size argument 'abc'");
|
|
new_ucmd!()
|
|
.args(&["--block-size=abc", "--si"])
|
|
.fails_with_code(1)
|
|
.stderr_contains("invalid --block-size argument 'abc'");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_h() {
|
|
new_ucmd!().args(&["-s", "-h", "-h"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_a() {
|
|
new_ucmd!().args(&["-a", "-a"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_apparent_size() {
|
|
new_ucmd!()
|
|
.args(&["-s", "-A", "-A"])
|
|
.succeeds()
|
|
.stdout_only("6\t.\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_block_size() {
|
|
new_ucmd!()
|
|
.args(&["-s", "-B", "100", "-B", "100"])
|
|
.succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_b() {
|
|
new_ucmd!()
|
|
.args(&["-s", "-b", "-b"])
|
|
.succeeds()
|
|
.stdout_only("5148\t.\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_c() {
|
|
new_ucmd!().args(&["-s", "-c", "-c"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_d() {
|
|
new_ucmd!().args(&["-d", "2", "-d", "2"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_inodes() {
|
|
new_ucmd!()
|
|
.args(&["-s", "--inodes", "--inodes"])
|
|
.succeeds()
|
|
.stdout_only("11\t.\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_k() {
|
|
new_ucmd!().args(&["-s", "-k", "-k"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_l() {
|
|
new_ucmd!().args(&["-s", "-l", "-l"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_dereference() {
|
|
new_ucmd!().args(&["-s", "-L", "-L"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_dereference_args() {
|
|
new_ucmd!().args(&["-s", "-D", "-D"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_no_dereference() {
|
|
new_ucmd!().args(&["-s", "-P", "-P"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_m() {
|
|
new_ucmd!()
|
|
.args(&["-s", "-m", "-m"])
|
|
.succeeds()
|
|
.stdout_only("1\t.\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_0() {
|
|
new_ucmd!().args(&["-s", "-0", "-0"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_separate_dirs() {
|
|
new_ucmd!().args(&["-s", "-S", "-S"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_s() {
|
|
new_ucmd!().args(&["-s", "-s"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_si() {
|
|
new_ucmd!().args(&["-s", "--si", "--si"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_x() {
|
|
new_ucmd!().args(&["-s", "-x", "-x"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_t() {
|
|
new_ucmd!()
|
|
.args(&["-s", "-t", "100", "-t", "100"])
|
|
.succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_v() {
|
|
new_ucmd!().args(&["-s", "-v", "-v"]).succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn test_du_repeated_files0_from() {
|
|
let ts = TestScenario::new(util_name!());
|
|
let at = &ts.fixtures;
|
|
|
|
// Set up the data for use with command 'du' and argument '--files0-from'.
|
|
// File 'somefile' has to contain a list of NUL-terminated directory and
|
|
// file names.
|
|
at.mkdir("dir");
|
|
at.write("dir/file2", "xyz");
|
|
at.write("./somefile", "dir\0");
|
|
|
|
ts.ucmd()
|
|
.args(&[
|
|
"-s",
|
|
"--files0-from",
|
|
"somefile",
|
|
"--files0-from",
|
|
"somefile",
|
|
])
|
|
.succeeds();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "touch")]
|
|
fn test_du_repeated_time() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
// du --time formats the timestamp according to the local timezone. We set the TZ
|
|
// environment variable to UTC in the commands below to ensure consistent outputs
|
|
// and test results regardless of the timezone of the machine this test runs in.
|
|
|
|
ts.ccmd("touch")
|
|
.env("TZ", "UTC")
|
|
.arg("-a")
|
|
.arg("-t")
|
|
.arg("201505150000")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
|
|
ts.ccmd("touch")
|
|
.env("TZ", "UTC")
|
|
.arg("-m")
|
|
.arg("-t")
|
|
.arg("201606160000")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.arg("--time")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00\tdate_test\n");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "touch")]
|
|
fn test_du_repeated_time_style() {
|
|
let ts = TestScenario::new(util_name!());
|
|
|
|
// du --time formats the timestamp according to the local timezone. We set the TZ
|
|
// environment variable to UTC in the commands below to ensure consistent outputs
|
|
// and test results regardless of the timezone of the machine this test runs in.
|
|
|
|
ts.ccmd("touch")
|
|
.env("TZ", "UTC")
|
|
.arg("-a")
|
|
.arg("-t")
|
|
.arg("201505150000")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
|
|
ts.ccmd("touch")
|
|
.env("TZ", "UTC")
|
|
.arg("-m")
|
|
.arg("-t")
|
|
.arg("201606160000")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
|
|
// full-iso
|
|
let result = ts
|
|
.ucmd()
|
|
.env("TZ", "UTC")
|
|
.arg("--time")
|
|
.arg("--time-style=full-iso")
|
|
.arg("--time-style=full-iso")
|
|
.arg("date_test")
|
|
.succeeds();
|
|
result.stdout_only("0\t2016-06-16 00:00:00.000000000 +0000\tdate_test\n");
|
|
}
|