use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
use flate2::Compression;
use flate2::write::GzEncoder;
use tar::{Builder, Header};
const STUFFR: &str = env!("CARGO_BIN_EXE_stuffr");
static RUN_TOKEN: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
use std::hash::{BuildHasher, Hasher};
let n = std::collections::hash_map::RandomState::new()
.build_hasher()
.finish();
format!("{}-{n:016x}", std::process::id())
});
fn tmp(name: &str) -> std::path::PathBuf {
let mut p = std::env::temp_dir();
p.push(format!("stuffr-cli-{}-{name}", *RUN_TOKEN));
p
}
#[test]
fn formats_now_reports_gzip() {
let out = Command::new(STUFFR).arg("formats").output().unwrap();
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains("gzip"), "gzip must appear: {text}");
assert!(
!text.contains("0 formats"),
"the build is no longer empty: {text}"
);
}
#[test]
fn info_names_the_format_and_the_rung() {
let src = tmp("info.txt");
let gz = tmp("info.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let out = Command::new(STUFFR)
.args(["info", gz.to_str().unwrap()])
.output()
.unwrap();
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains("gzip"), "{text}");
assert!(text.contains("exact"), "a file is read exactly: {text}");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn info_json_is_structured_and_names_how_the_format_was_detected() {
let src = tmp("json2.txt");
let gz = tmp("json2.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let out = Command::new(STUFFR)
.args(["info", "--json", gz.to_str().unwrap()])
.output()
.unwrap();
let text = String::from_utf8(out.stdout).unwrap();
let v: serde_json::Value =
serde_json::from_str(&text).unwrap_or_else(|e| panic!("not JSON: {e}\n{text}"));
assert_eq!(v["format"], "gzip");
assert_eq!(v["rung"], "exact");
assert_eq!(
v["detected_by"], "magic",
"a real gzip file is identified by its magic, not its name"
);
assert!(v["warnings"].as_array().unwrap().is_empty());
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn info_reports_the_resolved_memory_limit() {
let src = tmp("info-mem.txt");
let gz = tmp("info-mem.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let out = Command::new(STUFFR)
.args(["info", "--memory-limit", "512M", gz.to_str().unwrap()])
.output()
.unwrap();
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
assert!(
text.contains("512 MiB"),
"an explicit --memory-limit must be reflected in the report: {text}"
);
let out = Command::new(STUFFR)
.args([
"info",
"--json",
"--memory-limit",
"512M",
gz.to_str().unwrap(),
])
.output()
.unwrap();
let text = String::from_utf8(out.stdout).unwrap();
let v: serde_json::Value =
serde_json::from_str(&text).unwrap_or_else(|e| panic!("not JSON: {e}\n{text}"));
assert_eq!(v["memory_limit"], 512 * 1024 * 1024);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn info_withholds_the_fidelity_claim_only_where_a_forward_read_could_lose_something() {
let dir = tmp("info-fidelity-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();
let mut archives = Vec::new();
for (ext, has_trailing_index) in [("zip", true), ("tar", false), ("cpio", false), ("a", false)]
{
let path = dir.join(format!("bundle.{ext}"));
assert!(
Command::new(STUFFR)
.args(["pack", one.to_str().unwrap(), "-o", path.to_str().unwrap()])
.status()
.unwrap()
.success(),
"packing a .{ext} must succeed"
);
archives.push((ext, path, has_trailing_index));
}
for (ext, path, has_trailing_index) in &archives {
let bytes = std::fs::read(path).unwrap();
let text = String::from_utf8(
Command::new(STUFFR)
.args(["info", path.to_str().unwrap()])
.output()
.unwrap()
.stdout,
)
.unwrap();
assert!(text.contains("exact"), ".{ext} from a file: {text}");
assert!(
text.contains("nothing approximated"),
".{ext} from a FILE was read exactly — the claim is true and must be made: {text}"
);
let text = info_over_stdin(&bytes);
assert!(text.contains("forward-only"), ".{ext} on a pipe: {text}");
if *has_trailing_index {
assert!(
text.contains("not evaluated"),
".{ext} keeps its index at the END of the stream, so a forward read may well \
have approximated something and info has not looked: {text}"
);
assert!(!text.contains("nothing approximated"), ".{ext}: {text}");
} else {
assert!(
text.contains("nothing approximated"),
".{ext} carries every entry's metadata inline, so a forward read loses \
NOTHING — withholding the claim here is a false negative: {text}"
);
assert!(!text.contains("not evaluated"), ".{ext}: {text}");
}
let mut child = Command::new(STUFFR)
.args(["test", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(&bytes).unwrap();
let out = child.wait_with_output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
if *has_trailing_index {
assert!(
stderr.contains("fidelity warning"),
".{ext} on a pipe must really have warnings, or info would be right to \
claim none: {stderr}"
);
} else {
assert!(
out.status.success() && !stderr.contains("fidelity warning"),
".{ext} on a pipe must really have NO warnings, which is what makes info's \
claim true: {stderr}"
);
}
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn info_on_a_piped_zip_does_not_claim_nothing_was_approximated() {
let dir = tmp("info-zip-fidelity");
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();
let zip = dir.join("bundle.zip");
assert!(
Command::new(STUFFR)
.args(["pack", one.to_str().unwrap(), "-o", zip.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let bytes = std::fs::read(&zip).unwrap();
let text = info_over_stdin(&bytes);
assert!(
text.contains("forward-only"),
"the rung is still real and still reported: {text}"
);
assert!(
!text.contains("nothing approximated"),
"info has not opened the archive, so it must not claim this: {text}"
);
assert!(
text.contains("not evaluated"),
"the line must say the conclusion was withheld, not vanish: {text}"
);
let mut child = Command::new(STUFFR)
.args(["test", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(&bytes).unwrap();
let out = child.wait_with_output().unwrap();
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("central/trailing index was never read")
&& stderr.contains("entry count is unknown"),
"`test` opens the archive and must still report both losses: {stderr}"
);
let out = Command::new(STUFFR)
.args(["info", zip.to_str().unwrap()])
.output()
.unwrap();
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains("exact"), "a file is read exactly: {text}");
assert!(
text.contains("nothing approximated"),
"a seekable zip reads its central directory, so nothing is approximated: {text}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn info_on_a_bare_codec_stream_still_reports_nothing_approximated() {
let src = tmp("info-codec-fidelity.txt");
let gz = tmp("info-codec-fidelity.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let bytes = std::fs::read(&gz).unwrap();
for (what, text) in [
(
"a file",
String::from_utf8(
Command::new(STUFFR)
.args(["info", gz.to_str().unwrap()])
.output()
.unwrap()
.stdout,
)
.unwrap(),
),
("a pipe", info_over_stdin(&bytes)),
] {
assert!(
text.contains("nothing approximated"),
"{what}: a bare codec stream has no container to open, so this claim is real \
and must survive: {text}"
);
assert!(!text.contains("not evaluated"), "{what}: {text}");
}
let out = Command::new(STUFFR)
.args(["info", "--json", gz.to_str().unwrap()])
.output()
.unwrap();
let v: serde_json::Value = serde_json::from_str(&String::from_utf8(out.stdout).unwrap())
.expect("info --json must be JSON");
assert_eq!(v["fidelity_evaluated"], true, "{v}");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
fn info_over_stdin(bytes: &[u8]) -> String {
let mut child = Command::new(STUFFR)
.args(["info", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(bytes).unwrap();
let out = child.wait_with_output().unwrap();
assert!(out.status.success(), "stuffr info - must succeed");
String::from_utf8(out.stdout).unwrap()
}
#[test]
fn info_over_a_pipe_reports_forward_only_not_exact() {
let src = tmp("pipe-info.txt");
let gz = tmp("pipe-info.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let gz_bytes = std::fs::read(&gz).unwrap();
let mut child = Command::new(STUFFR)
.args(["info", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(&gz_bytes).unwrap();
let out = child.wait_with_output().unwrap();
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains("gzip"), "{text}");
assert!(
text.contains("forward-only"),
"a pipe cannot seek, so the rung must not be exact: {text}"
);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn cat_exits_cleanly_when_the_reader_closes_early() {
let src = tmp("broken-pipe.txt");
let gz = tmp("broken-pipe.txt.gz");
let _ = std::fs::remove_file(&gz);
let payload = "the quick brown fox jumps over the lazy dog\n".repeat(500_000);
std::fs::write(&src, payload.as_bytes()).unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let mut child = Command::new(STUFFR)
.args(["cat", gz.to_str().unwrap()])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut stdout = child.stdout.take().unwrap();
let mut prefix = [0u8; 16];
stdout
.read_exact(&mut prefix)
.expect("at least a few bytes before we stop reading");
drop(stdout);
let mut stderr = child.stderr.take().unwrap();
let mut err_text = String::new();
stderr.read_to_string(&mut err_text).unwrap();
let status = child.wait().unwrap();
assert!(
status.success(),
"an early-closing reader must not look like a failure: {status:?}"
);
assert!(
err_text.is_empty(),
"a clean broken-pipe exit prints nothing: {err_text}"
);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn formats_exits_cleanly_on_a_closed_stdout() {
let mut child = Command::new(STUFFR)
.arg("formats")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
drop(child.stdout.take().unwrap());
let mut stderr = child.stderr.take().unwrap();
let mut err_text = String::new();
stderr.read_to_string(&mut err_text).unwrap();
let status = child.wait().unwrap();
assert!(
status.success(),
"an early-closing reader must not look like a failure: {status:?}"
);
assert!(
err_text.is_empty(),
"a clean broken-pipe exit prints nothing: {err_text}"
);
}
#[test]
fn pack_then_unpack_round_trips_through_the_binary() {
let src = tmp("e2e.txt");
let gz = tmp("e2e.txt.gz");
let back = tmp("e2e-back.txt");
let _ = std::fs::remove_file(&gz);
let _ = std::fs::remove_file(&back);
let plain = b"the quick brown fox ".repeat(2000);
std::fs::write(&src, &plain).unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap()])
.status()
.unwrap()
.success()
);
assert!(
gz.exists(),
"pack must have written the .gz beside the input"
);
assert!(src.exists(), "pack must not destroy its input");
assert!(
Command::new(STUFFR)
.args(["unpack", gz.to_str().unwrap(), "-o", back.to_str().unwrap()])
.status()
.unwrap()
.success()
);
assert_eq!(
std::fs::read(&back).unwrap(),
plain,
"round trip must be byte-identical"
);
for p in [&src, &gz, &back] {
let _ = std::fs::remove_file(p);
}
}
#[test]
fn no_sync_is_accepted_by_pack_and_unpack_and_still_round_trips() {
let src = tmp("nosync-e2e.txt");
let gz = tmp("nosync-e2e.txt.gz");
let back = tmp("nosync-e2e-back.txt");
let _ = std::fs::remove_file(&gz);
let _ = std::fs::remove_file(&back);
let plain = b"the quick brown fox ".repeat(500);
std::fs::write(&src, &plain).unwrap();
assert!(
Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"--no-sync",
"--format",
"gzip"
])
.status()
.unwrap()
.success()
);
assert!(gz.exists(), "pack --no-sync must still write the output");
assert!(
Command::new(STUFFR)
.args([
"unpack",
gz.to_str().unwrap(),
"-o",
back.to_str().unwrap(),
"--no-sync",
])
.status()
.unwrap()
.success()
);
assert_eq!(
std::fs::read(&back).unwrap(),
plain,
"--no-sync must cost durability, never correctness"
);
for p in [&src, &gz, &back] {
let _ = std::fs::remove_file(p);
}
}
#[test]
fn cat_reads_a_stream_on_stdin() {
use std::io::Write;
use std::process::Stdio;
let src = tmp("pipe.txt");
let gz = tmp("pipe.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"needle in a haystack").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let packed = std::fs::read(&gz).unwrap();
let mut child = Command::new(STUFFR)
.args(["cat", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
child.stdin.as_mut().unwrap().write_all(&packed).unwrap();
let out = child.wait_with_output().unwrap();
assert!(out.status.success());
assert_eq!(out.stdout, b"needle in a haystack");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn pack_reads_input_from_stdin() {
let out = tmp("stdin-pack.gz");
let _ = std::fs::remove_file(&out);
let plain = b"stdin plaintext, packed then verified byte for byte".repeat(200);
let mut child = Command::new(STUFFR)
.args(["pack", "-", "-o", out.to_str().unwrap()])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(&plain).unwrap();
let result = child.wait_with_output().unwrap();
assert!(
result.status.success(),
"stderr: {}",
String::from_utf8_lossy(&result.stderr)
);
assert!(out.exists(), "pack must have written the output file");
let cat_out = Command::new(STUFFR)
.args(["cat", out.to_str().unwrap()])
.output()
.unwrap();
assert!(cat_out.status.success());
assert_eq!(cat_out.stdout, plain, "round trip must be byte-identical");
let _ = std::fs::remove_file(&out);
}
#[test]
fn pack_writes_output_to_stdout() {
let src = tmp("stdout-pack.txt");
let _ = std::fs::remove_file(&src);
let plain = b"payload written straight through to stdout by pack".repeat(200);
std::fs::write(&src, &plain).unwrap();
let pack_out = Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip", "-o", "-"])
.output()
.unwrap();
assert!(
pack_out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&pack_out.stderr)
);
assert_eq!(
&pack_out.stdout[..2],
&[0x1f, 0x8b],
"must be a real gzip stream"
);
let mut child = Command::new(STUFFR)
.args(["cat", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.take()
.unwrap()
.write_all(&pack_out.stdout)
.unwrap();
let cat_out = child.wait_with_output().unwrap();
assert!(cat_out.status.success());
assert_eq!(
cat_out.stdout, plain,
"round trip through a piped stdout destination must be byte-identical"
);
let _ = std::fs::remove_file(&src);
}
#[test]
fn pack_over_a_pipe_reports_forward_only_not_exact() {
let out = tmp("pack-rung-pipe.gz");
let _ = std::fs::remove_file(&out);
let plain = b"payload".repeat(50);
let mut child = Command::new(STUFFR)
.args(["pack", "-", "-o", out.to_str().unwrap()])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(&plain).unwrap();
let result = child.wait_with_output().unwrap();
assert!(result.status.success());
let err_text = String::from_utf8_lossy(&result.stderr);
assert!(
err_text.contains("forward-only"),
"a pipe cannot seek, so pack's reported rung must not be exact: {err_text}"
);
let _ = std::fs::remove_file(&out);
}
#[test]
fn pack_of_a_seekable_file_reports_exact() {
let src = tmp("pack-rung-file.txt");
let gz = tmp("pack-rung-file.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
let out = Command::new(STUFFR)
.args(["pack", src.to_str().unwrap()])
.output()
.unwrap();
assert!(out.status.success());
let err_text = String::from_utf8_lossy(&out.stderr);
assert!(err_text.contains("exact"), "{err_text}");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn an_existing_output_is_refused_with_exit_two() {
let src = tmp("clash.txt");
let gz = tmp("clash.txt.gz");
std::fs::write(&src, b"payload").unwrap();
std::fs::write(&gz, b"PRE-EXISTING").unwrap();
let out = Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(2), "usage errors exit 2");
assert!(String::from_utf8_lossy(&out.stderr).contains("--force"));
assert_eq!(
std::fs::read(&gz).unwrap(),
b"PRE-EXISTING",
"the refused command changed nothing"
);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn a_bomb_exits_six_and_leaves_no_partial_output() {
let src = tmp("boom.bin");
let gz = tmp("boom.bin.gz");
let out_path = tmp("boom-out.bin");
let _ = std::fs::remove_file(&gz);
let _ = std::fs::remove_file(&out_path);
std::fs::write(&src, vec![0u8; 2 * 1024 * 1024]).unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let out = Command::new(STUFFR)
.args([
"unpack",
gz.to_str().unwrap(),
"-o",
out_path.to_str().unwrap(),
"--max-ratio",
"100",
])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(6), "resource limits exit 6");
assert!(
!out_path.exists(),
"a refused decode must leave no partial file"
);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn an_unknown_verb_exits_two() {
let out = Command::new(STUFFR).arg("bogus").output().unwrap();
assert_eq!(out.status.code(), Some(2));
}
#[test]
fn examples_page_covers_every_format_and_flag() {
use clap::CommandFactory;
use stuffr_cli::cli::Cli;
let out = Command::new(STUFFR).arg("--examples").output().unwrap();
assert!(out.status.success(), "stuffr --examples must exit 0");
let text = String::from_utf8(out.stdout).unwrap();
for row in stuffr::registry().matrix() {
let id = row.id.as_str();
assert!(
text.contains(id),
"the examples page does not mention the `{id}` format"
);
}
let root = Cli::command();
for sub in root.get_subcommands() {
let name = sub.get_name();
if name == "help" {
continue;
}
assert!(
text.contains(name),
"the examples page does not mention the `{name}` subcommand"
);
for arg in sub.get_arguments() {
let Some(long) = arg.get_long() else {
continue;
};
if long == "help" || long == "version" {
continue;
}
let flag = format!("--{long}");
assert!(
text.contains(flag.as_str()),
"the examples page does not mention {flag}"
);
}
}
}
#[test]
fn the_examples_page_cannot_carry_a_stale_count_or_a_shipped_still_to_come() {
let out = Command::new(STUFFR).arg("--examples").output().unwrap();
assert!(out.status.success());
let text = String::from_utf8(out.stdout).unwrap();
let rows = stuffr::registry().matrix();
let containers: Vec<&str> = rows
.iter()
.filter(|r| stuffr::registry().container(r.id).is_some())
.map(|r| r.id.as_str())
.collect();
let codecs: Vec<&str> = rows
.iter()
.filter(|r| stuffr::registry().container(r.id).is_none())
.map(|r| r.id.as_str())
.collect();
for sentence in text.split(['.', '\n']) {
let lower = sentence.to_lowercase();
if !(lower.contains("still to come") || lower.contains("not yet supported")) {
continue;
}
for id in rows.iter().map(|r| r.id.as_str()) {
assert!(
!sentence.contains(id),
"the page says `{id}` is still to come, but this build registers it: \
{sentence:?}"
);
}
}
const NUMBERS: &[(&str, usize)] = &[
("one", 1),
("two", 2),
("three", 3),
("four", 4),
("five", 5),
("six", 6),
("seven", 7),
("eight", 8),
("nine", 9),
("ten", 10),
("eleven", 11),
("twelve", 12),
("thirteen", 13),
("fourteen", 14),
("fifteen", 15),
("sixteen", 16),
("seventeen", 17),
("eighteen", 18),
("nineteen", 19),
("twenty", 20),
];
let flat = text.split_whitespace().collect::<Vec<_>>().join(" ");
let words: Vec<&str> = flat.split(' ').collect();
for pair in words.windows(2) {
let (word, next) = (
pair[0].trim_matches(|c: char| !c.is_alphanumeric()),
pair[1],
);
let Some((_, value)) = NUMBERS.iter().find(|(w, _)| *w == word.to_lowercase()) else {
continue;
};
let noun = next
.trim_matches(|c: char| !c.is_alphanumeric())
.to_lowercase();
let expected = match noun.as_str() {
"formats" | "rows" => rows.len(),
"containers" => containers.len(),
"codecs" => codecs.len(),
_ => continue,
};
assert_eq!(
*value, expected,
"the page says `{word} {noun}`, but this build has {expected} — the counts \
on this page are what a user trusts before running `stuffr formats`"
);
}
let counting_sentences: Vec<&str> = flat
.split(". ")
.filter(|sentence| {
let l = sentence.to_lowercase();
NUMBERS
.iter()
.any(|(w, _)| l.contains(&format!("{w} containers")))
})
.collect();
assert!(
!counting_sentences.is_empty(),
"the page must state how many containers this build has"
);
let names_them_all = counting_sentences.iter().any(|sentence| {
containers.iter().all(|id| {
sentence
.split(|c: char| !c.is_alphanumeric())
.any(|word| word == *id)
})
});
assert!(
names_them_all,
"no sentence both counts the containers and names every one of them \
({containers:?}); the counting sentences are {counting_sentences:?}"
);
}
#[test]
fn examples_pages_first_worked_example_runs_as_documented() {
let dir = tmp("examples-first-example-dir");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let notes = dir.join("notes.txt");
let packed = dir.join("notes.txt.gz");
std::fs::write(¬es, b"the documented example's payload").unwrap();
let out = Command::new(STUFFR)
.arg("pack")
.arg("notes.txt")
.current_dir(&dir)
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
packed.exists(),
"the page says this writes notes.txt.gz beside it"
);
assert!(
notes.exists(),
"the page says the input, notes.txt, is left alone"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_corrupt_archive_exits_five_not_one() {
let src = tmp("cli-corrupt.txt");
let gz = tmp("cli-corrupt.txt.gz");
let out = tmp("cli-corrupt-out.txt");
let _ = std::fs::remove_file(&gz);
let _ = std::fs::remove_file(&out);
std::fs::write(&src, b"the quick brown fox ".repeat(200)).unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let mut bytes = std::fs::read(&gz).unwrap();
let mid = bytes.len() / 2;
bytes[mid] ^= 0xFF;
std::fs::write(&gz, &bytes).unwrap();
let res = Command::new(STUFFR)
.args(["unpack", gz.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert_eq!(
res.status.code(),
Some(5),
"a corrupt archive must be distinguishable from a full disk"
);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn unpack_accepts_an_explicit_format() {
let src = tmp("fmt-unpack.txt");
let gz = tmp("fmt-unpack.txt.gz");
let out = tmp("fmt-unpack-out.txt");
let _ = std::fs::remove_file(&gz);
let _ = std::fs::remove_file(&out);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let res = Command::new(STUFFR)
.args([
"unpack",
gz.to_str().unwrap(),
"--format",
"gzip",
"-o",
out.to_str().unwrap(),
])
.output()
.unwrap();
assert!(
res.status.success(),
"stderr: {}",
String::from_utf8_lossy(&res.stderr)
);
assert_eq!(std::fs::read(&out).unwrap(), b"payload");
for p in [&src, &gz, &out] {
let _ = std::fs::remove_file(p);
}
}
#[test]
fn cat_accepts_an_explicit_format() {
let src = tmp("fmt-cat.txt");
let gz = tmp("fmt-cat.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let res = Command::new(STUFFR)
.args(["cat", gz.to_str().unwrap(), "--format", "gzip"])
.output()
.unwrap();
assert!(
res.status.success(),
"stderr: {}",
String::from_utf8_lossy(&res.stderr)
);
assert_eq!(res.stdout, b"payload");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn pack_with_no_flags_defaults_to_gzip() {
let src = tmp("bare-default.txt");
let gz = tmp("bare-default.txt.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, b"payload").unwrap();
let out = Command::new(STUFFR)
.args(["pack", src.to_str().unwrap()])
.output()
.unwrap();
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
gz.exists(),
"a bare pack with no --format must default to gzip and write {}.gz",
src.display()
);
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn unpack_format_flag_is_honoured_not_merely_accepted() {
let src = tmp("fmt-deflate.txt");
let packed = tmp("fmt-deflate.dat");
let out = tmp("fmt-deflate-out.txt");
for p in [&packed, &out] {
let _ = std::fs::remove_file(p);
}
let payload = b"the quick brown fox jumps over the lazy dog".repeat(50);
std::fs::write(&src, &payload).unwrap();
assert!(
Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"--format",
"deflate",
"-o",
packed.to_str().unwrap(),
])
.status()
.unwrap()
.success()
);
let no_format = Command::new(STUFFR)
.args([
"unpack",
packed.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--force",
])
.output()
.unwrap();
assert!(
!no_format.status.success(),
"a raw deflate stream must NOT be detectable without --format"
);
let with_format = Command::new(STUFFR)
.args([
"unpack",
packed.to_str().unwrap(),
"--format",
"deflate",
"-o",
out.to_str().unwrap(),
"--force",
])
.output()
.unwrap();
assert!(
with_format.status.success(),
"stderr: {}",
String::from_utf8_lossy(&with_format.stderr)
);
assert_eq!(std::fs::read(&out).unwrap(), payload);
for p in [&src, &packed, &out] {
let _ = std::fs::remove_file(p);
}
}
#[test]
fn cat_format_flag_is_honoured_not_merely_accepted() {
let src = tmp("fmt-cat-deflate.txt");
let packed = tmp("fmt-cat-deflate.dat");
let _ = std::fs::remove_file(&packed);
let payload = b"the quick brown fox jumps over the lazy dog".repeat(50);
std::fs::write(&src, &payload).unwrap();
assert!(
Command::new(STUFFR)
.args([
"pack",
src.to_str().unwrap(),
"--format",
"deflate",
"-o",
packed.to_str().unwrap(),
])
.status()
.unwrap()
.success()
);
let no_format = Command::new(STUFFR)
.args(["cat", packed.to_str().unwrap()])
.output()
.unwrap();
assert!(
!no_format.status.success(),
"a raw deflate stream must NOT be detectable without --format"
);
let with_format = Command::new(STUFFR)
.args(["cat", packed.to_str().unwrap(), "--format", "deflate"])
.output()
.unwrap();
assert!(
with_format.status.success(),
"stderr: {}",
String::from_utf8_lossy(&with_format.stderr)
);
assert_eq!(with_format.stdout, payload);
for p in [&src, &packed] {
let _ = std::fs::remove_file(p);
}
}
#[test]
fn an_unknown_format_name_is_a_usage_error_on_every_verb() {
for verb in ["unpack", "cat"] {
let out = Command::new(STUFFR)
.args([verb, "/dev/null", "--format", "bogus"])
.output()
.unwrap();
assert_eq!(
out.status.code(),
Some(2),
"{verb} --format bogus must be a usage error"
);
assert!(
String::from_utf8_lossy(&out.stderr).contains("gzip"),
"the error must name what this build does have"
);
}
}
#[test]
fn cat_honours_max_ratio() {
let src = tmp("cat-bomb.bin");
let gz = tmp("cat-bomb.bin.gz");
let _ = std::fs::remove_file(&gz);
std::fs::write(&src, vec![0u8; 2 * 1024 * 1024]).unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let out = Command::new(STUFFR)
.args(["cat", gz.to_str().unwrap(), "--max-ratio", "100"])
.output()
.unwrap();
assert_eq!(out.status.code(), Some(6), "resource limits exit 6");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&gz);
}
#[test]
fn formats_lists_every_registered_format() {
let out = Command::new(STUFFR).arg("formats").output().unwrap();
let text = String::from_utf8(out.stdout).unwrap();
for row in stuffr::registry().matrix() {
assert!(
text.contains(row.id.as_str()),
"`stuffr formats` omits {}: {text}",
row.id
);
}
}
#[test]
fn the_formats_note_sits_below_the_table_after_a_blank_line() {
let out = Command::new(STUFFR).arg("formats").output().unwrap();
let text = String::from_utf8(out.stdout).unwrap();
let lines: Vec<&str> = text.lines().collect();
let note = lines
.iter()
.position(|l| l.starts_with("(WRITE shows"))
.unwrap_or_else(|| panic!("`stuffr formats` printed no WRITE note:\n{text}"));
for row in stuffr::registry().matrix() {
let at = lines
.iter()
.position(|l| l.starts_with(row.id.as_str()))
.unwrap_or_else(|| panic!("`stuffr formats` omits {}:\n{text}", row.id));
assert!(
at < note,
"{} is listed at line {at}, below the note at line {note}:\n{text}",
row.id
);
}
assert!(
note > 0,
"the note is the first line, so no table precedes it"
);
assert_eq!(
lines[note - 1],
"",
"the note must be separated from the table by a blank line, got {:?}:\n{text}",
lines[note - 1]
);
}
#[test]
fn a_malformed_memory_limit_is_a_usage_error() {
let src = tmp("mem-bad.txt");
let out_path = tmp("mem-bad.gz");
let _ = std::fs::remove_file(&out_path);
std::fs::write(&src, b"payload").unwrap();
let out = Command::new(STUFFR)
.args(["pack", "--memory-limit", "512MB", "--format", "gzip", "-o"])
.arg(&out_path)
.arg(&src)
.output()
.unwrap();
assert_eq!(out.status.code(), Some(2), "malformed size must be exit 2");
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.contains("512M") && err.contains("2G"),
"must name the accepted forms concretely: {err}"
);
assert!(!out_path.exists(), "a refused pack must leave no output");
let _ = std::fs::remove_file(&src);
let _ = std::fs::remove_file(&out_path);
}
#[test]
fn threads_is_not_offered_on_decode_subcommands() {
for sub in ["unpack", "cat"] {
let out = Command::new(STUFFR)
.args([sub, "--threads", "4", "-"])
.output()
.unwrap();
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.contains("unexpected argument") || err.contains("--threads"),
"{sub} must reject --threads, got: {err}"
);
}
}
#[test]
fn the_binary_reports_its_version() {
let want = env!("CARGO_PKG_VERSION");
let out = Command::new(STUFFR).arg("--version").output().unwrap();
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains(want), "--version must report {want}, got: {s}");
}
static NEXT_DIR: AtomicU64 = AtomicU64::new(0);
fn tmp_dir() -> PathBuf {
let n = NEXT_DIR.fetch_add(1, Ordering::Relaxed);
let mut p = std::env::temp_dir();
p.push(format!("stuffr-cli-archive-{}-{n}", *RUN_TOKEN));
std::fs::create_dir_all(&p).unwrap();
p
}
fn append_tar_entry<W: Write>(builder: &mut Builder<W>, name: &str, data: &[u8]) {
let mut header = Header::new_gnu();
header.set_size(data.len() as u64);
header.set_mode(0o644);
builder.append_data(&mut header, name, data).unwrap();
}
fn write_fixture_tar(dir: &Path, entries: &[(&str, &[u8])]) -> PathBuf {
let path = dir.join("fixture.tar");
let mut builder = Builder::new(std::fs::File::create(&path).unwrap());
for (name, data) in entries {
append_tar_entry(&mut builder, name, data);
}
builder.finish().unwrap();
path
}
fn write_fixture_tar_gz(dir: &Path, name: &str, entries: &[(&str, &[u8])]) -> PathBuf {
let mut tar_bytes = Vec::new();
{
let mut builder = Builder::new(&mut tar_bytes);
for (n, data) in entries {
append_tar_entry(&mut builder, n, data);
}
builder.finish().unwrap();
}
let path = dir.join(name);
let mut enc = GzEncoder::new(
std::fs::File::create(&path).unwrap(),
Compression::default(),
);
enc.write_all(&tar_bytes).unwrap();
enc.finish().unwrap();
path
}
fn gzip_wrap(bytes: &[u8]) -> Vec<u8> {
let mut enc = GzEncoder::new(Vec::new(), Compression::fast());
enc.write_all(bytes).unwrap();
enc.finish().unwrap()
}
fn corrupt_midway(good: &Path) -> PathBuf {
let bytes = std::fs::read(good).unwrap();
let cut = bytes.len() / 2;
let corrupt = good.with_file_name("corrupt.tar");
std::fs::write(&corrupt, &bytes[..cut]).unwrap();
corrupt
}
fn run(args: &[&str]) -> std::process::ExitStatus {
Command::new(STUFFR).args(args).status().unwrap()
}
fn run_output(args: &[&str]) -> std::process::Output {
Command::new(STUFFR).args(args).output().unwrap()
}
fn run_with_stdin_output(args: &[&str], input: &[u8]) -> std::process::Output {
let mut child = Command::new(STUFFR)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(input).unwrap();
child.wait_with_output().unwrap()
}
fn run_with_stdin(args: &[&str], input: &[u8]) -> String {
let out = run_with_stdin_output(args, input);
String::from_utf8(out.stdout).unwrap()
}
#[test]
fn list_names_every_entry_and_extracts_nothing() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b/c.bin", b"\x00\xff")]);
let out = run_output(&["list", archive.to_str().unwrap()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let text = String::from_utf8(out.stdout).unwrap();
assert!(text.contains("a.txt") && text.contains("b/c.bin"), "{text}");
assert!(!dir.join("a.txt").exists(), "list must not extract");
assert!(!dir.join("b").exists(), "list must not extract");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn ls_is_an_alias_for_list() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&["ls", archive.to_str().unwrap()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(String::from_utf8(out.stdout).unwrap().contains("a.txt"));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_json_reports_name_kind_and_size() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&["list", "--json", archive.to_str().unwrap()]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let text = String::from_utf8(out.stdout).unwrap();
let v: serde_json::Value =
serde_json::from_str(&text).unwrap_or_else(|e| panic!("not JSON: {e}\n{text}"));
let rows = v.as_array().expect("a JSON array of entries");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "a.txt");
assert_eq!(rows[0]["kind"], "file");
assert_eq!(rows[0]["size"], 5);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_verb_exits_five_on_a_corrupt_archive_and_zero_on_a_good_one() {
let dir = tmp_dir();
let good = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
assert_eq!(run(&["test", good.to_str().unwrap()]).code(), Some(0));
let bad = corrupt_midway(&good);
assert_eq!(run(&["test", bad.to_str().unwrap()]).code(), Some(5));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_works_on_a_pipe() {
let dir = tmp_dir();
let bytes = std::fs::read(write_fixture_tar(&dir, &[("a.txt", b"alpha")])).unwrap();
let text = run_with_stdin(&["list", "-"], &bytes);
assert!(text.contains("a.txt"), "{text}");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_and_test_succeed_on_a_real_tar_gz_from_a_file() {
let dir = tmp_dir();
let archive = write_fixture_tar_gz(
&dir,
"fixture.tar.gz",
&[("a.txt", b"alpha"), ("b/c.bin", b"\x00\xff")],
);
let list_out = run_output(&["list", archive.to_str().unwrap()]);
assert!(
list_out.status.success(),
"list stderr: {}",
String::from_utf8_lossy(&list_out.stderr)
);
let text = String::from_utf8(list_out.stdout).unwrap();
assert!(text.contains("a.txt") && text.contains("b/c.bin"), "{text}");
let test_out = run_output(&["test", archive.to_str().unwrap()]);
assert!(
test_out.status.success(),
"test stderr: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_and_test_succeed_on_a_real_tar_gz_over_a_pipe() {
let dir = tmp_dir();
let archive = write_fixture_tar_gz(&dir, "fixture.tar.gz", &[("a.txt", b"alpha")]);
let bytes = std::fs::read(&archive).unwrap();
let list_out = run_with_stdin_output(&["list", "-"], &bytes);
assert!(
list_out.status.success(),
"list stderr: {}",
String::from_utf8_lossy(&list_out.stderr)
);
assert!(
String::from_utf8(list_out.stdout)
.unwrap()
.contains("a.txt")
);
let test_out = run_with_stdin_output(&["test", "-"], &bytes);
assert!(
test_out.status.success(),
"test stderr: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_and_test_succeed_on_a_real_tgz_from_a_file() {
let dir = tmp_dir();
let archive = write_fixture_tar_gz(&dir, "fixture.tgz", &[("a.txt", b"alpha")]);
let list_out = run_output(&["list", archive.to_str().unwrap()]);
assert!(
list_out.status.success(),
"list stderr: {}",
String::from_utf8_lossy(&list_out.stderr)
);
assert!(
String::from_utf8(list_out.stdout)
.unwrap()
.contains("a.txt")
);
let test_out = run_output(&["test", archive.to_str().unwrap()]);
assert!(
test_out.status.success(),
"test stderr: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_and_test_succeed_on_a_real_tgz_over_a_pipe() {
let dir = tmp_dir();
let archive = write_fixture_tar_gz(&dir, "fixture.tgz", &[("a.txt", b"alpha")]);
let bytes = std::fs::read(&archive).unwrap();
let list_out = run_with_stdin_output(&["list", "-"], &bytes);
assert!(
list_out.status.success(),
"list stderr: {}",
String::from_utf8_lossy(&list_out.stderr)
);
assert!(
String::from_utf8(list_out.stdout)
.unwrap()
.contains("a.txt")
);
let test_out = run_with_stdin_output(&["test", "-"], &bytes);
assert!(
test_out.status.success(),
"test stderr: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn test_catches_a_payload_truncation() {
let dir = tmp_dir();
let payload = vec![b'x'; 8192];
let full = write_fixture_tar(&dir, &[("big.bin", &payload)]);
let bytes = std::fs::read(&full).unwrap();
let cut = 512 + payload.len() / 3;
let truncated = dir.join("truncated.tar");
std::fs::write(&truncated, &bytes[..cut]).unwrap();
let test_out = run_output(&["test", truncated.to_str().unwrap()]);
assert_eq!(
test_out.status.code(),
Some(5),
"test reads the payload and must catch the truncation: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let test_err = String::from_utf8_lossy(&test_out.stderr);
assert!(
test_err.contains("big.bin") && test_err.contains("short of the size"),
"must be tar's own per-entry short-read diagnosis, reclassified from a raw \
io::ErrorKind::InvalidData via Error::from_decode_io: {test_err}"
);
let list_out = run_output(&["list", truncated.to_str().unwrap()]);
assert_eq!(
list_out.status.code(),
Some(5),
"list also exits 5 here, via the tar crate's own header-skip detection, not via a \
payload read: {}",
String::from_utf8_lossy(&list_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_refuses_a_plain_codec_stream_as_not_an_archive() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
let gz = dir.join("notes.txt.gz");
std::fs::write(&src, b"just plain text, not an archive").unwrap();
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "--format", "gzip"])
.status()
.unwrap()
.success()
);
let out = run_output(&["list", gz.to_str().unwrap()]);
assert_eq!(
out.status.code(),
Some(2),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("not an archive"), "{err}");
assert!(
err.contains("gzip"),
"must name what the input actually resolved to: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_refuses_nesting_past_the_depth_bound() {
let mut bytes = b"payload too shallow to be an archive".to_vec();
for _ in 0..6 {
bytes = gzip_wrap(&bytes);
}
let out = run_with_stdin_output(&["list", "-"], &bytes);
assert_eq!(
out.status.code(),
Some(6),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("nesting"), "{err}");
}
#[test]
fn list_reports_a_truncated_codec_stream_as_corrupt_not_as_an_io_failure() {
let dir = tmp_dir();
let trunc = dir.join("trunc.zz");
std::fs::write(&trunc, [0x78, 0xda, 0x0a]).unwrap();
let cat_out = run_output(&["cat", trunc.to_str().unwrap()]);
assert_eq!(
cat_out.status.code(),
Some(5),
"cat (unaffected by this bug) must still exit 5: {}",
String::from_utf8_lossy(&cat_out.stderr)
);
let list_out = run_output(&["list", trunc.to_str().unwrap()]);
assert_eq!(
list_out.status.code(),
Some(5),
"list must agree with cat on the SAME bytes — exit 1 here means stuffr claimed it \
failed, when the truth is the input is corrupt: {}",
String::from_utf8_lossy(&list_out.stderr)
);
let list_err = String::from_utf8_lossy(&list_out.stderr);
assert!(
list_err.contains("archive is corrupt"),
"must be Error::Corrupt's own wording, not Error::Io's: {list_err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_reports_an_empty_lzip_stream_as_corrupt_not_as_an_io_failure() {
let dir = tmp_dir();
let empty = dir.join("empty.lz");
std::fs::write(&empty, []).unwrap();
let cat_out = run_output(&["cat", empty.to_str().unwrap()]);
assert_eq!(
cat_out.status.code(),
Some(5),
"cat (unaffected by this bug) must still exit 5: {}",
String::from_utf8_lossy(&cat_out.stderr)
);
let list_out = run_output(&["list", empty.to_str().unwrap()]);
assert_eq!(
list_out.status.code(),
Some(5),
"list must agree with cat on the SAME bytes: {}",
String::from_utf8_lossy(&list_out.stderr)
);
let list_err = String::from_utf8_lossy(&list_out.stderr);
assert!(
list_err.contains("archive is corrupt") && list_err.contains("LZIP"),
"must be Error::Corrupt's own wording, naming what lzip's decoder actually \
complained about: {list_err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_reports_a_directory_as_an_io_failure_not_as_corrupt() {
let dir = tmp_dir();
let sub = dir.join("not_a_file");
std::fs::create_dir_all(&sub).unwrap();
let out = run_output(&["list", sub.to_str().unwrap()]);
assert_eq!(
out.status.code(),
Some(1),
"a genuine i/o failure on the raw source must stay exit 1, not become exit 5: {}",
String::from_utf8_lossy(&out.stderr)
);
let err = String::from_utf8_lossy(&out.stderr);
assert!(
err.starts_with("stuffr: i/o error:"),
"must be Error::Io's own wording, not Error::Corrupt's \"archive is corrupt\": {err}"
);
assert!(
!err.contains("archive is corrupt"),
"must not have been reclassified as corrupt: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
fn write_bomb_tar_gz(dir: &Path) -> PathBuf {
write_fixture_tar_gz(
dir,
"bomb.tar.gz",
&[("bomb.bin", &vec![0u8; 2 * 1024 * 1024])],
)
}
#[test]
fn list_and_test_refuse_a_bomb_tar_gz_at_a_tight_max_ratio() {
let dir = tmp_dir();
let bomb = write_bomb_tar_gz(&dir);
let list_out = run_output(&["list", bomb.to_str().unwrap(), "--max-ratio", "100"]);
assert_eq!(
list_out.status.code(),
Some(6),
"list stderr: {}",
String::from_utf8_lossy(&list_out.stderr)
);
let list_err = String::from_utf8_lossy(&list_out.stderr);
assert!(
list_err.contains("100") && list_err.contains("--max-ratio"),
"must name the limit and the flag, matching how decompress reports it: {list_err}"
);
let test_out = run_output(&["test", bomb.to_str().unwrap(), "--max-ratio", "100"]);
assert_eq!(
test_out.status.code(),
Some(6),
"test stderr: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let test_err = String::from_utf8_lossy(&test_out.stderr);
assert!(
test_err.contains("100") && test_err.contains("--max-ratio"),
"must name the limit and the flag: {test_err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn raising_max_ratio_lets_the_bomb_through() {
let dir = tmp_dir();
let bomb = write_bomb_tar_gz(&dir);
let list_out = run_output(&["list", bomb.to_str().unwrap(), "--max-ratio", "100000"]);
assert!(
list_out.status.success(),
"list stderr: {}",
String::from_utf8_lossy(&list_out.stderr)
);
let test_out = run_output(&["test", bomb.to_str().unwrap(), "--max-ratio", "100000"]);
assert!(
test_out.status.success(),
"test stderr: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_legitimately_compressible_tar_gz_is_not_refused_at_the_default_ratio() {
let dir = tmp_dir();
let text = "the quick brown fox jumps over the lazy dog\n".repeat(200_000);
let archive = write_fixture_tar_gz(&dir, "legit.tar.gz", &[("log.txt", text.as_bytes())]);
let list_out = run_output(&["list", archive.to_str().unwrap()]);
assert!(
list_out.status.success(),
"a legitimately compressible archive must not be refused at the default ratio: {}",
String::from_utf8_lossy(&list_out.stderr)
);
assert!(
String::from_utf8(list_out.stdout)
.unwrap()
.contains("log.txt")
);
let test_out = run_output(&["test", archive.to_str().unwrap()]);
assert!(
test_out.status.success(),
"a legitimately compressible archive must not be refused at the default ratio: {}",
String::from_utf8_lossy(&test_out.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
const FIXTURE_MTIME: u64 = 1_600_000_000;
fn raw_header(
name: &str,
kind: tar::EntryType,
link_target: Option<&str>,
size: u64,
mode: u32,
) -> Header {
let mut header = Header::new_gnu();
header.set_size(size);
header.set_mode(mode);
header.set_mtime(FIXTURE_MTIME);
header.set_entry_type(kind);
{
let gnu = header.as_gnu_mut().expect("new_gnu is a GNU header");
let bytes = name.as_bytes();
assert!(
bytes.len() <= gnu.name.len(),
"raw fixture names must fit tar's 100-byte name field"
);
gnu.name[..bytes.len()].copy_from_slice(bytes);
if let Some(target) = link_target {
let bytes = target.as_bytes();
assert!(
bytes.len() <= gnu.linkname.len(),
"raw fixture link targets must fit tar's 100-byte linkname field"
);
gnu.linkname[..bytes.len()].copy_from_slice(bytes);
}
}
header.set_cksum();
header
}
enum Raw<'a> {
File(&'a str, &'a [u8]),
ModedFile(&'a str, &'a [u8], u32),
Dir(&'a str),
Symlink(&'a str, &'a str),
Fifo(&'a str),
}
fn write_raw_tar(path: &Path, entries: &[Raw<'_>]) -> PathBuf {
let mut builder = Builder::new(std::fs::File::create(path).unwrap());
for entry in entries {
match entry {
Raw::File(name, data) => {
let header = raw_header(
name,
tar::EntryType::Regular,
None,
data.len() as u64,
0o644,
);
builder.append(&header, *data).unwrap();
}
Raw::ModedFile(name, data, mode) => {
let header = raw_header(
name,
tar::EntryType::Regular,
None,
data.len() as u64,
*mode,
);
builder.append(&header, *data).unwrap();
}
Raw::Dir(name) => {
let header = raw_header(name, tar::EntryType::Directory, None, 0, 0o755);
builder.append(&header, std::io::empty()).unwrap();
}
Raw::Symlink(name, target) => {
let header = raw_header(name, tar::EntryType::Symlink, Some(target), 0, 0o777);
builder.append(&header, std::io::empty()).unwrap();
}
Raw::Fifo(name) => {
let header = raw_header(name, tar::EntryType::Fifo, None, 0, 0o644);
builder.append(&header, std::io::empty()).unwrap();
}
}
}
builder.finish().unwrap();
path.to_path_buf()
}
#[test]
fn a_traversing_entry_is_refused_and_writes_nothing_outside_the_destination() {
let dir = tmp_dir();
let evil = dir.join("evil.tar");
write_raw_tar(&evil, &[Raw::File("../escaped.txt", b"pwned")]);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dir.join("out").to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"a traversing entry must be refused as an unsafe path (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("escaped.txt").exists(),
"the file escaped the destination"
);
assert!(
String::from_utf8_lossy(&out.stderr).contains("../escaped.txt"),
"the refusal must name the offending entry, got: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn an_absolute_entry_path_is_refused() {
let dir = tmp_dir();
let evil = dir.join("abs.tar");
let escape = std::env::temp_dir().join(format!("stuffr-abs-escape-{}.txt", std::process::id()));
let _ = std::fs::remove_file(&escape);
write_raw_tar(&evil, &[Raw::File(escape.to_str().unwrap(), b"pwned")]);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dir.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"an absolute entry name must be refused as an unsafe path (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!escape.exists(),
"an absolute entry name wrote outside the destination: {}",
escape.display()
);
}
#[test]
fn cat_streams_one_named_entry_from_a_pipe() {
let bytes = std::fs::read(write_fixture_tar(
&tmp_dir(),
&[("a.txt", b"alpha"), ("b.txt", b"beta")],
))
.unwrap();
assert_eq!(run_with_stdin(&["cat", "-", "b.txt"], &bytes), "beta");
}
#[test]
fn pack_collects_several_paths_into_one_archive() {
let dir = tmp_dir();
std::fs::write(dir.join("one.txt"), b"1").unwrap();
std::fs::write(dir.join("two.txt"), b"2").unwrap();
let out = dir.join("bundle.tar");
let packed = run_output(&[
"pack",
dir.join("one.txt").to_str().unwrap(),
dir.join("two.txt").to_str().unwrap(),
"-o",
out.to_str().unwrap(),
]);
assert!(
packed.status.success(),
"stderr: {}",
String::from_utf8_lossy(&packed.stderr)
);
let listed = String::from_utf8(run_output(&["list", out.to_str().unwrap()]).stdout).unwrap();
assert!(
listed.contains("one.txt") && listed.contains("two.txt"),
"both entries must be listed, got: {listed}"
);
assert!(
!listed.contains(dir.to_str().unwrap()),
"absolute paths must not be stored as entry names, got: {listed}"
);
}
#[test]
fn a_symlink_whose_target_escapes_the_destination_is_refused_before_it_is_created() {
let dir = tmp_dir();
let dest = dir.join("out");
let evil = write_raw_tar(
&dir.join("link.tar"),
&[Raw::Symlink("link", "../../etc/passwd")],
);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"an escaping symlink target must be refused (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
std::fs::symlink_metadata(dest.join("link")).is_err(),
"the symlink must not have been created at all"
);
assert!(
String::from_utf8_lossy(&out.stderr).contains("../../etc/passwd"),
"the refusal must name the offending target, got: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn an_entry_written_through_an_escaping_symlink_lands_nowhere() {
let dir = tmp_dir();
let dest = dir.join("out");
let evil = write_raw_tar(
&dir.join("through.tar"),
&[
Raw::Symlink("evil", dir.to_str().unwrap()),
Raw::File("evil/pwned.txt", b"pwned"),
],
);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"an absolute symlink target must be refused (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("pwned.txt").exists(),
"the second entry was written through the symlink, outside the destination"
);
assert!(
std::fs::symlink_metadata(dest.join("evil")).is_err(),
"the symlink must not have been created"
);
}
#[test]
fn a_symlink_chain_cannot_walk_out_of_the_destination() {
let dir = tmp_dir();
let dest = dir.join("out");
let evil = write_raw_tar(
&dir.join("chain.tar"),
&[
Raw::Symlink("a", "b"),
Raw::Symlink("b", "../.."),
Raw::File("a/pwned.txt", b"pwned"),
],
);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"a chained symlink escape must be refused (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("pwned.txt").exists() && !dir.parent().unwrap().join("pwned.txt").exists(),
"the chain wrote outside the destination"
);
}
#[test]
fn a_traversal_buried_inside_a_deeper_path_is_refused() {
let dir = tmp_dir();
let dest = dir.join("out");
let evil = write_raw_tar(
&dir.join("deep.tar"),
&[Raw::File("a/b/../../../escaped.txt", b"pwned")],
);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"a buried traversal must be refused (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("escaped.txt").exists(),
"the buried traversal escaped the destination"
);
}
#[test]
fn a_directory_entry_that_escapes_the_destination_is_refused() {
let dir = tmp_dir();
let dest = dir.join("out");
let evil = write_raw_tar(&dir.join("dir.tar"), &[Raw::Dir("../escaped-dir/")]);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"an escaping directory entry must be refused (exit 7), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("escaped-dir").exists(),
"the directory escaped the destination"
);
}
#[test]
fn an_entry_named_dot_extracts_alongside_the_real_entries() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("dot.tar"),
&[Raw::Dir("./"), Raw::File("./a.txt", b"alpha")],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert!(
out.status.success(),
"a `./` entry must be accepted, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(std::fs::read(dest.join("a.txt")).unwrap(), b"alpha");
}
#[test]
fn a_system_tar_of_dot_extracts_cleanly() {
let Some(tar_bin) = which_tar() else {
eprintln!("system tar not found; skipping");
return;
};
let dir = tmp_dir();
let src = dir.join("src");
std::fs::create_dir_all(src.join("nested")).unwrap();
std::fs::write(src.join("top.txt"), b"top level").unwrap();
std::fs::write(src.join("nested/deep.txt"), b"nested payload").unwrap();
let archive = dir.join("dot.tar");
let status = Command::new(&tar_bin)
.arg("cf")
.arg(&archive)
.arg(".")
.current_dir(&src)
.status()
.unwrap();
assert!(
status.success(),
"the system tar failed to build the fixture"
);
let dest = dir.join("out");
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert!(
out.status.success(),
"`tar cf x.tar .` must extract cleanly, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(std::fs::read(dest.join("top.txt")).unwrap(), b"top level");
assert_eq!(
std::fs::read(dest.join("nested/deep.txt")).unwrap(),
b"nested payload"
);
}
fn which_tar() -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|dir| dir.join("tar"))
.find(|candidate| candidate.is_file())
}
#[cfg(unix)]
#[test]
fn extraction_writes_files_directories_and_contained_symlinks() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_raw_tar(
&dir.join("good.tar"),
&[
Raw::Dir("sub/"),
Raw::File("sub/a.txt", b"alpha"),
Raw::Symlink("sub/link", "a.txt"),
],
);
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!(dest.join("sub").is_dir());
assert_eq!(std::fs::read(dest.join("sub/a.txt")).unwrap(), b"alpha");
let link = std::fs::symlink_metadata(dest.join("sub/link")).unwrap();
assert!(
link.file_type().is_symlink(),
"a symlink entry must become a symlink"
);
assert_eq!(
std::fs::read_link(dest.join("sub/link")).unwrap(),
Path::new("a.txt")
);
assert_eq!(std::fs::read(dest.join("sub/link")).unwrap(), b"alpha");
}
#[test]
fn extraction_selects_only_the_entries_a_pattern_names() {
let dir = tmp_dir();
let dest = dir.join("out");
let archive = write_fixture_tar(
&dir,
&[
("a.txt", b"alpha"),
("b.txt", b"beta"),
("d/c.txt", b"gamma"),
],
);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"b.txt",
"d",
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dest.join("a.txt").exists(),
"an unmatched entry was extracted"
);
assert_eq!(std::fs::read(dest.join("b.txt")).unwrap(), b"beta");
assert_eq!(std::fs::read(dest.join("d/c.txt")).unwrap(), b"gamma");
}
#[test]
fn a_pattern_matching_no_entry_is_a_usage_error_rather_than_a_silent_success() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dir.join("out").to_str().unwrap(),
"nosuch.txt",
]);
assert_eq!(
out.status.code(),
Some(2),
"extracting nothing at all must not report success, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let out = run_output(&["cat", archive.to_str().unwrap(), "nosuch.txt"]);
assert_eq!(out.status.code(), Some(2), "cat must agree with unpack");
}
#[test]
fn extraction_refuses_an_existing_file_unless_force_is_given() {
let dir = tmp_dir();
let dest = dir.join("out");
std::fs::create_dir_all(&dest).unwrap();
std::fs::write(dest.join("a.txt"), b"mine").unwrap();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(2),
"an existing file must be refused like every other output, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(std::fs::read(dest.join("a.txt")).unwrap(), b"mine");
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--force",
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(std::fs::read(dest.join("a.txt")).unwrap(), b"alpha");
}
#[cfg(unix)]
#[test]
fn a_pre_existing_symlink_at_a_target_path_is_replaced_not_written_through() {
let dir = tmp_dir();
let dest = dir.join("out");
std::fs::create_dir_all(&dest).unwrap();
let outside = dir.join("outside.txt");
std::fs::write(&outside, b"untouched").unwrap();
std::os::unix::fs::symlink(&outside, dest.join("a.txt")).unwrap();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--force",
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
std::fs::read(&outside).unwrap(),
b"untouched",
"the entry was written through the pre-existing symlink"
);
assert_eq!(std::fs::read(dest.join("a.txt")).unwrap(), b"alpha");
}
#[test]
fn extraction_of_a_corrupt_archive_exits_five_not_seven() {
let dir = tmp_dir();
let good = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b.txt", b"beta")]);
let corrupt = corrupt_midway(&good);
let out = run_output(&[
"unpack",
corrupt.to_str().unwrap(),
"-C",
dir.join("out").to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(5),
"a corrupt archive is exit 5, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn extraction_of_a_bomb_exits_six_at_a_tight_max_ratio() {
let dir = tmp_dir();
let bomb = write_bomb_tar_gz(&dir);
let out = run_output(&[
"unpack",
bomb.to_str().unwrap(),
"-C",
dir.join("out").to_str().unwrap(),
"--max-ratio",
"2",
]);
assert_eq!(
out.status.code(),
Some(6),
"a bomb is a resource limit (exit 6), stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn cat_streams_every_entry_a_pattern_names_and_bounds_the_ratio() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b.txt", b"beta")]);
let out = run_output(&["cat", archive.to_str().unwrap(), "a.txt"]);
assert!(out.status.success());
assert_eq!(String::from_utf8(out.stdout).unwrap(), "alpha");
let bomb = write_bomb_tar_gz(&dir);
let out = run_output(&[
"cat",
bomb.to_str().unwrap(),
"bomb.bin",
"--max-ratio",
"2",
]);
assert_eq!(
out.status.code(),
Some(6),
"cat must bound an entry the same way extraction does, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn an_archive_needs_a_destination_and_the_error_says_which_flag() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let out = run_output(&["unpack", archive.to_str().unwrap()]);
assert_eq!(
out.status.code(),
Some(2),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("-C") && stderr.contains("tar"),
"the error must name both the container and the flag, got: {stderr}"
);
assert!(
!dir.join("fixture").exists(),
"nothing may be written for a refused command"
);
}
#[test]
fn pack_refuses_several_paths_when_the_output_is_not_a_container() {
let dir = tmp_dir();
std::fs::write(dir.join("one.txt"), b"1").unwrap();
std::fs::write(dir.join("two.txt"), b"2").unwrap();
let out = run_output(&[
"pack",
dir.join("one.txt").to_str().unwrap(),
dir.join("two.txt").to_str().unwrap(),
"-o",
dir.join("bundle.gz").to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(2),
"a codec cannot hold two inputs, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("bundle.gz").exists(),
"a refused command must write nothing"
);
}
#[test]
fn pack_into_a_container_round_trips_through_extraction() {
let dir = tmp_dir();
std::fs::write(dir.join("one.txt"), b"first").unwrap();
std::fs::write(dir.join("two.txt"), b"second").unwrap();
let archive = dir.join("bundle.tar");
let out = run_output(&[
"pack",
dir.join("one.txt").to_str().unwrap(),
dir.join("two.txt").to_str().unwrap(),
"-o",
archive.to_str().unwrap(),
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let dest = dir.join("out");
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("one.txt")).unwrap(), b"first");
assert_eq!(std::fs::read(dest.join("two.txt")).unwrap(), b"second");
}
#[test]
fn pack_refuses_two_inputs_that_would_share_one_entry_name() {
let dir = tmp_dir();
std::fs::create_dir_all(dir.join("a")).unwrap();
std::fs::create_dir_all(dir.join("b")).unwrap();
std::fs::write(dir.join("a/same.txt"), b"1").unwrap();
std::fs::write(dir.join("b/same.txt"), b"2").unwrap();
let archive = dir.join("dup.tar");
let out = run_output(&[
"pack",
dir.join("a/same.txt").to_str().unwrap(),
dir.join("b/same.txt").to_str().unwrap(),
"-o",
archive.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(2),
"two entries with one name would make an archive that cannot be \
extracted without --force, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(!archive.exists(), "a refused command must write nothing");
}
#[test]
fn pack_of_an_empty_directory_stores_the_directory_itself() {
let dir = tmp_dir();
std::fs::create_dir_all(dir.join("tree")).unwrap();
let archive = dir.join("tree.tar");
let out = run_output(&[
"pack",
dir.join("tree").to_str().unwrap(),
"-o",
archive.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(0),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let listed = run_output(&["list", archive.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
text.contains("tree"),
"the directory itself must be an entry, not an empty archive: {text}"
);
}
#[test]
fn flags_that_the_container_path_cannot_honour_are_refused_not_ignored() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha")]);
let dest = dir.join("out");
for extra in [vec!["--format", "gzip"], vec!["-o", "somewhere"]] {
let mut args = vec![
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
];
args.extend_from_slice(&extra);
let out = run_output(&args);
assert_eq!(
out.status.code(),
Some(2),
"`{}` must be refused alongside -C, stderr: {}",
extra.join(" "),
String::from_utf8_lossy(&out.stderr)
);
}
}
#[test]
fn pack_flags_the_container_path_cannot_honour_are_refused_not_ignored() {
let dir = tmp_dir();
let src = dir.join("a.txt");
std::fs::write(&src, b"alpha").unwrap();
let archive = dir.join("bundle.tar");
for extra in [
vec!["--threads", "4"],
vec!["--turbo"],
vec!["--allow-weak-encoder"],
] {
let mut args = vec![
"pack",
src.to_str().unwrap(),
"-o",
archive.to_str().unwrap(),
];
args.extend_from_slice(&extra);
let out = run_output(&args);
assert_eq!(
out.status.code(),
Some(2),
"`{}` must be refused on the container pack path, stderr: {}",
extra.join(" "),
String::from_utf8_lossy(&out.stderr)
);
assert!(
!archive.exists(),
"a refused command must write nothing: {}",
extra.join(" ")
);
}
let out = run_output(&[
"pack",
src.to_str().unwrap(),
"--format",
"gzip",
"--threads",
"2",
"--force",
]);
assert!(
out.status.success(),
"the codec path still honours --threads, stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
}
#[cfg(unix)]
#[test]
fn an_entry_written_through_a_symlinked_path_component_is_refused() {
let dir = tmp_dir();
let dest = dir.join("out");
let evil = write_raw_tar(
&dir.join("component.tar"),
&[
Raw::Dir("a/b/"),
Raw::Symlink("a/b/up", ".."),
Raw::Symlink("a/b/up/link", "../.."),
Raw::File("a/b/up/link/pwned.txt", b"pwned"),
],
);
let out = run_output(&[
"unpack",
evil.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
]);
assert_eq!(
out.status.code(),
Some(7),
"an entry resolving through a symlinked component must be refused (exit 7), \
stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert!(
!dir.join("pwned.txt").exists(),
"the entry escaped the destination through a symlinked path component"
);
}
#[cfg(unix)]
fn mode_of(path: &Path) -> u32 {
use std::os::unix::fs::PermissionsExt;
std::fs::metadata(path).unwrap().permissions().mode() & 0o7777
}
fn mtime_secs(path: &Path) -> u64 {
std::fs::metadata(path)
.unwrap()
.modified()
.unwrap()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
}
#[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)
);
}
fn craft_a_tar_lz_declaring_a_huge_dictionary(tag: &str) -> (PathBuf, PathBuf) {
let dir = tmp(tag);
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let tar_path = dir.join("bundle.tar");
write_raw_tar(
&tar_path,
&[Raw::File("a.txt", b"hello from inside the tar")],
);
assert!(
Command::new(STUFFR)
.args([
"pack",
tar_path.to_str().unwrap(),
"--format",
"lzip",
"--force"
])
.status()
.unwrap()
.success(),
"packing the fixture must succeed"
);
let legit = dir.join("bundle.tar.lz");
let mut bytes = std::fs::read(&legit).unwrap();
assert_eq!(&bytes[..4], b"LZIP", "fixture must really be lzip");
bytes[5] = 0x1D;
let crafted = dir.join("crafted.tar.lz");
std::fs::write(&crafted, &bytes).unwrap();
(legit, crafted)
}
#[test]
fn a_container_under_a_codec_declaring_a_huge_dictionary_is_refused_on_every_verb() {
let (_legit, crafted) = 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) = 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());
let _ = std::fs::remove_dir_all(&dest);
}
#[test]
fn memory_limit_is_honoured_rather_than_refused_on_the_container_path() {
let (_legit, crafted) = 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)
);
let _ = std::fs::remove_dir_all(&dest);
}
#[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(target),
"the refusal must name the archive's own target `{target}`, got: {stderr}"
);
assert!(
std::fs::symlink_metadata(dest.join("sub/top")).is_err(),
"the symlink must not have been created"
);
}
}
#[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}"
);
}
let _ = std::fs::remove_dir_all(&dir);
}
#[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()
);
}
#[test]
fn pack_walks_a_directory_and_round_trips_it() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::create_dir(root.join("empty")).unwrap();
std::fs::write(root.join("README.md"), b"readme").unwrap();
std::fs::write(root.join("src/main.rs"), b"fn main() {}").unwrap();
let out = dir.join("p.tar");
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let back = dir.join("back");
assert!(
Command::new(STUFFR)
.args([
"unpack",
out.to_str().unwrap(),
"-C",
back.to_str().unwrap()
])
.status()
.unwrap()
.success()
);
assert_eq!(
std::fs::read(back.join("proj/README.md")).unwrap(),
b"readme"
);
assert_eq!(
std::fs::read(back.join("proj/src/main.rs")).unwrap(),
b"fn main() {}"
);
assert!(
back.join("proj/empty").is_dir(),
"an empty directory must survive the round trip"
);
}
#[test]
fn pack_walks_a_directory_into_a_container_inside_a_codec() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/main.rs"), b"fn main() {}").unwrap();
let out = dir.join("backup.tar.gz");
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let back = dir.join("back");
assert!(
Command::new(STUFFR)
.args([
"unpack",
out.to_str().unwrap(),
"-C",
back.to_str().unwrap()
])
.status()
.unwrap()
.success()
);
assert_eq!(
std::fs::read(back.join("proj/src/main.rs")).unwrap(),
b"fn main() {}"
);
}
#[cfg(unix)]
#[test]
fn pack_stores_a_symlink_inside_a_directory_rather_than_following_it() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir(&root).unwrap();
std::fs::write(root.join("real.txt"), b"real").unwrap();
std::os::unix::fs::symlink("real.txt", root.join("link.txt")).unwrap();
let out = dir.join("p.tar");
assert!(
Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.status()
.unwrap()
.success()
);
let l = Command::new("tar")
.args(["tvf", out.to_str().unwrap()])
.output()
.expect("the system `tar` is this test's reference authority");
let text = String::from_utf8_lossy(&l.stdout);
assert!(
text.contains("link.txt -> real.txt"),
"tar tvf said: {text}"
);
}
#[cfg(unix)]
#[test]
fn pack_follows_a_symlink_named_on_the_command_line() {
let dir = tmp_dir();
std::fs::write(dir.join("real.txt"), b"real").unwrap();
std::os::unix::fs::symlink("real.txt", dir.join("link.txt")).unwrap();
let out = dir.join("named.tar");
assert!(
Command::new(STUFFR)
.args([
"pack",
dir.join("link.txt").to_str().unwrap(),
"-o",
out.to_str().unwrap()
])
.status()
.unwrap()
.success()
);
let back = dir.join("back");
assert!(
Command::new(STUFFR)
.args([
"unpack",
out.to_str().unwrap(),
"-C",
back.to_str().unwrap()
])
.status()
.unwrap()
.success()
);
assert_eq!(
std::fs::read(back.join("link.txt")).unwrap(),
b"real",
"a named symlink is followed, so the entry holds the target's bytes"
);
}
#[test]
fn packing_a_directory_into_a_container_that_cannot_hold_it_warns_rather_than_refusing() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("sub")).unwrap();
std::fs::write(root.join("sub/a.txt"), b"a").unwrap();
let out = dir.join("p.a");
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"must not refuse: {}",
String::from_utf8_lossy(&st.stderr)
);
let err = String::from_utf8_lossy(&st.stderr);
assert!(
err.contains("proj/sub"),
"it must SAY WHAT it dropped, by name; stderr: {err}"
);
let out2 = dir.join("p2.a");
let st2 = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
out2.to_str().unwrap(),
"--strict-fidelity",
])
.output()
.unwrap();
assert_eq!(
st2.status.code(),
Some(4),
"strict fidelity must exit 4 on the write side too"
);
}
#[test]
fn strict_fidelity_passes_on_a_pack_that_lost_nothing() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/main.rs"), b"fn main() {}").unwrap();
#[cfg(unix)]
std::os::unix::fs::symlink("main.rs", root.join("src/alias.rs")).unwrap();
let archive = dir.join("clean.tar");
let st = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
archive.to_str().unwrap(),
"--strict-fidelity",
])
.output()
.unwrap();
assert!(
st.status.success(),
"an ordinary tree into tar loses nothing, symlink included: {}",
String::from_utf8_lossy(&st.stderr)
);
#[cfg(unix)]
{
let listed = Command::new("tar")
.args(["tvf", archive.to_str().unwrap()])
.output()
.expect("the system `tar` is this test's reference authority");
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
text.contains("alias.rs -> main.rs"),
"the symlink must be stored as a link, not dropped or copied: {text}"
);
}
}
#[test]
fn two_directories_with_the_same_final_component_are_refused_before_writing() {
let dir = tmp_dir();
std::fs::create_dir_all(dir.join("a/proj")).unwrap();
std::fs::create_dir_all(dir.join("b/proj")).unwrap();
std::fs::write(dir.join("a/proj/x.txt"), b"x").unwrap();
std::fs::write(dir.join("b/proj/y.txt"), b"y").unwrap();
let out = dir.join("dup.tar");
let st = Command::new(STUFFR)
.args([
"pack",
dir.join("a/proj").to_str().unwrap(),
dir.join("b/proj").to_str().unwrap(),
"-o",
out.to_str().unwrap(),
])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(2),
"a name collision is a usage error"
);
assert!(
!out.exists(),
"nothing may be written: the refusal happens before the destination is opened"
);
}
#[test]
fn pack_of_dot_names_entries_after_the_directory_it_resolves_to() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/main.rs"), b"fn main() {}").unwrap();
let out = dir.join("dot.tar");
let st = Command::new(STUFFR)
.current_dir(&root)
.args(["pack", ".", "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"`pack .` must work: {}",
String::from_utf8_lossy(&st.stderr)
);
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
text.contains("proj/src/main.rs"),
"`.` resolves to its canonical final component, so entries sit under \
`proj/` exactly as `pack ../proj` would name them: {text}"
);
}
#[test]
fn pack_of_dotdot_names_entries_after_the_parent_it_resolves_to() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/main.rs"), b"fn main() {}").unwrap();
let out = dir.join("dotdot.tar");
let st = Command::new(STUFFR)
.current_dir(root.join("src"))
.args(["pack", "..", "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"`pack ..` must work: {}",
String::from_utf8_lossy(&st.stderr)
);
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
text.contains("proj/src/main.rs"),
"`..` must name entries after the directory it resolves to: {text}"
);
}
#[test]
fn a_trailing_slash_on_a_packed_directory_is_noise() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/main.rs"), b"fn main() {}").unwrap();
let out = dir.join("slash.tar");
let with_slash = format!("{}/", root.to_str().unwrap());
let st = Command::new(STUFFR)
.args(["pack", &with_slash, "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"a trailing slash must not change anything: {}",
String::from_utf8_lossy(&st.stderr)
);
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
text.contains("proj/src/main.rs"),
"entries must sit under `proj/`, not under an empty name: {text}"
);
}
#[test]
fn pack_excludes_its_own_output_from_the_walk() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(root.join("a.txt"), b"a").unwrap();
let st = Command::new(STUFFR)
.current_dir(&root)
.args(["pack", ".", "-o", "backup.tar"])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let out = root.join("backup.tar");
let first_len = std::fs::metadata(&out).unwrap().len();
let st = Command::new(STUFFR)
.current_dir(&root)
.args(["pack", ".", "-o", "backup.tar", "--force"])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let err = String::from_utf8_lossy(&st.stderr);
assert!(
err.contains("proj/backup.tar"),
"it must SAY WHAT it excluded, by name; stderr: {err}"
);
let second_len = std::fs::metadata(&out).unwrap().len();
assert_eq!(
first_len, second_len,
"a --force re-run must not nest the previous archive inside the new one"
);
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
!text.contains("backup.tar"),
"the archive must not contain an entry naming itself: {text}"
);
assert!(
text.contains("proj/a.txt"),
"everything else in the directory is still stored: {text}"
);
let st3 = Command::new(STUFFR)
.current_dir(&root)
.args([
"pack",
".",
"-o",
"backup.tar",
"--force",
"--strict-fidelity",
])
.output()
.unwrap();
assert_eq!(
st3.status.code(),
Some(0),
"excluding the archive from its own walk costs no fidelity, so the strict \
gate must not fire on it: {}",
String::from_utf8_lossy(&st3.stderr)
);
}
#[test]
fn pack_refuses_when_the_only_input_is_the_output_and_leaves_it_untouched() {
let dir = tmp_dir();
let root = dir.join("backups");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(root.join("data.txt"), b"last night's data").unwrap();
let st = Command::new(STUFFR)
.current_dir(&root)
.args(["pack", "data.txt", "-o", "backup.tar"])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let archive = root.join("backup.tar");
let before = std::fs::read(&archive).unwrap();
assert!(
before.len() > 1024,
"the fixture must be a real archive, not an empty one: {} bytes",
before.len()
);
let st = Command::new(STUFFR)
.current_dir(&root)
.args([
"pack",
"backup.tar",
"-o",
"backup.tar",
"--force",
"--strict-fidelity",
])
.output()
.unwrap();
let err = String::from_utf8_lossy(&st.stderr);
assert_eq!(
st.status.code(),
Some(2),
"an empty plan is a usage refusal, not a successful empty archive: {err}"
);
assert!(
err.contains("nothing to pack") && err.contains("backup.tar"),
"the refusal must name the cause and the destination: {err}"
);
assert_eq!(
std::fs::read(&archive).unwrap(),
before,
"refusing before the destination is opened means no temp file and no \
rename, so the existing archive must be byte-identical"
);
}
#[test]
fn pack_refuses_a_nightly_over_a_directory_holding_only_its_own_archive() {
let dir = tmp_dir();
let root = dir.join("backups");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(dir.join("data.txt"), b"last night's data").unwrap();
let st = Command::new(STUFFR)
.current_dir(&dir)
.args(["pack", "data.txt", "-o", "backups/nightly.tar"])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let archive = root.join("nightly.tar");
let before = std::fs::read(&archive).unwrap();
let st = Command::new(STUFFR)
.current_dir(&dir)
.args([
"pack",
"backups",
"-o",
"backups/nightly.tar",
"--force",
"--strict-fidelity",
])
.output()
.unwrap();
let err = String::from_utf8_lossy(&st.stderr);
assert_eq!(
st.status.code(),
Some(2),
"a plan reduced to the directory the output was removed from is a usage \
refusal, not an archive of one empty directory: {err}"
);
assert!(
err.contains("No archive was written") && err.contains("is unchanged"),
"the refusal must say plainly that the destination survives: {err}"
);
assert_eq!(
std::fs::read(&archive).unwrap(),
before,
"last night's archive must be byte-identical, not replaced by a shell"
);
}
#[test]
fn test_reports_a_zip_whose_index_declares_more_records_than_are_reachable() {
let dir = tmp_dir();
let src = dir.join("a.txt");
std::fs::write(&src, b"alpha").unwrap();
let zip_path = dir.join("dup.zip");
let st = Command::new(STUFFR)
.current_dir(&dir)
.args(["pack", "a.txt", "-o", "dup.zip"])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let st = Command::new(STUFFR)
.args(["test", zip_path.to_str().unwrap(), "--strict-fidelity"])
.output()
.unwrap();
let err = String::from_utf8_lossy(&st.stderr);
assert_eq!(st.status.code(), Some(0), "a clean zip is clean: {err}");
assert!(err.contains("exact fidelity"), "and still says so: {err}");
let mut bytes = std::fs::read(&zip_path).unwrap();
let eocd = bytes.len() - 22;
assert_eq!(&bytes[eocd..eocd + 4], b"PK\x05\x06", "no EOCD");
let le16 = |b: &[u8]| u16::from_le_bytes([b[0], b[1]]);
let cd_size = u32::from_le_bytes(bytes[eocd + 12..eocd + 16].try_into().unwrap()) as usize;
let cd_at = u32::from_le_bytes(bytes[eocd + 16..eocd + 20].try_into().unwrap()) as usize;
let cd = bytes[cd_at..cd_at + cd_size].to_vec();
let first = 46 + le16(&cd[28..]) as usize + le16(&cd[30..]) as usize + le16(&cd[32..]) as usize;
let entries = le16(&bytes[eocd + 10..]);
let mut out = bytes[..cd_at + cd_size].to_vec();
out.extend_from_slice(&cd[..first]);
let tail = &mut bytes[eocd..];
tail[8..10].copy_from_slice(&(entries + 1).to_le_bytes());
tail[10..12].copy_from_slice(&(entries + 1).to_le_bytes());
tail[12..16].copy_from_slice(&((cd_size + first) as u32).to_le_bytes());
out.extend_from_slice(tail);
let forged = dir.join("shadowed.zip");
std::fs::write(&forged, &out).unwrap();
let st = Command::new(STUFFR)
.args(["test", forged.to_str().unwrap()])
.output()
.unwrap();
let err = String::from_utf8_lossy(&st.stderr);
assert_eq!(
st.status.code(),
Some(0),
"a shadowed record is a fidelity loss, not corruption: {err}"
);
assert!(
err.contains("declares 2") && err.contains("only 1"),
"the warning must name both counts: {err}"
);
assert!(
!err.contains("exact fidelity"),
"a read that reached one of two records must not announce exact \
fidelity: {err}"
);
let st = Command::new(STUFFR)
.args(["test", forged.to_str().unwrap(), "--strict-fidelity"])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(4),
"--strict-fidelity must refuse it: {}",
String::from_utf8_lossy(&st.stderr)
);
}
fn which(bin: &str) -> Option<PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path).find_map(|dir| {
let candidate = dir.join(bin);
candidate.is_file().then_some(candidate)
})
}
fn require_bin(bin: &str) -> PathBuf {
which(bin).unwrap_or_else(|| {
panic!(
"no reference `{bin}` tool found on PATH — this test proved nothing, which is \
worth knowing rather than passing silently"
)
})
}
#[cfg(unix)]
fn perm_bits(md: &std::fs::Metadata) -> u32 {
use std::os::unix::fs::PermissionsExt;
md.permissions().mode() & 0o7777
}
#[cfg(unix)]
fn build_reference_tree(root: &Path) {
use std::os::unix::fs::PermissionsExt;
std::fs::create_dir_all(root.join("sub")).unwrap();
std::fs::create_dir(root.join("empty")).unwrap();
std::fs::write(root.join("a.txt"), b"alpha").unwrap();
std::fs::write(root.join("sub/b.bin"), b"\x00\xff\x00beta").unwrap();
std::os::unix::fs::symlink("a.txt", root.join("link")).unwrap();
for (p, mode) in [
(root.to_path_buf(), 0o700),
(root.join("sub"), 0o700),
(root.join("empty"), 0o700),
(root.join("a.txt"), 0o600),
(root.join("sub/b.bin"), 0o600),
] {
std::fs::set_permissions(&p, std::fs::Permissions::from_mode(mode)).unwrap();
}
}
#[cfg(unix)]
fn tree_snapshot(root: &Path) -> Vec<String> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).unwrap() {
let path = entry.unwrap().path();
let rel = path.strip_prefix(root).unwrap().display().to_string();
let md = std::fs::symlink_metadata(&path).unwrap();
if md.is_symlink() {
out.push(format!(
"{rel}: symlink -> {}",
std::fs::read_link(&path).unwrap().display()
));
} else if md.is_dir() {
out.push(format!("{rel}: dir mode {:o}", perm_bits(&md)));
stack.push(path);
} else {
out.push(format!(
"{rel}: file mode {:o} bytes {:?}",
perm_bits(&md),
std::fs::read(&path).unwrap()
));
}
}
}
out.sort();
out
}
fn run_tool(bin: &Path, args: &[&std::ffi::OsStr], cwd: &Path, stdin: &[u8]) -> Vec<u8> {
let mut child = Command::new(bin)
.args(args)
.current_dir(cwd)
.env("COPYFILE_DISABLE", "1")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(stdin).unwrap();
let out = child.wait_with_output().unwrap();
assert!(
out.status.success(),
"reference tool {} {:?} failed: {}",
bin.display(),
args,
String::from_utf8_lossy(&out.stderr)
);
out.stdout
}
fn os(s: &impl AsRef<std::ffi::OsStr>) -> &std::ffi::OsStr {
s.as_ref()
}
fn pack_ok(args: &[&std::ffi::OsStr]) -> String {
let out = Command::new(STUFFR)
.arg("pack")
.args(args)
.output()
.unwrap();
assert!(
out.status.success(),
"stuffr pack {:?} failed: {}",
args,
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stderr).to_string()
}
fn unpack_ok(archive: &Path, dest: &Path) {
let out = Command::new(STUFFR)
.args([
std::ffi::OsStr::new("unpack"),
archive.as_ref(),
std::ffi::OsStr::new("-C"),
dest.as_ref(),
])
.output()
.unwrap();
assert!(
out.status.success(),
"stuffr unpack {} failed: {}",
archive.display(),
String::from_utf8_lossy(&out.stderr)
);
}
#[cfg(unix)]
#[test]
fn every_container_round_trips_a_tree_against_its_reference_tool() {
let tar_bin = require_bin("tar");
let zip_bin = require_bin("zip");
let unzip_bin = require_bin("unzip");
let cpio_bin = require_bin("cpio");
let ar_bin = require_bin("ar");
let dir = tmp_dir();
let tree = dir.join("tree");
let root = tree.join("proj");
build_reference_tree(&root);
let want = tree_snapshot(&root);
assert_eq!(
want,
vec![
"a.txt: file mode 600 bytes [97, 108, 112, 104, 97]".to_string(),
"empty: dir mode 700".to_string(),
"link: symlink -> a.txt".to_string(),
"sub/b.bin: file mode 600 bytes [0, 255, 0, 98, 101, 116, 97]".to_string(),
"sub: dir mode 700".to_string(),
],
"the fixture must carry a nested dir, an EMPTY dir, a symlink and two \
differently-named files, or the comparisons below prove less than they claim"
);
let out_tar = dir.join("out.tar");
pack_ok(&[os(&root), os(&"-o"), os(&out_tar)]);
let back = dir.join("back-tar");
std::fs::create_dir(&back).unwrap();
run_tool(
&tar_bin,
&[os(&"-xf"), os(&out_tar), os(&"-C"), os(&back)],
&dir,
b"",
);
assert_eq!(
tree_snapshot(&back.join("proj")),
want,
"the system tar read our tar back as a different tree"
);
let ref_tar = dir.join("ref.tar");
run_tool(
&tar_bin,
&[os(&"-cf"), os(&ref_tar), os(&"proj")],
&tree,
b"",
);
let back = dir.join("back-ref-tar");
unpack_ok(&ref_tar, &back);
assert_eq!(
tree_snapshot(&back.join("proj")),
want,
"we read a tar the system tar wrote as a different tree"
);
let out_zip = dir.join("out.zip");
pack_ok(&[os(&root), os(&"-o"), os(&out_zip)]);
let back = dir.join("back-zip");
run_tool(
&unzip_bin,
&[os(&"-q"), os(&out_zip), os(&"-d"), os(&back)],
&dir,
b"",
);
assert_eq!(
tree_snapshot(&back.join("proj")),
want,
"unzip read our zip back as a different tree"
);
let ref_zip = dir.join("ref.zip");
run_tool(
&zip_bin,
&[os(&"-qry"), os(&ref_zip), os(&"proj")],
&tree,
b"",
);
let back = dir.join("back-ref-zip");
unpack_ok(&ref_zip, &back);
assert_eq!(
tree_snapshot(&back.join("proj")),
want,
"we read a zip the system zip wrote as a different tree"
);
let out_cpio = dir.join("out.cpio");
pack_ok(&[os(&root), os(&"-o"), os(&out_cpio)]);
let back = dir.join("back-cpio");
std::fs::create_dir(&back).unwrap();
run_tool(
&cpio_bin,
&[os(&"-i"), os(&"-d"), os(&"--quiet")],
&back,
&std::fs::read(&out_cpio).unwrap(),
);
assert_eq!(
tree_snapshot(&back.join("proj")),
want,
"the system cpio read our cpio back as a different tree"
);
let names = b"proj\nproj/a.txt\nproj/empty\nproj/link\nproj/sub\nproj/sub/b.bin\n";
let ref_cpio_bytes = run_tool(
&cpio_bin,
&[os(&"-o"), os(&"--format=newc"), os(&"--quiet")],
&tree,
names,
);
let ref_cpio = dir.join("ref.cpio");
std::fs::write(&ref_cpio, &ref_cpio_bytes).unwrap();
let back = dir.join("back-ref-cpio");
unpack_ok(&ref_cpio, &back);
assert_eq!(
tree_snapshot(&back.join("proj")),
want,
"we read a cpio the system cpio wrote as a different tree"
);
let out_ar = dir.join("out.a");
let stderr = pack_ok(&[os(&root), os(&"-o"), os(&out_ar)]);
for dropped in ["proj/empty", "proj/link", "proj/sub"] {
assert!(
stderr.contains(dropped),
"`ar` dropping `{dropped}` must be named in the fidelity report; stderr: {stderr}"
);
}
let listed = run_tool(&ar_bin, &[os(&"t"), os(&out_ar)], &dir, b"");
let mut members: Vec<String> = String::from_utf8_lossy(&listed)
.lines()
.map(str::to_string)
.collect();
members.sort();
assert_eq!(
members,
vec!["proj/a.txt".to_string(), "proj/sub/b.bin".to_string()],
"the system ar must list exactly the regular files, and nothing standing in for \
the directories or the symlink"
);
let ref_ar = dir.join("ref.a");
run_tool(
&ar_bin,
&[os(&"rcS"), os(&ref_ar), os(&"a.txt")],
&root,
b"",
);
let back = dir.join("back-ref-ar");
unpack_ok(&ref_ar, &back);
assert_eq!(
std::fs::read(back.join("a.txt")).unwrap(),
b"alpha",
"we read an ar the system ar wrote as different bytes"
);
}
#[test]
fn packing_the_same_tree_twice_produces_identical_bytes() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("b")).unwrap();
for n in ["z.txt", "m.txt", "a.txt"] {
std::fs::write(root.join(n), n.as_bytes()).unwrap();
}
std::fs::write(root.join("b/inner.txt"), b"inner").unwrap();
let readdir_order: Vec<String> = std::fs::read_dir(&root)
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().to_string())
.collect();
let mut sorted = readdir_order.clone();
sorted.sort();
assert_ne!(
readdir_order, sorted,
"this filesystem hands back directory entries already sorted, so the ordering \
assertion below could not fail; the fixture needs a name order this filesystem \
does not reproduce"
);
let listing = dir.join("order.tar");
let st = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
listing.to_str().unwrap(),
])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let listed = run_output(&["list", listing.to_str().unwrap()]);
let names: Vec<String> = String::from_utf8_lossy(&listed.stdout)
.lines()
.map(|l| l.split_whitespace().last().unwrap_or_default().to_string())
.collect();
assert_eq!(
names,
vec![
"proj",
"proj/a.txt",
"proj/b",
"proj/b/inner.txt",
"proj/m.txt",
"proj/z.txt",
],
"entries must be written in sorted, depth-first order — `readdir` order was {readdir_order:?}"
);
for ext in ["tar", "tar.gz", "zip"] {
let one = dir.join(format!("one.{ext}"));
let two = dir.join(format!("two.{ext}"));
for out in [&one, &two] {
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
}
assert_eq!(
std::fs::read(&one).unwrap(),
std::fs::read(&two).unwrap(),
"the same tree must pack to the same `.{ext}` bytes; readdir order is not stable"
);
}
}
#[cfg(unix)]
#[test]
fn a_failed_write_never_replaces_an_existing_archive() {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir(&root).unwrap();
std::fs::write(root.join("a.txt"), b"alpha").unwrap();
let big = root.join("zbig.bin");
let f = std::fs::File::create(&big).unwrap();
f.set_len(u64::from(u32::MAX) + 1).unwrap();
drop(f);
let md = std::fs::metadata(&big).unwrap();
assert!(
md.len() > u64::from(u32::MAX),
"the premise: the entry must exceed cpio's u32 size field"
);
assert!(
md.blocks() * 512 < md.len(),
"this filesystem did not make the fixture sparse ({} blocks for {} bytes); a \
non-sparse one would write 4 GiB to disk here",
md.blocks(),
md.len()
);
let out = dir.join("existing.cpio");
std::fs::write(&out, b"PRECIOUS EXISTING CONTENT").unwrap();
let st = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--force",
])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(3),
"an entry this container cannot express is a capability limit, exit 3; stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
assert_eq!(
std::fs::read(&out).unwrap(),
b"PRECIOUS EXISTING CONTENT",
"a pack that failed mid-write must not have replaced the archive already there"
);
let mut siblings: Vec<String> = std::fs::read_dir(&dir)
.unwrap()
.map(|e| e.unwrap().file_name().to_string_lossy().to_string())
.collect();
siblings.sort();
assert_eq!(
siblings,
vec!["existing.cpio".to_string(), "proj".to_string()],
"a failed pack must leave no temp file behind"
);
let ro = dir.join("readonly");
std::fs::create_dir(&ro).unwrap();
std::fs::set_permissions(&ro, std::fs::Permissions::from_mode(0o500)).unwrap();
assert!(
std::fs::File::create(ro.join("probe")).is_err(),
"this test needs a non-root user; the read-only directory is still writable"
);
let target = ro.join("out.tar");
let st = Command::new(STUFFR)
.args([
"pack",
root.join("a.txt").to_str().unwrap(),
"-o",
target.to_str().unwrap(),
])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(1),
"a destination that cannot be created is an i/o failure, exit 1; stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
assert!(!target.exists(), "a failed pack must publish nothing");
std::fs::set_permissions(&ro, std::fs::Permissions::from_mode(0o700)).unwrap();
}
#[cfg(unix)]
#[test]
fn an_unreadable_file_is_skipped_with_a_warning_rather_than_failing_the_pack() {
use std::os::unix::fs::PermissionsExt;
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir(&root).unwrap();
std::fs::write(root.join("a.txt"), b"alpha").unwrap();
let unreadable = root.join("secret.txt");
std::fs::write(&unreadable, b"beta").unwrap();
std::fs::set_permissions(&unreadable, std::fs::Permissions::from_mode(0o000)).unwrap();
assert!(
std::fs::File::open(&unreadable).is_err(),
"this test needs a non-root user; the unreadable file is still readable"
);
let out = dir.join("backup.tar");
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"one unreadable file must not lose the whole backup: {}",
String::from_utf8_lossy(&st.stderr)
);
let stderr = String::from_utf8_lossy(&st.stderr);
assert!(
stderr.contains("proj/secret.txt"),
"it must SAY WHAT it skipped, by name; stderr: {stderr}"
);
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(text.contains("proj/a.txt"), "{text}");
assert!(
!text.contains("secret.txt"),
"an entry that could not be read must not be written as an empty one: {text}"
);
let strict = dir.join("strict.tar");
let st = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
strict.to_str().unwrap(),
"--strict-fidelity",
])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(4),
"strict fidelity must refuse a pack that skipped a file; stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
std::fs::set_permissions(&unreadable, std::fs::Permissions::from_mode(0o600)).unwrap();
}
fn workspace_source_files() -> Vec<PathBuf> {
let crates = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let mut out = Vec::new();
let mut stack: Vec<PathBuf> = std::fs::read_dir(crates)
.unwrap()
.map(|e| e.unwrap().path().join("src"))
.filter(|p| p.is_dir())
.collect();
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
out.sort();
out
}
fn rust_string_literals(src: &str) -> Vec<(usize, String)> {
let c: Vec<char> = src.chars().collect();
let mut out = Vec::new();
let mut i = 0usize;
let mut line = 1usize;
while i < c.len() {
if c[i] == '\n' {
line += 1;
i += 1;
continue;
}
if c[i] == '/' && c.get(i + 1) == Some(&'/') {
while i < c.len() && c[i] != '\n' {
i += 1;
}
continue;
}
if c[i] == '/' && c.get(i + 1) == Some(&'*') {
let mut depth = 1usize;
i += 2;
while i < c.len() && depth > 0 {
if c[i] == '/' && c.get(i + 1) == Some(&'*') {
depth += 1;
i += 2;
} else if c[i] == '*' && c.get(i + 1) == Some(&'/') {
depth -= 1;
i += 2;
} else {
if c[i] == '\n' {
line += 1;
}
i += 1;
}
}
continue;
}
if c[i] == '\'' {
if c.get(i + 1) == Some(&'\\') {
let mut j = i + 2;
while j < c.len() && c[j] != '\'' {
j += 1;
}
i = j + 1;
continue;
}
if c.get(i + 2) == Some(&'\'') {
i += 3;
continue;
}
i += 1;
continue;
}
let ident_before = i > 0 && (c[i - 1].is_alphanumeric() || c[i - 1] == '_');
if (c[i] == 'r' || c[i] == 'b') && !ident_before {
let mut j = i;
if c[j] == 'b' {
j += 1;
}
if c.get(j) == Some(&'r') {
j += 1;
let mut hashes = 0usize;
while c.get(j) == Some(&'#') {
hashes += 1;
j += 1;
}
if c.get(j) == Some(&'"') {
let start_line = line;
j += 1;
let mut text = String::new();
loop {
if j >= c.len() {
break;
}
if c[j] == '"' && c[j + 1..].iter().take(hashes).all(|h| *h == '#') {
j += 1 + hashes;
break;
}
if c[j] == '\n' {
line += 1;
}
text.push(c[j]);
j += 1;
}
out.push((start_line, text));
i = j;
continue;
}
}
}
if c[i] == '"' {
let start_line = line;
let mut text = String::new();
let mut j = i + 1;
while j < c.len() && c[j] != '"' {
if c[j] == '\\' && c.get(j + 1) == Some(&'\n') {
line += 1;
j += 2;
while matches!(c.get(j), Some(' ' | '\t' | '\r' | '\n')) {
if c[j] == '\n' {
line += 1;
}
j += 1;
}
continue;
}
if c[j] == '\\' {
text.push(c[j]);
if let Some(&e) = c.get(j + 1) {
text.push(e);
}
j += 2;
continue;
}
if c[j] == '\n' {
line += 1;
}
text.push(c[j]);
j += 1;
}
out.push((start_line, text));
i = j + 1;
continue;
}
i += 1;
}
out
}
fn first_space_run(text: &str) -> Option<usize> {
text.as_bytes().windows(3).position(|w| w == b" ")
}
fn is_column_label(prefix: &str) -> bool {
let tail = prefix
.rsplit('\n')
.next()
.unwrap_or(prefix)
.rsplit("\\n")
.next()
.unwrap_or(prefix);
match tail.strip_suffix(':') {
Some(label) => {
!label.is_empty()
&& !label.contains(' ')
&& label
.chars()
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
}
None => false,
}
}
#[test]
fn no_message_literal_in_the_workspace_carries_a_run_of_collapsed_indentation() {
let defective = "fn f() { err(\"packing paths needs a container (`-o bundle.tar`, or --format tar)\"); }";
let found = rust_string_literals(defective);
assert_eq!(
found.len(),
1,
"the scanner must find the literal: {found:?}"
);
assert!(
first_space_run(&found[0].1).is_some_and(|at| !is_column_label(&found[0].1[..at])),
"the scanner must flag the historical defect: {:?}",
found[0].1
);
let continued = "fn f() { err(\"packing paths needs a container \\\n (`-o bundle.tar`, or --format tar)\"); }";
let found = rust_string_literals(continued);
assert_eq!(found.len(), 1);
assert_eq!(
found[0].1, "packing paths needs a container (`-o bundle.tar`, or --format tar)",
"a `\\`-continued literal carries no indentation, and the scanner must apply that \
rule rather than report the source text"
);
assert!(first_space_run(&found[0].1).is_none());
let aligned = "fn f() { println!(\"format: {}\", x); }";
let found = rust_string_literals(aligned);
let at = first_space_run(&found[0].1).expect("the column really is three spaces");
assert!(
is_column_label(&found[0].1[..at]),
"a deliberate column label must not be reported as a collapse"
);
let colon_prose = "fn f() { err(\"stuffr cannot do this: try force\"); }";
let found = rust_string_literals(colon_prose);
assert_eq!(found.len(), 1);
let at = first_space_run(&found[0].1).expect("the run is there");
assert!(
!is_column_label(&found[0].1[..at]),
"a prose clause ending in a colon is not a column label: {:?}",
found[0].1
);
assert!(is_column_label("MemAvailable:"));
assert!(!is_column_label("two words:"));
let quoted_char = "fn f() { let q = '\"'; err(\"a collapse\"); }";
let found = rust_string_literals(quoted_char);
assert_eq!(
found.iter().map(|(_, t)| t.as_str()).collect::<Vec<_>>(),
vec!["a collapse"],
"a `\"` inside a char literal must not desynchronise the scan"
);
let files = workspace_source_files();
assert!(
files.len() > 20,
"the walk found only {} source files, which is not this workspace: {files:?}",
files.len()
);
assert!(
files.iter().any(|p| p.ends_with("stuffr-cli/src/main.rs")),
"the walk must reach the crate every user-facing message is printed from"
);
let mut scanned = 0usize;
let mut offenders = Vec::new();
for file in &files {
let src = std::fs::read_to_string(file).unwrap();
for (line, text) in rust_string_literals(&src) {
scanned += 1;
let Some(at) = first_space_run(&text) else {
continue;
};
if is_column_label(&text[..at]) {
continue;
}
offenders.push(format!("{}:{line}: {text:?}", file.display()));
}
}
assert!(
scanned > 1000,
"only {scanned} literals scanned, which is not this workspace"
);
assert!(
offenders.is_empty(),
"a string literal carries a run of three or more spaces, which reaches the user as \
collapsed continuation indentation mid-sentence. Re-join the sentence and end each \
continued line with `\\` (which strips the newline AND the next line's indentation); \
a deliberate column is written `label: value`. Offenders:\n{}",
offenders.join("\n")
);
}
#[test]
fn packing_the_filesystem_root_is_a_usage_error_that_says_why() {
let dir = tmp_dir();
let out = dir.join("root.tar");
let st = Command::new(STUFFR)
.args(["pack", "/", "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(2),
"packing `/` is the caller's mistake, not an i/o failure; stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let stderr = String::from_utf8_lossy(&st.stderr);
assert!(
stderr.contains("no final path component"),
"the message must say what is wrong with `/`: {stderr}"
);
assert!(
!out.exists(),
"the refusal happens before the destination is opened"
);
}
#[test]
fn a_nonexistent_dotdot_path_is_reported_as_an_io_error() {
let dir = tmp_dir();
let out = dir.join("dd.tar");
let missing = dir.join("nosuch/..");
let st = Command::new(STUFFR)
.args([
"pack",
missing.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
])
.output()
.unwrap();
assert_eq!(
st.status.code(),
Some(1),
"a path that does not exist is an i/o error, whatever its final component; \
stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
assert!(!out.exists());
}
#[cfg(unix)]
#[test]
fn pack_of_dot_from_a_symlinked_directory_names_entries_after_the_real_one() {
let dir = tmp_dir();
std::fs::create_dir_all(dir.join("real/src")).unwrap();
std::fs::write(dir.join("real/src/main.rs"), b"fn main() {}").unwrap();
std::os::unix::fs::symlink("real", dir.join("proj")).unwrap();
let out = dir.join("sym.tar");
let st = Command::new(STUFFR)
.current_dir(dir.join("proj"))
.args(["pack", ".", "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(
text.contains("real/src/main.rs"),
"`.` resolves through the symlink, so entries sit under the real directory's \
name: {text}"
);
assert!(
!text.contains("proj/"),
"nothing may be named after the link itself: {text}"
);
}
#[test]
fn excluding_the_output_from_its_own_walk_is_a_note_not_a_fidelity_loss() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(root.join("a.txt"), b"payload\n").unwrap();
let out = root.join("backup.tar");
for run in 1..=3 {
let st = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
out.to_str().unwrap(),
"--force",
"--strict-fidelity",
])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&st.stderr).to_string();
assert_eq!(
st.status.code(),
Some(0),
"run {run}: declining to store the archive inside itself is not a \
fidelity loss, so the strict gate must stay green; stderr: {stderr}"
);
if run > 1 {
assert!(
stderr.contains("proj/backup.tar"),
"run {run}: the user must still be told, by name: {stderr}"
);
}
assert!(
stderr.contains("no fidelity loss"),
"run {run}: the count in the summary must agree with the (empty) \
warning list: {stderr}"
);
}
let listed = run_output(&["list", out.to_str().unwrap()]);
let text = String::from_utf8_lossy(&listed.stdout);
assert!(text.contains("proj/a.txt"), "{text}");
assert!(
!text.contains("backup.tar"),
"the archive must not be inside itself: {text}"
);
}
#[test]
fn a_tbz_output_writes_tar_inside_bzip2_and_not_a_bare_gzip() {
let dir = tmp_dir();
let src = dir.join("notes.txt");
std::fs::write(&src, b"payload\n").unwrap();
let out = dir.join("f.tbz");
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 bytes = std::fs::read(&out).unwrap();
assert_eq!(
&bytes[..3],
b"BZh",
"a .tbz must be bzip2 — it was gzip (1f 8b) before this: {:02x?}",
&bytes[..4.min(bytes.len())]
);
let listed = run_output(&["list", out.to_str().unwrap()]);
assert!(
listed.status.success(),
"a .tbz must open as an archive: {}",
String::from_utf8_lossy(&listed.stderr)
);
assert!(
String::from_utf8_lossy(&listed.stdout).contains("notes.txt"),
"the tar layer must be there: {}",
String::from_utf8_lossy(&listed.stdout)
);
let out2 = dir.join("f.tbz2");
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", out2.to_str().unwrap()])
.status()
.unwrap()
.success()
);
assert_eq!(&std::fs::read(&out2).unwrap()[..3], b"BZh");
let out3 = dir.join("f.bz");
assert!(
Command::new(STUFFR)
.args(["pack", src.to_str().unwrap(), "-o", out3.to_str().unwrap()])
.status()
.unwrap()
.success()
);
assert_ne!(
&std::fs::read(&out3).unwrap()[..3],
b"BZh",
"`bz` must not resolve to bzip2 outside the `t`-prefixed compound form"
);
}
#[test]
fn a_directory_on_the_single_stream_path_is_a_usage_error_naming_the_fix() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(&root).unwrap();
std::fs::write(root.join("a.txt"), b"payload\n").unwrap();
let out = dir.join("proj.gz");
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&st.stderr).to_string();
assert_eq!(
st.status.code(),
Some(2),
"asking a codec for a tree is the caller's mistake, not an i/o \
failure; stderr: {stderr}"
);
assert!(
!stderr.contains("os error 21"),
"the raw EISDIR must not reach the user: {stderr}"
);
assert!(
stderr.contains("proj.tar.gz"),
"the message must name the output that works: {stderr}"
);
assert!(
!out.exists(),
"the refusal happens before the destination is touched"
);
let good = dir.join("proj.tar.gz");
assert!(
Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", good.to_str().unwrap()])
.status()
.unwrap()
.success()
);
}
#[test]
fn the_pack_summary_counts_losses_rather_than_claiming_exact_fidelity() {
let dir = tmp_dir();
let root = dir.join("proj");
std::fs::create_dir_all(root.join("sub")).unwrap();
std::fs::write(root.join("sub").join("a.txt"), b"payload\n").unwrap();
let out = dir.join("p.a");
let st = Command::new(STUFFR)
.args(["pack", root.to_str().unwrap(), "-o", out.to_str().unwrap()])
.output()
.unwrap();
assert!(
st.status.success(),
"stderr: {}",
String::from_utf8_lossy(&st.stderr)
);
let stderr = String::from_utf8_lossy(&st.stderr).to_string();
assert!(
!stderr.contains("exact fidelity"),
"a pack that lost entries must not announce exact fidelity: {stderr}"
);
let reported: usize = stderr
.lines()
.find_map(|l| l.split_once(" fidelity loss(es)"))
.and_then(|(head, _)| head.rsplit(", ").next())
.and_then(|n| n.trim().parse().ok())
.unwrap_or_else(|| panic!("the summary must carry a loss count: {stderr}"));
let listed: usize = stderr
.lines()
.find_map(|l| l.strip_prefix("stuffr: "))
.and_then(|l| l.split_once(" fidelity warning(s):"))
.and_then(|(n, _)| n.parse().ok())
.unwrap_or_else(|| panic!("the warning header must carry a count: {stderr}"));
assert_eq!(
reported, listed,
"the summary's count and the warning list's count are the same fact: {stderr}"
);
assert!(
reported > 0,
"packing a tree into `ar` really does lose the \
directory entries: {stderr}"
);
let clean = dir.join("p.tar");
let st = Command::new(STUFFR)
.args([
"pack",
root.to_str().unwrap(),
"-o",
clean.to_str().unwrap(),
])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&st.stderr).to_string();
assert!(
stderr.contains("no fidelity loss"),
"a lossless pack must say so: {stderr}"
);
}
fn write_zip_via_pack(dir: &Path, entries: &[(&str, &[u8])]) -> PathBuf {
let src = dir.join("src");
std::fs::create_dir_all(&src).unwrap();
let mut paths = Vec::new();
for (name, data) in entries {
let p = src.join(name);
std::fs::write(&p, data).unwrap();
paths.push(p);
}
let zip = dir.join("bundle.zip");
let mut args: Vec<String> = vec!["pack".into()];
args.extend(paths.iter().map(|p| p.to_string_lossy().into_owned()));
args.push("-o".into());
args.push(zip.to_string_lossy().into_owned());
let out = Command::new(STUFFR).args(&args).output().unwrap();
assert!(
out.status.success(),
"building the zip fixture failed: {}",
String::from_utf8_lossy(&out.stderr)
);
zip
}
#[test]
fn the_index_list_prints_is_the_index_cat_selects_by() {
let dir = tmp_dir();
let archive = write_fixture_tar(
&dir,
&[
("a.txt", b"alpha"),
("b.txt", b"bravo"),
("c.txt", b"charlie"),
],
);
let listing =
String::from_utf8(run_output(&["list", archive.to_str().unwrap()]).stdout).unwrap();
let rows: Vec<&str> = listing.lines().collect();
assert_eq!(rows.len(), 3, "{listing}");
for (want_index, (_, payload)) in [
("a.txt", &b"alpha"[..]),
("b.txt", &b"bravo"[..]),
("c.txt", &b"charlie"[..]),
]
.iter()
.enumerate()
{
let printed = rows[want_index].split_whitespace().next().unwrap();
assert_eq!(
printed,
want_index.to_string(),
"list's first column must be the 0-based archive position: {listing}"
);
let out = run_output(&[
"cat",
archive.to_str().unwrap(),
"--index",
&want_index.to_string(),
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
out.stdout, *payload,
"row {want_index} of the listing and `--index {want_index}` must be \
the same entry"
);
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn index_selection_agrees_between_a_seekable_zip_and_the_same_zip_on_a_pipe() {
let dir = tmp_dir();
let payloads: [&[u8]; 4] = [b"first", b"second!!", b"third:::", b"fourth"];
let zip = write_zip_via_pack(
&dir,
&[
("one.txt", payloads[0]),
("two.txt", payloads[1]),
("three.txt", payloads[2]),
("four.txt", payloads[3]),
],
);
let bytes = std::fs::read(&zip).unwrap();
for (i, payload) in payloads.iter().enumerate() {
let n = i.to_string();
let seekable = run_output(&["cat", zip.to_str().unwrap(), "--index", &n]);
assert!(
seekable.status.success(),
"index {n} on a file: {}",
String::from_utf8_lossy(&seekable.stderr)
);
let piped = run_with_stdin_output(&["cat", "-", "--index", &n], &bytes);
assert!(
piped.status.success(),
"index {n} on a pipe: {}",
String::from_utf8_lossy(&piped.stderr)
);
assert_eq!(
seekable.stdout, piped.stdout,
"the random-access and counted routes disagreed at index {n}"
);
assert_eq!(
seekable.stdout,
payload.to_vec(),
"index {n} did not reach the entry the archive was built with"
);
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn repeated_index_flags_are_deduplicated_and_delivered_in_archive_order() {
let dir = tmp_dir();
let archive = write_fixture_tar(
&dir,
&[
("a.txt", b"alpha"),
("b.txt", b"bravo"),
("c.txt", b"charlie"),
],
);
let out = run_output(&[
"cat",
archive.to_str().unwrap(),
"--index",
"2",
"--index",
"0",
"--index",
"2",
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(
out.stdout, b"alphacharlie",
"archive order, once each — not typed order, and not twice"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn an_index_past_the_end_is_a_usage_error_naming_the_real_entry_count() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b.txt", b"bravo")]);
let zip = write_zip_via_pack(&dir, &[("one.txt", b"first"), ("two.txt", b"second")]);
for path in [&archive, &zip] {
let out = run_output(&["cat", path.to_str().unwrap(), "--index", "9"]);
assert_eq!(
out.status.code(),
Some(2),
"{}: stderr {}",
path.display(),
String::from_utf8_lossy(&out.stderr)
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("#9") && stderr.contains("0-1"),
"the refusal must name the index asked for and the VALID range, \
whichever route found it: {stderr}"
);
assert!(
out.stdout.is_empty(),
"an out-of-range index must not stream anything first"
);
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn index_and_patterns_together_are_a_usage_error() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b.txt", b"bravo")]);
let dest = dir.join("out");
let out = run_output(&["cat", archive.to_str().unwrap(), "a.txt", "--index", "1"]);
assert_eq!(out.status.code(), Some(2));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("--index"), "{stderr}");
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"a.txt",
"-C",
dest.to_str().unwrap(),
"--index",
"1",
]);
assert_eq!(out.status.code(), Some(2));
assert!(
!dest.exists(),
"the refusal must come before anything is created"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn unpack_by_index_without_a_directory_names_the_index_in_its_refusal() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b.txt", b"bravo")]);
let out = run_output(&["unpack", archive.to_str().unwrap(), "--index", "1"]);
assert_eq!(out.status.code(), Some(2));
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("--index 1") && stderr.contains("-C"),
"the refusal must name what was typed and what to do instead: {stderr}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn unpack_by_index_extracts_only_the_selected_entry() {
let dir = tmp_dir();
let archive = write_fixture_tar(
&dir,
&[
("a.txt", b"alpha"),
("b.txt", b"bravo"),
("c.txt", b"charlie"),
],
);
let dest = dir.join("out");
let out = run_output(&[
"unpack",
archive.to_str().unwrap(),
"-C",
dest.to_str().unwrap(),
"--index",
"1",
]);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(std::fs::read(dest.join("b.txt")).unwrap(), b"bravo");
assert!(!dest.join("a.txt").exists(), "only the selected entry");
assert!(!dest.join("c.txt").exists(), "only the selected entry");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_json_carries_the_archive_index_of_every_entry() {
let dir = tmp_dir();
let archive = write_fixture_tar(&dir, &[("a.txt", b"alpha"), ("b.txt", b"bravo")]);
let out = run_output(&["list", "--json", archive.to_str().unwrap()]);
let text = String::from_utf8(out.stdout).unwrap();
let v: serde_json::Value = serde_json::from_str(&text).unwrap();
let rows = v.as_array().unwrap();
assert_eq!(rows[0]["index"], 0);
assert_eq!(rows[0]["name"], "a.txt");
assert_eq!(rows[1]["index"], 1);
assert_eq!(rows[1]["name"], "b.txt");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_reports_the_same_fidelity_warnings_test_does() {
let dir = tmp_dir();
let zip = write_zip_via_pack(&dir, &[("one.txt", b"first"), ("two.txt", b"second")]);
let bytes = std::fs::read(&zip).unwrap();
let listed = run_with_stdin_output(&["list", "-"], &bytes);
assert!(
listed.status.success(),
"a warning is not a failure without --strict-fidelity"
);
let list_err = String::from_utf8_lossy(&listed.stderr).to_string();
let tested = run_with_stdin_output(&["test", "-"], &bytes);
let test_err = String::from_utf8_lossy(&tested.stderr).to_string();
assert!(
test_err.contains("fidelity warning(s)"),
"the fixture must be a shape that really does lose something: {test_err}"
);
assert!(
list_err.contains("fidelity warning(s)"),
"`list` must report what it lost, not print rows in silence: {list_err}"
);
let warnings_of = |s: &str| -> Vec<String> {
s.lines()
.filter(|l| l.trim_start().starts_with("- "))
.map(|l| l.trim().to_string())
.collect()
};
assert_eq!(
warnings_of(&list_err),
warnings_of(&test_err),
"list:\n{list_err}\ntest:\n{test_err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn list_strict_fidelity_turns_a_warning_into_exit_four() {
let dir = tmp_dir();
let zip = write_zip_via_pack(&dir, &[("one.txt", b"first")]);
let bytes = std::fs::read(&zip).unwrap();
let lenient = run_with_stdin_output(&["list", "-"], &bytes);
assert_eq!(lenient.status.code(), Some(0));
assert!(
String::from_utf8_lossy(&lenient.stdout).contains("one.txt"),
"the listing itself is still printed"
);
let strict = run_with_stdin_output(&["list", "-", "--strict-fidelity"], &bytes);
assert_eq!(
strict.status.code(),
Some(4),
"stderr: {}",
String::from_utf8_lossy(&strict.stderr)
);
assert!(
String::from_utf8_lossy(&strict.stdout).contains("one.txt"),
"the gate is a verdict on a listing that was still produced"
);
let clean = run_output(&["list", zip.to_str().unwrap(), "--strict-fidelity"]);
assert_eq!(
clean.status.code(),
Some(0),
"a seekable zip loses nothing: {}",
String::from_utf8_lossy(&clean.stderr)
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_temp_directory_name_is_not_reproducible_from_the_pid_and_a_counter() {
let dir = tmp_dir();
assert_eq!(
std::fs::read_dir(&dir).unwrap().count(),
0,
"a fresh temp directory must be empty — inheriting one with contents \
in it is the whole failure this guards against"
);
let name = dir.file_name().unwrap().to_string_lossy().into_owned();
let derivable = format!("stuffr-cli-archive-{}-", std::process::id());
let tail = name
.strip_prefix(&derivable)
.unwrap_or_else(|| panic!("unexpected temp directory name: {name}"));
assert!(
tail.contains('-') && !tail.chars().all(|c| c.is_ascii_digit()),
"everything after the pid was the counter alone, so a second run on a \
recycled pid reproduced this name exactly: {name}"
);
let other = tmp("token-check");
let other_name = other.file_name().unwrap().to_string_lossy().into_owned();
assert_ne!(
other_name,
format!("stuffr-cli-{}-token-check", std::process::id()),
"tmp() must carry the run token too"
);
assert!(
other_name.ends_with("-token-check"),
"…and must still end in the caller's own name: {other_name}"
);
let _ = std::fs::remove_dir_all(&dir);
}