|
4 | 4 | pub mod _utils; |
5 | 5 | pub use _utils::*; |
6 | 6 |
|
| 7 | +use assert_cmp::assert_op_expr; |
7 | 8 | use command_extra::CommandExtra; |
8 | 9 | use parallel_disk_usage::{ |
9 | 10 | bytes_format::BytesFormat, |
@@ -58,6 +59,56 @@ fn sample_tree() -> SampleTree { |
58 | 59 | .into_par_sorted(|left, right| left.size().cmp(&right.size()).reverse()) |
59 | 60 | } |
60 | 61 |
|
| 62 | +/// Sample tree whose entries are deliberately stored in ascending order of size, |
| 63 | +/// which is the opposite of the descending order produced by the default sorting. |
| 64 | +fn ascending_sample_tree() -> SampleTree { |
| 65 | + let file = |
| 66 | + |name: &'static str, size: u64| SampleTree::file(name.to_string(), Bytes::from(size)); |
| 67 | + SampleTree::dir( |
| 68 | + "root".to_string(), |
| 69 | + 1024.into(), |
| 70 | + vec![file("a", 50), file("b", 500), file("c", 5000)], |
| 71 | + ) |
| 72 | +} |
| 73 | + |
| 74 | +/// Feed a tree to `pdu --json-input` and return its trimmed stdout. |
| 75 | +fn run_json_input(tree: SampleTree, extra_args: &[&str]) -> String { |
| 76 | + let json_tree = JsonTree { |
| 77 | + tree: tree.into_reflection(), |
| 78 | + shared: Default::default(), |
| 79 | + }; |
| 80 | + let json_data = JsonData { |
| 81 | + schema_version: SchemaVersion, |
| 82 | + binary_version: None, |
| 83 | + body: json_tree.into(), |
| 84 | + }; |
| 85 | + let json = serde_json::to_string_pretty(&json_data).expect("convert sample tree to JSON"); |
| 86 | + let workspace = Temp::new_dir().expect("create temporary directory"); |
| 87 | + let mut child = Command::new(PDU) |
| 88 | + .with_current_dir(&workspace) |
| 89 | + .with_arg("--json-input") |
| 90 | + .with_arg("--bytes-format=metric") |
| 91 | + .with_arg("--total-width=100") |
| 92 | + .with_args(extra_args) |
| 93 | + .with_stdin(Stdio::piped()) |
| 94 | + .with_stdout(Stdio::piped()) |
| 95 | + .with_stderr(Stdio::piped()) |
| 96 | + .spawn() |
| 97 | + .expect("spawn command"); |
| 98 | + child |
| 99 | + .stdin |
| 100 | + .as_mut() |
| 101 | + .expect("get stdin of child process") |
| 102 | + .write_all(json.as_bytes()) |
| 103 | + .expect("write JSON string to child process's stdin"); |
| 104 | + child |
| 105 | + .wait_with_output() |
| 106 | + .expect("wait for output of child process") |
| 107 | + .pipe(stdout_text) |
| 108 | + .trim_end() |
| 109 | + .to_string() |
| 110 | +} |
| 111 | + |
61 | 112 | #[test] |
62 | 113 | fn json_output() { |
63 | 114 | let workspace = SampleWorkspace::default(); |
@@ -119,6 +170,7 @@ fn json_input() { |
119 | 170 | .with_arg("--bytes-format=metric") |
120 | 171 | .with_arg("--total-width=100") |
121 | 172 | .with_arg("--max-depth=10") |
| 173 | + .with_arg("--min-ratio=0") |
122 | 174 | .with_stdin(Stdio::piped()) |
123 | 175 | .with_stdout(Stdio::piped()) |
124 | 176 | .with_stderr(Stdio::piped()) |
@@ -151,6 +203,59 @@ fn json_input() { |
151 | 203 | assert_eq!(actual, expected); |
152 | 204 | } |
153 | 205 |
|
| 206 | +#[test] |
| 207 | +fn json_input_max_depth() { |
| 208 | + let actual = run_json_input(sample_tree(), &["--max-depth=2", "--min-ratio=0"]); |
| 209 | + eprintln!("ACTUAL:\n{actual}\n"); |
| 210 | + let unlimited = run_json_input(sample_tree(), &["--max-depth=10", "--min-ratio=0"]); |
| 211 | + eprintln!("UNLIMITED:\n{unlimited}\n"); |
| 212 | + |
| 213 | + // Limiting the depth must change the output. |
| 214 | + assert_ne!(actual, unlimited); |
| 215 | + |
| 216 | + // With two levels, the root's direct children appear while their deeper |
| 217 | + // descendants do not. `subdirectory with a really long name` lives at depth 2 |
| 218 | + // and renders in the unlimited run, so its absence here is caused by the limit. |
| 219 | + assert!(actual.contains("foo")); |
| 220 | + assert!(!actual.contains("subdirectory with a really long name")); |
| 221 | + assert!(unlimited.contains("subdirectory with a really long name")); |
| 222 | +} |
| 223 | + |
| 224 | +#[test] |
| 225 | +fn json_input_min_ratio() { |
| 226 | + let actual = run_json_input(sample_tree(), &["--max-depth=10", "--min-ratio=0.1"]); |
| 227 | + eprintln!("ACTUAL:\n{actual}\n"); |
| 228 | + let unculled = run_json_input(sample_tree(), &["--max-depth=10", "--min-ratio=0"]); |
| 229 | + eprintln!("UNCULLED:\n{unculled}\n"); |
| 230 | + |
| 231 | + // Culling must change the output. |
| 232 | + assert_ne!(actual, unculled); |
| 233 | + |
| 234 | + // `foo` is far above the 10% threshold and survives, while `bar` is far below |
| 235 | + // it and is culled. `bar` renders in the unculled run, so its absence here is |
| 236 | + // caused by the culling. |
| 237 | + assert!(actual.contains("foo")); |
| 238 | + assert!(!actual.contains("bar")); |
| 239 | + assert!(unculled.contains("bar")); |
| 240 | +} |
| 241 | + |
| 242 | +#[test] |
| 243 | +fn json_input_no_sort() { |
| 244 | + let actual = run_json_input(ascending_sample_tree(), &["--no-sort", "--min-ratio=0"]); |
| 245 | + eprintln!("ACTUAL:\n{actual}\n"); |
| 246 | + let sorted = run_json_input(ascending_sample_tree(), &["--min-ratio=0"]); |
| 247 | + eprintln!("SORTED:\n{sorted}\n"); |
| 248 | + |
| 249 | + // Sorting must change the output. |
| 250 | + assert_ne!(actual, sorted); |
| 251 | + |
| 252 | + // `--no-sort` preserves the ascending input order `a, b, c`, whereas the |
| 253 | + // default sorts by descending size, so their relative positions flip. |
| 254 | + let position = |text: &str, name: &str| text.find(name).expect("entry must be present"); |
| 255 | + assert_op_expr!(position(&actual, "a"), >, position(&actual, "c")); |
| 256 | + assert_op_expr!(position(&sorted, "a"), <, position(&sorted, "c")); |
| 257 | +} |
| 258 | + |
154 | 259 | #[test] |
155 | 260 | fn json_output_json_input() { |
156 | 261 | let workspace = SampleWorkspace::default(); |
|
0 commit comments