#![forbid(unsafe_code)]
use tempfile::TempDir;
use crate::core::extent::ChunkId;
use crate::store::transaction::{CrashHooks, CrashPoint};
use crate::store::{NewEntry, Store, StoreConfig};
fn create_store(dir: &TempDir) -> Store {
let cfg = StoreConfig {
segment_size: 1024 * 1024,
..Default::default()
};
Store::create(dir.path(), &cfg, [0xAA; 16]).unwrap()
}
fn ino(store: &mut Store) -> u64 {
store
.create_entry(
1,
b"f",
NewEntry::file(0o644, 1000, 1000),
&CrashHooks::none(),
)
.unwrap()
}
fn write_file(store: &mut Store, ino: u64, tag: u8) {
let content = vec![tag; 65536];
store.write_region(ino, 0, &content).unwrap();
}
#[test]
fn snapshot_lifecycle_create_list_delete_restore() {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
write_file(&mut store, f, 0x11);
store.create_snapshot(b"v1", &CrashHooks::none()).unwrap();
let v1_root = store
.snapshot_lookup(b"v1")
.unwrap()
.expect("snapshot exists")
.root_id;
write_file(&mut store, f, 0x22);
assert_eq!(store.list_snapshots().unwrap().len(), 1);
store.restore_snapshot(b"v1", &CrashHooks::none()).unwrap();
let content = store.read_file(f, 0, 65536).unwrap();
assert!(content.iter().all(|&b| b == 0x11));
assert_eq!(store.list_snapshots().unwrap().len(), 1);
assert_eq!(
store.snapshot_lookup(b"v1").unwrap().unwrap().root_id,
v1_root
);
assert!(store.delete_snapshot(b"v1", &CrashHooks::none()).unwrap());
assert!(!store.delete_snapshot(b"v1", &CrashHooks::none()).unwrap());
assert!(store.list_snapshots().unwrap().is_empty());
}
#[test]
fn gc_respects_snapshot_roots() {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
write_file(&mut store, f, 0x11);
store.create_snapshot(b"v1", &CrashHooks::none()).unwrap();
for v in 0..30u8 {
write_file(&mut store, f, v);
}
crate::store::gc::collect(&mut store, &CrashHooks::none()).unwrap();
let unreachable = crate::store::gc::unreachable_bytes(&store).unwrap();
let _ = unreachable;
store.restore_snapshot(b"v1", &CrashHooks::none()).unwrap();
let content = store.read_file(f, 0, 65536).unwrap();
assert!(
content.iter().all(|&b| b == 0x11),
"GC reclaimed snapshot-pinned data"
);
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(
report.is_clean(),
"fsck: {:?}",
report.issues.iter().take(3).collect::<Vec<_>>()
);
}
#[test]
fn snapshot_crash_matrix_is_linearizable() {
let points = [
CrashPoint::AfterRecordAppend,
CrashPoint::AfterSegmentFdatasync,
CrashPoint::AfterSegmentDirFsync,
CrashPoint::AfterRootWrite,
CrashPoint::AfterSuperblockWrite,
CrashPoint::AfterSuperblockFsync,
];
for point in points {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
write_file(&mut store, f, 0x11);
let hooks = CrashHooks::crash_at(point);
let _ = store.create_snapshot(b"v1", &hooks);
drop(store);
let mut store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
let has_snapshot = store.snapshot_lookup(b"v1").unwrap().is_some();
let content = store.read_file(f, 0, 65536).unwrap();
assert_eq!(content.len(), 65536);
if has_snapshot {
write_file(&mut store, f, 0x22);
store.restore_snapshot(b"v1", &CrashHooks::none()).unwrap();
let back = store.read_file(f, 0, 65536).unwrap();
assert!(back.iter().all(|&b| b == 0x11));
}
let report = crate::fsck::fsck(dir.path(), &crate::fsck::FsckOptions::default()).unwrap();
assert!(
report.is_clean(),
"fsck after crash at {point:?}: {}",
report
.issues
.iter()
.take(3)
.map(|i| i.message.clone())
.collect::<Vec<_>>()
.join("; ")
);
}
}
#[test]
fn snapshot_pins_inodes_across_remount() {
let dir = TempDir::new().unwrap();
let mut store = create_store(&dir);
let f = ino(&mut store);
write_file(&mut store, f, 0x33);
store
.create_snapshot(b"pre-remount", &CrashHooks::none())
.unwrap();
drop(store);
let mut store = Store::open(dir.path(), &StoreConfig::default()).unwrap();
assert_eq!(store.list_snapshots().unwrap().len(), 1);
let f2 = store
.create_entry(
1,
b"f2",
NewEntry::file(0o644, 1000, 1000),
&CrashHooks::none(),
)
.unwrap();
write_file(&mut store, f2, 0x44);
store
.restore_snapshot(b"pre-remount", &CrashHooks::none())
.unwrap();
assert!(store.dir_lookup(1, b"f2").unwrap().is_none());
let content = store.read_file(f, 0, 65536).unwrap();
assert!(content.iter().all(|&b| b == 0x33));
let _ = ChunkId::ZERO;
}