use std::error::Error;
use std::path::{Path, PathBuf};
use crate::db::Database;
use crate::fence::crash_harness::{PinCase, TestResult, run_pin};
use super::test_support::{attested, build_store, sweep_attested};
use super::vacuum_stats;
const KEYS: [&[u8]; 3] = [b"alpha", b"beta", b"gamma"];
fn data_dir(world: &Path) -> PathBuf {
world.join("db")
}
fn plan(dir: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
let report = sweep_attested(dir, false)?;
Ok(report
.sweep
.ok_or("dry run carries a summary")?
.deleted_paths)
}
fn recover_converges(world: &Path) -> TestResult {
let dir = data_dir(world);
sweep_attested(&dir, true)?;
vacuum_stats(&attested(&dir))?;
let second = sweep_attested(&dir, true)?;
let deleted = second.sweep.ok_or("summary")?.deleted_nodes;
if deleted != 0 {
return Err(format!("second sweep deleted {deleted} nodes — not converged").into());
}
let db = Database::open(&dir)?;
for key in KEYS {
db.read_events(key)?;
}
drop(db);
Ok(())
}
fn between_mark_and_sweep_write(world: &Path) -> TestResult {
build_store(&data_dir(world), 1, &KEYS, 4)?;
let planned = plan(&data_dir(world))?;
if planned.is_empty() {
return Err("the store must carry garbage to sweep".into());
}
Ok(())
}
#[test]
fn kill_between_mark_and_sweep_converges() -> TestResult {
run_pin(&PinCase {
test_path: "db::vacuum::sweep_crash_tests::kill_between_mark_and_sweep_converges",
write: between_mark_and_sweep_write,
recover: recover_converges,
})
}
fn mid_sweep_write(world: &Path) -> TestResult {
let dir = data_dir(world);
build_store(&dir, 1, &KEYS, 4)?;
let planned = plan(&dir)?;
if planned.len() < 2 {
return Err("need at least two garbage nodes to unlink a strict subset".into());
}
for path in &planned[..planned.len() / 2] {
std::fs::remove_file(path)?;
}
Ok(())
}
#[test]
fn kill_mid_sweep_converges() -> TestResult {
run_pin(&PinCase {
test_path: "db::vacuum::sweep_crash_tests::kill_mid_sweep_converges",
write: mid_sweep_write,
recover: recover_converges,
})
}
fn between_stores_write(world: &Path) -> TestResult {
let dir = data_dir(world);
build_store(&dir, 2, &KEYS, 4)?;
let planned = plan(&dir)?;
let shard0: Vec<&PathBuf> = planned
.iter()
.filter(|path| path.to_string_lossy().contains("/shard-0/"))
.collect();
if shard0.is_empty() || shard0.len() == planned.len() {
return Err("both shards must carry garbage for a between-stores kill".into());
}
for path in shard0 {
std::fs::remove_file(path)?;
}
Ok(())
}
#[test]
fn kill_between_stores_converges() -> TestResult {
run_pin(&PinCase {
test_path: "db::vacuum::sweep_crash_tests::kill_between_stores_converges",
write: between_stores_write,
recover: recover_converges,
})
}
fn during_report_write_write(world: &Path) -> TestResult {
let dir = data_dir(world);
build_store(&dir, 1, &KEYS, 4)?;
sweep_attested(&dir, true)?;
Ok(())
}
#[test]
fn kill_during_report_write_converges() -> TestResult {
run_pin(&PinCase {
test_path: "db::vacuum::sweep_crash_tests::kill_during_report_write_converges",
write: during_report_write_write,
recover: recover_converges,
})
}