• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.graph;
18 
19 import static com.google.common.graph.TestUtil.ERROR_NODE_NOT_IN_GRAPH;
20 import static com.google.common.graph.TestUtil.assertNodeNotInGraphErrorMessage;
21 import static com.google.common.graph.TestUtil.assertStronglyEquivalent;
22 import static com.google.common.graph.TestUtil.sanityCheckSet;
23 import static com.google.common.truth.Truth.assertThat;
24 import static org.junit.Assert.fail;
25 
26 import com.google.common.collect.ImmutableSet;
27 import com.google.errorprone.annotations.CanIgnoreReturnValue;
28 import java.util.HashSet;
29 import java.util.Set;
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Test;
33 
34 /**
35  * Abstract base class for testing implementations of {@link Graph} interface. Graph instances
36  * created for testing should have Integer node and String edge objects.
37  *
38  * <p>Test cases that should be handled similarly in any graph implementation are included in this
39  * class. For example, testing that {@code nodes()} method returns the set of the nodes in the
40  * graph. The following test cases are left for the subclasses to handle:
41  *
42  * <ul>
43  *   <li>Test cases related to whether the graph is directed, undirected, mutable, or immutable.
44  *   <li>Test cases related to the specific implementation of the {@link Graph} interface.
45  * </ul>
46  *
47  * TODO(user): Make this class generic (using <N, E>) for all node and edge types.
48  * TODO(user): Differentiate between directed and undirected edge strings.
49  */
50 public abstract class AbstractGraphTest {
51   MutableGraph<Integer> graph;
52   static final Integer N1 = 1;
53   static final Integer N2 = 2;
54   static final Integer N3 = 3;
55   static final Integer N4 = 4;
56   static final Integer N5 = 5;
57   static final Integer NODE_NOT_IN_GRAPH = 1000;
58 
59   // TODO(user): Consider separating Strings that we've defined here to capture
60   // identifiable substrings of expected error messages, from Strings that we've defined
61   // here to provide error messages.
62   // TODO(user): Some Strings used in the subclasses can be added as static Strings
63   // here too.
64   static final String ERROR_MODIFIABLE_SET = "Set returned is unexpectedly modifiable";
65   static final String ERROR_SELF_LOOP = "self-loops are not allowed";
66   static final String ERROR_ADDED_SELF_LOOP = "Should not be allowed to add a self-loop edge.";
67 
68   /** Creates and returns an instance of the graph to be tested. */
createGraph()69   public abstract MutableGraph<Integer> createGraph();
70 
71   /**
72    * A proxy method that adds the node {@code n} to the graph being tested. In case of Immutable
73    * graph implementations, this method should add {@code n} to the graph builder and build a new
74    * graph with the current builder state.
75    *
76    * @return {@code true} iff the graph was modified as a result of this call
77    */
78   @CanIgnoreReturnValue
addNode(Integer n)79   protected boolean addNode(Integer n) {
80     return graph.addNode(n);
81   }
82 
83   /**
84    * A proxy method that adds the edge {@code e} to the graph being tested. In case of Immutable
85    * graph implementations, this method should add {@code e} to the graph builder and build a new
86    * graph with the current builder state.
87    *
88    * <p>This method should be used in tests of specific implementations if you want to ensure
89    * uniform behavior (including side effects) with how edges are added elsewhere in the tests. For
90    * example, the existing implementations of this method explicitly add the supplied nodes to the
91    * graph, and then call {@code graph.addEdge()} to connect the edge to the nodes; this is not part
92    * of the contract of {@code graph.addEdge()} and is done for convenience. In cases where you want
93    * to avoid such side effects (e.g., if you're testing what happens in your implementation if you
94    * add an edge whose end-points don't already exist in the graph), you should <b>not</b> use this
95    * method.
96    *
97    * @return {@code true} iff the graph was modified as a result of this call
98    */
99   @CanIgnoreReturnValue
putEdge(Integer n1, Integer n2)100   protected boolean putEdge(Integer n1, Integer n2) {
101     graph.addNode(n1);
102     graph.addNode(n2);
103     return graph.putEdge(n1, n2);
104   }
105 
106   @CanIgnoreReturnValue
putEdge(EndpointPair<Integer> endpoints)107   protected boolean putEdge(EndpointPair<Integer> endpoints) {
108     graph.addNode(endpoints.nodeU());
109     graph.addNode(endpoints.nodeV());
110     return graph.putEdge(endpoints);
111   }
112 
113   @Before
init()114   public void init() {
115     graph = createGraph();
116   }
117 
118   @After
validateGraphState()119   public void validateGraphState() {
120     validateGraph(graph);
121   }
122 
validateGraph(Graph<N> graph)123   static <N> void validateGraph(Graph<N> graph) {
124     assertStronglyEquivalent(graph, Graphs.copyOf(graph));
125     assertStronglyEquivalent(graph, ImmutableGraph.copyOf(graph));
126 
127     String graphString = graph.toString();
128     assertThat(graphString).contains("isDirected: " + graph.isDirected());
129     assertThat(graphString).contains("allowsSelfLoops: " + graph.allowsSelfLoops());
130 
131     int nodeStart = graphString.indexOf("nodes:");
132     int edgeStart = graphString.indexOf("edges:");
133     String nodeString = graphString.substring(nodeStart, edgeStart);
134 
135     Set<EndpointPair<N>> allEndpointPairs = new HashSet<>();
136 
137     for (N node : sanityCheckSet(graph.nodes())) {
138       assertThat(nodeString).contains(node.toString());
139 
140       if (graph.isDirected()) {
141         assertThat(graph.degree(node)).isEqualTo(graph.inDegree(node) + graph.outDegree(node));
142         assertThat(graph.predecessors(node)).hasSize(graph.inDegree(node));
143         assertThat(graph.successors(node)).hasSize(graph.outDegree(node));
144       } else {
145         int selfLoopCount = graph.adjacentNodes(node).contains(node) ? 1 : 0;
146         assertThat(graph.degree(node)).isEqualTo(graph.adjacentNodes(node).size() + selfLoopCount);
147         assertThat(graph.predecessors(node)).isEqualTo(graph.adjacentNodes(node));
148         assertThat(graph.successors(node)).isEqualTo(graph.adjacentNodes(node));
149         assertThat(graph.inDegree(node)).isEqualTo(graph.degree(node));
150         assertThat(graph.outDegree(node)).isEqualTo(graph.degree(node));
151       }
152 
153       for (N adjacentNode : sanityCheckSet(graph.adjacentNodes(node))) {
154         if (!graph.allowsSelfLoops()) {
155           assertThat(node).isNotEqualTo(adjacentNode);
156         }
157         assertThat(
158                 graph.predecessors(node).contains(adjacentNode)
159                     || graph.successors(node).contains(adjacentNode))
160             .isTrue();
161       }
162 
163       for (N predecessor : sanityCheckSet(graph.predecessors(node))) {
164         assertThat(graph.successors(predecessor)).contains(node);
165         assertThat(graph.hasEdgeConnecting(predecessor, node)).isTrue();
166         assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, predecessor, node));
167       }
168 
169       for (N successor : sanityCheckSet(graph.successors(node))) {
170         allEndpointPairs.add(EndpointPair.of(graph, node, successor));
171         assertThat(graph.predecessors(successor)).contains(node);
172         assertThat(graph.hasEdgeConnecting(node, successor)).isTrue();
173         assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, node, successor));
174       }
175 
176       for (EndpointPair<N> endpoints : sanityCheckSet(graph.incidentEdges(node))) {
177         if (graph.isDirected()) {
178           assertThat(graph.hasEdgeConnecting(endpoints.source(), endpoints.target())).isTrue();
179         } else {
180           assertThat(graph.hasEdgeConnecting(endpoints.nodeU(), endpoints.nodeV())).isTrue();
181         }
182       }
183     }
184 
185     sanityCheckSet(graph.edges());
186     assertThat(graph.edges()).doesNotContain(EndpointPair.of(graph, new Object(), new Object()));
187     assertThat(graph.edges()).isEqualTo(allEndpointPairs);
188   }
189 
190   /**
191    * Verifies that the {@code Set} returned by {@code nodes} has the expected mutability property
192    * (see the {@code Graph} documentation for more information).
193    */
194   @Test
nodes_checkReturnedSetMutability()195   public abstract void nodes_checkReturnedSetMutability();
196 
197   /**
198    * Verifies that the {@code Set} returned by {@code adjacentNodes} has the expected mutability
199    * property (see the {@code Graph} documentation for more information).
200    */
201   @Test
adjacentNodes_checkReturnedSetMutability()202   public abstract void adjacentNodes_checkReturnedSetMutability();
203 
204   /**
205    * Verifies that the {@code Set} returned by {@code predecessors} has the expected mutability
206    * property (see the {@code Graph} documentation for more information).
207    */
208   @Test
predecessors_checkReturnedSetMutability()209   public abstract void predecessors_checkReturnedSetMutability();
210 
211   /**
212    * Verifies that the {@code Set} returned by {@code successors} has the expected mutability
213    * property (see the {@code Graph} documentation for more information).
214    */
215   @Test
successors_checkReturnedSetMutability()216   public abstract void successors_checkReturnedSetMutability();
217 
218   /**
219    * Verifies that the {@code Set} returned by {@code incidentEdges} has the expected mutability
220    * property (see the {@code Graph} documentation for more information).
221    */
222   @Test
incidentEdges_checkReturnedSetMutability()223   public abstract void incidentEdges_checkReturnedSetMutability();
224 
225   @Test
nodes_oneNode()226   public void nodes_oneNode() {
227     addNode(N1);
228     assertThat(graph.nodes()).containsExactly(N1);
229   }
230 
231   @Test
nodes_noNodes()232   public void nodes_noNodes() {
233     assertThat(graph.nodes()).isEmpty();
234   }
235 
236   @Test
adjacentNodes_oneEdge()237   public void adjacentNodes_oneEdge() {
238     putEdge(N1, N2);
239     assertThat(graph.adjacentNodes(N1)).containsExactly(N2);
240     assertThat(graph.adjacentNodes(N2)).containsExactly(N1);
241   }
242 
243   @Test
adjacentNodes_noAdjacentNodes()244   public void adjacentNodes_noAdjacentNodes() {
245     addNode(N1);
246     assertThat(graph.adjacentNodes(N1)).isEmpty();
247   }
248 
249   @Test
adjacentNodes_nodeNotInGraph()250   public void adjacentNodes_nodeNotInGraph() {
251     try {
252       graph.adjacentNodes(NODE_NOT_IN_GRAPH);
253       fail(ERROR_NODE_NOT_IN_GRAPH);
254     } catch (IllegalArgumentException e) {
255       assertNodeNotInGraphErrorMessage(e);
256     }
257   }
258 
259   @Test
predecessors_noPredecessors()260   public void predecessors_noPredecessors() {
261     addNode(N1);
262     assertThat(graph.predecessors(N1)).isEmpty();
263   }
264 
265   @Test
predecessors_nodeNotInGraph()266   public void predecessors_nodeNotInGraph() {
267     try {
268       graph.predecessors(NODE_NOT_IN_GRAPH);
269       fail(ERROR_NODE_NOT_IN_GRAPH);
270     } catch (IllegalArgumentException e) {
271       assertNodeNotInGraphErrorMessage(e);
272     }
273   }
274 
275   @Test
successors_noSuccessors()276   public void successors_noSuccessors() {
277     addNode(N1);
278     assertThat(graph.successors(N1)).isEmpty();
279   }
280 
281   @Test
successors_nodeNotInGraph()282   public void successors_nodeNotInGraph() {
283     try {
284       graph.successors(NODE_NOT_IN_GRAPH);
285       fail(ERROR_NODE_NOT_IN_GRAPH);
286     } catch (IllegalArgumentException e) {
287       assertNodeNotInGraphErrorMessage(e);
288     }
289   }
290 
291   @Test
incidentEdges_noIncidentEdges()292   public void incidentEdges_noIncidentEdges() {
293     addNode(N1);
294     assertThat(graph.incidentEdges(N1)).isEmpty();
295   }
296 
297   @Test
incidentEdges_nodeNotInGraph()298   public void incidentEdges_nodeNotInGraph() {
299     try {
300       graph.incidentEdges(NODE_NOT_IN_GRAPH);
301       fail(ERROR_NODE_NOT_IN_GRAPH);
302     } catch (IllegalArgumentException e) {
303       assertNodeNotInGraphErrorMessage(e);
304     }
305   }
306 
307   @Test
degree_oneEdge()308   public void degree_oneEdge() {
309     putEdge(N1, N2);
310     assertThat(graph.degree(N1)).isEqualTo(1);
311     assertThat(graph.degree(N2)).isEqualTo(1);
312   }
313 
314   @Test
degree_isolatedNode()315   public void degree_isolatedNode() {
316     addNode(N1);
317     assertThat(graph.degree(N1)).isEqualTo(0);
318   }
319 
320   @Test
degree_nodeNotInGraph()321   public void degree_nodeNotInGraph() {
322     try {
323       graph.degree(NODE_NOT_IN_GRAPH);
324       fail(ERROR_NODE_NOT_IN_GRAPH);
325     } catch (IllegalArgumentException e) {
326       assertNodeNotInGraphErrorMessage(e);
327     }
328   }
329 
330   @Test
inDegree_isolatedNode()331   public void inDegree_isolatedNode() {
332     addNode(N1);
333     assertThat(graph.inDegree(N1)).isEqualTo(0);
334   }
335 
336   @Test
inDegree_nodeNotInGraph()337   public void inDegree_nodeNotInGraph() {
338     try {
339       graph.inDegree(NODE_NOT_IN_GRAPH);
340       fail(ERROR_NODE_NOT_IN_GRAPH);
341     } catch (IllegalArgumentException e) {
342       assertNodeNotInGraphErrorMessage(e);
343     }
344   }
345 
346   @Test
outDegree_isolatedNode()347   public void outDegree_isolatedNode() {
348     addNode(N1);
349     assertThat(graph.outDegree(N1)).isEqualTo(0);
350   }
351 
352   @Test
outDegree_nodeNotInGraph()353   public void outDegree_nodeNotInGraph() {
354     try {
355       graph.outDegree(NODE_NOT_IN_GRAPH);
356       fail(ERROR_NODE_NOT_IN_GRAPH);
357     } catch (IllegalArgumentException e) {
358       assertNodeNotInGraphErrorMessage(e);
359     }
360   }
361 
362   @Test
addNode_newNode()363   public void addNode_newNode() {
364     assertThat(addNode(N1)).isTrue();
365     assertThat(graph.nodes()).contains(N1);
366   }
367 
368   @Test
addNode_existingNode()369   public void addNode_existingNode() {
370     addNode(N1);
371     ImmutableSet<Integer> nodes = ImmutableSet.copyOf(graph.nodes());
372     assertThat(addNode(N1)).isFalse();
373     assertThat(graph.nodes()).containsExactlyElementsIn(nodes);
374   }
375 
376   @Test
removeNode_existingNode()377   public void removeNode_existingNode() {
378     putEdge(N1, N2);
379     putEdge(N4, N1);
380     assertThat(graph.removeNode(N1)).isTrue();
381     assertThat(graph.removeNode(N1)).isFalse();
382     assertThat(graph.nodes()).containsExactly(N2, N4);
383     assertThat(graph.adjacentNodes(N2)).isEmpty();
384     assertThat(graph.adjacentNodes(N4)).isEmpty();
385   }
386 
387   @Test
removeNode_antiparallelEdges()388   public void removeNode_antiparallelEdges() {
389     putEdge(N1, N2);
390     putEdge(N2, N1);
391 
392     assertThat(graph.removeNode(N1)).isTrue();
393     assertThat(graph.nodes()).containsExactly(N2);
394     assertThat(graph.edges()).isEmpty();
395 
396     assertThat(graph.removeNode(N2)).isTrue();
397     assertThat(graph.nodes()).isEmpty();
398     assertThat(graph.edges()).isEmpty();
399   }
400 
401   @Test
removeNode_nodeNotPresent()402   public void removeNode_nodeNotPresent() {
403     addNode(N1);
404     ImmutableSet<Integer> nodes = ImmutableSet.copyOf(graph.nodes());
405     assertThat(graph.removeNode(NODE_NOT_IN_GRAPH)).isFalse();
406     assertThat(graph.nodes()).containsExactlyElementsIn(nodes);
407   }
408 
409   @Test
removeNode_queryAfterRemoval()410   public void removeNode_queryAfterRemoval() {
411     addNode(N1);
412     @SuppressWarnings("unused")
413     Set<Integer> unused = graph.adjacentNodes(N1); // ensure cache (if any) is populated
414     assertThat(graph.removeNode(N1)).isTrue();
415     try {
416       graph.adjacentNodes(N1);
417       fail(ERROR_NODE_NOT_IN_GRAPH);
418     } catch (IllegalArgumentException e) {
419       assertNodeNotInGraphErrorMessage(e);
420     }
421   }
422 
423   @Test
removeEdge_existingEdge()424   public void removeEdge_existingEdge() {
425     putEdge(N1, N2);
426     assertThat(graph.successors(N1)).containsExactly(N2);
427     assertThat(graph.predecessors(N2)).containsExactly(N1);
428     assertThat(graph.removeEdge(N1, N2)).isTrue();
429     assertThat(graph.removeEdge(N1, N2)).isFalse();
430     assertThat(graph.successors(N1)).isEmpty();
431     assertThat(graph.predecessors(N2)).isEmpty();
432   }
433 
434   @Test
removeEdge_oneOfMany()435   public void removeEdge_oneOfMany() {
436     putEdge(N1, N2);
437     putEdge(N1, N3);
438     putEdge(N1, N4);
439     assertThat(graph.removeEdge(N1, N3)).isTrue();
440     assertThat(graph.adjacentNodes(N1)).containsExactly(N2, N4);
441   }
442 
443   @Test
removeEdge_nodeNotPresent()444   public void removeEdge_nodeNotPresent() {
445     putEdge(N1, N2);
446     assertThat(graph.removeEdge(N1, NODE_NOT_IN_GRAPH)).isFalse();
447     assertThat(graph.successors(N1)).contains(N2);
448   }
449 
450   @Test
removeEdge_edgeNotPresent()451   public void removeEdge_edgeNotPresent() {
452     putEdge(N1, N2);
453     addNode(N3);
454     assertThat(graph.removeEdge(N1, N3)).isFalse();
455     assertThat(graph.successors(N1)).contains(N2);
456   }
457 }
458