use std::{
fs,
io::Cursor,
process::Command,
time::{SystemTime, UNIX_EPOCH},
};
use zarust::{ArchiveReader, ArchiveWriter, EntryKind, ROOT_NODE};
fn sample_archive() -> Vec<u8> {
let mut writer = ArchiveWriter::new(Cursor::new(Vec::new()));
writer.make_dir("Games", false).unwrap();
writer.make_dir("Games/Empty", false).unwrap();
writer
.add_file("Games/readme.txt", Cursor::new(b"hello ZArchive"))
.unwrap();
let large = (0..150_000).map(|value| value as u8).collect::<Vec<_>>();
writer
.add_file("Games/data.bin", Cursor::new(&large))
.unwrap();
writer.finish().unwrap().into_inner()
}
#[test]
fn round_trips_directories_files_and_random_reads() {
let archive = sample_archive();
let mut reader = ArchiveReader::new(Cursor::new(archive)).unwrap();
assert_eq!(reader.directory_len(ROOT_NODE).unwrap(), 1);
let games = reader
.lookup_kind("games", Some(EntryKind::Directory))
.unwrap();
let readme = reader
.lookup_kind("GAMES\\README.TXT", Some(EntryKind::File))
.unwrap();
assert_eq!(reader.read_file_to_end(readme).unwrap(), b"hello ZArchive");
let data = reader.lookup("Games/data.bin").unwrap();
let mut slice = vec![0; 80_000];
assert_eq!(reader.read_file(data, 60_000, &mut slice).unwrap(), 80_000);
let expected = (60_000..140_000)
.map(|value| value as u8)
.collect::<Vec<_>>();
assert_eq!(slice, expected);
assert_eq!(reader.directory_len(games).unwrap(), 3);
assert!(reader.verify_integrity().unwrap());
}
#[test]
fn supports_extended_name_headers() {
let name = "n".repeat(200);
let mut writer = ArchiveWriter::new(Cursor::new(Vec::new()));
writer.make_dir(name.as_bytes(), false).unwrap();
let archive = writer.finish().unwrap().into_inner();
let reader = ArchiveReader::new(Cursor::new(archive)).unwrap();
let entry = reader.directory_entry(ROOT_NODE, 0).unwrap();
assert_eq!(entry.name, name.as_bytes());
}
#[test]
fn empty_archives_remain_readable() {
let archive = ArchiveWriter::new(Cursor::new(Vec::new()))
.finish()
.unwrap()
.into_inner();
let mut reader = ArchiveReader::new(Cursor::new(archive)).unwrap();
assert_eq!(reader.directory_len(ROOT_NODE).unwrap(), 0);
assert!(reader.verify_integrity().unwrap());
}
#[test]
fn integrity_check_detects_modified_data() {
let mut archive = sample_archive();
archive[0] ^= 0x80;
let mut reader = ArchiveReader::new(Cursor::new(archive)).unwrap();
assert!(!reader.verify_integrity().unwrap());
}
#[test]
fn rejects_cyclic_file_trees() {
let mut archive = sample_archive();
let footer = archive.len() - 144;
let tree_offset = u64::from_be_bytes(archive[footer + 48..footer + 56].try_into().unwrap());
let root = tree_offset as usize;
archive[root + 4..root + 8].copy_from_slice(&0_u32.to_be_bytes());
archive[root + 8..root + 12].copy_from_slice(&1_u32.to_be_bytes());
assert!(ArchiveReader::new(Cursor::new(archive)).is_err());
}
#[test]
fn rejects_case_insensitive_duplicates() {
let mut writer = ArchiveWriter::new(Cursor::new(Vec::new()));
writer.make_dir("Folder", false).unwrap();
assert!(writer.make_dir("folder", false).is_err());
}
#[test]
fn cli_packs_and_extracts_with_the_original_command_shape() {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!("zarust-cli-{}-{unique}", std::process::id()));
let input = root.join("input");
let nested = input.join("nested");
let archive = root.join("output.zar");
let extracted = root.join("extracted");
fs::create_dir_all(&nested).unwrap();
fs::write(input.join("hello.txt"), b"hello from the CLI").unwrap();
fs::write(nested.join("data.bin"), (0..=255).collect::<Vec<_>>()).unwrap();
let command = env!("CARGO_BIN_EXE_zarust");
let pack = Command::new(command)
.arg(&input)
.arg(&archive)
.output()
.unwrap();
assert!(
pack.status.success(),
"{}",
String::from_utf8_lossy(&pack.stderr)
);
let progress = String::from_utf8_lossy(&pack.stderr);
assert!(progress.contains("100.0%"));
assert!(progress.contains("processed"));
assert!(progress.contains("speed"));
assert!(progress.contains("elapsed"));
assert!(progress.contains("ETA"));
let extract = Command::new(command)
.arg(&archive)
.arg(&extracted)
.output()
.unwrap();
assert!(
extract.status.success(),
"{}",
String::from_utf8_lossy(&extract.stderr)
);
assert_eq!(
fs::read(extracted.join("hello.txt")).unwrap(),
b"hello from the CLI"
);
assert_eq!(
fs::read(extracted.join("nested/data.bin")).unwrap(),
(0..=255).collect::<Vec<_>>()
);
fs::remove_dir_all(root).unwrap();
}