diff --git a/src/app/sub.rs b/src/app/sub.rs index 314ddaf4..2298998c 100644 --- a/src/app/sub.rs +++ b/src/app/sub.rs @@ -96,13 +96,14 @@ where let data_tree = if iter.len() == 0 { data_tree } else { + let max_depth = max_depth.get(); let children: Vec<_> = once(data_tree).chain(iter).collect(); DataTree::dir( OsStringDisplay::os_string_from("(total)"), Size::default(), children, - max_depth.get(), ) + .into_par_retained(|_, depth| depth + 1 < max_depth) }; if reporter.destroy().is_err() { diff --git a/src/data_tree/constructors.rs b/src/data_tree/constructors.rs index 36eca434..8de267b4 100644 --- a/src/data_tree/constructors.rs +++ b/src/data_tree/constructors.rs @@ -3,9 +3,8 @@ use crate::size; impl DataTree { /// Create a tree representation of a directory. - pub fn dir(name: Name, inode_size: Size, children: Vec, depth: u64) -> Self { + pub fn dir(name: Name, inode_size: Size, children: Vec) -> Self { let size = inode_size + children.iter().map(DataTree::size).sum(); - let children = if depth > 0 { children } else { Vec::new() }; DataTree { name, size, @@ -27,6 +26,11 @@ impl DataTree { where Size: Copy, { - move |name, children| DataTree::dir(name, inode_size, children, 1) + move |name, children| DataTree::dir(name, inode_size, children) + } + + /// Remove the children. Free memory usage. + pub(crate) fn drop_children(&mut self) { + self.children = Vec::new(); } } diff --git a/src/data_tree/retain/test.rs b/src/data_tree/retain/test.rs index c5a09851..2c8e7fef 100644 --- a/src/data_tree/retain/test.rs +++ b/src/data_tree/retain/test.rs @@ -7,7 +7,7 @@ type SampleData = Bytes; type SampleTree = DataTree; fn dir(name: &'static str, children: Vec) -> SampleTree { - SampleTree::dir(name.to_string(), INODE_SIZE.into(), children, 10) + SampleTree::dir(name.to_string(), INODE_SIZE.into(), children) } fn file(name: &'static str, size: u64) -> SampleTree { @@ -23,7 +23,6 @@ fn culled_dir( name.to_string(), (INODE_SIZE + culled_size).into(), children, - 10, ) } diff --git a/src/tree_builder.rs b/src/tree_builder.rs index e228f84f..c050cd04 100644 --- a/src/tree_builder.rs +++ b/src/tree_builder.rs @@ -59,8 +59,13 @@ where max_depth, }) .map(Self::from) - .collect(); + .collect(); // TODO: this collect can be called into different types depending on whether `max_depth` is 0 - DataTree::dir(name, size, children, max_depth) + let mut tree = DataTree::dir(name, size, children); + if max_depth == 0 { + // TODO: replace this with a more memory efficient method that doesn't require constructing `children` in the first place + tree.drop_children(); + } + tree } } diff --git a/tests/_utils.rs b/tests/_utils.rs index 378fa90c..0d228cb1 100644 --- a/tests/_utils.rs +++ b/tests/_utils.rs @@ -77,13 +77,13 @@ impl Default for SampleWorkspace { MergeableFileSystemTree::<&str, String>::from(dir! { "flat" => dir! { "0" => file!("") - "1" => file!("a".repeat(1000)) - "2" => file!("a".repeat(2000)) - "3" => file!("a".repeat(3000)) + "1" => file!("a".repeat(100_000)) + "2" => file!("a".repeat(200_000)) + "3" => file!("a".repeat(300_000)) } "nested" => dir! { "0" => dir! { - "1" => file!("a".repeat(5000)) + "1" => file!("a".repeat(500_000)) } } "empty-dir" => dir! {} diff --git a/tests/json.rs b/tests/json.rs index 41d9248d..6c29ef93 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -30,7 +30,7 @@ type SampleTree = DataTree; fn sample_tree() -> SampleTree { let dir = |name: &'static str, children: Vec| { - SampleTree::dir(name.to_string(), 1024.into(), children, 10) + SampleTree::dir(name.to_string(), 1024.into(), children) }; let file = |name: &'static str, size: u64| SampleTree::file(name.to_string(), Bytes::from(size)); diff --git a/tests/usual_cli.rs b/tests/usual_cli.rs index d4ec8cb7..f9b0f3b7 100644 --- a/tests/usual_cli.rs +++ b/tests/usual_cli.rs @@ -623,7 +623,6 @@ fn multiple_names() { OsStringDisplay::os_string_from("(total)"), 0.into(), children.collect(), - 10, ) }) .into_par_sorted(|left, right| left.size().cmp(&right.size()).reverse()); @@ -640,4 +639,132 @@ fn multiple_names() { eprintln!("EXPECTED:\n{expected}\n"); assert_eq!(actual, expected); + + let mut lines = actual.lines(); + assert!(lines.next().unwrap().contains("┌──1")); + assert!(lines.next().unwrap().contains("┌─┴0")); + assert!(lines.next().unwrap().contains("┌─┴nested")); + assert!(lines.next().unwrap().contains("│ ┌──1")); + assert!(lines.next().unwrap().contains("│ ├──2")); + assert!(lines.next().unwrap().contains("│ ├──3")); + assert!(lines.next().unwrap().contains("├─┴flat")); + assert!(lines.next().unwrap().contains("┌─┴(total)")); + assert_eq!(lines.next(), None); +} + +#[test] +fn multiple_names_max_depth_2() { + let workspace = SampleWorkspace::default(); + let actual = Command::new(PDU) + .with_current_dir(&workspace) + .with_arg("--quantity=apparent-size") + .with_arg("--total-width=100") + .with_arg("--max-depth=2") + .with_arg("nested") + .with_arg("flat") + .with_arg("empty-dir") + .pipe(stdio) + .output() + .expect("spawn command") + .pipe(stdout_text); + eprintln!("ACTUAL:\n{actual}\n"); + + let mut data_tree = ["nested", "flat", "empty-dir"] + .iter() + .map(|name| { + let builder = FsTreeBuilder { + root: workspace.to_path_buf().join(name), + size_getter: GetApparentSize, + reporter: ErrorOnlyReporter::new(ErrorReport::SILENT), + max_depth: 1, + }; + let mut data_tree: DataTree = builder.into(); + *data_tree.name_mut() = OsStringDisplay::os_string_from(name); + data_tree + }) + .pipe(|children| { + DataTree::dir( + OsStringDisplay::os_string_from("(total)"), + 0.into(), + children.collect(), + ) + }) + .into_par_sorted(|left, right| left.size().cmp(&right.size()).reverse()); + data_tree.par_cull_insignificant_data(0.01); + let visualizer = Visualizer:: { + data_tree: &data_tree, + bytes_format: BytesFormat::MetricUnits, + direction: Direction::BottomUp, + bar_alignment: BarAlignment::Left, + column_width_distribution: ColumnWidthDistribution::total(100), + }; + let expected = format!("{visualizer}"); + let expected = expected.trim_end(); + eprintln!("EXPECTED:\n{expected}\n"); + + assert_eq!(actual, expected); + + let mut lines = actual.lines(); + assert!(lines.next().unwrap().contains("┌──nested")); + assert!(lines.next().unwrap().contains("├──flat")); + assert!(lines.next().unwrap().contains("┌─┴(total)")); + assert_eq!(lines.next(), None); +} + +#[test] +fn multiple_names_max_depth_1() { + let workspace = SampleWorkspace::default(); + let actual = Command::new(PDU) + .with_current_dir(&workspace) + .with_arg("--quantity=apparent-size") + .with_arg("--total-width=100") + .with_arg("--max-depth=1") + .with_arg("nested") + .with_arg("flat") + .with_arg("empty-dir") + .pipe(stdio) + .output() + .expect("spawn command") + .pipe(stdout_text); + eprintln!("ACTUAL:\n{actual}\n"); + + let mut data_tree = ["nested", "flat", "empty-dir"] + .iter() + .map(|name| { + let builder = FsTreeBuilder { + root: workspace.to_path_buf().join(name), + size_getter: GetApparentSize, + reporter: ErrorOnlyReporter::new(ErrorReport::SILENT), + max_depth: 10, + }; + let mut data_tree: DataTree = builder.into(); + *data_tree.name_mut() = OsStringDisplay::os_string_from(name); + data_tree + }) + .pipe(|children| { + DataTree::dir( + OsStringDisplay::os_string_from("(total)"), + 0.into(), + children.collect(), + ) + }) + .into_par_retained(|_, _| false) + .into_par_sorted(|left, right| left.size().cmp(&right.size()).reverse()); + data_tree.par_cull_insignificant_data(0.01); + let visualizer = Visualizer:: { + data_tree: &data_tree, + bytes_format: BytesFormat::MetricUnits, + direction: Direction::BottomUp, + bar_alignment: BarAlignment::Left, + column_width_distribution: ColumnWidthDistribution::total(100), + }; + let expected = format!("{visualizer}"); + let expected = expected.trim_end(); + eprintln!("EXPECTED:\n{expected}\n"); + + assert_eq!(actual, expected); + + let mut lines = actual.lines(); + assert!(lines.next().unwrap().contains("┌──(total)")); + assert_eq!(lines.next(), None); } diff --git a/tests/visualizer.rs b/tests/visualizer.rs index 5e346017..1a158cb9 100644 --- a/tests/visualizer.rs +++ b/tests/visualizer.rs @@ -403,7 +403,7 @@ fn nested_tree( ) -> DataTree<&'static str, Size> { if let Some((head, tail)) = dir_names.split_first() { let child = nested_tree(tail, size_per_dir, file_name, file_size); - DataTree::dir(*head, size_per_dir, vec![child], 10) + DataTree::dir(*head, size_per_dir, vec![child]) } else { DataTree::file(file_name, file_size) } @@ -649,7 +649,7 @@ fn empty_dir(inode_size: Size) -> DataTree<&'static str, Size> where Size: size::Size + Ord + From + Send, { - DataTree::dir("empty directory", inode_size, Vec::new(), 10).into_par_sorted(order_tree) + DataTree::dir("empty directory", inode_size, Vec::new()).into_par_sorted(order_tree) } test_case! {