use super::*;
#[test]
fn same_seed_same_fsync_drop_schedule() {
let a = SimFs::new(99, 4);
let b = SimFs::new(99, 4);
let pa: Vec<_> = (0..64).map(|_| a.state.fsync_dropped()).collect();
let pb: Vec<_> = (0..64).map(|_| b.state.fsync_dropped()).collect();
assert_eq!(
pa, pb,
"PROPERTY: identical seeds produce identical fsync-drop schedules"
);
}
#[test]
fn crash_truncates_to_durable_length() {
let dir = tempfile::tempdir().expect("tmpdir");
let fs = SimFs::new(1, 0);
let path = dir.path().join("seg.fbat");
let mut file = fs.create_new_file(&path).expect("create");
file.write_all(b"durable").expect("write durable");
file.sync_all().expect("honored sync");
let durable = fs.durable_len(&path);
file.write_all(b"-and-lost-tail").expect("write tail");
crate::store::platform::sync::sync_file_all_io(
file.as_std_file()
.expect("SimFs over RealFs mints real files"),
)
.expect("flush real bytes to disk");
fs.crash();
let recovered = crate::store::platform::fs::metadata(&path)
.expect("stat")
.len();
assert_eq!(
recovered, durable,
"PROPERTY: a crash truncates the real file to its last durable (synced) length"
);
assert_eq!(
recovered,
b"durable".len() as u64,
"PROPERTY: only the synced prefix survives the crash"
);
}
#[test]
fn dropped_fsync_does_not_advance_durable_length() {
let dir = tempfile::tempdir().expect("tmpdir");
let fs = SimFs::new(7, 1);
let path = dir.path().join("seg.fbat");
let mut file = fs.create_new_file(&path).expect("create");
file.write_all(b"unsynced").expect("write");
crate::store::platform::sync::sync_file_all_io(
file.as_std_file()
.expect("SimFs over RealFs mints real files"),
)
.expect("flush real bytes");
file.sync_all()
.expect("dropped sync still returns Ok to the store");
assert_eq!(
fs.durable_len(&path),
0,
"PROPERTY: a dropped sync returns Ok but never advances the durable length"
);
fs.crash();
assert_eq!(
crate::store::platform::fs::metadata(&path)
.expect("stat")
.len(),
0,
"PROPERTY: an all-dropped-sync file loses its entire tail on crash"
);
}
#[test]
fn remove_dir_all_removes_the_directory_entry_over_every_inner() {
let real_dir = tempfile::tempdir().expect("tmpdir");
let cases: Vec<(SimFs, std::path::PathBuf)> = vec![
(
SimFs::layered(0x5117_D111, 0, Arc::new(RealFs)),
real_dir.path().join("store"),
),
(
SimFs::layered(
0x5117_D222,
0,
Arc::new(crate::store::platform::mem_fs::MemFs::new()),
),
std::path::PathBuf::from("/virtual/sim-remove-dir"),
),
];
for (fs, root) in cases {
let dir = root.join("cursors");
fs.create_dir_all(&dir).expect("seed directory");
let child = dir.join("resume.ckpt");
let mut file = fs.create_new_file(&child).expect("create child");
file.write_all(b"checkpoint").expect("write child");
file.sync_all().expect("sync child");
drop(file);
assert!(
fs.remove_dir_all_if_present(&dir)
.expect("remove existing directory"),
"an existing directory reports removed"
);
assert!(
matches!(fs.read(&child), Err(error) if error.kind() == io::ErrorKind::NotFound),
"a child under a removed directory must be gone"
);
assert!(
!fs.remove_dir_all_if_present(&dir)
.expect("second removal of an absent directory"),
"an already-removed directory reports Ok(false) — the entry is truly gone"
);
}
}
#[test]
fn crash_truncates_the_virtual_inner_even_when_a_host_file_shadows_the_path() {
let host_dir = tempfile::tempdir().expect("host tmpdir");
let path = host_dir.path().join("seg.fbat");
let real = RealFs;
{
let mut decoy = real.create_new_file(&path).expect("create host decoy");
decoy
.write_all(b"HOST-DECOY-must-not-be-touched")
.expect("write decoy");
}
let mem = crate::store::platform::mem_fs::MemFs::new();
mem.create_dir_all(host_dir.path())
.expect("seed the virtual data dir");
let sim = SimFs::layered(0x5117_C0DE, 0, Arc::new(mem.clone()));
let mut file = sim.create_new_file(&path).expect("create virtual file");
file.write_all(b"durable").expect("write durable prefix");
file.sync_all().expect("honored sync records durable_len");
assert_eq!(
sim.durable_len(&path),
7,
"the durable prefix is the synced 'durable' bytes"
);
file.write_all(b"-and-lost-tail").expect("write tail");
drop(file);
sim.crash();
assert_eq!(
mem.read(&path).expect("read virtual file"),
b"durable",
"SimFs crash must truncate the virtual inner via the seam, not skip it"
);
assert_eq!(
real.read(&path).expect("read host decoy"),
b"HOST-DECOY-must-not-be-touched",
"a virtual-inner crash must not mutate an unrelated same-named host file"
);
}
#[test]
fn a_dropped_staged_sync_loses_the_publish_on_crash() {
let mem = crate::store::platform::mem_fs::MemFs::new();
let dir = std::path::Path::new("/virtual/staged-drop");
mem.create_dir_all(dir).expect("seed dir");
let sim = SimFs::layered(0xDACE_D40F, 1, Arc::new(mem.clone()));
let final_path = dir.join("cursor.ckpt");
let mut staged = sim.named_temp_in(dir).expect("stage");
staged.write_all(b"checkpoint-bytes").expect("write staged");
staged
.sync_all()
.expect("staged sync (dropped by the schedule)");
let admission = crate::store::platform::sync::admit_current_parent_dir_sync()
.expect("mint parent-dir-sync admission");
staged.persist(&final_path, admission).expect("publish");
assert_eq!(
mem.read(&final_path).expect("read published"),
b"checkpoint-bytes"
);
sim.crash();
assert!(
mem.read(&final_path).expect("read after crash").is_empty(),
"a dropped staged sync must lose the publish on crash, not survive it"
);
}
#[test]
fn an_honored_staged_sync_survives_a_crash() {
let mem = crate::store::platform::mem_fs::MemFs::new();
let dir = std::path::Path::new("/virtual/staged-honored");
mem.create_dir_all(dir).expect("seed dir");
let sim = SimFs::layered(0xDACE_5A7E, 0, Arc::new(mem.clone()));
let final_path = dir.join("cursor.ckpt");
let mut staged = sim.named_temp_in(dir).expect("stage");
staged.write_all(b"checkpoint-bytes").expect("write staged");
staged.sync_all().expect("staged sync (honored)");
let admission = crate::store::platform::sync::admit_current_parent_dir_sync()
.expect("mint parent-dir-sync admission");
staged.persist(&final_path, admission).expect("publish");
sim.crash();
assert_eq!(
mem.read(&final_path).expect("read after crash"),
b"checkpoint-bytes",
"an honored staged sync makes the publish durable across a crash"
);
}
#[test]
fn mmap_probe_scratch_removal_does_not_consume_a_remove_file_fault() {
let dir = tempfile::tempdir().expect("tmpdir");
let sim = SimFs::new(0x0BE1_F00D, 0).with_fault_on(CrashOp::RemoveFile, 1);
let evidence = crate::store::platform::evidence::mmap_evidence_for_store_path(dir.path(), &sim);
assert_eq!(
evidence,
crate::store::stats::MmapEvidence::FileBacked,
"the probe over a real inner reports FileBacked without consuming the fault"
);
let victim = dir.path().join("segment.fbat");
{
let mut file = sim.create_new_file(&victim).expect("create victim");
file.write_all(b"x").expect("write victim");
}
assert!(
sim.remove_file(&victim).is_err(),
"the armed RemoveFile fault must still fire on the first real remove — the \
probe's scratch removes must not have consumed it"
);
}