Skip to content

Commit 5c8b069

Browse files
committed
test: multiple hardlinks to a single file
1 parent de0dca8 commit 5c8b069

2 files changed

Lines changed: 115 additions & 1 deletion

File tree

tests/_utils.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rand::{distr::Alphanumeric, rng, Rng};
1616
use rayon::prelude::*;
1717
use std::{
1818
env::temp_dir,
19-
fs::{create_dir, metadata, remove_dir_all},
19+
fs::{create_dir, metadata, remove_dir_all, symlink_metadata},
2020
io::Error,
2121
path::{Path, PathBuf},
2222
process::{Command, Output},
@@ -96,6 +96,28 @@ impl Default for SampleWorkspace {
9696
}
9797
}
9898

99+
/// POSIX-exclusive functions
100+
#[cfg(unix)]
101+
impl SampleWorkspace {
102+
/// Set up a temporary directory for tests.
103+
///
104+
/// This directory would have a single file being hard-linked multiple times.
105+
pub fn multiple_hardlinks_to_a_single_file() -> Self {
106+
use std::fs::{hard_link, write as write_file};
107+
let temp = Temp::new_dir().expect("create working directory for sample workspace");
108+
109+
let file_path = temp.join("file.txt");
110+
write_file(&file_path, "a".repeat(100_000)).expect("create file.txt");
111+
112+
for num in 0..10 {
113+
hard_link(&file_path, temp.join(format!("link.{num}")))
114+
.unwrap_or_else(|error| panic!("Failed to create 'link.{num}': {error}"));
115+
}
116+
117+
SampleWorkspace(temp)
118+
}
119+
}
120+
99121
/// Make the snapshot of a [`TreeReflection`] testable.
100122
///
101123
/// The real filesystem is often messy, causing `children` to mess up its order.
@@ -332,3 +354,10 @@ pub fn inspect_stderr(stderr: &[u8]) {
332354
eprintln!("STDERR:\n{text}\n");
333355
}
334356
}
357+
358+
/// Read [apparent size](std::fs::Metadata::len) of a path.
359+
pub fn read_apparent_size(path: &Path) -> u64 {
360+
path.pipe(symlink_metadata)
361+
.unwrap_or_else(|error| panic!("Can't read metadata at {path:?}: {error}"))
362+
.len()
363+
}

tests/deduplicate_hardlinks.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#![cfg(unix)] // This feature is not available in Windows
2+
#![cfg(feature = "cli")]
3+
4+
pub mod _utils;
5+
pub use _utils::*;
6+
7+
use command_extra::CommandExtra;
8+
use parallel_disk_usage::{
9+
json_data::{JsonData, UnitAndTree},
10+
size::Bytes,
11+
};
12+
use pipe_trait::Pipe;
13+
use pretty_assertions::assert_eq;
14+
use std::{
15+
ops::{Add, Mul},
16+
process::{Command, Stdio},
17+
};
18+
19+
fn stdio(command: Command) -> Command {
20+
command
21+
.with_stdin(Stdio::null())
22+
.with_stdout(Stdio::piped())
23+
.with_stderr(Stdio::piped())
24+
}
25+
26+
#[test]
27+
fn deduplicate_multiple_hardlinks_to_a_single_file() {
28+
let workspace = SampleWorkspace::multiple_hardlinks_to_a_single_file();
29+
30+
let json = Command::new(PDU)
31+
.with_current_dir(&workspace)
32+
.with_arg("--quantity=apparent-size")
33+
.with_arg("--deduplicate-hardlinks")
34+
.with_arg("--json-output")
35+
.pipe(stdio)
36+
.output()
37+
.expect("spawn command")
38+
.pipe(stdout_text)
39+
.pipe_as_ref(serde_json::from_str::<JsonData>)
40+
.expect("parse stdout as JsonData");
41+
42+
let actual_size = match &json.unit_and_tree {
43+
UnitAndTree::Blocks(_) => panic!("expecting Bytes, but got {:?}", &json.unit_and_tree),
44+
UnitAndTree::Bytes(tree) => tree.size,
45+
};
46+
47+
let expected_size = workspace
48+
.join("file.txt")
49+
.pipe_as_ref(read_apparent_size)
50+
.mul(2) // TODO: fix the algorithm and remove this line
51+
.add(read_apparent_size(&workspace))
52+
.pipe(Bytes::new);
53+
54+
assert_eq!(actual_size, expected_size);
55+
}
56+
57+
#[test]
58+
fn do_not_deduplicate_multiple_hardlinks_to_a_single_file() {
59+
let workspace = SampleWorkspace::multiple_hardlinks_to_a_single_file();
60+
61+
let json = Command::new(PDU)
62+
.with_current_dir(&workspace)
63+
.with_arg("--quantity=apparent-size")
64+
.with_arg("--json-output")
65+
.pipe(stdio)
66+
.output()
67+
.expect("spawn command")
68+
.pipe(stdout_text)
69+
.pipe_as_ref(serde_json::from_str::<JsonData>)
70+
.expect("parse stdout as JsonData");
71+
72+
let actual_size = match &json.unit_and_tree {
73+
UnitAndTree::Blocks(_) => panic!("expecting Bytes, but got {:?}", &json.unit_and_tree),
74+
UnitAndTree::Bytes(tree) => tree.size,
75+
};
76+
77+
let expected_size = workspace
78+
.join("file.txt")
79+
.pipe_as_ref(read_apparent_size)
80+
.mul(11)
81+
.add(read_apparent_size(&workspace))
82+
.pipe(Bytes::new);
83+
84+
assert_eq!(actual_size, expected_size);
85+
}

0 commit comments

Comments
 (0)