Skip to content

Commit 38f6127

Browse files
committed
test: mixing exclusive-only and external-only
1 parent becf1ec commit 38f6127

2 files changed

Lines changed: 219 additions & 0 deletions

File tree

tests/hardlinks_deduplication.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,3 +675,168 @@ fn exclusive_hardlinks_only() {
675675
expected_hardlinks_summary.trim_end(),
676676
);
677677
}
678+
679+
#[test]
680+
fn exclusive_only_and_external_only_hardlinks() {
681+
let files_per_branch = 2 * 4;
682+
let workspace =
683+
SampleWorkspace::complex_tree_with_shared_and_unique_files(files_per_branch, 100_000);
684+
685+
let tree = Command::new(PDU)
686+
.with_current_dir(&workspace)
687+
.with_arg("--min-ratio=0")
688+
.with_arg("--quantity=apparent-size")
689+
.with_arg("--json-output")
690+
.with_arg("--deduplicate-hardlinks")
691+
.with_arg("only-hardlinks/mixed")
692+
.pipe(stdio)
693+
.output()
694+
.expect("spawn command")
695+
.pipe(stdout_text)
696+
.pipe_as_ref(serde_json::from_str::<JsonData>)
697+
.expect("parse stdout as JsonData")
698+
.body
699+
.pipe(JsonTree::<Bytes>::try_from)
700+
.expect("get tree of bytes");
701+
702+
let file_size = workspace
703+
.join("only-hardlinks/mixed/link0-0.txt")
704+
.pipe_as_ref(read_apparent_size)
705+
.pipe(Bytes::new);
706+
707+
let inode_size = |path: &str| {
708+
workspace
709+
.join(path)
710+
.pipe_as_ref(read_apparent_size)
711+
.pipe(Bytes::new)
712+
};
713+
714+
let file_inode = |name: &str| {
715+
workspace
716+
.join("only-hardlinks/mixed")
717+
.join(name)
718+
.pipe_as_ref(read_inode_number)
719+
.pipe(InodeNumber::from)
720+
};
721+
722+
let shared_paths = |file_names: &[&str]| {
723+
file_names
724+
.iter()
725+
.map(|file_name| PathBuf::from("only-hardlinks/mixed").join(file_name))
726+
.collect::<HashSet<_>>()
727+
.pipe(LinkPathListReflection)
728+
};
729+
730+
let actual_size = tree.size;
731+
let expected_size = inode_size("only-hardlinks/mixed") + file_size * files_per_branch;
732+
assert_eq!(actual_size, expected_size);
733+
734+
let actual_shared_details: Vec<_> = tree
735+
.shared
736+
.details
737+
.as_ref()
738+
.unwrap()
739+
.iter()
740+
.cloned()
741+
.collect();
742+
let expected_shared_details = iter::empty()
743+
.par_bridge()
744+
.chain(
745+
(0..(files_per_branch / 2))
746+
.par_bridge()
747+
.map(|index| ReflectionEntry {
748+
ino: file_inode(&format!("link0-{index}.txt")),
749+
size: file_size,
750+
links: 2,
751+
paths: shared_paths(&[&format!("link0-{index}.txt")]),
752+
}),
753+
)
754+
.chain(
755+
((files_per_branch / 2)..files_per_branch)
756+
.par_bridge()
757+
.map(|index| ReflectionEntry {
758+
ino: file_inode(&format!("link0-{index}.txt")),
759+
size: file_size,
760+
links: 2,
761+
paths: shared_paths(&[
762+
&format!("link0-{index}.txt"),
763+
&format!("link1-{index}.txt"),
764+
]),
765+
}),
766+
)
767+
.collect::<Vec<_>>()
768+
.into_sorted_by_key(|item: &ReflectionEntry<Bytes>| u64::from(item.ino));
769+
assert_eq!(actual_shared_details, expected_shared_details);
770+
771+
let actual_shared_summary = tree.shared.summary;
772+
let expected_shared_summary = Summary::default()
773+
.with_inodes(files_per_branch)
774+
.with_exclusive_inodes(files_per_branch / 2)
775+
.with_all_links(2 * files_per_branch as u64)
776+
.with_detected_links(files_per_branch + files_per_branch / 2)
777+
.with_exclusive_links(files_per_branch * 2 / 2)
778+
.with_shared_size(files_per_branch * file_size)
779+
.with_exclusive_shared_size((files_per_branch / 2) * file_size)
780+
.pipe(Some);
781+
assert_eq!(actual_shared_summary, expected_shared_summary);
782+
783+
let visualization = Command::new(PDU)
784+
.with_current_dir(&workspace)
785+
.with_arg("--quantity=apparent-size")
786+
.with_arg("--deduplicate-hardlinks")
787+
.with_arg("only-hardlinks/mixed")
788+
.pipe(stdio)
789+
.output()
790+
.expect("spawn command")
791+
.pipe(stdout_text);
792+
793+
eprintln!("STDOUT:\n{visualization}");
794+
795+
let actual_hardlinks_summary = visualization
796+
.lines()
797+
.skip_while(|line| !line.starts_with("Hardlinks detected!"))
798+
.join("\n");
799+
let expected_hardlinks_summary = {
800+
use parallel_disk_usage::size::Size;
801+
use std::fmt::Write;
802+
let mut summary = String::new();
803+
writeln!(
804+
summary,
805+
"Hardlinks detected! Some files have links outside this tree",
806+
)
807+
.unwrap();
808+
writeln!(
809+
summary,
810+
"* Number of shared inodes: {total} total, {exclusive} exclusive",
811+
total = expected_shared_summary.unwrap().inodes,
812+
exclusive = expected_shared_summary.unwrap().exclusive_inodes,
813+
)
814+
.unwrap();
815+
writeln!(
816+
summary,
817+
"* Total number of links: {total} total, {detected} detected, {exclusive} exclusive",
818+
total = expected_shared_summary.unwrap().all_links,
819+
detected = expected_shared_summary.unwrap().detected_links,
820+
exclusive = expected_shared_summary.unwrap().exclusive_links,
821+
)
822+
.unwrap();
823+
writeln!(
824+
summary,
825+
"* Total shared size: {total} total, {exclusive} exclusive",
826+
total = expected_shared_summary
827+
.unwrap()
828+
.shared_size
829+
.display(BytesFormat::MetricUnits),
830+
exclusive = expected_shared_summary
831+
.unwrap()
832+
.exclusive_shared_size
833+
.display(BytesFormat::MetricUnits),
834+
)
835+
.unwrap();
836+
summary
837+
};
838+
assert_eq!(
839+
actual_hardlinks_summary.trim_end(),
840+
expected_hardlinks_summary.trim_end(),
841+
);
842+
}

