#![cfg(unix)]
use std::process::Command;
use tempfile::NamedTempFile;
const FSTOOL: &str = env!("CARGO_BIN_EXE_fstool");
fn which(tool: &str) -> bool {
Command::new(tool)
.arg("--version")
.output()
.ok()
.is_some_and(|o| o.status.success())
}
#[test]
fn repack_into_tar_gz_then_inspect() {
if !which("mke2fs") {
eprintln!("skipping: mke2fs not installed");
return;
}
let srcdir = tempfile::tempdir().unwrap();
std::fs::write(srcdir.path().join("hello.txt"), b"hello compressed\n").unwrap();
std::fs::create_dir(srcdir.path().join("sub")).unwrap();
std::fs::write(srcdir.path().join("sub/nested.txt"), b"nested body\n").unwrap();
let src_img = NamedTempFile::new().unwrap();
let out = Command::new(FSTOOL)
.args([
"create",
"-t",
"ext2",
srcdir.path().to_str().unwrap(),
"-o",
src_img.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(
out.status.success(),
"create failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let tarball = tempfile::Builder::new()
.suffix(".tar.gz")
.tempfile()
.unwrap();
let out = Command::new(FSTOOL)
.args([
"repack",
src_img.path().to_str().unwrap(),
tarball.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(
out.status.success(),
"repack ext2 → tar.gz failed:\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
let magic = std::fs::read(tarball.path()).unwrap();
assert!(
magic.len() >= 2 && magic[0] == 0x1f && magic[1] == 0x8b,
"expected gzip magic at start of {}",
tarball.path().display()
);
let out = Command::new(FSTOOL)
.args(["ls", tarball.path().to_str().unwrap(), "/"])
.output()
.unwrap();
assert!(
out.status.success(),
"fstool ls on .tar.gz failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let listing = String::from_utf8_lossy(&out.stdout);
assert!(
listing.contains("hello.txt"),
"expected hello.txt in listing:\n{listing}"
);
let out = Command::new(FSTOOL)
.args(["cat", tarball.path().to_str().unwrap(), "/hello.txt"])
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(out.stdout, b"hello compressed\n");
}
#[test]
fn repack_into_tar_lz4_interops_with_lz4_cli() {
if !which("mke2fs") {
eprintln!("skipping: mke2fs not installed");
return;
}
let srcdir = tempfile::tempdir().unwrap();
std::fs::write(srcdir.path().join("hello.txt"), b"hello lz4 frame\n").unwrap();
let src_img = NamedTempFile::new().unwrap();
let out = Command::new(FSTOOL)
.args([
"create",
"-t",
"ext2",
srcdir.path().to_str().unwrap(),
"-o",
src_img.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(
out.status.success(),
"create: {}",
String::from_utf8_lossy(&out.stderr)
);
let tarball = tempfile::Builder::new()
.suffix(".tar.lz4")
.tempfile()
.unwrap();
let out = Command::new(FSTOOL)
.args([
"repack",
src_img.path().to_str().unwrap(),
tarball.path().to_str().unwrap(),
])
.output()
.unwrap();
assert!(
out.status.success(),
"repack →.tar.lz4: {}",
String::from_utf8_lossy(&out.stderr)
);
let bytes = std::fs::read(tarball.path()).unwrap();
assert_eq!(
&bytes[0..4],
&[0x04, 0x22, 0x4d, 0x18],
"expected LZ4 frame magic"
);
if which("lz4") {
let t = Command::new("lz4")
.arg("-t")
.arg(tarball.path())
.output()
.unwrap();
assert!(
t.status.success(),
"lz4 -t rejected fstool's frame: {}",
String::from_utf8_lossy(&t.stderr)
);
}
let out = Command::new(FSTOOL)
.args(["cat", tarball.path().to_str().unwrap(), "/hello.txt"])
.output()
.unwrap();
assert!(
out.status.success(),
"cat .tar.lz4: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(out.stdout, b"hello lz4 frame\n");
}
fn xz_pipe(args: &[&str], input: &[u8]) -> Vec<u8> {
use std::io::Write;
use std::process::Stdio;
let mut child = Command::new("xz")
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn xz");
child
.stdin
.take()
.unwrap()
.write_all(input)
.expect("write to xz stdin");
let out = child.wait_with_output().expect("wait xz");
assert!(
out.status.success(),
"xz {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
out.stdout
}
#[test]
fn lzma_and_xz_interoperate_with_xz_cli() {
use fstool::compression::{Algo, compress, decompress};
if !which("xz") {
eprintln!("skipping: xz not installed");
return;
}
let mut payload = Vec::new();
for i in 0..4000u32 {
payload.extend_from_slice(format!("line {i:05} the quick brown fox жжж\n").as_bytes());
}
for (algo, fmt) in [(Algo::Lzma, "lzma"), (Algo::Xz, "xz")] {
let enc = compress(algo, &payload).expect("fstool compress");
let via_cli = xz_pipe(&["--format", fmt, "-dc"], &enc);
assert_eq!(
via_cli, payload,
"{fmt}: system xz could not decode fstool output"
);
let cli_enc = xz_pipe(&["--format", fmt, "-c"], &payload);
let via_fstool = decompress(algo, &cli_enc, payload.len()).expect("fstool decompress");
assert_eq!(
via_fstool, payload,
"{fmt}: fstool could not decode system xz output"
);
}
}