use crate::common::*;
use std::process::Command;
#[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}"
);
}