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 entries.entry(current.clone()).or_insert(Entry::Directory {
187 modified: SystemTime::now(),
188 });
189 }
190 }
191 Ok(())
192 }
193}
194
195#[async_trait]
196impl Filesystem for MemoryFs {
197 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
198 self.read_inner(path, 0).await
199 }
200
201 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
202 let normalized = Self::normalize(path);
203
204 self.ensure_parents(&normalized).await?;
206
207 let mut entries = self.entries.write().await;
208
209 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
211 return Err(io::Error::new(
212 io::ErrorKind::IsADirectory,
213 format!("is a directory: {}", path.display()),
214 ));
215 }
216
217 entries.insert(
218 normalized,
219 Entry::File {
220 data: data.to_vec(),
221 modified: SystemTime::now(),
222 },
223 );
224 Ok(())
225 }
226
227 async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
228 let normalized = Self::normalize(path);
229 let entries = self.entries.read().await;
230
231 match entries.get(&normalized) {
233 Some(Entry::Directory { .. }) => {}
234 Some(Entry::File { .. }) => {
235 return Err(io::Error::new(
236 io::ErrorKind::NotADirectory,
237 format!("not a directory: {}", path.display()),
238 ))
239 }
240 Some(Entry::Symlink { .. }) => {
241 return Err(io::Error::new(
242 io::ErrorKind::NotADirectory,
243 format!("not a directory: {}", path.display()),
244 ))
245 }
246 None if normalized.as_os_str().is_empty() => {
247 }
249 None => {
250 return Err(io::Error::new(
251 io::ErrorKind::NotFound,
252 format!("not found: {}", path.display()),
253 ))
254 }
255 }
256
257 let prefix = if normalized.as_os_str().is_empty() {
259 PathBuf::new()
260 } else {
261 normalized.clone()
262 };
263
264 let mut result = Vec::new();
265 for (entry_path, entry) in entries.iter() {
266 if let Some(parent) = entry_path.parent()
267 && parent == prefix && entry_path != &normalized
268 && let Some(name) = entry_path.file_name() {
269 let (kind, size, modified, symlink_target) = match entry {
270 Entry::File { data, modified } => (DirEntryKind::File, data.len() as u64, Some(*modified), None),
271 Entry::Directory { modified } => (DirEntryKind::Directory, 0, Some(*modified), None),
272 Entry::Symlink { target, modified } => (DirEntryKind::Symlink, 0, Some(*modified), Some(target.clone())),
273 };
274 result.push(DirEntry {
275 name: name.to_string_lossy().into_owned(),
276 kind,
277 size,
278 modified,
279 permissions: None,
280 symlink_target,
281 });
282 }
283 }
284
285 result.sort_by(|a, b| a.name.cmp(&b.name));
287 Ok(result)
288 }
289
290 async fn stat(&self, path: &Path) -> io::Result<DirEntry> {
291 let mut entry = self.stat_inner(path, 0).await?;
292 let normalized = Self::normalize(path);
294 entry.name = normalized
295 .file_name()
296 .map(|n| n.to_string_lossy().into_owned())
297 .unwrap_or_else(|| "/".to_string());
298 Ok(entry)
299 }
300
301 async fn lstat(&self, path: &Path) -> io::Result<DirEntry> {
302 let normalized = Self::normalize(path);
303
304 let name = normalized
305 .file_name()
306 .map(|n| n.to_string_lossy().into_owned())
307 .unwrap_or_else(|| "/".to_string());
308
309 let entries = self.entries.read().await;
310
311 if normalized.as_os_str().is_empty() {
313 return Ok(DirEntry {
314 name,
315 kind: DirEntryKind::Directory,
316 size: 0,
317 modified: Some(SystemTime::now()),
318 permissions: None,
319 symlink_target: None,
320 });
321 }
322
323 match entries.get(&normalized) {
324 Some(Entry::File { data, modified }) => Ok(DirEntry {
325 name,
326 kind: DirEntryKind::File,
327 size: data.len() as u64,
328 modified: Some(*modified),
329 permissions: None,
330 symlink_target: None,
331 }),
332 Some(Entry::Directory { modified }) => Ok(DirEntry {
333 name,
334 kind: DirEntryKind::Directory,
335 size: 0,
336 modified: Some(*modified),
337 permissions: None,
338 symlink_target: None,
339 }),
340 Some(Entry::Symlink { target, modified }) => Ok(DirEntry {
341 name,
342 kind: DirEntryKind::Symlink,
343 size: 0,
344 modified: Some(*modified),
345 permissions: None,
346 symlink_target: Some(target.clone()),
347 }),
348 None => Err(io::Error::new(
349 io::ErrorKind::NotFound,
350 format!("not found: {}", path.display()),
351 )),
352 }
353 }
354
355 async fn read_link(&self, path: &Path) -> io::Result<PathBuf> {
356 let normalized = Self::normalize(path);
357 let entries = self.entries.read().await;
358
359 match entries.get(&normalized) {
360 Some(Entry::Symlink { target, .. }) => Ok(target.clone()),
361 Some(_) => Err(io::Error::new(
362 io::ErrorKind::InvalidInput,
363 format!("not a symbolic link: {}", path.display()),
364 )),
365 None => Err(io::Error::new(
366 io::ErrorKind::NotFound,
367 format!("not found: {}", path.display()),
368 )),
369 }
370 }
371
372 async fn symlink(&self, target: &Path, link: &Path) -> io::Result<()> {
373 let normalized = Self::normalize(link);
374
375 self.ensure_parents(&normalized).await?;
377
378 let mut entries = self.entries.write().await;
379
380 if entries.contains_key(&normalized) {
382 return Err(io::Error::new(
383 io::ErrorKind::AlreadyExists,
384 format!("file exists: {}", link.display()),
385 ));
386 }
387
388 entries.insert(
389 normalized,
390 Entry::Symlink {
391 target: target.to_path_buf(),
392 modified: SystemTime::now(),
393 },
394 );
395 Ok(())
396 }
397
398 async fn mkdir(&self, path: &Path) -> io::Result<()> {
399 let normalized = Self::normalize(path);
400
401 self.ensure_parents(&normalized).await?;
403
404 let mut entries = self.entries.write().await;
405
406 if let Some(existing) = entries.get(&normalized) {
408 return match existing {
409 Entry::Directory { .. } => Ok(()), Entry::File { .. } | Entry::Symlink { .. } => Err(io::Error::new(
411 io::ErrorKind::AlreadyExists,
412 format!("file exists: {}", path.display()),
413 )),
414 };
415 }
416
417 entries.insert(
418 normalized,
419 Entry::Directory {
420 modified: SystemTime::now(),
421 },
422 );
423 Ok(())
424 }
425
426 async fn remove(&self, path: &Path) -> io::Result<()> {
427 let normalized = Self::normalize(path);
428
429 if normalized.as_os_str().is_empty() {
430 return Err(io::Error::new(
431 io::ErrorKind::PermissionDenied,
432 "cannot remove root directory",
433 ));
434 }
435
436 let mut entries = self.entries.write().await;
437
438 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
440 let has_children = entries.keys().any(|k| {
442 k.parent() == Some(&normalized) && k != &normalized
443 });
444 if has_children {
445 return Err(io::Error::new(
446 io::ErrorKind::DirectoryNotEmpty,
447 format!("directory not empty: {}", path.display()),
448 ));
449 }
450 }
451
452 entries.remove(&normalized).ok_or_else(|| {
453 io::Error::new(
454 io::ErrorKind::NotFound,
455 format!("not found: {}", path.display()),
456 )
457 })?;
458 Ok(())
459 }
460
461 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
462 let from_normalized = Self::normalize(from);
463 let to_normalized = Self::normalize(to);
464
465 if from_normalized.as_os_str().is_empty() {
466 return Err(io::Error::new(
467 io::ErrorKind::PermissionDenied,
468 "cannot rename root directory",
469 ));
470 }
471
472 drop(self.ensure_parents(&to_normalized).await);
474
475 let mut entries = self.entries.write().await;
476
477 let entry = entries.remove(&from_normalized).ok_or_else(|| {
479 io::Error::new(
480 io::ErrorKind::NotFound,
481 format!("not found: {}", from.display()),
482 )
483 })?;
484
485 if let Some(existing) = entries.get(&to_normalized) {
487 match (&entry, existing) {
488 (Entry::File { .. }, Entry::Directory { .. }) => {
489 entries.insert(from_normalized, entry);
491 return Err(io::Error::new(
492 io::ErrorKind::IsADirectory,
493 format!("destination is a directory: {}", to.display()),
494 ));
495 }
496 (Entry::Directory { .. }, Entry::File { .. }) => {
497 entries.insert(from_normalized, entry);
498 return Err(io::Error::new(
499 io::ErrorKind::NotADirectory,
500 format!("destination is not a directory: {}", to.display()),
501 ));
502 }
503 _ => {}
504 }
505 }
506
507 if matches!(entry, Entry::Directory { .. }) {
509 let children_to_rename: Vec<(PathBuf, Entry)> = entries
511 .iter()
512 .filter(|(k, _)| k.starts_with(&from_normalized) && *k != &from_normalized)
513 .map(|(k, v)| (k.clone(), v.clone()))
514 .collect();
515
516 for (old_path, child_entry) in children_to_rename {
518 entries.remove(&old_path);
519 let Ok(relative) = old_path.strip_prefix(&from_normalized) else {
520 continue;
521 };
522 let new_path = to_normalized.join(relative);
523 entries.insert(new_path, child_entry);
524 }
525 }
526
527 entries.insert(to_normalized, entry);
529 Ok(())
530 }
531
532 fn read_only(&self) -> bool {
533 false
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540
541 #[tokio::test]
542 async fn test_write_and_read() {
543 let fs = MemoryFs::new();
544 fs.write(Path::new("test.txt"), b"hello world").await.unwrap();
545 let data = fs.read(Path::new("test.txt")).await.unwrap();
546 assert_eq!(data, b"hello world");
547 }
548
549 #[tokio::test]
550 async fn test_read_not_found() {
551 let fs = MemoryFs::new();
552 let result = fs.read(Path::new("nonexistent.txt")).await;
553 assert!(result.is_err());
554 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
555 }
556
557 #[tokio::test]
558 async fn test_nested_directories() {
559 let fs = MemoryFs::new();
560 fs.write(Path::new("a/b/c/file.txt"), b"nested").await.unwrap();
561
562 let entry = fs.stat(Path::new("a")).await.unwrap();
564 assert_eq!(entry.kind, DirEntryKind::Directory);
565
566 let entry = fs.stat(Path::new("a/b")).await.unwrap();
567 assert_eq!(entry.kind, DirEntryKind::Directory);
568
569 let entry = fs.stat(Path::new("a/b/c")).await.unwrap();
570 assert_eq!(entry.kind, DirEntryKind::Directory);
571
572 let data = fs.read(Path::new("a/b/c/file.txt")).await.unwrap();
573 assert_eq!(data, b"nested");
574 }
575
576 #[tokio::test]
577 async fn test_list_directory() {
578 let fs = MemoryFs::new();
579 fs.write(Path::new("a.txt"), b"a").await.unwrap();
580 fs.write(Path::new("b.txt"), b"b").await.unwrap();
581 fs.mkdir(Path::new("subdir")).await.unwrap();
582
583 let entries = fs.list(Path::new("")).await.unwrap();
584 assert_eq!(entries.len(), 3);
585
586 let names: Vec<_> = entries.iter().map(|e| &e.name).collect();
587 assert!(names.contains(&&"a.txt".to_string()));
588 assert!(names.contains(&&"b.txt".to_string()));
589 assert!(names.contains(&&"subdir".to_string()));
590 }
591
592 #[tokio::test]
593 async fn test_mkdir_and_stat() {
594 let fs = MemoryFs::new();
595 fs.mkdir(Path::new("mydir")).await.unwrap();
596
597 let entry = fs.stat(Path::new("mydir")).await.unwrap();
598 assert_eq!(entry.kind, DirEntryKind::Directory);
599 }
600
601 #[tokio::test]
602 async fn test_remove_file() {
603 let fs = MemoryFs::new();
604 fs.write(Path::new("file.txt"), b"data").await.unwrap();
605
606 fs.remove(Path::new("file.txt")).await.unwrap();
607
608 let result = fs.stat(Path::new("file.txt")).await;
609 assert!(result.is_err());
610 }
611
612 #[tokio::test]
613 async fn test_remove_empty_directory() {
614 let fs = MemoryFs::new();
615 fs.mkdir(Path::new("emptydir")).await.unwrap();
616
617 fs.remove(Path::new("emptydir")).await.unwrap();
618
619 let result = fs.stat(Path::new("emptydir")).await;
620 assert!(result.is_err());
621 }
622
623 #[tokio::test]
624 async fn test_remove_non_empty_directory_fails() {
625 let fs = MemoryFs::new();
626 fs.write(Path::new("dir/file.txt"), b"data").await.unwrap();
627
628 let result = fs.remove(Path::new("dir")).await;
629 assert!(result.is_err());
630 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::DirectoryNotEmpty);
631 }
632
633 #[tokio::test]
634 async fn test_path_normalization() {
635 let fs = MemoryFs::new();
636 fs.write(Path::new("/a/b/c.txt"), b"data").await.unwrap();
637
638 let data1 = fs.read(Path::new("a/b/c.txt")).await.unwrap();
640 let data2 = fs.read(Path::new("/a/b/c.txt")).await.unwrap();
641 let data3 = fs.read(Path::new("a/./b/c.txt")).await.unwrap();
642 let data4 = fs.read(Path::new("a/b/../b/c.txt")).await.unwrap();
643
644 assert_eq!(data1, data2);
645 assert_eq!(data2, data3);
646 assert_eq!(data3, data4);
647 }
648
649 #[tokio::test]
650 async fn test_overwrite_file() {
651 let fs = MemoryFs::new();
652 fs.write(Path::new("file.txt"), b"first").await.unwrap();
653 fs.write(Path::new("file.txt"), b"second").await.unwrap();
654
655 let data = fs.read(Path::new("file.txt")).await.unwrap();
656 assert_eq!(data, b"second");
657 }
658
659 #[tokio::test]
660 async fn test_exists() {
661 let fs = MemoryFs::new();
662 assert!(!fs.exists(Path::new("nope.txt")).await);
663
664 fs.write(Path::new("yes.txt"), b"here").await.unwrap();
665 assert!(fs.exists(Path::new("yes.txt")).await);
666 }
667
668 #[tokio::test]
669 async fn test_rename_file() {
670 let fs = MemoryFs::new();
671 fs.write(Path::new("old.txt"), b"content").await.unwrap();
672
673 fs.rename(Path::new("old.txt"), Path::new("new.txt")).await.unwrap();
674
675 let data = fs.read(Path::new("new.txt")).await.unwrap();
677 assert_eq!(data, b"content");
678
679 assert!(!fs.exists(Path::new("old.txt")).await);
681 }
682
683 #[tokio::test]
684 async fn test_rename_directory() {
685 let fs = MemoryFs::new();
686 fs.write(Path::new("dir/a.txt"), b"a").await.unwrap();
687 fs.write(Path::new("dir/b.txt"), b"b").await.unwrap();
688 fs.write(Path::new("dir/sub/c.txt"), b"c").await.unwrap();
689
690 fs.rename(Path::new("dir"), Path::new("renamed")).await.unwrap();
691
692 assert!(fs.exists(Path::new("renamed")).await);
694 assert!(fs.exists(Path::new("renamed/a.txt")).await);
695 assert!(fs.exists(Path::new("renamed/b.txt")).await);
696 assert!(fs.exists(Path::new("renamed/sub/c.txt")).await);
697
698 assert!(!fs.exists(Path::new("dir")).await);
700 assert!(!fs.exists(Path::new("dir/a.txt")).await);
701
702 let data = fs.read(Path::new("renamed/a.txt")).await.unwrap();
704 assert_eq!(data, b"a");
705 }
706
707 #[tokio::test]
708 async fn test_rename_not_found() {
709 let fs = MemoryFs::new();
710 let result = fs.rename(Path::new("nonexistent"), Path::new("dest")).await;
711 assert!(result.is_err());
712 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
713 }
714
715 #[tokio::test]
718 async fn test_symlink_create_and_read_link() {
719 let fs = MemoryFs::new();
720 fs.write(Path::new("target.txt"), b"content").await.unwrap();
721 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
722
723 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
725 assert_eq!(target, Path::new("target.txt"));
726 }
727
728 #[tokio::test]
729 async fn test_symlink_read_follows_link() {
730 let fs = MemoryFs::new();
731 fs.write(Path::new("target.txt"), b"hello from target").await.unwrap();
732 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
733
734 let data = fs.read(Path::new("link.txt")).await.unwrap();
736 assert_eq!(data, b"hello from target");
737 }
738
739 #[tokio::test]
740 async fn test_symlink_stat_follows_link() {
741 let fs = MemoryFs::new();
742 fs.write(Path::new("target.txt"), b"12345").await.unwrap();
743 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
744
745 let entry = fs.stat(Path::new("link.txt")).await.unwrap();
747 assert_eq!(entry.kind, DirEntryKind::File);
748 assert_eq!(entry.size, 5);
749 }
750
751 #[tokio::test]
752 async fn test_symlink_lstat_returns_symlink_info() {
753 let fs = MemoryFs::new();
754 fs.write(Path::new("target.txt"), b"content").await.unwrap();
755 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
756
757 let entry = fs.lstat(Path::new("link.txt")).await.unwrap();
759 assert_eq!(entry.kind, DirEntryKind::Symlink);
760 }
761
762 #[tokio::test]
763 async fn test_symlink_in_list() {
764 let fs = MemoryFs::new();
765 fs.write(Path::new("file.txt"), b"content").await.unwrap();
766 fs.symlink(Path::new("file.txt"), Path::new("link.txt")).await.unwrap();
767 fs.mkdir(Path::new("dir")).await.unwrap();
768
769 let entries = fs.list(Path::new("")).await.unwrap();
770 assert_eq!(entries.len(), 3);
771
772 let link_entry = entries.iter().find(|e| e.name == "link.txt").unwrap();
774 assert_eq!(link_entry.kind, DirEntryKind::Symlink);
775 assert_eq!(link_entry.symlink_target, Some(PathBuf::from("file.txt")));
776 }
777
778 #[tokio::test]
779 async fn test_symlink_broken_link() {
780 let fs = MemoryFs::new();
781 fs.symlink(Path::new("nonexistent.txt"), Path::new("broken.txt")).await.unwrap();
783
784 let target = fs.read_link(Path::new("broken.txt")).await.unwrap();
786 assert_eq!(target, Path::new("nonexistent.txt"));
787
788 let entry = fs.lstat(Path::new("broken.txt")).await.unwrap();
790 assert_eq!(entry.kind, DirEntryKind::Symlink);
791
792 let result = fs.stat(Path::new("broken.txt")).await;
794 assert!(result.is_err());
795 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
796
797 let result = fs.read(Path::new("broken.txt")).await;
799 assert!(result.is_err());
800 }
801
802 #[tokio::test]
803 async fn test_symlink_read_link_on_non_symlink_fails() {
804 let fs = MemoryFs::new();
805 fs.write(Path::new("file.txt"), b"content").await.unwrap();
806
807 let result = fs.read_link(Path::new("file.txt")).await;
808 assert!(result.is_err());
809 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
810 }
811
812 #[tokio::test]
813 async fn test_symlink_already_exists() {
814 let fs = MemoryFs::new();
815 fs.write(Path::new("existing.txt"), b"content").await.unwrap();
816
817 let result = fs.symlink(Path::new("target"), Path::new("existing.txt")).await;
819 assert!(result.is_err());
820 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::AlreadyExists);
821 }
822
823 #[tokio::test]
826 async fn test_symlink_chain() {
827 let fs = MemoryFs::new();
829 fs.write(Path::new("file.txt"), b"end of chain").await.unwrap();
830 fs.symlink(Path::new("file.txt"), Path::new("c")).await.unwrap();
831 fs.symlink(Path::new("c"), Path::new("b")).await.unwrap();
832 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
833
834 let data = fs.read(Path::new("a")).await.unwrap();
836 assert_eq!(data, b"end of chain");
837
838 let entry = fs.stat(Path::new("a")).await.unwrap();
840 assert_eq!(entry.kind, DirEntryKind::File);
841 }
842
843 #[tokio::test]
844 async fn test_symlink_to_directory() {
845 let fs = MemoryFs::new();
846 fs.mkdir(Path::new("realdir")).await.unwrap();
847 fs.write(Path::new("realdir/file.txt"), b"inside dir").await.unwrap();
848 fs.symlink(Path::new("realdir"), Path::new("linkdir")).await.unwrap();
849
850 let entry = fs.stat(Path::new("linkdir")).await.unwrap();
852 assert_eq!(entry.kind, DirEntryKind::Directory);
853
854 }
857
858 #[tokio::test]
859 async fn test_symlink_relative_path_stored_as_is() {
860 let fs = MemoryFs::new();
861 fs.mkdir(Path::new("subdir")).await.unwrap();
862 fs.write(Path::new("subdir/target.txt"), b"content").await.unwrap();
863
864 fs.symlink(Path::new("../subdir/target.txt"), Path::new("subdir/link.txt")).await.unwrap();
866
867 let target = fs.read_link(Path::new("subdir/link.txt")).await.unwrap();
869 assert_eq!(target.to_string_lossy(), "../subdir/target.txt");
870 }
871
872 #[tokio::test]
873 async fn test_symlink_absolute_path() {
874 let fs = MemoryFs::new();
875 fs.write(Path::new("target.txt"), b"content").await.unwrap();
876
877 fs.symlink(Path::new("/target.txt"), Path::new("link.txt")).await.unwrap();
879
880 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
881 assert_eq!(target.to_string_lossy(), "/target.txt");
882
883 let data = fs.read(Path::new("link.txt")).await.unwrap();
885 assert_eq!(data, b"content");
886 }
887
888 #[tokio::test]
889 async fn test_symlink_remove() {
890 let fs = MemoryFs::new();
891 fs.write(Path::new("target.txt"), b"content").await.unwrap();
892 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
893
894 fs.remove(Path::new("link.txt")).await.unwrap();
896
897 assert!(!fs.exists(Path::new("link.txt")).await);
899
900 assert!(fs.exists(Path::new("target.txt")).await);
902 }
903
904 #[tokio::test]
905 async fn test_symlink_overwrite_target_content() {
906 let fs = MemoryFs::new();
907 fs.write(Path::new("target.txt"), b"original").await.unwrap();
908 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
909
910 fs.write(Path::new("target.txt"), b"modified").await.unwrap();
912
913 let data = fs.read(Path::new("link.txt")).await.unwrap();
915 assert_eq!(data, b"modified");
916 }
917
918 #[tokio::test]
919 async fn test_symlink_empty_name() {
920 let fs = MemoryFs::new();
921 fs.write(Path::new("target.txt"), b"content").await.unwrap();
922
923 fs.symlink(Path::new("./target.txt"), Path::new("link.txt")).await.unwrap();
925
926 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
927 assert_eq!(target.to_string_lossy(), "./target.txt");
928 }
929
930 #[tokio::test]
931 async fn test_symlink_nested_creation() {
932 let fs = MemoryFs::new();
933 fs.symlink(Path::new("target"), Path::new("a/b/c/link")).await.unwrap();
935
936 let entry = fs.stat(Path::new("a/b")).await.unwrap();
938 assert_eq!(entry.kind, DirEntryKind::Directory);
939
940 let entry = fs.lstat(Path::new("a/b/c/link")).await.unwrap();
942 assert_eq!(entry.kind, DirEntryKind::Symlink);
943 }
944
945 #[tokio::test]
946 async fn test_symlink_read_link_not_found() {
947 let fs = MemoryFs::new();
948
949 let result = fs.read_link(Path::new("nonexistent")).await;
950 assert!(result.is_err());
951 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
952 }
953
954 #[tokio::test]
955 async fn test_symlink_read_link_on_directory() {
956 let fs = MemoryFs::new();
957 fs.mkdir(Path::new("dir")).await.unwrap();
958
959 let result = fs.read_link(Path::new("dir")).await;
960 assert!(result.is_err());
961 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
962 }
963
964 #[tokio::test]
965 async fn test_symlink_circular_read_returns_error() {
966 let fs = MemoryFs::new();
968 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
969 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
970
971 let result = fs.read(Path::new("a")).await;
972 assert!(result.is_err());
973 let err = result.unwrap_err();
974 assert!(
975 err.to_string().contains("symbolic links"),
976 "expected symlink loop error, got: {}",
977 err
978 );
979 }
980
981 #[tokio::test]
982 async fn test_symlink_circular_stat_returns_error() {
983 let fs = MemoryFs::new();
984 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
985 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
986
987 let result = fs.stat(Path::new("a")).await;
988 assert!(result.is_err());
989 let err = result.unwrap_err();
990 assert!(
991 err.to_string().contains("symbolic links"),
992 "expected symlink loop error, got: {}",
993 err
994 );
995 }
996}