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