Skip to content

Commit becf1ec

Browse files
committed
test: exclusive hardlinks only
1 parent 5d5a0c6 commit becf1ec

2 files changed

Lines changed: 172 additions & 0 deletions

File tree

tests/hardlinks_deduplication.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use parallel_disk_usage::{
2020
};
2121
use pipe_trait::Pipe;
2222
use pretty_assertions::assert_eq;
23+
use rayon::prelude::*;
2324
use std::{
2425
collections::HashSet,
2526
iter,
@@ -546,3 +547,131 @@ fn hardlinks_and_non_hardlinks() {
546547
expected_hardlinks_summary.trim_end(),
547548
);
548549
}
550+
551+
#[test]
552+
fn exclusive_hardlinks_only() {
553+
let files_per_branch = 2 * 4;
554+
let workspace =
555+
SampleWorkspace::complex_tree_with_shared_and_unique_files(files_per_branch, 100_000);
556+
557+
let tree = Command::new(PDU)
558+
.with_current_dir(&workspace)
559+
.with_arg("--min-ratio=0")
560+
.with_arg("--quantity=apparent-size")
561+
.with_arg("--json-output")
562+
.with_arg("--deduplicate-hardlinks")
563+
.with_arg("only-hardlinks/exclusive")
564+
.pipe(stdio)
565+
.output()
566+
.expect("spawn command")
567+
.pipe(stdout_text)
568+
.pipe_as_ref(serde_json::from_str::<JsonData>)
569+
.expect("parse stdout as JsonData")
570+
.body
571+
.pipe(JsonTree::<Bytes>::try_from)
572+
.expect("get tree of bytes");
573+
574+
let file_size = workspace
575+
.join("only-hardlinks/exclusive/file-0.txt")
576+
.pipe_as_ref(read_apparent_size)
577+
.pipe(Bytes::new);
578+
579+
let inode_size = |path: &str| {
580+
workspace
581+
.join(path)
582+
.pipe_as_ref(read_apparent_size)
583+
.pipe(Bytes::new)
584+
};
585+
586+
let file_inode = |name: &str| {
587+
workspace
588+
.join("only-hardlinks/exclusive")
589+
.join(name)
590+
.pipe_as_ref(read_inode_number)
591+
.pipe(InodeNumber::from)
592+
};
593+
594+
let shared_paths = |file_names: &[&str]| {
595+
file_names
596+
.iter()
597+
.map(|file_name| PathBuf::from("only-hardlinks/exclusive").join(file_name))
598+
.collect::<HashSet<_>>()
599+
.pipe(LinkPathListReflection)
600+
};
601+
602+
let actual_size = tree.size;
603+
let expected_size = inode_size("only-hardlinks/exclusive") + file_size * files_per_branch;
604+
assert_eq!(actual_size, expected_size);
605+
606+
let actual_shared_details: Vec<_> = tree
607+
.shared
608+
.details
609+
.as_ref()
610+
.unwrap()
611+
.iter()
612+
.cloned()
613+
.collect();
614+
let expected_shared_details = (0..files_per_branch)
615+
.par_bridge()
616+
.map(|index| ReflectionEntry {
617+
ino: file_inode(&format!("file-{index}.txt")),
618+
size: file_size,
619+
links: 2,
620+
paths: shared_paths(&[&format!("file-{index}.txt"), &format!("link-{index}.txt")]),
621+
})
622+
.collect::<Vec<_>>()
623+
.into_sorted_by_key(|item: &ReflectionEntry<Bytes>| u64::from(item.ino));
624+
assert_eq!(actual_shared_details, expected_shared_details);
625+
626+
let actual_shared_summary = tree.shared.summary;
627+
let expected_shared_summary = Summary::default()
628+
.with_inodes(files_per_branch)
629+
.with_exclusive_inodes(files_per_branch)
630+
.with_all_links(2 * files_per_branch as u64)
631+
.with_detected_links(2 * files_per_branch)
632+
.with_exclusive_links(2 * files_per_branch)
633+
.with_shared_size(files_per_branch * file_size)
634+
.with_exclusive_shared_size(files_per_branch * file_size)
635+
.pipe(Some);
636+
assert_eq!(actual_shared_summary, expected_shared_summary);
637+
638+
let visualization = Command::new(PDU)
639+
.with_current_dir(&workspace)
640+
.with_arg("--quantity=apparent-size")
641+
.with_arg("--deduplicate-hardlinks")
642+
.with_arg("only-hardlinks/exclusive")
643+
.pipe(stdio)
644+
.output()
645+
.expect("spawn command")
646+
.pipe(stdout_text);
647+
648+
eprintln!("STDOUT:\n{visualization}");
649+
650+
let actual_hardlinks_summary = visualization
651+
.lines()
652+
.skip_while(|line| !line.starts_with("Hardlinks detected!"))
653+
.join("\n");
654+
let expected_hardlinks_summary = {
655+
use parallel_disk_usage::size::Size;
656+
use std::fmt::Write;
657+
let mut summary = String::new();
658+
writeln!(
659+
summary,
660+
"Hardlinks detected! No files have links outside this tree",
661+
)
662+
.unwrap();
663+
writeln!(summary, "* Number of shared inodes: {files_per_branch}").unwrap();
664+
writeln!(summary, "* Total number of links: {}", 2 * files_per_branch).unwrap();
665+
writeln!(
666+
summary,
667+
"* Total shared size: {}",
668+
(file_size * files_per_branch).display(BytesFormat::MetricUnits),
669+
)
670+
.unwrap();
671+
summary
672+
};
673+
assert_eq!(
674+
actual_hardlinks_summary.trim_end(),
675+
expected_hardlinks_summary.trim_end(),
676+
);
677+
}

tests/hardlinks_without_deduplication.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,46 @@ fn hardlinks_and_non_hardlinks() {
178178
eprintln!("STDOUT:\n{visualization}");
179179
assert!(!visualization.contains("Hardlinks detected!"));
180180
}
181+
182+
#[test]
183+
fn exclusive_hardlinks_only() {
184+
let files_per_branch = 2 * 4;
185+
let workspace =
186+
SampleWorkspace::complex_tree_with_shared_and_unique_files(files_per_branch, 100_000);
187+
188+
let tree = Command::new(PDU)
189+
.with_current_dir(&workspace)
190+
.with_arg("--min-ratio=0")
191+
.with_arg("--quantity=apparent-size")
192+
.with_arg("--json-output")
193+
.with_arg("only-hardlinks/exclusive")
194+
.pipe(stdio)
195+
.output()
196+
.expect("spawn command")
197+
.pipe(stdout_text)
198+
.pipe_as_ref(serde_json::from_str::<JsonData>)
199+
.expect("parse stdout as JsonData")
200+
.body
201+
.pipe(JsonTree::<Bytes>::try_from)
202+
.expect("get tree of bytes");
203+
204+
let file_size = workspace
205+
.join("only-hardlinks/exclusive/file-0.txt")
206+
.pipe_as_ref(read_apparent_size)
207+
.pipe(Bytes::new);
208+
209+
let inode_size = |path: &str| {
210+
workspace
211+
.join(path)
212+
.pipe_as_ref(read_apparent_size)
213+
.pipe(Bytes::new)
214+
};
215+
216+
let actual_size = tree.size;
217+
let expected_size =
218+
inode_size("only-hardlinks/exclusive") + 2usize * file_size * files_per_branch;
219+
assert_eq!(actual_size, expected_size);
220+
221+
assert_eq!(tree.shared.details, None);
222+
assert_eq!(tree.shared.summary, None);
223+
}

0 commit comments

Comments
 (0)