swh_graph/views/
spy.rs

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