• 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.assertEdgeNotInGraphErrorMessage;
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 com.google.common.truth.TruthJUnit.assume;
25 import static java.util.concurrent.Executors.newFixedThreadPool;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertThrows;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.ImmutableSet;
33 import com.google.common.collect.Sets;
34 import java.util.Set;
35 import java.util.concurrent.Callable;
36 import java.util.concurrent.CyclicBarrier;
37 import java.util.concurrent.ExecutorService;
38 import java.util.concurrent.Future;
39 import org.checkerframework.checker.nullness.qual.Nullable;
40 import org.junit.After;
41 import org.junit.Before;
42 import org.junit.Test;
43 
44 /**
45  * Abstract base class for testing implementations of {@link Network} interface. Network instances
46  * created for testing should have Integer node and String edge objects.
47  *
48  * <p>Test cases that should be handled similarly in any graph implementation are included in this
49  * class. For example, testing that {@code nodes()} method returns the set of the nodes in the
50  * graph. The following test cases are left for the subclasses to handle:
51  *
52  * <ul>
53  *   <li>Test cases related to whether the graph is directed, undirected, mutable, or immutable.
54  *   <li>Test cases related to the specific implementation of the {@link Network} interface.
55  * </ul>
56  *
57  * TODO(user): Make this class generic (using <N, E>) for all node and edge types.
58  * TODO(user): Differentiate between directed and undirected edge strings.
59  */
60 public abstract class AbstractNetworkTest {
61 
62   Network<Integer, String> network;
63 
64   /**
65    * The same reference as {@link #network}, except as a mutable network. This field is null in case
66    * {@link #createGraph()} didn't return a mutable network.
67    */
68   MutableNetwork<Integer, String> networkAsMutableNetwork;
69 
70   static final Integer N1 = 1;
71   static final Integer N2 = 2;
72   static final Integer N3 = 3;
73   static final Integer N4 = 4;
74   static final Integer N5 = 5;
75   static final Integer NODE_NOT_IN_GRAPH = 1000;
76 
77   static final String E11 = "1-1";
78   static final String E11_A = "1-1a";
79   static final String E12 = "1-2";
80   static final String E12_A = "1-2a";
81   static final String E12_B = "1-2b";
82   static final String E21 = "2-1";
83   static final String E13 = "1-3";
84   static final String E14 = "1-4";
85   static final String E23 = "2-3";
86   static final String E31 = "3-1";
87   static final String E34 = "3-4";
88   static final String E41 = "4-1";
89   static final String E15 = "1-5";
90   static final String EDGE_NOT_IN_GRAPH = "edgeNotInGraph";
91 
92   // TODO(user): Consider separating Strings that we've defined here to capture
93   // identifiable substrings of expected error messages, from Strings that we've defined
94   // here to provide error messages.
95   // TODO(user): Some Strings used in the subclasses can be added as static Strings
96   // here too.
97   static final String ERROR_PARALLEL_EDGE = "connected by a different edge";
98   static final String ERROR_REUSE_EDGE = "it cannot be reused to connect";
99   static final String ERROR_MODIFIABLE_COLLECTION =
100       "Collection returned is unexpectedly modifiable";
101   static final String ERROR_SELF_LOOP = "self-loops are not allowed";
102   static final String ERROR_EDGE_NOT_IN_GRAPH =
103       "Should not be allowed to pass an edge that is not an element of the graph.";
104   static final String ERROR_ADDED_SELF_LOOP = "Should not be allowed to add a self-loop edge.";
105   static final String ERROR_ADDED_PARALLEL_EDGE = "Should not be allowed to add a parallel edge.";
106   static final String ERROR_ADDED_EXISTING_EDGE =
107       "Reusing an existing edge to connect different nodes succeeded";
108 
109   /** Creates and returns an instance of the graph to be tested. */
createGraph()110   abstract Network<Integer, String> createGraph();
111 
112   /**
113    * A proxy method that adds the node {@code n} to the graph being tested. In case of Immutable
114    * graph implementations, this method should replace {@link #network} with a new graph that
115    * includes this node.
116    */
addNode(Integer n)117   abstract void addNode(Integer n);
118 
119   /**
120    * A proxy method that adds the edge {@code e} to the graph being tested. In case of Immutable
121    * graph implementations, this method should replace {@link #network} with a new graph that
122    * includes this edge.
123    */
addEdge(Integer n1, Integer n2, String e)124   abstract void addEdge(Integer n1, Integer n2, String e);
125 
graphIsMutable()126   final boolean graphIsMutable() {
127     return networkAsMutableNetwork != null;
128   }
129 
130   @Before
init()131   public void init() {
132     network = createGraph();
133     if (network instanceof MutableNetwork) {
134       networkAsMutableNetwork = (MutableNetwork<Integer, String>) network;
135     }
136   }
137 
138   @After
validateNetworkState()139   public void validateNetworkState() {
140     validateNetwork(network);
141   }
142 
validateNetwork(Network<N, E> network)143   static <N, E> void validateNetwork(Network<N, E> network) {
144     assertStronglyEquivalent(network, Graphs.copyOf(network));
145     assertStronglyEquivalent(network, ImmutableNetwork.copyOf(network));
146 
147     String networkString = network.toString();
148     assertThat(networkString).contains("isDirected: " + network.isDirected());
149     assertThat(networkString).contains("allowsParallelEdges: " + network.allowsParallelEdges());
150     assertThat(networkString).contains("allowsSelfLoops: " + network.allowsSelfLoops());
151 
152     int nodeStart = networkString.indexOf("nodes:");
153     int edgeStart = networkString.indexOf("edges:");
154     String nodeString = networkString.substring(nodeStart, edgeStart);
155     String edgeString = networkString.substring(edgeStart);
156 
157     Graph<N> asGraph = network.asGraph();
158     AbstractGraphTest.validateGraph(asGraph);
159     assertThat(network.nodes()).isEqualTo(asGraph.nodes());
160     assertThat(network.edges().size()).isAtLeast(asGraph.edges().size());
161     assertThat(network.nodeOrder()).isEqualTo(asGraph.nodeOrder());
162     assertThat(network.isDirected()).isEqualTo(asGraph.isDirected());
163     assertThat(network.allowsSelfLoops()).isEqualTo(asGraph.allowsSelfLoops());
164 
165     for (E edge : sanityCheckSet(network.edges())) {
166       // TODO(b/27817069): Consider verifying the edge's incident nodes in the string.
167       assertThat(edgeString).contains(edge.toString());
168 
169       EndpointPair<N> endpointPair = network.incidentNodes(edge);
170       N nodeU = endpointPair.nodeU();
171       N nodeV = endpointPair.nodeV();
172       assertThat(asGraph.edges()).contains(EndpointPair.of(network, nodeU, nodeV));
173       assertThat(network.edgesConnecting(nodeU, nodeV)).contains(edge);
174       assertThat(network.successors(nodeU)).contains(nodeV);
175       assertThat(network.adjacentNodes(nodeU)).contains(nodeV);
176       assertThat(network.outEdges(nodeU)).contains(edge);
177       assertThat(network.incidentEdges(nodeU)).contains(edge);
178       assertThat(network.predecessors(nodeV)).contains(nodeU);
179       assertThat(network.adjacentNodes(nodeV)).contains(nodeU);
180       assertThat(network.inEdges(nodeV)).contains(edge);
181       assertThat(network.incidentEdges(nodeV)).contains(edge);
182 
183       for (N incidentNode : network.incidentNodes(edge)) {
184         assertThat(network.nodes()).contains(incidentNode);
185         for (E adjacentEdge : network.incidentEdges(incidentNode)) {
186           assertTrue(
187               edge.equals(adjacentEdge) || network.adjacentEdges(edge).contains(adjacentEdge));
188         }
189       }
190     }
191 
192     for (N node : sanityCheckSet(network.nodes())) {
193       assertThat(nodeString).contains(node.toString());
194 
195       assertThat(network.adjacentNodes(node)).isEqualTo(asGraph.adjacentNodes(node));
196       assertThat(network.predecessors(node)).isEqualTo(asGraph.predecessors(node));
197       assertThat(network.successors(node)).isEqualTo(asGraph.successors(node));
198 
199       int selfLoopCount = network.edgesConnecting(node, node).size();
200       assertThat(network.incidentEdges(node).size() + selfLoopCount)
201           .isEqualTo(network.degree(node));
202 
203       if (network.isDirected()) {
204         assertThat(network.incidentEdges(node).size() + selfLoopCount)
205             .isEqualTo(network.inDegree(node) + network.outDegree(node));
206         assertThat(network.inEdges(node)).hasSize(network.inDegree(node));
207         assertThat(network.outEdges(node)).hasSize(network.outDegree(node));
208       } else {
209         assertThat(network.predecessors(node)).isEqualTo(network.adjacentNodes(node));
210         assertThat(network.successors(node)).isEqualTo(network.adjacentNodes(node));
211         assertThat(network.inEdges(node)).isEqualTo(network.incidentEdges(node));
212         assertThat(network.outEdges(node)).isEqualTo(network.incidentEdges(node));
213         assertThat(network.inDegree(node)).isEqualTo(network.degree(node));
214         assertThat(network.outDegree(node)).isEqualTo(network.degree(node));
215       }
216 
217       for (N otherNode : network.nodes()) {
218         Set<E> edgesConnecting = sanityCheckSet(network.edgesConnecting(node, otherNode));
219         switch (edgesConnecting.size()) {
220           case 0:
221             assertThat(network.edgeConnectingOrNull(node, otherNode)).isNull();
222             assertThat(network.edgeConnecting(node, otherNode).isPresent()).isFalse();
223             assertThat(network.hasEdgeConnecting(node, otherNode)).isFalse();
224             break;
225           case 1:
226             E edge = edgesConnecting.iterator().next();
227             assertThat(network.edgeConnectingOrNull(node, otherNode)).isEqualTo(edge);
228             assertThat(network.edgeConnecting(node, otherNode).get()).isEqualTo(edge);
229             assertThat(network.hasEdgeConnecting(node, otherNode)).isTrue();
230             break;
231           default:
232             assertThat(network.hasEdgeConnecting(node, otherNode)).isTrue();
233             try {
234               network.edgeConnectingOrNull(node, otherNode);
235               fail();
236             } catch (IllegalArgumentException expected) {
237             }
238             try {
239               network.edgeConnecting(node, otherNode);
240               fail();
241             } catch (IllegalArgumentException expected) {
242             }
243         }
244 
245         boolean isSelfLoop = node.equals(otherNode);
246         boolean connected = !edgesConnecting.isEmpty();
247         if (network.isDirected() || !isSelfLoop) {
248           assertThat(edgesConnecting)
249               .isEqualTo(Sets.intersection(network.outEdges(node), network.inEdges(otherNode)));
250         }
251         if (!network.allowsParallelEdges()) {
252           assertThat(edgesConnecting.size()).isAtMost(1);
253         }
254         if (!network.allowsSelfLoops() && isSelfLoop) {
255           assertThat(connected).isFalse();
256         }
257 
258         assertThat(network.successors(node).contains(otherNode)).isEqualTo(connected);
259         assertThat(network.predecessors(otherNode).contains(node)).isEqualTo(connected);
260         for (E edge : edgesConnecting) {
261           assertThat(network.incidentNodes(edge))
262               .isEqualTo(EndpointPair.of(network, node, otherNode));
263           assertThat(network.outEdges(node)).contains(edge);
264           assertThat(network.inEdges(otherNode)).contains(edge);
265         }
266       }
267 
268       for (N adjacentNode : sanityCheckSet(network.adjacentNodes(node))) {
269         assertTrue(
270             network.predecessors(node).contains(adjacentNode)
271                 || network.successors(node).contains(adjacentNode));
272         assertTrue(
273             !network.edgesConnecting(node, adjacentNode).isEmpty()
274                 || !network.edgesConnecting(adjacentNode, node).isEmpty());
275       }
276 
277       for (N predecessor : sanityCheckSet(network.predecessors(node))) {
278         assertThat(network.successors(predecessor)).contains(node);
279         assertThat(network.edgesConnecting(predecessor, node)).isNotEmpty();
280       }
281 
282       for (N successor : sanityCheckSet(network.successors(node))) {
283         assertThat(network.predecessors(successor)).contains(node);
284         assertThat(network.edgesConnecting(node, successor)).isNotEmpty();
285       }
286 
287       for (E incidentEdge : sanityCheckSet(network.incidentEdges(node))) {
288         assertTrue(
289             network.inEdges(node).contains(incidentEdge)
290                 || network.outEdges(node).contains(incidentEdge));
291         assertThat(network.edges()).contains(incidentEdge);
292         assertThat(network.incidentNodes(incidentEdge)).contains(node);
293       }
294 
295       for (E inEdge : sanityCheckSet(network.inEdges(node))) {
296         assertThat(network.incidentEdges(node)).contains(inEdge);
297         assertThat(network.outEdges(network.incidentNodes(inEdge).adjacentNode(node)))
298             .contains(inEdge);
299         if (network.isDirected()) {
300           assertThat(network.incidentNodes(inEdge).target()).isEqualTo(node);
301         }
302       }
303 
304       for (E outEdge : sanityCheckSet(network.outEdges(node))) {
305         assertThat(network.incidentEdges(node)).contains(outEdge);
306         assertThat(network.inEdges(network.incidentNodes(outEdge).adjacentNode(node)))
307             .contains(outEdge);
308         if (network.isDirected()) {
309           assertThat(network.incidentNodes(outEdge).source()).isEqualTo(node);
310         }
311       }
312     }
313   }
314 
315   /**
316    * Verifies that the {@code Set} returned by {@code nodes} has the expected mutability property
317    * (see the {@code Network} documentation for more information).
318    */
319   @Test
nodes_checkReturnedSetMutability()320   public abstract void nodes_checkReturnedSetMutability();
321 
322   /**
323    * Verifies that the {@code Set} returned by {@code edges} has the expected mutability property
324    * (see the {@code Network} documentation for more information).
325    */
326   @Test
edges_checkReturnedSetMutability()327   public abstract void edges_checkReturnedSetMutability();
328 
329   /**
330    * Verifies that the {@code Set} returned by {@code incidentEdges} has the expected mutability
331    * property (see the {@code Network} documentation for more information).
332    */
333   @Test
incidentEdges_checkReturnedSetMutability()334   public abstract void incidentEdges_checkReturnedSetMutability();
335 
336   /**
337    * Verifies that the {@code Set} returned by {@code adjacentNodes} has the expected mutability
338    * property (see the {@code Network} documentation for more information).
339    */
340   @Test
adjacentNodes_checkReturnedSetMutability()341   public abstract void adjacentNodes_checkReturnedSetMutability();
342 
343   /**
344    * Verifies that the {@code Set} returned by {@code adjacentEdges} has the expected mutability
345    * property (see the {@code Network} documentation for more information).
346    */
347   @Test
adjacentEdges_checkReturnedSetMutability()348   public abstract void adjacentEdges_checkReturnedSetMutability();
349 
350   /**
351    * Verifies that the {@code Set} returned by {@code edgesConnecting} has the expected mutability
352    * property (see the {@code Network} documentation for more information).
353    */
354   @Test
edgesConnecting_checkReturnedSetMutability()355   public abstract void edgesConnecting_checkReturnedSetMutability();
356 
357   /**
358    * Verifies that the {@code Set} returned by {@code inEdges} has the expected mutability property
359    * (see the {@code Network} documentation for more information).
360    */
361   @Test
inEdges_checkReturnedSetMutability()362   public abstract void inEdges_checkReturnedSetMutability();
363 
364   /**
365    * Verifies that the {@code Set} returned by {@code outEdges} has the expected mutability property
366    * (see the {@code Network} documentation for more information).
367    */
368   @Test
outEdges_checkReturnedSetMutability()369   public abstract void outEdges_checkReturnedSetMutability();
370 
371   /**
372    * Verifies that the {@code Set} returned by {@code predecessors} has the expected mutability
373    * property (see the {@code Network} documentation for more information).
374    */
375   @Test
predecessors_checkReturnedSetMutability()376   public abstract void predecessors_checkReturnedSetMutability();
377 
378   /**
379    * Verifies that the {@code Set} returned by {@code successors} has the expected mutability
380    * property (see the {@code Network} documentation for more information).
381    */
382   @Test
successors_checkReturnedSetMutability()383   public abstract void successors_checkReturnedSetMutability();
384 
385   @Test
nodes_oneNode()386   public void nodes_oneNode() {
387     addNode(N1);
388     assertThat(network.nodes()).containsExactly(N1);
389   }
390 
391   @Test
nodes_noNodes()392   public void nodes_noNodes() {
393     assertThat(network.nodes()).isEmpty();
394   }
395 
396   @Test
edges_oneEdge()397   public void edges_oneEdge() {
398     addEdge(N1, N2, E12);
399     assertThat(network.edges()).containsExactly(E12);
400   }
401 
402   @Test
edges_noEdges()403   public void edges_noEdges() {
404     assertThat(network.edges()).isEmpty();
405     // Network with no edges, given disconnected nodes
406     addNode(N1);
407     addNode(N2);
408     assertThat(network.edges()).isEmpty();
409   }
410 
411   @Test
incidentEdges_oneEdge()412   public void incidentEdges_oneEdge() {
413     addEdge(N1, N2, E12);
414     assertThat(network.incidentEdges(N2)).containsExactly(E12);
415     assertThat(network.incidentEdges(N1)).containsExactly(E12);
416   }
417 
418   @Test
incidentEdges_isolatedNode()419   public void incidentEdges_isolatedNode() {
420     addNode(N1);
421     assertThat(network.incidentEdges(N1)).isEmpty();
422   }
423 
424   @Test
incidentEdges_nodeNotInGraph()425   public void incidentEdges_nodeNotInGraph() {
426     IllegalArgumentException e =
427         assertThrows(
428             IllegalArgumentException.class, () -> network.incidentEdges(NODE_NOT_IN_GRAPH));
429     assertNodeNotInGraphErrorMessage(e);
430   }
431 
432   @Test
incidentNodes_oneEdge()433   public void incidentNodes_oneEdge() {
434     addEdge(N1, N2, E12);
435     assertThat(network.incidentNodes(E12)).containsExactly(N1, N2);
436   }
437 
438   @Test
incidentNodes_edgeNotInGraph()439   public void incidentNodes_edgeNotInGraph() {
440     IllegalArgumentException e =
441         assertThrows(
442             IllegalArgumentException.class, () -> network.incidentNodes(EDGE_NOT_IN_GRAPH));
443     assertEdgeNotInGraphErrorMessage(e);
444   }
445 
446   @Test
adjacentNodes_oneEdge()447   public void adjacentNodes_oneEdge() {
448     addEdge(N1, N2, E12);
449     assertThat(network.adjacentNodes(N1)).containsExactly(N2);
450     assertThat(network.adjacentNodes(N2)).containsExactly(N1);
451   }
452 
453   @Test
adjacentNodes_noAdjacentNodes()454   public void adjacentNodes_noAdjacentNodes() {
455     addNode(N1);
456     assertThat(network.adjacentNodes(N1)).isEmpty();
457   }
458 
459   @Test
adjacentNodes_nodeNotInGraph()460   public void adjacentNodes_nodeNotInGraph() {
461     IllegalArgumentException e =
462         assertThrows(
463             IllegalArgumentException.class, () -> network.adjacentNodes(NODE_NOT_IN_GRAPH));
464     assertNodeNotInGraphErrorMessage(e);
465   }
466 
467   @Test
adjacentEdges_bothEndpoints()468   public void adjacentEdges_bothEndpoints() {
469     addEdge(N1, N2, E12);
470     addEdge(N2, N3, E23);
471     addEdge(N3, N1, E31);
472     addEdge(N3, N4, E34);
473     assertThat(network.adjacentEdges(E12)).containsExactly(E31, E23);
474   }
475 
476   @Test
adjacentEdges_noAdjacentEdges()477   public void adjacentEdges_noAdjacentEdges() {
478     addEdge(N1, N2, E12);
479     addEdge(N3, N4, E34);
480     assertThat(network.adjacentEdges(E12)).isEmpty();
481   }
482 
483   @Test
adjacentEdges_edgeNotInGraph()484   public void adjacentEdges_edgeNotInGraph() {
485     IllegalArgumentException e =
486         assertThrows(
487             IllegalArgumentException.class, () -> network.adjacentEdges(EDGE_NOT_IN_GRAPH));
488     assertEdgeNotInGraphErrorMessage(e);
489   }
490 
491   @Test
adjacentEdges_parallelEdges()492   public void adjacentEdges_parallelEdges() {
493     assume().that(network.allowsParallelEdges()).isTrue();
494 
495     addEdge(N1, N2, E12);
496     addEdge(N1, N2, E12_A);
497     addEdge(N1, N2, E12_B);
498     addEdge(N3, N4, E34);
499 
500     assertThat(network.adjacentEdges(E12)).containsExactly(E12_A, E12_B);
501   }
502 
503   @Test
edgesConnecting_disconnectedNodes()504   public void edgesConnecting_disconnectedNodes() {
505     addNode(N1);
506     addNode(N2);
507     assertThat(network.edgesConnecting(N1, N2)).isEmpty();
508   }
509 
510   @Test
edgesConnecting_nodesNotInGraph()511   public void edgesConnecting_nodesNotInGraph() {
512     addNode(N1);
513     addNode(N2);
514     IllegalArgumentException e =
515         assertThrows(
516             IllegalArgumentException.class, () -> network.edgesConnecting(N1, NODE_NOT_IN_GRAPH));
517     assertNodeNotInGraphErrorMessage(e);
518     e =
519         assertThrows(
520             IllegalArgumentException.class, () -> network.edgesConnecting(NODE_NOT_IN_GRAPH, N2));
521     assertNodeNotInGraphErrorMessage(e);
522     e =
523         assertThrows(
524             IllegalArgumentException.class,
525             () -> network.edgesConnecting(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH));
526     assertNodeNotInGraphErrorMessage(e);
527   }
528 
529   @Test
edgesConnecting_parallelEdges_directed()530   public void edgesConnecting_parallelEdges_directed() {
531     assume().that(network.allowsParallelEdges()).isTrue();
532     assume().that(network.isDirected()).isTrue();
533 
534     addEdge(N1, N2, E12);
535     addEdge(N1, N2, E12_A);
536 
537     assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12, E12_A);
538     // Passed nodes should be in the correct edge direction, first is the
539     // source node and the second is the target node
540     assertThat(network.edgesConnecting(N2, N1)).isEmpty();
541   }
542 
543   @Test
edgesConnecting_parallelEdges_undirected()544   public void edgesConnecting_parallelEdges_undirected() {
545     assume().that(network.allowsParallelEdges()).isTrue();
546     assume().that(network.isDirected()).isFalse();
547 
548     addEdge(N1, N2, E12);
549     addEdge(N1, N2, E12_A);
550     addEdge(N2, N1, E21);
551 
552     assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12, E12_A, E21);
553     assertThat(network.edgesConnecting(N2, N1)).containsExactly(E12, E12_A, E21);
554   }
555 
556   @Test
edgesConnecting_parallelSelfLoopEdges()557   public void edgesConnecting_parallelSelfLoopEdges() {
558     assume().that(network.allowsParallelEdges()).isTrue();
559     assume().that(network.allowsSelfLoops()).isTrue();
560 
561     addEdge(N1, N1, E11);
562     addEdge(N1, N1, E11_A);
563 
564     assertThat(network.edgesConnecting(N1, N1)).containsExactly(E11, E11_A);
565   }
566 
567   @Test
hasEdgeConnecting_disconnectedNodes()568   public void hasEdgeConnecting_disconnectedNodes() {
569     addNode(N1);
570     addNode(N2);
571     assertThat(network.hasEdgeConnecting(N1, N2)).isFalse();
572   }
573 
574   @Test
hasEdgesConnecting_nodesNotInGraph()575   public void hasEdgesConnecting_nodesNotInGraph() {
576     addNode(N1);
577     addNode(N2);
578     assertThat(network.hasEdgeConnecting(N1, NODE_NOT_IN_GRAPH)).isFalse();
579     assertThat(network.hasEdgeConnecting(NODE_NOT_IN_GRAPH, N2)).isFalse();
580     assertThat(network.hasEdgeConnecting(NODE_NOT_IN_GRAPH, NODE_NOT_IN_GRAPH)).isFalse();
581   }
582 
583   @Test
inEdges_noInEdges()584   public void inEdges_noInEdges() {
585     addNode(N1);
586     assertThat(network.inEdges(N1)).isEmpty();
587   }
588 
589   @Test
inEdges_nodeNotInGraph()590   public void inEdges_nodeNotInGraph() {
591     IllegalArgumentException e =
592         assertThrows(IllegalArgumentException.class, () -> network.inEdges(NODE_NOT_IN_GRAPH));
593     assertNodeNotInGraphErrorMessage(e);
594   }
595 
596   @Test
outEdges_noOutEdges()597   public void outEdges_noOutEdges() {
598     addNode(N1);
599     assertThat(network.outEdges(N1)).isEmpty();
600   }
601 
602   @Test
outEdges_nodeNotInGraph()603   public void outEdges_nodeNotInGraph() {
604     IllegalArgumentException e =
605         assertThrows(IllegalArgumentException.class, () -> network.outEdges(NODE_NOT_IN_GRAPH));
606     assertNodeNotInGraphErrorMessage(e);
607   }
608 
609   @Test
predecessors_noPredecessors()610   public void predecessors_noPredecessors() {
611     addNode(N1);
612     assertThat(network.predecessors(N1)).isEmpty();
613   }
614 
615   @Test
predecessors_nodeNotInGraph()616   public void predecessors_nodeNotInGraph() {
617     IllegalArgumentException e =
618         assertThrows(IllegalArgumentException.class, () -> network.predecessors(NODE_NOT_IN_GRAPH));
619     assertNodeNotInGraphErrorMessage(e);
620   }
621 
622   @Test
successors_noSuccessors()623   public void successors_noSuccessors() {
624     addNode(N1);
625     assertThat(network.successors(N1)).isEmpty();
626   }
627 
628   @Test
successors_nodeNotInGraph()629   public void successors_nodeNotInGraph() {
630     IllegalArgumentException e =
631         assertThrows(IllegalArgumentException.class, () -> network.successors(NODE_NOT_IN_GRAPH));
632     assertNodeNotInGraphErrorMessage(e);
633   }
634 
635   @Test
addNode_newNode()636   public void addNode_newNode() {
637     assume().that(graphIsMutable()).isTrue();
638 
639     assertTrue(networkAsMutableNetwork.addNode(N1));
640     assertThat(networkAsMutableNetwork.nodes()).contains(N1);
641   }
642 
643   @Test
addNode_existingNode()644   public void addNode_existingNode() {
645     assume().that(graphIsMutable()).isTrue();
646 
647     addNode(N1);
648     ImmutableSet<Integer> nodes = ImmutableSet.copyOf(networkAsMutableNetwork.nodes());
649     assertFalse(networkAsMutableNetwork.addNode(N1));
650     assertThat(networkAsMutableNetwork.nodes()).containsExactlyElementsIn(nodes);
651   }
652 
653   @Test
removeNode_existingNode()654   public void removeNode_existingNode() {
655     assume().that(graphIsMutable()).isTrue();
656 
657     addEdge(N1, N2, E12);
658     addEdge(N4, N1, E41);
659     assertTrue(networkAsMutableNetwork.removeNode(N1));
660     assertFalse(networkAsMutableNetwork.removeNode(N1));
661     assertThat(networkAsMutableNetwork.nodes()).containsExactly(N2, N4);
662     assertThat(networkAsMutableNetwork.edges()).doesNotContain(E12);
663     assertThat(networkAsMutableNetwork.edges()).doesNotContain(E41);
664   }
665 
666   @Test
removeNode_nodeNotPresent()667   public void removeNode_nodeNotPresent() {
668     assume().that(graphIsMutable()).isTrue();
669 
670     addNode(N1);
671     ImmutableSet<Integer> nodes = ImmutableSet.copyOf(networkAsMutableNetwork.nodes());
672     assertFalse(networkAsMutableNetwork.removeNode(NODE_NOT_IN_GRAPH));
673     assertThat(networkAsMutableNetwork.nodes()).containsExactlyElementsIn(nodes);
674   }
675 
676   @Test
removeNode_queryAfterRemoval()677   public void removeNode_queryAfterRemoval() {
678     assume().that(graphIsMutable()).isTrue();
679 
680     addEdge(N1, N2, E12);
681     Set<Integer> n1AdjacentNodes = networkAsMutableNetwork.adjacentNodes(N1);
682     Set<Integer> n2AdjacentNodes = networkAsMutableNetwork.adjacentNodes(N2);
683     assertTrue(networkAsMutableNetwork.removeNode(N1));
684     assertThat(n1AdjacentNodes).isEmpty();
685     assertThat(n2AdjacentNodes).isEmpty();
686     IllegalArgumentException e =
687         assertThrows(
688             IllegalArgumentException.class, () -> networkAsMutableNetwork.adjacentNodes(N1));
689     assertNodeNotInGraphErrorMessage(e);
690   }
691 
692   @Test
removeEdge_existingEdge()693   public void removeEdge_existingEdge() {
694     assume().that(graphIsMutable()).isTrue();
695 
696     addEdge(N1, N2, E12);
697     assertTrue(networkAsMutableNetwork.removeEdge(E12));
698     assertFalse(networkAsMutableNetwork.removeEdge(E12));
699     assertThat(networkAsMutableNetwork.edges()).doesNotContain(E12);
700     assertThat(networkAsMutableNetwork.edgesConnecting(N1, N2)).isEmpty();
701   }
702 
703   @Test
removeEdge_oneOfMany()704   public void removeEdge_oneOfMany() {
705     assume().that(graphIsMutable()).isTrue();
706 
707     addEdge(N1, N2, E12);
708     addEdge(N1, N3, E13);
709     addEdge(N1, N4, E14);
710     assertThat(networkAsMutableNetwork.edges()).containsExactly(E12, E13, E14);
711     assertTrue(networkAsMutableNetwork.removeEdge(E13));
712     assertThat(networkAsMutableNetwork.edges()).containsExactly(E12, E14);
713   }
714 
715   @Test
removeEdge_edgeNotPresent()716   public void removeEdge_edgeNotPresent() {
717     assume().that(graphIsMutable()).isTrue();
718 
719     addEdge(N1, N2, E12);
720     ImmutableSet<String> edges = ImmutableSet.copyOf(networkAsMutableNetwork.edges());
721     assertFalse(networkAsMutableNetwork.removeEdge(EDGE_NOT_IN_GRAPH));
722     assertThat(networkAsMutableNetwork.edges()).containsExactlyElementsIn(edges);
723   }
724 
725   @Test
removeEdge_queryAfterRemoval()726   public void removeEdge_queryAfterRemoval() {
727     assume().that(graphIsMutable()).isTrue();
728 
729     addEdge(N1, N2, E12);
730     @SuppressWarnings("unused")
731     EndpointPair<Integer> unused =
732         networkAsMutableNetwork.incidentNodes(E12); // ensure cache (if any) is populated
733     assertTrue(networkAsMutableNetwork.removeEdge(E12));
734     IllegalArgumentException e =
735         assertThrows(
736             IllegalArgumentException.class, () -> networkAsMutableNetwork.incidentNodes(E12));
737     assertEdgeNotInGraphErrorMessage(e);
738   }
739 
740   @Test
removeEdge_parallelEdge()741   public void removeEdge_parallelEdge() {
742     assume().that(graphIsMutable()).isTrue();
743     assume().that(network.allowsParallelEdges()).isTrue();
744 
745     addEdge(N1, N2, E12);
746     addEdge(N1, N2, E12_A);
747     assertTrue(networkAsMutableNetwork.removeEdge(E12_A));
748     assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12);
749   }
750 
751   @Test
removeEdge_parallelSelfLoopEdge()752   public void removeEdge_parallelSelfLoopEdge() {
753     assume().that(graphIsMutable()).isTrue();
754     assume().that(network.allowsParallelEdges()).isTrue();
755     assume().that(network.allowsSelfLoops()).isTrue();
756 
757     addEdge(N1, N1, E11);
758     addEdge(N1, N1, E11_A);
759     addEdge(N1, N2, E12);
760     assertTrue(networkAsMutableNetwork.removeEdge(E11_A));
761     assertThat(network.edgesConnecting(N1, N1)).containsExactly(E11);
762     assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12);
763     assertTrue(networkAsMutableNetwork.removeEdge(E11));
764     assertThat(network.edgesConnecting(N1, N1)).isEmpty();
765     assertThat(network.edgesConnecting(N1, N2)).containsExactly(E12);
766   }
767 
768   @Test
concurrentIteration()769   public void concurrentIteration() throws Exception {
770     addEdge(1, 2, "foo");
771     addEdge(3, 4, "bar");
772     addEdge(5, 6, "baz");
773 
774     int threadCount = 20;
775     ExecutorService executor = newFixedThreadPool(threadCount);
776     final CyclicBarrier barrier = new CyclicBarrier(threadCount);
777     ImmutableList.Builder<Future<?>> futures = ImmutableList.builder();
778     for (int i = 0; i < threadCount; i++) {
779       futures.add(
780           executor.submit(
781               new Callable<@Nullable Void>() {
782                 @Override
783                 public @Nullable Void call() throws Exception {
784                   barrier.await();
785                   Integer first = network.nodes().iterator().next();
786                   for (Integer node : network.nodes()) {
787                     Set<Integer> unused = network.successors(node);
788                   }
789                   /*
790                    * Also look up an earlier node so that, if the graph is using MapRetrievalCache,
791                    * we read one of the fields declared in that class.
792                    */
793                   Set<Integer> unused = network.successors(first);
794                   return null;
795                 }
796               }));
797     }
798 
799     /*
800      * It's unlikely that any operations would fail by throwing an exception, but let's check them
801      * just to be safe.
802      *
803      * The real purpose of this test is to produce a TSAN failure if MapIteratorCache is unsafe for
804      * reads from multiple threads -- unsafe, in fact, even in the absence of a concurrent write.
805      * The specific problem we had was unsafe reads of lastEntryReturnedBySomeIterator. (To fix the
806      * problem, we've since marked that field as volatile.)
807      *
808      * When MapIteratorCache is used from Immutable* classes, the TSAN failure doesn't indicate a
809      * real problem: The Entry objects are ImmutableMap entries, whose fields are all final and thus
810      * safe to read even when the Entry object is unsafely published. But with a mutable graph, the
811      * Entry object is likely to have a non-final value field, which is not safe to read when
812      * unsafely published. (The Entry object might even be newly created by each iterator.next()
813      * call, so we can't assume that writes to the Entry have been safely published by some other
814      * synchronization actions.)
815      *
816      * All that said: I haven't actually managed to make this particular test produce a TSAN error
817      * for the field accesses in MapIteratorCache. This teset *has* found other TSAN errors,
818      * including in MapRetrievalCache, so I'm not sure why this one is different. I did at least
819      * confirm that my change to MapIteratorCache fixes the TSAN error in the (larger) test it was
820      * originally reported in.
821      */
822     for (Future<?> future : futures.build()) {
823       future.get();
824     }
825     executor.shutdown();
826   }
827 }
828