1use graph_api_lib::Graph;
2use graph_api_lib::{EdgeReference, EdgeSearch};
3use proptest::prelude::*;
4use std::collections::HashSet;
5
6#[derive(Debug, Clone)]
7pub enum GraphOperation {
8 AddVertex(u32),
9 RemoveVertex(usize),
10 AddEdge(usize, usize),
11 RemoveEdge(usize),
12}
13
14prop_compose! {
15 pub fn arb_graph_operation()(
16 op_type in 0..4u8,
17 vertex_weight in any::<u32>(),
18 index in any::<usize>(),
19 from_idx in any::<usize>(),
20 to_idx in any::<usize>(),
21 ) -> GraphOperation {
22 match op_type {
23 0 => GraphOperation::AddVertex(vertex_weight),
24 1 => GraphOperation::RemoveVertex(index),
25 2 => GraphOperation::AddEdge(from_idx, to_idx),
26 _ => GraphOperation::RemoveEdge(index),
27 }
28 }
29}
30
31pub fn test_fuzz(
32 mut graph: impl Graph<Vertex = u32, Edge = ()>,
33 operations: Vec<GraphOperation>,
34) -> bool {
35 let mut vertex_ids = HashSet::new();
36 let mut edge_ids = HashSet::new();
37
38 for op in operations {
39 match op {
40 GraphOperation::AddVertex(weight) => {
41 let id = graph.add_vertex(weight);
42 vertex_ids.insert(id);
43 }
44 GraphOperation::RemoveVertex(idx) => {
45 if let Some(id) = vertex_ids.iter().nth(idx % (vertex_ids.len() + 1)).cloned() {
46 let connected_edges: HashSet<_> = edge_ids
49 .iter()
50 .filter(|&&edge_id| {
51 let edge = graph.edge(edge_id).unwrap();
52 let head = edge.head();
53 let tail = edge.tail();
54 head == id || tail == id
55 })
56 .cloned()
57 .collect();
58
59 graph.remove_vertex(id);
61 vertex_ids.remove(&id);
62
63 edge_ids = &edge_ids - &connected_edges;
65 }
66 }
67 GraphOperation::AddEdge(from_idx, to_idx) => {
68 let vertices: Vec<_> = vertex_ids.iter().collect();
69 if vertices.len() >= 2 {
70 let from = vertices[from_idx % vertices.len()];
71 let to = vertices[to_idx % vertices.len()];
72 let edge_id = graph.add_edge(*from, *to, ());
73 edge_ids.insert(edge_id);
74 }
75 }
76 GraphOperation::RemoveEdge(idx) => {
77 if let Some(id) = edge_ids.iter().nth(idx % (edge_ids.len() + 1)).cloned() {
78 graph.remove_edge(id);
80 edge_ids.remove(&id);
81 }
82 }
83 }
84
85 for edge_id in &edge_ids {
87 let edge = graph.edge(*edge_id).expect("Edge must exist");
88 assert!(vertex_ids.contains(&edge.head()));
89 assert!(vertex_ids.contains(&edge.tail()));
90 }
91
92 for start in &vertex_ids {
94 let reachable: HashSet<_> = graph
95 .walk()
96 .vertices_by_id([*start])
97 .edges(EdgeSearch::scan().outgoing())
98 .head()
99 .collect();
100
101 assert!(reachable.is_subset(&vertex_ids));
102 }
103 }
104 true
105}