1use std::collections::{HashMap, HashSet};
10use std::io::{self, Error, ErrorKind};
11use std::path::{Component, Path, PathBuf};
12use std::sync::{Arc, RwLock};
13
14use super::{DirEntry, FileType, Metadata, ReadStorage};
15
16use super::{Capabilities, Storage};
17
18#[derive(Debug, Clone, Default)]
46pub struct InMemoryFs {
47 files: Arc<RwLock<HashMap<PathBuf, String>>>,
49 binary_files: Arc<RwLock<HashMap<PathBuf, Vec<u8>>>>,
51 directories: Arc<RwLock<HashSet<PathBuf>>>,
54 symlinks: Arc<RwLock<HashMap<PathBuf, PathBuf>>>,
58}
59
60impl InMemoryFs {
61 pub fn new() -> Self {
63 Self::default()
64 }
65
66 pub fn with_files(entries: Vec<(PathBuf, String)>) -> Self {
69 let fs = Self::new();
70 {
71 let mut files = fs.files.write().unwrap();
72 let mut dirs = fs.directories.write().unwrap();
73 for (path, content) in entries {
74 insert_ancestor_dirs(&mut dirs, &path);
75 files.insert(path, content);
76 }
77 }
78 fs
79 }
80
81 pub fn load_from_entries(entries: Vec<(String, String)>) -> Self {
84 Self::with_files(
85 entries
86 .into_iter()
87 .map(|(path, content)| (PathBuf::from(path), content))
88 .collect(),
89 )
90 }
91
92 pub fn export_entries(&self) -> Vec<(String, String)> {
96 self.files
97 .read()
98 .unwrap()
99 .iter()
100 .map(|(path, content)| (path.to_string_lossy().into_owned(), content.clone()))
101 .collect()
102 }
103
104 pub fn export_binary_entries(&self) -> Vec<(String, Vec<u8>)> {
106 self.binary_files
107 .read()
108 .unwrap()
109 .iter()
110 .map(|(path, content)| (path.to_string_lossy().into_owned(), content.clone()))
111 .collect()
112 }
113
114 pub fn load_binary_entries(&self, entries: Vec<(String, Vec<u8>)>) {
116 let mut binary_files = self.binary_files.write().unwrap();
117 let mut dirs = self.directories.write().unwrap();
118 for (path_str, content) in entries {
119 let path = PathBuf::from(path_str);
120 insert_ancestor_dirs(&mut dirs, &path);
121 binary_files.insert(path, content);
122 }
123 }
124
125 pub fn list_all_files(&self) -> Vec<PathBuf> {
127 self.files.read().unwrap().keys().cloned().collect()
128 }
129
130 pub fn clear(&self) {
134 self.files.write().unwrap().clear();
135 self.binary_files.write().unwrap().clear();
136 self.directories.write().unwrap().clear();
137 self.symlinks.write().unwrap().clear();
138 }
139
140 pub fn add_symlink(&self, link: &Path, target: &Path) {
147 let link = normalize_path(link);
148 let target = normalize_path(target);
149 insert_ancestor_dirs(&mut self.directories.write().unwrap(), &link);
150 self.symlinks.write().unwrap().insert(link, target);
151 }
152
153 fn resolve(&self, normalized: &Path) -> PathBuf {
157 self.symlinks
158 .read()
159 .unwrap()
160 .get(normalized)
161 .cloned()
162 .unwrap_or_else(|| normalized.to_path_buf())
163 }
164}
165
166fn normalize_path(path: &Path) -> PathBuf {
171 let mut components: Vec<Component> = Vec::new();
172 for component in path.components() {
173 match component {
174 Component::CurDir => {}
175 Component::ParentDir => {
176 if !matches!(components.last(), None | Some(Component::RootDir)) {
177 components.pop();
178 }
179 }
180 c => components.push(c),
181 }
182 }
183 components.iter().collect()
184}
185
186fn insert_ancestor_dirs(dirs: &mut HashSet<PathBuf>, path: &Path) {
190 let mut current = path;
191 while let Some(parent) = current.parent() {
192 if parent.as_os_str().is_empty() {
193 break;
194 }
195 dirs.insert(parent.to_path_buf());
196 current = parent;
197 }
198}
199
200fn not_found(path: &Path) -> Error {
201 Error::new(
202 ErrorKind::NotFound,
203 format!("not found: {}", path.display()),
204 )
205}
206
207impl ReadStorage for InMemoryFs {
208 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
209 let normalized = normalize_path(path);
210 let resolved = self.resolve(&normalized);
211 if let Some(data) = self.binary_files.read().unwrap().get(&resolved) {
212 return Ok(data.clone());
213 }
214 if let Some(text) = self.files.read().unwrap().get(&resolved) {
215 return Ok(text.as_bytes().to_vec());
216 }
217 Err(not_found(path))
218 }
219
220 async fn read_to_string(&self, path: &Path) -> io::Result<String> {
221 let bytes = self.read(path).await?;
227 String::from_utf8(bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e))
228 }
229
230 async fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
231 let normalized = normalize_path(path);
232 if !normalized.as_os_str().is_empty()
233 && !self.directories.read().unwrap().contains(&normalized)
234 {
235 return Err(not_found(path));
236 }
237
238 let mut result = Vec::new();
239 for entry in self.files.read().unwrap().keys() {
240 if entry.parent() == Some(normalized.as_path()) {
241 result.push(DirEntry::new(entry.clone(), FileType::FILE));
242 }
243 }
244 for entry in self.binary_files.read().unwrap().keys() {
245 if entry.parent() == Some(normalized.as_path()) {
246 result.push(DirEntry::new(entry.clone(), FileType::FILE));
247 }
248 }
249 for entry in self.symlinks.read().unwrap().keys() {
253 if entry.parent() == Some(normalized.as_path()) {
254 result.push(DirEntry::new(entry.clone(), FileType::SYMLINK));
255 }
256 }
257 for entry in self.directories.read().unwrap().iter() {
258 if entry.parent() == Some(normalized.as_path()) && entry != &normalized {
259 result.push(DirEntry::new(entry.clone(), FileType::DIR));
260 }
261 }
262 Ok(result)
263 }
264
265 async fn metadata(&self, path: &Path) -> io::Result<Metadata> {
266 let normalized = normalize_path(path);
267 let resolved = self.resolve(&normalized);
268
269 if let Some(data) = self.binary_files.read().unwrap().get(&resolved) {
270 return Ok(Metadata::new(FileType::FILE, data.len() as u64, None));
271 }
272 if let Some(text) = self.files.read().unwrap().get(&resolved) {
273 return Ok(Metadata::new(FileType::FILE, text.len() as u64, None));
274 }
275 if self.directories.read().unwrap().contains(&resolved) {
276 return Ok(Metadata::new(FileType::DIR, 0, None));
277 }
278 Err(not_found(path))
279 }
280
281 async fn read_link(&self, path: &Path) -> io::Result<Option<PathBuf>> {
293 let normalized = normalize_path(path);
298 if let Some(target) = self.symlinks.read().unwrap().get(&normalized) {
299 return Ok(Some(target.clone()));
300 }
301 let occupied = self.files.read().unwrap().contains_key(&normalized)
302 || self.binary_files.read().unwrap().contains_key(&normalized)
303 || self.directories.read().unwrap().contains(&normalized);
304 if occupied {
305 return Err(Error::new(
306 ErrorKind::InvalidInput,
307 format!("not a symbolic link: {}", path.display()),
308 ));
309 }
310 Err(not_found(path))
311 }
312}
313
314impl Storage for InMemoryFs {
315 async fn write(&self, path: &Path, contents: &[u8]) -> io::Result<()> {
316 let normalized = normalize_path(path);
317 let resolved = self.resolve(&normalized);
323 insert_ancestor_dirs(&mut self.directories.write().unwrap(), &resolved);
324
325 match std::str::from_utf8(contents) {
331 Ok(s) => {
332 self.files
333 .write()
334 .unwrap()
335 .insert(resolved.clone(), s.to_string());
336 self.binary_files.write().unwrap().remove(&resolved);
337 }
338 Err(_) => {
339 self.binary_files
340 .write()
341 .unwrap()
342 .insert(resolved.clone(), contents.to_vec());
343 self.files.write().unwrap().remove(&resolved);
344 }
345 }
346 Ok(())
347 }
348
349 async fn create_new(&self, path: &Path, contents: &[u8]) -> io::Result<()> {
350 let normalized = normalize_path(path);
351 let occupied = self.files.read().unwrap().contains_key(&normalized)
359 || self.binary_files.read().unwrap().contains_key(&normalized)
360 || self.symlinks.read().unwrap().contains_key(&normalized)
361 || self.directories.read().unwrap().contains(&normalized);
362 if occupied {
363 return Err(Error::new(
364 ErrorKind::AlreadyExists,
365 format!("already exists: {}", path.display()),
366 ));
367 }
368 self.write(path, contents).await
369 }
370
371 async fn create_dir_all(&self, path: &Path) -> io::Result<()> {
372 let normalized = normalize_path(path);
373 let mut dirs = self.directories.write().unwrap();
374 if !normalized.as_os_str().is_empty() {
375 dirs.insert(normalized.clone());
376 }
377 insert_ancestor_dirs(&mut dirs, &normalized);
378 Ok(())
379 }
380
381 async fn remove_file(&self, path: &Path) -> io::Result<()> {
382 let normalized = normalize_path(path);
383 if self.files.write().unwrap().remove(&normalized).is_some() {
384 return Ok(());
385 }
386 if self
387 .binary_files
388 .write()
389 .unwrap()
390 .remove(&normalized)
391 .is_some()
392 {
393 return Ok(());
394 }
395 if self.symlinks.write().unwrap().remove(&normalized).is_some() {
396 return Ok(());
397 }
398 Err(not_found(path))
399 }
400
401 async fn remove_dir_all(&self, path: &Path) -> io::Result<()> {
402 let normalized = normalize_path(path);
403 self.files
404 .write()
405 .unwrap()
406 .retain(|p, _| !p.starts_with(&normalized));
407 self.binary_files
408 .write()
409 .unwrap()
410 .retain(|p, _| !p.starts_with(&normalized));
411 self.symlinks
412 .write()
413 .unwrap()
414 .retain(|p, _| !p.starts_with(&normalized));
415 self.directories
416 .write()
417 .unwrap()
418 .retain(|p| p != &normalized && !p.starts_with(&normalized));
419 Ok(())
420 }
421
422 async fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
423 let from_norm = normalize_path(from);
424 let to_norm = normalize_path(to);
425 if from_norm == to_norm {
426 return Ok(());
427 }
428
429 let is_dir = self.directories.read().unwrap().contains(&from_norm);
430 if is_dir {
431 self.rename_dir(&from_norm, &to_norm, to)
432 } else {
433 self.rename_file(&from_norm, &to_norm, from, to).await
434 }
435 }
436
437 async fn set_link(&self, path: &Path, target: &Path) -> io::Result<()> {
438 let normalized = normalize_path(path);
443 insert_ancestor_dirs(&mut self.directories.write().unwrap(), &normalized);
444 self.files.write().unwrap().remove(&normalized);
445 self.binary_files.write().unwrap().remove(&normalized);
446 self.symlinks
447 .write()
448 .unwrap()
449 .insert(normalized, normalize_path(target));
450 Ok(())
451 }
452
453 fn capabilities(&self) -> Capabilities {
454 Capabilities::IN_MEMORY
455 }
456
457 async fn replace(&self, path: &Path, contents: &[u8]) -> io::Result<()> {
458 self.symlinks.write().unwrap().remove(&normalize_path(path));
474 self.write(path, contents).await
475 }
476}
477
478impl InMemoryFs {
479 fn rename_dir(&self, from_norm: &Path, to_norm: &Path, to: &Path) -> io::Result<()> {
480 {
481 let files = self.files.read().unwrap();
488 let bin = self.binary_files.read().unwrap();
489 let dirs = self.directories.read().unwrap();
490 let links = self.symlinks.read().unwrap();
491 if files.contains_key(to_norm)
492 || bin.contains_key(to_norm)
493 || dirs.contains(to_norm)
494 || links.contains_key(to_norm)
495 {
496 return Err(Error::new(
497 ErrorKind::AlreadyExists,
498 format!("destination already exists: {}", to.display()),
499 ));
500 }
501 }
502
503 let files_to_move: Vec<(PathBuf, String)> = self
504 .files
505 .read()
506 .unwrap()
507 .iter()
508 .filter(|(p, _)| p.starts_with(from_norm))
509 .map(|(p, c)| (p.clone(), c.clone()))
510 .collect();
511 let binaries_to_move: Vec<(PathBuf, Vec<u8>)> = self
512 .binary_files
513 .read()
514 .unwrap()
515 .iter()
516 .filter(|(p, _)| p.starts_with(from_norm))
517 .map(|(p, c)| (p.clone(), c.clone()))
518 .collect();
519
520 {
521 let mut files = self.files.write().unwrap();
522 for (old_path, content) in files_to_move {
523 files.remove(&old_path);
524 let relative = old_path.strip_prefix(from_norm).unwrap();
525 files.insert(to_norm.join(relative), content);
526 }
527 }
528 {
529 let mut binary = self.binary_files.write().unwrap();
530 for (old_path, content) in binaries_to_move {
531 binary.remove(&old_path);
532 let relative = old_path.strip_prefix(from_norm).unwrap();
533 binary.insert(to_norm.join(relative), content);
534 }
535 }
536 {
537 let mut dirs = self.directories.write().unwrap();
538 let old_dirs: Vec<PathBuf> = dirs
539 .iter()
540 .filter(|d| d.starts_with(from_norm))
541 .cloned()
542 .collect();
543 for old_dir in old_dirs {
544 dirs.remove(&old_dir);
545 let relative = old_dir.strip_prefix(from_norm).unwrap();
546 dirs.insert(to_norm.join(relative));
547 }
548 insert_ancestor_dirs(&mut dirs, to_norm);
549 }
550
551 Ok(())
552 }
553
554 async fn rename_file(
555 &self,
556 from_norm: &Path,
557 to_norm: &Path,
558 from: &Path,
559 to: &Path,
560 ) -> io::Result<()> {
561 {
562 let files = self.files.read().unwrap();
563 let bin = self.binary_files.read().unwrap();
564 let links = self.symlinks.read().unwrap();
565 if !files.contains_key(from_norm)
566 && !bin.contains_key(from_norm)
567 && !links.contains_key(from_norm)
568 {
569 return Err(not_found(from));
570 }
571 if self.directories.read().unwrap().contains(to_norm) {
578 return Err(Error::new(
579 ErrorKind::AlreadyExists,
580 format!("destination is a directory: {}", to.display()),
581 ));
582 }
583 }
584
585 if let Some(parent) = to_norm.parent() {
586 self.create_dir_all(parent).await?;
587 }
588
589 self.files.write().unwrap().remove(to_norm);
592 self.binary_files.write().unwrap().remove(to_norm);
593 self.symlinks.write().unwrap().remove(to_norm);
594
595 let moved_link = self.symlinks.write().unwrap().remove(from_norm);
598 if let Some(target) = moved_link {
599 self.symlinks
600 .write()
601 .unwrap()
602 .insert(to_norm.to_path_buf(), target);
603 return Ok(());
604 }
605
606 let removed_text = self.files.write().unwrap().remove(from_norm);
615 if let Some(content) = removed_text {
616 self.files
617 .write()
618 .unwrap()
619 .insert(to_norm.to_path_buf(), content);
620 return Ok(());
621 }
622 let removed_binary = self.binary_files.write().unwrap().remove(from_norm);
623 if let Some(content) = removed_binary {
624 self.binary_files
625 .write()
626 .unwrap()
627 .insert(to_norm.to_path_buf(), content);
628 return Ok(());
629 }
630 Err(not_found(from))
631 }
632}
633
634#[cfg(test)]
635mod tests {
636 use super::*;
637 use crate::exec::block_on;
638
639 #[test]
640 fn read_write_roundtrip() {
641 let fs = InMemoryFs::new();
642 block_on(fs.write(Path::new("test.md"), b"Hello, World!")).unwrap();
643 assert_eq!(
644 block_on(fs.read_to_string(Path::new("test.md"))).unwrap(),
645 "Hello, World!"
646 );
647 assert!(block_on(fs.try_exists(Path::new("test.md"))).unwrap());
648 block_on(fs.remove_file(Path::new("test.md"))).unwrap();
649 assert!(!block_on(fs.try_exists(Path::new("test.md"))).unwrap());
650 }
651
652 #[test]
653 fn binary_content_round_trips_through_read_but_not_read_to_string() {
654 let fs = InMemoryFs::new();
655 let invalid_utf8 = vec![0xff, 0xfe, 0xfd];
656 block_on(fs.write(Path::new("bin.dat"), &invalid_utf8)).unwrap();
657 assert_eq!(
658 block_on(fs.read(Path::new("bin.dat"))).unwrap(),
659 invalid_utf8
660 );
661 let err = block_on(fs.read_to_string(Path::new("bin.dat"))).unwrap_err();
662 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
663 }
664
665 #[test]
666 fn create_dir_all_creates_parents_implicitly_via_write() {
667 let fs = InMemoryFs::new();
668 block_on(fs.write(Path::new("a/b/c/file.md"), b"Content")).unwrap();
669 assert!(block_on(fs.metadata(Path::new("a"))).unwrap().is_dir());
670 assert!(block_on(fs.metadata(Path::new("a/b"))).unwrap().is_dir());
671 assert!(block_on(fs.metadata(Path::new("a/b/c"))).unwrap().is_dir());
672 assert!(block_on(fs.try_exists(Path::new("a/b/c/file.md"))).unwrap());
673 }
674
675 #[test]
676 fn read_dir_returns_immediate_children_only() {
677 let fs = InMemoryFs::new();
678 block_on(fs.write(Path::new("dir/file1.md"), b"1")).unwrap();
679 block_on(fs.write(Path::new("dir/file2.md"), b"2")).unwrap();
680 block_on(fs.write(Path::new("dir/subdir/file3.md"), b"3")).unwrap();
681
682 let entries = block_on(fs.read_dir(Path::new("dir"))).unwrap();
683 let paths: Vec<PathBuf> = entries.iter().map(|e| e.path().to_path_buf()).collect();
684 assert!(paths.contains(&PathBuf::from("dir/file1.md")));
685 assert!(paths.contains(&PathBuf::from("dir/file2.md")));
686 assert!(paths.contains(&PathBuf::from("dir/subdir")));
687 assert!(!paths.contains(&PathBuf::from("dir/subdir/file3.md")));
688 }
689
690 #[test]
691 fn read_dir_of_an_untracked_directory_is_not_found() {
692 let fs = InMemoryFs::new();
695 let err = block_on(fs.read_dir(Path::new("never/created"))).unwrap_err();
696 assert_eq!(err.kind(), io::ErrorKind::NotFound);
697 }
698
699 #[test]
700 fn read_dir_of_the_root_never_errors() {
701 let fs = InMemoryFs::new();
705 assert!(block_on(fs.read_dir(Path::new(""))).unwrap().is_empty());
706 }
707
708 #[test]
709 fn export_then_import_roundtrip() {
710 let fs = InMemoryFs::new();
711 block_on(fs.write(Path::new("file1.md"), b"Content 1")).unwrap();
712 block_on(fs.write(Path::new("dir/file2.md"), b"Content 2")).unwrap();
713
714 let entries = fs.export_entries();
715 let fs2 = InMemoryFs::load_from_entries(entries);
716
717 assert_eq!(
718 block_on(fs2.read_to_string(Path::new("file1.md"))).unwrap(),
719 "Content 1"
720 );
721 assert_eq!(
722 block_on(fs2.read_to_string(Path::new("dir/file2.md"))).unwrap(),
723 "Content 2"
724 );
725 }
726
727 #[test]
728 fn path_normalization() {
729 let fs = InMemoryFs::new();
730 block_on(fs.write(Path::new("dir/file.md"), b"Content")).unwrap();
731 assert!(block_on(fs.try_exists(Path::new("dir/file.md"))).unwrap());
732 assert!(block_on(fs.try_exists(Path::new("dir/./file.md"))).unwrap());
733 assert!(block_on(fs.try_exists(Path::new("dir/subdir/../file.md"))).unwrap());
734 }
735
736 #[test]
737 fn rename_moves_a_single_file() {
738 let fs = InMemoryFs::new();
739 block_on(fs.write(Path::new("old.md"), b"content")).unwrap();
740 block_on(fs.rename(Path::new("old.md"), Path::new("new.md"))).unwrap();
741 assert!(!block_on(fs.try_exists(Path::new("old.md"))).unwrap());
742 assert_eq!(
743 block_on(fs.read_to_string(Path::new("new.md"))).unwrap(),
744 "content"
745 );
746 }
747
748 #[test]
749 fn rename_moves_a_directory_and_its_contents() {
750 let fs = InMemoryFs::new();
751 block_on(fs.write(Path::new("dir/a.md"), b"a")).unwrap();
752 block_on(fs.write(Path::new("dir/sub/b.md"), b"b")).unwrap();
753
754 block_on(fs.rename(Path::new("dir"), Path::new("moved"))).unwrap();
755
756 assert!(!block_on(fs.try_exists(Path::new("dir/a.md"))).unwrap());
757 assert_eq!(
758 block_on(fs.read_to_string(Path::new("moved/a.md"))).unwrap(),
759 "a"
760 );
761 assert_eq!(
762 block_on(fs.read_to_string(Path::new("moved/sub/b.md"))).unwrap(),
763 "b"
764 );
765 assert!(
766 block_on(fs.metadata(Path::new("moved/sub")))
767 .unwrap()
768 .is_dir()
769 );
770 }
771
772 #[test]
773 fn rename_replaces_an_occupied_file_destination() {
774 let fs = InMemoryFs::new();
780 block_on(fs.write(Path::new("a.md"), b"a")).unwrap();
781 block_on(fs.write(Path::new("b.md"), b"b")).unwrap();
782 block_on(fs.rename(Path::new("a.md"), Path::new("b.md"))).unwrap();
783 assert!(!block_on(fs.try_exists(Path::new("a.md"))).unwrap());
784 assert_eq!(block_on(fs.read_to_string(Path::new("b.md"))).unwrap(), "a");
785 }
786
787 #[test]
788 fn rename_refuses_a_directory_destination() {
789 let fs = InMemoryFs::new();
791 block_on(fs.write(Path::new("a.md"), b"a")).unwrap();
792 block_on(fs.create_dir_all(Path::new("dir"))).unwrap();
793 let err = block_on(fs.rename(Path::new("a.md"), Path::new("dir"))).unwrap_err();
794 assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
795 assert_eq!(block_on(fs.read_to_string(Path::new("a.md"))).unwrap(), "a");
796 }
797
798 #[test]
799 fn a_directory_rename_still_refuses_any_occupied_destination() {
800 let fs = InMemoryFs::new();
804 block_on(fs.write(Path::new("dir/a.md"), b"a")).unwrap();
805 block_on(fs.write(Path::new("other/b.md"), b"b")).unwrap();
806 block_on(fs.write(Path::new("file.md"), b"f")).unwrap();
807 for taken in ["other", "file.md"] {
808 let err = block_on(fs.rename(Path::new("dir"), Path::new(taken))).unwrap_err();
809 assert_eq!(err.kind(), io::ErrorKind::AlreadyExists, "{taken}");
810 }
811 }
812
813 #[test]
819 fn metadata_and_read_follow_a_symlink_to_its_target() {
820 let fs = InMemoryFs::new();
821 block_on(fs.write(Path::new("real.md"), b"hello")).unwrap();
822 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
823
824 let m = block_on(fs.metadata(Path::new("link.md"))).unwrap();
825 assert!(m.is_file());
826 assert!(!m.is_dir());
827
828 assert_eq!(
829 block_on(fs.read_to_string(Path::new("link.md"))).unwrap(),
830 "hello"
831 );
832 }
833
834 #[test]
835 fn read_dir_reports_a_symlink_by_its_own_unfollowed_type() {
836 let fs = InMemoryFs::new();
839 block_on(fs.write(Path::new("real.md"), b"hello")).unwrap();
840 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
841
842 let entries = block_on(fs.read_dir(Path::new(""))).unwrap();
843 let link_entry = entries
844 .iter()
845 .find(|e| e.path() == Path::new("link.md"))
846 .expect("symlink should appear in its parent's listing");
847 assert!(link_entry.file_type().is_symlink());
848
849 let real_entry = entries
850 .iter()
851 .find(|e| e.path() == Path::new("real.md"))
852 .expect("the real file should also be listed");
853 assert!(!real_entry.file_type().is_symlink());
854 }
855
856 #[test]
857 fn a_symlink_to_a_missing_target_is_not_found_by_metadata() {
858 let fs = InMemoryFs::new();
859 fs.add_symlink(Path::new("dangling.md"), Path::new("nowhere.md"));
860 let err = block_on(fs.metadata(Path::new("dangling.md"))).unwrap_err();
861 assert_eq!(err.kind(), io::ErrorKind::NotFound);
862 }
863
864 #[test]
865 fn write_follows_a_link_to_its_target() {
866 let fs = InMemoryFs::new();
870 block_on(fs.write(Path::new("real.md"), b"old")).unwrap();
871 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
872
873 block_on(fs.write(Path::new("link.md"), b"new")).unwrap();
874
875 assert_eq!(
876 block_on(fs.read_to_string(Path::new("real.md"))).unwrap(),
877 "new",
878 "the bytes must land in the target"
879 );
880 assert_eq!(
881 block_on(fs.read_to_string(Path::new("link.md"))).unwrap(),
882 "new"
883 );
884 assert_eq!(
885 block_on(fs.read_link(Path::new("link.md"))).unwrap(),
886 Some(PathBuf::from("real.md")),
887 "the link itself must still stand"
888 );
889 }
890
891 #[test]
892 fn write_atomic_replaces_a_link_rather_than_writing_through_it() {
893 let fs = InMemoryFs::new();
896 block_on(fs.write(Path::new("real.md"), b"target bytes")).unwrap();
897 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
898
899 block_on(fs.write_atomic(Path::new("link.md"), b"a file now")).unwrap();
900
901 assert_eq!(
902 block_on(fs.read_link(Path::new("link.md")))
903 .unwrap_err()
904 .kind(),
905 io::ErrorKind::InvalidInput,
906 "the link must be gone, replaced by a regular file"
907 );
908 assert_eq!(
909 block_on(fs.read_to_string(Path::new("link.md"))).unwrap(),
910 "a file now"
911 );
912 assert_eq!(
913 block_on(fs.read_to_string(Path::new("real.md"))).unwrap(),
914 "target bytes",
915 "nothing may be written through the link"
916 );
917 }
918
919 #[test]
920 fn rename_moves_a_link_as_a_link_and_replaces_one_at_the_destination() {
921 let fs = InMemoryFs::new();
922 block_on(fs.write(Path::new("real.md"), b"content")).unwrap();
923 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
924
925 block_on(fs.rename(Path::new("link.md"), Path::new("moved.md"))).unwrap();
926 assert_eq!(
927 block_on(fs.read_link(Path::new("moved.md"))).unwrap(),
928 Some(PathBuf::from("real.md"))
929 );
930 assert!(!block_on(fs.try_exists(Path::new("link.md"))).unwrap());
931
932 block_on(fs.write(Path::new("other.md"), b"other")).unwrap();
935 block_on(fs.rename(Path::new("other.md"), Path::new("moved.md"))).unwrap();
936 assert_eq!(
937 block_on(fs.read_link(Path::new("moved.md")))
938 .unwrap_err()
939 .kind(),
940 ErrorKind::InvalidInput,
941 "the link must be gone, replaced by the renamed file"
942 );
943 assert_eq!(
944 block_on(fs.read_to_string(Path::new("moved.md"))).unwrap(),
945 "other"
946 );
947 assert_eq!(
948 block_on(fs.read_to_string(Path::new("real.md"))).unwrap(),
949 "content",
950 "nothing may be renamed through the link"
951 );
952 }
953
954 #[test]
955 fn removing_a_symlink_leaves_its_target_untouched() {
956 let fs = InMemoryFs::new();
957 block_on(fs.write(Path::new("real.md"), b"hello")).unwrap();
958 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
959
960 block_on(fs.remove_file(Path::new("link.md"))).unwrap();
961
962 assert!(!block_on(fs.try_exists(Path::new("link.md"))).unwrap());
963 assert_eq!(
964 block_on(fs.read_to_string(Path::new("real.md"))).unwrap(),
965 "hello"
966 );
967 }
968
969 #[test]
972 fn create_new_writes_a_fresh_file_and_refuses_a_second() {
973 let fs = InMemoryFs::new();
974 block_on(fs.create_new(Path::new("once.md"), b"first")).unwrap();
975 assert_eq!(
976 block_on(fs.read_to_string(Path::new("once.md"))).unwrap(),
977 "first"
978 );
979 let err = block_on(fs.create_new(Path::new("once.md"), b"second")).unwrap_err();
980 assert_eq!(err.kind(), ErrorKind::AlreadyExists);
981 assert_eq!(
982 block_on(fs.read_to_string(Path::new("once.md"))).unwrap(),
983 "first",
984 "the loser must have changed nothing"
985 );
986 }
987
988 #[test]
989 fn create_new_counts_every_kind_of_occupant() {
990 let fs = InMemoryFs::new();
993 block_on(fs.create_dir_all(Path::new("dir"))).unwrap();
994 block_on(fs.write(Path::new("bin.dat"), &[0xff, 0xfe])).unwrap();
995 block_on(fs.write(Path::new("real.md"), b"hello")).unwrap();
996 fs.add_symlink(Path::new("link.md"), Path::new("real.md"));
997
998 for taken in ["dir", "bin.dat", "link.md"] {
999 let err = block_on(fs.create_new(Path::new(taken), b"x")).unwrap_err();
1000 assert_eq!(err.kind(), ErrorKind::AlreadyExists, "{taken}");
1001 }
1002 }
1003
1004 #[test]
1005 fn in_memory_declares_exclusive_create() {
1006 assert!(InMemoryFs::new().capabilities().exclusive_create);
1007 }
1008
1009 #[test]
1012 fn in_memory_declares_atomic_replace_but_no_durability_across_a_restart() {
1013 let fs = InMemoryFs::new();
1014 let caps = fs.capabilities();
1015 assert!(
1016 caps.atomic_replace,
1017 "a single locked write is already atomic"
1018 );
1019 assert_eq!(
1020 caps.sync_guarantee,
1021 super::super::SyncGuarantee::None,
1022 "nothing here survives the process exiting, so there is not even an \
1023 ordering worth promising against a crash"
1024 );
1025 assert!(
1026 !caps.native_transactions,
1027 "the lock covers one call, not a batch of several committed together"
1028 );
1029 }
1030
1031 #[test]
1032 fn write_atomic_lands_the_new_contents_without_a_temp_sibling() {
1033 let fs = InMemoryFs::new();
1034 block_on(fs.write(Path::new("doc.md"), b"old")).unwrap();
1035 block_on(fs.write_atomic(Path::new("doc.md"), b"new")).unwrap();
1036
1037 assert_eq!(
1038 block_on(fs.read_to_string(Path::new("doc.md"))).unwrap(),
1039 "new"
1040 );
1041 let entries = block_on(fs.read_dir(Path::new(""))).unwrap();
1045 assert_eq!(entries.len(), 1, "no stray temp-sibling entry: {entries:?}");
1046 }
1047
1048 #[test]
1051 fn clones_share_the_same_backing_store() {
1052 let fs = InMemoryFs::new();
1053 let clone = fs.clone();
1054 block_on(fs.write(Path::new("shared.md"), b"visible everywhere")).unwrap();
1055 assert_eq!(
1056 block_on(clone.read_to_string(Path::new("shared.md"))).unwrap(),
1057 "visible everywhere"
1058 );
1059 }
1060
1061 #[test]
1062 fn clear_empties_every_store() {
1063 let fs = InMemoryFs::new();
1064 block_on(fs.write(Path::new("a.md"), b"a")).unwrap();
1065 fs.add_symlink(Path::new("link.md"), Path::new("a.md"));
1066
1067 fs.clear();
1068
1069 assert!(!block_on(fs.try_exists(Path::new("a.md"))).unwrap());
1070 assert!(block_on(fs.metadata(Path::new("link.md"))).is_err());
1071 }
1072}