1use super::traits::{DirEntry, DirEntryKind, Filesystem};
6use async_trait::async_trait;
7use std::collections::HashMap;
8use std::io;
9use std::path::{Path, PathBuf};
10use std::time::SystemTime;
11use tokio::sync::RwLock;
12
13#[derive(Debug, Clone)]
15enum Entry {
16 File { data: Vec<u8>, modified: SystemTime },
17 Directory { modified: SystemTime },
18 Symlink { target: PathBuf, modified: SystemTime },
19}
20
21#[derive(Debug)]
25pub struct MemoryFs {
26 entries: RwLock<HashMap<PathBuf, Entry>>,
27}
28
29impl Default for MemoryFs {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl MemoryFs {
36 pub fn new() -> Self {
38 let mut entries = HashMap::new();
39 entries.insert(
41 PathBuf::from(""),
42 Entry::Directory {
43 modified: SystemTime::now(),
44 },
45 );
46 Self {
47 entries: RwLock::new(entries),
48 }
49 }
50
51 fn normalize(path: &Path) -> PathBuf {
53 let mut result = PathBuf::new();
54 for component in path.components() {
55 match component {
56 std::path::Component::RootDir => {}
57 std::path::Component::CurDir => {}
58 std::path::Component::ParentDir => {
59 result.pop();
60 }
61 std::path::Component::Normal(s) => {
62 result.push(s);
63 }
64 std::path::Component::Prefix(_) => {}
65 }
66 }
67 result
68 }
69
70 const MAX_SYMLINK_DEPTH: usize = 40;
72
73 fn read_inner(&self, path: &Path, depth: usize) -> std::pin::Pin<Box<dyn std::future::Future<Output = io::Result<Vec<u8>>> + Send + '_>> {
75 let path = path.to_path_buf();
76 Box::pin(async move {
77 if depth > Self::MAX_SYMLINK_DEPTH {
78 return Err(io::Error::other(
79 "too many levels of symbolic links",
80 ));
81 }
82 let normalized = Self::normalize(&path);
83 let entries = self.entries.read().await;
84
85 match entries.get(&normalized) {
86 Some(Entry::File { data, .. }) => Ok(data.clone()),
87 Some(Entry::Directory { .. }) => Err(io::Error::new(
88 io::ErrorKind::IsADirectory,
89 format!("is a directory: {}", path.display()),
90 )),
91 Some(Entry::Symlink { target, .. }) => {
92 let target = target.clone();
93 drop(entries);
94 self.read_inner(&target, depth + 1).await
95 }
96 None => Err(io::Error::new(
97 io::ErrorKind::NotFound,
98 format!("not found: {}", path.display()),
99 )),
100 }
101 })
102 }
103
104 fn stat_inner(&self, path: &Path, depth: usize) -> std::pin::Pin<Box<dyn std::future::Future<Output = io::Result<DirEntry>> + Send + '_>> {
107 let path = path.to_path_buf();
108 Box::pin(async move {
109 if depth > Self::MAX_SYMLINK_DEPTH {
110 return Err(io::Error::other(
111 "too many levels of symbolic links",
112 ));
113 }
114 let normalized = Self::normalize(&path);
115
116 if normalized.as_os_str().is_empty() {
117 return Ok(DirEntry {
118 name: String::new(),
119 kind: DirEntryKind::Directory,
120 size: 0,
121 modified: Some(SystemTime::now()),
122 permissions: None,
123 symlink_target: None,
124 });
125 }
126
127 let entry_info: Option<(DirEntry, Option<PathBuf>)> = {
128 let entries = self.entries.read().await;
129 match entries.get(&normalized) {
130 Some(Entry::File { data, modified }) => Some((
131 DirEntry {
132 name: String::new(),
133 kind: DirEntryKind::File,
134 size: data.len() as u64,
135 modified: Some(*modified),
136 permissions: None,
137 symlink_target: None,
138 },
139 None,
140 )),
141 Some(Entry::Directory { modified }) => Some((
142 DirEntry {
143 name: String::new(),
144 kind: DirEntryKind::Directory,
145 size: 0,
146 modified: Some(*modified),
147 permissions: None,
148 symlink_target: None,
149 },
150 None,
151 )),
152 Some(Entry::Symlink { target, .. }) => Some((
153 DirEntry {
154 name: String::new(),
155 kind: DirEntryKind::File, size: 0,
157 modified: None,
158 permissions: None,
159 symlink_target: None,
160 },
161 Some(target.clone()),
162 )),
163 None => None,
164 }
165 };
166
167 match entry_info {
168 Some((entry, None)) => Ok(entry),
169 Some((_, Some(target))) => self.stat_inner(&target, depth + 1).await,
170 None => Err(io::Error::new(
171 io::ErrorKind::NotFound,
172 format!("not found: {}", path.display()),
173 )),
174 }
175 })
176 }
177
178 async fn ensure_parents(&self, path: &Path) -> io::Result<()> {
180 let mut entries = self.entries.write().await;
181
182 let mut current = PathBuf::new();
183 for component in path.parent().into_iter().flat_map(|p| p.components()) {
184 if let std::path::Component::Normal(s) = component {
185 current.push(s);
186 match entries.entry(current.clone()) {
187 std::collections::hash_map::Entry::Occupied(e) => {
188 if matches!(e.get(), Entry::File { .. }) {
189 return Err(io::Error::new(
190 io::ErrorKind::NotADirectory,
191 format!("not a directory: {}", current.display()),
192 ));
193 }
194 }
195 std::collections::hash_map::Entry::Vacant(e) => {
196 e.insert(Entry::Directory {
197 modified: SystemTime::now(),
198 });
199 }
200 }
201 }
202 }
203 Ok(())
204 }
205}
206
207#[async_trait]
208impl Filesystem for MemoryFs {
209 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
210 self.read_inner(path, 0).await
211 }
212
213 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
214 let normalized = Self::normalize(path);
215
216 self.ensure_parents(&normalized).await?;
218
219 let mut entries = self.entries.write().await;
220
221 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
223 return Err(io::Error::new(
224 io::ErrorKind::IsADirectory,
225 format!("is a directory: {}", path.display()),
226 ));
227 }
228
229 entries.insert(
230 normalized,
231 Entry::File {
232 data: data.to_vec(),
233 modified: SystemTime::now(),
234 },
235 );
236 Ok(())
237 }
238
239 async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
240 let normalized = Self::normalize(path);
241 let entries = self.entries.read().await;
242
243 match entries.get(&normalized) {
245 Some(Entry::Directory { .. }) => {}
246 Some(Entry::File { .. }) => {
247 return Err(io::Error::new(
248 io::ErrorKind::NotADirectory,
249 format!("not a directory: {}", path.display()),
250 ))
251 }
252 Some(Entry::Symlink { .. }) => {
253 return Err(io::Error::new(
254 io::ErrorKind::NotADirectory,
255 format!("not a directory: {}", path.display()),
256 ))
257 }
258 None if normalized.as_os_str().is_empty() => {
259 }
261 None => {
262 return Err(io::Error::new(
263 io::ErrorKind::NotFound,
264 format!("not found: {}", path.display()),
265 ))
266 }
267 }
268
269 let prefix = if normalized.as_os_str().is_empty() {
271 PathBuf::new()
272 } else {
273 normalized.clone()
274 };
275
276 let mut result = Vec::new();
277 for (entry_path, entry) in entries.iter() {
278 if let Some(parent) = entry_path.parent()
279 && parent == prefix && entry_path != &normalized
280 && let Some(name) = entry_path.file_name() {
281 let (kind, size, modified, symlink_target) = match entry {
282 Entry::File { data, modified } => (DirEntryKind::File, data.len() as u64, Some(*modified), None),
283 Entry::Directory { modified } => (DirEntryKind::Directory, 0, Some(*modified), None),
284 Entry::Symlink { target, modified } => (DirEntryKind::Symlink, 0, Some(*modified), Some(target.clone())),
285 };
286 result.push(DirEntry {
287 name: name.to_string_lossy().into_owned(),
288 kind,
289 size,
290 modified,
291 permissions: None,
292 symlink_target,
293 });
294 }
295 }
296
297 result.sort_by(|a, b| a.name.cmp(&b.name));
299 Ok(result)
300 }
301
302 async fn stat(&self, path: &Path) -> io::Result<DirEntry> {
303 let mut entry = self.stat_inner(path, 0).await?;
304 let normalized = Self::normalize(path);
306 entry.name = normalized
307 .file_name()
308 .map(|n| n.to_string_lossy().into_owned())
309 .unwrap_or_else(|| "/".to_string());
310 Ok(entry)
311 }
312
313 async fn lstat(&self, path: &Path) -> io::Result<DirEntry> {
314 let normalized = Self::normalize(path);
315
316 let name = normalized
317 .file_name()
318 .map(|n| n.to_string_lossy().into_owned())
319 .unwrap_or_else(|| "/".to_string());
320
321 let entries = self.entries.read().await;
322
323 if normalized.as_os_str().is_empty() {
325 return Ok(DirEntry {
326 name,
327 kind: DirEntryKind::Directory,
328 size: 0,
329 modified: Some(SystemTime::now()),
330 permissions: None,
331 symlink_target: None,
332 });
333 }
334
335 match entries.get(&normalized) {
336 Some(Entry::File { data, modified }) => Ok(DirEntry {
337 name,
338 kind: DirEntryKind::File,
339 size: data.len() as u64,
340 modified: Some(*modified),
341 permissions: None,
342 symlink_target: None,
343 }),
344 Some(Entry::Directory { modified }) => Ok(DirEntry {
345 name,
346 kind: DirEntryKind::Directory,
347 size: 0,
348 modified: Some(*modified),
349 permissions: None,
350 symlink_target: None,
351 }),
352 Some(Entry::Symlink { target, modified }) => Ok(DirEntry {
353 name,
354 kind: DirEntryKind::Symlink,
355 size: 0,
356 modified: Some(*modified),
357 permissions: None,
358 symlink_target: Some(target.clone()),
359 }),
360 None => Err(io::Error::new(
361 io::ErrorKind::NotFound,
362 format!("not found: {}", path.display()),
363 )),
364 }
365 }
366
367 async fn read_link(&self, path: &Path) -> io::Result<PathBuf> {
368 let normalized = Self::normalize(path);
369 let entries = self.entries.read().await;
370
371 match entries.get(&normalized) {
372 Some(Entry::Symlink { target, .. }) => Ok(target.clone()),
373 Some(_) => Err(io::Error::new(
374 io::ErrorKind::InvalidInput,
375 format!("not a symbolic link: {}", path.display()),
376 )),
377 None => Err(io::Error::new(
378 io::ErrorKind::NotFound,
379 format!("not found: {}", path.display()),
380 )),
381 }
382 }
383
384 async fn symlink(&self, target: &Path, link: &Path) -> io::Result<()> {
385 let normalized = Self::normalize(link);
386
387 self.ensure_parents(&normalized).await?;
389
390 let mut entries = self.entries.write().await;
391
392 if entries.contains_key(&normalized) {
394 return Err(io::Error::new(
395 io::ErrorKind::AlreadyExists,
396 format!("file exists: {}", link.display()),
397 ));
398 }
399
400 entries.insert(
401 normalized,
402 Entry::Symlink {
403 target: target.to_path_buf(),
404 modified: SystemTime::now(),
405 },
406 );
407 Ok(())
408 }
409
410 async fn mkdir(&self, path: &Path) -> io::Result<()> {
411 let normalized = Self::normalize(path);
412
413 self.ensure_parents(&normalized).await?;
415
416 let mut entries = self.entries.write().await;
417
418 if let Some(existing) = entries.get(&normalized) {
420 return match existing {
421 Entry::Directory { .. } => Ok(()), Entry::File { .. } | Entry::Symlink { .. } => Err(io::Error::new(
423 io::ErrorKind::AlreadyExists,
424 format!("file exists: {}", path.display()),
425 )),
426 };
427 }
428
429 entries.insert(
430 normalized,
431 Entry::Directory {
432 modified: SystemTime::now(),
433 },
434 );
435 Ok(())
436 }
437
438 async fn remove(&self, path: &Path) -> io::Result<()> {
439 let normalized = Self::normalize(path);
440
441 if normalized.as_os_str().is_empty() {
442 return Err(io::Error::new(
443 io::ErrorKind::PermissionDenied,
444 "cannot remove root directory",
445 ));
446 }
447
448 let mut entries = self.entries.write().await;
449
450 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
452 let has_children = entries.keys().any(|k| {
454 k.parent() == Some(&normalized) && k != &normalized
455 });
456 if has_children {
457 return Err(io::Error::new(
458 io::ErrorKind::DirectoryNotEmpty,
459 format!("directory not empty: {}", path.display()),
460 ));
461 }
462 }
463
464 entries.remove(&normalized).ok_or_else(|| {
465 io::Error::new(
466 io::ErrorKind::NotFound,
467 format!("not found: {}", path.display()),
468 )
469 })?;
470 Ok(())
471 }
472
473 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
474 let from_normalized = Self::normalize(from);
475 let to_normalized = Self::normalize(to);
476
477 if from_normalized.as_os_str().is_empty() {
478 return Err(io::Error::new(
479 io::ErrorKind::PermissionDenied,
480 "cannot rename root directory",
481 ));
482 }
483
484 if from_normalized == to_normalized {
486 return Ok(());
487 }
488
489 if to_normalized.starts_with(&from_normalized) {
491 return Err(io::Error::new(
492 io::ErrorKind::InvalidInput,
493 format!("cannot move '{}' into itself", from.display()),
494 ));
495 }
496
497 drop(self.ensure_parents(&to_normalized).await);
499
500 let mut entries = self.entries.write().await;
501
502 let entry = entries.remove(&from_normalized).ok_or_else(|| {
504 io::Error::new(
505 io::ErrorKind::NotFound,
506 format!("not found: {}", from.display()),
507 )
508 })?;
509
510 if let Some(existing) = entries.get(&to_normalized) {
512 match (&entry, existing) {
513 (Entry::File { .. }, Entry::Directory { .. }) => {
514 entries.insert(from_normalized, entry);
516 return Err(io::Error::new(
517 io::ErrorKind::IsADirectory,
518 format!("destination is a directory: {}", to.display()),
519 ));
520 }
521 (Entry::Directory { .. }, Entry::File { .. }) => {
522 entries.insert(from_normalized, entry);
523 return Err(io::Error::new(
524 io::ErrorKind::NotADirectory,
525 format!("destination is not a directory: {}", to.display()),
526 ));
527 }
528 _ => {}
529 }
530 }
531
532 if matches!(entry, Entry::Directory { .. }) {
534 let children_to_rename: Vec<(PathBuf, Entry)> = entries
536 .iter()
537 .filter(|(k, _)| k.starts_with(&from_normalized) && *k != &from_normalized)
538 .map(|(k, v)| (k.clone(), v.clone()))
539 .collect();
540
541 for (old_path, child_entry) in children_to_rename {
543 entries.remove(&old_path);
544 let Ok(relative) = old_path.strip_prefix(&from_normalized) else {
545 continue;
546 };
547 let new_path = to_normalized.join(relative);
548 entries.insert(new_path, child_entry);
549 }
550 }
551
552 entries.insert(to_normalized, entry);
554 Ok(())
555 }
556
557 fn read_only(&self) -> bool {
558 false
559 }
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 #[tokio::test]
567 async fn test_write_and_read() {
568 let fs = MemoryFs::new();
569 fs.write(Path::new("test.txt"), b"hello world").await.unwrap();
570 let data = fs.read(Path::new("test.txt")).await.unwrap();
571 assert_eq!(data, b"hello world");
572 }
573
574 #[tokio::test]
575 async fn test_read_not_found() {
576 let fs = MemoryFs::new();
577 let result = fs.read(Path::new("nonexistent.txt")).await;
578 assert!(result.is_err());
579 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
580 }
581
582 #[tokio::test]
583 async fn test_nested_directories() {
584 let fs = MemoryFs::new();
585 fs.write(Path::new("a/b/c/file.txt"), b"nested").await.unwrap();
586
587 let entry = fs.stat(Path::new("a")).await.unwrap();
589 assert_eq!(entry.kind, DirEntryKind::Directory);
590
591 let entry = fs.stat(Path::new("a/b")).await.unwrap();
592 assert_eq!(entry.kind, DirEntryKind::Directory);
593
594 let entry = fs.stat(Path::new("a/b/c")).await.unwrap();
595 assert_eq!(entry.kind, DirEntryKind::Directory);
596
597 let data = fs.read(Path::new("a/b/c/file.txt")).await.unwrap();
598 assert_eq!(data, b"nested");
599 }
600
601 #[tokio::test]
602 async fn test_list_directory() {
603 let fs = MemoryFs::new();
604 fs.write(Path::new("a.txt"), b"a").await.unwrap();
605 fs.write(Path::new("b.txt"), b"b").await.unwrap();
606 fs.mkdir(Path::new("subdir")).await.unwrap();
607
608 let entries = fs.list(Path::new("")).await.unwrap();
609 assert_eq!(entries.len(), 3);
610
611 let names: Vec<_> = entries.iter().map(|e| &e.name).collect();
612 assert!(names.contains(&&"a.txt".to_string()));
613 assert!(names.contains(&&"b.txt".to_string()));
614 assert!(names.contains(&&"subdir".to_string()));
615 }
616
617 #[tokio::test]
618 async fn test_mkdir_and_stat() {
619 let fs = MemoryFs::new();
620 fs.mkdir(Path::new("mydir")).await.unwrap();
621
622 let entry = fs.stat(Path::new("mydir")).await.unwrap();
623 assert_eq!(entry.kind, DirEntryKind::Directory);
624 }
625
626 #[tokio::test]
627 async fn test_remove_file() {
628 let fs = MemoryFs::new();
629 fs.write(Path::new("file.txt"), b"data").await.unwrap();
630
631 fs.remove(Path::new("file.txt")).await.unwrap();
632
633 let result = fs.stat(Path::new("file.txt")).await;
634 assert!(result.is_err());
635 }
636
637 #[tokio::test]
638 async fn test_remove_empty_directory() {
639 let fs = MemoryFs::new();
640 fs.mkdir(Path::new("emptydir")).await.unwrap();
641
642 fs.remove(Path::new("emptydir")).await.unwrap();
643
644 let result = fs.stat(Path::new("emptydir")).await;
645 assert!(result.is_err());
646 }
647
648 #[tokio::test]
649 async fn test_remove_non_empty_directory_fails() {
650 let fs = MemoryFs::new();
651 fs.write(Path::new("dir/file.txt"), b"data").await.unwrap();
652
653 let result = fs.remove(Path::new("dir")).await;
654 assert!(result.is_err());
655 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::DirectoryNotEmpty);
656 }
657
658 #[tokio::test]
659 async fn test_path_normalization() {
660 let fs = MemoryFs::new();
661 fs.write(Path::new("/a/b/c.txt"), b"data").await.unwrap();
662
663 let data1 = fs.read(Path::new("a/b/c.txt")).await.unwrap();
665 let data2 = fs.read(Path::new("/a/b/c.txt")).await.unwrap();
666 let data3 = fs.read(Path::new("a/./b/c.txt")).await.unwrap();
667 let data4 = fs.read(Path::new("a/b/../b/c.txt")).await.unwrap();
668
669 assert_eq!(data1, data2);
670 assert_eq!(data2, data3);
671 assert_eq!(data3, data4);
672 }
673
674 #[tokio::test]
675 async fn test_overwrite_file() {
676 let fs = MemoryFs::new();
677 fs.write(Path::new("file.txt"), b"first").await.unwrap();
678 fs.write(Path::new("file.txt"), b"second").await.unwrap();
679
680 let data = fs.read(Path::new("file.txt")).await.unwrap();
681 assert_eq!(data, b"second");
682 }
683
684 #[tokio::test]
685 async fn test_exists() {
686 let fs = MemoryFs::new();
687 assert!(!fs.exists(Path::new("nope.txt")).await);
688
689 fs.write(Path::new("yes.txt"), b"here").await.unwrap();
690 assert!(fs.exists(Path::new("yes.txt")).await);
691 }
692
693 #[tokio::test]
694 async fn test_rename_file() {
695 let fs = MemoryFs::new();
696 fs.write(Path::new("old.txt"), b"content").await.unwrap();
697
698 fs.rename(Path::new("old.txt"), Path::new("new.txt")).await.unwrap();
699
700 let data = fs.read(Path::new("new.txt")).await.unwrap();
702 assert_eq!(data, b"content");
703
704 assert!(!fs.exists(Path::new("old.txt")).await);
706 }
707
708 #[tokio::test]
709 async fn test_rename_directory() {
710 let fs = MemoryFs::new();
711 fs.write(Path::new("dir/a.txt"), b"a").await.unwrap();
712 fs.write(Path::new("dir/b.txt"), b"b").await.unwrap();
713 fs.write(Path::new("dir/sub/c.txt"), b"c").await.unwrap();
714
715 fs.rename(Path::new("dir"), Path::new("renamed")).await.unwrap();
716
717 assert!(fs.exists(Path::new("renamed")).await);
719 assert!(fs.exists(Path::new("renamed/a.txt")).await);
720 assert!(fs.exists(Path::new("renamed/b.txt")).await);
721 assert!(fs.exists(Path::new("renamed/sub/c.txt")).await);
722
723 assert!(!fs.exists(Path::new("dir")).await);
725 assert!(!fs.exists(Path::new("dir/a.txt")).await);
726
727 let data = fs.read(Path::new("renamed/a.txt")).await.unwrap();
729 assert_eq!(data, b"a");
730 }
731
732 #[tokio::test]
733 async fn test_rename_not_found() {
734 let fs = MemoryFs::new();
735 let result = fs.rename(Path::new("nonexistent"), Path::new("dest")).await;
736 assert!(result.is_err());
737 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
738 }
739
740 #[tokio::test]
743 async fn test_symlink_create_and_read_link() {
744 let fs = MemoryFs::new();
745 fs.write(Path::new("target.txt"), b"content").await.unwrap();
746 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
747
748 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
750 assert_eq!(target, Path::new("target.txt"));
751 }
752
753 #[tokio::test]
754 async fn test_symlink_read_follows_link() {
755 let fs = MemoryFs::new();
756 fs.write(Path::new("target.txt"), b"hello from target").await.unwrap();
757 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
758
759 let data = fs.read(Path::new("link.txt")).await.unwrap();
761 assert_eq!(data, b"hello from target");
762 }
763
764 #[tokio::test]
765 async fn test_symlink_stat_follows_link() {
766 let fs = MemoryFs::new();
767 fs.write(Path::new("target.txt"), b"12345").await.unwrap();
768 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
769
770 let entry = fs.stat(Path::new("link.txt")).await.unwrap();
772 assert_eq!(entry.kind, DirEntryKind::File);
773 assert_eq!(entry.size, 5);
774 }
775
776 #[tokio::test]
777 async fn test_symlink_lstat_returns_symlink_info() {
778 let fs = MemoryFs::new();
779 fs.write(Path::new("target.txt"), b"content").await.unwrap();
780 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
781
782 let entry = fs.lstat(Path::new("link.txt")).await.unwrap();
784 assert_eq!(entry.kind, DirEntryKind::Symlink);
785 }
786
787 #[tokio::test]
788 async fn test_symlink_in_list() {
789 let fs = MemoryFs::new();
790 fs.write(Path::new("file.txt"), b"content").await.unwrap();
791 fs.symlink(Path::new("file.txt"), Path::new("link.txt")).await.unwrap();
792 fs.mkdir(Path::new("dir")).await.unwrap();
793
794 let entries = fs.list(Path::new("")).await.unwrap();
795 assert_eq!(entries.len(), 3);
796
797 let link_entry = entries.iter().find(|e| e.name == "link.txt").unwrap();
799 assert_eq!(link_entry.kind, DirEntryKind::Symlink);
800 assert_eq!(link_entry.symlink_target, Some(PathBuf::from("file.txt")));
801 }
802
803 #[tokio::test]
804 async fn test_symlink_broken_link() {
805 let fs = MemoryFs::new();
806 fs.symlink(Path::new("nonexistent.txt"), Path::new("broken.txt")).await.unwrap();
808
809 let target = fs.read_link(Path::new("broken.txt")).await.unwrap();
811 assert_eq!(target, Path::new("nonexistent.txt"));
812
813 let entry = fs.lstat(Path::new("broken.txt")).await.unwrap();
815 assert_eq!(entry.kind, DirEntryKind::Symlink);
816
817 let result = fs.stat(Path::new("broken.txt")).await;
819 assert!(result.is_err());
820 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
821
822 let result = fs.read(Path::new("broken.txt")).await;
824 assert!(result.is_err());
825 }
826
827 #[tokio::test]
828 async fn test_symlink_read_link_on_non_symlink_fails() {
829 let fs = MemoryFs::new();
830 fs.write(Path::new("file.txt"), b"content").await.unwrap();
831
832 let result = fs.read_link(Path::new("file.txt")).await;
833 assert!(result.is_err());
834 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
835 }
836
837 #[tokio::test]
838 async fn test_symlink_already_exists() {
839 let fs = MemoryFs::new();
840 fs.write(Path::new("existing.txt"), b"content").await.unwrap();
841
842 let result = fs.symlink(Path::new("target"), Path::new("existing.txt")).await;
844 assert!(result.is_err());
845 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::AlreadyExists);
846 }
847
848 #[tokio::test]
851 async fn test_symlink_chain() {
852 let fs = MemoryFs::new();
854 fs.write(Path::new("file.txt"), b"end of chain").await.unwrap();
855 fs.symlink(Path::new("file.txt"), Path::new("c")).await.unwrap();
856 fs.symlink(Path::new("c"), Path::new("b")).await.unwrap();
857 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
858
859 let data = fs.read(Path::new("a")).await.unwrap();
861 assert_eq!(data, b"end of chain");
862
863 let entry = fs.stat(Path::new("a")).await.unwrap();
865 assert_eq!(entry.kind, DirEntryKind::File);
866 }
867
868 #[tokio::test]
869 async fn test_symlink_to_directory() {
870 let fs = MemoryFs::new();
871 fs.mkdir(Path::new("realdir")).await.unwrap();
872 fs.write(Path::new("realdir/file.txt"), b"inside dir").await.unwrap();
873 fs.symlink(Path::new("realdir"), Path::new("linkdir")).await.unwrap();
874
875 let entry = fs.stat(Path::new("linkdir")).await.unwrap();
877 assert_eq!(entry.kind, DirEntryKind::Directory);
878
879 }
882
883 #[tokio::test]
884 async fn test_symlink_relative_path_stored_as_is() {
885 let fs = MemoryFs::new();
886 fs.mkdir(Path::new("subdir")).await.unwrap();
887 fs.write(Path::new("subdir/target.txt"), b"content").await.unwrap();
888
889 fs.symlink(Path::new("../subdir/target.txt"), Path::new("subdir/link.txt")).await.unwrap();
891
892 let target = fs.read_link(Path::new("subdir/link.txt")).await.unwrap();
894 assert_eq!(target.to_string_lossy(), "../subdir/target.txt");
895 }
896
897 #[tokio::test]
898 async fn test_symlink_absolute_path() {
899 let fs = MemoryFs::new();
900 fs.write(Path::new("target.txt"), b"content").await.unwrap();
901
902 fs.symlink(Path::new("/target.txt"), Path::new("link.txt")).await.unwrap();
904
905 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
906 assert_eq!(target.to_string_lossy(), "/target.txt");
907
908 let data = fs.read(Path::new("link.txt")).await.unwrap();
910 assert_eq!(data, b"content");
911 }
912
913 #[tokio::test]
914 async fn test_symlink_remove() {
915 let fs = MemoryFs::new();
916 fs.write(Path::new("target.txt"), b"content").await.unwrap();
917 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
918
919 fs.remove(Path::new("link.txt")).await.unwrap();
921
922 assert!(!fs.exists(Path::new("link.txt")).await);
924
925 assert!(fs.exists(Path::new("target.txt")).await);
927 }
928
929 #[tokio::test]
930 async fn test_symlink_overwrite_target_content() {
931 let fs = MemoryFs::new();
932 fs.write(Path::new("target.txt"), b"original").await.unwrap();
933 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
934
935 fs.write(Path::new("target.txt"), b"modified").await.unwrap();
937
938 let data = fs.read(Path::new("link.txt")).await.unwrap();
940 assert_eq!(data, b"modified");
941 }
942
943 #[tokio::test]
944 async fn test_symlink_empty_name() {
945 let fs = MemoryFs::new();
946 fs.write(Path::new("target.txt"), b"content").await.unwrap();
947
948 fs.symlink(Path::new("./target.txt"), Path::new("link.txt")).await.unwrap();
950
951 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
952 assert_eq!(target.to_string_lossy(), "./target.txt");
953 }
954
955 #[tokio::test]
956 async fn test_symlink_nested_creation() {
957 let fs = MemoryFs::new();
958 fs.symlink(Path::new("target"), Path::new("a/b/c/link")).await.unwrap();
960
961 let entry = fs.stat(Path::new("a/b")).await.unwrap();
963 assert_eq!(entry.kind, DirEntryKind::Directory);
964
965 let entry = fs.lstat(Path::new("a/b/c/link")).await.unwrap();
967 assert_eq!(entry.kind, DirEntryKind::Symlink);
968 }
969
970 #[tokio::test]
971 async fn test_symlink_read_link_not_found() {
972 let fs = MemoryFs::new();
973
974 let result = fs.read_link(Path::new("nonexistent")).await;
975 assert!(result.is_err());
976 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
977 }
978
979 #[tokio::test]
980 async fn test_symlink_read_link_on_directory() {
981 let fs = MemoryFs::new();
982 fs.mkdir(Path::new("dir")).await.unwrap();
983
984 let result = fs.read_link(Path::new("dir")).await;
985 assert!(result.is_err());
986 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
987 }
988
989 #[tokio::test]
990 async fn test_symlink_circular_read_returns_error() {
991 let fs = MemoryFs::new();
993 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
994 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
995
996 let result = fs.read(Path::new("a")).await;
997 assert!(result.is_err());
998 let err = result.unwrap_err();
999 assert!(
1000 err.to_string().contains("symbolic links"),
1001 "expected symlink loop error, got: {}",
1002 err
1003 );
1004 }
1005
1006 #[tokio::test]
1007 async fn test_symlink_circular_stat_returns_error() {
1008 let fs = MemoryFs::new();
1009 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
1010 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
1011
1012 let result = fs.stat(Path::new("a")).await;
1013 assert!(result.is_err());
1014 let err = result.unwrap_err();
1015 assert!(
1016 err.to_string().contains("symbolic links"),
1017 "expected symlink loop error, got: {}",
1018 err
1019 );
1020 }
1021
1022 #[tokio::test]
1023 async fn test_rename_into_self_errors() {
1024 let fs = MemoryFs::new();
1025 fs.mkdir(Path::new("a")).await.unwrap();
1026
1027 let result = fs.rename(Path::new("a"), Path::new("a/b")).await;
1028 assert!(result.is_err());
1029 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
1030 }
1031
1032 #[tokio::test]
1033 async fn test_rename_identity_noop() {
1034 let fs = MemoryFs::new();
1035 fs.write(Path::new("a"), b"data").await.unwrap();
1036
1037 fs.rename(Path::new("a"), Path::new("a")).await.unwrap();
1039
1040 let data = fs.read(Path::new("a")).await.unwrap();
1042 assert_eq!(data, b"data");
1043 }
1044
1045 #[tokio::test]
1046 async fn test_ensure_parents_rejects_file_as_dir() {
1047 let fs = MemoryFs::new();
1048 fs.write(Path::new("a"), b"I am a file").await.unwrap();
1050
1051 let result = fs.write(Path::new("a/b"), b"child").await;
1053 assert!(result.is_err());
1054 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotADirectory);
1055 }
1056}