graph_api_test/steps/
boxed.rs

1use crate::{Edge, Vertex, assert_elements_eq, populate_graph};
2use graph_api_lib::{EdgeReference, EdgeSearch, Graph, VertexSearch};
3
4pub fn test_boxed_simple<T>(graph: &mut T)
5where
6    T: Graph<Vertex = Vertex, Edge = Edge>,
7{
8    let refs = populate_graph(graph);
9
10    // Test basic boxing functionality - should produce same results as unboxed
11    let unboxed_result = graph
12        .walk()
13        .vertices_by_id(vec![refs.bryn])
14        .edges(EdgeSearch::scan().outgoing())
15        .head()
16        .collect::<Vec<_>>();
17
18    let boxed_result = graph
19        .walk()
20        .vertices_by_id(vec![refs.bryn])
21        .edges(EdgeSearch::scan().outgoing())
22        .boxed() // ← Type erasure here
23        .head()
24        .collect::<Vec<_>>();
25
26    assert_elements_eq!(
27        graph,
28        unboxed_result.clone(),
29        vec![refs.graph_api, refs.julia]
30    );
31    assert_elements_eq!(graph, boxed_result, vec![refs.graph_api, refs.julia]);
32}
33
34pub fn test_boxed_complex_traversal<T>(graph: &mut T)
35where
36    T: Graph<Vertex = Vertex, Edge = Edge>,
37{
38    let _refs = populate_graph(graph);
39
40    // Extremely long traversal that would create deeply nested types without boxing
41    // This demonstrates the type complexity reduction benefits
42    let complex_unboxed_result = graph
43        .walk()
44        .vertices(VertexSearch::scan())
45        .edges(EdgeSearch::scan())
46        .head()
47        .edges(EdgeSearch::scan())
48        .head()
49        .edges(EdgeSearch::scan())
50        .head()
51        .edges(EdgeSearch::scan())
52        .head()
53        .edges(EdgeSearch::scan())
54        .head()
55        .collect::<Vec<_>>();
56
57    // Same traversal with strategic boxing to reduce compilation complexity
58    // Type complexity is broken at multiple points using boxed()
59    let complex_boxed_result = graph
60        .walk()
61        .vertices(VertexSearch::scan())
62        .edges(EdgeSearch::scan())
63        .boxed() // ← First boxing point
64        .head()
65        .edges(EdgeSearch::scan())
66        .head()
67        .boxed() // ← Second boxing point
68        .edges(EdgeSearch::scan())
69        .head()
70        .edges(EdgeSearch::scan())
71        .boxed() // ← Third boxing point
72        .head()
73        .edges(EdgeSearch::scan())
74        .head()
75        .collect::<Vec<_>>();
76
77    // Results should be identical despite different type complexity
78    assert_eq!(complex_unboxed_result.len(), complex_boxed_result.len());
79}
80
81pub fn test_boxed_ultra_long_traversal<T>(graph: &mut T)
82where
83    T: Graph<Vertex = Vertex, Edge = Edge>,
84{
85    let _refs = populate_graph(graph);
86
87    // Ultra-long traversal that would be extremely slow to compile without boxing
88    // This creates a type chain that would be:
89    // Endpoints<Edges<Endpoints<Edges<Endpoints<Edges<...>>>>>
90    // Which grows exponentially and severely impacts compilation time
91    let ultra_long_result = graph
92        .walk()
93        .vertices(VertexSearch::scan())
94        .take(2) // Limit starting vertices to reduce explosion
95        .edges(EdgeSearch::scan().outgoing()) // Use outgoing to be more specific
96        .boxed() // ← Strategic boxing after first hop
97        .head()
98        .edges(EdgeSearch::scan().outgoing())
99        .boxed() // ← Boxing after multiple hops
100        .head()
101        .edges(EdgeSearch::scan().outgoing())
102        .boxed() // ← More boxing to control complexity
103        .head()
104        .edges(EdgeSearch::scan().outgoing())
105        .boxed() // ← Final boxing before collection
106        .head()
107        .collect::<Vec<_>>();
108
109    // Test that extremely long traversals don't panic and produce valid results
110    // The main goal is to ensure compilation works with deep type nesting
111    // The exact count is less important than successful compilation and execution
112    assert!(ultra_long_result.len() < 1_000_000); // Just ensure it doesn't explode
113}
114
115pub fn test_boxed_mixed_operations<T>(graph: &mut T)
116where
117    T: Graph<Vertex = Vertex, Edge = Edge>,
118{
119    let _refs = populate_graph(graph);
120
121    // Test boxing with various walker operations to ensure compatibility
122    let mixed_result = graph
123        .walk()
124        .vertices(VertexSearch::scan())
125        .take(2)
126        .edges(EdgeSearch::scan())
127        .boxed() // ← Box after edge traversal
128        .head()
129        .take(1)
130        .edges(EdgeSearch::scan())
131        .boxed() // ← Box again after more operations
132        .head()
133        .collect::<Vec<_>>();
134
135    // Should produce valid results without compilation issues
136    assert!(mixed_result.len() <= 4); // Max 4 vertices in test graph
137}
138
139pub fn test_boxed_edge_walker<T>(graph: &mut T)
140where
141    T: Graph<Vertex = Vertex, Edge = Edge>,
142{
143    let refs = populate_graph(graph);
144
145    // Test that edge walkers can also be boxed
146    let edge_result = graph
147        .walk()
148        .vertices_by_id(vec![refs.bryn])
149        .edges(EdgeSearch::scan().outgoing())
150        .boxed() // ← Box the edge walker
151        .collect::<Vec<_>>();
152
153    assert_elements_eq!(
154        graph,
155        edge_result,
156        vec![refs.bryn_knows_julia, refs.bryn_created_graph_api]
157    );
158}
159
160pub fn test_boxed_performance_equivalence<T>(graph: &mut T)
161where
162    T: Graph<Vertex = Vertex, Edge = Edge>,
163{
164    let _refs = populate_graph(graph);
165
166    // Ensure boxed and unboxed produce identical results for complex case
167    let unboxed = graph
168        .walk()
169        .vertices(VertexSearch::scan())
170        .edges(EdgeSearch::scan())
171        .head()
172        .edges(EdgeSearch::scan())
173        .head()
174        .take(2)
175        .collect::<Vec<_>>();
176
177    let boxed = graph
178        .walk()
179        .vertices(VertexSearch::scan())
180        .edges(EdgeSearch::scan())
181        .boxed() // ← Only difference is boxing
182        .head()
183        .edges(EdgeSearch::scan())
184        .head()
185        .take(2)
186        .collect::<Vec<_>>();
187
188    // Results must be identical - boxing should not change behavior
189    assert_eq!(unboxed.len(), boxed.len());
190    assert!(boxed.len() <= 4); // Max 4 vertices in test graph
191}
192
193pub fn test_boxed_with_context<T>(graph: &mut T)
194where
195    T: Graph<Vertex = Vertex, Edge = Edge>,
196{
197    let refs = populate_graph(graph);
198
199    // Test that boxed step works with custom contexts
200    // First establish context, then box afterward
201    let result_with_context = graph
202        .walk()
203        .vertices_by_id(vec![refs.bryn])
204        .edges(EdgeSearch::scan().outgoing())
205        .push_context(|edge, _| format!("edge-{:?}", edge.id()))
206        .boxed() // ← Boxing with custom String context
207        .head()
208        .collect::<Vec<_>>();
209
210    assert_elements_eq!(graph, result_with_context, vec![refs.graph_api, refs.julia]);
211}
212
213#[derive(Clone, Debug, PartialEq)]
214struct TestContext {
215    counter: u32,
216    label: String,
217}
218
219pub fn test_boxed_context_delegation<T>(graph: &mut T)
220where
221    T: Graph<Vertex = Vertex, Edge = Edge>,
222{
223    let refs = populate_graph(graph);
224
225    // Test that context is properly delegated through boxed walker
226    let result = graph
227        .walk()
228        .push_context(TestContext {
229            counter: 0,
230            label: "start".to_string(),
231        })
232        .vertices_by_id(vec![refs.bryn])
233        .mutate_context(|_, ctx| {
234            ctx.counter += 1;
235            ctx.label = "after_vertex".to_string();
236        })
237        .edges(EdgeSearch::scan().outgoing())
238        .boxed() // ← Box with custom context
239        .mutate_context(|_, ctx| {
240            ctx.counter += 10; // This should work if context delegation is correct
241            ctx.label = "after_boxing".to_string();
242        })
243        .head()
244        .map(|vertex, context| {
245            // ACTUALLY verify context was preserved and modified correctly
246            assert_eq!(
247                context.counter, 11,
248                "Context counter should be 0 + 1 + 10 = 11"
249            );
250            assert_eq!(
251                context.label, "after_boxing",
252                "Context label should be updated after boxing"
253            );
254            vertex
255        })
256        .collect::<Vec<_>>();
257
258    assert_eq!(result.len(), 2); // Should have 2 vertices - context verification is done in map above
259}
260
261pub fn test_boxed_context_access_before_next<T>(graph: &mut T)
262where
263    T: Graph<Vertex = Vertex, Edge = Edge>,
264{
265    let refs = populate_graph(graph);
266    // Test that context can be accessed and modified through boxed walker
267    let result = graph
268        .walk()
269        .push_context(TestContext {
270            counter: 42,
271            label: "initial".to_string(),
272        })
273        .vertices_by_id(vec![refs.bryn])
274        .edges(EdgeSearch::scan().outgoing())
275        .boxed() // ← Box with context
276        .mutate_context(|_, ctx| {
277            // This tests that context can be accessed and modified after boxing
278            assert_eq!(ctx.counter, 42, "Initial context should be preserved");
279            assert_eq!(
280                ctx.label, "initial",
281                "Initial context label should be preserved"
282            );
283            ctx.counter = 100;
284            ctx.label = "modified_after_boxing".to_string();
285        })
286        .head()
287        .map(|vertex, context| {
288            // Verify context modifications worked
289            assert_eq!(context.counter, 100, "Context should be modified to 100");
290            assert_eq!(
291                context.label, "modified_after_boxing",
292                "Context label should be updated"
293            );
294            vertex
295        })
296        .collect::<Vec<_>>();
297
298    assert_eq!(result.len(), 2); // Context verification is done in map above
299}
300
301pub fn test_boxed_nested_contexts<T>(graph: &mut T)
302where
303    T: Graph<Vertex = Vertex, Edge = Edge>,
304{
305    let refs = populate_graph(graph);
306
307    // Test boxed walker with nested context structure
308    let result = graph
309        .walk()
310        .push_context("outer")
311        .vertices_by_id(vec![refs.bryn])
312        .push_context(|_, parent_ctx| format!("inner-{}", parent_ctx))
313        .edges(EdgeSearch::scan().outgoing())
314        .boxed() // ← Box with nested context
315        .head()
316        .map(|vertex, context| {
317            // ACTUALLY verify nested context structure is preserved
318            assert_eq!(
319                *context, "inner-outer",
320                "Nested context should combine properly"
321            );
322            assert_eq!(
323                *context.parent(),
324                "outer",
325                "Parent context should be accessible"
326            );
327            vertex
328        })
329        .collect::<Vec<_>>();
330
331    assert_eq!(result.len(), 2); // Context verification is done in map above
332}
333
334pub fn test_boxed_context_persistence<T>(graph: &mut T)
335where
336    T: Graph<Vertex = Vertex, Edge = Edge>,
337{
338    let refs = populate_graph(graph);
339
340    // Test that context persists correctly through boxed walkers
341    // This also tests that context mutations are visible in subsequent steps
342    let result = graph
343        .walk()
344        .push_context(TestContext {
345            counter: 5,
346            label: "original".to_string(),
347        })
348        .vertices_by_id(vec![refs.bryn, refs.julia])
349        .boxed() // ← Box early to test context persistence
350        .mutate_context(|_, ctx| {
351            // Verify initial context state and modify it
352            assert_eq!(ctx.counter, 5, "Counter should start at 5");
353            assert_eq!(ctx.label, "original", "Label should start as 'original'");
354
355            // Modify context so we can verify persistence
356            ctx.label = "modified_by_mutate_context".to_string();
357            ctx.counter = 42;
358        })
359        .map(|vertex, context| {
360            // Verify context was modified in previous step
361            assert_eq!(context.counter, 42, "Counter should be modified to 42");
362            assert_eq!(
363                context.label, "modified_by_mutate_context",
364                "Label should be updated"
365            );
366            vertex
367        })
368        .collect::<Vec<_>>();
369
370    assert_eq!(result.len(), 2); // Context verification is done in map above
371}