graph_api_test/
lib.rs

1pub extern crate proptest;
2pub use proptest::*;
3pub mod fuzz;
4pub mod graph;
5pub mod index;
6pub mod steps;
7
8use graph_api_derive::{EdgeExt, VertexExt};
9use graph_api_lib::ElementId;
10#[allow(unused_imports)]
11use graph_api_lib::{
12    SupportsClear, SupportsEdgeAdjacentLabelIndex, SupportsEdgeHashIndex, SupportsEdgeLabelIndex,
13    SupportsEdgeRangeIndex, SupportsVertexFullTextIndex, SupportsVertexHashIndex,
14    SupportsVertexLabelIndex, SupportsVertexRangeIndex,
15};
16use std::collections::HashSet;
17use std::fmt::{Debug, Display, Formatter};
18use thiserror::Error;
19use uuid::Uuid;
20
21#[derive(Debug, Clone, VertexExt)]
22pub enum Vertex {
23    Person {
24        #[index(hash)]
25        name: String,
26        #[index(range)]
27        age: u64,
28        #[index(hash)]
29        unique_id: Uuid,
30        #[index(range)]
31        username: String,
32        #[index(full_text)]
33        biography: String,
34    },
35    Project(Project),
36    Rust,
37}
38
39#[derive(Debug, Clone)]
40pub struct Project {
41    pub name: String,
42}
43
44#[derive(Debug, Clone, EdgeExt)]
45pub enum Edge {
46    Knows { since: i32 },
47    Created,
48    Language(Language),
49}
50#[derive(Debug, Clone)]
51pub struct Language {
52    pub name: String,
53}
54pub struct Refs<Graph>
55where
56    Graph: graph_api_lib::Graph,
57{
58    pub bryn: Graph::VertexId,
59    pub julia: Graph::VertexId,
60    pub graph_api: Graph::VertexId,
61    pub rust: Graph::VertexId,
62    pub bryn_knows_julia: Graph::EdgeId,
63    pub julia_knows_bryn: Graph::EdgeId,
64    pub bryn_created_graph_api: Graph::EdgeId,
65    pub graph_api_language_rust: Graph::EdgeId,
66}
67
68pub fn populate_graph<Graph>(graph: &mut Graph) -> Refs<Graph>
69where
70    Graph: graph_api_lib::Graph<Vertex = Vertex, Edge = Edge>,
71{
72    // Hey Tinkerpop folks, long time no see! Hope things are going well!
73    let bryn = graph.add_vertex(Vertex::Person {
74        name: "Bryn".to_string(),
75        age: 45,
76        unique_id: Uuid::from_u128(1),
77        username: "bryn".to_string(),
78        biography: "Did some graph stuff".to_string(),
79    });
80    let julia = graph.add_vertex(Vertex::Person {
81        name: "Julia".to_string(),
82        age: 48,
83        unique_id: Uuid::from_u128(2),
84        username: "julia".to_string(),
85        biography: "Mastered the English language".to_string(),
86    });
87    let graph_api = graph.add_vertex(Vertex::Project(Project {
88        name: "GraphApi".to_string(),
89    }));
90
91    let rust = graph.add_vertex(Vertex::Rust);
92
93    let bryn_knows_julia = graph.add_edge(bryn, julia, Edge::Knows { since: 1999 });
94    let julia_knows_bryn = graph.add_edge(julia, bryn, Edge::Knows { since: 1999 });
95    let bryn_created_graph_api = graph.add_edge(bryn, graph_api, Edge::Created);
96    let graph_api_language_rust = graph.add_edge(
97        graph_api,
98        rust,
99        Edge::Language(Language {
100            name: "Rust".to_string(),
101        }),
102    );
103
104    Refs {
105        bryn,
106        julia,
107        graph_api,
108        rust,
109        bryn_knows_julia,
110        julia_knows_bryn,
111        bryn_created_graph_api,
112        graph_api_language_rust,
113    }
114}
115
116#[derive(Error, Debug)]
117pub enum TestError {
118    Mismatch {
119        missing: Vec<String>,
120        extra: Vec<String>,
121        expected: Vec<String>,
122    },
123    MoreThanOneElement {
124        expected: Vec<String>,
125        actual: Vec<String>,
126    },
127}
128
129#[macro_export]
130macro_rules! check_unsupported {
131    ($setup:expr, $name:ident, $feature:path) => {
132        #[test]
133        #[ignore] // Ignoring these tests since we can't check for !Trait
134        fn $name() {
135            fn check(g: impl graph_api_lib::Graph<Vertex = (), Edge = ()>) {}
136            // For now, we just verify the graph can be created
137            let g = $setup;
138            check(g);
139        }
140    };
141}
142
143#[macro_export]
144macro_rules! general_test {
145    ($setup:expr, $name:ident, $path:path) => {
146        #[test]
147        fn $name() {
148            let mut g = $setup;
149            $path(&mut g);
150        }
151    };
152}
153
154#[cfg(feature = "edge-label-index")]
155#[macro_export]
156macro_rules! edge_index_label_test {
157    ($setup:expr, $name:ident, $path:path) => {
158        #[test]
159        fn $name() {
160            let mut g = $setup;
161            $path(&mut g);
162        }
163    };
164}
165#[cfg(not(feature = "edge-label-index"))]
166#[macro_export]
167macro_rules! edge_index_label_test {
168    ($setup:expr, $name:ident, $path:path) => {
169        $crate::check_unsupported!($setup, $name, SupportsEdgeLabelIndex);
170    };
171}
172
173#[cfg(feature = "vertex-label-index")]
174#[macro_export]
175macro_rules! vertex_index_label_test {
176    ($setup:expr, $name:ident, $path:path) => {
177        #[test]
178        fn $name() {
179            let mut g = $setup;
180            $path(&mut g);
181        }
182    };
183}
184
185#[cfg(not(feature = "vertex-label-index"))]
186#[macro_export]
187macro_rules! vertex_index_label_test {
188    ($setup:expr, $name:ident, $path:path) => {
189        $crate::check_unsupported!($setup, $name, SupportsVertexLabelIndex);
190    };
191}
192
193#[cfg(feature = "vertex-hash-index")]
194#[macro_export]
195macro_rules! vertex_index_hash_test {
196    ($setup:expr, $name:ident, $path:path) => {
197        #[test]
198        fn $name() {
199            let mut g = $setup;
200            $path(&mut g);
201        }
202    };
203}
204#[cfg(not(feature = "vertex-hash-index"))]
205#[macro_export]
206macro_rules! vertex_index_hash_test {
207    ($setup:expr, $name:ident, $path:path) => {
208        $crate::check_unsupported!($setup, $name, SupportsVertexHashIndex);
209    };
210}
211
212#[cfg(feature = "vertex-full-text-index")]
213#[macro_export]
214macro_rules! vertex_index_full_text_test {
215    ($setup:expr, $name:ident, $path:path) => {
216        #[test]
217        fn $name() {
218            let mut g = $setup;
219            $path(&mut g);
220        }
221    };
222}
223#[cfg(not(feature = "vertex-full-text-index"))]
224#[macro_export]
225macro_rules! vertex_index_full_text_test {
226    ($setup:expr, $name:ident, $path:path) => {
227        $crate::check_unsupported!($setup, $name, SupportsVertexFullTextIndex);
228    };
229}
230
231#[cfg(feature = "vertex-range-index")]
232#[macro_export]
233macro_rules! vertex_index_range_test {
234    ($setup:expr, $name:ident, $path:path) => {
235        #[test]
236        fn $name() {
237            let mut g = $setup;
238            $path(&mut g);
239        }
240    };
241}
242
243#[cfg(not(feature = "vertex-range-index"))]
244#[macro_export]
245macro_rules! vertex_index_range_test {
246    ($setup:expr, $name:ident, $path:path) => {
247        $crate::check_unsupported!($setup, $name, SupportsVertexRangeIndex);
248    };
249}
250
251#[macro_export]
252macro_rules! test_suite {
253    ($setup:expr) => {
254        $crate::general_test! {$setup, graph_test_add_vertex, $crate::graph::test_add_vertex}
255        $crate::general_test!{$setup, graph_test_mutate_vertex, $crate::graph::test_mutate_vertex}
256        $crate::general_test!{$setup, graph_test_remove_vertex, $crate::graph::test_remove_vertex}
257        $crate::general_test!{$setup, graph_test_add_edge, $crate::graph::test_add_edge}
258        $crate::general_test!{$setup, graph_test_mutate_edge, $crate::graph::test_mutate_edge}
259        $crate::general_test!{$setup, graph_test_remove_edge, $crate::graph::test_remove_edge}
260        $crate::general_test!{$setup, graph_test_remove_vertex_with_edges, $crate::graph::test_remove_vertex_with_edges}
261        $crate::general_test!{$setup, filter_test_vertices_filter, $crate::steps::filter::test_vertices_filter}
262        $crate::general_test!{$setup, filter_test_edges_filter, $crate::steps::filter::test_edges_filter}
263        $crate::general_test!{$setup, vertices_test_vertices_collect, $crate::steps::collect::test_vertices_collect}
264        $crate::general_test!{$setup, vertices_test_edges_collect, $crate::steps::collect::test_edges_collect}
265        $crate::general_test!{$setup, edges_test_out_edges, $crate::steps::edges::test_out_edges}
266        $crate::general_test!{$setup, edges_test_out_edges_limit, $crate::steps::edges::test_out_edges_limit}
267        $crate::general_test!{$setup, edges_test_in_edges, $crate::steps::edges::test_in_edges}
268        $crate::general_test!{$setup, edges_test_in_edges_limit, $crate::steps::edges::test_in_edges_limit}
269        $crate::general_test!{$setup, edges_test_all_edges, $crate::steps::edges::test_all_edges}
270        $crate::general_test!{$setup, edges_test_all_edges_limit, $crate::steps::edges::test_all_edges_limit}
271        $crate::general_test!{$setup, edges_test_out_edges_filtered, $crate::steps::edges::test_out_edges_filtered}
272        $crate::general_test!{$setup, edges_test_out_edges_filtered_limit, $crate::steps::edges::test_out_edges_filtered_limit}
273        $crate::general_test!{$setup, edges_test_in_edges_filtered, $crate::steps::edges::test_in_edges_filtered}
274        $crate::general_test!{$setup, edges_test_in_edges_filtered_limit, $crate::steps::edges::test_in_edges_filtered_limit}
275        $crate::general_test!{$setup, edges_test_all_edges_filtered, $crate::steps::edges::test_all_edges_filtered}
276        $crate::general_test!{$setup, edges_test_all_edges_filtered_limit, $crate::steps::edges::test_all_edges_filtered_limit}
277        $crate::general_test!{$setup, context_test_vertices_context, $crate::steps::context::test_vertices_context}
278        $crate::general_test!{$setup, vertices_test_take, $crate::steps::vertices::test_take}
279        $crate::general_test!{$setup, vertices_test_head, $crate::steps::vertices::test_head}
280        $crate::general_test!{$setup, vertices_test_tail, $crate::steps::vertices::test_tail}
281        $crate::general_test!{$setup, mutate_context_vertex, $crate::steps::mutate_context::test_vertex_mutate_context}
282        $crate::general_test!{$setup, mutate_context_edge, $crate::steps::mutate_context::test_edge_mutate_context}
283        $crate::general_test!{$setup, mutation_test_mutation, $crate::steps::mutation::test_mutation}
284        $crate::general_test!{$setup, mutation_test_edge_mutation, $crate::steps::mutation::test_edge_mutation}
285        $crate::general_test!{$setup, count_test_vertices_count, $crate::steps::count::test_vertices_count}
286        $crate::general_test!{$setup, count_test_edges_count, $crate::steps::count::test_edges_count}
287        $crate::general_test!{$setup, take_test_vertices_take, $crate::steps::take::test_vertices_take}
288        $crate::general_test!{$setup, take_test_edges_take, $crate::steps::take::test_edges_take}
289        $crate::general_test!{$setup, first_test_vertices_first, $crate::steps::first::test_vertices_first}
290        $crate::general_test!{$setup, first_test_edges_first, $crate::steps::first::test_edges_first}
291        $crate::general_test!{$setup, fold_test_vertices_fold, $crate::steps::fold::test_vertices_fold}
292        $crate::general_test!{$setup, fold_test_edges_fold, $crate::steps::fold::test_edges_fold}
293        $crate::general_test!{$setup, reduce_test_vertices_reduce, $crate::steps::reduce::test_vertices_reduce}
294        $crate::general_test!{$setup, reduce_test_edges_reduce, $crate::steps::reduce::test_edges_reduce}
295        $crate::general_test!{$setup, detour_test_vertices_detour, $crate::steps::detour::test_vertices_detour}
296        $crate::general_test!{$setup, filter_derive_test_vertices_filter, $crate::steps::filter_derive::test_vertices_filter}
297        $crate::general_test!{$setup, filter_derive_test_edges_filter, $crate::steps::filter_derive::test_edges_filter}
298        $crate::general_test!{$setup, probe_test_vertices_probe, $crate::steps::probe::test_vertices_probe}
299        $crate::general_test!{$setup, probe_test_edges_probe, $crate::steps::probe::test_edges_probe}
300        $crate::general_test!{$setup, control_flow_test_vertices_control_flow, $crate::steps::control_flow::test_vertices_control_flow}
301        $crate::general_test!{$setup, control_flow_test_edges_control_flow, $crate::steps::control_flow::test_edges_control_flow}
302        $crate::edge_index_label_test!{$setup, index_edge_label_test_index, $crate::index::edge_label::test_index}
303        $crate::edge_index_label_test!{$setup, index_edge_label_test_index_limit, $crate::index::edge_label::test_index_limit}
304        $crate::vertex_index_label_test!{$setup, index_vertex_label_test_index, $crate::index::vertex_label::test_index}
305        $crate::vertex_index_label_test!{$setup, index_vertex_label_test_index_limit, $crate::index::vertex_label::test_index_limit}
306        $crate::vertex_index_hash_test!{$setup, index_vertex_hash_test_index, $crate::index::vertex_hash::test_index}
307        $crate::vertex_index_hash_test!{$setup, index_vertex_hash_test_index_remove, $crate::index::vertex_hash::test_index_remove}
308        $crate::vertex_index_hash_test!{$setup, index_vertex_hash_test_index_update, $crate::index::vertex_hash::test_index_update}
309        $crate::vertex_index_full_text_test!{$setup, index_vertex_full_text_test_index, $crate::index::vertex_full_text::test_index}
310        $crate::vertex_index_full_text_test!{$setup, index_vertex_full_text_test_index_remove, $crate::index::vertex_full_text::test_index_remove}
311        $crate::vertex_index_full_text_test!{$setup, index_vertex_full_text_test_index_update, $crate::index::vertex_full_text::test_index_update}
312        $crate::vertex_index_range_test!{$setup, index_vertex_range_test_index, $crate::index::vertex_range::test_index}
313        $crate::vertex_index_range_test!{$setup, index_vertex_range_test_index_remove, $crate::index::vertex_range::test_index_remove}
314        $crate::vertex_index_range_test!{$setup, index_vertex_range_test_index_update, $crate::index::vertex_range::test_index_update}
315
316        $crate::proptest! {
317            #[test]
318            fn fuzz_test(operations in $crate::collection::vec($crate::fuzz::arb_graph_operation(), 0..100)) {
319                $crate::fuzz::test_fuzz($setup, operations);
320            }
321        }
322    };
323}
324
325impl Display for TestError {
326    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
327        match self {
328            TestError::Mismatch {
329                missing,
330                extra,
331                expected,
332            } => {
333                if !missing.is_empty() {
334                    write!(f, "Missing elements:\n{}\n", missing.join("\n"),)?;
335                }
336                if !extra.is_empty() {
337                    write!(f, "Extra elements:\n{}\n", extra.join("\n"),)?;
338                }
339                write!(f, "Expected elements:\n{}", expected.join("\n"))?;
340            }
341            TestError::MoreThanOneElement { actual, expected } => {
342                write!(
343                    f,
344                    "Expected one of:\n{}\nBut got:\n{}",
345                    expected.join("\n"),
346                    actual.join("\n")
347                )?;
348            }
349        }
350
351        Ok(())
352    }
353}
354
355pub fn assert_elements_one_of<Graph>(
356    graph: &Graph,
357    actual: impl IntoIterator<Item = impl Into<ElementId<Graph>>>,
358    expected: impl IntoIterator<Item = impl Into<ElementId<Graph>>>,
359) -> Result<(), TestError>
360where
361    Graph: graph_api_lib::Graph,
362{
363    let actual: Vec<ElementId<Graph>> = actual.into_iter().map(Into::into).collect();
364    let expected: Vec<ElementId<Graph>> = expected.into_iter().map(Into::into).collect();
365
366    // First convert to debug strings for error reporting
367    let actual_strings: Vec<String> = actual.iter().map(|e| graph.dbg(*e)).collect();
368    let expected_strings: Vec<String> = expected.iter().map(|e| graph.dbg(*e)).collect();
369
370    if actual.len() != 1 {
371        return Err(TestError::MoreThanOneElement {
372            expected: expected_strings,
373            actual: actual_strings,
374        });
375    }
376
377    Ok(())
378}
379
380pub fn assert_elements_eq<Graph>(
381    graph: &Graph,
382    actual: impl IntoIterator<Item = impl Into<ElementId<Graph>>>,
383    expected: impl IntoIterator<Item = impl Into<ElementId<Graph>>>,
384) -> Result<(), TestError>
385where
386    Graph: graph_api_lib::Graph,
387{
388    let actual = actual.into_iter().map(Into::into).collect::<HashSet<_>>();
389    let expected = expected.into_iter().map(Into::into).collect::<HashSet<_>>();
390    if actual != expected {
391        let missing: Vec<String> = expected
392            .difference(&actual)
393            .map(|e| graph.dbg(*e))
394            .collect();
395        let extra: Vec<String> = actual
396            .difference(&expected)
397            .map(|e| graph.dbg(*e))
398            .collect();
399
400        let expected: Vec<String> = expected.iter().map(|e| graph.dbg(*e)).collect();
401
402        return Err(TestError::Mismatch {
403            missing,
404            extra,
405            expected,
406        });
407    }
408    Ok(())
409}
410
411#[macro_export]
412macro_rules! assert_elements_eq {
413    ($graph:expr, $actual:expr, $expected:expr) => {
414        if let Err(e) = $crate::assert_elements_eq($graph, $actual, $expected) {
415            panic!("{}", e);
416        }
417    };
418}
419
420#[macro_export]
421macro_rules! assert_elements_one_of {
422    ($graph:expr, $actual:expr, $expected:expr) => {
423        if let Err(e) = $crate::assert_elements_one_of($graph, $actual, $expected) {
424            panic!("{}", e);
425        }
426    };
427}