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*/