use std::{
collections::HashMap,
fs,
io::{self, Read},
path::Path,
};
use crate::Builder;
use crate::test_util::{build_and_open, prepare_test_dir};
use assert2::assert;
use proptest::prelude::{Arbitrary, BoxedStrategy, Strategy};
use tempfile::TempDir;
use test_strategy::proptest;
#[derive(Debug, Clone)]
enum Entry {
File(Vec<u8>),
Dir(HashMap<String, Entry>),
}
impl Arbitrary for Entry {
type Parameters = (u32, u32, u32);
type Strategy = BoxedStrategy<Entry>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(proptest::bits::u8::ANY, 0..10)
.prop_map(Entry::File)
.prop_recursive(
args.0, args.1, args.2 / 2, move |entry| {
proptest::collection::hash_map("[a-z]+", entry, 0..(args.2 as usize))
.prop_map(Entry::Dir)
},
)
.boxed()
}
}
impl Entry {
fn make_files(&self, target: &Path) -> io::Result<()> {
match self {
Entry::File(bytes) => fs::write(target, bytes),
Entry::Dir(entries) => {
fs::create_dir(target)?;
for (name, entry) in entries {
let mut p = target.to_owned();
p.push(name);
entry.make_files(&p)?;
}
Ok(())
}
}
}
fn make_expected_content(&self, mut name: String, out: &mut HashMap<String, Vec<u8>>) {
match self {
Entry::File(bytes) => assert!(out.insert(name, bytes.clone()).is_none()),
Entry::Dir(entries) => {
name += "/";
assert!(out.insert(name.clone(), Vec::new()).is_none());
for (entry_name, entry) in entries {
let entry_name = name.clone() + entry_name;
entry.make_expected_content(entry_name, out);
}
}
}
}
}
#[proptest(async = "tokio")]
async fn any_archive_filesystem(#[any((8, 64, 16))] data: Entry) {
let tempdir = TempDir::new().unwrap();
let entry_path = {
let mut p = tempdir.path().to_owned();
p.push("x");
p
};
let mut expected_content = HashMap::new();
data.make_expected_content("x".to_owned(), &mut expected_content);
data.make_files(&entry_path).unwrap();
let mut builder = Builder::new();
builder
.add_directory_recursive(tempdir.path().to_owned(), None)
.await
.unwrap();
let mut unpacked = build_and_open(builder).await;
let mut unpacked_content = HashMap::new();
for i in 0..unpacked.len() {
let mut zipfile = unpacked.by_index(i).unwrap();
let name = std::str::from_utf8(zipfile.name_raw()).unwrap().to_string();
let mut file_content = Vec::new();
zipfile.read_to_end(&mut file_content).unwrap();
unpacked_content.insert(name, file_content);
}
dbg!(&unpacked_content);
dbg!(&expected_content);
assert!(unpacked_content == expected_content);
}
#[cfg(unix)]
#[tokio::test]
async fn filesystem_file_dir_symlink() {
let tempdir = prepare_test_dir();
let mut builder = Builder::new();
builder
.add_directory_recursive(tempdir.path().to_owned(), None)
.await
.unwrap();
let mut unpacked = build_and_open(builder).await;
let unpacked_dir = unpacked
.by_name("dir/")
.expect("Directory must have an entry");
assert!(unpacked_dir.size() == 0);
assert!(unpacked_dir.unix_mode().expect("Must have unix mode") == 0o40_755);
drop(unpacked_dir);
let mut unpacked_file = unpacked
.by_name("dir/file")
.expect("file must have an entry");
let mut s = String::new();
unpacked_file.read_to_string(&mut s).unwrap();
assert!(s == "Hello world");
assert!(unpacked_file.unix_mode().expect("Must have unix mode") == 0o100_644);
drop(unpacked_file);
let mut unpacked_link1 = unpacked.by_name("link1").expect("link must have an entry");
let mut s = String::new();
unpacked_link1.read_to_string(&mut s).unwrap();
assert!(s == "dir/file");
assert!(unpacked_link1.unix_mode().expect("Must have unix mode") == 0o120_777);
drop(unpacked_link1);
let mut unpacked_link2 = unpacked.by_name("link2").expect("link must have an entry");
let mut s = String::new();
unpacked_link2.read_to_string(&mut s).unwrap();
assert!(s == "/foo/bar");
assert!(unpacked_link2.unix_mode().expect("Must have unix mode") == 0o120_777);
drop(unpacked_link2);
}