#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::path::PathBuf;
use std::time::{Duration, Instant};
use sqlite_core::{Database, Value};
use sqlite_forensic::carve_all_deleted_records;
const DELETE_LO: i64 = 40_001;
const DELETE_HI: i64 = 120_000;
#[allow(clippy::duration_suboptimal_units)]
const WALL_CLOCK_CEILING: Duration = Duration::from_secs(120);
fn perf_db_path() -> Option<PathBuf> {
std::env::var_os("SQLITE_FORENSIC_PERF_DB").map(PathBuf::from)
}
fn body_id(values: &[Value]) -> Option<i64> {
for v in values {
if let Value::Text(t) = v {
if let Some(rest) = t.strip_prefix("MSG-") {
if let Some((num, _)) = rest.split_once('-') {
if let Ok(id) = num.parse::<i64>() {
return Some(id);
}
}
}
}
}
None
}
#[test]
fn perf_smoke_carves_large_db_under_ceiling() {
let Some(path) = perf_db_path() else {
eprintln!(
"SKIP perf_smoke_carves_large_db_under_ceiling: set SQLITE_FORENSIC_PERF_DB \
(generate with tests/data/paper_fp/gen_large.py)"
);
return;
};
if !path.is_file() {
eprintln!(
"SKIP perf_smoke_carves_large_db_under_ceiling: {} not found \
(generate with tests/data/paper_fp/gen_large.py)",
path.display()
);
return;
}
let bytes = std::fs::read(&path).expect("read perf db");
let db = Database::open(bytes).expect("open perf db");
let started = Instant::now();
let recs = carve_all_deleted_records(&db);
let elapsed = started.elapsed();
let total_deleted = DELETE_HI - DELETE_LO + 1;
let mut recovered = std::collections::HashSet::new();
let mut live_false_positives = std::collections::HashSet::new();
for r in &recs {
if let Some(id) = body_id(&r.values) {
if (DELETE_LO..=DELETE_HI).contains(&id) {
recovered.insert(id);
} else {
live_false_positives.insert(id);
}
}
}
assert!(
live_false_positives.is_empty(),
"carve surfaced {} live rows as deleted (false positives): {:?}",
live_false_positives.len(),
{
let mut v: Vec<i64> = live_false_positives.iter().copied().collect();
v.sort_unstable();
v.truncate(10);
v
}
);
let recall = recovered.len() as f64 / total_deleted as f64;
assert!(
recall >= 0.95,
"carver must recover >=95% of the {total_deleted} deleted rows; got {} ({:.4})",
recovered.len(),
recall
);
assert!(
elapsed <= WALL_CLOCK_CEILING,
"carve of {} took {elapsed:?}, over the {WALL_CLOCK_CEILING:?} ceiling",
path.display()
);
eprintln!(
"perf: carved {} records, recovered {}/{} deleted ({:.4}), {} live FP, in {elapsed:?}",
recs.len(),
recovered.len(),
total_deleted,
recall,
live_false_positives.len()
);
}