1use super::traits::{DirEntry, EntryType, Filesystem, Metadata};
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<Metadata>> + Send + '_>> {
106 let path = path.to_path_buf();
107 Box::pin(async move {
108 if depth > Self::MAX_SYMLINK_DEPTH {
109 return Err(io::Error::other(
110 "too many levels of symbolic links",
111 ));
112 }
113 let normalized = Self::normalize(&path);
114
115 if normalized.as_os_str().is_empty() {
116 return Ok(Metadata {
117 is_dir: true,
118 is_file: false,
119 is_symlink: false,
120 size: 0,
121 modified: Some(SystemTime::now()),
122 });
123 }
124
125 let entry_info: Option<(Metadata, Option<PathBuf>)> = {
126 let entries = self.entries.read().await;
127 match entries.get(&normalized) {
128 Some(Entry::File { data, modified }) => Some((
129 Metadata {
130 is_dir: false,
131 is_file: true,
132 is_symlink: false,
133 size: data.len() as u64,
134 modified: Some(*modified),
135 },
136 None,
137 )),
138 Some(Entry::Directory { modified }) => Some((
139 Metadata {
140 is_dir: true,
141 is_file: false,
142 is_symlink: false,
143 size: 0,
144 modified: Some(*modified),
145 },
146 None,
147 )),
148 Some(Entry::Symlink { target, .. }) => Some((
149 Metadata {
150 is_dir: false,
151 is_file: false,
152 is_symlink: false,
153 size: 0,
154 modified: None,
155 },
156 Some(target.clone()),
157 )),
158 None => None,
159 }
160 };
161
162 match entry_info {
163 Some((meta, None)) => Ok(meta),
164 Some((_, Some(target))) => self.stat_inner(&target, depth + 1).await,
165 None => Err(io::Error::new(
166 io::ErrorKind::NotFound,
167 format!("not found: {}", path.display()),
168 )),
169 }
170 })
171 }
172
173 async fn ensure_parents(&self, path: &Path) -> io::Result<()> {
175 let mut entries = self.entries.write().await;
176
177 let mut current = PathBuf::new();
178 for component in path.parent().into_iter().flat_map(|p| p.components()) {
179 if let std::path::Component::Normal(s) = component {
180 current.push(s);
181 entries.entry(current.clone()).or_insert(Entry::Directory {
182 modified: SystemTime::now(),
183 });
184 }
185 }
186 Ok(())
187 }
188}
189
190#[async_trait]
191impl Filesystem for MemoryFs {
192 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
193 self.read_inner(path, 0).await
194 }
195
196 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
197 let normalized = Self::normalize(path);
198
199 self.ensure_parents(&normalized).await?;
201
202 let mut entries = self.entries.write().await;
203
204 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
206 return Err(io::Error::new(
207 io::ErrorKind::IsADirectory,
208 format!("is a directory: {}", path.display()),
209 ));
210 }
211
212 entries.insert(
213 normalized,
214 Entry::File {
215 data: data.to_vec(),
216 modified: SystemTime::now(),
217 },
218 );
219 Ok(())
220 }
221
222 async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
223 let normalized = Self::normalize(path);
224 let entries = self.entries.read().await;
225
226 match entries.get(&normalized) {
228 Some(Entry::Directory { .. }) => {}
229 Some(Entry::File { .. }) => {
230 return Err(io::Error::new(
231 io::ErrorKind::NotADirectory,
232 format!("not a directory: {}", path.display()),
233 ))
234 }
235 Some(Entry::Symlink { .. }) => {
236 return Err(io::Error::new(
237 io::ErrorKind::NotADirectory,
238 format!("not a directory: {}", path.display()),
239 ))
240 }
241 None if normalized.as_os_str().is_empty() => {
242 }
244 None => {
245 return Err(io::Error::new(
246 io::ErrorKind::NotFound,
247 format!("not found: {}", path.display()),
248 ))
249 }
250 }
251
252 let prefix = if normalized.as_os_str().is_empty() {
254 PathBuf::new()
255 } else {
256 normalized.clone()
257 };
258
259 let mut result = Vec::new();
260 for (entry_path, entry) in entries.iter() {
261 if let Some(parent) = entry_path.parent()
262 && parent == prefix && entry_path != &normalized
263 && let Some(name) = entry_path.file_name() {
264 let (entry_type, size, symlink_target) = match entry {
265 Entry::File { data, .. } => (EntryType::File, data.len() as u64, None),
266 Entry::Directory { .. } => (EntryType::Directory, 0, None),
267 Entry::Symlink { target, .. } => (EntryType::Symlink, 0, Some(target.clone())),
268 };
269 result.push(DirEntry {
270 name: name.to_string_lossy().into_owned(),
271 entry_type,
272 size,
273 symlink_target,
274 });
275 }
276 }
277
278 result.sort_by(|a, b| a.name.cmp(&b.name));
280 Ok(result)
281 }
282
283 async fn stat(&self, path: &Path) -> io::Result<Metadata> {
284 self.stat_inner(path, 0).await
285 }
286
287 async fn lstat(&self, path: &Path) -> io::Result<Metadata> {
288 let normalized = Self::normalize(path);
289 let entries = self.entries.read().await;
290
291 if normalized.as_os_str().is_empty() {
293 return Ok(Metadata {
294 is_dir: true,
295 is_file: false,
296 is_symlink: false,
297 size: 0,
298 modified: Some(SystemTime::now()),
299 });
300 }
301
302 match entries.get(&normalized) {
303 Some(Entry::File { data, modified }) => Ok(Metadata {
304 is_dir: false,
305 is_file: true,
306 is_symlink: false,
307 size: data.len() as u64,
308 modified: Some(*modified),
309 }),
310 Some(Entry::Directory { modified }) => Ok(Metadata {
311 is_dir: true,
312 is_file: false,
313 is_symlink: false,
314 size: 0,
315 modified: Some(*modified),
316 }),
317 Some(Entry::Symlink { modified, .. }) => Ok(Metadata {
318 is_dir: false,
319 is_file: false,
320 is_symlink: true,
321 size: 0,
322 modified: Some(*modified),
323 }),
324 None => Err(io::Error::new(
325 io::ErrorKind::NotFound,
326 format!("not found: {}", path.display()),
327 )),
328 }
329 }
330
331 async fn read_link(&self, path: &Path) -> io::Result<PathBuf> {
332 let normalized = Self::normalize(path);
333 let entries = self.entries.read().await;
334
335 match entries.get(&normalized) {
336 Some(Entry::Symlink { target, .. }) => Ok(target.clone()),
337 Some(_) => Err(io::Error::new(
338 io::ErrorKind::InvalidInput,
339 format!("not a symbolic link: {}", path.display()),
340 )),
341 None => Err(io::Error::new(
342 io::ErrorKind::NotFound,
343 format!("not found: {}", path.display()),
344 )),
345 }
346 }
347
348 async fn symlink(&self, target: &Path, link: &Path) -> io::Result<()> {
349 let normalized = Self::normalize(link);
350
351 self.ensure_parents(&normalized).await?;
353
354 let mut entries = self.entries.write().await;
355
356 if entries.contains_key(&normalized) {
358 return Err(io::Error::new(
359 io::ErrorKind::AlreadyExists,
360 format!("file exists: {}", link.display()),
361 ));
362 }
363
364 entries.insert(
365 normalized,
366 Entry::Symlink {
367 target: target.to_path_buf(),
368 modified: SystemTime::now(),
369 },
370 );
371 Ok(())
372 }
373
374 async fn mkdir(&self, path: &Path) -> io::Result<()> {
375 let normalized = Self::normalize(path);
376
377 self.ensure_parents(&normalized).await?;
379
380 let mut entries = self.entries.write().await;
381
382 if let Some(existing) = entries.get(&normalized) {
384 return match existing {
385 Entry::Directory { .. } => Ok(()), Entry::File { .. } | Entry::Symlink { .. } => Err(io::Error::new(
387 io::ErrorKind::AlreadyExists,
388 format!("file exists: {}", path.display()),
389 )),
390 };
391 }
392
393 entries.insert(
394 normalized,
395 Entry::Directory {
396 modified: SystemTime::now(),
397 },
398 );
399 Ok(())
400 }
401
402 async fn remove(&self, path: &Path) -> io::Result<()> {
403 let normalized = Self::normalize(path);
404
405 if normalized.as_os_str().is_empty() {
406 return Err(io::Error::new(
407 io::ErrorKind::PermissionDenied,
408 "cannot remove root directory",
409 ));
410 }
411
412 let mut entries = self.entries.write().await;
413
414 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
416 let has_children = entries.keys().any(|k| {
418 k.parent() == Some(&normalized) && k != &normalized
419 });
420 if has_children {
421 return Err(io::Error::new(
422 io::ErrorKind::DirectoryNotEmpty,
423 format!("directory not empty: {}", path.display()),
424 ));
425 }
426 }
427
428 entries.remove(&normalized).ok_or_else(|| {
429 io::Error::new(
430 io::ErrorKind::NotFound,
431 format!("not found: {}", path.display()),
432 )
433 })?;
434 Ok(())
435 }
436
437 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
438 let from_normalized = Self::normalize(from);
439 let to_normalized = Self::normalize(to);
440
441 if from_normalized.as_os_str().is_empty() {
442 return Err(io::Error::new(
443 io::ErrorKind::PermissionDenied,
444 "cannot rename root directory",
445 ));
446 }
447
448 drop(self.ensure_parents(&to_normalized).await);
450
451 let mut entries = self.entries.write().await;
452
453 let entry = entries.remove(&from_normalized).ok_or_else(|| {
455 io::Error::new(
456 io::ErrorKind::NotFound,
457 format!("not found: {}", from.display()),
458 )
459 })?;
460
461 if let Some(existing) = entries.get(&to_normalized) {
463 match (&entry, existing) {
464 (Entry::File { .. }, Entry::Directory { .. }) => {
465 entries.insert(from_normalized, entry);
467 return Err(io::Error::new(
468 io::ErrorKind::IsADirectory,
469 format!("destination is a directory: {}", to.display()),
470 ));
471 }
472 (Entry::Directory { .. }, Entry::File { .. }) => {
473 entries.insert(from_normalized, entry);
474 return Err(io::Error::new(
475 io::ErrorKind::NotADirectory,
476 format!("destination is not a directory: {}", to.display()),
477 ));
478 }
479 _ => {}
480 }
481 }
482
483 if matches!(entry, Entry::Directory { .. }) {
485 let children_to_rename: Vec<(PathBuf, Entry)> = entries
487 .iter()
488 .filter(|(k, _)| k.starts_with(&from_normalized) && *k != &from_normalized)
489 .map(|(k, v)| (k.clone(), v.clone()))
490 .collect();
491
492 for (old_path, child_entry) in children_to_rename {
494 entries.remove(&old_path);
495 let Ok(relative) = old_path.strip_prefix(&from_normalized) else {
496 continue;
497 };
498 let new_path = to_normalized.join(relative);
499 entries.insert(new_path, child_entry);
500 }
501 }
502
503 entries.insert(to_normalized, entry);
505 Ok(())
506 }
507
508 fn read_only(&self) -> bool {
509 false
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[tokio::test]
518 async fn test_write_and_read() {
519 let fs = MemoryFs::new();
520 fs.write(Path::new("test.txt"), b"hello world").await.unwrap();
521 let data = fs.read(Path::new("test.txt")).await.unwrap();
522 assert_eq!(data, b"hello world");
523 }
524
525 #[tokio::test]
526 async fn test_read_not_found() {
527 let fs = MemoryFs::new();
528 let result = fs.read(Path::new("nonexistent.txt")).await;
529 assert!(result.is_err());
530 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
531 }
532
533 #[tokio::test]
534 async fn test_nested_directories() {
535 let fs = MemoryFs::new();
536 fs.write(Path::new("a/b/c/file.txt"), b"nested").await.unwrap();
537
538 let meta = fs.stat(Path::new("a")).await.unwrap();
540 assert!(meta.is_dir);
541
542 let meta = fs.stat(Path::new("a/b")).await.unwrap();
543 assert!(meta.is_dir);
544
545 let meta = fs.stat(Path::new("a/b/c")).await.unwrap();
546 assert!(meta.is_dir);
547
548 let data = fs.read(Path::new("a/b/c/file.txt")).await.unwrap();
549 assert_eq!(data, b"nested");
550 }
551
552 #[tokio::test]
553 async fn test_list_directory() {
554 let fs = MemoryFs::new();
555 fs.write(Path::new("a.txt"), b"a").await.unwrap();
556 fs.write(Path::new("b.txt"), b"b").await.unwrap();
557 fs.mkdir(Path::new("subdir")).await.unwrap();
558
559 let entries = fs.list(Path::new("")).await.unwrap();
560 assert_eq!(entries.len(), 3);
561
562 let names: Vec<_> = entries.iter().map(|e| &e.name).collect();
563 assert!(names.contains(&&"a.txt".to_string()));
564 assert!(names.contains(&&"b.txt".to_string()));
565 assert!(names.contains(&&"subdir".to_string()));
566 }
567
568 #[tokio::test]
569 async fn test_mkdir_and_stat() {
570 let fs = MemoryFs::new();
571 fs.mkdir(Path::new("mydir")).await.unwrap();
572
573 let meta = fs.stat(Path::new("mydir")).await.unwrap();
574 assert!(meta.is_dir);
575 assert!(!meta.is_file);
576 }
577
578 #[tokio::test]
579 async fn test_remove_file() {
580 let fs = MemoryFs::new();
581 fs.write(Path::new("file.txt"), b"data").await.unwrap();
582
583 fs.remove(Path::new("file.txt")).await.unwrap();
584
585 let result = fs.stat(Path::new("file.txt")).await;
586 assert!(result.is_err());
587 }
588
589 #[tokio::test]
590 async fn test_remove_empty_directory() {
591 let fs = MemoryFs::new();
592 fs.mkdir(Path::new("emptydir")).await.unwrap();
593
594 fs.remove(Path::new("emptydir")).await.unwrap();
595
596 let result = fs.stat(Path::new("emptydir")).await;
597 assert!(result.is_err());
598 }
599
600 #[tokio::test]
601 async fn test_remove_non_empty_directory_fails() {
602 let fs = MemoryFs::new();
603 fs.write(Path::new("dir/file.txt"), b"data").await.unwrap();
604
605 let result = fs.remove(Path::new("dir")).await;
606 assert!(result.is_err());
607 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::DirectoryNotEmpty);
608 }
609
610 #[tokio::test]
611 async fn test_path_normalization() {
612 let fs = MemoryFs::new();
613 fs.write(Path::new("/a/b/c.txt"), b"data").await.unwrap();
614
615 let data1 = fs.read(Path::new("a/b/c.txt")).await.unwrap();
617 let data2 = fs.read(Path::new("/a/b/c.txt")).await.unwrap();
618 let data3 = fs.read(Path::new("a/./b/c.txt")).await.unwrap();
619 let data4 = fs.read(Path::new("a/b/../b/c.txt")).await.unwrap();
620
621 assert_eq!(data1, data2);
622 assert_eq!(data2, data3);
623 assert_eq!(data3, data4);
624 }
625
626 #[tokio::test]
627 async fn test_overwrite_file() {
628 let fs = MemoryFs::new();
629 fs.write(Path::new("file.txt"), b"first").await.unwrap();
630 fs.write(Path::new("file.txt"), b"second").await.unwrap();
631
632 let data = fs.read(Path::new("file.txt")).await.unwrap();
633 assert_eq!(data, b"second");
634 }
635
636 #[tokio::test]
637 async fn test_exists() {
638 let fs = MemoryFs::new();
639 assert!(!fs.exists(Path::new("nope.txt")).await);
640
641 fs.write(Path::new("yes.txt"), b"here").await.unwrap();
642 assert!(fs.exists(Path::new("yes.txt")).await);
643 }
644
645 #[tokio::test]
646 async fn test_rename_file() {
647 let fs = MemoryFs::new();
648 fs.write(Path::new("old.txt"), b"content").await.unwrap();
649
650 fs.rename(Path::new("old.txt"), Path::new("new.txt")).await.unwrap();
651
652 let data = fs.read(Path::new("new.txt")).await.unwrap();
654 assert_eq!(data, b"content");
655
656 assert!(!fs.exists(Path::new("old.txt")).await);
658 }
659
660 #[tokio::test]
661 async fn test_rename_directory() {
662 let fs = MemoryFs::new();
663 fs.write(Path::new("dir/a.txt"), b"a").await.unwrap();
664 fs.write(Path::new("dir/b.txt"), b"b").await.unwrap();
665 fs.write(Path::new("dir/sub/c.txt"), b"c").await.unwrap();
666
667 fs.rename(Path::new("dir"), Path::new("renamed")).await.unwrap();
668
669 assert!(fs.exists(Path::new("renamed")).await);
671 assert!(fs.exists(Path::new("renamed/a.txt")).await);
672 assert!(fs.exists(Path::new("renamed/b.txt")).await);
673 assert!(fs.exists(Path::new("renamed/sub/c.txt")).await);
674
675 assert!(!fs.exists(Path::new("dir")).await);
677 assert!(!fs.exists(Path::new("dir/a.txt")).await);
678
679 let data = fs.read(Path::new("renamed/a.txt")).await.unwrap();
681 assert_eq!(data, b"a");
682 }
683
684 #[tokio::test]
685 async fn test_rename_not_found() {
686 let fs = MemoryFs::new();
687 let result = fs.rename(Path::new("nonexistent"), Path::new("dest")).await;
688 assert!(result.is_err());
689 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
690 }
691
692 #[tokio::test]
695 async fn test_symlink_create_and_read_link() {
696 let fs = MemoryFs::new();
697 fs.write(Path::new("target.txt"), b"content").await.unwrap();
698 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
699
700 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
702 assert_eq!(target, Path::new("target.txt"));
703 }
704
705 #[tokio::test]
706 async fn test_symlink_read_follows_link() {
707 let fs = MemoryFs::new();
708 fs.write(Path::new("target.txt"), b"hello from target").await.unwrap();
709 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
710
711 let data = fs.read(Path::new("link.txt")).await.unwrap();
713 assert_eq!(data, b"hello from target");
714 }
715
716 #[tokio::test]
717 async fn test_symlink_stat_follows_link() {
718 let fs = MemoryFs::new();
719 fs.write(Path::new("target.txt"), b"12345").await.unwrap();
720 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
721
722 let meta = fs.stat(Path::new("link.txt")).await.unwrap();
724 assert!(meta.is_file);
725 assert!(!meta.is_symlink);
726 assert_eq!(meta.size, 5);
727 }
728
729 #[tokio::test]
730 async fn test_symlink_lstat_returns_symlink_info() {
731 let fs = MemoryFs::new();
732 fs.write(Path::new("target.txt"), b"content").await.unwrap();
733 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
734
735 let meta = fs.lstat(Path::new("link.txt")).await.unwrap();
737 assert!(meta.is_symlink);
738 assert!(!meta.is_file);
739 assert!(!meta.is_dir);
740 }
741
742 #[tokio::test]
743 async fn test_symlink_in_list() {
744 let fs = MemoryFs::new();
745 fs.write(Path::new("file.txt"), b"content").await.unwrap();
746 fs.symlink(Path::new("file.txt"), Path::new("link.txt")).await.unwrap();
747 fs.mkdir(Path::new("dir")).await.unwrap();
748
749 let entries = fs.list(Path::new("")).await.unwrap();
750 assert_eq!(entries.len(), 3);
751
752 let link_entry = entries.iter().find(|e| e.name == "link.txt").unwrap();
754 assert_eq!(link_entry.entry_type, EntryType::Symlink);
755 assert_eq!(link_entry.symlink_target, Some(PathBuf::from("file.txt")));
756 }
757
758 #[tokio::test]
759 async fn test_symlink_broken_link() {
760 let fs = MemoryFs::new();
761 fs.symlink(Path::new("nonexistent.txt"), Path::new("broken.txt")).await.unwrap();
763
764 let target = fs.read_link(Path::new("broken.txt")).await.unwrap();
766 assert_eq!(target, Path::new("nonexistent.txt"));
767
768 let meta = fs.lstat(Path::new("broken.txt")).await.unwrap();
770 assert!(meta.is_symlink);
771
772 let result = fs.stat(Path::new("broken.txt")).await;
774 assert!(result.is_err());
775 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
776
777 let result = fs.read(Path::new("broken.txt")).await;
779 assert!(result.is_err());
780 }
781
782 #[tokio::test]
783 async fn test_symlink_read_link_on_non_symlink_fails() {
784 let fs = MemoryFs::new();
785 fs.write(Path::new("file.txt"), b"content").await.unwrap();
786
787 let result = fs.read_link(Path::new("file.txt")).await;
788 assert!(result.is_err());
789 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
790 }
791
792 #[tokio::test]
793 async fn test_symlink_already_exists() {
794 let fs = MemoryFs::new();
795 fs.write(Path::new("existing.txt"), b"content").await.unwrap();
796
797 let result = fs.symlink(Path::new("target"), Path::new("existing.txt")).await;
799 assert!(result.is_err());
800 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::AlreadyExists);
801 }
802
803 #[tokio::test]
806 async fn test_symlink_chain() {
807 let fs = MemoryFs::new();
809 fs.write(Path::new("file.txt"), b"end of chain").await.unwrap();
810 fs.symlink(Path::new("file.txt"), Path::new("c")).await.unwrap();
811 fs.symlink(Path::new("c"), Path::new("b")).await.unwrap();
812 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
813
814 let data = fs.read(Path::new("a")).await.unwrap();
816 assert_eq!(data, b"end of chain");
817
818 let meta = fs.stat(Path::new("a")).await.unwrap();
820 assert!(meta.is_file);
821 }
822
823 #[tokio::test]
824 async fn test_symlink_to_directory() {
825 let fs = MemoryFs::new();
826 fs.mkdir(Path::new("realdir")).await.unwrap();
827 fs.write(Path::new("realdir/file.txt"), b"inside dir").await.unwrap();
828 fs.symlink(Path::new("realdir"), Path::new("linkdir")).await.unwrap();
829
830 let meta = fs.stat(Path::new("linkdir")).await.unwrap();
832 assert!(meta.is_dir);
833
834 }
837
838 #[tokio::test]
839 async fn test_symlink_relative_path_stored_as_is() {
840 let fs = MemoryFs::new();
841 fs.mkdir(Path::new("subdir")).await.unwrap();
842 fs.write(Path::new("subdir/target.txt"), b"content").await.unwrap();
843
844 fs.symlink(Path::new("../subdir/target.txt"), Path::new("subdir/link.txt")).await.unwrap();
846
847 let target = fs.read_link(Path::new("subdir/link.txt")).await.unwrap();
849 assert_eq!(target.to_string_lossy(), "../subdir/target.txt");
850 }
851
852 #[tokio::test]
853 async fn test_symlink_absolute_path() {
854 let fs = MemoryFs::new();
855 fs.write(Path::new("target.txt"), b"content").await.unwrap();
856
857 fs.symlink(Path::new("/target.txt"), Path::new("link.txt")).await.unwrap();
859
860 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
861 assert_eq!(target.to_string_lossy(), "/target.txt");
862
863 let data = fs.read(Path::new("link.txt")).await.unwrap();
865 assert_eq!(data, b"content");
866 }
867
868 #[tokio::test]
869 async fn test_symlink_remove() {
870 let fs = MemoryFs::new();
871 fs.write(Path::new("target.txt"), b"content").await.unwrap();
872 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
873
874 fs.remove(Path::new("link.txt")).await.unwrap();
876
877 assert!(!fs.exists(Path::new("link.txt")).await);
879
880 assert!(fs.exists(Path::new("target.txt")).await);
882 }
883
884 #[tokio::test]
885 async fn test_symlink_overwrite_target_content() {
886 let fs = MemoryFs::new();
887 fs.write(Path::new("target.txt"), b"original").await.unwrap();
888 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
889
890 fs.write(Path::new("target.txt"), b"modified").await.unwrap();
892
893 let data = fs.read(Path::new("link.txt")).await.unwrap();
895 assert_eq!(data, b"modified");
896 }
897
898 #[tokio::test]
899 async fn test_symlink_empty_name() {
900 let fs = MemoryFs::new();
901 fs.write(Path::new("target.txt"), b"content").await.unwrap();
902
903 fs.symlink(Path::new("./target.txt"), Path::new("link.txt")).await.unwrap();
905
906 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
907 assert_eq!(target.to_string_lossy(), "./target.txt");
908 }
909
910 #[tokio::test]
911 async fn test_symlink_nested_creation() {
912 let fs = MemoryFs::new();
913 fs.symlink(Path::new("target"), Path::new("a/b/c/link")).await.unwrap();
915
916 let meta = fs.stat(Path::new("a/b")).await.unwrap();
918 assert!(meta.is_dir);
919
920 let meta = fs.lstat(Path::new("a/b/c/link")).await.unwrap();
922 assert!(meta.is_symlink);
923 }
924
925 #[tokio::test]
926 async fn test_symlink_read_link_not_found() {
927 let fs = MemoryFs::new();
928
929 let result = fs.read_link(Path::new("nonexistent")).await;
930 assert!(result.is_err());
931 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
932 }
933
934 #[tokio::test]
935 async fn test_symlink_read_link_on_directory() {
936 let fs = MemoryFs::new();
937 fs.mkdir(Path::new("dir")).await.unwrap();
938
939 let result = fs.read_link(Path::new("dir")).await;
940 assert!(result.is_err());
941 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
942 }
943
944 #[tokio::test]
945 async fn test_symlink_circular_read_returns_error() {
946 let fs = MemoryFs::new();
948 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
949 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
950
951 let result = fs.read(Path::new("a")).await;
952 assert!(result.is_err());
953 let err = result.unwrap_err();
954 assert!(
955 err.to_string().contains("symbolic links"),
956 "expected symlink loop error, got: {}",
957 err
958 );
959 }
960
961 #[tokio::test]
962 async fn test_symlink_circular_stat_returns_error() {
963 let fs = MemoryFs::new();
964 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
965 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
966
967 let result = fs.stat(Path::new("a")).await;
968 assert!(result.is_err());
969 let err = result.unwrap_err();
970 assert!(
971 err.to_string().contains("symbolic links"),
972 "expected symlink loop error, got: {}",
973 err
974 );
975 }
976}