tests/hardlinks_without_deduplication.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,57 @@ fn exclusive_hardlinks_only() {
221221
assert_eq!(tree.shared.details, None);
222222
assert_eq!(tree.shared.summary, None);
223223
}
224+
225+
#[test]
226+
fn exclusive_only_and_external_only_hardlinks() {
227+
let files_per_branch = 2 * 4;
228+
let workspace =
229+
SampleWorkspace::complex_tree_with_shared_and_unique_files(files_per_branch, 100_000);
230+
231+
let tree = Command::new(PDU)
232+
.with_current_dir(&workspace)
233+
.with_arg("--min-ratio=0")
234+
.with_arg("--quantity=apparent-size")
235+
.with_arg("--json-output")
236+
.with_arg("only-hardlinks/mixed")
237+
.pipe(stdio)
238+
.output()
239+
.expect("spawn command")
240+
.pipe(stdout_text)
241+
.pipe_as_ref(serde_json::from_str::<JsonData>)
242+
.expect("parse stdout as JsonData")
243+
.body
244+
.pipe(JsonTree::<Bytes>::try_from)
245+
.expect("get tree of bytes");
246+
247+
let file_size = workspace
248+
.join("only-hardlinks/mixed/link0-0.txt")
249+
.pipe_as_ref(read_apparent_size)
250+
.pipe(Bytes::new);
251+
252+
let inode_size = |path: &str| {
253+
workspace
254+
.join(path)
255+
.pipe_as_ref(read_apparent_size)
256+
.pipe(Bytes::new)
257+
};
258+
259+
let actual_size = tree.size;
260+
let expected_size =
261+
inode_size("only-hardlinks/mixed") + file_size * (files_per_branch + files_per_branch / 2);
262+
assert_eq!(actual_size, expected_size);
263+
264+
assert_eq!(tree.shared.details, None);
265+
assert_eq!(tree.shared.summary, None);
266+
267+
let visualization = Command::new(PDU)
268+
.with_current_dir(&workspace)
269+
.with_arg("--quantity=apparent-size")
270+
.with_arg("only-hardlinks/mixed")
271+
.pipe(stdio)
272+
.output()
273+
.expect("spawn command")
274+
.pipe(stdout_text);
275+
eprintln!("STDOUT:\n{visualization}");
276+
assert!(!visualization.contains("Hardlinks detected!"));
277+
}

0 commit comments

Comments
 (0)