#[cfg(unix)]
mod compat_harness;
#[cfg(unix)]
mod unix {
use super::compat_harness::*;
use std::path::Path;
const COMPLETED: &str = "b : Completed in 0.00 sec (cpu load : 100%)\n";
fn zstd() -> Option<&'static str> {
zstd_dir()
}
fn case<'a>(dir: &str, args: Vec<&'a str>) -> Case<'a> {
Case {
name: "zstd",
args,
files: vec![(
"a",
b"the quick brown fox jumps over the lazy dog\n".repeat(40),
)],
stdin: None,
tty_stdin: false,
tty_stdout: false,
env: vec![],
compare_output_bytes: true,
expect_stdout_plain: None,
setup: None,
roundtrip: Roundtrip::Zstd {
ref_zstd: Box::leak(format!("{dir}/zstd").into_boxed_str()),
},
}
}
fn run(code: i32, stderr: &str) -> Run {
Run {
code,
stdout: Vec::new(),
stderr: stderr.to_owned(),
tree: Tree::new(),
start: now_secs(),
}
}
fn verdict(c: &Case, r: &Run, s: &Run) -> Option<String> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| assert_same(c, r, s)))
.err()
.map(panic_text)
}
#[test]
fn reference_is_1_5_7() {
let Some(dir) = zstd() else {
assert!(
std::env::var_os("CI").is_none(),
"zstd_dir returned None under CI instead of panicking"
);
return;
};
let out = |arg: &str| {
let o = std::process::Command::new(format!("{dir}/zstd"))
.arg(arg)
.env_clear()
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).into_owned()
};
assert_eq!(out("-qV"), "1.5.7\n");
let v = out("-vV");
let supports = v.lines().nth(1).unwrap_or_default();
for w in ["gzip", "lz4", "lzma", "xz"] {
assert!(supports.contains(w), "{supports:?} lacks {w}");
}
for link in ["unzstd", "zstdcat", "zstdmt"] {
assert!(Path::new(dir).join(link).exists(), "{dir}/{link}");
}
}
#[test]
fn normalise_hides_only_timing() {
let a = normalise_zstd_progress(COMPLETED);
assert_eq!(a, "b : Completed in <elapsed> sec (cpu load : <cpu>%)\n");
assert_eq!(
a,
normalise_zstd_progress("b : Completed in 12.34 sec (cpu load : 367%)\n")
);
let doc = format!("b : 0.02% (300000 B => 56 B, b.zst)\n{COMPLETED}tail");
assert_eq!(
normalise_zstd_progress(&doc),
format!("b : 0.02% (300000 B => 56 B, b.zst)\n{}tail", a)
);
assert_ne!(a, normalise_zstd_progress(&COMPLETED.replace("b :", "c :")));
assert_ne!(
a,
normalise_zstd_progress(&COMPLETED.replace("sec (", "sec (")),
"the two blanks are part of the shape"
);
let size = "b : 0.02% (300000 B => 56 B, b.zst)\n";
assert_ne!(
normalise_zstd_progress(size),
normalise_zstd_progress(&size.replace("56 B", "57 B"))
);
let msg = "zstd: a: 3 sec (cpu load : 4%)\nCompleted in soon\n\
Completed in 1.5 sec (cpu load : x%)\n";
assert_eq!(normalise_zstd_progress(msg), msg);
}
#[test]
fn assert_same_sees_one_byte_of_stderr() {
let Some(dir) = zstd() else { return };
let c = case(dir, vec!["no-such-file"]);
let r = run(
1,
"zstd: can't stat no-such-file : No such file or directory -- ignored\n",
);
let same = run(
1,
"zstd: can't stat no-such-file : No such file or directory -- ignored\n",
);
assert_eq!(verdict(&c, &r, &same), None);
let s = run(
1,
"zstd: can't stat no-such-file : No such file or directory -- ignored.\n",
);
let msg = verdict(&c, &r, &s).expect("assert_same missed a one-byte stderr difference");
assert!(msg.contains("stderr differs"), "{msg}");
assert!(msg.contains("in 1 way(s)"), "{msg}");
}
#[test]
fn assert_same_sees_the_exit_code() {
let Some(dir) = zstd() else { return };
let c = case(dir, vec!["no-such-file"]);
let r = run(1, "zstd: x\n");
let s = run(2, "zstd: x\n");
let msg = verdict(&c, &r, &s).expect("assert_same missed an exit code difference");
assert!(msg.contains("exit code: reference 1 vs stuffr 2"), "{msg}");
assert!(msg.contains("in 1 way(s)"), "{msg}");
}
fn reference_help(dir: &str) -> String {
let o = std::process::Command::new(format!("{dir}/zstd"))
.arg("-H")
.env_clear()
.output()
.unwrap();
String::from_utf8(o.stdout).unwrap()
}
#[test]
fn strip_bench_help_removes_only_the_bench_block() {
let Some(dir) = zstd() else { return };
let help = reference_help(dir);
assert_eq!(help.matches(ZSTD_BENCH_HELP).count(), 1, "bench block");
assert_eq!(help.matches(ZSTD_TRACE_HELP).count(), 1, "trace line");
let stripped = strip_bench_help(&help);
assert!(!stripped.contains("Benchmark options"), "{stripped}");
assert!(!stripped.contains("--trace"), "{stripped}");
assert!(!stripped.contains("--priority"), "{stripped}");
assert_eq!(
stripped.len(),
help.len() - ZSTD_BENCH_HELP.len() - ZSTD_TRACE_HELP.len()
);
let at_trace = help.find(ZSTD_TRACE_HELP).unwrap();
let at_bench = help.find(ZSTD_BENCH_HELP).unwrap() - ZSTD_TRACE_HELP.len();
let mut back = stripped.clone();
back.insert_str(at_bench, ZSTD_BENCH_HELP);
back.insert_str(at_trace, ZSTD_TRACE_HELP);
assert_eq!(back, help);
assert_eq!(strip_bench_help(&stripped), stripped);
assert_eq!(strip_bench_help("zstd: bad usage\n"), "zstd: bad usage\n");
let altered = help.replace("Default: 3]\n -e#", "Default: 4]\n -e#");
assert_ne!(altered, help);
assert!(strip_bench_help(&altered).contains("Benchmark options"));
}
#[test]
fn zstd_outputs_are_judged_by_decoding() {
let Some(dir) = zstd() else { return };
let mut c = case(dir, vec!["-k", "a"]);
c.compare_output_bytes = false;
let r = reference(dir, &c).unwrap();
let s = reference(dir, &c).unwrap();
assert_eq!(verdict(&c, &r, &s), None);
let mut bad = reference(dir, &c).unwrap();
let (bytes, mode, mtime) = bad.tree.get("a.zst").cloned().unwrap();
let mut broken = bytes.clone();
let n = broken.len();
broken[n - 6] ^= 0xff;
bad.tree.insert("a.zst".into(), (broken, mode, mtime));
let msg = verdict(&c, &r, &bad).expect("a corrupt a.zst was accepted");
assert!(
msg.contains("reference zstd rejects stuffr's a.zst"),
"{msg}"
);
let mut gz = reference(dir, &c).unwrap();
let (_, mode, mtime) = gz.tree.get("a.zst").cloned().unwrap();
let member = case(dir, vec!["-k", "--format=gzip", "a"]);
let g = reference(dir, &member).unwrap();
let (gbytes, _, _) = g.tree.get("a.gz").cloned().unwrap();
gz.tree.insert("a.zst".into(), (gbytes, mode, mtime));
let msg = verdict(&c, &r, &gz).expect("a gzip member passed for a zstd frame");
assert!(
msg.contains("stream format differs: reference zstd, stuffr gzip"),
"{msg}"
);
}
}