use super::*;
use alloc::{collections::BTreeSet, string::String, vec};
use codec::DecodeAll;
use rand::{distr::Alphanumeric, Rng};
use std::{
io::Cursor,
path::{Path, PathBuf},
};
use tempfile::TempDir;
impl BlockRef {
fn zero() -> Self {
Self { service_id: 0, hash: Hash::default() }
}
}
#[test]
fn file_name_works() {
assert!(FileName::new(c"hello".into()).is_ok());
assert!(FileName::new(c"a/b".into()).is_err());
assert!(FileName::new(c"".into()).is_err());
assert!(FileName::new({
let mut name = vec![b'a'; MAX_FILE_NAME_LEN - 1];
name.push(0);
CString::from_vec_with_nul(name).unwrap()
})
.is_ok());
assert!(FileName::new({
let mut name = vec![b'a'; MAX_FILE_NAME_LEN];
name.push(0);
CString::from_vec_with_nul(name).unwrap()
})
.is_err());
}
#[test]
fn file_name_encode_decode_work() {
for name in [
CString::from(c"hello"),
c"a".into(),
c"aa".into(),
c"aaa".into(),
CString::from_vec_with_nul({
let mut name = vec![b'a'; MAX_FILE_NAME_LEN - 1];
name.push(0);
name
})
.unwrap(),
] {
let file_name = FileName::new(name).unwrap();
let bytes = file_name.encode();
let actual = FileName::decode_all(&mut &bytes[..]).unwrap();
assert_eq!(file_name, actual);
}
}
#[test]
fn main_block_encode_decode_work() {
for expected in [
MainBlock {
kind: NodeKind::File,
file_size: 0,
block_size: MAX_BLOCK_SIZE as u64,
block_refs: Default::default(),
first_block: FileBlock::new(Default::default()).unwrap(),
},
MainBlock {
kind: NodeKind::File,
file_size: 1,
block_size: MAX_BLOCK_SIZE as u64,
block_refs: Default::default(),
first_block: FileBlock::new(vec![b'x'; 1].into()).unwrap(),
},
MainBlock {
kind: NodeKind::File,
file_size: MAX_BLOCK_SIZE as u64,
block_size: MAX_BLOCK_SIZE as u64,
block_refs: vec![BlockRef::zero(); 1],
first_block: FileBlock::new(vec![b'x'; MIN_BLOCK_SIZE].into()).unwrap(),
},
] {
let mut bytes = Vec::new();
expected.encode_to(&mut bytes);
let actual = MainBlock::decode(bytes.into())
.inspect_err(|e| panic!("{e}, main block = {expected:?}"))
.unwrap();
assert_eq!(expected.encode(), actual.encode());
}
}
impl MainBlock {
fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
self.encode_to(&mut buf);
buf
}
}
#[test]
fn node_reader_writer_works() {
let _ = env_logger::try_init();
for block_size in
[MIN_BLOCK_SIZE, MIN_BLOCK_SIZE + (MAX_BLOCK_SIZE - MIN_BLOCK_SIZE) / 3, MAX_BLOCK_SIZE]
{
for file_size in [0, 1, 2, 133, block_size - 1, block_size, block_size + 1] {
for buf_len in [1, 123] {
std::panic::catch_unwind(|| {
let mut rng = rand::rng();
let blocks: VecMap<BlockRef, Bytes> = VecMap::new();
let mut writer =
NodeWriter::new(0, blocks, file_size as u64, block_size).unwrap();
let contents: Vec<u8> = (&mut rng).random_iter().take(file_size).collect();
writer.write_all(&contents).unwrap();
let (block_ref, blocks) = writer.finish().unwrap();
let mut reader = NodeReader::new(&block_ref, blocks).unwrap();
assert_eq!(file_size as u64, reader.file().main_block().file_size());
let mut buf = Vec::new();
let remaining = (reader.file().main_block().file_size() -
reader.file().position()) as usize;
buf.resize(remaining, 0_u8);
let mut offset = 0;
while offset != buf.len() {
let n = (buf.len() - offset).min(buf_len);
let nread = reader.read(&mut buf[offset..offset + n]).unwrap();
assert_eq!(n, nread);
offset += n;
}
assert_eq!(contents, buf);
buf.clear();
reader.seek(0).unwrap();
let nread = reader.read_to_end(&mut buf).unwrap();
assert_eq!(file_size, nread);
assert_eq!(contents, buf);
})
.inspect_err(|_| {
std::eprintln!(
"Panic context: block size = {block_size}, \
file size = {file_size}, read buffer len = {buf_len}"
);
})
.unwrap();
}
}
}
}
#[test]
fn copy_file_in_out_works() {
let _ = env_logger::try_init();
for block_size in
[MIN_BLOCK_SIZE, MIN_BLOCK_SIZE + (MAX_BLOCK_SIZE - MIN_BLOCK_SIZE) / 3, MAX_BLOCK_SIZE]
{
for file_size in [0, 1, 2, 133, block_size - 1, block_size, block_size + 1] {
std::panic::catch_unwind(|| {
let mut rng = rand::rng();
let contents: Vec<u8> = (&mut rng).random_iter().take(file_size).collect();
let mut blocks: VecMap<BlockRef, Bytes> = VecMap::new();
let block_ref =
copy_file_in(&mut Cursor::new(&contents[..]), 0, &mut blocks, block_size)
.unwrap();
let mut buf = vec![0_u8; contents.len()];
copy_file_out(&block_ref, &mut blocks, &mut &mut buf[..]).unwrap();
assert_eq!(contents, buf);
})
.inspect_err(|_| {
std::eprintln!("Panic context: block size = {block_size}, file size = {file_size}");
})
.unwrap();
}
}
}
#[test]
fn copy_dir_in_out_works() {
let _ = env_logger::try_init();
for block_size in
[MIN_BLOCK_SIZE, MIN_BLOCK_SIZE + (MAX_BLOCK_SIZE - MIN_BLOCK_SIZE) / 3, MAX_BLOCK_SIZE]
{
for _ in 0..100 {
std::panic::catch_unwind(|| {
let mut blocks: VecMap<BlockRef, Bytes> = VecMap::new();
let input_dir = random_dir();
let dir_reader = StdDirReader::new(input_dir.path().to_path_buf()).unwrap();
let block_ref = copy_dir_in(dir_reader, 0, &mut blocks, block_size).unwrap();
let out_dir = TempDir::new().unwrap();
let dir_writer = StdDirWriter::new(out_dir.path().to_path_buf());
copy_dir_out(&block_ref, &mut blocks, dir_writer).unwrap();
assert_eq!(list_dir_all(input_dir.path()), list_dir_all(out_dir.path()));
})
.inspect_err(|_| {
std::eprintln!("Panic context: block size = {block_size}");
})
.unwrap();
}
}
}
fn random_dir() -> TempDir {
let dir = TempDir::new().unwrap();
let mut current_path = dir.path().to_path_buf();
let mut all_paths = BTreeSet::new();
let mut rng = rand::rng();
let rng = &mut rng;
let num_files = rng.random_range(0..=10);
for _ in 0..num_files {
let file_path = loop {
let file_name_len = rng.random_range(1..10);
let file_name: String =
rng.sample_iter(&Alphanumeric).take(file_name_len).map(char::from).collect();
let file_path = current_path.join(file_name.to_lowercase());
if all_paths.insert(file_path.clone()) {
break file_path;
}
};
if rng.random::<bool>() {
let file_size = rng.random_range(0..=100);
let contents: Vec<u8> = rng.random_iter().take(file_size).collect();
std::fs::write(&file_path, &contents)
.unwrap_or_else(|e| panic!("Failed to create file {file_path:?}: {e}"));
} else {
std::fs::create_dir(&file_path)
.unwrap_or_else(|e| panic!("Failed to create directory {file_path:?}: {e}"));
if rng.random::<bool>() {
current_path = file_path;
}
}
}
dir
}
fn list_dir_all(dir_path: &Path) -> Vec<(PathBuf, Entry)> {
let mut all_entries = Vec::new();
let mut queue = VecDeque::new();
queue.push_back(dir_path.to_path_buf());
while let Some(path) = queue.pop_front() {
let dir = std::fs::read_dir(&path).unwrap();
for entry in dir {
let entry = entry.unwrap();
let path = entry.path();
let relative_path = path.strip_prefix(dir_path).unwrap().to_path_buf();
let file_type = entry.file_type().unwrap();
if file_type.is_file() {
let contents = std::fs::read(&path).unwrap();
all_entries.push((relative_path, Entry::File(contents)));
continue;
}
if file_type.is_dir() {
queue.push_back(entry.path());
all_entries.push((relative_path, Entry::Dir));
continue;
}
panic!("Unsupported file type {file_type:?}");
}
}
all_entries.sort();
all_entries
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Entry {
File(Vec<u8>),
Dir,
}
#[test]
fn resolve_path_works() {
let mut blocks: VecMap<BlockRef, Bytes> = VecMap::new();
let input_dir = {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("x"), "123").unwrap();
std::fs::create_dir_all(dir.path().join("a").join("b")).unwrap();
std::fs::write(dir.path().join("a").join("y"), "456").unwrap();
dir
};
let dir_reader = StdDirReader::new(input_dir.path().to_path_buf()).unwrap();
let root_dir_hash = copy_dir_in(dir_reader, 0, &mut blocks, MAX_BLOCK_SIZE).unwrap();
let x_hash = copy_file_in(
&mut std::fs::File::open(input_dir.path().join("x")).unwrap(),
0,
&mut blocks,
MAX_BLOCK_SIZE,
)
.unwrap();
let y_hash = copy_file_in(
&mut std::fs::File::open(input_dir.path().join("a").join("y")).unwrap(),
0,
&mut blocks,
MAX_BLOCK_SIZE,
)
.unwrap();
for path in [c"/", c"//", c"/./", c"/.", c"/..", c"/./../", c"/../..", c"/a/..", c"/a/b/../.."]
{
assert_eq!(
root_dir_hash,
resolve_path(root_dir_hash, c"", path, &mut blocks).unwrap(),
"path = {path:?}"
);
}
for path in [c"/x", c"/a/../x", c"/a/../../x"] {
assert_eq!(
x_hash,
resolve_path(root_dir_hash, c"", path, &mut blocks).unwrap(),
"path = {path:?}"
);
}
for path in [c"/a/y", c"/a/b/../y", c"/a/../../a/y"] {
assert_eq!(
y_hash,
resolve_path(root_dir_hash, c"", path, &mut blocks).unwrap(),
"path = {path:?}"
);
}
for (path, cwd) in [(c"..", c"/a"), (c"../..", c"/a/b")] {
assert_eq!(
root_dir_hash,
resolve_path(root_dir_hash, cwd, path, &mut blocks).unwrap(),
"path = {path:?}"
);
}
}