Skip to content

Commit 7cd78a4

Browse files
committed
test: external hardlinks only
1 parent f3094ea commit 7cd78a4

2 files changed

Lines changed: 190 additions & 0 deletions

File tree

tests/hardlinks_deduplication.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,3 +840,140 @@ fn exclusive_only_and_external_only_hardlinks() {
840840
expected_hardlinks_summary.trim_end(),
841841
);
842842
}
843+
844+
#[test]
845+
fn external_hardlinks_only() {
846+
let files_per_branch = 2 * 4;
847+
let workspace =
848+
SampleWorkspace::complex_tree_with_shared_and_unique_files(files_per_branch, 100_000);
849+
850+
let tree = Command::new(PDU)
851+
.with_current_dir(&workspace)
852+
.with_arg("--min-ratio=0")
853+
.with_arg("--quantity=apparent-size")
854+
.with_arg("--json-output")
855+
.with_arg("--deduplicate-hardlinks")
856+
.with_arg("only-hardlinks/external")
857+
.pipe(stdio)
858+
.output()
859+
.expect("spawn command")
860+
.pipe(stdout_text)
861+
.pipe_as_ref(serde_json::from_str::<JsonData>)
862+
.expect("parse stdout as JsonData")
863+
.body
864+
.pipe(JsonTree::<Bytes>::try_from)
865+
.expect("get tree of bytes");
866+
867+
let file_size = workspace
868+
.join("only-hardlinks/external/linkX-0.txt")
869+
.pipe_as_ref(read_apparent_size)
870+
.pipe(Bytes::new);
871+
872+
let inode_size = |path: &str| {
873+
workspace
874+
.join(path)
875+
.pipe_as_ref(read_apparent_size)
876+
.pipe(Bytes::new)
877+
};
878+
879+
let file_inode = |name: &str| {
880+
workspace
881+
.join("only-hardlinks/external")
882+
.join(name)
883+
.pipe_as_ref(read_inode_number)
884+
.pipe(InodeNumber::from)
885+
};
886+
887+
let shared_paths = |file_names: &[&str]| {
888+
file_names
889+
.iter()
890+
.map(|file_name| PathBuf::from("only-hardlinks/external").join(file_name))
891+
.collect::<HashSet<_>>()
892+
.pipe(LinkPathListReflection)
893+
};
894+
895+
let actual_size = tree.size;
896+
let expected_size = inode_size("only-hardlinks/external") + file_size * files_per_branch;
897+
assert_eq!(actual_size, expected_size);
898+
899+
let actual_shared_details: Vec<_> = tree
900+
.shared
901+
.details
902+
.as_ref()
903+
.unwrap()
904+
.iter()
905+
.cloned()
906+
.collect();
907+
let expected_shared_details = (0..files_per_branch)
908+
.par_bridge()
909+
.map(|index| ReflectionEntry {
910+
ino: file_inode(&format!("linkX-{index}.txt")),
911+
size: file_size,
912+
links: 2,
913+
paths: shared_paths(&[&format!("linkX-{index}.txt")]),
914+
})
915+
.collect::<Vec<_>>()
916+
.into_sorted_by_key(|item: &ReflectionEntry<Bytes>| u64::from(item.ino));
917+
assert_eq!(actual_shared_details, expected_shared_details);
918+
919+
let actual_shared_summary = tree.shared.summary;
920+
let expected_shared_summary = Summary::default()
921+
.with_inodes(files_per_branch)
922+
.with_exclusive_inodes(0)
923+
.with_all_links(2 * files_per_branch as u64)
924+
.with_detected_links(files_per_branch)
925+
.with_exclusive_links(0)
926+
.with_shared_size(files_per_branch * file_size)
927+
.with_exclusive_shared_size(Bytes::new(0))
928+
.pipe(Some);
929+
assert_eq!(actual_shared_summary, expected_shared_summary);
930+
931+
let visualization = Command::new(PDU)
932+
.with_current_dir(&workspace)
933+
.with_arg("--quantity=apparent-size")
934+
.with_arg("--deduplicate-hardlinks")
935+
.with_arg("only-hardlinks/external")
936+
.pipe(stdio)
937+
.output()
938+
.expect("spawn command")
939+
.pipe(stdout_text);
940+
941+
eprintln!("STDOUT:\n{visualization}");
942+
943+
let actual_hardlinks_summary = visualization
944+
.lines()
945+
.skip_while(|line| !line.starts_with("Hardlinks detected!"))
946+
.join("\n");
947+
let expected_hardlinks_summary = {
948+
use parallel_disk_usage::size::Size;
949+
use std::fmt::Write;
950+
let mut summary = String::new();
951+
writeln!(
952+
summary,
953+
"Hardlinks detected! All hardlinks within this tree have links without",
954+
)
955+
.unwrap();
956+
writeln!(summary, "* Number of shared inodes: {files_per_branch}").unwrap();
957+
writeln!(
958+
summary,
959+
"* Total number of links: {total} total, {detected} detected",
960+
total = expected_shared_summary.unwrap().all_links,
961+
detected = expected_shared_summary.unwrap().detected_links,
962+
)
963+
.unwrap();
964+
writeln!(
965+
summary,
966+
"* Total shared size: {}",
967+
expected_shared_summary
968+
.unwrap()
969+
.shared_size
970+
.display(BytesFormat::MetricUnits),
971+
)
972+
.unwrap();
973+
summary
974+
};
975+
assert_eq!(
976+
actual_hardlinks_summary.trim_end(),
977+
expected_hardlinks_summary.trim_end(),
978+
);
979+
}

