#![cfg(unix)]
use std::process::{Command, Stdio};
use std::time::Duration;
#[test]
fn crash_writer_child() {
let Some(db_path) = std::env::var_os("TOPODB_CRASH_WRITER") else {
return; };
let db = topodb::Db::open(std::path::Path::new(&db_path)).unwrap();
let scope = topodb::Scope::Shared;
let mut prior_memories: Vec<topodb::NodeId> = Vec::new();
let mut round: u64 = 0;
loop {
let m = topodb::NodeId::new();
let e = topodb::NodeId::new();
let mut ops = vec![
topodb::Op::CreateNode {
id: m,
scope,
label: "Memory".into(),
props: [(
"content".to_string(),
topodb::PropValue::Str("crash corpus".into()),
)]
.into_iter()
.collect(),
},
topodb::Op::CreateNode {
id: e,
scope,
label: "Entity".into(),
props: [("name".to_string(), topodb::PropValue::Str(format!("E{m}")))]
.into_iter()
.collect(),
},
topodb::Op::CreateEdge {
id: topodb::EdgeId::new(),
scope,
ty: "about".into(),
from: m,
to: e,
props: Default::default(),
valid_from: None,
},
topodb::Op::SetNodeProps {
id: m,
props: [(
"content".to_string(),
Some(topodb::PropValue::Str("crash corpus, revised".into())),
)]
.into_iter()
.collect(),
},
];
let removing = if round.is_multiple_of(5) && !prior_memories.is_empty() {
let old = prior_memories.remove(0);
ops.push(topodb::Op::RemoveNode { id: old });
Some(old)
} else {
None
};
if db.submit(ops).is_ok() {
prior_memories.push(m);
if prior_memories.len() > 32 {
prior_memories.remove(0);
}
} else if let Some(old) = removing {
prior_memories.insert(0, old);
}
round += 1;
}
}
fn verify_replay_equivalence(db: &topodb::Db) {
let nodes_before = db.debug_dump_nodes();
let edges_before = db.debug_dump_edges();
let adjacency_before = db.debug_dump_adjacency().unwrap();
let postings_before = db.debug_dump_postings().unwrap();
let vectors_before = db.debug_dump_vectors().unwrap();
let embedding_ref_before = db.debug_dump_embedding_ref().unwrap();
let vector_dims_before = db.debug_dump_vector_dims().unwrap();
let label_index_before = db.debug_dump_label_index().unwrap();
db.rebuild_state_from_ops().unwrap();
assert_eq!(
nodes_before,
db.debug_dump_nodes(),
"NODES table must equal a replay of the surviving op log"
);
assert_eq!(
edges_before,
db.debug_dump_edges(),
"EDGES table must equal a replay of the surviving op log"
);
assert_eq!(
adjacency_before,
db.debug_dump_adjacency().unwrap(),
"OUT_ADJ/IN_ADJ must equal a replay of the surviving op log"
);
assert_eq!(
postings_before,
db.debug_dump_postings().unwrap(),
"POSTINGS must equal a replay of the surviving op log"
);
assert_eq!(
vectors_before,
db.debug_dump_vectors().unwrap(),
"VECTORS must equal a replay of the surviving op log"
);
assert_eq!(
embedding_ref_before,
db.debug_dump_embedding_ref().unwrap(),
"EMBEDDING_REF must equal a replay of the surviving op log"
);
assert_eq!(
vector_dims_before,
db.debug_dump_vector_dims().unwrap(),
"VECTOR_DIMS must equal a replay of the surviving op log"
);
assert_eq!(
label_index_before,
db.debug_dump_label_index().unwrap(),
"LABEL_INDEX must equal a replay of the surviving op log"
);
}
#[test]
fn killed_mid_write_recovers_and_replays_identically() {
let exe = std::env::current_exe().unwrap();
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("crash.redb");
let mut prev_count = 0usize;
for round in 0..25 {
let mut child = Command::new(&exe)
.args(["crash_writer_child", "--exact", "--nocapture"])
.env("TOPODB_CRASH_WRITER", &db_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
std::thread::sleep(Duration::from_millis(1300 + (round * 97) % 900));
child.kill().unwrap(); child.wait().unwrap();
let db = topodb::Db::open(&db_path).unwrap();
verify_replay_equivalence(&db);
let count = db.debug_dump_nodes().len();
eprintln!("round {round}: cumulative node count = {count}");
if round >= 1 {
assert!(
count > prev_count,
"round {round}: node count did not grow ({prev_count} -> {count}) — \
the child may have been killed before writing anything, which would \
make this round's equivalence check pass vacuously"
);
}
prev_count = count;
drop(db);
}
assert!(
prev_count > 50,
"final cumulative node count {prev_count} is too small to show the child \
wrote meaningfully across the 25 rounds"
);
}