use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use anyhow::anyhow;
use crate::core::{FsBackend, Result, utils};
use crate::{Entry, EntryType};
pub struct MapFS {
root: PathBuf, cwd: PathBuf, entries: BTreeMap<PathBuf, Entry>, }
impl MapFS {
pub fn new() -> Self {
Self {
root: PathBuf::from("/"),
cwd: PathBuf::from("/"),
entries: BTreeMap::new(),
}
}
pub fn set_root<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let path = path.as_ref();
if !path.is_absolute() {
return Err(anyhow!("root path must be an absolute"));
}
self.root = path.to_path_buf();
Ok(())
}
fn to_inner<P: AsRef<Path>>(&self, inner_path: P) -> PathBuf {
utils::normalize(self.cwd.join(inner_path))
}
}
impl FsBackend for MapFS {
fn root(&self) -> &Path {
self.root.as_path()
}
fn cwd(&self) -> &Path {
self.cwd.as_path()
}
fn to_host<P: AsRef<Path>>(&self, inner_path: P) -> Result<PathBuf> {
let inner = self.to_inner(inner_path);
Ok(self.root.join(inner.strip_prefix("/")?))
}
fn cd<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let target = self.to_inner(path);
if !self.is_dir(&target)? {
return Err(anyhow!("{} not a directory", target.display()));
}
self.cwd = target;
Ok(())
}
fn exists<P: AsRef<Path>>(&self, path: P) -> bool {
let inner = self.to_inner(path);
utils::is_virtual_root(&inner) || self.entries.contains_key(&inner)
}
fn is_dir<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
let path = path.as_ref();
let inner = self.to_inner(path);
if !self.exists(&inner) {
return Err(anyhow!("{} does not exist", path.display()));
}
Ok(utils::is_virtual_root(&inner) || self.entries[&inner].is_dir())
}
fn is_file<P: AsRef<Path>>(&self, path: P) -> Result<bool> {
let path = path.as_ref();
let inner = self.to_inner(path);
if !self.exists(&inner) {
return Err(anyhow!("{} does not exist", path.display()));
}
Ok(!utils::is_virtual_root(&inner) && self.entries[&inner].is_file())
}
fn ls<P: AsRef<Path>>(&self, path: P) -> Result<impl Iterator<Item = &Path>> {
let inner_path = self.to_inner(path);
if !self.exists(&inner_path) {
return Err(anyhow!("{} does not exist", inner_path.display()));
}
let is_file = self.is_file(&inner_path)?;
let component_count = if is_file {
inner_path.components().count()
} else {
inner_path.components().count() + 1
};
Ok(self
.entries
.iter()
.map(|(pb, _)| pb.as_path())
.filter(move |&path| {
path.starts_with(&inner_path)
&& (path != inner_path || is_file)
&& path.components().count() == component_count
}))
}
fn tree<P: AsRef<Path>>(&self, path: P) -> Result<impl Iterator<Item = &Path>> {
let inner_path = self.to_inner(path);
if !self.exists(&inner_path) {
return Err(anyhow!("{} does not exist", inner_path.display()));
}
let is_file = self.is_file(&inner_path)?;
Ok(self
.entries
.iter()
.map(|(pb, _)| pb.as_path())
.filter(move |&path| path.starts_with(&inner_path) && (path != inner_path || is_file)))
}
fn mkdir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
if path.as_ref().as_os_str().is_empty() {
return Err(anyhow!("invalid path: empty"));
}
let inner_path = self.to_inner(path);
if self.exists(&inner_path) {
return Err(anyhow!("path already exists: {}", inner_path.display()));
}
let mut existed_parent = inner_path.clone();
while let Some(parent) = existed_parent.parent() {
let parent_buf = parent.to_path_buf();
if self.exists(parent) {
existed_parent = parent_buf;
break;
}
existed_parent = parent_buf;
}
let need_to_create: Vec<_> = inner_path
.strip_prefix(&existed_parent)?
.components()
.collect();
let mut built = PathBuf::from(&existed_parent);
for component in need_to_create {
built.push(component);
if !self.exists(&built) {
self.entries
.insert(built.clone(), Entry::new(EntryType::Directory));
}
}
Ok(())
}
fn mkfile<P: AsRef<Path>>(&mut self, file_path: P, content: Option<&[u8]>) -> Result<()> {
let file_path = self.to_inner(file_path);
if self.exists(&file_path) {
return Err(anyhow!("{} already exist", file_path.display()));
}
if let Some(parent) = file_path.parent() {
if !self.exists(parent) {
self.mkdir(parent)?;
}
}
let mut entry = Entry::new(EntryType::File);
if let Some(content) = content {
entry.set_content(content);
}
self.entries.insert(file_path.clone(), entry);
Ok(())
}
fn read<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>> {
let path = path.as_ref();
if self.is_dir(path)? {
return Err(anyhow!("{} is a directory", path.display()));
}
Ok(self.entries[path].content().cloned().unwrap_or(Vec::new()))
}
fn write<P: AsRef<Path>>(&mut self, path: P, content: &[u8]) -> Result<()> {
let path = path.as_ref();
if self.is_dir(path)? {
return Err(anyhow!("{} is a directory", path.display()));
}
self.entries.get_mut(path).unwrap().set_content(content); Ok(())
}
fn append<P: AsRef<Path>>(&mut self, path: P, content: &[u8]) -> Result<()> {
let path = path.as_ref();
if self.is_dir(path)? {
return Err(anyhow!("{} is a directory", path.display()));
}
self.entries.get_mut(path).unwrap().append_content(content); Ok(())
}
fn rm<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
if path.as_ref().as_os_str().is_empty() {
return Err(anyhow!("invalid path: empty"));
}
if utils::is_virtual_root(&path) {
return Err(anyhow!("invalid path: the root cannot be removed"));
}
let inner_path = self.to_inner(path);
if !self.exists(&inner_path) {
return Err(anyhow!("{} does not exist", inner_path.display()));
}
let removed: Vec<PathBuf> = self
.entries
.iter()
.map(|(pb, _)| pb)
.filter(|&pb| pb.starts_with(&inner_path)) .cloned()
.collect();
for p in &removed {
self.entries.remove(p);
}
Ok(())
}
fn cleanup(&mut self) -> bool {
self.entries.clear();
true
}
}
#[cfg(test)]
mod tests {
use super::*;
mod creations {
use super::*;
#[test]
fn test_new_map_fs() {
let mut fs = MapFS::new();
assert_eq!(fs.root(), "/");
assert_eq!(fs.cwd(), "/");
#[cfg(unix)]
{
fs.set_root("/new/root").unwrap();
assert_eq!(fs.root(), "/new/root");
let host_path = fs.to_host("/inner/path").unwrap();
assert_eq!(host_path.as_path(), "/new/root/inner/path");
}
#[cfg(windows)]
{
fs.set_root("c:\\new\\root").unwrap();
assert_eq!(fs.root(), "c:\\new\\root");
let host_path = fs.to_host("\\inner\\path").unwrap();
assert_eq!(host_path.as_path(), "c:\\new\\root\\inner\\path");
}
let result = fs.set_root("new/relative/root");
assert!(result.is_err());
}
}
mod cd {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkdir("/etc").unwrap();
vfs.mkfile("/home/user/config.txt", Some(b"Config content"))
.unwrap();
vfs
}
#[test]
fn test_cd_absolute_path_success() -> Result<()> {
let mut vfs = setup_test_vfs();
assert_eq!(vfs.cwd, Path::new("/"));
vfs.cd("/home/user")?;
assert_eq!(vfs.cwd, Path::new("/home/user"));
Ok(())
}
#[test]
fn test_cd_relative_path_success() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home")?; assert_eq!(vfs.cwd, Path::new("/home"));
vfs.cd("user")?;
assert_eq!(vfs.cwd, Path::new("/home/user"));
Ok(())
}
#[test]
fn test_cd_root_directory() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/")?;
assert_eq!(vfs.cwd, Path::new("/"));
Ok(())
}
#[test]
fn test_cd_nonexistent_path_error() -> Result<()> {
let mut vfs = setup_test_vfs();
let result = vfs.cd("/nonexistent/path");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Error message should indicate path does not exist"
);
assert_eq!(vfs.cwd, Path::new("/"));
Ok(())
}
#[test]
fn test_cd_file_path_error() -> Result<()> {
let mut vfs = setup_test_vfs();
let result = vfs.cd("/home/user/config.txt");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("not a directory"),
"Even though the file exists, cd() should fail because it's not a directory"
);
assert_eq!(vfs.cwd, Path::new("/"));
Ok(())
}
#[test]
fn test_cd_same_directory() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home")?;
assert_eq!(vfs.cwd, Path::new("/home"));
vfs.cd("/home")?;
assert_eq!(vfs.cwd, Path::new("/home")); Ok(())
}
#[test]
fn test_cd_deep_nested_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user")?;
assert_eq!(vfs.cwd, Path::new("/home/user"));
Ok(())
}
#[test]
fn test_cd_sequential_changes() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/etc")?;
assert_eq!(vfs.cwd, Path::new("/etc"));
vfs.cd("/home")?;
assert_eq!(vfs.cwd, Path::new("/home"));
vfs.cd("/")?;
assert_eq!(vfs.cwd, Path::new("/"));
Ok(())
}
#[test]
fn test_cd_with_trailing_slash() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/")?;
assert_eq!(vfs.cwd, Path::new("/home"));
vfs.cd("/home/user//")?;
assert_eq!(vfs.cwd, Path::new("/home/user"));
Ok(())
}
}
mod exists {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkfile("/home/user/file.txt", Some(b"Hello")).unwrap();
vfs.mkfile("/readme.md", Some(b"Project docs")).unwrap();
vfs
}
#[test]
fn test_exists_absolute_path_file() {
let vfs = setup_test_vfs();
assert!(vfs.exists("/home/user/file.txt"));
}
#[test]
fn test_exists_absolute_path_directory() {
let vfs = setup_test_vfs();
assert!(vfs.exists("/home/user"));
}
#[test]
fn test_exists_root_directory() {
let vfs = setup_test_vfs();
assert!(vfs.exists("/"));
}
#[test]
fn test_exists_relative_path_from_root() {
let vfs = setup_test_vfs();
assert!(vfs.exists("home/user/file.txt"));
}
#[test]
fn test_exists_relative_path_nested() {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap(); assert!(vfs.exists("file.txt")); }
#[test]
fn test_exists_nonexistent_file() {
let vfs = setup_test_vfs();
assert!(!vfs.exists("/home/user/nonexistent.txt"));
}
#[test]
fn test_exists_nonexistent_directory() {
let vfs = setup_test_vfs();
assert!(!vfs.exists("/tmp"));
}
#[test]
fn test_exists_partial_path() {
let vfs = setup_test_vfs();
assert!(!vfs.exists("/home/us"));
}
#[test]
fn test_exists_with_trailing_slash() {
let vfs = setup_test_vfs();
assert!(vfs.exists("/home/")); assert!(vfs.exists("/home/user/")); assert!(vfs.exists("/readme.md/")); }
#[test]
fn test_exists_case_sensitivity() {
#[cfg(unix)]
{
let mut vfs = setup_test_vfs();
vfs.mkdir("/Home/User").unwrap();
assert!(vfs.exists("/Home/User"));
assert!(!vfs.exists("/home/User")); }
}
#[test]
fn test_exists_empty_string() {
let vfs = setup_test_vfs();
assert!(vfs.exists(""));
}
#[test]
fn test_exists_dot_path() {
let vfs = setup_test_vfs();
assert!(vfs.exists(".")); assert!(vfs.exists("./home")); }
#[test]
fn test_exists_double_dot_path() {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap();
assert!(vfs.exists("..")); assert!(vfs.exists("../user")); assert!(vfs.exists("../../etc")); }
}
mod is_dir_file {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkfile("/home/user/file.txt", Some(b"Hello")).unwrap();
vfs.mkfile("/readme.md", Some(b"Project docs")).unwrap();
vfs.mkfile("/empty.bin", None).unwrap();
vfs
}
#[test]
fn test_is_dir_existing_directory_absolute() -> Result<()> {
let vfs = setup_test_vfs();
assert!(vfs.is_dir("/home")?);
assert!(vfs.is_dir("/home/user")?);
assert!(vfs.is_dir("/")?); Ok(())
}
#[test]
fn test_is_dir_existing_directory_relative() -> Result<()> {
let vfs = setup_test_vfs();
assert!(vfs.is_dir("home")?);
let mut vfs2 = setup_test_vfs();
vfs2.cd("/home").unwrap();
assert!(vfs2.is_dir("user")?);
Ok(())
}
#[test]
fn test_is_dir_file_path() -> Result<()> {
let vfs = setup_test_vfs();
assert!(!vfs.is_dir("/home/user/file.txt")?);
assert!(!vfs.is_dir("/readme.md")?);
Ok(())
}
#[test]
fn test_is_dir_nonexistent_path() {
let vfs = setup_test_vfs();
let result = vfs.is_dir("/nonexistent");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Error should mention path does not exist"
);
}
#[test]
fn test_is_file_existing_file_absolute() -> Result<()> {
let vfs = setup_test_vfs();
assert!(vfs.is_file("/home/user/file.txt")?);
assert!(vfs.is_file("/readme.md")?);
assert!(vfs.is_file("/empty.bin")?); Ok(())
}
#[test]
fn test_is_file_existing_file_relative() -> Result<()> {
let vfs = setup_test_vfs();
assert!(vfs.is_file("readme.md")?);
let mut vfs2 = setup_test_vfs();
vfs2.cd("/home/user").unwrap();
assert!(vfs2.is_file("file.txt")?);
Ok(())
}
#[test]
fn test_is_file_directory_path() -> Result<()> {
let vfs = setup_test_vfs();
assert!(!vfs.is_file("/home")?);
assert!(!vfs.is_file("/home/user")?);
assert!(!vfs.is_file("/")?); Ok(())
}
#[test]
fn test_is_file_nonexistent_path() {
let vfs = setup_test_vfs();
let result = vfs.is_file("/nonexistent.txt");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Error should mention path does not exist"
);
}
#[test]
fn test_is_dir_and_is_file_on_same_file() -> Result<()> {
let vfs = setup_test_vfs();
let file_path = "/home/user/file.txt";
assert!(!vfs.is_dir(file_path)?); assert!(vfs.is_file(file_path)?);
Ok(())
}
#[test]
fn test_is_dir_and_is_file_on_same_directory() -> Result<()> {
let vfs = setup_test_vfs();
let dir_path = "/home/user";
assert!(vfs.is_dir(dir_path)?); assert!(!vfs.is_file(dir_path)?);
Ok(())
}
#[test]
fn test_is_dir_with_trailing_slash() -> Result<()> {
let vfs = setup_test_vfs();
assert!(vfs.is_dir("/home/")?); assert!(vfs.is_dir("/home/user/")?);
Ok(())
}
#[test]
fn test_is_file_with_trailing_slash() -> Result<()> {
let vfs = setup_test_vfs();
assert!(vfs.is_file("/readme.md/")?);
assert!(vfs.is_file("/home/user/file.txt/")?);
Ok(())
}
#[test]
fn test_is_dir_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home").unwrap();
assert!(vfs.is_dir(".")?); assert!(vfs.is_dir("./user")?); Ok(())
}
#[test]
fn test_is_file_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap();
assert!(vfs.is_file("./file.txt")?);
Ok(())
}
#[test]
fn test_is_dir_double_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap();
assert!(vfs.is_dir("..")?);
let result = vfs.is_dir("../etc");
assert!(result.is_err()); Ok(())
}
}
mod ls {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkdir("/home/guest").unwrap();
vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
.unwrap();
vfs.mkfile("/home/user/file2.txt", Some(b"Content 2"))
.unwrap();
vfs.mkfile("/home/guest/note.txt", Some(b"Note")).unwrap();
vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
vfs
}
#[test]
fn test_ls_root_directory() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.ls("/")?.collect();
assert_eq!(entries.len(), 3);
assert!(entries.contains(&Path::new("/etc")));
assert!(entries.contains(&Path::new("/home")));
assert!(entries.contains(&Path::new("/readme.md")));
Ok(())
}
#[test]
fn test_ls_home_directory() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.ls("/home")?.collect();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&Path::new("/home/user")));
assert!(entries.contains(&Path::new("/home/guest")));
Ok(())
}
#[test]
fn test_ls_user_directory() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.ls("/home/user")?.collect();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/file2.txt")));
Ok(())
}
#[test]
fn test_ls_nonexistent_path() {
let vfs = setup_test_vfs();
let result: Result<Vec<_>> = vfs.ls("/nonexistent").map(|iter| iter.collect());
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Error should mention path does not exist"
);
}
#[test]
fn test_ls_file_path() {
let vfs = setup_test_vfs();
let result: Result<Vec<_>> = vfs.ls("/home/user/file1.txt").map(|iter| iter.collect());
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["/home/user/file1.txt"]);
}
#[test]
fn test_ls_empty_directory() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkdir("/empty_dir").unwrap();
let entries: Vec<_> = vfs.ls("/empty_dir")?.collect();
assert_eq!(entries.len(), 0);
Ok(())
}
#[test]
fn test_ls_relative_path_from_root() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.ls("home")?.collect();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&Path::new("/home/user")));
assert!(entries.contains(&Path::new("/home/guest")));
Ok(())
}
#[test]
fn test_ls_relative_path_nested() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home").unwrap();
let entries: Vec<_> = vfs.ls("user")?.collect();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/file2.txt")));
Ok(())
}
#[test]
fn test_ls_with_trailing_slash() -> Result<()> {
let vfs = setup_test_vfs();
let entries1: Vec<_> = vfs.ls("/home/")?.collect(); let entries2: Vec<_> = vfs.ls("/home")?.collect();
assert_eq!(entries1, entries2); Ok(())
}
#[test]
fn test_ls_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap();
let entries: Vec<_> = vfs.ls(".")?.collect();
assert_eq!(entries.len(), 2);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/file2.txt")));
Ok(())
}
#[test]
fn test_ls_double_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap();
let entries: Vec<_> = vfs.ls("..")?.collect(); assert_eq!(entries.len(), 2);
assert!(entries.contains(&Path::new("/home/user")));
assert!(entries.contains(&Path::new("/home/guest")));
Ok(())
}
#[test]
fn test_ls_iterator_lazy_evaluation() -> Result<()> {
let vfs = setup_test_vfs();
let mut iter = vfs.ls("/home/user")?;
assert!(iter.next().is_some());
let count = iter.count();
assert_eq!(count + 1, 2);
Ok(())
}
}
mod tree {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkdir("/home/user/projects").unwrap();
vfs.mkdir("/home/guest").unwrap();
vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
.unwrap();
vfs.mkfile("/home/user/projects/proj1.rs", Some(b"Code 1"))
.unwrap();
vfs.mkfile("/home/user/projects/proj2.rs", Some(b"Code 2"))
.unwrap();
vfs.mkfile("/home/guest/note.txt", Some(b"Note")).unwrap();
vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
vfs
}
#[test]
fn test_tree_root() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.tree("/")?.collect();
assert_eq!(entries.len(), 10);
assert!(entries.contains(&Path::new("/etc")));
assert!(entries.contains(&Path::new("/home")));
assert!(entries.contains(&Path::new("/home/user")));
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
assert!(entries.contains(&Path::new("/home/guest")));
assert!(entries.contains(&Path::new("/home/guest/note.txt")));
Ok(())
}
#[test]
fn test_tree_home_directory() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.tree("/home")?.collect();
assert_eq!(entries.len(), 7);
assert!(entries.contains(&Path::new("/home/user")));
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
assert!(entries.contains(&Path::new("/home/guest")));
assert!(entries.contains(&Path::new("/home/guest/note.txt")));
Ok(())
}
#[test]
fn test_tree_user_directory() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.tree("/home/user")?.collect();
assert_eq!(entries.len(), 4);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
Ok(())
}
#[test]
fn test_tree_nonexistent_path() {
let vfs = setup_test_vfs();
let result: Result<Vec<_>> = vfs.tree("/nonexistent").map(|iter| iter.collect());
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Error should mention path does not exist"
);
}
#[test]
fn test_tree_file_path() {
let vfs = setup_test_vfs();
let result: Result<Vec<_>> =
vfs.tree("/home/user/file1.txt").map(|iter| iter.collect());
assert!(result.is_ok());
assert_eq!(result.unwrap(), vec!["/home/user/file1.txt"]);
}
#[test]
fn test_tree_empty_directory() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkdir("/empty_dir").unwrap();
let entries: Vec<_> = vfs.tree("/empty_dir")?.collect();
assert_eq!(entries.len(), 0);
Ok(())
}
#[test]
fn test_tree_relative_path_from_root() -> Result<()> {
let vfs = setup_test_vfs();
let entries: Vec<_> = vfs.tree("home")?.collect();
assert_eq!(entries.len(), 7);
assert!(entries.contains(&Path::new("/home/user")));
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
assert!(entries.contains(&Path::new("/home/guest")));
assert!(entries.contains(&Path::new("/home/guest/note.txt")));
Ok(())
}
#[test]
fn test_tree_relative_path_nested() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home").unwrap();
let entries: Vec<_> = vfs.tree("user")?.collect();
assert_eq!(entries.len(), 4);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
Ok(())
}
#[test]
fn test_tree_with_trailing_slash() -> Result<()> {
let vfs = setup_test_vfs();
let entries1: Vec<_> = vfs.tree("/home/")?.collect(); let entries2: Vec<_> = vfs.tree("/home")?.collect();
assert_eq!(entries1, entries2); Ok(())
}
#[test]
fn test_tree_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user").unwrap();
let entries: Vec<_> = vfs.tree(".")?.collect();
assert_eq!(entries.len(), 4);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
Ok(())
}
#[test]
fn test_tree_double_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user/projects").unwrap();
let entries: Vec<_> = vfs.tree("..")?.collect(); assert_eq!(entries.len(), 4);
assert!(entries.contains(&Path::new("/home/user/file1.txt")));
assert!(entries.contains(&Path::new("/home/user/projects")));
assert!(entries.contains(&Path::new("/home/user/projects/proj1.rs")));
assert!(entries.contains(&Path::new("/home/user/projects/proj2.rs")));
Ok(())
}
#[test]
fn test_tree_single_entry() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkdir("/single").unwrap();
let entries: Vec<_> = vfs.tree("/single")?.collect();
assert_eq!(entries.len(), 0);
Ok(())
}
#[test]
fn test_tree_iterator_lazy_evaluation() -> Result<()> {
let vfs = setup_test_vfs();
let mut iter = vfs.tree("/home/user")?;
assert!(iter.next().is_some());
let count = iter.count();
assert_eq!(count + 1, 4);
Ok(())
}
#[test]
fn test_tree_case_sensitivity() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkdir("/CASE_TEST").unwrap();
vfs.mkfile("/CASE_TEST/file.txt", Some(b"Data")).unwrap();
let entries: Vec<_> = vfs.tree("/CASE_TEST")?.collect();
assert_eq!(entries.len(), 1);
assert!(entries.contains(&Path::new("/CASE_TEST/file.txt")));
Ok(())
}
}
mod mkdir_mkfile {
use super::*;
fn setup_vfs() -> MapFS {
MapFS::new()
}
#[test]
fn test_mkdir_simple_directory() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkdir("/test")?;
assert!(vfs.exists("/test"));
assert!(vfs.is_dir("/test")?);
Ok(())
}
#[test]
fn test_mkdir_nested_directories() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkdir("/a/b/c/d")?;
assert!(vfs.exists("/a"));
assert!(vfs.exists("/a/b"));
assert!(vfs.exists("/a/b/c"));
assert!(vfs.exists("/a/b/c/d"));
Ok(())
}
#[test]
fn test_mkdir_existing_path() {
let mut vfs = setup_vfs();
vfs.mkdir("/existing").unwrap();
let result = vfs.mkdir("/existing");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("path already exists"),
"Should error when path exists"
);
}
#[test]
fn test_mkdir_empty_path() {
let mut vfs = setup_vfs();
let result = vfs.mkdir("");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("invalid path: empty"),
"Empty path should be rejected"
);
}
#[test]
fn test_mkdir_root_path() {
let mut vfs = setup_vfs();
let result = vfs.mkdir("/");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("path already exists"),
"Root always exists, should error"
);
}
#[test]
fn test_mkdir_with_trailing_slash() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkdir("/test/")?;
assert!(vfs.exists("/test"));
assert!(vfs.is_dir("/test")?);
Ok(())
}
#[test]
fn test_mkfile_simple_file() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkfile("/file.txt", Some(b"Hello World"))?;
assert!(vfs.exists("/file.txt"));
assert!(vfs.is_file("/file.txt")?);
assert_eq!(vfs.read("/file.txt")?, b"Hello World");
Ok(())
}
#[test]
fn test_mkfile_in_nested_directory() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkfile("/a/b/c/file.txt", Some(b"Content"))?;
assert!(vfs.exists("/a"));
assert!(vfs.exists("/a/b"));
assert!(vfs.exists("/a/b/c"));
assert!(vfs.exists("/a/b/c/file.txt"));
assert_eq!(vfs.read("/a/b/c/file.txt")?, b"Content");
Ok(())
}
#[test]
fn test_mkfile_empty_content() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkfile("/empty.txt", None)?;
assert!(vfs.exists("/empty.txt"));
assert!(vfs.is_file("/empty.txt")?);
assert_eq!(vfs.read("/empty.txt")?, &[]);
Ok(())
}
#[test]
fn test_mkfile_existing_file() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkfile("/test.txt", Some(b"Original"))?;
let result = vfs.mkfile("/test.txt", Some(b"New"));
assert!(result.is_err());
assert_eq!(vfs.read("/test.txt")?, b"Original");
Ok(())
}
#[test]
fn test_mkfile_to_existing_directory() {
let mut vfs = setup_vfs();
vfs.mkdir("/dir").unwrap();
let result = vfs.mkfile("/dir", Some(b"Content"));
assert!(result.is_err());
}
#[test]
fn test_mkfile_with_trailing_slash() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkfile("/file.txt/", Some(b"With slash"))?;
assert!(vfs.exists("/file.txt")); assert_eq!(vfs.read("/file.txt")?, b"With slash");
Ok(())
}
#[test]
fn test_mkfile_relative_path() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkdir("/home")?;
vfs.cd("/home")?;
vfs.mkfile("file.txt", Some(b"Relative"))?;
assert!(vfs.exists("/home/file.txt"));
assert_eq!(vfs.read("/home/file.txt")?, b"Relative");
Ok(())
}
#[test]
fn test_mkdir_and_mkfile_combination() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkdir("/projects")?;
vfs.mkfile("/projects/main.rs", Some(b"fn main() {}"))?;
vfs.mkdir("/projects/tests")?;
vfs.mkfile("/projects/tests/test1.rs", Some(b"#[test]"))?;
assert!(vfs.exists("/projects"));
assert!(vfs.exists("/projects/main.rs"));
assert!(vfs.exists("/projects/tests"));
assert!(vfs.exists("/projects/tests/test1.rs"));
Ok(())
}
#[test]
fn test_mkdir_case_sensitivity() -> Result<()> {
let mut vfs = setup_vfs();
vfs.mkdir("/CaseDir")?;
assert!(vfs.exists("/CaseDir"));
assert!(!vfs.exists("/casedir"));
Ok(())
}
}
mod read_write_append {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkfile("/readme.md", Some(b"Project docs")).unwrap();
vfs.mkfile("/data.bin", Some(b"\x00\x01\x02")).unwrap();
vfs.mkfile("/empty.txt", None).unwrap(); vfs.mkfile("/home/user/file.txt", Some(b"Hello World"))
.unwrap();
vfs
}
#[test]
fn test_read_existing_file() -> Result<()> {
let vfs = setup_test_vfs();
let content = vfs.read("/readme.md")?;
assert_eq!(content, b"Project docs");
Ok(())
}
#[test]
fn test_read_binary_file() -> Result<()> {
let vfs = setup_test_vfs();
let content = vfs.read("/data.bin")?;
assert_eq!(content, vec![0x00, 0x01, 0x02]);
Ok(())
}
#[test]
fn test_read_empty_file() -> Result<()> {
let vfs = setup_test_vfs();
let content = vfs.read("/empty.txt")?;
assert!(content.is_empty());
Ok(())
}
#[test]
fn test_read_nonexistent_file() {
let vfs = setup_test_vfs();
let result = vfs.read("/nonexistent.txt");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Error should mention file does not exist"
);
}
#[test]
fn test_read_directory_as_file() {
let vfs = setup_test_vfs();
let result = vfs.read("/etc");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("is a directory"),
"Reading directory as file should error"
);
}
#[test]
fn test_write_existing_file() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.write("/readme.md", b"Updated content")?;
let content = vfs.read("/readme.md")?;
assert_eq!(content, b"Updated content");
Ok(())
}
#[test]
fn test_write_binary_content() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.write("/data.bin", &[0xFF, 0xFE, 0xFD])?;
let content = vfs.read("/data.bin")?;
assert_eq!(content, vec![0xFF, 0xFE, 0xFD]);
Ok(())
}
#[test]
fn test_write_empty_content() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.write("/empty.txt", &[])?;
let content = vfs.read("/empty.txt")?;
assert!(content.is_empty());
Ok(())
}
#[test]
fn test_write_nonexistent_file() {
let mut vfs = setup_test_vfs();
let result = vfs.write("/newfile.txt", b"Content");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Writing to nonexistent file should fail"
);
}
#[test]
fn test_write_directory_as_file() {
let mut vfs = setup_test_vfs();
let result = vfs.write("/etc", b"Content");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("is a directory"),
"Writing to directory should error"
);
}
#[test]
fn test_append_to_file() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.append("/readme.md", b" - appended")?;
let content = vfs.read("/readme.md")?;
assert_eq!(content, b"Project docs - appended");
Ok(())
}
#[test]
fn test_append_binary_data() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.append("/data.bin", &[0xAA, 0xBB])?;
let content = vfs.read("/data.bin")?;
assert_eq!(content, vec![0x00, 0x01, 0x02, 0xAA, 0xBB]);
Ok(())
}
#[test]
fn test_append_empty_slice() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.append("/empty.txt", &[])?;
let content = vfs.read("/empty.txt")?;
assert!(content.is_empty()); Ok(())
}
#[test]
fn test_append_nonexistent_file() {
let mut vfs = setup_test_vfs();
let result = vfs.append("/newfile.txt", b"More content");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Appending to nonexistent file should fail"
);
}
#[test]
fn test_append_directory_as_file() {
let mut vfs = setup_test_vfs();
let result = vfs.append("/etc", b"Data");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("is a directory"),
"Appending to directory should error"
);
}
#[test]
fn test_write_and_append_sequence() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkfile("/test.txt", None)?;
vfs.write("/test.txt", b"Initial")?;
vfs.append("/test.txt", b" + appended")?;
vfs.write("/test.txt", b"Overwritten")?;
let final_content = vfs.read("/test.txt")?;
assert_eq!(final_content, b"Overwritten");
Ok(())
}
#[test]
fn test_read_after_write_and_append() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkfile("/log.txt", None)?;
vfs.write("/log.txt", b"Entry 1\n")?;
vfs.append("/log.txt", b"Entry 2\n")?;
vfs.write("/log.txt", b"Overwritten log\n")?;
vfs.append("/log.txt", b"Final entry\n")?;
let content = vfs.read("/log.txt")?;
assert_eq!(content, b"Overwritten log\nFinal entry\n");
Ok(())
}
}
mod rm {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkdir("/home/user/projects").unwrap();
vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
.unwrap();
vfs.mkfile("/home/user/projects/proj1.rs", Some(b"Code 1"))
.unwrap();
vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
vfs.mkfile("/data.bin", Some(b"\x00\x01")).unwrap();
vfs
}
#[test]
fn test_rm_file() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.rm("/readme.md")?;
assert!(!vfs.exists("/readme.md"));
assert!(vfs.exists("/data.bin"));
Ok(())
}
#[test]
fn test_rm_directory_recursive() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.rm("/home/user")?;
assert!(!vfs.exists("/home/user"));
assert!(!vfs.exists("/home/user/file1.txt"));
assert!(!vfs.exists("/home/user/projects"));
assert!(!vfs.exists("/home/user/projects/proj1.rs"));
assert!(vfs.exists("/home"));
assert!(vfs.exists("/etc"));
Ok(())
}
#[test]
fn test_rm_nonexistent_path() {
let mut vfs = setup_test_vfs();
let result = vfs.rm("/nonexistent");
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("does not exist"),
"Should error for non‑existent path"
);
}
#[test]
fn test_rm_empty_path() {
let mut vfs = setup_test_vfs();
let result = vfs.rm("");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("invalid path: empty"),
"Empty path should be rejected"
);
}
#[test]
fn test_rm_root_directory() {
let mut vfs = setup_test_vfs();
let result = vfs.rm("/");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("invalid path: the root cannot be removed"),
"Root directory cannot be removed"
);
}
#[test]
fn test_rm_with_trailing_slash() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.rm("/etc/")?;
assert!(!vfs.exists("/etc"));
Ok(())
}
#[test]
fn test_rm_relative_path_from_root() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home").unwrap();
vfs.rm("user")?;
assert!(!vfs.exists("/home/user"));
assert!(vfs.exists("/etc"));
Ok(())
}
#[test]
fn test_rm_relative_path_nested() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user/projects").unwrap();
vfs.rm("../file1.txt")?;
assert!(!vfs.exists("/home/user/file1.txt"));
assert!(vfs.exists("/home/user/projects/proj1.rs"));
Ok(())
}
#[test]
fn test_rm_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user")?;
let result = vfs.rm(".");
assert!(result.is_ok());
assert!(!vfs.exists("/home/user"));
Ok(())
}
#[test]
fn test_rm_double_dot_path() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.cd("/home/user/projects").unwrap();
let result = vfs.rm("..");
assert!(result.is_ok());
assert!(!vfs.exists("/home/user"));
assert!(vfs.exists("/home"));
Ok(())
}
#[test]
fn test_rm_single_file_in_dir() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.rm("/home/user/file1.txt")?;
assert!(!vfs.exists("/home/user/file1.txt"));
assert!(vfs.exists("/home/user/projects/proj1.rs")); assert!(vfs.exists("/home/user"));
Ok(())
}
#[test]
fn test_rm_idempotent() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.rm("/data.bin")?;
let result = vfs.rm("/data.bin");
assert!(result.is_err());
Ok(())
}
#[test]
fn test_rm_case_sensitivity() -> Result<()> {
let mut vfs = setup_test_vfs();
vfs.mkdir("/CaseDir").unwrap();
vfs.mkfile("/CaseDir/file.txt", Some(b"Content")).unwrap();
vfs.rm("/CaseDir")?;
assert!(!vfs.exists("/CaseDir"));
assert!(!vfs.exists("/casedir"));
Ok(())
}
}
mod cleanup {
use super::*;
fn setup_test_vfs() -> MapFS {
let mut vfs = MapFS::new();
vfs.mkdir("/etc").unwrap();
vfs.mkdir("/home").unwrap();
vfs.mkdir("/home/user").unwrap();
vfs.mkdir("/home/user/projects").unwrap();
vfs.mkfile("/home/user/file1.txt", Some(b"Content 1"))
.unwrap();
vfs.mkfile("/home/user/projects/proj1.rs", Some(b"Code 1"))
.unwrap();
vfs.mkfile("/readme.md", Some(b"Docs")).unwrap();
vfs.mkfile("/data.bin", Some(b"\x00\x01")).unwrap();
vfs.mkfile("/empty.txt", None).unwrap();
vfs
}
#[test]
fn test_cleanup_removes_all_entries() {
let mut vfs = setup_test_vfs();
let result = vfs.cleanup();
assert!(result); assert_eq!(vfs.entries.len(), 0); }
#[test]
fn test_cleanup_preserves_root() {
let mut vfs = setup_test_vfs();
vfs.cleanup();
assert!(vfs.exists("/"));
}
#[test]
fn test_cleanup_empty_vfs() {
let mut vfs = MapFS::new(); let result = vfs.cleanup();
assert!(result);
assert_eq!(vfs.entries.len(), 0);
}
#[test]
fn test_cleanup_after_partial_removal() {
let mut vfs = setup_test_vfs();
vfs.rm("/readme.md").unwrap();
vfs.rm("/home/user/projects").unwrap();
let result = vfs.cleanup();
assert!(result);
assert_eq!(vfs.entries.len(), 0);
}
#[test]
fn test_cleanup_idempotent() {
let mut vfs = setup_test_vfs();
assert!(vfs.cleanup());
assert!(vfs.cleanup());
assert_eq!(vfs.entries.len(), 0);
}
#[test]
fn test_cleanup_with_nested_structure() {
let mut vfs = setup_test_vfs();
vfs.mkdir("/a/b/c/d").unwrap();
vfs.mkfile("/a/b/c/d/file.txt", Some(b"Deep file")).unwrap();
vfs.cleanup();
assert_eq!(vfs.entries.len(), 0);
}
#[test]
fn test_cleanup_returns_true_always() {
let mut vfs1 = setup_test_vfs();
let mut vfs2 = MapFS::new();
assert!(vfs1.cleanup()); assert!(vfs2.cleanup()); }
}
}