use crate::common::*;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
#[cfg(unix)]
#[test]
fn extraction_restores_modes_and_mtimes_for_files_and_directories() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("meta.tar"),
&[
Raw::Dir("sub/"),
Raw::ModedFile("sub/script.sh", b"#!/bin/sh\n", 0o750),
Raw::ModedFile("readonly.txt", b"ro", 0o400),
],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(mode_of(&dest.join("sub/script.sh")), 0o750);
assert_eq!(mode_of(&dest.join("readonly.txt")), 0o400);
assert_eq!(mode_of(&dest.join("sub")), 0o755);
assert_eq!(mtime_secs(&dest.join("sub/script.sh")), FIXTURE_MTIME);
assert_eq!(mtime_secs(&dest.join("readonly.txt")), FIXTURE_MTIME);
assert_eq!(mtime_secs(&dest.join("sub")), FIXTURE_MTIME);
let strict = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dir.join("strict").to_str().unwrap(),
"--strict-fidelity",
]);
assert!(
strict.status.success(),
"an extraction that lost nothing must pass --strict-fidelity, stderr: {}",
String::from_utf8_lossy(&strict.stderr)
);
}
#[cfg(unix)]
#[test]
fn a_setuid_bit_is_not_restored_and_strict_fidelity_fails_on_the_loss() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("setuid.tar"),
&[Raw::ModedFile("suid", b"payload", 0o4755)],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert!(
out.status.success(),
"the extraction itself succeeds, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
mode_of(&dest.join("suid")),
0o755,
"the setuid bit must not be restored"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("suid") && stderr.contains("mode"),
"the loss must be reported, naming the entry and the field: {stderr}"
);
let strict = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dir.join("strict").to_str().unwrap(),
"--force",
"--strict-fidelity",
]);
assert_eq!(
strict.status.code(),
Some(4),
"a dropped mode must fail --strict-fidelity, stderr: {}",
String::from_utf8_lossy(&strict.stderr)
);
}
#[cfg(unix)]
#[test]
fn an_entry_with_no_shape_on_disk_is_skipped_and_said_out_loud() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("fifo.tar"),
&[Raw::File("a.txt", b"alpha"), Raw::Fifo("pipe")],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(std::fs::read(dest.join("a.txt")).unwrap(), b"alpha");
assert!(
std::fs::symlink_metadata(dest.join("pipe")).is_err(),
"a fifo entry must not become a regular file"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("skipped entry") && stderr.contains("pipe"),
"the skip must name the entry and why: {stderr}"
);
let strict = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dir.join("strict").to_str().unwrap(),
"--strict-fidelity",
]);
assert_eq!(
strict.status.code(),
Some(4),
"a skipped entry must fail --strict-fidelity, stderr: {}",
String::from_utf8_lossy(&strict.stderr)
);
}
#[cfg(unix)]
#[test]
fn a_symlinks_own_mode_and_mtime_are_reported_as_lost() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("link.tar"),
&[Raw::File("a.txt", b"alpha"), Raw::Symlink("link", "a.txt")],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--strict-fidelity",
]);
assert_eq!(
out.status.code(),
Some(4),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("link") && stderr.contains("mtime"),
"the loss must name the entry and the field: {stderr}"
);
assert_eq!(
std::fs::read_link(dest.join("link")).unwrap(),
Path::new("a.txt")
);
}
#[test]
fn test_verb_passes_strict_fidelity_on_an_ordinary_tarball() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&["test", archive.to_str().unwrap(), "--strict-fidelity"]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[cfg(unix)]
#[test]
fn an_st_mode_shaped_header_mode_raises_no_warning() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("commons.tar"),
&[Raw::ModedFile("a.txt", b"alpha", 0o100_644)],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--strict-fidelity",
]);
assert!(
out.status.success(),
"an st_mode-shaped mode field must not fail the gate, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("missing metadata"),
"nothing was lost, so nothing may be reported: {stderr}"
);
assert_eq!(
mode_of(&dest.join("a.txt")),
0o644,
"the permission bits apply; the type bits are not permissions"
);
let suid = write_raw_tar(
&dir.join("suid.tar"),
&[Raw::ModedFile("b.txt", b"beta", 0o104_755)],
);
let out = run_output(&[
"unpack",
suid.to_str().unwrap(),
"-C",
dir.join("suid-out").to_str().unwrap(),
"--strict-fidelity",
]);
assert_eq!(
out.status.code(),
Some(4),
"setuid inside an st_mode-shaped field is still a dropped mode, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn a_container_under_a_codec_declaring_a_huge_dictionary_is_refused_on_every_verb() {
let (_legit, crafted, _dir) = craft_a_tar_lz_declaring_a_huge_dictionary("c1-refuse");
let path = crafted.to_str().unwrap();
const LIMIT: &str = "64M";
for args in [
vec!["list", path, "--memory-limit", LIMIT],
vec!["test", path, "--memory-limit", LIMIT],
vec!["cat", path, "a.txt", "--memory-limit", LIMIT],
] {
let out = Command::new(STUFFR).args(&args).output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert_eq!(
out.status.code(),
Some(6),
"`stuffr {}` must refuse a 512 MiB dictionary declaration at exit 6, \
not allocate for it; stderr: {stderr}",
args.join(" ")
);
assert!(
stderr.contains("memory-limit"),
"the refusal must name the flag that raises it; stderr: {stderr}"
);
}
let out = Command::new(STUFFR)
.args([
"unpack",
path,
"-o",
"/dev/null",
"--force",
"--memory-limit",
LIMIT,
])
.output()
.unwrap();
assert_eq!(
out.status.code(),
Some(6),
"the single-stream path's existing refusal must be unchanged; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let mut child = Command::new(STUFFR)
.args(["list", "-", "--memory-limit", LIMIT])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let bytes = std::fs::read(&crafted).unwrap();
child.stdin.take().unwrap().write_all(&bytes).unwrap();
let out = child.wait_with_output().unwrap();
assert_eq!(
out.status.code(),
Some(6),
"a piped crafted archive must be refused too; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn a_legitimate_archive_of_the_same_size_still_works_on_every_verb() {
let (legit, crafted, _dir) = craft_a_tar_lz_declaring_a_huge_dictionary("c1-allow");
let path = legit.to_str().unwrap();
assert_eq!(
std::fs::metadata(&legit).unwrap().len(),
std::fs::metadata(&crafted).unwrap().len(),
"the two fixtures must differ only in that one byte"
);
const LIMIT: &str = "64M";
let out = Command::new(STUFFR)
.args(["list", path, "--memory-limit", LIMIT])
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
String::from_utf8_lossy(&out.stdout).contains("a.txt"),
"list must still show the entry"
);
let out = Command::new(STUFFR)
.args(["test", path, "--memory-limit", LIMIT])
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let out = Command::new(STUFFR)
.args(["cat", path, "a.txt", "--memory-limit", LIMIT])
.output()
.unwrap();
assert!(out.status.success());
assert_eq!(out.stdout, b"hello from inside the tar");
let dest = tmp("c1-allow-dest");
let _ = std::fs::remove_dir_all(&dest);
let out = Command::new(STUFFR)
.args([
"unpack",
path,
"-C",
dest.to_str().unwrap(),
"--memory-limit",
LIMIT,
])
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(dest.join("a.txt").exists());
}
#[test]
fn memory_limit_is_honoured_rather_than_refused_on_the_container_path() {
let (_legit, crafted, _dir) = craft_a_tar_lz_declaring_a_huge_dictionary("c1-flag");
let path = crafted.to_str().unwrap();
for args in [
vec!["list", path, "--memory-limit", "1G"],
vec!["test", path, "--memory-limit", "1G"],
vec!["cat", path, "a.txt", "--memory-limit", "1G"],
] {
let out = Command::new(STUFFR).args(&args).output().unwrap();
assert!(
out.status.success(),
"`stuffr {}` must succeed once the limit is raised past the declaration; \
stderr: {}",
args.join(" "),
String::from_utf8_lossy(&out.stderr)
);
}
let dest = tmp("c1-flag-dest");
let _ = std::fs::remove_dir_all(&dest);
let out = Command::new(STUFFR)
.args([
"unpack",
path,
"-C",
dest.to_str().unwrap(),
"--memory-limit",
"1G",
])
.output()
.unwrap();
assert!(
out.status.success(),
"unpack -C must accept --memory-limit rather than refusing it; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn a_symlink_pointing_at_the_extraction_root_is_extracted_not_refused() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("root-link.tar"),
&[
Raw::Dir("sub"),
Raw::Symlink("sub/top", ".."),
Raw::File("sub/a.txt", b"alpha"),
],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert!(
out.status.success(),
"a symlink netting to the destination is contained, not an escape; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let link = dest.join("sub/top");
assert!(
std::fs::symlink_metadata(&link).is_ok(),
"the symlink must have been created"
);
assert_eq!(std::fs::read_link(&link).unwrap(), Path::new(".."));
assert!(dest.join("sub/a.txt").exists(), "extraction must not abort");
}
#[test]
fn a_symlink_one_step_past_the_extraction_root_is_still_refused() {
for (tag, target) in [
("i1-two-up", "../.."),
("i1-deep", "../../../etc"),
("i1-mixed", "../sub/../.."),
] {
let dir = tmp_dir();
let dest = dir.join(tag);
let archive = write_raw_tar(
&dir.join(format!("{tag}.tar")),
&[Raw::Dir("sub"), Raw::Symlink("sub/top", target)],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"`{target}` escapes and must still be refused, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert!(
stderr.contains("unsafe entry path `sub/top` refused"),
"the refusal must name the entry `sub/top`, got: {stderr}"
);
assert!(
std::fs::symlink_metadata(dest.join("sub/top")).is_err(),
"the symlink must not have been created"
);
}
}
#[cfg(unix)]
#[test]
fn a_gnu_tar_transform_rewritten_symlink_target_is_skipped_not_fatal() {
let gtar = require_gnu_tar();
let dir = tmp_dir();
let tree = dir.join("tree");
std::fs::create_dir_all(tree.join("bin")).unwrap();
std::fs::create_dir_all(tree.join("lib")).unwrap();
std::fs::write(tree.join("lib/foo"), b"lib-bytes").unwrap();
std::os::unix::fs::symlink("../lib/foo", tree.join("bin/foo")).unwrap();
let archive = dir.join("pkg.tar");
let made = Command::new(>ar)
.args([
"-cf",
archive.to_str().unwrap(),
"--transform",
"s,^,pkg-1.0/,",
"-C",
])
.arg(&tree)
.args(["bin", "lib"])
.output()
.unwrap();
assert!(
made.status.success(),
"{}",
String::from_utf8_lossy(&made.stderr)
);
let listed = Command::new(>ar)
.args(["-tvf", archive.to_str().unwrap()])
.output()
.unwrap();
assert!(
String::from_utf8_lossy(&listed.stdout).contains("pkg-1.0/bin/foo -> pkg-1.0/../lib/foo"),
"{}",
String::from_utf8_lossy(&listed.stdout)
);
for (strict, code) in [(false, 0), (true, 4)] {
let dest = dir.join(if strict { "strict" } else { "plain" });
let mut args = vec![
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
];
if strict {
args.push("--strict-fidelity");
}
let out = run_output(&args);
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert_eq!(
out.status.code(),
Some(code),
"strict={strict}, stderr: {stderr}"
);
assert_eq!(
std::fs::read(dest.join("pkg-1.0/lib/foo")).unwrap(),
b"lib-bytes",
"strict={strict}: extraction continues past the skip"
);
assert!(
std::fs::symlink_metadata(dest.join("pkg-1.0/bin/foo")).is_err(),
"strict={strict}: the skipped link is not created"
);
assert_eq!(
stderr
.matches(
"skipped entry `pkg-1.0/bin/foo`: symlink target uses `..` after a name, \
which can climb out through another link"
)
.count(),
1,
"strict={strict}: one named skip, stderr: {stderr}"
);
}
}
#[test]
fn info_does_not_claim_nothing_was_approximated_for_a_container_under_a_codec() {
let dir = tmp("info-codec-wrapped-matrix");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let one = dir.join("one.txt");
std::fs::write(&one, b"payload").unwrap();
for (ext, has_trailing_index) in [("zip", true), ("tar", false), ("cpio", false), ("a", false)]
{
let bare = dir.join(format!("bundle.{ext}"));
assert!(
Command::new(STUFFR)
.args(["pack", one.to_str().unwrap(), "-o", bare.to_str().unwrap()])
.status()
.unwrap()
.success(),
"packing a .{ext} must succeed"
);
assert!(
Command::new(STUFFR)
.args([
"pack",
bare.to_str().unwrap(),
"--format",
"gzip",
"--force"
])
.status()
.unwrap()
.success(),
"wrapping the .{ext} in gzip must succeed"
);
let wrapped = dir.join(format!("bundle.{ext}.gz"));
let bytes = std::fs::read(&wrapped).unwrap();
let strict = Command::new(STUFFR)
.args(["test", wrapped.to_str().unwrap(), "--strict-fidelity"])
.output()
.unwrap();
let strict_err = String::from_utf8_lossy(&strict.stderr).to_string();
let really_loses = !strict.status.success();
assert_eq!(
really_loses, has_trailing_index,
".{ext}.gz: only a trailing index can be missed by a forward read; \
stderr: {strict_err}"
);
let text = String::from_utf8(
Command::new(STUFFR)
.args(["info", wrapped.to_str().unwrap()])
.output()
.unwrap()
.stdout,
)
.unwrap();
assert!(
text.contains("exact"),
".{ext}.gz from a file: the rung is real and unchanged: {text}"
);
assert!(
text.contains(ext),
".{ext}.gz from a file must resolve the whole chain: {text}"
);
if has_trailing_index {
assert!(
text.contains("not evaluated"),
".{ext}.gz from a file: the zip is read out of a gzip decoder, which is \
forward-only however seekable the FILE is — `test` on these very bytes \
exits 4. info has not looked and must not claim: {text}"
);
assert!(!text.contains("nothing approximated"), "{text}");
} else {
assert!(
text.contains("nothing approximated"),
".{ext}.gz from a file carries every entry's metadata inline, so a forward \
read loses nothing — withholding here would be a false negative, the \
round-3 defect repeating: {text}"
);
assert!(!text.contains("not evaluated"), "{text}");
}
let text = info_over_stdin(&bytes);
assert!(text.contains("forward-only"), ".{ext}.gz on a pipe: {text}");
assert!(
text.contains("chain: gzip"),
".{ext}.gz on a pipe resolves to the codec alone — info does not decode to \
look inside: {text}"
);
let text = String::from_utf8(
Command::new(STUFFR)
.args(["info", bare.to_str().unwrap()])
.output()
.unwrap()
.stdout,
)
.unwrap();
assert!(
text.contains("nothing approximated"),
"a bare seekable .{ext} is unaffected by the codec-wrapped rule: {text}"
);
}
}
#[test]
fn every_entry_aware_verb_applies_the_same_expansion_bound() {
let dir = tmp_dir();
let payload = vec![0u8; 8 * 1024 * 1024];
let big = dir.join("big.bin");
std::fs::write(&big, &payload).unwrap();
let bomb = dir.join("bomb.zip");
assert!(
Command::new(STUFFR)
.args(["pack", big.to_str().unwrap(), "-o", bomb.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let compressed = std::fs::metadata(&bomb).unwrap().len();
assert!(
compressed * 10 < payload.len() as u64,
"the fixture must actually exceed a ratio of 10 ({compressed} compressed)"
);
let dest = dir.join("out");
for args in [
vec!["test", bomb.to_str().unwrap(), "--max-ratio", "10"],
vec![
"cat",
bomb.to_str().unwrap(),
"big.bin",
"--max-ratio",
"10",
],
vec![
"unpack",
bomb.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--max-ratio",
"10",
],
] {
let out = Command::new(STUFFR).args(&args).output().unwrap();
assert_eq!(
out.status.code(),
Some(6),
"`stuffr {}` must refuse the same bomb the other verbs refuse; stderr: {}",
args[0],
String::from_utf8_lossy(&out.stderr)
);
}
let _ = std::fs::remove_dir_all(&dest);
for args in [
vec!["test", bomb.to_str().unwrap()],
vec!["cat", bomb.to_str().unwrap(), "big.bin"],
vec![
"unpack",
bomb.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
],
] {
let out = Command::new(STUFFR).args(&args).output().unwrap();
assert!(
out.status.success(),
"`stuffr {}` must still accept a legitimate archive under the default ratio; \
stderr: {}",
args[0],
String::from_utf8_lossy(&out.stderr)
);
}
}
#[test]
fn a_piped_archive_larger_than_the_ratio_floor_is_not_refused() {
let dir = tmp_dir();
let payload: Vec<u8> = (0..3u32 * 1024 * 1024).map(|i| (i % 251) as u8).collect();
let src = dir.join("big.bin");
std::fs::write(&src, &payload).unwrap();
let archive = dir.join("big.tar");
assert!(
Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"-o",
archive.to_str().unwrap()
])
.status()
.unwrap()
.success()
);
let bytes = std::fs::read(&archive).unwrap();
assert!(
bytes.len() as u64 > stuffr::RATIO_FLOOR,
"the fixture must exceed the floor or this test proves nothing"
);
let piped = |args: &[&str], input: &[u8]| {
let mut child = Command::new(STUFFR)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut sin = child.stdin.take().unwrap();
let data = input.to_vec();
let writer = std::thread::spawn(move || {
let _ = sin.write_all(&data);
});
let out = child.wait_with_output().unwrap();
writer.join().unwrap();
out
};
let dest = dir.join("out");
for args in [
vec!["test", "-"],
vec!["cat", "-", "big.bin"],
vec!["unpack", "-", "-C", dest.to_str().unwrap()],
] {
let out = piped(&args, &bytes);
assert!(
out.status.success(),
"`stuffr {}` over a pipe must not refuse a {}-byte archive; stderr: {}",
args.join(" "),
bytes.len(),
String::from_utf8_lossy(&out.stderr)
);
}
let zeros = vec![0u8; 8 * 1024 * 1024];
let zsrc = dir.join("zeros.bin");
std::fs::write(&zsrc, &zeros).unwrap();
let bomb = dir.join("bomb.zip");
assert!(
Command::new(STUFFR)
.args(["pack", zsrc.to_str().unwrap(), "-o", bomb.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let bomb_bytes = std::fs::read(&bomb).unwrap();
let piped_test = |extra: &[&str]| {
let mut argv = vec!["test", "-"];
argv.extend_from_slice(extra);
piped(&argv, &bomb_bytes)
};
assert_eq!(
piped_test(&["--max-ratio", "10"]).status.code(),
Some(6),
"a bomb piped in must still be refused"
);
assert!(
piped_test(&["--max-ratio", "100000"]).status.success(),
"raising --max-ratio must change the outcome on a pipe — that is what \
proves the flag reaches this path rather than being parsed and dropped"
);
}
#[test]
fn a_valid_odc_cpio_archive_reports_a_capability_limit_not_corruption() {
let dir = tmp_dir();
let src = dir.join("f.txt");
std::fs::write(&src, b"payload").unwrap();
let newc = dir.join("archive.cpio");
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", newc.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let mut bytes = std::fs::read(&newc).unwrap();
assert_eq!(&bytes[..6], b"070701");
bytes[..6].copy_from_slice(b"070707");
let odc = dir.join("odc.cpio");
std::fs::write(&odc, &bytes).unwrap();
let out = Command::new(STUFFR)
.args(["list", odc.to_str().unwrap()])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert_eq!(
out.status.code(),
Some(3),
"an intact archive in an unreadable variant is a capability limit, \
not damage; stderr: {stderr}"
);
assert!(
stderr.contains("odc"),
"the message must name the variant: {stderr}"
);
assert!(
!stderr.contains("corrupt"),
"the message must not suggest damage: {stderr}"
);
let mut broken = std::fs::read(&newc).unwrap();
broken.truncate(30);
let cut = dir.join("cut.cpio");
std::fs::write(&cut, &broken).unwrap();
let out = Command::new(STUFFR)
.args(["list", cut.to_str().unwrap()])
.output()
.unwrap();
assert_eq!(
out.status.code(),
Some(5),
"a truncated archive really is damaged; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn pointing_a_verb_at_the_wrong_file_always_exits_two() {
let dir = tmp_dir();
let plain = dir.join("plain.txt");
std::fs::write(&plain, b"just some text, no format at all").unwrap();
let gz = dir.join("plain.txt.gz");
assert!(
Command::new(STUFFR)
.args([
"pack",
plain.to_str().unwrap(),
"--format",
"gzip",
"-o",
gz.to_str().unwrap()
])
.status()
.unwrap()
.success()
);
for (label, path) in [
("a codec stream, which is not an archive", &gz),
("a file of no recognised format", &plain),
] {
let out = Command::new(STUFFR)
.args(["list", path.to_str().unwrap()])
.output()
.unwrap();
assert_eq!(
out.status.code(),
Some(2),
"{label}: both are the caller pointing `list` at the wrong file, \
so both are exit 2; stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
}
#[test]
fn no_cli_message_contains_a_run_of_collapsed_indentation() {
let dir = tmp_dir();
let a = dir.join("a.txt");
let b = dir.join("b.txt");
std::fs::write(&a, b"alpha").unwrap();
std::fs::write(&b, b"beta").unwrap();
let archive = write_fixture_tar(&dir, &[("x.txt", b"x")]);
let cases: Vec<Vec<String>> = vec![
vec![
"pack".into(),
a.to_str().unwrap().into(),
b.to_str().unwrap().into(),
],
vec![
"pack".into(),
a.to_str().unwrap().into(),
b.to_str().unwrap().into(),
"--format".into(),
"tar".into(),
],
vec![
"unpack".into(),
archive.to_str().unwrap().into(),
"x.txt".into(),
],
];
for args in cases {
let out = Command::new(STUFFR).args(&args).output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert_eq!(
out.status.code(),
Some(2),
"`stuffr {}` must be a usage error; stderr: {stderr}",
args.join(" ")
);
assert!(
!stderr.contains(" "),
"a CLI message must not carry a run of collapsed continuation \
indentation; `stuffr {}` printed: {stderr:?}",
args.join(" ")
);
assert!(
!stderr.trim().is_empty(),
"and it must actually say something: {stderr:?}"
);
}
let out = Command::new(STUFFR)
.args(["pack", a.to_str().unwrap(), b.to_str().unwrap()])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
assert!(
stderr.contains("`-o bundle.tar`, or --format tar"),
"the phrase either side of the old collapse must read as one sentence: {stderr:?}"
);
}
#[test]
fn a_container_over_a_codec_is_written_in_one_step() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"hello from inside the tar\n").unwrap();
let out = dir.join("bundle.tar.gz");
let st = Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let t = Command::new("gunzip")
.args(["-t", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
t.status.success(),
"gunzip -t rejected it: {}",
String::from_utf8_lossy(&t.stderr)
);
let l = Command::new("tar")
.args(["tzf", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
l.status.success(),
"tar tzf failed: {}",
String::from_utf8_lossy(&l.stderr)
);
assert!(String::from_utf8_lossy(&l.stdout).contains("notes.txt"));
let r = Command::new(STUFFR)
.args(["list", out.to_str().unwrap()])
.output()
.unwrap();
assert!(r.status.success());
assert!(String::from_utf8_lossy(&r.stdout).contains("notes.txt"));
}
#[test]
fn a_dot_tar_dot_gz_output_is_never_silently_the_wrong_format() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"payload\n").unwrap();
for (name, extra) in [
("a.tar.gz", vec![]),
("b.tgz", vec![]),
("c.tar.gz", vec!["--format", "tar"]),
] {
let out = dir.join(name);
let mut args = vec!["pack", src.to_str().unwrap(), "-o", out.to_str().unwrap()];
args.extend(extra);
let st = Command::new(STUFFR).args(&args).output().unwrap();
assert!(
st.status.success(),
"{name}: {}",
String::from_utf8_lossy(&st.stderr)
);
let t = Command::new("gunzip")
.args(["-t", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
t.status.success(),
"{name} is not valid gzip: {}",
String::from_utf8_lossy(&t.stderr)
);
let l = Command::new("tar")
.args(["tzf", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
l.status.success(),
"{name} has no tar inside: {}",
String::from_utf8_lossy(&l.stderr)
);
assert!(
String::from_utf8_lossy(&l.stdout).contains("notes.txt"),
"{name}"
);
}
}
#[test]
fn a_codec_only_or_container_only_output_is_unaffected() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"payload\n").unwrap();
let gz = dir.join("plain.gz");
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", gz.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let out = Command::new("gunzip")
.args(["-c", gz.to_str().unwrap()])
.output()
.unwrap();
assert_eq!(out.stdout, b"payload\n");
let tar = dir.join("plain.tar");
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", tar.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let l = Command::new("tar")
.args(["tf", tar.to_str().unwrap()])
.output()
.unwrap();
assert!(String::from_utf8_lossy(&l.stdout).contains("notes.txt"));
}
#[test]
fn a_zip_round_trips_bare_and_inside_a_codec() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"zip payload\n").unwrap();
for name in ["bare.zip", "wrapped.zip.gz"] {
let out = dir.join(name);
let st = Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"{name}: {}",
String::from_utf8_lossy(&st.stderr)
);
if name.ends_with(".gz") {
let t = Command::new("gunzip")
.args(["-t", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
t.status.success(),
"{name} is not valid gzip: {}",
String::from_utf8_lossy(&t.stderr)
);
}
let r = Command::new(STUFFR)
.args(["list", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
r.status.success(),
"{name}: {}",
String::from_utf8_lossy(&r.stderr)
);
assert!(
String::from_utf8_lossy(&r.stdout).contains("notes.txt"),
"{name}"
);
}
}
#[test]
fn a_format_flag_contradicting_the_output_name_is_refused_before_anything_is_written() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"payload\n").unwrap();
for (fmt, name) in [
("zip", "x.tar.gz"),
("tar", "x.zip.gz"),
("tar", "x.cpio"),
("cpio", "x.tgz"),
] {
let out = dir.join(name);
let o = Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"--format",
fmt,
"-o",
out.to_str().unwrap(),
])
.output()
.unwrap();
assert_eq!(
o.status.code(),
Some(2),
"--format {fmt} -o {name} must be a usage error, not a written file"
);
let stderr = String::from_utf8_lossy(&o.stderr).to_string();
assert!(
stderr.contains(fmt) && stderr.contains(name),
"the message must name both sides of the contradiction: {stderr:?}"
);
assert!(!out.exists(), "--format {fmt} -o {name} left a file behind");
}
for (fmt, name) in [("tar", "ok.tar.gz"), ("tar", "ok.gz"), ("zip", "ok.zip.gz")] {
let out = dir.join(name);
let o = Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"--format",
fmt,
"-o",
out.to_str().unwrap(),
])
.output()
.unwrap();
assert!(
o.status.success(),
"--format {fmt} -o {name} must still work: {}",
String::from_utf8_lossy(&o.stderr)
);
let l = Command::new(STUFFR)
.args(["list", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
l.status.success(),
"stuffr must be able to read back what it wrote to {name}: {}",
String::from_utf8_lossy(&l.stderr)
);
assert!(
String::from_utf8_lossy(&l.stdout).contains("notes.txt"),
"{name}"
);
}
}
#[test]
fn the_composed_path_honours_stuffr_threads_now_that_a_codec_is_present() {
let dir = tmp_dir();
let src = dir.join("big.txt");
let mut data = Vec::with_capacity(4 << 20);
let mut x: u32 = 0x1234_5678;
while data.len() < (4 << 20) {
x = x.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
data.extend_from_slice(format!("line {} of the corpus\n", x >> 16).as_bytes());
}
std::fs::write(&src, &data).unwrap();
let plain = dir.join("plain.tar.xz");
let env = dir.join("env.tar.xz");
for (out, threads) in [(&plain, None), (&env, Some("4"))] {
let mut cmd = Command::new(STUFFR);
cmd.args([
"pack",
src.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--level",
"1",
]);
match threads {
Some(n) => cmd.env("STUFFR_THREADS", n),
None => cmd.env_remove("STUFFR_THREADS"),
};
let o = cmd.output().unwrap();
assert!(
o.status.success(),
"pack failed: {}",
String::from_utf8_lossy(&o.stderr)
);
}
assert_ne!(
std::fs::read(&plain).unwrap(),
std::fs::read(&env).unwrap(),
"STUFFR_THREADS=4 must now change the bytes on a composed write with a codec \
layer, the same way it already does on a single-stream one; identical bytes \
here means the governor silently went back to None"
);
let flagged = Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"-o",
dir.join("flag.tar.xz").to_str().unwrap(),
"--threads",
"4",
])
.output()
.unwrap();
assert!(
flagged.status.success(),
"--threads must be honoured where a codec is present: {}",
String::from_utf8_lossy(&flagged.stderr)
);
}
#[test]
fn encoder_flags_are_refused_only_where_there_is_no_codec_to_honour_them() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"payload\n").unwrap();
let st = Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"-o",
dir.join("a.tar").to_str().unwrap(),
"--threads",
"2",
])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(2),
"a bare container has no codec to hand threads to"
);
let out = dir.join("b.tar.gz");
let st = Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--threads",
"2",
])
.output()
.unwrap();
assert!(
st.status.success(),
"a composed write HAS a codec: {}",
String::from_utf8_lossy(&st.stderr)
);
assert!(
Command::new("gunzip")
.args(["-t", out.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let big = dir.join("big.txt");
let mut payload = String::new();
for i in 0..4000 {
payload.push_str(&format!(
"line {i} of a log with some repeated shape {}\n",
i % 97
));
}
std::fs::write(&big, payload.as_bytes()).unwrap();
let mut sizes = Vec::new();
for level in ["1", "9"] {
let out = dir.join(format!("c{level}.tar.gz"));
assert!(
Command::new(STUFFR)
.args([
"pack",
big.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--level",
level,
])
.status()
.unwrap()
.success(),
"--level {level} must be accepted on a composed write"
);
assert!(
Command::new("gunzip")
.args(["-t", out.to_str().unwrap()])
.status()
.unwrap()
.success(),
"--level {level} must still produce valid gzip"
);
sizes.push(std::fs::metadata(&out).unwrap().len());
}
assert!(
sizes[0] > sizes[1],
"--level must reach the codec beneath the container: level 1 produced \
{} bytes and level 9 produced {} — equal sizes mean the flag was \
accepted and dropped",
sizes[0],
sizes[1]
);
assert!(
Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"-o",
dir.join("d.gz").to_str().unwrap(),
"--threads",
"2",
])
.status()
.unwrap()
.success()
);
}