Skip to content

Commit c931b42

Browse files
committed
refactor(test): replace fuse2fs with squashfuse for cross-device test
Use `mksquashfs` + `squashfuse` instead of `mkfs.ext2` + `fuse2fs`. The squashfs image is pre-built with the test file baked in, so the mount is read-only — which is sufficient since `pdu` only reads. This removes the `e2fsprogs` dependency and the need for `fakeroot` mount options. The new dependencies are `squashfs-tools` (for `mksquashfs`) and `squashfuse`. https://claude.ai/code/session_01LfpnUZrgq93MVZgA3KVqE6
1 parent a535b40 commit c931b42

1 file changed

Lines changed: 51 additions & 38 deletions

File tree

tests/one_file_system.rs

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
//!
88
//! ## Integration test via FUSE
99
//!
10-
//! [`cross_device_excludes_mount`] uses `fuse2fs` to mount an ext2 filesystem image via FUSE
10+
//! [`cross_device_excludes_mount`] uses `squashfuse` to mount a squashfs image via FUSE
1111
//! (no root or user namespaces required) and checks that `-x` correctly excludes entries on
1212
//! the mounted filesystem.
1313
//!
14-
//! The FUSE test panics when `fuse2fs`, `/dev/fuse`, or `fusermount` are unavailable.
15-
//! It can be excluded via `RUSTFLAGS='--cfg pdu_test_skip_cross_device'`.
14+
//! The FUSE test panics when `mksquashfs`, `squashfuse`, `/dev/fuse`, or `fusermount` are
15+
//! unavailable. It can be excluded via `RUSTFLAGS='--cfg pdu_test_skip_cross_device'`.
1616
1717
#![cfg(unix)]
1818
#![cfg(feature = "cli")]
@@ -71,27 +71,39 @@ struct FuseTools {
7171
fusermount: &'static str,
7272
}
7373

