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