1use std::{collections::HashMap, ffi::OsString, path::Path, sync::Arc};
2
3use anyhow::anyhow;
4use async_trait::async_trait;
5use tokio::sync::RwLock;
6
7use super::{FileSystem, FileSystemResult};
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum InMemoryFile {
11 File { mode: u32, contents: Vec<u8> },
12 Directory { mode: u32 },
13}
14
15impl InMemoryFile {
16 pub fn file<C>(contents: C) -> Self
17 where
18 C: AsRef<str>,
19 {
20 Self::file_raw(contents.as_ref())
21 }
22
23 pub fn file_raw<C>(contents: C) -> Self
24 where
25 C: AsRef<[u8]>,
26 {
27 Self::File {
28 mode: 0o664,
29 contents: contents.as_ref().to_vec(),
30 }
31 }
32
33 pub fn empty() -> Self {
34 Self::file_raw(vec![])
35 }
36
37 pub fn dir() -> Self {
38 Self::Directory { mode: 0o775 }
39 }
40
41 pub fn mode(&self) -> u32 {
42 match *self {
43 Self::File { mode, .. } => mode,
44 Self::Directory { mode, .. } => mode,
45 }
46 }
47
48 pub fn contents_raw(&self) -> Option<Vec<u8>> {
49 match self {
50 Self::File { contents, .. } => Some(contents.to_vec()),
51 Self::Directory { .. } => None,
52 }
53 }
54
55 pub fn contents(&self) -> Option<String> {
56 match self {
57 Self::File { contents, .. } => Some(String::from_utf8_lossy(contents).to_string()),
58 Self::Directory { .. } => None,
59 }
60 }
61}
62
63#[derive(Default, Debug, Clone)]
64pub struct InMemoryFileSystem {
65 pub files: Arc<RwLock<HashMap<OsString, InMemoryFile>>>,
66}
67
68impl InMemoryFileSystem {
69 pub fn new(files: HashMap<OsString, InMemoryFile>) -> Self {
70 Self {
71 files: Arc::new(RwLock::new(files)),
72 }
73 }
74}
75
76#[async_trait]
77impl FileSystem for InMemoryFileSystem {
78 async fn create_dir<P>(&self, path: P) -> FileSystemResult<()>
79 where
80 P: AsRef<Path> + Send,
81 {
82 let path = path.as_ref();
83 let os_path = path.as_os_str();
84 match self.files.read().await.get(os_path) {
85 Some(InMemoryFile::File { .. }) => {
86 Err(anyhow!("file {:?} already exists", os_path.to_owned(),))?
87 },
88 Some(InMemoryFile::Directory { .. }) => {
89 Err(anyhow!("directory {:?} already exists", os_path.to_owned(),))?
90 },
91 None => {},
92 };
93
94 for path in path.ancestors().skip(1) {
95 match self.files.read().await.get(path.as_os_str()) {
96 Some(InMemoryFile::Directory { .. }) => continue,
97 Some(InMemoryFile::File { .. }) => Err(anyhow!(
98 "ancestor {:?} is not a directory",
99 path.as_os_str(),
100 ))?,
101 None => Err(anyhow!("ancestor {:?} doesn't exists", path.as_os_str(),))?,
102 };
103 }
104
105 self.files
106 .write()
107 .await
108 .insert(os_path.to_owned(), InMemoryFile::dir());
109
110 Ok(())
111 }
112
113 async fn create_dir_all<P>(&self, path: P) -> FileSystemResult<()>
114 where
115 P: AsRef<Path> + Send,
116 {
117 let path = path.as_ref();
118 let mut files = self.files.write().await;
119 let ancestors = path
120 .ancestors()
121 .collect::<Vec<&Path>>()
122 .into_iter()
123 .rev()
124 .skip(1);
125
126 for path in ancestors {
127 match files.get(path.as_os_str()) {
128 Some(InMemoryFile::Directory { .. }) => continue,
129 Some(InMemoryFile::File { .. }) => Err(anyhow!(
130 "ancestor {:?} is not a directory",
131 path.as_os_str().to_owned(),
132 ))?,
133 None => files.insert(path.as_os_str().to_owned(), InMemoryFile::dir()),
134 };
135 }
136
137 Ok(())
138 }
139
140 async fn read<P>(&self, path: P) -> FileSystemResult<Vec<u8>>
141 where
142 P: AsRef<Path> + Send,
143 {
144 let os_path = path.as_ref().as_os_str();
145
146 match self.files.read().await.get(os_path) {
147 Some(InMemoryFile::File { contents, .. }) => Ok(contents.clone()),
148 Some(InMemoryFile::Directory { .. }) => {
149 Err(anyhow!("file {:?} is a directory", os_path).into())
150 },
151 None => Err(anyhow!("file {:?} not found", os_path).into()),
152 }
153 }
154
155 async fn read_to_string<P>(&self, path: P) -> FileSystemResult<String>
156 where
157 P: AsRef<Path> + Send,
158 {
159 let os_path = path.as_ref().as_os_str().to_owned();
160 let content = self.read(path).await?;
161
162 String::from_utf8(content)
163 .map_err(|_| anyhow!("invalid utf-8 encoding for file {:?}", os_path).into())
164 }
165
166 async fn write<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
167 where
168 P: AsRef<Path> + Send,
169 C: AsRef<[u8]> + Send,
170 {
171 let path = path.as_ref();
172 let os_path = path.as_os_str();
173 let mut files = self.files.write().await;
174
175 for path in path.ancestors().skip(1) {
176 match files.get(path.as_os_str()) {
177 Some(InMemoryFile::Directory { .. }) => continue,
178 Some(InMemoryFile::File { .. }) => Err(anyhow!(
179 "ancestor {:?} is not a directory",
180 path.as_os_str()
181 ))?,
182 None => Err(anyhow!("ancestor {:?} doesn't exists", path.as_os_str()))?,
183 };
184 }
185
186 if let Some(InMemoryFile::Directory { .. }) = files.get(os_path) {
187 return Err(anyhow!("file {:?} is a directory", os_path).into());
188 }
189
190 files.insert(os_path.to_owned(), InMemoryFile::file_raw(contents));
191
192 Ok(())
193 }
194
195 async fn append<P, C>(&self, path: P, contents: C) -> FileSystemResult<()>
196 where
197 P: AsRef<Path> + Send,
198 C: AsRef<[u8]> + Send,
199 {
200 let path = path.as_ref();
201 let mut existing_contents = match self.read(path).await {
202 Ok(existing_contents) => existing_contents,
203 Err(err) if err.to_string() == format!("file {:?} not found", path.as_os_str()) => {
204 vec![]
205 },
206 Err(err) => Err(err)?,
207 };
208 existing_contents.append(&mut contents.as_ref().to_vec());
209
210 self.write(path, existing_contents).await
211 }
212
213 async fn copy<P1, P2>(&self, from: P1, to: P2) -> FileSystemResult<()>
214 where
215 P1: AsRef<Path> + Send,
216 P2: AsRef<Path> + Send,
217 {
218 let from_ref = from.as_ref();
219 let to_ref = to.as_ref();
220 let content = self.read(from_ref).await?;
221
222 self.write(to_ref, content).await
223 }
224
225 async fn set_mode<P>(&self, path: P, mode: u32) -> FileSystemResult<()>
226 where
227 P: AsRef<Path> + Send,
228 {
229 let os_path = path.as_ref().as_os_str();
230 if let Some(file) = self.files.write().await.get_mut(os_path) {
231 match file {
232 InMemoryFile::File { mode: old_mode, .. } => {
233 *old_mode = mode;
234 },
235 InMemoryFile::Directory { mode: old_mode, .. } => {
236 *old_mode = mode;
237 },
238 };
239 Ok(())
240 } else {
241 Err(anyhow!("file {:?} not found", os_path).into())
242 }
243 }
244
245 async fn exists<P>(&self, path: P) -> bool
246 where
247 P: AsRef<Path> + Send,
248 {
249 self.files
250 .read()
251 .await
252 .contains_key(path.as_ref().as_os_str())
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use std::str::FromStr;
259
260 use super::*;
261
262 #[tokio::test]
263 async fn create_dir_should_create_a_directory_at_root() {
264 let fs = InMemoryFileSystem::new(HashMap::from([(
265 OsString::from_str("/").unwrap(),
266 InMemoryFile::dir(),
267 )]));
268
269 fs.create_dir("/dir").await.unwrap();
270
271 assert_eq!(fs.files.read().await.len(), 2);
272 assert!(matches!(
273 fs.files
274 .read()
275 .await
276 .get(&OsString::from_str("/dir").unwrap())
277 .unwrap(),
278 InMemoryFile::Directory { mode } if *mode == 0o775
279 ));
280 }
281
282 #[tokio::test]
283 async fn create_dir_should_return_an_error_if_directory_already_exists() {
284 let fs = InMemoryFileSystem::new(HashMap::from([
285 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
286 (OsString::from_str("/dir").unwrap(), InMemoryFile::dir()),
287 ]));
288
289 let err = fs.create_dir("/dir").await.unwrap_err();
290
291 assert_eq!(fs.files.read().await.len(), 2);
292 assert_eq!(err.to_string(), "directory \"/dir\" already exists");
293 }
294
295 #[tokio::test]
296 async fn create_dir_should_return_an_error_if_file_already_exists() {
297 let fs = InMemoryFileSystem::new(HashMap::from([
298 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
299 (OsString::from_str("/dir").unwrap(), InMemoryFile::empty()),
300 ]));
301
302 let err = fs.create_dir("/dir").await.unwrap_err();
303
304 assert_eq!(fs.files.read().await.len(), 2);
305 assert_eq!(err.to_string(), "file \"/dir\" already exists");
306 }
307
308 #[tokio::test]
309 async fn create_dir_should_create_a_directory_if_all_ancestors_exist() {
310 let fs = InMemoryFileSystem::new(HashMap::from([
311 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
312 (OsString::from_str("/path").unwrap(), InMemoryFile::dir()),
313 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
314 (
315 OsString::from_str("/path/to/my").unwrap(),
316 InMemoryFile::dir(),
317 ),
318 ]));
319
320 fs.create_dir("/path/to/my/dir").await.unwrap();
321
322 assert_eq!(fs.files.read().await.len(), 5);
323 assert!(matches!(
324 fs.files
325 .read()
326 .await
327 .get(&OsString::from_str("/path/to/my/dir").unwrap())
328 .unwrap(),
329 InMemoryFile::Directory { mode} if *mode == 0o775
330 ));
331 }
332
333 #[tokio::test]
334 async fn create_dir_should_return_an_error_if_some_directory_ancestor_doesnt_exists() {
335 let fs = InMemoryFileSystem::new(HashMap::from([
336 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
337 (OsString::from_str("/path").unwrap(), InMemoryFile::dir()),
338 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
339 ]));
340
341 let err = fs.create_dir("/path/to/my/dir").await.unwrap_err();
342
343 assert_eq!(fs.files.read().await.len(), 3);
344 assert_eq!(err.to_string(), "ancestor \"/path/to/my\" doesn't exists");
345 }
346
347 #[tokio::test]
348 async fn create_dir_should_return_an_error_if_some_ancestor_is_not_a_directory() {
349 let fs = InMemoryFileSystem::new(HashMap::from([
350 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
351 (OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
352 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
353 (
354 OsString::from_str("/path/to/my").unwrap(),
355 InMemoryFile::dir(),
356 ),
357 ]));
358
359 let err = fs.create_dir("/path/to/my/dir").await.unwrap_err();
360
361 assert_eq!(fs.files.read().await.len(), 4);
362 assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
363 }
364
365 #[tokio::test]
366 async fn create_dir_all_should_create_a_directory_and_all_its_ancestors_if_they_dont_exist() {
367 let fs = InMemoryFileSystem::new(HashMap::from([(
368 OsString::from_str("/").unwrap(),
369 InMemoryFile::dir(),
370 )]));
371
372 fs.create_dir_all("/path/to/my/dir").await.unwrap();
373
374 assert_eq!(fs.files.read().await.len(), 5);
375 assert!(matches!(
376 fs.files
377 .read()
378 .await
379 .get(&OsString::from_str("/path").unwrap())
380 .unwrap(),
381 InMemoryFile::Directory { mode } if *mode == 0o775
382 ));
383 assert!(matches!(
384 fs.files
385 .read()
386 .await
387 .get(&OsString::from_str("/path/to").unwrap())
388 .unwrap(),
389 InMemoryFile::Directory { mode } if *mode == 0o775
390 ));
391 assert!(matches!(
392 fs.files
393 .read()
394 .await
395 .get(&OsString::from_str("/path/to/my").unwrap())
396 .unwrap(),
397 InMemoryFile::Directory { mode } if *mode == 0o775
398 ));
399 assert!(matches!(
400 fs.files
401 .read()
402 .await
403 .get(&OsString::from_str("/path/to/my/dir").unwrap())
404 .unwrap(),
405 InMemoryFile::Directory { mode } if *mode == 0o775
406 ));
407 }
408
409 #[tokio::test]
410 async fn create_dir_all_should_create_a_directory_and_some_of_its_ancestors_if_they_dont_exist()
411 {
412 let fs = InMemoryFileSystem::new(HashMap::from([
413 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
414 (OsString::from_str("/path").unwrap(), InMemoryFile::dir()),
415 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
416 ]));
417
418 fs.create_dir_all("/path/to/my/dir").await.unwrap();
419
420 assert_eq!(fs.files.read().await.len(), 5);
421 assert!(matches!(
422 fs.files
423 .read()
424 .await
425 .get(&OsString::from_str("/path/to/my").unwrap())
426 .unwrap(),
427 InMemoryFile::Directory { mode } if *mode == 0o775
428 ));
429 assert!(matches!(
430 fs.files
431 .read()
432 .await
433 .get(&OsString::from_str("/path/to/my/dir").unwrap())
434 .unwrap(),
435 InMemoryFile::Directory { mode } if *mode == 0o775
436 ));
437 }
438
439 #[tokio::test]
440 async fn create_dir_all_should_return_an_error_if_some_ancestor_is_not_a_directory() {
441 let fs = InMemoryFileSystem::new(HashMap::from([
442 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
443 (OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
444 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
445 ]));
446
447 let err = fs.create_dir_all("/path/to/my/dir").await.unwrap_err();
448
449 assert_eq!(fs.files.read().await.len(), 3);
450 assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
451 }
452
453 #[tokio::test]
454 async fn read_should_return_the_file_content() {
455 let fs = InMemoryFileSystem::new(HashMap::from([(
456 OsString::from_str("/myfile").unwrap(),
457 InMemoryFile::file("content"),
458 )]));
459
460 let content = fs.read("/myfile").await.unwrap();
461
462 assert_eq!(content, "content".as_bytes().to_vec());
463 }
464
465 #[tokio::test]
466 async fn read_should_return_an_error_if_file_doesnt_exists() {
467 let fs = InMemoryFileSystem::new(HashMap::new());
468
469 let err = fs.read("/myfile").await.unwrap_err();
470
471 assert_eq!(err.to_string(), "file \"/myfile\" not found");
472 }
473
474 #[tokio::test]
475 async fn read_should_return_an_error_if_file_is_a_directory() {
476 let fs = InMemoryFileSystem::new(HashMap::from([(
477 OsString::from_str("/myfile").unwrap(),
478 InMemoryFile::dir(),
479 )]));
480
481 let err = fs.read("/myfile").await.unwrap_err();
482
483 assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
484 }
485
486 #[tokio::test]
487 async fn read_to_string_should_return_the_file_content_as_a_string() {
488 let fs = InMemoryFileSystem::new(HashMap::from([(
489 OsString::from_str("/myfile").unwrap(),
490 InMemoryFile::file("content"),
491 )]));
492
493 let content = fs.read_to_string("/myfile").await.unwrap();
494
495 assert_eq!(content, "content");
496 }
497
498 #[tokio::test]
499 async fn read_to_string_should_return_an_error_if_file_doesnt_exists() {
500 let fs = InMemoryFileSystem::new(HashMap::new());
501
502 let err = fs.read_to_string("/myfile").await.unwrap_err();
503
504 assert_eq!(err.to_string(), "file \"/myfile\" not found");
505 }
506
507 #[tokio::test]
508 async fn read_to_string_should_return_an_error_if_file_is_a_directory() {
509 let fs = InMemoryFileSystem::new(HashMap::from([(
510 OsString::from_str("/myfile").unwrap(),
511 InMemoryFile::dir(),
512 )]));
513
514 let err = fs.read_to_string("/myfile").await.unwrap_err();
515
516 assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
517 }
518
519 #[tokio::test]
520 async fn read_to_string_should_return_an_error_if_file_isnt_utf8_encoded() {
521 let fs = InMemoryFileSystem::new(HashMap::from([(
522 OsString::from_str("/myfile").unwrap(),
523 InMemoryFile::file_raw(vec![0xC3, 0x28]),
524 )]));
525
526 let err = fs.read_to_string("/myfile").await.unwrap_err();
527
528 assert_eq!(
529 err.to_string(),
530 "invalid utf-8 encoding for file \"/myfile\""
531 );
532 }
533
534 #[tokio::test]
535 async fn write_should_create_file_with_content_if_file_doesnt_exists() {
536 let fs = InMemoryFileSystem::new(HashMap::from([(
537 OsString::from_str("/").unwrap(),
538 InMemoryFile::dir(),
539 )]));
540
541 fs.write("/myfile", "my file content").await.unwrap();
542
543 assert_eq!(fs.files.read().await.len(), 2);
544 assert!(matches!(
545 fs.files
546 .read()
547 .await
548 .get(&OsString::from_str("/myfile").unwrap()),
549 Some(InMemoryFile::File {mode, contents, .. }) if *mode == 0o664 && contents == "my file content".as_bytes()
550 ));
551 }
552
553 #[tokio::test]
554 async fn write_should_overwrite_file_content_if_file_exists() {
555 let fs = InMemoryFileSystem::new(HashMap::from([
556 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
557 (
558 OsString::from_str("/myfile").unwrap(),
559 InMemoryFile::file("my file content"),
560 ),
561 ]));
562
563 fs.write("/myfile", "my new file content").await.unwrap();
564
565 assert_eq!(fs.files.read().await.len(), 2);
566 assert!(matches!(
567 fs.files
568 .read()
569 .await
570 .get(&OsString::from_str("/myfile").unwrap()),
571 Some(InMemoryFile::File { mode, contents, .. }) if *mode == 0o664 && contents == "my new file content".as_bytes()
572 ));
573 }
574
575 #[tokio::test]
576 async fn write_should_return_an_error_if_file_is_a_directory() {
577 let fs = InMemoryFileSystem::new(HashMap::from([
578 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
579 (OsString::from_str("/myfile").unwrap(), InMemoryFile::dir()),
580 ]));
581
582 let err = fs.write("/myfile", "my file content").await.unwrap_err();
583
584 assert_eq!(fs.files.read().await.len(), 2);
585 assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
586 }
587
588 #[tokio::test]
589 async fn write_should_return_an_error_if_file_is_new_and_some_ancestor_doesnt_exists() {
590 let fs = InMemoryFileSystem::new(HashMap::from([
591 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
592 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
593 ]));
594
595 let err = fs
596 .write("/path/to/myfile", "my file content")
597 .await
598 .unwrap_err();
599
600 assert_eq!(fs.files.read().await.len(), 2);
601 assert_eq!(err.to_string(), "ancestor \"/path\" doesn't exists");
602 }
603
604 #[tokio::test]
605 async fn write_should_return_an_error_if_file_is_new_and_some_ancestor_is_not_a_directory() {
606 let fs = InMemoryFileSystem::new(HashMap::from([
607 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
608 (OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
609 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
610 ]));
611
612 let err = fs
613 .write("/path/to/myfile", "my file content")
614 .await
615 .unwrap_err();
616
617 assert_eq!(fs.files.read().await.len(), 3);
618 assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
619 }
620
621 #[tokio::test]
622 async fn append_should_update_file_content_if_file_exists() {
623 let fs = InMemoryFileSystem::new(HashMap::from([
624 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
625 (
626 OsString::from_str("/myfile").unwrap(),
627 InMemoryFile::file("my file content"),
628 ),
629 ]));
630
631 fs.append("/myfile", " has been updated with new things")
632 .await
633 .unwrap();
634
635 assert_eq!(fs.files.read().await.len(), 2);
636 assert!(matches!(
637 fs.files
638 .read()
639 .await
640 .get(&OsString::from_str("/myfile").unwrap()),
641 Some(InMemoryFile::File { mode, contents, .. }) if *mode == 0o664 && contents == "my file content has been updated with new things".as_bytes()
642 ));
643 }
644
645 #[tokio::test]
646 async fn append_should_create_file_with_content_if_file_doesnt_exists() {
647 let fs = InMemoryFileSystem::new(HashMap::from([(
648 OsString::from_str("/").unwrap(),
649 InMemoryFile::dir(),
650 )]));
651
652 fs.append("/myfile", "my file content").await.unwrap();
653
654 assert_eq!(fs.files.read().await.len(), 2);
655 assert!(matches!(
656 fs.files
657 .read()
658 .await
659 .get(&OsString::from_str("/myfile").unwrap()),
660 Some(InMemoryFile::File { mode,contents, .. }) if *mode == 0o664 && contents == "my file content".as_bytes()
661 ));
662 }
663
664 #[tokio::test]
665 async fn append_should_return_an_error_if_file_is_a_directory() {
666 let fs = InMemoryFileSystem::new(HashMap::from([(
667 OsString::from_str("/myfile").unwrap(),
668 InMemoryFile::dir(),
669 )]));
670
671 let err = fs.append("/myfile", "my file content").await.unwrap_err();
672
673 assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
674 }
675
676 #[tokio::test]
677 async fn append_should_return_an_error_if_file_is_new_and_some_ancestor_doesnt_exists() {
678 let fs = InMemoryFileSystem::new(HashMap::from([
679 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
680 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
681 ]));
682
683 let err = fs
684 .append("/path/to/myfile", "my file content")
685 .await
686 .unwrap_err();
687
688 assert_eq!(fs.files.read().await.len(), 2);
689 assert_eq!(err.to_string(), "ancestor \"/path\" doesn't exists");
690 }
691
692 #[tokio::test]
693 async fn append_should_return_an_error_if_file_is_new_and_some_ancestor_is_not_a_directory() {
694 let fs = InMemoryFileSystem::new(HashMap::from([
695 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
696 (OsString::from_str("/path").unwrap(), InMemoryFile::empty()),
697 (OsString::from_str("/path/to").unwrap(), InMemoryFile::dir()),
698 ]));
699
700 let err = fs
701 .append("/path/to/myfile", "my file content")
702 .await
703 .unwrap_err();
704
705 assert_eq!(fs.files.read().await.len(), 3);
706 assert_eq!(err.to_string(), "ancestor \"/path\" is not a directory");
707 }
708
709 #[tokio::test]
710 async fn copy_should_creates_new_destination_file_if_it_doesnt_exists() {
711 let fs = InMemoryFileSystem::new(HashMap::from([
712 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
713 (
714 OsString::from_str("/myfile").unwrap(),
715 InMemoryFile::file("my file content"),
716 ),
717 ]));
718
719 fs.copy("/myfile", "/myfilecopy").await.unwrap();
720
721 assert_eq!(fs.files.read().await.len(), 3);
722 assert!(
723 matches!(fs.files.read().await.get(&OsString::from_str("/myfilecopy").unwrap()).unwrap(), InMemoryFile::File { mode, contents, .. } if *mode == 0o664 && contents == "my file content".as_bytes())
724 );
725 }
726
727 #[tokio::test]
728 async fn copy_should_updates_the_file_content_of_the_destination_file_if_it_already_exists() {
729 let fs = InMemoryFileSystem::new(HashMap::from([
730 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
731 (
732 OsString::from_str("/myfile").unwrap(),
733 InMemoryFile::file("my new file content"),
734 ),
735 (
736 OsString::from_str("/myfilecopy").unwrap(),
737 InMemoryFile::file("my file content"),
738 ),
739 ]));
740
741 fs.copy("/myfile", "/myfilecopy").await.unwrap();
742
743 assert_eq!(fs.files.read().await.len(), 3);
744 assert!(
745 matches!(fs.files.read().await.get(&OsString::from_str("/myfilecopy").unwrap()).unwrap(), InMemoryFile::File { mode, contents, .. } if *mode == 0o664 && contents == "my new file content".as_bytes())
746 );
747 }
748
749 #[tokio::test]
750 async fn copy_should_returns_an_error_if_source_file_doesnt_exists() {
751 let fs = InMemoryFileSystem::new(HashMap::from([(
752 OsString::from_str("/").unwrap(),
753 InMemoryFile::dir(),
754 )]));
755
756 let err = fs.copy("/myfile", "/mfilecopy").await.unwrap_err();
757
758 assert_eq!(err.to_string(), "file \"/myfile\" not found");
759 }
760
761 #[tokio::test]
762 async fn copy_should_returns_an_error_if_source_file_is_a_directory() {
763 let fs = InMemoryFileSystem::new(HashMap::from([
764 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
765 (OsString::from_str("/myfile").unwrap(), InMemoryFile::dir()),
766 ]));
767
768 let err = fs.copy("/myfile", "/mfilecopy").await.unwrap_err();
769
770 assert_eq!(err.to_string(), "file \"/myfile\" is a directory");
771 }
772
773 #[tokio::test]
774 async fn copy_should_returns_an_error_if_destination_file_is_a_directory() {
775 let fs = InMemoryFileSystem::new(HashMap::from([
776 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
777 (
778 OsString::from_str("/myfile").unwrap(),
779 InMemoryFile::file("my file content"),
780 ),
781 (
782 OsString::from_str("/myfilecopy").unwrap(),
783 InMemoryFile::dir(),
784 ),
785 ]));
786
787 let err = fs.copy("/myfile", "/myfilecopy").await.unwrap_err();
788
789 assert_eq!(err.to_string(), "file \"/myfilecopy\" is a directory");
790 }
791
792 #[tokio::test]
793 async fn copy_should_returns_an_error_if_destination_file_is_new_and_some_ancestor_doesnt_exists(
794 ) {
795 let fs = InMemoryFileSystem::new(HashMap::from([
796 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
797 (
798 OsString::from_str("/myfile").unwrap(),
799 InMemoryFile::file("my file content"),
800 ),
801 ]));
802
803 let err = fs.copy("/myfile", "/somedir/myfilecopy").await.unwrap_err();
804
805 assert_eq!(fs.files.read().await.len(), 2);
806 assert_eq!(err.to_string(), "ancestor \"/somedir\" doesn't exists");
807 }
808
809 #[tokio::test]
810 async fn copy_should_returns_an_error_if_destination_file_is_new_and_some_ancestor_is_not_a_directory(
811 ) {
812 let fs = InMemoryFileSystem::new(HashMap::from([
813 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
814 (
815 OsString::from_str("/myfile").unwrap(),
816 InMemoryFile::file("my file content"),
817 ),
818 (
819 OsString::from_str("/mypath").unwrap(),
820 InMemoryFile::empty(),
821 ),
822 ]));
823
824 let err = fs.copy("/myfile", "/mypath/myfilecopy").await.unwrap_err();
825
826 assert_eq!(fs.files.read().await.len(), 3);
827 assert_eq!(err.to_string(), "ancestor \"/mypath\" is not a directory");
828 }
829
830 #[tokio::test]
831 async fn set_mode_should_update_the_file_mode_at_path() {
832 let fs = InMemoryFileSystem::new(HashMap::from([
833 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
834 (
835 OsString::from_str("/myfile").unwrap(),
836 InMemoryFile::file("my file content"),
837 ),
838 ]));
839 assert!(
840 matches!(fs.files.read().await.get(&OsString::from_str("/myfile").unwrap()).unwrap(), InMemoryFile::File { mode, .. } if *mode == 0o664)
841 );
842
843 fs.set_mode("/myfile", 0o400).await.unwrap();
844
845 assert!(
846 matches!(fs.files.read().await.get(&OsString::from_str("/myfile").unwrap()).unwrap(), InMemoryFile::File { mode, .. } if *mode == 0o400)
847 );
848 }
849
850 #[tokio::test]
851 async fn set_mode_should_update_the_directory_mode_at_path() {
852 let fs = InMemoryFileSystem::new(HashMap::from([
853 (OsString::from_str("/").unwrap(), InMemoryFile::dir()),
854 (OsString::from_str("/mydir").unwrap(), InMemoryFile::dir()),
855 ]));
856 assert!(
857 matches!(fs.files.read().await.get(&OsString::from_str("/mydir").unwrap()).unwrap(), InMemoryFile::Directory { mode } if *mode == 0o775)
858 );
859
860 fs.set_mode("/mydir", 0o700).await.unwrap();
861
862 assert!(
863 matches!(fs.files.read().await.get(&OsString::from_str("/mydir").unwrap()).unwrap(), InMemoryFile::Directory { mode } if *mode == 0o700)
864 );
865 }
866
867 #[tokio::test]
868 async fn set_mode_should_returns_an_error_if_file_doesnt_exists() {
869 let fs = InMemoryFileSystem::new(HashMap::from([(
870 OsString::from_str("/").unwrap(),
871 InMemoryFile::dir(),
872 )]));
873 let err = fs.set_mode("/myfile", 0o400).await.unwrap_err();
876
877 assert_eq!(err.to_string(), "file \"/myfile\" not found");
878 }
879}