74-
/// Probes for `fuse2fs` and FUSE infrastructure.
74+
/// Probes for `squashfuse`, `mksquashfs`, and FUSE infrastructure.
7575
///
7676
/// Verifies:
77-
/// 1. `fuse2fs` binary exists
78-
/// 2. `/dev/fuse` is accessible
79-
/// 3. `fusermount` (or `fusermount3`) binary exists
77+
/// 1. `mksquashfs` binary exists
78+
/// 2. `squashfuse` binary exists
79+
/// 3. `/dev/fuse` is accessible
80+
/// 4. `fusermount` (or `fusermount3`) binary exists
8081
///
8182
/// Returns `Ok(FuseTools)` with the discovered tool paths, or `Err` with a diagnostic message.
8283
#[cfg(target_os = "linux")]
8384
#[cfg(not(pdu_test_skip_cross_device))]
8485
fn fuse_probe() -> Result<FuseTools, String> {
8586
use std::{path::Path, process::Command};
8687

87-
// Check that fuse2fs is installed
88-
Command::new("fuse2fs")
88+
// Check that mksquashfs is installed
89+
Command::new("mksquashfs")
90+
.arg("-version")
91+
.output()
92+
.map_err(|error| {
93+
format!(
94+
"`mksquashfs` not found: {error}. \
95+
Install via `apt install squashfs-tools`."
96+
)
97+
})?;
98+
99+
// Check that squashfuse is installed
100+
Command::new("squashfuse")
89101
.arg("--help")
90102
.output()
91103
.map_err(|error| {
92104
format!(
93-
"`fuse2fs` not found: {error}. \
94-
Install the `fuse2fs` package (or `e2fsprogs` on distros that bundle it)."
105+
"`squashfuse` not found: {error}. \
106+
Install via `apt install squashfuse`."
95107
)
96108
})?;
97109

@@ -111,9 +123,9 @@ fn fuse_probe() -> Result<FuseTools, String> {
111123
(true, _) => "fusermount",
112124
(_, true) => "fusermount3",
113125
_ => {
114-
return Err(
115-
"Neither `fusermount` nor `fusermount3` found. Install fuse or fuse3.".to_string(),
116-
);
126+
return Err("Neither `fusermount` nor `fusermount3` found. \
127+
Install via `apt install fuse3`."
128+
.to_string());
117129
}
118130
};
119131

@@ -122,8 +134,9 @@ fn fuse_probe() -> Result<FuseTools, String> {
122134

123135
/// When a subdirectory is a mount point for a different filesystem, `-x` should exclude it.
124136
///
125-
/// Uses `fuse2fs` to mount an ext2 filesystem image via FUSE — no root privileges or
126-
/// user namespaces required.
137+
/// Uses `squashfuse` to mount a squashfs image via FUSE — no root privileges or
138+
/// user namespaces required. The image is pre-built with `mksquashfs` containing the
139+
/// test file, so the mount is read-only (which is fine since `pdu` only reads).
127140
/// Skipped when FUSE infrastructure is unavailable.
128141
#[test]
129142
#[cfg(target_os = "linux")]
@@ -140,9 +153,10 @@ fn cross_device_excludes_mount() {
140153
let fuse_tools = match fuse_probe() {
141154
Ok(tools) => tools,
142155
Err(reason) => panic!(
143-
"error: This test requires FUSE (`fuse2fs`, `/dev/fuse`, `fusermount`) but the probe failed.\n\
156+
"error: This test requires FUSE (`mksquashfs`, `squashfuse`, `/dev/fuse`, \
157+
`fusermount`) but the probe failed.\n\
144158
reason: {reason}\n\
145-
hint: Install `fuse2fs` and `fuse3` packages, or set \
159+
hint: Install via `apt install squashfs-tools squashfuse fuse3`, or set \
146160
`RUSTFLAGS='--cfg pdu_test_skip_cross_device'` to skip this test.",
147161
),
148162
};
@@ -151,55 +165,54 @@ fn cross_device_excludes_mount() {
151165
let temp = Temp::new_dir().expect("create temp dir for cross-device test");
152166
let workspace = temp.join("workspace");
153167
let mount_point = workspace.join("mounted");
154-
let image_path = temp.join("ext2.img");
168+
let image_path = temp.join("squash.img");
169+
let staging_dir = temp.join("staging");
155170

156171
fs::create_dir_all(&mount_point).expect("create workspace and mount point");
172+
fs::create_dir_all(&staging_dir).expect("create staging directory");
157173

158174
// Write a file on the root filesystem
159175
let outside_content = "A".repeat(1000);
160176
fs::write(workspace.join("outside.txt"), &outside_content).expect("write outside.txt");
161177

162-
// Create a small ext2 filesystem image (4 MiB)
163-
let mkfs_output = Command::new("mkfs.ext2")
164-
.with_args(["-F", "-q"])
178+
// Create a file in the staging directory to be packed into the squashfs image
179+
let inside_content = "B".repeat(2000);
180+
fs::write(staging_dir.join("inside.txt"), &inside_content).expect("write staging/inside.txt");
181+
182+
// Build a squashfs image from the staging directory
183+
let mksquashfs_output = Command::new("mksquashfs")
184+
.with_arg(&staging_dir)
165185
.with_arg(&image_path)
166-
.with_arg("4096") // 4096 × 1K blocks = 4 MiB
186+
.with_args(["-noappend", "-quiet"])
167187
.with_stdout(Stdio::piped())
168188
.with_stderr(Stdio::piped())
169189
.output()
170-
.expect("run mkfs.ext2");
190+
.expect("run mksquashfs");
171191
assert!(
172-
mkfs_output.status.success(),
173-
"mkfs.ext2 failed: {}",
174-
String::from_utf8_lossy(&mkfs_output.stderr),
192+
mksquashfs_output.status.success(),
193+
"mksquashfs failed: {}",
194+
String::from_utf8_lossy(&mksquashfs_output.stderr),
175195
);
176196

177-
// Mount the image via fuse2fs
178-
let mount_output = Command::new("fuse2fs")
197+
// Mount the squashfs image via squashfuse (read-only)
198+
let mount_output = Command::new("squashfuse")
179199
.with_arg(&image_path)
180200
.with_arg(&mount_point)
181-
.with_args(["-o", "rw,fakeroot"])
182201
.with_stdout(Stdio::piped())
183202
.with_stderr(Stdio::piped())
184203
.output()
185-
.expect("run fuse2fs");
204+
.expect("run squashfuse");
186205
assert!(
187206
mount_output.status.success(),
188-
"fuse2fs mount failed: {}",
207+
"squashfuse mount failed: {}",
189208
String::from_utf8_lossy(&mount_output.stderr),
190209
);
191210

192211
// Small delay to let FUSE settle
193212
thread::sleep(Duration::from_millis(100));
194213

195-
// Write a file on the mounted (different) filesystem
196-
let inside_content = "B".repeat(2000);
197-
let write_result = fs::write(mount_point.join("inside.txt"), &inside_content);
198-
199214
// Ensure we unmount even if assertions fail
200215
let test_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
201-
write_result.expect("write inside.txt on mounted filesystem");
202-
203216
// Run pdu WITHOUT -x — should see both files
204217
let without_x = Command::new(pdu)
205218
.with_args(["--bytes-format=plain"])

0 commit comments

Comments
 (0)