use selene_core::{Change, EdgeId, GraphId, LabelSet, NodeId, PropertyMap, db_string};
use super::{append_wal, temp_dir};
use crate::{SeleneGraph, SharedGraph};
fn node_created(id: u64, label: &str) -> Change {
Change::NodeCreated {
id: NodeId::new(id),
labels: LabelSet::single(db_string(label).unwrap()),
properties: PropertyMap::new(),
}
}
fn edge_created(id: u64, source: u64, target: u64, label: &str) -> Change {
Change::EdgeCreated {
id: EdgeId::new(id),
label: db_string(label).unwrap(),
source: NodeId::new(source),
target: NodeId::new(target),
properties: PropertyMap::new(),
}
}
fn base_creates() -> Vec<Change> {
vec![
node_created(1, "trec.L"),
node_created(2, "trec.L"),
node_created(3, "trec.L"),
node_created(4, "trec.Keep"),
edge_created(1, 1, 2, "trec.E1"),
edge_created(2, 2, 3, "trec.E2"),
edge_created(3, 3, 4, "trec.E1"),
edge_created(4, 4, 1, "trec.E2"),
edge_created(5, 4, 4, "trec.E1"), ]
}
fn assert_same_observable_state(a: &SeleneGraph, b: &SeleneGraph) {
assert_eq!(a.node_store.alive, b.node_store.alive, "alive nodes differ");
assert_eq!(a.edge_store.alive, b.edge_store.alive, "alive edges differ");
assert_eq!(a.idx_label, b.idx_label, "node label index differs");
assert_eq!(
a.idx_edge_label, b.idx_edge_label,
"edge label index differs"
);
assert_eq!(
a.adjacency_out, b.adjacency_out,
"outgoing adjacency differs"
);
assert_eq!(a.adjacency_in, b.adjacency_in, "incoming adjacency differs");
}
#[test]
fn recovery_of_declarative_truncate_matches_expanded_form() {
let dir_a = temp_dir("trec-declarative");
let mut changes_a = base_creates();
changes_a.push(Change::NodesOfTypeTruncated {
label: db_string("trec.L").unwrap(),
});
append_wal(&dir_a, 0, &changes_a);
let recovered_a = SharedGraph::recover(&dir_a, GraphId::new(7)).unwrap();
let dir_b = temp_dir("trec-expanded");
let mut changes_b = base_creates();
changes_b.push(Change::NodeDeleted { id: NodeId::new(1) });
changes_b.push(Change::NodeDeleted { id: NodeId::new(2) });
changes_b.push(Change::NodeDeleted { id: NodeId::new(3) });
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(1) });
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(2) });
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(3) });
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(4) });
append_wal(&dir_b, 0, &changes_b);
let recovered_b = SharedGraph::recover(&dir_b, GraphId::new(7)).unwrap();
assert_same_observable_state(&recovered_a.read(), &recovered_b.read());
let g = recovered_a.read();
assert!(g.is_node_alive(NodeId::new(4)));
for id in [1_u64, 2, 3] {
assert!(!g.is_node_alive(NodeId::new(id)), "node {id} must be dead");
}
assert!(g.is_edge_alive(EdgeId::new(5)), "survivor edge stays alive");
for id in [1_u64, 2, 3, 4] {
assert!(!g.is_edge_alive(EdgeId::new(id)), "edge {id} must be dead");
}
let _ = std::fs::remove_dir_all(dir_a);
let _ = std::fs::remove_dir_all(dir_b);
}
#[test]
fn recovery_of_edge_type_truncate_matches_expanded_form() {
let dir_a = temp_dir("trec-edge-declarative");
let mut changes_a = base_creates();
changes_a.push(Change::EdgesOfTypeTruncated {
label: db_string("trec.E1").unwrap(),
});
append_wal(&dir_a, 0, &changes_a);
let recovered_a = SharedGraph::recover(&dir_a, GraphId::new(7)).unwrap();
let dir_b = temp_dir("trec-edge-expanded");
let mut changes_b = base_creates();
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(1) });
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(3) });
changes_b.push(Change::EdgeDeleted { id: EdgeId::new(5) });
append_wal(&dir_b, 0, &changes_b);
let recovered_b = SharedGraph::recover(&dir_b, GraphId::new(7)).unwrap();
assert_same_observable_state(&recovered_a.read(), &recovered_b.read());
let g = recovered_a.read();
assert_eq!(g.node_count(), 4, "edge truncate leaves all nodes alive");
assert!(g.edges_with_label(&db_string("trec.E1").unwrap()).is_none());
let _ = std::fs::remove_dir_all(dir_a);
let _ = std::fs::remove_dir_all(dir_b);
}
#[test]
fn recovery_of_graph_reset_matches_expanded_form() {
let dir_a = temp_dir("trec-reset-declarative");
let mut changes_a = base_creates();
changes_a.push(Change::GraphReset {});
append_wal(&dir_a, 0, &changes_a);
let recovered_a = SharedGraph::recover(&dir_a, GraphId::new(7)).unwrap();
let dir_b = temp_dir("trec-reset-expanded");
let mut changes_b = base_creates();
for id in 1_u64..=4 {
changes_b.push(Change::NodeDeleted {
id: NodeId::new(id),
});
}
for id in 1_u64..=5 {
changes_b.push(Change::EdgeDeleted {
id: EdgeId::new(id),
});
}
append_wal(&dir_b, 0, &changes_b);
let recovered_b = SharedGraph::recover(&dir_b, GraphId::new(7)).unwrap();
assert_same_observable_state(&recovered_a.read(), &recovered_b.read());
let g = recovered_a.read();
assert_eq!(g.node_count(), 0, "graph reset wipes every node");
assert_eq!(g.edge_count(), 0, "graph reset wipes every edge");
assert!(
g.nodes_with_label(&db_string("trec.Keep").unwrap())
.is_none(),
"label indexes are cleared for reset nodes"
);
assert!(
g.edges_with_label(&db_string("trec.E1").unwrap()).is_none(),
"edge-label indexes are cleared for reset edges"
);
let _ = std::fs::remove_dir_all(dir_a);
let _ = std::fs::remove_dir_all(dir_b);
}