1use super::{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 fn ensure_parents_locked(
189 entries: &mut HashMap<PathBuf, Entry>,
190 path: &Path,
191 ) -> io::Result<()> {
192 let mut current = PathBuf::new();
193 for component in path.parent().into_iter().flat_map(|p| p.components()) {
194 if let std::path::Component::Normal(s) = component {
195 current.push(s);
196 match entries.entry(current.clone()) {
197 std::collections::hash_map::Entry::Occupied(e) => {
198 if matches!(e.get(), Entry::File { .. }) {
199 return Err(io::Error::new(
200 io::ErrorKind::NotADirectory,
201 format!("not a directory: {}", current.display()),
202 ));
203 }
204 }
205 std::collections::hash_map::Entry::Vacant(e) => {
206 e.insert(Entry::Directory {
207 modified: SystemTime::now(),
208 });
209 }
210 }
211 }
212 }
213 Ok(())
214 }
215}
216
217#[async_trait]
218impl Filesystem for MemoryFs {
219 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
220 self.read_inner(path, 0).await
221 }
222
223 async fn write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
224 let normalized = Self::normalize(path);
225
226 let mut entries = self.entries.write().await;
227
228 Self::ensure_parents_locked(&mut entries, &normalized)?;
230
231 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
233 return Err(io::Error::new(
234 io::ErrorKind::IsADirectory,
235 format!("is a directory: {}", path.display()),
236 ));
237 }
238
239 entries.insert(
240 normalized,
241 Entry::File {
242 data: data.to_vec(),
243 modified: SystemTime::now(),
244 },
245 );
246 Ok(())
247 }
248
249 async fn set_mtime(&self, path: &Path, mtime: SystemTime) -> io::Result<()> {
250 let normalized = Self::normalize(path);
251 let mut entries = self.entries.write().await;
252 match entries.get_mut(&normalized) {
253 Some(Entry::File { modified, .. })
254 | Some(Entry::Directory { modified })
255 | Some(Entry::Symlink { modified, .. }) => {
256 *modified = mtime;
257 Ok(())
258 }
259 None => Err(io::Error::new(
260 io::ErrorKind::NotFound,
261 format!("no such file or directory: {}", path.display()),
262 )),
263 }
264 }
265
266 async fn list(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
267 let normalized = Self::normalize(path);
268 let entries = self.entries.read().await;
269
270 match entries.get(&normalized) {
272 Some(Entry::Directory { .. }) => {}
273 Some(Entry::File { .. }) => {
274 return Err(io::Error::new(
275 io::ErrorKind::NotADirectory,
276 format!("not a directory: {}", path.display()),
277 ))
278 }
279 Some(Entry::Symlink { .. }) => {
280 return Err(io::Error::new(
281 io::ErrorKind::NotADirectory,
282 format!("not a directory: {}", path.display()),
283 ))
284 }
285 None if normalized.as_os_str().is_empty() => {
286 }
288 None => {
289 return Err(io::Error::new(
290 io::ErrorKind::NotFound,
291 format!("not found: {}", path.display()),
292 ))
293 }
294 }
295
296 let prefix = if normalized.as_os_str().is_empty() {
298 PathBuf::new()
299 } else {
300 normalized.clone()
301 };
302
303 let mut result = Vec::new();
304 for (entry_path, entry) in entries.iter() {
305 if let Some(parent) = entry_path.parent()
306 && parent == prefix && entry_path != &normalized
307 && let Some(name) = entry_path.file_name() {
308 let (kind, size, modified, symlink_target) = match entry {
309 Entry::File { data, modified } => (DirEntryKind::File, data.len() as u64, Some(*modified), None),
310 Entry::Directory { modified } => (DirEntryKind::Directory, 0, Some(*modified), None),
311 Entry::Symlink { target, modified } => (DirEntryKind::Symlink, 0, Some(*modified), Some(target.clone())),
312 };
313 result.push(DirEntry {
314 name: name.to_string_lossy().into_owned(),
315 kind,
316 size,
317 modified,
318 permissions: None,
319 symlink_target,
320 });
321 }
322 }
323
324 result.sort_by(|a, b| a.name.cmp(&b.name));
326 Ok(result)
327 }
328
329 async fn stat(&self, path: &Path) -> io::Result<DirEntry> {
330 let mut entry = self.stat_inner(path, 0).await?;
331 let normalized = Self::normalize(path);
333 entry.name = normalized
334 .file_name()
335 .map(|n| n.to_string_lossy().into_owned())
336 .unwrap_or_else(|| "/".to_string());
337 Ok(entry)
338 }
339
340 async fn lstat(&self, path: &Path) -> io::Result<DirEntry> {
341 let normalized = Self::normalize(path);
342
343 let name = normalized
344 .file_name()
345 .map(|n| n.to_string_lossy().into_owned())
346 .unwrap_or_else(|| "/".to_string());
347
348 let entries = self.entries.read().await;
349
350 if normalized.as_os_str().is_empty() {
352 return Ok(DirEntry {
353 name,
354 kind: DirEntryKind::Directory,
355 size: 0,
356 modified: Some(SystemTime::now()),
357 permissions: None,
358 symlink_target: None,
359 });
360 }
361
362 match entries.get(&normalized) {
363 Some(Entry::File { data, modified }) => Ok(DirEntry {
364 name,
365 kind: DirEntryKind::File,
366 size: data.len() as u64,
367 modified: Some(*modified),
368 permissions: None,
369 symlink_target: None,
370 }),
371 Some(Entry::Directory { modified }) => Ok(DirEntry {
372 name,
373 kind: DirEntryKind::Directory,
374 size: 0,
375 modified: Some(*modified),
376 permissions: None,
377 symlink_target: None,
378 }),
379 Some(Entry::Symlink { target, modified }) => Ok(DirEntry {
380 name,
381 kind: DirEntryKind::Symlink,
382 size: 0,
383 modified: Some(*modified),
384 permissions: None,
385 symlink_target: Some(target.clone()),
386 }),
387 None => Err(io::Error::new(
388 io::ErrorKind::NotFound,
389 format!("not found: {}", path.display()),
390 )),
391 }
392 }
393
394 async fn read_link(&self, path: &Path) -> io::Result<PathBuf> {
395 let normalized = Self::normalize(path);
396 let entries = self.entries.read().await;
397
398 match entries.get(&normalized) {
399 Some(Entry::Symlink { target, .. }) => Ok(target.clone()),
400 Some(_) => Err(io::Error::new(
401 io::ErrorKind::InvalidInput,
402 format!("not a symbolic link: {}", path.display()),
403 )),
404 None => Err(io::Error::new(
405 io::ErrorKind::NotFound,
406 format!("not found: {}", path.display()),
407 )),
408 }
409 }
410
411 async fn symlink(&self, target: &Path, link: &Path) -> io::Result<()> {
412 let normalized = Self::normalize(link);
413
414 let mut entries = self.entries.write().await;
415
416 Self::ensure_parents_locked(&mut entries, &normalized)?;
418
419 if entries.contains_key(&normalized) {
421 return Err(io::Error::new(
422 io::ErrorKind::AlreadyExists,
423 format!("file exists: {}", link.display()),
424 ));
425 }
426
427 entries.insert(
428 normalized,
429 Entry::Symlink {
430 target: target.to_path_buf(),
431 modified: SystemTime::now(),
432 },
433 );
434 Ok(())
435 }
436
437 async fn mkdir(&self, path: &Path) -> io::Result<()> {
438 let normalized = Self::normalize(path);
439
440 let mut entries = self.entries.write().await;
441
442 Self::ensure_parents_locked(&mut entries, &normalized)?;
444
445 if let Some(existing) = entries.get(&normalized) {
447 return match existing {
448 Entry::Directory { .. } => Ok(()), Entry::File { .. } | Entry::Symlink { .. } => Err(io::Error::new(
450 io::ErrorKind::AlreadyExists,
451 format!("file exists: {}", path.display()),
452 )),
453 };
454 }
455
456 entries.insert(
457 normalized,
458 Entry::Directory {
459 modified: SystemTime::now(),
460 },
461 );
462 Ok(())
463 }
464
465 async fn remove(&self, path: &Path) -> io::Result<()> {
466 let normalized = Self::normalize(path);
467
468 if normalized.as_os_str().is_empty() {
469 return Err(io::Error::new(
470 io::ErrorKind::PermissionDenied,
471 "cannot remove root directory",
472 ));
473 }
474
475 let mut entries = self.entries.write().await;
476
477 if let Some(Entry::Directory { .. }) = entries.get(&normalized) {
479 let has_children = entries.keys().any(|k| {
481 k.parent() == Some(&normalized) && k != &normalized
482 });
483 if has_children {
484 return Err(io::Error::new(
485 io::ErrorKind::DirectoryNotEmpty,
486 format!("directory not empty: {}", path.display()),
487 ));
488 }
489 }
490
491 entries.remove(&normalized).ok_or_else(|| {
492 io::Error::new(
493 io::ErrorKind::NotFound,
494 format!("not found: {}", path.display()),
495 )
496 })?;
497 Ok(())
498 }
499
500 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
501 let from_normalized = Self::normalize(from);
502 let to_normalized = Self::normalize(to);
503
504 if from_normalized.as_os_str().is_empty() {
505 return Err(io::Error::new(
506 io::ErrorKind::PermissionDenied,
507 "cannot rename root directory",
508 ));
509 }
510
511 if from_normalized == to_normalized {
513 return Ok(());
514 }
515
516 if to_normalized.starts_with(&from_normalized) {
518 return Err(io::Error::new(
519 io::ErrorKind::InvalidInput,
520 format!("cannot move '{}' into itself", from.display()),
521 ));
522 }
523
524 let mut entries = self.entries.write().await;
525
526 let _ = Self::ensure_parents_locked(&mut entries, &to_normalized);
530
531 let entry = entries.remove(&from_normalized).ok_or_else(|| {
533 io::Error::new(
534 io::ErrorKind::NotFound,
535 format!("not found: {}", from.display()),
536 )
537 })?;
538
539 if let Some(existing) = entries.get(&to_normalized) {
541 match (&entry, existing) {
542 (Entry::File { .. }, Entry::Directory { .. }) => {
543 entries.insert(from_normalized, entry);
545 return Err(io::Error::new(
546 io::ErrorKind::IsADirectory,
547 format!("destination is a directory: {}", to.display()),
548 ));
549 }
550 (Entry::Directory { .. }, Entry::File { .. }) => {
551 entries.insert(from_normalized, entry);
552 return Err(io::Error::new(
553 io::ErrorKind::NotADirectory,
554 format!("destination is not a directory: {}", to.display()),
555 ));
556 }
557 _ => {}
558 }
559 }
560
561 if matches!(entry, Entry::Directory { .. }) {
563 let children_to_rename: Vec<(PathBuf, Entry)> = entries
565 .iter()
566 .filter(|(k, _)| k.starts_with(&from_normalized) && *k != &from_normalized)
567 .map(|(k, v)| (k.clone(), v.clone()))
568 .collect();
569
570 for (old_path, child_entry) in children_to_rename {
572 entries.remove(&old_path);
573 let Ok(relative) = old_path.strip_prefix(&from_normalized) else {
574 continue;
575 };
576 let new_path = to_normalized.join(relative);
577 entries.insert(new_path, child_entry);
578 }
579 }
580
581 entries.insert(to_normalized, entry);
583 Ok(())
584 }
585
586 fn read_only(&self) -> bool {
587 false
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594
595 #[tokio::test]
596 async fn test_write_and_read() {
597 let fs = MemoryFs::new();
598 fs.write(Path::new("test.txt"), b"hello world").await.unwrap();
599 let data = fs.read(Path::new("test.txt")).await.unwrap();
600 assert_eq!(data, b"hello world");
601 }
602
603 #[tokio::test]
604 async fn test_set_mtime_updates_existing() {
605 let fs = MemoryFs::new();
606 fs.write(Path::new("t.txt"), b"x").await.unwrap();
607 let pinned = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000);
608 fs.set_mtime(Path::new("t.txt"), pinned).await.unwrap();
609 let entry = fs.stat(Path::new("t.txt")).await.unwrap();
610 assert_eq!(entry.modified, Some(pinned));
611 }
612
613 #[tokio::test]
614 async fn test_set_mtime_missing_errors() {
615 let fs = MemoryFs::new();
616 let result = fs.set_mtime(Path::new("nope.txt"), SystemTime::now()).await;
617 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
618 }
619
620 #[tokio::test]
621 async fn test_read_not_found() {
622 let fs = MemoryFs::new();
623 let result = fs.read(Path::new("nonexistent.txt")).await;
624 assert!(result.is_err());
625 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
626 }
627
628 #[tokio::test]
629 async fn test_nested_directories() {
630 let fs = MemoryFs::new();
631 fs.write(Path::new("a/b/c/file.txt"), b"nested").await.unwrap();
632
633 let entry = fs.stat(Path::new("a")).await.unwrap();
635 assert_eq!(entry.kind, DirEntryKind::Directory);
636
637 let entry = fs.stat(Path::new("a/b")).await.unwrap();
638 assert_eq!(entry.kind, DirEntryKind::Directory);
639
640 let entry = fs.stat(Path::new("a/b/c")).await.unwrap();
641 assert_eq!(entry.kind, DirEntryKind::Directory);
642
643 let data = fs.read(Path::new("a/b/c/file.txt")).await.unwrap();
644 assert_eq!(data, b"nested");
645 }
646
647 #[tokio::test]
648 async fn test_list_directory() {
649 let fs = MemoryFs::new();
650 fs.write(Path::new("a.txt"), b"a").await.unwrap();
651 fs.write(Path::new("b.txt"), b"b").await.unwrap();
652 fs.mkdir(Path::new("subdir")).await.unwrap();
653
654 let entries = fs.list(Path::new("")).await.unwrap();
655 assert_eq!(entries.len(), 3);
656
657 let names: Vec<_> = entries.iter().map(|e| &e.name).collect();
658 assert!(names.contains(&&"a.txt".to_string()));
659 assert!(names.contains(&&"b.txt".to_string()));
660 assert!(names.contains(&&"subdir".to_string()));
661 }
662
663 #[tokio::test]
664 async fn test_mkdir_and_stat() {
665 let fs = MemoryFs::new();
666 fs.mkdir(Path::new("mydir")).await.unwrap();
667
668 let entry = fs.stat(Path::new("mydir")).await.unwrap();
669 assert_eq!(entry.kind, DirEntryKind::Directory);
670 }
671
672 #[tokio::test]
673 async fn test_remove_file() {
674 let fs = MemoryFs::new();
675 fs.write(Path::new("file.txt"), b"data").await.unwrap();
676
677 fs.remove(Path::new("file.txt")).await.unwrap();
678
679 let result = fs.stat(Path::new("file.txt")).await;
680 assert!(result.is_err());
681 }
682
683 #[tokio::test]
684 async fn test_remove_empty_directory() {
685 let fs = MemoryFs::new();
686 fs.mkdir(Path::new("emptydir")).await.unwrap();
687
688 fs.remove(Path::new("emptydir")).await.unwrap();
689
690 let result = fs.stat(Path::new("emptydir")).await;
691 assert!(result.is_err());
692 }
693
694 #[tokio::test]
695 async fn test_remove_non_empty_directory_fails() {
696 let fs = MemoryFs::new();
697 fs.write(Path::new("dir/file.txt"), b"data").await.unwrap();
698
699 let result = fs.remove(Path::new("dir")).await;
700 assert!(result.is_err());
701 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::DirectoryNotEmpty);
702 }
703
704 #[tokio::test]
705 async fn test_path_normalization() {
706 let fs = MemoryFs::new();
707 fs.write(Path::new("/a/b/c.txt"), b"data").await.unwrap();
708
709 let data1 = fs.read(Path::new("a/b/c.txt")).await.unwrap();
711 let data2 = fs.read(Path::new("/a/b/c.txt")).await.unwrap();
712 let data3 = fs.read(Path::new("a/./b/c.txt")).await.unwrap();
713 let data4 = fs.read(Path::new("a/b/../b/c.txt")).await.unwrap();
714
715 assert_eq!(data1, data2);
716 assert_eq!(data2, data3);
717 assert_eq!(data3, data4);
718 }
719
720 #[tokio::test]
721 async fn test_overwrite_file() {
722 let fs = MemoryFs::new();
723 fs.write(Path::new("file.txt"), b"first").await.unwrap();
724 fs.write(Path::new("file.txt"), b"second").await.unwrap();
725
726 let data = fs.read(Path::new("file.txt")).await.unwrap();
727 assert_eq!(data, b"second");
728 }
729
730 #[tokio::test]
731 async fn test_exists() {
732 let fs = MemoryFs::new();
733 assert!(!fs.exists(Path::new("nope.txt")).await);
734
735 fs.write(Path::new("yes.txt"), b"here").await.unwrap();
736 assert!(fs.exists(Path::new("yes.txt")).await);
737 }
738
739 #[tokio::test]
740 async fn test_rename_file() {
741 let fs = MemoryFs::new();
742 fs.write(Path::new("old.txt"), b"content").await.unwrap();
743
744 fs.rename(Path::new("old.txt"), Path::new("new.txt")).await.unwrap();
745
746 let data = fs.read(Path::new("new.txt")).await.unwrap();
748 assert_eq!(data, b"content");
749
750 assert!(!fs.exists(Path::new("old.txt")).await);
752 }
753
754 #[tokio::test]
755 async fn test_rename_directory() {
756 let fs = MemoryFs::new();
757 fs.write(Path::new("dir/a.txt"), b"a").await.unwrap();
758 fs.write(Path::new("dir/b.txt"), b"b").await.unwrap();
759 fs.write(Path::new("dir/sub/c.txt"), b"c").await.unwrap();
760
761 fs.rename(Path::new("dir"), Path::new("renamed")).await.unwrap();
762
763 assert!(fs.exists(Path::new("renamed")).await);
765 assert!(fs.exists(Path::new("renamed/a.txt")).await);
766 assert!(fs.exists(Path::new("renamed/b.txt")).await);
767 assert!(fs.exists(Path::new("renamed/sub/c.txt")).await);
768
769 assert!(!fs.exists(Path::new("dir")).await);
771 assert!(!fs.exists(Path::new("dir/a.txt")).await);
772
773 let data = fs.read(Path::new("renamed/a.txt")).await.unwrap();
775 assert_eq!(data, b"a");
776 }
777
778 #[tokio::test]
779 async fn test_rename_not_found() {
780 let fs = MemoryFs::new();
781 let result = fs.rename(Path::new("nonexistent"), Path::new("dest")).await;
782 assert!(result.is_err());
783 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
784 }
785
786 #[tokio::test]
789 async fn test_symlink_create_and_read_link() {
790 let fs = MemoryFs::new();
791 fs.write(Path::new("target.txt"), b"content").await.unwrap();
792 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
793
794 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
796 assert_eq!(target, Path::new("target.txt"));
797 }
798
799 #[tokio::test]
800 async fn test_symlink_read_follows_link() {
801 let fs = MemoryFs::new();
802 fs.write(Path::new("target.txt"), b"hello from target").await.unwrap();
803 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
804
805 let data = fs.read(Path::new("link.txt")).await.unwrap();
807 assert_eq!(data, b"hello from target");
808 }
809
810 #[tokio::test]
811 async fn test_symlink_stat_follows_link() {
812 let fs = MemoryFs::new();
813 fs.write(Path::new("target.txt"), b"12345").await.unwrap();
814 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
815
816 let entry = fs.stat(Path::new("link.txt")).await.unwrap();
818 assert_eq!(entry.kind, DirEntryKind::File);
819 assert_eq!(entry.size, 5);
820 }
821
822 #[tokio::test]
823 async fn test_symlink_lstat_returns_symlink_info() {
824 let fs = MemoryFs::new();
825 fs.write(Path::new("target.txt"), b"content").await.unwrap();
826 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
827
828 let entry = fs.lstat(Path::new("link.txt")).await.unwrap();
830 assert_eq!(entry.kind, DirEntryKind::Symlink);
831 }
832
833 #[tokio::test]
834 async fn test_symlink_in_list() {
835 let fs = MemoryFs::new();
836 fs.write(Path::new("file.txt"), b"content").await.unwrap();
837 fs.symlink(Path::new("file.txt"), Path::new("link.txt")).await.unwrap();
838 fs.mkdir(Path::new("dir")).await.unwrap();
839
840 let entries = fs.list(Path::new("")).await.unwrap();
841 assert_eq!(entries.len(), 3);
842
843 let link_entry = entries.iter().find(|e| e.name == "link.txt").unwrap();
845 assert_eq!(link_entry.kind, DirEntryKind::Symlink);
846 assert_eq!(link_entry.symlink_target, Some(PathBuf::from("file.txt")));
847 }
848
849 #[tokio::test]
850 async fn test_symlink_broken_link() {
851 let fs = MemoryFs::new();
852 fs.symlink(Path::new("nonexistent.txt"), Path::new("broken.txt")).await.unwrap();
854
855 let target = fs.read_link(Path::new("broken.txt")).await.unwrap();
857 assert_eq!(target, Path::new("nonexistent.txt"));
858
859 let entry = fs.lstat(Path::new("broken.txt")).await.unwrap();
861 assert_eq!(entry.kind, DirEntryKind::Symlink);
862
863 let result = fs.stat(Path::new("broken.txt")).await;
865 assert!(result.is_err());
866 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
867
868 let result = fs.read(Path::new("broken.txt")).await;
870 assert!(result.is_err());
871 }
872
873 #[tokio::test]
874 async fn test_symlink_read_link_on_non_symlink_fails() {
875 let fs = MemoryFs::new();
876 fs.write(Path::new("file.txt"), b"content").await.unwrap();
877
878 let result = fs.read_link(Path::new("file.txt")).await;
879 assert!(result.is_err());
880 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
881 }
882
883 #[tokio::test]
884 async fn test_symlink_already_exists() {
885 let fs = MemoryFs::new();
886 fs.write(Path::new("existing.txt"), b"content").await.unwrap();
887
888 let result = fs.symlink(Path::new("target"), Path::new("existing.txt")).await;
890 assert!(result.is_err());
891 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::AlreadyExists);
892 }
893
894 #[tokio::test]
897 async fn test_symlink_chain() {
898 let fs = MemoryFs::new();
900 fs.write(Path::new("file.txt"), b"end of chain").await.unwrap();
901 fs.symlink(Path::new("file.txt"), Path::new("c")).await.unwrap();
902 fs.symlink(Path::new("c"), Path::new("b")).await.unwrap();
903 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
904
905 let data = fs.read(Path::new("a")).await.unwrap();
907 assert_eq!(data, b"end of chain");
908
909 let entry = fs.stat(Path::new("a")).await.unwrap();
911 assert_eq!(entry.kind, DirEntryKind::File);
912 }
913
914 #[tokio::test]
915 async fn test_symlink_to_directory() {
916 let fs = MemoryFs::new();
917 fs.mkdir(Path::new("realdir")).await.unwrap();
918 fs.write(Path::new("realdir/file.txt"), b"inside dir").await.unwrap();
919 fs.symlink(Path::new("realdir"), Path::new("linkdir")).await.unwrap();
920
921 let entry = fs.stat(Path::new("linkdir")).await.unwrap();
923 assert_eq!(entry.kind, DirEntryKind::Directory);
924
925 }
928
929 #[tokio::test]
930 async fn test_symlink_relative_path_stored_as_is() {
931 let fs = MemoryFs::new();
932 fs.mkdir(Path::new("subdir")).await.unwrap();
933 fs.write(Path::new("subdir/target.txt"), b"content").await.unwrap();
934
935 fs.symlink(Path::new("../subdir/target.txt"), Path::new("subdir/link.txt")).await.unwrap();
937
938 let target = fs.read_link(Path::new("subdir/link.txt")).await.unwrap();
940 assert_eq!(target.to_string_lossy(), "../subdir/target.txt");
941 }
942
943 #[tokio::test]
944 async fn test_symlink_absolute_path() {
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 let data = fs.read(Path::new("link.txt")).await.unwrap();
956 assert_eq!(data, b"content");
957 }
958
959 #[tokio::test]
960 async fn test_symlink_remove() {
961 let fs = MemoryFs::new();
962 fs.write(Path::new("target.txt"), b"content").await.unwrap();
963 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
964
965 fs.remove(Path::new("link.txt")).await.unwrap();
967
968 assert!(!fs.exists(Path::new("link.txt")).await);
970
971 assert!(fs.exists(Path::new("target.txt")).await);
973 }
974
975 #[tokio::test]
976 async fn test_symlink_overwrite_target_content() {
977 let fs = MemoryFs::new();
978 fs.write(Path::new("target.txt"), b"original").await.unwrap();
979 fs.symlink(Path::new("target.txt"), Path::new("link.txt")).await.unwrap();
980
981 fs.write(Path::new("target.txt"), b"modified").await.unwrap();
983
984 let data = fs.read(Path::new("link.txt")).await.unwrap();
986 assert_eq!(data, b"modified");
987 }
988
989 #[tokio::test]
990 async fn test_symlink_empty_name() {
991 let fs = MemoryFs::new();
992 fs.write(Path::new("target.txt"), b"content").await.unwrap();
993
994 fs.symlink(Path::new("./target.txt"), Path::new("link.txt")).await.unwrap();
996
997 let target = fs.read_link(Path::new("link.txt")).await.unwrap();
998 assert_eq!(target.to_string_lossy(), "./target.txt");
999 }
1000
1001 #[tokio::test]
1002 async fn test_symlink_nested_creation() {
1003 let fs = MemoryFs::new();
1004 fs.symlink(Path::new("target"), Path::new("a/b/c/link")).await.unwrap();
1006
1007 let entry = fs.stat(Path::new("a/b")).await.unwrap();
1009 assert_eq!(entry.kind, DirEntryKind::Directory);
1010
1011 let entry = fs.lstat(Path::new("a/b/c/link")).await.unwrap();
1013 assert_eq!(entry.kind, DirEntryKind::Symlink);
1014 }
1015
1016 #[tokio::test]
1017 async fn test_symlink_read_link_not_found() {
1018 let fs = MemoryFs::new();
1019
1020 let result = fs.read_link(Path::new("nonexistent")).await;
1021 assert!(result.is_err());
1022 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
1023 }
1024
1025 #[tokio::test]
1026 async fn test_symlink_read_link_on_directory() {
1027 let fs = MemoryFs::new();
1028 fs.mkdir(Path::new("dir")).await.unwrap();
1029
1030 let result = fs.read_link(Path::new("dir")).await;
1031 assert!(result.is_err());
1032 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
1033 }
1034
1035 #[tokio::test]
1036 async fn test_symlink_circular_read_returns_error() {
1037 let fs = MemoryFs::new();
1039 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
1040 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
1041
1042 let result = fs.read(Path::new("a")).await;
1043 assert!(result.is_err());
1044 let err = result.unwrap_err();
1045 assert!(
1046 err.to_string().contains("symbolic links"),
1047 "expected symlink loop error, got: {}",
1048 err
1049 );
1050 }
1051
1052 #[tokio::test]
1053 async fn test_symlink_circular_stat_returns_error() {
1054 let fs = MemoryFs::new();
1055 fs.symlink(Path::new("b"), Path::new("a")).await.unwrap();
1056 fs.symlink(Path::new("a"), Path::new("b")).await.unwrap();
1057
1058 let result = fs.stat(Path::new("a")).await;
1059 assert!(result.is_err());
1060 let err = result.unwrap_err();
1061 assert!(
1062 err.to_string().contains("symbolic links"),
1063 "expected symlink loop error, got: {}",
1064 err
1065 );
1066 }
1067
1068 #[tokio::test]
1069 async fn test_rename_into_self_errors() {
1070 let fs = MemoryFs::new();
1071 fs.mkdir(Path::new("a")).await.unwrap();
1072
1073 let result = fs.rename(Path::new("a"), Path::new("a/b")).await;
1074 assert!(result.is_err());
1075 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::InvalidInput);
1076 }
1077
1078 #[tokio::test]
1079 async fn test_rename_identity_noop() {
1080 let fs = MemoryFs::new();
1081 fs.write(Path::new("a"), b"data").await.unwrap();
1082
1083 fs.rename(Path::new("a"), Path::new("a")).await.unwrap();
1085
1086 let data = fs.read(Path::new("a")).await.unwrap();
1088 assert_eq!(data, b"data");
1089 }
1090
1091 #[tokio::test]
1092 async fn test_ensure_parents_rejects_file_as_dir() {
1093 let fs = MemoryFs::new();
1094 fs.write(Path::new("a"), b"I am a file").await.unwrap();
1096
1097 let result = fs.write(Path::new("a/b"), b"child").await;
1099 assert!(result.is_err());
1100 assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotADirectory);
1101 }
1102}