use chisel::ChiselError;
mod common;
use common::{open_chisel, Backing};
fn test_defrag_without_active_txn_errors_immediately_body(b: &Backing) {
let mut db = open_chisel(b);
db.begin().unwrap();
for i in 0..4 {
db.allocate(&[i as u8; 32]).unwrap();
}
db.commit().unwrap();
let err = db.defrag(Default::default()).unwrap_err();
assert!(
matches!(err, ChiselError::NoActiveTransaction),
"expected NoActiveTransaction, got {err:?}"
);
}
dual_backing_test!(
test_defrag_without_active_txn_errors_immediately,
test_defrag_without_active_txn_errors_immediately_body
);
fn test_defrag_reclaims_space_after_deletes_body(b: &Backing) {
let mut db = open_chisel(b);
db.begin().unwrap();
let mut handles = Vec::new();
for i in 0..50 {
handles.push(db.allocate(&[i as u8; 200]).unwrap());
}
db.commit().unwrap();
db.begin().unwrap();
for &h in &handles[5..] {
db.delete(h).unwrap();
}
db.commit().unwrap();
db.begin().unwrap();
let result = db.defrag(Default::default()).unwrap();
db.commit().unwrap();
for (i, &h) in handles[..5].iter().enumerate() {
assert_eq!(
db.read(h).unwrap(),
vec![i as u8; 200],
"handle {h} (index {i}) lost or corrupted its value across defrag"
);
}
assert_eq!(
result.pages_examined, 1,
"expected exactly 1 sparse page examined"
);
assert_eq!(
result.values_moved, 5,
"expected exactly 5 values relocated"
);
assert_eq!(result.pages_freed, 1, "expected exactly 1 page freed");
}
dual_backing_test!(
test_defrag_reclaims_space_after_deletes,
test_defrag_reclaims_space_after_deletes_body
);
fn test_defrag_preserves_all_data_body(b: &Backing) {
let mut db = open_chisel(b);
db.begin().unwrap();
let h1 = db.allocate(b"alpha").unwrap();
let h2 = db.allocate(b"beta").unwrap();
let h3 = db.allocate(b"gamma").unwrap();
db.commit().unwrap();
db.begin().unwrap();
let _ = db.defrag(Default::default()).unwrap();
db.commit().unwrap();
assert_eq!(db.read(h1).unwrap(), b"alpha");
assert_eq!(db.read(h2).unwrap(), b"beta");
assert_eq!(db.read(h3).unwrap(), b"gamma");
}
dual_backing_test!(
test_defrag_preserves_all_data,
test_defrag_preserves_all_data_body
);
fn test_defrag_skips_dense_pages_body(b: &Backing) {
let mut db = open_chisel(b);
db.begin().unwrap();
for i in 0..50 {
db.allocate(format!("dense-{i}").as_bytes()).unwrap();
}
db.commit().unwrap();
db.begin().unwrap();
let result = db.defrag(Default::default()).unwrap();
db.commit().unwrap();
assert_eq!(
result.values_moved, 0,
"dense pages should not be touched by selective defrag"
);
assert_eq!(result.pages_freed, 0);
assert_eq!(result.pages_examined, 0);
}
dual_backing_test!(
test_defrag_skips_dense_pages,
test_defrag_skips_dense_pages_body
);
fn test_defrag_stats_are_page_accurate_body(b: &Backing) {
let mut db = open_chisel(b);
db.begin().unwrap();
let handles: Vec<chisel::Handle> = (0..100)
.map(|i| db.allocate(&[i as u8; 200]).unwrap())
.collect();
db.commit().unwrap();
db.begin().unwrap();
for &h in &handles[3..] {
db.delete(h).unwrap();
}
db.commit().unwrap();
db.begin().unwrap();
let result = db.defrag(Default::default()).unwrap();
db.commit().unwrap();
assert!(
result.pages_examined > 0 && result.pages_examined < 10,
"pages_examined should be unique sparse pages (got {})",
result.pages_examined
);
assert!(
result.pages_freed > 0,
"expected pages_freed > 0, got {}",
result.pages_freed
);
for (i, &h) in handles[..3].iter().enumerate() {
assert_eq!(db.read(h).unwrap(), [i as u8; 200]);
}
}
dual_backing_test!(
test_defrag_stats_are_page_accurate,
test_defrag_stats_are_page_accurate_body
);
fn test_defrag_respects_max_values_body(b: &Backing) {
use chisel::DefragOptions;
let mut db = open_chisel(b);
db.begin().unwrap();
let handles: Vec<chisel::Handle> = (0..50)
.map(|i| db.allocate(&[i as u8; 200]).unwrap())
.collect();
db.commit().unwrap();
db.begin().unwrap();
for &h in &handles[2..] {
db.delete(h).unwrap();
}
db.commit().unwrap();
db.begin().unwrap();
let result = db
.defrag(
DefragOptions::default()
.sparse_threshold(0.25)
.max_values(2),
)
.unwrap();
db.commit().unwrap();
assert!(
result.values_moved <= 2,
"max_values=2 should cap values_moved to 2, got {}",
result.values_moved
);
for (i, &h) in handles[..2].iter().enumerate() {
assert_eq!(db.read(h).unwrap(), [i as u8; 200]);
}
}
dual_backing_test!(
test_defrag_respects_max_values,
test_defrag_respects_max_values_body
);