Skip to main content

swh_graph/views/
spy.rs

1// Copyright (C) 2024-2026  The Software Heritage developers
2// See the AUTHORS file at the top-level directory of this distribution
3// License: GNU General Public License version 3, or any later version
4// See top-level LICENSE file for more information
5
6use std::collections::HashMap;
7use std::path::Path;
8use std::sync::{Arc, Mutex};
9
10use anyhow::Result;
11
12use crate::graph::*;
13use crate::properties;
14use crate::NodeType;
15
16/// Wraps a graph, and records calls to its methods, useful for tests
17#[derive(Clone, Debug)]
18pub struct GraphSpy<G: SwhGraph> {
19    pub graph: G,
20    /// Pairs of `(method, arguments)`
21    pub history: Arc<Mutex<Vec<(&'static str, String)>>>,
22}
23
24impl<G: SwhGraph> GraphSpy<G> {
25    pub fn new(graph: G) -> Self {
26        GraphSpy {
27            graph,
28            history: Arc::new(Mutex::new(Vec::new())),
29        }
30    }
31
32    #[inline(always)]
33    fn record<Args: std::fmt::Debug>(&self, method: &'static str, arguments: Args) {
34        self.history
35            .lock()
36            .unwrap()
37            .push((method, format!("{arguments:?}")));
38    }
39}
40
41impl<G: SwhGraph> SwhGraph for GraphSpy<G> {
42    #[inline(always)]
43    fn path(&self) -> &Path {
44        self.record("path", ());
45        self.graph.path()
46    }
47    #[inline(always)]
48    fn is_transposed(&self) -> bool {
49        self.record("is_transposed", ());
50        self.graph.is_transposed()
51    }
52    #[inline(always)]
53    fn num_nodes(&self) -> usize {
54        self.record("num_nodes", ());
55        self.graph.num_nodes()
56    }
57    #[inline(always)]
58    fn has_node(&self, node_id: NodeId) -> bool {
59        self.record("has_node", (node_id,));
60        self.graph.has_node(node_id)
61    }
62    #[inline(always)]
63    fn num_arcs(&self) -> u64 {
64        self.record("num_arcs", ());
65        self.graph.num_arcs()
66    }
67    fn num_nodes_by_type(&self) -> Result<HashMap<NodeType, usize>> {
68        self.record("num_nodes_by_type", ());
69        self.graph.num_nodes_by_type()
70    }
71    fn num_arcs_by_type(&self) -> Result<HashMap<(NodeType, NodeType), usize>> {
72        self.record("num_arcs_by_type", ());
73        self.graph.num_arcs_by_type()
74    }
75    #[inline(always)]
76    fn has_arc(&self, src_node_id: NodeId, dst_node_id: NodeId) -> bool {
77        self.record("has_arc", (src_node_id, dst_node_id));
78        self.graph.has_arc(src_node_id, dst_node_id)
79    }
80}
81
82impl<G: SwhForwardGraph> SwhForwardGraph for GraphSpy<G> {
83    type Successors<'succ>
84        = <G as SwhForwardGraph>::Successors<'succ>
85    where
86        Self: 'succ;
87
88    #[inline(always)]
89    fn successors(&self, node_id: NodeId) -> Self::Successors<'_> {
90        self.record("successors", (node_id,));
91        self.graph.successors(node_id)
92    }
93    #[inline(always)]
94    fn outdegree(&self, node_id: NodeId) -> usize {
95        self.record("outdegree", (node_id,));
96        self.graph.outdegree(node_id)
97    }
98}
99
100impl<G: SwhLabeledForwardGraph> SwhLabeledForwardGraph for GraphSpy<G> {
101    type LabeledArcs<'arc>
102        = <G as SwhLabeledForwardGraph>::LabeledArcs<'arc>
103    where
104        Self: 'arc;
105    type LabeledSuccessors<'succ>
106        = <G as SwhLabeledForwardGraph>::LabeledSuccessors<'succ>
107    where
108        Self: 'succ;
109
110    fn untyped_labeled_successors(&self, node_id: NodeId) -> Self::LabeledSuccessors<'_> {
111        self.record("untyped_labeled_successors", (node_id,));
112        self.graph.untyped_labeled_successors(node_id)
113    }
114}
115
116impl<G: SwhBackwardGraph> SwhBackwardGraph for GraphSpy<G> {
117    type Predecessors<'succ>
118        = <G as SwhBackwardGraph>::Predecessors<'succ>
119    where
120        Self: 'succ;
121
122    fn predecessors(&self, node_id: NodeId) -> Self::Predecessors<'_> {
123        self.record("predecessors", (node_id,));
124        self.graph.predecessors(node_id)
125    }
126    fn indegree(&self, node_id: NodeId) -> usize {
127        self.record("indegree", (node_id,));
128        self.graph.indegree(node_id)
129    }
130}
131
132impl<G: SwhLabeledBackwardGraph> SwhLabeledBackwardGraph for GraphSpy<G> {
133    type LabeledArcs<'arc>
134        = <G as SwhLabeledBackwardGraph>::LabeledArcs<'arc>
135    where
136        Self: 'arc;
137    type LabeledPredecessors<'succ>
138        = <G as SwhLabeledBackwardGraph>::LabeledPredecessors<'succ>
139    where
140        Self: 'succ;
141
142    fn untyped_labeled_predecessors(&self, node_id: NodeId) -> Self::LabeledPredecessors<'_> {
143        self.record("untyped_labeled_predecessors", (node_id,));
144        self.graph.untyped_labeled_predecessors(node_id)
145    }
146}
147
148impl<G: SwhGraphWithProperties> SwhGraphWithProperties for GraphSpy<G> {
149    type Maps = <G as SwhGraphWithProperties>::Maps;
150    type Timestamps = <G as SwhGraphWithProperties>::Timestamps;
151    type Persons = <G as SwhGraphWithProperties>::Persons;
152    type Contents = <G as SwhGraphWithProperties>::Contents;
153    type Strings = <G as SwhGraphWithProperties>::Strings;
154    type LabelNames = <G as SwhGraphWithProperties>::LabelNames;
155
156    fn properties(
157        &self,
158    ) -> &properties::SwhGraphProperties<
159        Self::Maps,
160        Self::Timestamps,
161        Self::Persons,
162        Self::Contents,
163        Self::Strings,
164        Self::LabelNames,
165    > {
166        self.record("properties", ());
167        self.graph.properties()
168    }
169}
170
171/* TODO:
172impl<G: SwhGraphWithProperties> SwhGraphWithProperties for GraphSpy<G>
173where
174    <G as SwhGraphWithProperties>::Maps: Maps,
175    <G as SwhGraphWithProperties>::Timestamps: Timestamps,
176    <G as SwhGraphWithProperties>::Persons: Persons,
177    <G as SwhGraphWithProperties>::Contents: Contents,
178    <G as SwhGraphWithProperties>::Strings: Strings,
179    <G as SwhGraphWithProperties>::LabelNames: LabelNames,
180{
181    type Maps = PropertiesSpy<<G as SwhGraphWithProperties>::Maps>;
182    type Timestamps = PropertiesSpy<<G as SwhGraphWithProperties>::Timestamps>;
183    type Persons = PropertiesSpy<<G as SwhGraphWithProperties>::Persons>;
184    type Contents = PropertiesSpy<<G as SwhGraphWithProperties>::Contents>;
185    type Strings = PropertiesSpy<<G as SwhGraphWithProperties>::Strings>;
186    type LabelNames = PropertiesSpy<<G as SwhGraphWithProperties>::LabelNames>;
187
188    fn properties(&self) -> ??? {
189        self.record("properties", ());
190        let SwhGraphProperties {
191            path,
192            num_nodes,
193            maps,
194            timestamps,
195            persons,
196            contents,
197            strings,
198            label_names,
199        } = *self.graph.properties();
200        Arc::new(SwhGraphProperties {
201            path,
202            num_nodes,
203            maps: PropertiesSpy {
204                inner: maps,
205                history: self.history.clone(),
206            },
207            timestamps: PropertiesSpy {
208                inner: timestamps,
209                history: self.history.clone(),
210            },
211            persons: PropertiesSpy {
212                inner: persons,
213                history: self.history.clone(),
214            },
215            contents: PropertiesSpy {
216                inner: contents,
217                history: self.history.clone(),
218            },
219            strings: PropertiesSpy {
220                inner: strings,
221                history: self.history.clone(),
222            },
223            label_names: PropertiesSpy {
224                inner: label_names,
225                history: self.history.clone(),
226            },
227        })
228    }
229}
230
231pub struct PropertiesSpy<P> {
232    pub inner: P,
233    /// Pairs of `(method, arguments)`
234    pub history: Arc<Mutex<Vec<(&'static str, String)>>>,
235}
236
237impl<P> PropertiesSpy<P> {
238    fn record<Args: std::fmt::Debug>(&self, method: &'static str, arguments: Args) {
239        self.history
240            .lock()
241            .unwrap()
242            .push((method, format!("{:?}", arguments)));
243    }
244}
245
246impl<C: Contents> Contents for PropertiesSpy<C> {
247    type Data<'a> = <C as Contents>::Data<'a> where C: 'a;
248
249    fn is_skipped_content(&self) -> Self::Data<'_> {
250        self.record("is_skipped_content", ());
251        self.inner.is_skipped_content()
252    }
253    fn content_length(&self) -> Self::Data<'_> {
254        self.record("content_length", ());
255        self.inner.content_length()
256    }
257}
258
259impl<L: LabelNames> LabelNames for PropertiesSpy<L> {
260    type LabelNames<'a> = <L as LabelNames>::LabelNames<'a> where L: 'a;
261
262    fn label_names(&self) -> Self::LabelNames<'_> {
263        self.record("label_names", ());
264        self.inner.label_names()
265    }
266}
267
268impl<M: Maps> Maps for PropertiesSpy<M> {
269    type MPHF = <M as Maps>::MPHF;
270    type Perm = <M as Maps>::Perm;
271    type Memory = <M as Maps>::Memory;
272
273    fn mphf(&self) -> &Self::MPHF {
274        self.record("mphf", ());
275        self.inner.mphf()
276    }
277    fn order(&self) -> &Self::Perm {
278        self.record("order", ());
279        self.inner.order()
280    }
281    fn node2swhid(&self) -> &Node2SWHID<Self::Memory> {
282        self.record("node2swhid", ());
283        self.inner.node2swhid()
284    }
285    fn node2type(&self) -> &Node2Type<UsizeMmap<Self::Memory>> {
286        self.record("node2type", ());
287        self.inner.node2type()
288    }
289}
290
291impl<P: Persons> Persons for PropertiesSpy<P> {
292    type PersonIds<'a> = <P as Persons>::PersonIds<'a> where P: 'a;
293
294    fn author_id(&self) -> Self::PersonIds<'_> {
295        self.record("author_id", ());
296        self.inner.author_id()
297    }
298    fn committer_id(&self) -> Self::PersonIds<'_> {
299        self.record("committer_id", ());
300        self.inner.committer_id()
301    }
302}
303
304impl<S: Strings> Strings for PropertiesSpy<S> {
305    type Offsets<'a> = <S as Strings>::Offsets<'a> where S: 'a;
306
307    fn message(&self) -> &[u8] {
308        self.record("message", ());
309        self.inner.message()
310    }
311    fn message_offset(&self) -> Self::Offsets<'_> {
312        self.record("message_offset", ());
313        self.inner.message_offset()
314    }
315    fn tag_name(&self) -> &[u8] {
316        self.record("tag_name", ());
317        self.inner.tag_name()
318    }
319    fn tag_name_offset(&self) -> Self::Offsets<'_> {
320        self.record("tag_name_offset", ());
321        self.inner.tag_name_offset()
322    }
323}
324
325impl<T: Timestamps> Timestamps for PropertiesSpy<T> {
326    type Timestamps<'a> = <T as Timestamps>::Timestamps<'a> where T: 'a;
327    type Offsets<'a> = <T as Timestamps>::Offsets<'a> where T: 'a;
328
329    fn author_timestamp(&self) -> Self::Timestamps<'_> {
330        self.record("author_timestamp", ());
331        self.inner.author_timestamp()
332    }
333    fn author_timestamp_offset(&self) -> Self::Offsets<'_> {
334        self.record("author_timestamp_offset", ());
335        self.inner.author_timestamp_offset()
336    }
337    fn committer_timestamp(&self) -> Self::Timestamps<'_> {
338        self.record("committer_timestamp", ());
339        self.inner.committer_timestamp()
340    }
341    fn committer_timestamp_offset(&self) -> Self::Offsets<'_> {
342        self.record("committer_timestamp_offset", ());
343        self.inner.committer_timestamp_offset()
344    }
345}
346*/