1use std::ffi::OsString;
6use std::fs::{DirBuilder, File, OpenOptions};
7use std::io::{self, Write};
8use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
9use std::path::Path;
10
11pub const FILE_MODE: u32 = 0o600;
15
16pub const DIR_MODE: u32 = 0o700;
18
19pub trait Fs: Send + Sync {
21 fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()>;
29 fn sync_file(&self, path: &Path) -> io::Result<()>;
35 fn rename(&self, from: &Path, to: &Path) -> io::Result<()>;
41 fn sync_dir(&self, dir: &Path) -> io::Result<()>;
48 fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
54 fn remove(&self, path: &Path) -> io::Result<()>;
61 fn list(&self, dir: &Path) -> io::Result<Vec<OsString>>;
67 fn create_dir_all(&self, dir: &Path) -> io::Result<()>;
74 fn remove_dir_all(&self, dir: &Path) -> io::Result<()>;
82}
83
84#[derive(Debug, Clone, Copy, Default)]
88pub struct OsFs;
89
90impl Fs for OsFs {
91 fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()> {
92 OpenOptions::new()
93 .write(true)
94 .create_new(true)
95 .mode(FILE_MODE)
96 .open(path)?
97 .write_all(data)
98 }
99
100 fn sync_file(&self, path: &Path) -> io::Result<()> {
101 File::open(path)?.sync_all()
102 }
103
104 fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
105 std::fs::rename(from, to)
106 }
107
108 fn sync_dir(&self, dir: &Path) -> io::Result<()> {
109 File::open(dir)?.sync_all()
110 }
111
112 fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
113 std::fs::read(path)
114 }
115
116 fn remove(&self, path: &Path) -> io::Result<()> {
117 std::fs::remove_file(path)
118 }
119
120 fn list(&self, dir: &Path) -> io::Result<Vec<OsString>> {
121 let mut names = std::fs::read_dir(dir)?
122 .map(|entry| entry.map(|e| e.file_name()))
123 .collect::<io::Result<Vec<_>>>()?;
124 names.sort();
125 Ok(names)
126 }
127
128 fn create_dir_all(&self, dir: &Path) -> io::Result<()> {
129 DirBuilder::new().recursive(true).mode(DIR_MODE).create(dir)
130 }
131
132 fn remove_dir_all(&self, dir: &Path) -> io::Result<()> {
133 std::fs::remove_dir_all(dir)
134 }
135}
136
137#[cfg(test)]
138pub(crate) use crash::{CrashFs, Op};
139
140#[cfg(test)]
141mod crash {
142 use std::collections::BTreeMap;
143 use std::ffi::OsString;
144 use std::io;
145 use std::path::{Path, PathBuf};
146 use std::sync::Mutex;
147
148 use super::Fs;
149
150 #[derive(Debug, Clone, PartialEq, Eq)]
152 pub(crate) enum Op {
153 Write(PathBuf),
154 SyncFile(PathBuf),
155 Rename(PathBuf, PathBuf),
156 SyncDir(PathBuf),
157 Remove(PathBuf),
158 CreateDir(PathBuf),
159 RemoveDir(PathBuf),
160 }
161
162 pub(crate) struct CrashFs {
175 state: Mutex<State>,
176 }
177
178 struct State {
179 root: PathBuf,
180 dirs: BTreeMap<PathBuf, Dir>,
181 files: Vec<File>,
182 log: Vec<Op>,
183 fail_from: Option<usize>,
185 }
186
187 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
188 enum Entry {
189 File(usize),
191 Dir,
192 }
193
194 #[derive(Default)]
196 struct Dir {
197 now: BTreeMap<OsString, Entry>,
198 synced: BTreeMap<OsString, Entry>,
199 }
200
201 struct File {
202 now: Vec<u8>,
203 synced: Vec<u8>,
204 }
205
206 fn not_found() -> io::Error {
207 io::Error::from(io::ErrorKind::NotFound)
208 }
209
210 fn split(path: &Path) -> io::Result<(PathBuf, OsString)> {
212 match (path.parent(), path.file_name()) {
213 (Some(dir), Some(name)) => Ok((dir.to_owned(), name.to_owned())),
214 _ => Err(io::Error::from(io::ErrorKind::InvalidInput)),
215 }
216 }
217
218 impl State {
219 fn alive(&self) -> io::Result<()> {
221 match self.fail_from {
222 Some(n) if self.log.len() >= n => Err(io::Error::other("the process has died")),
223 _ => Ok(()),
224 }
225 }
226
227 fn exists(&self, dir: &Path) -> bool {
230 if dir == self.root {
231 return true;
232 }
233 let Ok((parent, name)) = split(dir) else {
234 return false;
235 };
236 self.exists(&parent)
237 && self
238 .dirs
239 .get(&parent)
240 .is_some_and(|p| p.now.get(&name) == Some(&Entry::Dir))
241 }
242
243 fn dir(&mut self, dir: &Path) -> io::Result<&mut Dir> {
244 if !self.exists(dir) {
245 return Err(not_found());
246 }
247 self.dirs.get_mut(dir).ok_or_else(not_found)
248 }
249
250 fn file(&mut self, path: &Path) -> io::Result<usize> {
251 let (dir, name) = split(path)?;
252 match self.dir(&dir)?.now.get(&name) {
253 Some(Entry::File(index)) => Ok(*index),
254 _ => Err(not_found()),
255 }
256 }
257 }
258
259 impl CrashFs {
260 pub(crate) fn new(root: &Path) -> CrashFs {
262 CrashFs {
263 state: Mutex::new(State {
264 root: root.to_owned(),
265 dirs: BTreeMap::from([(root.to_owned(), Dir::default())]),
266 files: Vec::new(),
267 log: Vec::new(),
268 fail_from: None,
269 }),
270 }
271 }
272
273 fn state(&self) -> std::sync::MutexGuard<'_, State> {
274 self.state
275 .lock()
276 .expect("no test panicked holding the lock")
277 }
278
279 pub(crate) fn crash(&self) {
281 let mut state = self.state();
282 state.fail_from = None;
283 for dir in state.dirs.values_mut() {
284 dir.now = dir.synced.clone();
285 }
286 for file in &mut state.files {
287 file.now = file.synced.clone();
288 }
289 }
290
291 pub(crate) fn fail_from(&self, n: usize) {
295 self.state().fail_from = Some(n);
296 }
297
298 pub(crate) fn log(&self) -> Vec<Op> {
300 self.state().log.clone()
301 }
302 }
303
304 impl Fs for CrashFs {
305 fn write_new(&self, path: &Path, data: &[u8]) -> io::Result<()> {
306 let (dir, name) = split(path)?;
307 let mut state = self.state();
308 state.alive()?;
309 let index = state.files.len();
310 let entries = &mut state.dir(&dir)?.now;
311 if entries.contains_key(&name) {
312 return Err(io::Error::from(io::ErrorKind::AlreadyExists));
313 }
314 entries.insert(name, Entry::File(index));
315 state.files.push(File {
316 now: data.to_vec(),
317 synced: Vec::new(),
318 });
319 state.log.push(Op::Write(path.to_owned()));
320 Ok(())
321 }
322
323 fn sync_file(&self, path: &Path) -> io::Result<()> {
324 let mut state = self.state();
325 state.alive()?;
326 let index = state.file(path)?;
327 let file = &mut state.files[index];
328 file.synced = file.now.clone();
329 state.log.push(Op::SyncFile(path.to_owned()));
330 Ok(())
331 }
332
333 fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
334 let ((from_dir, from_name), (to_dir, to_name)) = (split(from)?, split(to)?);
335 let mut state = self.state();
336 state.alive()?;
337 let index = state.file(from)?;
338 state.dir(&to_dir)?;
339 state.dir(&from_dir)?.now.remove(&from_name);
340 state.dir(&to_dir)?.now.insert(to_name, Entry::File(index));
341 state.log.push(Op::Rename(from.to_owned(), to.to_owned()));
342 Ok(())
343 }
344
345 fn sync_dir(&self, dir: &Path) -> io::Result<()> {
346 let mut state = self.state();
347 state.alive()?;
348 let entries = state.dir(dir)?;
349 entries.synced = entries.now.clone();
350 state.log.push(Op::SyncDir(dir.to_owned()));
351 Ok(())
352 }
353
354 fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
355 let mut state = self.state();
356 let index = state.file(path)?;
357 Ok(state.files[index].now.clone())
358 }
359
360 fn remove(&self, path: &Path) -> io::Result<()> {
361 let (dir, name) = split(path)?;
362 let mut state = self.state();
363 state.alive()?;
364 state.file(path)?;
365 state.dir(&dir)?.now.remove(&name);
366 state.log.push(Op::Remove(path.to_owned()));
367 Ok(())
368 }
369
370 fn list(&self, dir: &Path) -> io::Result<Vec<OsString>> {
371 Ok(self.state().dir(dir)?.now.keys().cloned().collect())
372 }
373
374 fn create_dir_all(&self, dir: &Path) -> io::Result<()> {
375 let mut state = self.state();
376 state.alive()?;
377 let below = dir
378 .strip_prefix(&state.root)
379 .map_err(|_| io::Error::from(io::ErrorKind::InvalidInput))?
380 .to_owned();
381 let mut path = state.root.clone();
384 let mut missing = Vec::new();
385 for part in &below {
386 let next = path.join(part);
387 let parent = std::mem::replace(&mut path, next);
388 if !missing.is_empty() {
389 missing.push(path.clone());
390 continue;
391 }
392 match state.dir(&parent)?.now.get(part) {
393 Some(Entry::Dir) => {}
394 Some(Entry::File(_)) => {
395 return Err(io::Error::from(io::ErrorKind::AlreadyExists));
396 }
397 None => missing.push(path.clone()),
398 }
399 }
400 if missing.is_empty() {
401 return Ok(());
402 }
403 std::fs::create_dir_all(dir)?;
404 for path in missing {
405 let (parent, name) = split(&path)?;
406 state.dir(&parent)?.now.insert(name, Entry::Dir);
407 state.dirs.insert(path, Dir::default());
408 }
409 state.log.push(Op::CreateDir(dir.to_owned()));
410 Ok(())
411 }
412
413 fn remove_dir_all(&self, dir: &Path) -> io::Result<()> {
414 let (parent, name) = split(dir)?;
415 let mut state = self.state();
416 state.alive()?;
417 if !state.exists(dir) {
418 return Err(not_found());
419 }
420 state.dir(&parent)?.now.remove(&name);
421 state.log.push(Op::RemoveDir(dir.to_owned()));
422 Ok(())
423 }
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use std::io::ErrorKind::{AlreadyExists, NotFound};
430 use std::path::PathBuf;
431
432 use super::*;
433
434 fn behaves_like_a_filesystem(fs: &dyn Fs, root: &Path) {
436 let dir = root.join("objects");
437 let (a, b) = (dir.join("a"), dir.join("b"));
438 assert_eq!(
439 fs.write_new(&a, b"x").map_err(|e| e.kind()),
440 Err(NotFound),
441 "no dir yet"
442 );
443 fs.create_dir_all(&dir).expect("create the dir");
444 fs.create_dir_all(&dir).expect("create it again");
445 fs.write_new(&a, b"one").expect("write");
446 assert_eq!(
447 fs.write_new(&a, b"two").map_err(|e| e.kind()),
448 Err(AlreadyExists),
449 "write_new never replaces a file"
450 );
451 assert!(fs.create_dir_all(&a).is_err(), "a file has the name");
452 assert!(
453 fs.create_dir_all(&a.join("d")).is_err(),
454 "a file stands in the way"
455 );
456 assert!(!a.join("d").exists(), "and nothing is made on disk");
457 assert_eq!(
458 fs.read(&a).expect("read"),
459 b"one",
460 "the refused write left the first one"
461 );
462 fs.sync_file(&a).expect("sync the file");
463 fs.sync_dir(&dir).expect("sync the dir");
464 fs.rename(&a, &b).expect("rename");
465 assert_eq!(
466 fs.read(&a).map_err(|e| e.kind()),
467 Err(NotFound),
468 "rename moves the file away"
469 );
470 assert_eq!(
471 fs.read(&b).expect("read"),
472 b"one",
473 "the file is under its new name"
474 );
475 fs.rename(&b, &b).expect("rename onto itself");
476 assert_eq!(
477 fs.read(&b).expect("read"),
478 b"one",
479 "renaming onto itself changes nothing"
480 );
481 fs.write_new(&a, b"three").expect("write again");
482 fs.rename(&a, &b).expect("rename over");
483 assert_eq!(
484 fs.read(&b).expect("read"),
485 b"three",
486 "rename replaces the target"
487 );
488 fs.write_new(&a, b"four").expect("write");
489 fs.create_dir_all(&dir.join("c"))
490 .expect("a directory inside");
491 assert_eq!(
492 fs.list(&dir).expect("list"),
493 ["a", "b", "c"].map(OsString::from),
494 "files and directories, sorted"
495 );
496 assert_eq!(
497 fs.write_new(&dir.join("c"), b"x").map_err(|e| e.kind()),
498 Err(AlreadyExists),
499 "a directory's name is taken"
500 );
501 assert!(
502 fs.remove(&dir.join("c")).is_err(),
503 "remove takes only a file"
504 );
505 assert!(
506 fs.remove_dir_all(&a).is_err(),
507 "remove_dir_all takes only a directory"
508 );
509 fs.create_dir_all(&dir.join("c/d"))
510 .expect("a directory inside c");
511 fs.write_new(&dir.join("c/f"), b"x")
512 .expect("a file inside c");
513 fs.remove_dir_all(&dir.join("c"))
514 .expect("remove c and all in it");
515 assert_eq!(
516 fs.list(&dir.join("c")).map_err(|e| e.kind()),
517 Err(NotFound),
518 "remove_dir_all removed c itself"
519 );
520 assert_eq!(
521 fs.remove_dir_all(&dir.join("c")).map_err(|e| e.kind()),
522 Err(NotFound),
523 "a directory that is gone cannot be removed again"
524 );
525 fs.create_dir_all(&dir.join("c")).expect("c again");
526 fs.remove(&a).expect("remove");
527 fs.remove(&b).expect("remove");
528 assert_eq!(
529 fs.list(&dir).expect("list"),
530 [OsString::from("c")],
531 "the removed files are gone and c is left"
532 );
533 assert_eq!(
534 fs.remove(&b).map_err(|e| e.kind()),
535 Err(NotFound),
536 "b was removed"
537 );
538 assert_eq!(
539 fs.sync_file(&b).map_err(|e| e.kind()),
540 Err(NotFound),
541 "b was removed"
542 );
543 assert_eq!(
544 fs.sync_dir(&root.join("none")).map_err(|e| e.kind()),
545 Err(NotFound),
546 "no directory has that name"
547 );
548 assert_eq!(
549 fs.list(&root.join("none")).map_err(|e| e.kind()),
550 Err(NotFound),
551 "no directory has that name"
552 );
553 }
554
555 #[test]
556 fn the_os_filesystem_behaves_like_a_filesystem() {
557 let root = tempfile::tempdir().expect("temp dir");
558 behaves_like_a_filesystem(&OsFs, root.path());
559 }
560
561 #[test]
562 fn the_crash_filesystem_behaves_like_a_filesystem() {
563 let root = tempfile::tempdir().expect("temp dir");
564 behaves_like_a_filesystem(&CrashFs::new(root.path()), root.path());
565 }
566
567 struct Model {
569 root: tempfile::TempDir,
570 fs: CrashFs,
571 }
572
573 impl Model {
574 fn new() -> Model {
575 let root = tempfile::tempdir().expect("temp dir");
576 let fs = CrashFs::new(root.path());
577 Model { root, fs }
578 }
579
580 fn root(&self) -> &Path {
581 self.root.path()
582 }
583
584 fn dir(&self) -> PathBuf {
586 self.root().join("objects")
587 }
588
589 fn f(&self) -> PathBuf {
590 self.dir().join("f")
591 }
592 }
593
594 fn after(steps: impl FnOnce(&CrashFs, &Path)) -> Model {
596 let m = Model::new();
597 m.fs.create_dir_all(&m.dir()).expect("create the dir");
598 m.fs.sync_dir(m.root()).expect("sync the root");
599 steps(&m.fs, &m.f());
600 m
601 }
602
603 fn synced(fs: &CrashFs, f: &Path) {
604 fs.write_new(f, b"data").expect("write");
605 fs.sync_file(f).expect("sync the file");
606 fs.sync_dir(f.parent().expect("a dir"))
607 .expect("sync the dir");
608 }
609
610 #[test]
611 fn a_crash_drops_a_file_whose_entry_was_not_synced() {
612 for sync_data in [false, true] {
613 let m = after(|fs, f| {
614 fs.write_new(f, b"data").expect("write");
615 if sync_data {
616 fs.sync_file(f).expect("sync the file");
617 }
618 });
619 m.fs.crash();
620 assert_eq!(
621 m.fs.read(&m.f()).map_err(|e| e.kind()),
622 Err(NotFound),
623 "the entry was never synced, so the crash loses the file (data synced: {sync_data})"
624 );
625 }
626 }
627
628 #[test]
629 fn a_crash_empties_a_file_whose_data_was_not_synced() {
630 let m = after(|fs, f| {
631 fs.write_new(f, b"data").expect("write");
632 fs.sync_dir(f.parent().expect("a dir"))
633 .expect("sync the dir");
634 });
635 m.fs.crash();
636 assert_eq!(
637 m.fs.read(&m.f()).expect("the entry survives"),
638 b"",
639 "the entry was synced but the data was not, so the file comes back empty"
640 );
641 }
642
643 #[test]
644 fn a_crash_keeps_a_file_synced_in_both_ways() {
645 let m = after(synced);
646 m.fs.crash();
647 assert_eq!(
648 m.fs.read(&m.f()).expect("survives"),
649 b"data",
650 "data and entry were both synced, so the crash keeps the file whole"
651 );
652 }
653
654 #[test]
655 fn a_rename_or_remove_survives_a_crash_only_once_the_dir_is_synced() {
656 for sync in [false, true] {
657 let renamed = after(|fs, f| {
658 synced(fs, f);
659 fs.rename(f, &f.with_file_name("g")).expect("rename");
660 if sync {
661 fs.sync_dir(f.parent().expect("a dir"))
662 .expect("sync the dir");
663 }
664 });
665 renamed.fs.crash();
666 let names = if sync { ["g"] } else { ["f"] };
667 assert_eq!(
668 renamed.fs.list(&renamed.dir()).expect("list"),
669 names.map(OsString::from),
670 "rename, sync {sync}"
671 );
672 assert_eq!(
673 renamed
674 .fs
675 .read(&renamed.dir().join(names[0]))
676 .expect("read"),
677 b"data",
678 "whichever name survives, the synced data is under it"
679 );
680
681 let removed = after(|fs, f| {
682 synced(fs, f);
683 fs.remove(f).expect("remove");
684 if sync {
685 fs.sync_dir(f.parent().expect("a dir"))
686 .expect("sync the dir");
687 }
688 });
689 removed.fs.crash();
690 let left = removed.fs.list(&removed.dir()).expect("list").len();
691 assert_eq!(left, usize::from(!sync), "remove, sync {sync}");
692 }
693 }
694
695 #[test]
696 fn a_new_directory_survives_a_crash_only_once_its_parent_is_synced() {
697 let m = Model::new();
698 let (a, b) = (m.root().join("a"), m.root().join("a/b"));
699 m.fs.create_dir_all(&b).expect("create a/b");
700 m.fs.crash();
701 assert_eq!(
702 m.fs.list(m.root()).expect("list"),
703 Vec::<OsString>::new(),
704 "nothing was synced, so the crash loses a"
705 );
706 assert_eq!(
707 m.fs.list(&a).map_err(|e| e.kind()),
708 Err(NotFound),
709 "a is gone after the crash"
710 );
711
712 m.fs.create_dir_all(&b).expect("create a/b");
713 m.fs.sync_dir(&a).expect("sync a, not the root");
714 m.fs.crash();
715 assert_eq!(
716 m.fs.list(&b).map_err(|e| e.kind()),
717 Err(NotFound),
718 "b is lost with a"
719 );
720
721 m.fs.create_dir_all(&b).expect("create a/b");
722 m.fs.sync_dir(m.root()).expect("sync the root");
723 m.fs.crash();
724 assert_eq!(
725 m.fs.list(m.root()).expect("list"),
726 [OsString::from("a")],
727 "the root was synced, so its entry for a survives"
728 );
729 assert_eq!(
730 m.fs.list(&a).expect("a survives"),
731 Vec::<OsString>::new(),
732 "b's entry in a was never synced"
733 );
734
735 m.fs.create_dir_all(&b).expect("create b");
736 m.fs.sync_dir(&a).expect("sync a");
737 m.fs.crash();
738 assert_eq!(
739 m.fs.list(&b).expect("b survives"),
740 Vec::<OsString>::new(),
741 "a was synced, so its entry for b survives, and b is empty"
742 );
743 }
744
745 #[test]
746 fn a_lost_directory_takes_its_synced_files_with_it() {
747 let m = Model::new();
748 let a = m.root().join("a");
749 m.fs.create_dir_all(&a).expect("create a");
750 synced(&m.fs, &a.join("f"));
751 m.fs.crash();
752 assert_eq!(
753 m.fs.list(m.root()).expect("list"),
754 Vec::<OsString>::new(),
755 "the root was never synced, so a is lost with its synced file"
756 );
757 m.fs.create_dir_all(&a).expect("create a again");
758 assert_eq!(
759 m.fs.list(&a).expect("list"),
760 Vec::<OsString>::new(),
761 "a new directory starts empty"
762 );
763 }
764
765 #[test]
766 fn a_directory_removal_survives_a_crash_only_once_its_parent_is_synced() {
767 for sync in [false, true] {
768 let m = Model::new();
769 let a = m.root().join("a");
770 m.fs.create_dir_all(&a).expect("create a");
771 synced(&m.fs, &a.join("f"));
772 m.fs.sync_dir(m.root()).expect("sync the root");
773 m.fs.remove_dir_all(&a).expect("remove a");
774 if sync {
775 m.fs.sync_dir(m.root()).expect("sync the root");
776 }
777 m.fs.crash();
778 if sync {
779 assert_eq!(
780 m.fs.list(&a).map_err(|e| e.kind()),
781 Err(NotFound),
782 "the root was synced after the removal, so it holds"
783 );
784 } else {
785 assert_eq!(
786 m.fs.read(&a.join("f")).expect("back"),
787 b"data",
788 "the removal was never synced, so a comes back with its file"
789 );
790 }
791 }
792 }
793
794 #[test]
797 fn a_directory_is_removed_only_from_the_model() {
798 let m = Model::new();
799 let a = m.root().join("a");
800 m.fs.create_dir_all(&a).expect("create a");
801 m.fs.remove_dir_all(&a).expect("remove a");
802 assert!(
803 a.is_dir(),
804 "the directory stays on disk, for a crash that brings it back"
805 );
806 }
807
808 #[test]
811 fn a_directory_is_also_made_on_disk() {
812 let m = Model::new();
813 let b = m.root().join("a/b");
814 m.fs.create_dir_all(&b).expect("create a/b");
815 assert!(
816 b.is_dir(),
817 "the directory is on disk, where SQLite will open its files"
818 );
819 }
820
821 #[test]
822 fn nothing_outside_the_root_can_be_made() {
823 let m = Model::new();
824 let outside = m.root().with_file_name("elsewhere-5c1e");
825 assert!(
826 m.fs.create_dir_all(&outside).is_err(),
827 "the model refuses a path outside its root"
828 );
829 assert!(!outside.exists(), "and makes nothing there on disk");
830 }
831
832 #[test]
833 fn every_call_that_changes_state_is_logged_in_order() {
834 let m = Model::new();
835 let (dir, f, g) = (m.dir(), m.f(), m.dir().join("g"));
836 m.fs.create_dir_all(&dir).expect("create the dir");
837 m.fs.create_dir_all(&dir).expect("already there: no change");
838 synced(&m.fs, &f);
839 m.fs.rename(&f, &g).expect("rename");
840 m.fs.remove(&g).expect("remove");
841 m.fs.remove_dir_all(&dir).expect("remove the dir");
842 assert_eq!(
843 m.fs.log(),
844 [
845 Op::CreateDir(dir.clone()),
846 Op::Write(f.clone()),
847 Op::SyncFile(f.clone()),
848 Op::SyncDir(dir.clone()),
849 Op::Rename(f, g.clone()),
850 Op::Remove(g),
851 Op::RemoveDir(dir),
852 ],
853 "one entry per change, in order; the create that changed nothing is not logged"
854 );
855 }
856
857 #[test]
858 fn from_the_fail_point_on_no_change_happens_until_a_crash() {
859 let m = after(|fs, f| fs.write_new(f, b"data").expect("write"));
860 let ops = m.fs.log().len();
861 m.fs.fail_from(ops);
862 let (f, g) = (m.f(), m.dir().join("g"));
863 assert!(m.fs.sync_file(&f).is_err(), "sync");
864 assert!(m.fs.write_new(&g, b"x").is_err(), "write");
865 assert!(m.fs.rename(&f, &g).is_err(), "rename");
866 assert!(m.fs.sync_dir(&m.dir()).is_err(), "sync dir");
867 assert!(m.fs.remove(&f).is_err(), "remove");
868 assert!(
869 m.fs.create_dir_all(&m.dir().join("d")).is_err(),
870 "create dir"
871 );
872 assert!(m.fs.remove_dir_all(&m.dir()).is_err(), "remove dir");
873 assert!(!m.dir().join("d").exists(), "not made on disk either");
874 assert_eq!(m.fs.log().len(), ops, "nothing changed");
875 assert_eq!(
876 m.fs.read(&f).expect("reads still work"),
877 b"data",
878 "the fail point stops changes, not reads"
879 );
880 m.fs.crash();
881 m.fs.write_new(&g, b"x")
882 .expect("works again after the crash");
883 }
884}