use chisel::{Chisel, DefragOptions, Options};
#[test]
fn reclaims_freed_pages_with_multipage_freemap() {
let mut db = Chisel::open_in_memory().unwrap();
db.begin().unwrap();
let mut hs = Vec::new();
for i in 0..3000u64 {
hs.push(db.allocate(format!("v{i}").as_bytes()).unwrap());
}
db.commit().unwrap();
db.begin().unwrap();
for h in hs.iter().take(1500) {
db.delete(*h).unwrap();
}
db.commit().unwrap();
let before = db.stats().unwrap().total_pages;
db.begin().unwrap();
for i in 0..1000u64 {
db.allocate(format!("r{i}").as_bytes()).unwrap();
}
db.commit().unwrap();
let after = db.stats().unwrap().total_pages;
assert!(
after - before < 1000,
"freed pages must be reused (before={before} after={after}, \
grew by {}; expected growth < 1000)",
after - before
);
}
#[test]
fn steady_state_file_is_flat_under_churn() {
let mut db = Chisel::open_in_memory().unwrap();
db.begin().unwrap();
let mut hs = Vec::new();
for i in 0..500u64 {
hs.push(db.allocate(format!("v{i}").as_bytes()).unwrap());
}
db.commit().unwrap();
for _ in 0..10 {
db.begin().unwrap();
let old = std::mem::take(&mut hs);
for h in &old {
db.delete(*h).unwrap();
}
for i in 0..500u64 {
hs.push(db.allocate(format!("c{i}").as_bytes()).unwrap());
}
db.commit().unwrap();
}
let baseline = db.stats().unwrap().total_pages;
for _ in 0..30 {
db.begin().unwrap();
let old = std::mem::take(&mut hs);
for h in &old {
db.delete(*h).unwrap();
}
for i in 0..500u64 {
hs.push(db.allocate(format!("c{i}").as_bytes()).unwrap());
}
db.commit().unwrap();
}
let after = db.stats().unwrap().total_pages;
let growth = after - baseline;
assert!(
growth < 32,
"file grew by {growth} pages over 30 steady-churn cycles \
(baseline={baseline}, after={after}); \
expected < 32 (handle-table tombstone accumulation ~1/cycle is normal; \
freemap structural leak would add on top and breach this bound)"
);
}
#[test]
fn crash_orphans_are_reclaimed_by_defrag() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("t.chisel");
let big = vec![0xCDu8; 9000];
{
let mut db = Chisel::open(&path, Options::default()).unwrap();
db.begin().unwrap();
let mut hs = Vec::new();
for _ in 0..400u64 {
hs.push(db.allocate(&big).unwrap());
}
db.commit().unwrap();
db.begin().unwrap();
for h in &hs {
db.delete(*h).unwrap();
}
db.commit().unwrap();
}
let mut db = Chisel::open(&path, Options::default()).unwrap();
db.begin().unwrap();
let first = db.defrag(DefragOptions::default()).unwrap();
db.commit().unwrap();
assert!(
first.freemap_orphans_reclaimed >= 1,
"first defrag must reclaim >= 1 crash-orphaned freemap page \
(got {}); the final delete-commit should have superseded freemap pages \
into the in-memory pool that the crash then orphaned",
first.freemap_orphans_reclaimed
);
db.begin().unwrap();
let second = db.defrag(DefragOptions::default()).unwrap();
db.commit().unwrap();
assert_eq!(
second.freemap_orphans_reclaimed, 0,
"second defrag sweep must find no orphans — the first sweep must \
have reclaimed all crash-orphaned freemap pages \
(first={}, second={})",
first.freemap_orphans_reclaimed, second.freemap_orphans_reclaimed
);
}