use std::ffi::OsString;
use std::io::Cursor;
use super::*;
fn xxh32(data: &[u8]) -> u32 {
const P: [u32; 5] = [
2_654_435_761,
2_246_822_519,
3_266_489_917,
668_265_263,
374_761_393,
];
let word = |i: usize| u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
let round = |acc: u32, input: u32| {
acc.wrapping_add(input.wrapping_mul(P[1]))
.rotate_left(13)
.wrapping_mul(P[0])
};
let mut i = 0;
let mut h;
if data.len() >= 16 {
let mut v = [P[0].wrapping_add(P[1]), P[1], 0, 0u32.wrapping_sub(P[0])];
while i + 16 <= data.len() {
for (k, lane) in v.iter_mut().enumerate() {
*lane = round(*lane, word(i + 4 * k));
}
i += 16;
}
h = v[0]
.rotate_left(1)
.wrapping_add(v[1].rotate_left(7))
.wrapping_add(v[2].rotate_left(12))
.wrapping_add(v[3].rotate_left(18));
} else {
h = P[4];
}
h = h.wrapping_add(data.len() as u32);
while i + 4 <= data.len() {
h = h
.wrapping_add(word(i).wrapping_mul(P[2]))
.rotate_left(17)
.wrapping_mul(P[3]);
i += 4;
}
while i < data.len() {
h = h
.wrapping_add(u32::from(data[i]).wrapping_mul(P[4]))
.rotate_left(11)
.wrapping_mul(P[0]);
i += 1;
}
h ^= h >> 15;
h = h.wrapping_mul(P[1]);
h ^= h >> 13;
h = h.wrapping_mul(P[2]);
h ^ (h >> 16)
}
#[derive(Clone)]
struct Shape {
id: u8,
linked: bool,
block_checksum: bool,
content_checksum: bool,
content_size: Option<u64>,
dictionary: Option<u32>,
blocks: Vec<u32>,
end: bool,
}
impl Shape {
fn new() -> Shape {
Shape {
id: 7,
linked: false,
block_checksum: false,
content_checksum: true,
content_size: None,
dictionary: None,
blocks: vec![3],
end: true,
}
}
fn header(&self) -> Vec<u8> {
let flg = 0x40
| (u8::from(!self.linked) << 5)
| (u8::from(self.block_checksum) << 4)
| (u8::from(self.content_size.is_some()) << 3)
| (u8::from(self.content_checksum) << 2)
| u8::from(self.dictionary.is_some());
let mut d = vec![flg, self.id << 4];
if let Some(n) = self.content_size {
d.extend_from_slice(&n.to_le_bytes());
}
if let Some(n) = self.dictionary {
d.extend_from_slice(&n.to_le_bytes());
}
let hc = (xxh32(&d) >> 8) as u8;
let mut out = MAGIC.to_le_bytes().to_vec();
out.extend_from_slice(&d);
out.push(hc);
out
}
fn bytes(&self) -> Vec<u8> {
let mut out = self.header();
for &n in &self.blocks {
out.extend_from_slice(&(n | 0x8000_0000).to_le_bytes());
out.resize(out.len() + n as usize, b'x');
if self.block_checksum {
out.extend_from_slice(&[0; 4]);
}
}
if self.end {
out.extend_from_slice(&[0; 4]);
if self.content_checksum {
out.extend_from_slice(&[0; 4]);
}
}
out
}
}
fn skippable(n: u32) -> Vec<u8> {
let mut out = 0x184D_2A50u32.to_le_bytes().to_vec();
out.extend_from_slice(&n.to_le_bytes());
out.resize(out.len() + n as usize, b's');
out
}
fn legacy(blocks: &[u32]) -> Vec<u8> {
let mut out = LEGACY_MAGIC.to_le_bytes().to_vec();
for &n in blocks {
out.extend_from_slice(&n.to_le_bytes());
out.resize(out.len() + n as usize, b'l');
}
out
}
fn walk(file: &[u8]) -> (Vec<FrameInfo>, Option<Stop>) {
let mut w = Walker::new(Cursor::new(file.to_vec()));
let mut frames = Vec::new();
loop {
match w.next_frame() {
Ok(Some(f)) => frames.push(f),
Ok(None) => return (frames, None),
Err(s) => return (frames, Some(s)),
}
}
}
fn stop_of(file: &[u8]) -> Option<Stop> {
walk(file).1
}
fn sizes(file: &[u8]) -> Vec<u64> {
walk(file).0.iter().map(|f| f.size).collect()
}
#[test]
fn a_plain_frame() {
let f = Shape::new().bytes();
let (frames, stop) = walk(&f);
assert_eq!(stop, None);
assert_eq!(
frames,
[FrameInfo {
kind: Kind::Frame,
size: f.len() as u64,
block: Some((7, false)),
checksum: true,
content: 0,
}]
);
}
#[test]
fn header_fields_reach_the_row() {
let mut s = Shape::new();
s.content_size = Some(100);
s.id = 4;
s.linked = true;
s.content_checksum = false;
let (frames, _) = walk(&s.bytes());
assert_eq!(frames[0].block, Some((4, true)));
assert_eq!((frames[0].content, frames[0].checksum), (100, false));
s.content_size = Some(0);
assert_eq!(walk(&s.bytes()).0[0].content, 0);
}
#[test]
fn blocks_and_checksums_add_up() {
let mut s = Shape::new();
s.blocks = vec![10, 5];
s.block_checksum = true;
assert_eq!(sizes(&s.bytes()), [7 + 16 + 15 + 8]);
s.content_checksum = false;
assert_eq!(sizes(&s.bytes()), [7 + 16 + 15 + 4]);
let mut s = Shape::new();
s.blocks = vec![0, 3];
let (frames, stop) = walk(&s.bytes());
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].size, 7 + 4 + 4);
assert_eq!(stop, Some(Stop::Trailing(19)));
}
#[test]
fn a_dictionary_id_needs_a_content_size() {
let mut s = Shape::new();
s.dictionary = Some(0x1234_5678);
s.content_size = Some(7);
let (frames, stop) = walk(&s.bytes());
assert_eq!((stop, frames[0].content), (None, 7));
s.content_size = None;
assert_eq!(stop_of(&s.bytes()), Some(Stop::NotRecognized));
}
#[test]
fn headers_liblz4_refuses() {
let good = Shape::new().bytes();
let refuse = |at: usize, v: u8| {
let mut f = good.clone();
f[at] = v;
let hc = (xxh32(&f[4..6]) >> 8) as u8;
f[6] = hc;
stop_of(&f)
};
assert_eq!(refuse(4, 0x40 | 0x02), Some(Stop::NotRecognized));
assert_eq!(refuse(4, 0x20), Some(Stop::NotRecognized));
assert_eq!(refuse(4, 0x80 | 0x20), Some(Stop::NotRecognized));
assert_eq!(refuse(5, 0x70 | 1), Some(Stop::NotRecognized));
assert_eq!(refuse(5, 0x80 | 0x70), Some(Stop::NotRecognized));
assert_eq!(refuse(5, 0x30), Some(Stop::NotRecognized));
assert_eq!(refuse(5, 0x00), Some(Stop::NotRecognized));
let mut bad = good.clone();
bad[6] ^= 1;
assert_eq!(stop_of(&bad), Some(Stop::NotRecognized));
}
#[test]
fn every_prefix_of_a_header_with_a_content_size() {
let mut s = Shape::new();
s.content_size = Some(14);
let f = s.bytes();
for n in 0..=f.len() {
let want = match n {
0 => Some(Stop::NotRecognized),
1..=3 => Some(Stop::MagicUnreadable),
4 => Some(Stop::MagicOnly),
5..=7 => Some(Stop::HeaderCut),
8..=15 => Some(Stop::NotRecognized),
_ => None,
};
assert_eq!(stop_of(&f[..n]), want, "{n} bytes");
}
}
#[test]
fn every_prefix_of_a_plain_header() {
let f = Shape::new().bytes();
for n in 0..=f.len() {
let want = match n {
0 => Some(Stop::NotRecognized),
1..=3 => Some(Stop::MagicUnreadable),
4 => Some(Stop::MagicOnly),
5..=7 => Some(Stop::NotRecognized),
_ => None,
};
assert_eq!(stop_of(&f[..n]), want, "{n} bytes");
}
let mut two = f.clone();
two.extend_from_slice(&f[..6]);
assert_eq!(stop_of(&two), Some(Stop::NotRecognized));
}
#[test]
fn a_frame_cut_short_counts_what_it_promised() {
let f = Shape::new().bytes();
assert_eq!(f.len(), 22);
assert_eq!(sizes(&f[..14]), [22 - 4 - 4]);
assert_eq!(sizes(&f[..12]), [22 - 4 - 4]);
assert_eq!(sizes(&f[..20]), [22]);
let mut s = Shape::new();
s.end = false;
assert_eq!(sizes(&s.bytes()), [7 + 4 + 3]);
}
#[test]
fn a_word_cut_short_is_completed_from_the_last() {
let mut s = Shape::new();
s.blocks = vec![0x0001_0203];
s.end = false;
let head = s.bytes();
let at = 7 + 4 + 0x0001_0203;
assert_eq!(head.len(), at);
for (tail, size) in [
(&[7u8][..], 132_121),
(&[7, 0][..], 131_609),
(&[7, 0, 0][..], 66_073),
(&[0x34, 0x12][..], 136_262),
] {
let mut f = head.clone();
f.extend_from_slice(tail);
assert_eq!(sizes(&f), [size], "{tail:?}");
}
let mut s = Shape::new();
s.blocks = vec![];
s.end = false;
let mut f = s.bytes();
f.push(9);
assert_eq!(sizes(&f), [7 + 4 + 9]);
}
#[test]
fn a_header_alone_is_not_a_frame() {
let mut s = Shape::new();
s.blocks = vec![];
s.end = false;
let mut f = s.bytes();
assert_eq!(stop_of(&f), Some(Stop::NotRecognized));
f.push(b'z');
assert_eq!(sizes(&f), [7 + 4 + 0x7A]);
let mut two = Shape::new().bytes();
two.extend_from_slice(&s.bytes());
let (frames, stop) = walk(&two);
assert_eq!((frames.len(), stop), (1, Some(Stop::NotRecognized)));
}
#[test]
fn hostile_block_sizes_cost_one_seek() {
let mut s = Shape::new();
s.id = 4;
s.blocks = vec![];
s.end = false;
let mut f = s.bytes();
f.extend_from_slice(&0x7FFF_FFFFu32.to_le_bytes());
f.extend_from_slice(b"ab");
assert_eq!(sizes(&f), [7 + 4 + 0x7FFF_FFFF]);
let mut g = s.bytes();
g.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
assert_eq!(sizes(&g), [7 + 4 + 0x7FFF_FFFF]);
}
#[test]
fn sizes_are_sixty_four_bits() {
let mut s = Shape::new();
s.blocks = vec![];
s.end = false;
let mut f = s.bytes();
let word = 0x7FFF_FFFFu32.to_le_bytes();
f.extend_from_slice(&word);
assert_eq!(sizes(&f), [7 + 4 + 0x7FFF_FFFF]);
}
#[test]
fn skippable_frames() {
assert_eq!(sizes(&skippable(10)), [18]);
assert_eq!(sizes(&skippable(0)), [8]);
let mut f = skippable(3);
f[..4].copy_from_slice(&0x184D_2A5Fu32.to_le_bytes());
assert_eq!(walk(&f).0[0].kind, Kind::Skippable);
let cut = skippable(3);
assert_eq!(stop_of(&cut[..4]), Some(Stop::SkipSize));
assert_eq!(stop_of(&cut[..6]), Some(Stop::SkipSize));
let mut t = skippable(3);
t.extend_from_slice(&[1, 1]);
assert_eq!(stop_of(&t), Some(Stop::MagicUnreadable));
assert_eq!(sizes(&cut[..8]), [11]);
assert_eq!(sizes(&skippable(10)[..12]), [18]);
}
#[test]
fn a_huge_skippable_frame_wraps_its_size() {
let mut f = 0x184D_2A50u32.to_le_bytes().to_vec();
f.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
f.extend_from_slice(b"ab");
f.extend_from_slice(&Shape::new().bytes());
let (frames, stop) = walk(&f);
assert_eq!(
(stop, frames.iter().map(|f| f.size).collect::<Vec<_>>()),
(None, vec![7])
);
f[4..8].copy_from_slice(&0xFFFF_FFF0u32.to_le_bytes());
assert_eq!(walk(&f).0[0].size, 4_294_967_288);
}
#[test]
fn legacy_frames() {
assert_eq!(sizes(&legacy(&[10])), [18]);
assert_eq!(sizes(&legacy(&[10, 20])), [42]);
assert_eq!(sizes(&legacy(&[0, 0, 0])), [16]);
let mut two = legacy(&[3]);
two.extend_from_slice(&legacy(&[4]));
assert_eq!(sizes(&two), [11, 12]);
let mut then = legacy(&[3]);
then.extend_from_slice(&Shape::new().bytes());
let (frames, _) = walk(&then);
assert_eq!(
frames.iter().map(|f| f.kind).collect::<Vec<_>>(),
[Kind::Legacy, Kind::Frame]
);
let mut then = legacy(&[3]);
then.extend_from_slice(&skippable(2));
assert_eq!(walk(&then).0[1].kind, Kind::Skippable);
assert_eq!(sizes(&legacy(&[5])[..11]), [13]);
let magic = LEGACY_MAGIC.to_le_bytes();
assert_eq!(stop_of(&magic), Some(Stop::NotRecognized));
let mut f = magic.to_vec();
f.extend_from_slice(&magic);
assert_eq!(stop_of(&f), Some(Stop::NotRecognized));
let mut f = magic.to_vec();
f.extend_from_slice(&Shape::new().bytes());
assert_eq!(stop_of(&f), Some(Stop::NotRecognized));
for n in 1..=3 {
let mut f = legacy(&[3]);
f.resize(f.len() + n, 1);
assert_eq!(stop_of(&f), Some(Stop::CorruptLegacy), "{n}");
let mut g = magic.to_vec();
g.resize(4 + n, 1);
assert_eq!(stop_of(&g), Some(Stop::CorruptLegacy), "{n}");
}
let mut f = magic.to_vec();
f.extend_from_slice(&(8u32 << 20).to_le_bytes());
assert_eq!(sizes(&f), [8 + (8 << 20)]);
let mut f = magic.to_vec();
f.extend_from_slice(&((8u32 << 20) + 1).to_le_bytes());
assert_eq!(stop_of(&f), Some(Stop::CorruptLegacy));
let mut f = legacy(&[3]);
f.extend_from_slice(&u32::MAX.to_le_bytes());
assert_eq!(stop_of(&f), Some(Stop::CorruptLegacy));
}
#[test]
fn unknown_magic_numbers() {
assert_eq!(stop_of(b"notlz4 at all"), Some(Stop::Trailing(4)));
let mut f = Shape::new().bytes();
f.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(stop_of(&f), Some(Stop::Trailing(22 + 4)));
let mut f = skippable(3);
f.extend_from_slice(&[0; 4]);
assert_eq!(stop_of(&f), Some(Stop::Trailing(15)));
}
#[test]
fn a_long_run_of_blocks_is_one_pass() {
let mut f = LEGACY_MAGIC.to_le_bytes().to_vec();
f.resize(4 + 4 * 300_000, 0);
assert_eq!(sizes(&f), [4 + 4 * 300_000]);
}
#[test]
fn sizes_in_the_compact_table() {
for (n, want) in [
(0, "0.00"),
(28, "28.00"),
(1023, "1023.00"),
(1024, "1.00K"),
(1025, "1.00K"),
(1536, "1.50K"),
((1 << 20) - 1, "1024.00K"),
(1 << 20, "1.00M"),
((1 << 20) + 5000, "1.00M"),
((1 << 30) - 1, "1024.00M"),
(1 << 30, "1.00G"),
(40_000_000, "38.15M"),
(1 << 40, "1.00T"),
(1 << 50, "1.00P"),
(1 << 60, "1.00E"),
(5 << 60, "5.00E"),
(u64::MAX, "16.00E"),
] {
assert_eq!(size_cell(n), want, "{n}");
}
}
#[test]
fn ratios() {
assert_eq!(ratio_cell(11_764, 11_737), "100.23");
assert_eq!(ratio_cell(28, 1023), "2.74");
assert_eq!(ratio_cell(56, 0), "inf");
assert_eq!(ratio_cell(28, u64::MAX), "0.00");
}
#[test]
fn base_names() {
assert_eq!(base_name(b"a.lz4"), b"a.lz4");
assert_eq!(base_name(b"d/e/a.lz4"), b"a.lz4");
assert_eq!(base_name(b"./a.lz4"), b"a.lz4");
assert_eq!(base_name(b"d//a.lz4"), b"a.lz4");
assert_eq!(base_name(b"d/"), b"");
}
#[test]
fn totals_collect_like_lz4() {
let modern = |id, linked, content| FrameInfo {
kind: Kind::Frame,
size: 30,
block: Some((id, linked)),
checksum: true,
content,
};
let mut t = Totals::new();
t.add(&modern(7, false, 3));
t.add(&modern(7, false, 3));
assert!(t.kind_same && t.block_same && t.known);
assert_eq!((t.count, t.content), (2, 6));
t.add(&modern(4, false, 3));
assert!(t.kind_same && !t.block_same && t.known);
t.add(&modern(7, false, 0));
assert!(!t.known);
let mut t = Totals::new();
t.add(&modern(7, false, 1 << 63));
t.add(&modern(7, false, 1 << 63));
assert!(t.known);
assert_eq!(t.content, 0);
let mut t = Totals::new();
t.add(&modern(7, false, 3));
t.add(&FrameInfo {
kind: Kind::Skippable,
size: 8,
block: None,
checksum: false,
content: 0,
});
assert!(!t.kind_same && !t.known);
}
struct Dir(std::path::PathBuf);
impl Dir {
fn new(files: &[(&str, &[u8])]) -> Dir {
static N: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let dir = std::env::temp_dir().join(format!("stuffr-lz4-list-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
for (name, data) in files {
std::fs::write(dir.join(name), data).unwrap();
}
Dir(dir)
}
fn run(&self, args: &[&str]) -> (i32, String, String) {
let args: Vec<OsString> = args.iter().map(OsString::from).collect();
let (code, out, err, _) =
super::super::run_in_counted(&self.0, "lz4", &args, b"", &|_| None);
(
code,
String::from_utf8(out).unwrap(),
String::from_utf8(err).unwrap(),
)
}
}
impl Drop for Dir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
const HEADER: &str = m::LIST_HEADER;
fn cells(spec: &[(&str, usize)]) -> String {
spec.iter()
.map(|&(s, w)| format!("{s:>w$}"))
.collect::<Vec<_>>()
.join(" ")
}
fn sp(n: usize) -> String {
" ".repeat(n)
}
fn compact(t: (&str, &str, &str, &str, &str, &str), known: bool, name: &str) -> String {
let c = cells(&[
(t.0, 10),
(t.1, 14),
(t.2, 5),
(t.3, 11),
(t.4, 13),
(t.5, 8),
]);
if known {
format!("{c}%{}{name} \n", sp(2))
} else {
format!("{c}{}{name}\n", sp(3))
}
}
fn verbose(t: (&str, &str, &str, &str, &str, &str, &str), end: &str) -> String {
let c = cells(&[
(t.0, 10),
(t.1, 14),
(t.2, 5),
(t.3, 8),
(t.4, 20),
(t.5, 20),
(t.6, 9),
]);
format!("{c}{end}\n")
}
const VERBOSE_HEADER: &str = m::LIST_HEADER_VERBOSE;
#[test]
fn the_compact_table() {
let plain = Shape::new().bytes();
let mut s = Shape::new();
s.content_size = Some(100);
let sized = s.bytes();
let d = Dir::new(&[
("plain.lz4", &plain),
("sized.lz4", &sized),
("leg.lz4", &legacy(&[10])),
("skip.lz4", &skippable(10)),
]);
let (code, out, err) = d.run(&["--list", "plain.lz4", "sized.lz4", "leg.lz4", "skip.lz4"]);
assert_eq!((code, err.as_str()), (0, ""));
let want = format!(
"{HEADER}{}{}{}{}",
compact(
("1", "LZ4Frame", "B7I", "22.00", "-", "-"),
false,
"plain.lz4"
),
compact(
("1", "LZ4Frame", "B7I", "30.00", "100.00", "30.00"),
true,
"sized.lz4"
),
compact(
("1", "LegacyFrame", "-", "18.00", "-", "-"),
false,
"leg.lz4"
),
compact(
("1", "SkippableFrame", "-", "18.00", "-", "-"),
false,
"skip.lz4"
),
);
assert_eq!(out, want);
}
#[test]
fn the_verbose_table() {
let plain = Shape::new().bytes();
let mut s = Shape::new();
s.content_size = Some(100);
s.block_checksum = true;
s.linked = true;
s.content_checksum = false;
let mut two = plain.clone();
two.extend_from_slice(&skippable(3));
two.extend_from_slice(&legacy(&[1]));
two.extend_from_slice(&s.bytes());
let d = Dir::new(&[("two.lz4", &two)]);
std::fs::create_dir(d.0.join("sub")).unwrap();
std::fs::write(d.0.join("sub/two.lz4"), &plain).unwrap();
let (code, out, err) = d.run(&["--list", "-v", "two.lz4", "sub/two.lz4"]);
assert_eq!(code, 0);
assert!(
err.starts_with("*** lz4 v1.10.0 64-bit multithread (stuffr "),
"{err:?}"
);
assert_eq!(err.lines().count(), 1, "{err:?}");
let col = VERBOSE_HEADER;
let want = format!(
"two.lz4(1/2)\n{col}{}{}{}{}\ntwo.lz4(2/2)\n{col}{}\n",
verbose(("1", "LZ4Frame", "B7I", "XXH32", "22", "-", "-"), " "),
verbose(("2", "SkippableFrame", "-", "-", "11", "-", "-"), ""),
verbose(("3", "LegacyFrame", "-", "-", "9", "-", "-"), ""),
verbose(("4", "LZ4Frame", "B7D", "-", "30", "100", "30.00"), "%"),
verbose(("1", "LZ4Frame", "B7I", "XXH32", "22", "-", "-"), " "),
);
assert_eq!(out, want);
}
#[test]
fn the_levels() {
let d = Dir::new(&[("a.lz4", &Shape::new().bytes())]);
let (_, out, err) = d.run(&["--list", "-vv", "a.lz4"]);
assert!(out.starts_with("a.lz4(1/1)\n"), "{out:?}");
let mut lines: Vec<_> = err.lines().collect();
if cfg!(target_os = "linux") {
assert_eq!(lines.get(1), Some(&"_POSIX_C_SOURCE defined: 200809L"));
lines.remove(1);
}
assert_eq!(lines.len(), 3, "{err:?}");
assert!(lines[1].starts_with("_POSIX_VERSION defined: "), "{err:?}");
assert!(lines[1].ends_with('L'));
assert_eq!(
lines[2],
lines[1].replacen("_POSIX_VERSION", "PLATFORM_POSIX_VERSION", 1)
);
let (code, out, err) = d.run(&["--list", "-qq", "nosuch"]);
assert_eq!((code, out.as_str(), err.as_str()), (1, HEADER, ""));
let (code, _, err) = d.run(&["--list", "-q", "nosuch"]);
assert_eq!(
(code, err.as_str()),
(1, "lz4: nosuch is not a regular file \n")
);
}
#[test]
fn failures_end_the_run() {
let f = Shape::new().bytes();
let mut sized = Shape::new();
sized.content_size = Some(14);
let mut cut = sized.bytes();
cut.truncate(6);
let d = Dir::new(&[("a.lz4", &f), ("notlz4", b"hello hello"), ("cut.lz4", &cut)]);
let (code, out, err) = d.run(&["--list"]);
assert_eq!((code, out.as_str(), err.as_str()), (0, HEADER, ""));
let (code, out, _) = d.run(&["--list", "-"]);
assert_eq!((code, out.as_str()), (0, HEADER));
let (code, out, err) = d.run(&["--list", "a.lz4", "-", "a.lz4"]);
assert_eq!((code, err.as_str()), (0, ""));
assert_eq!(out.matches("a.lz4").count(), 2);
let (code, _, err) = d.run(&["--list", "stdin"]);
assert_eq!(
(code, err.as_str()),
(1, "lz4: stdin is not a regular file \n")
);
let (code, out, err) = d.run(&["--list", "a.lz4", "notlz4", "a.lz4"]);
assert_eq!(code, 1);
assert_eq!(out.matches("a.lz4").count(), 1, "{out}");
assert_eq!(err, "lz4: notlz4: File format not recognized \n");
let (code, out, err) = d.run(&["--list", "cut.lz4", "a.lz4"]);
assert_eq!((code, out.as_str()), (72, HEADER));
assert_eq!(err, "Error 72 : Error reading cut.lz4 \n");
}
#[test]
fn failure_messages_by_level() {
let mut garb = Shape::new().bytes();
garb.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
let mut leg = legacy(&[3]);
leg.extend_from_slice(&u32::MAX.to_le_bytes());
let d = Dir::new(&[("g.lz4", &garb), ("l.lz4", &leg)]);
let nr = |n: &str| format!("lz4: {n}: File format not recognized \n");
let (code, _, err) = d.run(&["--list", "g.lz4"]);
assert_eq!((code, err), (1, nr("g.lz4")));
let (_, _, err) = d.run(&["--list", "-v", "g.lz4"]);
assert!(
err.ends_with(&format!(
"Stream followed by undecodable data at position 26 \n{}",
nr("g.lz4")
)),
"{err:?}"
);
let (_, out, err) = d.run(&["--list", "-q", "l.lz4"]);
assert_eq!(out, HEADER);
assert_eq!(err, format!("Corrupted legacy frame \n{}", nr("l.lz4")));
let (_, out, _) = d.run(&["--list", "-v", "g.lz4"]);
assert!(out.ends_with("- \n") && !out.ends_with("\n\n"), "{out:?}");
}
#[test]
fn directories() {
let d = Dir::new(&[]);
std::fs::create_dir_all(d.0.join("e")).unwrap();
std::fs::create_dir_all(d.0.join("f")).unwrap();
std::fs::write(d.0.join("f/a.lz4"), Shape::new().bytes()).unwrap();
let (code, out, _) = d.run(&["--list", "-r", "f", "e"]);
assert_eq!(code, 0);
assert!(out.ends_with(&format!("{}a.lz4\n", sp(3))), "{out:?}");
let (code, _, err) = d.run(&["--list", "-r", "e"]);
assert_eq!((code, err.as_str()), (1, "lz4: e is not a regular file \n"));
let (code, _, err) = d.run(&["--list", "e"]);
assert_eq!((code, err.as_str()), (1, "lz4: e is not a regular file \n"));
}