Skip to main content
Glama
mod.rs64.3 kB
use std::collections::{ HashMap, HashSet, }; use petgraph::visit::{ Control, DfsEvent, IntoNeighbors, IntoNodeIdentifiers, }; use strum::EnumDiscriminants; use updates::subgraph_as_updates; use super::*; #[derive(Clone, PartialEq, Eq, Hash)] struct TestNodeWeight { id: SplitGraphNodeId, lineage_id: SplitGraphNodeId, name: String, merkle_tree_hash: MerkleTreeHash, } impl TestNodeWeight { fn set_name(&mut self, name: String) { self.name = name; } } impl std::fmt::Debug for TestNodeWeight { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "id = {:?},\nname = {}", self.id, self.name) } } impl NodeKind for () {} impl CustomNodeWeight for TestNodeWeight { type Kind = (); fn kind(&self) -> Self::Kind {} fn id(&self) -> SplitGraphNodeId { self.id } fn set_id(&mut self, id: SplitGraphNodeId) { self.id = id; } fn lineage_id(&self) -> SplitGraphNodeId { self.lineage_id } fn set_lineage_id(&mut self, id: SplitGraphNodeId) { self.lineage_id = id; } fn dot_details(&self) -> String { self.name.clone() } fn entity_kind(&self) -> EntityKind { EntityKind::Component } fn set_merkle_tree_hash(&mut self, hash: MerkleTreeHash) { self.merkle_tree_hash = hash; } fn merkle_tree_hash(&self) -> MerkleTreeHash { self.merkle_tree_hash } fn node_hash(&self) -> ContentHash { let mut hasher = ContentHash::hasher(); hasher.update(&self.id.inner().to_bytes()); hasher.update(self.name.as_bytes()); hasher.finalize() } } fn add_edges_to_splitgraph<'a, 'b, E, K>( graph: &'a mut SplitGraph<TestNodeWeight, E, K>, edges: &'a [(Option<&'b str>, E, &'b str, bool)], node_id_map: &'a HashMap<&'b str, Ulid>, ) where E: CustomEdgeWeight<K>, K: EdgeKind, { for (source, edge, target, ordered) in edges { let from_id = match source { Some(source) => node_id_map .get(source) .copied() .expect("source should be in map"), None => graph.root_id().expect("should have a root id"), }; let to_id = node_id_map .get(target) .copied() .expect("target should be in map"); if *ordered { graph .add_ordered_edge(from_id, edge.clone(), to_id) .expect("add ordered edge"); } else { graph .add_edge(from_id, edge.clone(), to_id) .expect("add edge"); } } } fn add_nodes_to_splitgraph<'a, 'b, E, K>( graph: &'a mut SplitGraph<TestNodeWeight, E, K>, nodes: &'a [&'b str], ) -> HashMap<&'b str, Ulid> where E: CustomEdgeWeight<K>, K: EdgeKind, { let mut node_id_map = HashMap::new(); for &node in nodes { let id = SplitGraphNodeId::new(); let node_weight = TestNodeWeight { id, lineage_id: SplitGraphNodeId::new(), name: node.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }; graph .add_or_replace_node(node_weight) .expect("add_or_replace_node"); node_id_map.insert(node, id); } node_id_map } fn add_nodes_to_subgraph<'a, 'b, E, K>( graph: &'a mut SubGraph<TestNodeWeight, E, K>, nodes: &'a [&'b str], ) -> HashMap<&'b str, Ulid> where E: CustomEdgeWeight<K>, K: EdgeKind, { let mut node_id_map = HashMap::new(); for &node in nodes { let id = SplitGraphNodeId::new(); let node_weight = TestNodeWeight { id, lineage_id: SplitGraphNodeId::new(), name: node.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }; graph.add_node(SplitGraphNodeWeight::Custom(node_weight)); node_id_map.insert(node, id); } node_id_map } #[derive(EnumDiscriminants, Clone, Debug, PartialEq, Eq, Hash)] #[strum_discriminants(derive(Hash))] pub enum TestEdgeWeight { EdgeA, EdgeB { is_default: bool }, } impl EdgeKind for TestEdgeWeightDiscriminants {} impl CustomEdgeWeight<TestEdgeWeightDiscriminants> for TestEdgeWeight { fn kind(&self) -> TestEdgeWeightDiscriminants { self.into() } fn edge_entropy(&self) -> Option<Vec<u8>> { Some(match self { TestEdgeWeight::EdgeA => 1u8.to_le_bytes().to_vec(), TestEdgeWeight::EdgeB { is_default } => [*is_default as u8].to_vec(), }) } fn clone_as_non_default(&self) -> Self { match self { TestEdgeWeight::EdgeA => TestEdgeWeight::EdgeA, TestEdgeWeight::EdgeB { .. } => TestEdgeWeight::EdgeB { is_default: false }, } } fn is_default(&self) -> bool { false } } #[test] fn ordered_edges() -> SplitGraphResult<()> { let mut splitgraph = SplitGraph::new(10000); let mut node_name_to_id_map = HashMap::new(); let container_nodes: Vec<TestNodeWeight> = ["a"] .into_iter() .map(|name| TestNodeWeight { id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }) .collect(); let mut nodes_per_container = HashMap::new(); for container_node in container_nodes { let container_name = container_node.name.to_owned(); let container_node_id = container_node.id(); splitgraph.add_or_replace_node(container_node)?; splitgraph.add_edge( splitgraph.root_id()?, TestEdgeWeight::EdgeA, container_node_id, )?; node_name_to_id_map.insert(container_name.to_owned(), container_node_id); let mut nodes = vec![]; for i in 0..5 { let node_name = format!("{container_name}-{i}"); let node_id = Ulid::new(); node_name_to_id_map.insert(node_name.to_owned(), node_id); splitgraph.add_or_replace_node(TestNodeWeight { id: node_id, lineage_id: SplitGraphNodeId::new(), name: node_name, merkle_tree_hash: MerkleTreeHash::nil(), })?; nodes.push(node_id); splitgraph.add_ordered_edge(container_node_id, TestEdgeWeight::EdgeA, node_id)?; } nodes_per_container.insert(container_node_id, nodes); } for (container_id, expected_nodes) in nodes_per_container { let ordered_children = splitgraph .ordered_children(container_id) .expect("should have ordered children"); assert_eq!(expected_nodes, ordered_children); let reversed_nodes: Vec<_> = expected_nodes.into_iter().rev().collect(); splitgraph.reorder_node(container_id, |current_order| { current_order .iter() .rev() .map(ToOwned::to_owned) .collect::<Vec<_>>() })?; let ordered_children = splitgraph .ordered_children(container_id) .expect("should have ordered children"); assert_eq!(reversed_nodes, ordered_children); } Ok(()) } // #[test] // fn cross_graph_external_source_deep_removals() -> SplitGraphResult<()> { // let mut split_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(1); // let nodes: Vec<_> = ["a", "b", "c", "d", "e", "f"] // .into_iter() // .map(|name| TestNodeWeight { // id: Ulid::new(), // lineage_id: SplitGraphNodeId::new(), // name: name.to_string(), // merkle_tree_hash: MerkleTreeHash::nil(), // }) // .collect(); // let mut node_id_map = HashMap::new(); // let mut last_node = None; // for node in &nodes { // split_graph.add_or_replace_node(node.clone())?; // node_id_map.insert(node.name.as_str(), node.id); // if node.name == "a" { // split_graph.add_edge(split_graph.root_id()?, TestEdgeWeight::EdgeA, node.id)?; // } // if let Some(last_node_id) = last_node { // split_graph.add_edge(last_node_id, TestEdgeWeight::EdgeA, node.id)?; // } // last_node = Some(node.id); // } // let new_root_id = Ulid::new(); // split_graph.add_or_replace_node(TestNodeWeight { // id: new_root_id, // lineage_id: Ulid::new(), // name: "replacement".into(), // merkle_tree_hash: MerkleTreeHash::nil(), // })?; // split_graph.add_edge(split_graph.root_id()?, TestEdgeWeight::EdgeA, new_root_id)?; // Ok(()) // } // #[test] fn default_edges() -> SplitGraphResult<()> { let mut split_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(3); let nodes: Vec<_> = ["a", "b", "c", "d", "e", "f"] .into_iter() .map(|name| TestNodeWeight { id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }) .collect(); let mut node_id_map = HashMap::new(); for node in &nodes { split_graph.add_or_replace_node(node.clone())?; node_id_map.insert(node.name.as_str(), node.id); if node.name == "a" { split_graph.add_edge(split_graph.root_id()?, TestEdgeWeight::EdgeA, node.id)?; } else { split_graph.add_edge( node_id_map.get(&"a").copied().unwrap(), TestEdgeWeight::EdgeB { is_default: false }, node.id, )?; } } let a_id = node_id_map.get(&"a").copied().unwrap(); let b_id = node_id_map.get(&"b").copied().unwrap(); split_graph.cleanup_and_merkle_tree_hash(); let mut updated_graph = split_graph.clone(); updated_graph.remove_edge(a_id, TestEdgeWeightDiscriminants::EdgeB, b_id)?; updated_graph.add_edge(a_id, TestEdgeWeight::EdgeB { is_default: true }, b_id)?; updated_graph.cleanup_and_merkle_tree_hash(); fn default_edge_assertions( graph: &SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants>, from_id: SplitGraphNodeId, default_target: SplitGraphNodeId, ) -> SplitGraphResult<()> { let edges: Vec<_> = graph .edges_directed(from_id, Outgoing)? .map(|edge| (edge.weight().clone(), edge.target())) .collect(); assert_eq!(5, edges.len()); let mut hit_default = false; for edge in edges { if edge.1 != default_target { assert!(matches!( edge.0, TestEdgeWeight::EdgeB { is_default: false } )); } else { assert!( matches!(edge.0, TestEdgeWeight::EdgeB { is_default: true }), "{default_target:?} should be default target" ); hit_default = true; } } assert!(hit_default); Ok(()) } default_edge_assertions(&updated_graph, a_id, b_id)?; let updates = split_graph.detect_updates(&updated_graph); split_graph.perform_updates(&updates); split_graph.cleanup_and_merkle_tree_hash(); default_edge_assertions(&split_graph, a_id, b_id)?; Ok(()) } #[test] fn cross_graph_node_id_updates() -> SplitGraphResult<()> { let mut split_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(1); let nodes: Vec<_> = ["a", "b", "c", "d", "e", "f"] .into_iter() .map(|name| TestNodeWeight { id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }) .collect(); let mut node_id_map = HashMap::new(); for node in &nodes { node_id_map.insert(node.name.as_str(), node.id()); split_graph.add_or_replace_node(node.clone())?; let subgraph = split_graph.subgraph_mut_for_node(node.id()).unwrap(); let index = subgraph.node_id_to_index(node.id()).unwrap(); subgraph.add_edge( subgraph.root_index, SplitGraphEdgeWeight::Custom(TestEdgeWeight::EdgeA), index, ); } split_graph.cleanup_and_merkle_tree_hash(); let mut split_graph_with_node_id_update = split_graph.clone(); let a_id = node_id_map.get(&"a").copied().unwrap(); let b_id = node_id_map.get(&"b").copied().unwrap(); let a_lineage_id = split_graph_with_node_id_update .node_weight(a_id) .unwrap() .lineage_id(); let f_id = node_id_map.get(&"f").copied().unwrap(); split_graph_with_node_id_update.remove_node(a_id)?; split_graph_with_node_id_update.update_node_id(dbg!(f_id), dbg!(a_id), a_lineage_id)?; split_graph_with_node_id_update.cleanup_and_merkle_tree_hash(); assert!(split_graph_with_node_id_update.node_weight(a_id).is_some()); let updates = split_graph.detect_updates(&split_graph_with_node_id_update); split_graph.perform_updates(&updates); split_graph.cleanup_and_merkle_tree_hash(); assert!(split_graph.node_weight(a_id).is_some()); assert!(split_graph.node_weight(f_id).is_none()); split_graph_with_node_id_update.remove_node(a_id)?; split_graph_with_node_id_update.update_node_id(dbg!(b_id), dbg!(a_id), a_lineage_id)?; split_graph_with_node_id_update.cleanup_and_merkle_tree_hash(); assert!(split_graph_with_node_id_update.node_weight(a_id).is_some()); assert!(split_graph_with_node_id_update.node_weight(b_id).is_none()); let updates = split_graph.detect_updates(&split_graph_with_node_id_update); dbg!(&updates); split_graph.perform_updates(&updates); split_graph.cleanup_and_merkle_tree_hash(); dbg!(split_graph.node_id_to_index(a_id)); assert!(split_graph.node_weight(a_id).is_some()); assert!(split_graph.node_weight(b_id).is_none()); Ok(()) } #[test] fn external_source_many_to_one_removal_and_id_updates() -> SplitGraphResult<()> { let mut split_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(3); let first_graph_nodes: Vec<_> = ["a", "b", "c"] .into_iter() .map(|name| TestNodeWeight { id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }) .collect(); for node in &first_graph_nodes { split_graph.add_or_replace_node(node.clone())?; } let second_graph_nodes: Vec<_> = ["d", "e", "f"] .into_iter() .map(|name| TestNodeWeight { id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }) .collect(); for node in &second_graph_nodes { split_graph.add_or_replace_node(node.clone())?; let subgraph = split_graph.subgraphs.get_mut(1).unwrap(); let index = subgraph.node_id_to_index(node.id()).unwrap(); subgraph.add_edge( subgraph.root_index, SplitGraphEdgeWeight::Custom(TestEdgeWeight::EdgeA), index, ); } for second_graph_node in &second_graph_nodes { for first_graph_node in &first_graph_nodes { split_graph.add_edge( second_graph_node.id, TestEdgeWeight::EdgeA, first_graph_node.id, )?; split_graph.add_edge( second_graph_node.id, TestEdgeWeight::EdgeB { is_default: false }, first_graph_node.id, )?; } } for first_graph_node in &first_graph_nodes { let subgraph = split_graph.subgraphs().first().unwrap(); let index = subgraph .node_index_by_id .get(&first_graph_node.id) .copied() .unwrap(); let incoming_edges: Vec<_> = subgraph .graph .edges_directed(index, Incoming) .filter(|edge| match edge.weight() { SplitGraphEdgeWeight::ExternalSource { source_id, .. } => second_graph_nodes .iter() .any(|node| node.id() == *source_id), _ => false, }) .map(|edge| edge.weight().clone()) .collect(); assert_eq!(incoming_edges.len(), 6); } split_graph.cleanup_and_merkle_tree_hash(); let mut split_graph_with_deletes = split_graph.clone(); for first_graph_node in &first_graph_nodes { let subgraph = split_graph_with_deletes.subgraphs().first().unwrap(); let index = subgraph .node_index_by_id .get(&first_graph_node.id) .copied() .unwrap(); let incoming_edges: Vec<_> = subgraph .graph .edges_directed(index, Incoming) .filter(|edge| match edge.weight() { SplitGraphEdgeWeight::ExternalSource { source_id, .. } => second_graph_nodes .iter() .any(|node| node.id() == *source_id), _ => false, }) .map(|edge| edge.weight().clone()) .collect(); assert_eq!(incoming_edges.len(), 6); } let d_id = second_graph_nodes.first().unwrap().id(); split_graph_with_deletes.remove_node(d_id)?; split_graph_with_deletes.cleanup_and_merkle_tree_hash(); let mut updated_split_graph = split_graph.clone(); updated_split_graph.cleanup_and_merkle_tree_hash(); let updates = updated_split_graph.detect_updates(&split_graph_with_deletes); updated_split_graph.perform_updates(&updates); updated_split_graph.cleanup_and_merkle_tree_hash(); for first_graph_node in &first_graph_nodes { let subgraph_1 = split_graph_with_deletes.subgraphs().first().unwrap(); let subgraph_2 = updated_split_graph.subgraphs().first().unwrap(); for subgraph in [subgraph_1, subgraph_2] { let index = subgraph .node_index_by_id .get(&first_graph_node.id) .copied() .unwrap(); let incoming_edges: Vec<_> = subgraph .graph .edges_directed(index, Incoming) .filter(|edge| match edge.weight() { SplitGraphEdgeWeight::ExternalSource { source_id, .. } => second_graph_nodes .iter() .any(|node| node.id() == *source_id), _ => false, }) .map(|edge| edge.weight().clone()) .collect(); assert_eq!(incoming_edges.len(), 4); } } let e_id = second_graph_nodes.get(1).unwrap().id(); let e_lineage_id = second_graph_nodes.get(1).unwrap().lineage_id(); let a_id = first_graph_nodes.first().unwrap().id(); let a_lineage_id = first_graph_nodes.first().unwrap().lineage_id(); split_graph_with_deletes.remove_edge(e_id, TestEdgeWeightDiscriminants::EdgeB, a_id)?; split_graph_with_deletes.cleanup_and_merkle_tree_hash(); let updates = updated_split_graph.detect_updates(&split_graph_with_deletes); updated_split_graph.perform_updates(&updates); let incoming_to_a: Vec<_> = split_graph_with_deletes .edges_directed(a_id, Incoming)? .map(|edge| (edge.weight().clone(), edge.source())) .collect(); assert_eq!(3, incoming_to_a.len()); for (_, source) in incoming_to_a { assert_ne!(source, d_id); assert!(second_graph_nodes.iter().any(|node| node.id() == source)); } let incoming_to_a: Vec<_> = updated_split_graph .edges_directed(a_id, Incoming)? .map(|edge| (edge.weight().clone(), edge.source())) .collect(); assert_eq!(3, incoming_to_a.len()); // update a_id let new_id = Ulid::new(); let external_targets_a = { let subgraph_1 = split_graph_with_deletes.subgraphs().get(1).unwrap(); let external_targets_a: Vec<_> = subgraph_1 .nodes() .filter(|node| match node { SplitGraphNodeWeight::ExternalTarget { target, .. } => *target == a_id, _ => false, }) .cloned() .collect(); external_targets_a }; assert_eq!(3, external_targets_a.len()); split_graph_with_deletes.update_node_id(dbg!(a_id), dbg!(new_id), a_lineage_id)?; split_graph_with_deletes.cleanup_and_merkle_tree_hash(); assert!(split_graph_with_deletes.node_weight(a_id).is_none()); assert!(split_graph_with_deletes.node_weight(new_id).is_some()); updated_split_graph.cleanup_and_merkle_tree_hash(); let updates = updated_split_graph.detect_updates(&split_graph_with_deletes); updated_split_graph.perform_updates(&updates); assert!(updated_split_graph.node_weight(new_id).is_some()); updated_split_graph.cleanup_and_merkle_tree_hash(); assert!(updated_split_graph.node_weight(a_id).is_none()); assert!(updated_split_graph.node_weight(new_id).is_some()); { let subgraph_1 = split_graph_with_deletes.subgraphs().get(1).unwrap(); let subgraph_1_updated = updated_split_graph.subgraphs().get(1).unwrap(); for subgraph in [subgraph_1, subgraph_1_updated] { let external_targets: Vec<_> = subgraph .nodes() .filter(|node| { external_targets_a .iter() .any(|ext_target_a| ext_target_a.id() == node.id()) }) .filter(|node| node.external_target_id() == Some(new_id)) .cloned() .collect(); assert_eq!(external_targets_a.len(), external_targets.len()); } } // update e_id id and verify source_id edge updates let external_source_id_old_e_before_changes: Vec<_> = split_graph_with_deletes .subgraphs() .first() .unwrap() .edges() .map(|(edge, _, _)| edge) .filter(|edge| edge.external_source_data().map(|data| data.source_id) == Some(e_id)) .cloned() .collect(); dbg!(external_source_id_old_e_before_changes.len()); let new_e_id = Ulid::new(); split_graph_with_deletes.update_node_id(e_id, new_e_id, e_lineage_id)?; split_graph_with_deletes.cleanup_and_merkle_tree_hash(); assert!(split_graph_with_deletes.node_weight(e_id).is_none()); assert!(split_graph_with_deletes.node_weight(new_e_id).is_some()); updated_split_graph.cleanup_and_merkle_tree_hash(); let updates = updated_split_graph.detect_updates(&split_graph_with_deletes); updated_split_graph.perform_updates(&updates); assert!(updated_split_graph.node_weight(new_e_id).is_some()); updated_split_graph.cleanup_and_merkle_tree_hash(); assert!(updated_split_graph.node_weight(e_id).is_none()); assert!(updated_split_graph.node_weight(new_e_id).is_some()); { let subgraph_0 = split_graph_with_deletes.subgraphs().first().unwrap(); let subgraph_0_updated = updated_split_graph.subgraphs().first().unwrap(); for subgraph in [subgraph_0, subgraph_0_updated] { let external_source_id_new_e: Vec<_> = subgraph .edges() .map(|(edge, _, _)| edge) .filter(|edge| { edge.external_source_data().map(|data| data.source_id) == Some(new_e_id) }) .cloned() .collect(); assert_eq!( external_source_id_old_e_before_changes.len(), external_source_id_new_e.len() ); let external_source_id_old_e: Vec<_> = subgraph .edges() .map(|(edge, _, _)| edge) .filter(|edge| edge.external_source_data().map(|data| data.source_id) == Some(e_id)) .cloned() .collect(); assert!(external_source_id_old_e.is_empty()); } } Ok(()) } #[test] fn replace_node() -> SplitGraphResult<()> { let mut splitgraph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(2); let mut nodes: Vec<TestNodeWeight> = ["1", "2", "3", "4", "5", "6"] .into_iter() .map(|name| TestNodeWeight { id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), }) .collect(); for node in &nodes { splitgraph.add_or_replace_node(node.clone())?; } for node in nodes.iter_mut() { node.name = format!("{}-{}", node.name, node.id); splitgraph.add_or_replace_node(node.clone())?; } for node in &nodes { assert_eq!( Some(node), splitgraph .raw_node_weight(node.id()) .and_then(|n| n.custom()) ); } Ok(()) } #[test] fn cross_graph_edges() -> SplitGraphResult<()> { for ordered in [false, true] { let mut splitgraph = SplitGraph::new(9); let mut unsplitgraph = SplitGraph::new(32678); let nodes = [ "graph-1-a", "graph-1-b", "graph-1-c", "graph-1-d", "graph-1-e", "graph-1-f", "graph-1-g", "graph-1-h", "graph-1-i", "graph-2-j", "graph-2-k", "graph-2-l", "graph-2-m", "graph-2-n", "graph-2-o", "graph-2-p", "graph-2-q", "graph-2-r", "graph-3-s", "graph-3-t", "graph-3-u", "graph-3-v", "graph-3-w", "graph-3-x", "graph-3-y", "graph-3-z", ]; let edges = [ ("", "graph-1-a"), ("graph-1-a", "graph-1-b"), ("graph-1-a", "graph-1-c"), ("graph-1-c", "graph-1-d"), ("graph-1-d", "graph-1-e"), ("graph-1-e", "graph-1-f"), ("graph-1-f", "graph-1-g"), ("graph-1-g", "graph-1-h"), ("graph-1-h", "graph-1-i"), ("graph-1-a", "graph-2-j"), ("graph-1-a", "graph-2-k"), ("graph-1-b", "graph-2-k"), ("graph-1-c", "graph-2-k"), ("", "graph-2-l"), ("graph-2-l", "graph-1-b"), ("graph-2-l", "graph-1-c"), ("graph-2-l", "graph-1-d"), ("graph-2-l", "graph-2-m"), ("graph-2-l", "graph-2-n"), ("graph-2-l", "graph-2-o"), ("graph-2-l", "graph-2-p"), ("graph-2-p", "graph-2-q"), ("graph-2-q", "graph-2-r"), ("graph-2-q", "graph-3-s"), ("graph-2-q", "graph-3-t"), ("graph-3-t", "graph-1-b"), ("graph-3-t", "graph-3-u"), ("graph-3-t", "graph-3-v"), ("graph-3-t", "graph-3-w"), ("graph-3-t", "graph-3-x"), ("graph-3-t", "graph-3-y"), ("graph-3-t", "graph-3-z"), ]; let mut name_to_id_map = HashMap::new(); for name in &nodes { let id = Ulid::new(); let lineage_id = SplitGraphNodeId::new(); splitgraph.add_or_replace_node(TestNodeWeight { id, lineage_id, name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), })?; unsplitgraph.add_or_replace_node(TestNodeWeight { id, lineage_id, name: name.to_string(), merkle_tree_hash: MerkleTreeHash::nil(), })?; name_to_id_map.insert(name, id); } let mut expected_outgoing_targets: HashMap<SplitGraphNodeId, HashSet<SplitGraphNodeId>> = HashMap::new(); let mut split_expected_incoming_sources: HashMap< SplitGraphNodeId, HashSet<SplitGraphNodeId>, > = HashMap::new(); let mut unsplit_expected_incoming_sources: HashMap< SplitGraphNodeId, HashSet<SplitGraphNodeId>, > = HashMap::new(); for (from_name, to_name) in edges { let (split_from_id, unsplit_from_id) = if from_name.is_empty() { (splitgraph.root_id()?, unsplitgraph.root_id()?) } else { ( name_to_id_map .get(&from_name) .copied() .expect("from name should exist"), name_to_id_map .get(&from_name) .copied() .expect("from name should exist"), ) }; let to_id = name_to_id_map.get(&to_name).copied().unwrap(); if ordered { splitgraph.add_ordered_edge(split_from_id, TestEdgeWeight::EdgeA, to_id)?; unsplitgraph.add_ordered_edge(unsplit_from_id, TestEdgeWeight::EdgeA, to_id)?; } else { splitgraph.add_edge(split_from_id, TestEdgeWeight::EdgeA, to_id)?; unsplitgraph.add_edge(unsplit_from_id, TestEdgeWeight::EdgeA, to_id)?; } expected_outgoing_targets .entry(split_from_id) .and_modify(|outgoing| { outgoing.insert(to_id); }) .or_insert(HashSet::from([to_id])); expected_outgoing_targets .entry(unsplit_from_id) .and_modify(|outgoing| { outgoing.insert(to_id); }) .or_insert(HashSet::from([to_id])); split_expected_incoming_sources .entry(to_id) .and_modify(|incoming| { incoming.insert(split_from_id); }) .or_insert(HashSet::from([split_from_id])); unsplit_expected_incoming_sources .entry(to_id) .and_modify(|incoming| { incoming.insert(unsplit_from_id); }) .or_insert(HashSet::from([unsplit_from_id])); } for from_name in &nodes { let (split_from_id, unsplit_from_id) = if from_name.is_empty() { (splitgraph.root_id()?, unsplitgraph.root_id()?) } else { let id = name_to_id_map .get(&from_name) .copied() .expect("should exist"); (id, id) }; let outgoing_targets: HashSet<SplitGraphNodeId> = splitgraph .edges_directed(split_from_id, Outgoing)? .map(|edge_ref| edge_ref.target()) .collect(); let unsplit_outgoing_targets: HashSet<SplitGraphNodeId> = unsplitgraph .edges_directed(unsplit_from_id, Outgoing)? .map(|edge_ref| edge_ref.target()) .collect(); let incoming_sources: HashSet<SplitGraphNodeId> = splitgraph .edges_directed(split_from_id, Incoming)? .map(|edge_ref| edge_ref.source()) .collect(); let unsplit_incoming_sources: HashSet<SplitGraphNodeId> = unsplitgraph .edges_directed(unsplit_from_id, Incoming)? .map(|edge_ref| edge_ref.source()) .collect(); if !outgoing_targets.is_empty() { assert_eq!( expected_outgoing_targets .get(&split_from_id) .cloned() .unwrap(), outgoing_targets ); assert_eq!( expected_outgoing_targets .get(&unsplit_from_id) .cloned() .unwrap(), unsplit_outgoing_targets ); for target_id in outgoing_targets { if let Some(node) = splitgraph .raw_node_weight(target_id) .and_then(|n| n.custom()) { assert_eq!( Some(target_id), name_to_id_map.get(&node.name.as_str()).copied() ); } } } if !incoming_sources.is_empty() { assert_eq!( split_expected_incoming_sources .get(&split_from_id) .cloned() .unwrap(), incoming_sources ); assert_eq!( unsplit_expected_incoming_sources .get(&unsplit_from_id) .cloned() .unwrap(), unsplit_incoming_sources ); } } // splitgraph.tiny_dot_to_file("before-removal"); // unsplitgraph.tiny_dot_to_file("unsplitgraph"); let graph_2_q = "graph-2-q"; let graph_3_t = "graph-3-t"; let graph_3_s = "graph-3-s"; let graph_2_q_id = name_to_id_map.get(&graph_2_q).copied().unwrap(); let graph_3_t_id = name_to_id_map.get(&graph_3_t).copied().unwrap(); let graph_3_s_id = name_to_id_map.get(&graph_3_s).copied().unwrap(); splitgraph.remove_edge( graph_2_q_id, TestEdgeWeightDiscriminants::EdgeA, graph_3_t_id, )?; unsplitgraph.remove_edge( graph_2_q_id, TestEdgeWeightDiscriminants::EdgeA, graph_3_t_id, )?; splitgraph.cleanup(); splitgraph.recalculate_merkle_tree_hashes_based_on_touched_nodes(); unsplitgraph.cleanup(); unsplitgraph.recalculate_merkle_tree_hashes_based_on_touched_nodes(); // splitgraph.tiny_dot_to_file("after-removal"); assert!(splitgraph.raw_node_weight(graph_2_q_id).is_some()); assert!(unsplitgraph.raw_node_weight(graph_2_q_id).is_some()); assert!(splitgraph.raw_node_weight(graph_3_s_id).is_some()); assert!(unsplitgraph.raw_node_weight(graph_3_s_id).is_some()); for graph_3_name in [ "graph-3-t", "graph-3-u", "graph-3-v", "graph-3-w", "graph-3-x", "graph-3-y", "graph-3-z", ] { let id = name_to_id_map.get(&graph_3_name).copied().unwrap(); assert!(splitgraph.raw_node_weight(id).is_none()); assert!(unsplitgraph.raw_node_weight(id).is_none()); } } Ok(()) } #[test] fn detect_changes_no_difference() -> SplitGraphResult<()> { let mut base_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(200); base_graph.cleanup_and_merkle_tree_hash(); let mut updated_graph = base_graph.clone(); updated_graph.cleanup_and_merkle_tree_hash(); assert!(base_graph.detect_changes(&updated_graph)?.is_empty()); Ok(()) } #[test] fn detect_changes_simple() -> SplitGraphResult<()> { for split_max in [1, 2, 3, 1000] { let mut base_graph = SplitGraph::new(split_max); let nodes = [ ("severian"), ("thecla"), ("terminus est"), ("dorcas"), ("vodalus"), ("drotte"), ]; // root --> severian --> thecla --> vodalus --> drotte // --> terminus est // --> vodalus (--> drotte) let edges = [ (None, TestEdgeWeight::EdgeA, "severian", false), (Some("severian"), TestEdgeWeight::EdgeA, "thecla", true), (Some("thecla"), TestEdgeWeight::EdgeA, "vodalus", false), ( Some("severian"), TestEdgeWeight::EdgeA, "terminus est", true, ), (Some("severian"), TestEdgeWeight::EdgeA, "vodalus", true), (Some("vodalus"), TestEdgeWeight::EdgeA, "drotte", false), ]; let node_id_map = add_nodes_to_splitgraph(&mut base_graph, &nodes); add_edges_to_splitgraph(&mut base_graph, &edges, &node_id_map); base_graph.cleanup_and_merkle_tree_hash(); // Changing severian should update severian, root let mut updated_graph = base_graph.clone(); let severian_id = node_id_map .get(&"severian") .copied() .expect("should get severian's id"); let mut severian = updated_graph .node_weight(severian_id) .cloned() .expect("severian node should exist"); severian.name = "severian the torturer".into(); updated_graph.add_or_replace_node(severian)?; updated_graph.cleanup_and_merkle_tree_hash(); let changes = updated_graph.detect_changes(&base_graph)?; let changed_ids: Vec<_> = changes .into_iter() .map(|change| change.entity_id.into_inner().into()) .collect(); assert_eq!( &[updated_graph.root_id()?, severian_id], changed_ids.as_slice() ); // Changing thecla should update severian, root let mut updated_graph = base_graph.clone(); let thecla_id = node_id_map .get(&"thecla") .copied() .expect("should get thecla's id"); let mut thecla = updated_graph .node_weight(thecla_id) .cloned() .expect("thecla node should exist"); thecla.name = "chatelaine thecla".into(); updated_graph.add_or_replace_node(thecla)?; updated_graph.cleanup_and_merkle_tree_hash(); let changes = updated_graph.detect_changes(&base_graph)?; let changed_ids: Vec<_> = changes .into_iter() .map(|change| change.entity_id.into_inner().into()) .collect(); assert_eq!( &[updated_graph.root_id()?, severian_id, thecla_id], changed_ids.as_slice() ); // Changing vodalus should update vodalus, thecla, severian, root let mut updated_graph = base_graph.clone(); let vodalus_id = node_id_map .get(&"vodalus") .copied() .expect("should get thecla's id"); let mut vodalus = updated_graph .node_weight(vodalus_id) .cloned() .expect("vodalus node should exist"); vodalus.name = "vodalus the exultant".into(); updated_graph.add_or_replace_node(vodalus)?; updated_graph.cleanup_and_merkle_tree_hash(); let changes = updated_graph.detect_changes(&base_graph)?; // Order of changes is trickier to predict now let changed_ids: HashSet<_> = changes .into_iter() .map(|change| change.entity_id.into_inner().into()) .collect(); assert_eq!( HashSet::from([updated_graph.root_id()?, severian_id, thecla_id, vodalus_id]), changed_ids, ); // Changing drotte should update the whole graph except terminus est let mut updated_graph = base_graph.clone(); let drotte_id = node_id_map .get(&"drotte") .copied() .expect("should get drotte's id"); let mut drotte = updated_graph .node_weight(drotte_id) .cloned() .expect("drotte node should exist"); drotte.name = "drotte the journeyman".into(); updated_graph.add_or_replace_node(drotte)?; updated_graph.cleanup_and_merkle_tree_hash(); let changes = updated_graph.detect_changes(&base_graph)?; let changed_ids: HashSet<_> = changes .into_iter() .map(|change| change.entity_id.into_inner().into()) .collect(); assert_eq!( HashSet::from([ updated_graph.root_id()?, severian_id, thecla_id, vodalus_id, drotte_id ]), changed_ids, ); } Ok(()) } #[test] fn detect_and_perform_updates_ordered_containers() -> SplitGraphResult<()> { for split_max in [1, 2, 1000] { let mut base_graph = SplitGraph::new(split_max); base_graph.cleanup_and_merkle_tree_hash(); let mut updated_graph = base_graph.clone(); updated_graph.cleanup_and_merkle_tree_hash(); let damaya = TestNodeWeight { name: "damaya".to_string(), id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), merkle_tree_hash: MerkleTreeHash::nil(), }; let evil_earth = TestNodeWeight { name: "evil_earth".to_string(), id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), merkle_tree_hash: MerkleTreeHash::nil(), }; updated_graph.add_or_replace_node(damaya.clone())?; updated_graph.add_edge(updated_graph.root_id()?, TestEdgeWeight::EdgeA, damaya.id())?; updated_graph.add_or_replace_node(evil_earth.clone())?; updated_graph.add_edge( updated_graph.root_id()?, TestEdgeWeight::EdgeA, evil_earth.id(), )?; let mut name_to_id_map = HashMap::new(); let children = ["corundum", "uche", "nassun"]; let mut ordered_child_ids = vec![]; for name in &children { let new_node = TestNodeWeight { name: name.to_string(), id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), merkle_tree_hash: MerkleTreeHash::nil(), }; ordered_child_ids.push(new_node.id()); updated_graph.add_or_replace_node(new_node.clone())?; name_to_id_map.insert(name.to_string(), new_node.id()); updated_graph.add_ordered_edge( damaya.id(), TestEdgeWeight::EdgeB { is_default: false }, new_node.id(), )?; updated_graph.add_edge(evil_earth.id(), TestEdgeWeight::EdgeA, new_node.id())?; } updated_graph.cleanup_and_merkle_tree_hash(); let updates = base_graph.detect_updates(&updated_graph); assert!(!updates.is_empty()); base_graph.perform_updates(&updates); base_graph.cleanup_and_merkle_tree_hash(); let updates = base_graph.detect_updates(&updated_graph); assert!(updates.is_empty()); assert_eq!( Some(&ordered_child_ids), updated_graph.ordered_children(damaya.id()).as_ref() ); assert_eq!( Some(&ordered_child_ids), base_graph.ordered_children(damaya.id()).as_ref() ); let evil_earth_outgoing_updated: HashSet<_> = updated_graph .edges_directed(evil_earth.id(), Outgoing)? .map(|edge_ref| edge_ref.target()) .collect(); let evil_earth_outgoing_base: HashSet<_> = base_graph .edges_directed(evil_earth.id(), Outgoing)? .map(|edge_ref| edge_ref.target()) .collect(); assert!( evil_earth_outgoing_base .difference(&evil_earth_outgoing_updated) .next() .is_none() ); updated_graph.reorder_node(damaya.id(), |order| order.iter().copied().rev().collect())?; let subgraph_for_damaya = updated_graph.subgraph_index_for_node(damaya.id()).unwrap(); let updated_root_merkle_before_calculation = updated_graph .raw_node_weight(updated_graph.subgraph_root_id(subgraph_for_damaya).unwrap()) .unwrap() .merkle_tree_hash(); updated_graph.cleanup_and_merkle_tree_hash(); let updated_root_merkle_after_calculation = updated_graph .raw_node_weight(updated_graph.subgraph_root_id(subgraph_for_damaya).unwrap()) .unwrap() .merkle_tree_hash(); assert_ne!( updated_root_merkle_before_calculation, updated_root_merkle_after_calculation ); let reversed_ids: Vec<_> = ordered_child_ids.iter().copied().rev().collect(); assert_eq!( Some(&reversed_ids), updated_graph.ordered_children(damaya.id()).as_ref() ); let updates_after_reorder = base_graph.detect_updates(&updated_graph); assert_eq!(1, updates_after_reorder.len()); assert!(matches!( updates_after_reorder.first().unwrap(), Update::ReplaceNode { node_weight: SplitGraphNodeWeight::Ordering { .. }, .. } )); base_graph.perform_updates(&updates_after_reorder); base_graph.cleanup_and_merkle_tree_hash(); assert_eq!( Some(&reversed_ids), base_graph.ordered_children(damaya.id()).as_ref() ); } Ok(()) } #[test] fn detect_updates_simple() -> SplitGraphResult<()> { let mut base_graph = SplitGraph::new(3200); base_graph.cleanup_and_merkle_tree_hash(); let mut updated_graph = base_graph.clone(); updated_graph.cleanup_and_merkle_tree_hash(); assert!(base_graph.detect_updates(&updated_graph).is_empty()); let new_node = TestNodeWeight { name: "damaya".to_string(), id: Ulid::new(), lineage_id: SplitGraphNodeId::new(), merkle_tree_hash: MerkleTreeHash::nil(), }; updated_graph.add_or_replace_node(new_node.clone())?; updated_graph.add_edge( updated_graph.root_id()?, TestEdgeWeight::EdgeA, new_node.id(), )?; updated_graph.cleanup_and_merkle_tree_hash(); let updates = base_graph.detect_updates(&updated_graph); assert_eq!(2, updates.len()); let update_1 = updates.first().unwrap(); let update_2 = updates.get(1).unwrap(); assert!(matches!( update_1, Update::NewNode { node_weight: SplitGraphNodeWeight::Custom(TestNodeWeight { .. }), .. } )); let Update::NewNode { node_weight: SplitGraphNodeWeight::Custom(custom_node), .. } = update_1 else { unreachable!("we already asserted this!") }; assert_eq!(new_node.node_hash(), custom_node.node_hash()); assert!(matches!( update_2, Update::NewEdge { edge_weight: SplitGraphEdgeWeight::Custom(TestEdgeWeight::EdgeA), .. } )); let Update::NewEdge { source, destination, .. } = update_2 else { unreachable!("bridge over the river kwai"); }; assert_eq!(updated_graph.root_id()?, source.id); assert_eq!(new_node.id(), destination.id); let inverse_updates = updated_graph.detect_updates(&base_graph); assert_eq!(1, inverse_updates.len()); assert!(matches!( inverse_updates.first().unwrap(), Update::RemoveEdge { .. } )); let mut second_updated_graph = updated_graph.clone(); let mut updated_node = new_node.clone(); updated_node.set_name("syenite".into()); second_updated_graph.add_or_replace_node(updated_node)?; second_updated_graph.cleanup_and_merkle_tree_hash(); let replace_node_update = updated_graph.detect_updates(&second_updated_graph); assert!(matches!( replace_node_update.first().unwrap(), Update::ReplaceNode { node_weight: SplitGraphNodeWeight::Custom(TestNodeWeight { .. }), base_graph_node_id: None, .. } )); Ok(()) } #[test] fn single_subgraph_as_updates() -> SplitGraphResult<()> { let nodes = ["r", "t", "u", "v", "a", "b", "c", "d", "e"]; let edges = [ (None, "r"), (None, "t"), (Some("t"), "u"), (Some("u"), "v"), (Some("r"), "a"), (Some("r"), "b"), (Some("r"), "e"), (Some("a"), "c"), (Some("b"), "c"), (Some("c"), "d"), (Some("b"), "d"), (Some("d"), "e"), (Some("c"), "e"), (Some("e"), "u"), (Some("c"), "u"), (Some("a"), "u"), (Some("a"), "b"), ]; let mut subgraph = SubGraph::new_with_root(); let node_id_map = add_nodes_to_subgraph(&mut subgraph, &nodes); for (source, target) in edges { let from_index = match source { Some(name) => subgraph .node_id_to_index(node_id_map.get(&name).copied().unwrap()) .unwrap(), None => subgraph.root_index, }; let to_index = subgraph .node_id_to_index(node_id_map.get(&target).copied().unwrap()) .unwrap(); subgraph.add_edge( from_index, SplitGraphEdgeWeight::Custom(TestEdgeWeight::EdgeA), to_index, ); } subgraph.remove_externals(); subgraph.cleanup_maps(); let subgraph_root_id = subgraph .graph .node_weight(subgraph.root_index) .unwrap() .id(); let expected_edges: Vec<Update<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants>> = edges .into_iter() .map(|(source, target)| { let source_id = source .map(|source| node_id_map.get(&source).copied().unwrap()) .unwrap_or(subgraph_root_id); let destination_id = node_id_map.get(&target).copied().unwrap(); let source = subgraph.node_weight(source_id).unwrap(); let destination = subgraph.node_weight(destination_id).unwrap(); Update::NewEdge { source: source.into(), destination: destination.into(), edge_weight: SplitGraphEdgeWeight::Custom(TestEdgeWeight::EdgeA), subgraph_root_id, } }) .collect(); let updates = subgraph_as_updates(&subgraph, subgraph_root_id); assert!(!updates.is_empty()); let mut new_edge_count = 0; let mut new_node_count = 0; for update in updates { match &update { new_edge @ Update::NewEdge { .. } => { new_edge_count += 1; assert!(expected_edges.contains(new_edge)); } Update::NewNode { node_weight: SplitGraphNodeWeight::Custom(TestNodeWeight { id, name, .. }), .. } => { new_node_count += 1; assert_eq!(node_id_map.get(&name.as_str()), Some(id)) } _ => {} } } assert_eq!(expected_edges.len(), new_edge_count); assert_eq!(nodes.len(), new_node_count); Ok(()) } #[test] fn graph_dfs() -> SplitGraphResult<()> { let mut split_graph = SplitGraph::new(1); let nodes = &["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]; let node_id_map = add_nodes_to_splitgraph( &mut split_graph, &["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"], ); let mut first_layer = vec![]; for node in nodes.iter().take(3).copied() { let node_id = node_id_map.get(node).copied().unwrap(); first_layer.push(node_id); split_graph.add_edge(split_graph.root_id()?, TestEdgeWeight::EdgeA, node_id)?; } let mut second_layer = vec![]; for first_layer_node_id in first_layer { for node in nodes.iter().skip(3).take(3).copied() { let node_id = node_id_map.get(node).copied().unwrap(); second_layer.push(node_id); split_graph.add_edge(first_layer_node_id, TestEdgeWeight::EdgeA, node_id)?; } } for second_layer_node_id in second_layer { for node in nodes.iter().skip(6).take(6).copied() { let node_id = node_id_map.get(node).copied().unwrap(); split_graph.add_edge(second_layer_node_id, TestEdgeWeight::EdgeA, node_id)?; } } let mut expected_nodes = HashSet::from_iter(node_id_map.values().copied()); expected_nodes.insert(split_graph.root_id()?); let mut dfs = petgraph::visit::DfsPostOrder::new(&split_graph, split_graph.root_id()?); let mut found_nodes = HashSet::new(); while let Some(node_id) = dfs.next(&split_graph) { found_nodes.insert(node_id); } assert_eq!(expected_nodes, found_nodes); petgraph::visit::Dfs::new(&split_graph, split_graph.root_id()?); let mut dfs = petgraph::visit::DfsPostOrder::new(&split_graph, split_graph.root_id()?); let mut found_nodes = HashSet::new(); while let Some(node_id) = dfs.next(&split_graph) { found_nodes.insert(node_id); } assert_eq!(expected_nodes, found_nodes); let mut found_nodes = HashSet::new(); petgraph::visit::depth_first_search(&split_graph, Some(split_graph.root_id()?), |event| { match event { DfsEvent::Discover(node_id, _) => { found_nodes.insert(node_id); } DfsEvent::BackEdge(_, _) => { panic!("should not have back edges (they indicate a cycle)"); } _ => {} } Control::<()>::Continue }); assert_eq!(expected_nodes, found_nodes); Ok(()) } #[test] fn graph_cycle_test() -> SplitGraphResult<()> { let mut split_graph = SplitGraph::new(3); let mut node_id_map = add_nodes_to_splitgraph(&mut split_graph, &["a", "b", "c", "d", "e", "f"]); for node_id in node_id_map.values().copied() { split_graph.add_edge_with_cycle_check( split_graph.root_id()?, TestEdgeWeight::EdgeA, node_id, )?; } split_graph.add_edge_with_cycle_check( node_id_map.get(&"a").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get(&"b").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"b").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get(&"c").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"a").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get(&"c").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"a").copied().unwrap(), TestEdgeWeight::EdgeB { is_default: false }, node_id_map.get(&"c").copied().unwrap(), )?; assert!( split_graph .add_edge_with_cycle_check( node_id_map.get(&"c").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get(&"a").copied().unwrap(), ) .is_err() ); assert!(split_graph.is_acyclic_directed()); split_graph.add_edge_with_cycle_check( node_id_map.get(&"c").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get(&"f").copied().unwrap(), )?; assert!( split_graph .add_edge_with_cycle_check( node_id_map.get(&"f").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get(&"a").copied().unwrap(), ) .is_err() ); assert!(split_graph.is_acyclic_directed()); node_id_map.extend(add_nodes_to_splitgraph( &mut split_graph, &["g", "h", "i", "j", "k", "l"], )); for edge in [ TestEdgeWeight::EdgeA, TestEdgeWeight::EdgeB { is_default: false }, ] { split_graph.add_edge_with_cycle_check( node_id_map.get(&"a").copied().unwrap(), edge.clone(), node_id_map.get(&"g").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"g").copied().unwrap(), edge.clone(), node_id_map.get(&"h").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"h").copied().unwrap(), edge.clone(), node_id_map.get(&"i").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"h").copied().unwrap(), edge.clone(), node_id_map.get(&"c").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"h").copied().unwrap(), edge.clone(), node_id_map.get(&"b").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"h").copied().unwrap(), edge.clone(), node_id_map.get(&"j").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"h").copied().unwrap(), edge.clone(), node_id_map.get(&"k").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"h").copied().unwrap(), edge.clone(), node_id_map.get(&"l").copied().unwrap(), )?; split_graph.add_edge_with_cycle_check( node_id_map.get(&"l").copied().unwrap(), edge.clone(), node_id_map.get("b").copied().unwrap(), )?; } assert!( split_graph .add_edge_with_cycle_check( node_id_map.get(&"l").copied().unwrap(), TestEdgeWeight::EdgeA, node_id_map.get("a").copied().unwrap(), ) .is_err() ); Ok(()) } #[test] fn graph_cycle_test_mimic_component_parentage() -> SplitGraphResult<()> { for split_max in [1, 2, 3, 500] { let mut split_graph = SplitGraph::new(split_max); let node_id_map = add_nodes_to_splitgraph( &mut split_graph, &[ "components", "variants", "sv1", "sv1_prop", "sv1_prop2", "sv1_prop3", "sv1_prop4", "sv1_prop5", "sv1_prop6", "sv2", "sv2_prop", "sv2_prop2", "sv2_prop3", "component1", "component1_av", "component1_av2", "component1_av3", "component1_av4", "component1_av5", "component1_av6", "component2", "component2_av", "component2_av2", "component2_av3", ], ); let edges = [ (None, "components"), (None, "variants"), (Some("variants"), "sv1"), (Some("sv1"), "sv1_prop"), (Some("sv1_prop"), "sv1_prop2"), (Some("sv1_prop2"), "sv1_prop3"), (Some("sv1_prop2"), "sv1_prop4"), (Some("sv1_prop4"), "sv1_prop5"), (Some("sv1_prop4"), "sv1_prop6"), (Some("components"), "component1"), (Some("component1"), "sv1"), (Some("component1"), "component1_av"), (Some("component1_av"), "sv1_prop"), (Some("component1_av"), "component1_av2"), (Some("component1_av2"), "sv1_prop2"), (Some("component1_av2"), "component1_av3"), (Some("component1_av3"), "sv1_prop3"), (Some("component1_av2"), "component1_av4"), (Some("component1_av4"), "sv1_prop4"), (Some("component1_av4"), "component1_av5"), (Some("component1_av5"), "sv1_prop5"), (Some("component1_av5"), "component1_av6"), (Some("component1_av6"), "sv1_prop6"), (Some("variants"), "sv2"), (Some("sv2"), "sv2_prop"), (Some("components"), "component2"), (Some("component2"), "component2_av"), (Some("component2"), "sv2"), (Some("component2_av"), "sv2_prop"), (Some("component2_av"), "component2_av2"), (Some("component2_av2"), "sv2_prop2"), (Some("component2_av2"), "component2_av3"), (Some("component2_av3"), "sv2_prop3"), ]; for (from, to) in edges { let from_id = match from { Some(from) => node_id_map.get(from).copied().unwrap(), None => split_graph.root_id()?, }; let to_id = node_id_map.get(to).copied().unwrap(); split_graph.add_edge_with_cycle_check(from_id, TestEdgeWeight::EdgeA, to_id)?; } split_graph.add_edge_with_cycle_check( node_id_map.get("component1").copied().unwrap(), TestEdgeWeight::EdgeB { is_default: true }, node_id_map.get("component2").copied().unwrap(), )?; assert!( split_graph .add_edge_with_cycle_check( node_id_map.get("component2").copied().unwrap(), TestEdgeWeight::EdgeB { is_default: true }, node_id_map.get("component1").copied().unwrap(), ) .is_err() ); } Ok(()) } #[test] fn into_node_identifiers() -> SplitGraphResult<()> { let mut split_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(2); let node_id_map = add_nodes_to_splitgraph(&mut split_graph, &["a", "b", "c", "d", "e", "f"]); let ids: Vec<_> = split_graph.node_identifiers().collect(); // 3 graph roots, 6 test nodes assert_eq!(9, ids.len()); let mut custom_nodes = Vec::new(); let mut graph_roots = Vec::new(); for id in ids { let weight = split_graph.raw_node_weight(id); assert!(weight.is_some()); match weight { Some(weight) => match weight { SplitGraphNodeWeight::Custom(_) => { custom_nodes.push(id); } SplitGraphNodeWeight::ExternalTarget { .. } => { panic!("there should be no external targets"); } SplitGraphNodeWeight::Ordering { .. } => { panic!("there should be no ordering nodes"); } SplitGraphNodeWeight::GraphRoot { id, .. } | SplitGraphNodeWeight::SubGraphRoot { id, .. } => { graph_roots.push(id); } }, None => unreachable!("i just asserted it was some"), } } assert_eq!(6, custom_nodes.len()); assert_eq!(3, graph_roots.len()); let node_ids: HashSet<SplitGraphNodeId> = HashSet::from_iter(node_id_map.into_values()); let custom_nodes = HashSet::from_iter(custom_nodes); assert_eq!(node_ids, custom_nodes); Ok(()) } #[test] fn into_neighbors() -> SplitGraphResult<()> { let mut split_graph: SplitGraph<TestNodeWeight, TestEdgeWeight, TestEdgeWeightDiscriminants> = SplitGraph::new(2); let node_id_map = add_nodes_to_splitgraph(&mut split_graph, &["a", "b", "c", "d", "e", "f"]); for &node_id in node_id_map.values() { split_graph.add_edge(split_graph.root_id()?, TestEdgeWeight::EdgeA, node_id)?; } for id in split_graph.node_identifiers() { dbg!(split_graph.raw_node_weight(id)); for neighbor_id in split_graph.neighbors(id) { dbg!(split_graph.raw_node_weight(neighbor_id)); } } Ok(()) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server