#![allow(dead_code)]
use {
crate::{
Uri,
fs::FS,
},
slotmap::{
SlotMap,
new_key_type,
},
std::collections::BTreeMap,
tempfile::TempDir,
};
new_key_type! {
pub struct NodeKey;
}
#[derive(Debug)]
pub struct TestNode {
pub name: String,
pub content: Option<Vec<u8>>,
pub children: BTreeMap<String, NodeKey>,
}
pub struct TestData {
nodes: SlotMap<NodeKey, TestNode>,
root: NodeKey,
globs: BTreeMap<String, Vec<String>>,
}
impl TestData {
pub fn new() -> Self {
let mut nodes: SlotMap<NodeKey, TestNode> = SlotMap::with_key();
let root = nodes.insert(TestNode {
name: String::from(""),
content: None,
children: BTreeMap::new(),
});
TestData {
nodes,
root,
globs: BTreeMap::new(),
}
}
pub fn add_glob_test(
&mut self,
pattern: &str,
expected: Vec<String>,
) -> &mut Self {
self.globs.insert(pattern.to_string(), expected);
self
}
pub fn dir<F>(&mut self, name: &str, f: F) -> &mut Self
where
F: FnOnce(&mut DirBuilder),
{
let node_key = self.nodes.insert(TestNode {
name: name.to_string(),
content: None,
children: BTreeMap::new(),
});
if let Some(root) = self.nodes.get_mut(self.root) {
root.children.insert(name.to_string(), node_key);
}
let mut builder = DirBuilder {
nodes: &mut self.nodes,
current: node_key,
};
f(&mut builder);
self
}
pub fn create_physical(&self) -> (TempDir, Uri) {
let temp_dir = tempfile::Builder::new()
.prefix("laburnum-fs-test")
.tempdir()
.expect("Failed to create temp directory");
let base_uri =
Uri::from_file_path(temp_dir.path()).expect("Failed to create base Uri");
self.create_fs_structure(temp_dir.path(), self.root);
(temp_dir, base_uri)
}
pub fn flatten_structure(&self) -> Vec<(String, Option<Vec<u8>>)> {
let mut result = Vec::new();
self.flatten_node("", self.root, &mut result);
result
}
fn flatten_node(
&self,
prefix: &str,
node_key: NodeKey,
result: &mut Vec<(String, Option<Vec<u8>>)>,
) {
if let Some(node) = self.nodes.get(node_key) {
let current_path = if prefix.is_empty() {
node.name.clone()
} else {
format!("{}/{}", prefix, node.name)
};
if !current_path.is_empty() {
result.push((current_path.clone(), node.content.clone()));
}
for child_key in node.children.values() {
self.flatten_node(¤t_path, *child_key, result);
}
}
}
fn create_fs_structure(
&self,
base_path: &std::path::Path,
node_key: NodeKey,
) {
if let Some(node) = self.nodes.get(node_key) {
let current_path = base_path.join(&node.name);
if let Some(content) = &node.content {
std::fs::write(¤t_path, content).expect("Failed to write file");
} else if !node.name.is_empty() {
std::fs::create_dir_all(¤t_path)
.expect("Failed to create directory");
}
for child_key in node.children.values() {
self.create_fs_structure(¤t_path, *child_key);
}
}
}
}
pub struct DirBuilder<'a> {
nodes: &'a mut SlotMap<NodeKey, TestNode>,
current: NodeKey,
}
impl<'a> DirBuilder<'a> {
pub fn file(&mut self, name: &str, content: &str) {
let node_key = self.nodes.insert(TestNode {
name: name.to_string(),
content: Some(content.as_bytes().to_vec()),
children: BTreeMap::new(),
});
if let Some(current) = self.nodes.get_mut(self.current) {
current.children.insert(name.to_string(), node_key);
}
}
pub fn dir<F>(&mut self, name: &str, f: F)
where
F: FnOnce(&mut DirBuilder),
{
let node_key = self.nodes.insert(TestNode {
name: name.to_string(),
content: None,
children: BTreeMap::new(),
});
if let Some(current) = self.nodes.get_mut(self.current) {
current.children.insert(name.to_string(), node_key);
}
let mut builder = DirBuilder {
nodes: self.nodes,
current: node_key,
};
f(&mut builder);
}
}
pub fn run_filesystem_test(fs: FS, test_data: TestData, base_uri: Uri) {
let structure = test_data.flatten_structure();
println!("\nTesting filesystem with base_uri: {base_uri}");
println!("Test structure contains {} entries:", structure.len());
for (path, content) in &structure {
println!(
" {} ({})",
path,
if content.is_some() {
"file"
} else {
"directory"
}
);
}
println!("\nPhase 0: Creating initial structure");
for (path, content) in &structure {
let uri = base_uri.join(path).unwrap_or_else(|| {
panic!("Failed to create Uri for path '{path}'");
});
if content.is_none() {
match fs.dir(&uri) {
| Ok(_) => println!("✓ Created directory: {path}"),
| Err(e) => panic!("Failed to create directory '{path}': {e}"),
}
} else {
match fs.write(&uri, content.as_ref().unwrap()) {
| Ok(_) => println!("✓ Created file: {path}"),
| Err(e) => panic!("Failed to create file '{path}': {e}"),
}
}
}
println!("\nPhase 1: Verifying initial structure");
for (path, content) in &structure {
let uri = base_uri.join(path).unwrap_or_else(|| {
panic!("Failed to create Uri for path '{path}'");
});
match fs.metadata(&uri) {
| Ok(metadata) => {
println!("✓ Found entry: {path}");
assert_eq!(
metadata.is_dir,
content.is_none(),
"Type mismatch for '{}': expected {}, got directory",
path,
if content.is_none() {
"directory"
} else {
"file"
}
);
if let Some(content) = content {
assert_eq!(
metadata.len,
content.len() as u64,
"Size mismatch for file '{}': expected {} bytes, got {} bytes",
path,
content.len(),
metadata.len
);
}
},
| Err(e) => {
panic!("Failed to get metadata for '{path}' (uri: {uri}): {e}");
},
}
}
println!("\nPhase 2: Verifying file contents");
for (path, content) in &structure {
if let Some(expected_content) = content {
let uri = base_uri.join(path).unwrap();
match fs.read(&uri) {
| Ok(actual_content) => {
if actual_content == *expected_content {
println!("✓ Content verified: {path}");
} else {
panic!(
"Content mismatch for '{}':\nExpected: {}\nActual: {}\n",
path,
String::from_utf8_lossy(expected_content),
String::from_utf8_lossy(&actual_content)
);
}
},
| Err(e) => {
panic!("Failed to read content from '{path}': {e}");
},
}
}
}
println!("\nPhase 3: Testing pattern matching");
for (pattern, expected_paths) in test_data.globs {
println!("\nTesting pattern: {pattern}");
match fs.find(&base_uri, std::slice::from_ref(&pattern)) {
| Ok(entries) => {
let found_paths: Vec<String> = entries
.into_iter()
.map(|e| {
let full_path = e.path();
if full_path.scheme().as_str() == "memory" {
full_path.path_str().trim_start_matches('/').to_string()
} else {
let base_path = base_uri.path_str().trim_end_matches('/');
full_path
.path_str()
.trim_start_matches(base_path)
.trim_start_matches('/')
.to_string()
}
})
.collect();
println!("Found {} matches:", found_paths.len());
for path in &found_paths {
println!(" {path}");
}
for expected in &expected_paths {
assert!(
found_paths.contains(expected),
"Expected path '{}' not found for pattern '{}'\nFound paths:\n{}\n",
expected,
pattern,
found_paths.join("\n ")
);
}
for found in &found_paths {
assert!(
expected_paths.contains(found),
"Unexpected path '{}' found for pattern '{}'\nExpected paths:\n{}\n",
found,
pattern,
expected_paths.join("\n ")
);
}
},
| Err(e) => {
panic!("Failed to perform pattern matching for '{pattern}': {e}");
},
}
}
println!("\nPhase 4: Testing deletion");
let mut paths: Vec<_> = structure.iter().collect();
paths.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len()));
for (path, _) in paths {
let uri = base_uri.join(path).unwrap();
match fs.metadata(&uri) {
| Ok(_) => println!("✓ Verified existence: {path}"),
| Err(e) => {
panic!("Entry '{path}' doesn't exist before deletion attempt: {e}")
},
}
if let Err(e) = fs.delete(&uri) {
panic!("Failed to delete '{path}': {e}");
}
println!("✓ Deleted: {path}");
match fs.metadata(&uri) {
| Ok(_) => panic!("Entry '{path}' still exists after deletion"),
| Err(_) => println!("✓ Verified deletion: {path}"),
}
}
println!("\nAll tests passed successfully!");
}
#[cfg(test)]
mod test_cases;