tests/hardlinks_without_deduplication.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,56 @@ fn exclusive_only_and_external_only_hardlinks() {
286286
eprintln!("STDOUT:\n{visualization}");
287287
assert!(!visualization.contains("Hardlinks detected!"));
288288
}
289+
290+
#[test]
291+
fn external_hardlinks_only() {
292+
let files_per_branch = 2 * 4;
293+
let workspace =
294+
SampleWorkspace::complex_tree_with_shared_and_unique_files(files_per_branch, 100_000);
295+
296+
let tree = Command::new(PDU)
297+
.with_current_dir(&workspace)
298+
.with_arg("--min-ratio=0")
299+
.with_arg("--quantity=apparent-size")
300+
.with_arg("--json-output")
301+
.with_arg("only-hardlinks/external")
302+
.pipe(stdio)
303+
.output()
304+
.expect("spawn command")
305+
.pipe(stdout_text)
306+
.pipe_as_ref(serde_json::from_str::<JsonData>)
307+
.expect("parse stdout as JsonData")
308+
.body
309+
.pipe(JsonTree::<Bytes>::try_from)
310+
.expect("get tree of bytes");
311+
312+
let file_size = workspace
313+
.join("only-hardlinks/external/linkX-0.txt")
314+
.pipe_as_ref(read_apparent_size)
315+
.pipe(Bytes::new);
316+
317+
let inode_size = |path: &str| {
318+
workspace
319+
.join(path)
320+
.pipe_as_ref(read_apparent_size)
321+
.pipe(Bytes::new)
322+
};
323+
324+
let actual_size = tree.size;
325+
let expected_size = inode_size("only-hardlinks/external") + file_size * files_per_branch;
326+
assert_eq!(actual_size, expected_size);
327+
328+
assert_eq!(tree.shared.details, None);
329+
assert_eq!(tree.shared.summary, None);
330+
331+
let visualization = Command::new(PDU)
332+
.with_current_dir(&workspace)
333+
.with_arg("--quantity=apparent-size")
334+
.with_arg("only-hardlinks/external")
335+
.pipe(stdio)
336+
.output()
337+
.expect("spawn command")
338+
.pipe(stdout_text);
339+
eprintln!("STDOUT:\n{visualization}");
340+
assert!(!visualization.contains("Hardlinks detected!"));
341+
}

0 commit comments

Comments
 (0)