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.assertNodeNotInGraphErrorMessage; 20 import static com.google.common.graph.TestUtil.assertNodeRemovedFromGraphErrorMessage; 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 org.junit.Assert.assertThrows; 26 27 import com.google.common.collect.ImmutableSet; 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 or undirected. 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 52 Graph<Integer> graph; 53 54 /** 55 * The same reference as {@link #graph}, except as a mutable graph. This field is null in case 56 * {@link #createGraph()} didn't return a mutable graph. 57 */ 58 MutableGraph<Integer> graphAsMutableGraph; 59 60 static final Integer N1 = 1; 61 static final Integer N2 = 2; 62 static final Integer N3 = 3; 63 static final Integer N4 = 4; 64 static final Integer N5 = 5; 65 static final Integer NODE_NOT_IN_GRAPH = 1000; 66 67 // TODO(user): Consider separating Strings that we've defined here to capture 68 // identifiable substrings of expected error messages, from Strings that we've defined 69 // here to provide error messages. 70 // TODO(user): Some Strings used in the subclasses can be added as static Strings 71 // here too. 72 static final String ERROR_MODIFIABLE_SET = "Set returned is unexpectedly modifiable"; 73 static final String ERROR_SELF_LOOP = "self-loops are not allowed"; 74 static final String ERROR_ADDED_SELF_LOOP = "Should not be allowed to add a self-loop edge."; 75 76 /** Creates and returns an instance of the graph to be tested. */ createGraph()77 abstract Graph<Integer> createGraph(); 78 79 /** 80 * A proxy method that adds the node {@code n} to the graph being tested. In case of Immutable 81 * graph implementations, this method should replace {@link #graph} with a new graph that includes 82 * this node. 83 */ addNode(Integer n)84 abstract void addNode(Integer n); 85 86 /** 87 * A proxy method that adds the edge {@code e} to the graph being tested. In case of Immutable 88 * graph implementations, this method should replace {@link #graph} with a new graph that includes 89 * this edge. 90 */ putEdge(Integer n1, Integer n2)91 abstract void putEdge(Integer n1, Integer n2); 92 graphIsMutable()93 final boolean graphIsMutable() { 94 return graphAsMutableGraph != null; 95 } 96 97 @Before init()98 public final void init() { 99 graph = createGraph(); 100 if (graph instanceof MutableGraph) { 101 graphAsMutableGraph = (MutableGraph<Integer>) graph; 102 } 103 } 104 105 @After validateGraphState()106 public final void validateGraphState() { 107 validateGraph(graph); 108 } 109 validateGraph(Graph<N> graph)110 static <N> void validateGraph(Graph<N> graph) { 111 assertStronglyEquivalent(graph, Graphs.copyOf(graph)); 112 assertStronglyEquivalent(graph, ImmutableGraph.copyOf(graph)); 113 114 String graphString = graph.toString(); 115 assertThat(graphString).contains("isDirected: " + graph.isDirected()); 116 assertThat(graphString).contains("allowsSelfLoops: " + graph.allowsSelfLoops()); 117 118 int nodeStart = graphString.indexOf("nodes:"); 119 int edgeStart = graphString.indexOf("edges:"); 120 String nodeString = graphString.substring(nodeStart, edgeStart); 121 122 Set<EndpointPair<N>> allEndpointPairs = new HashSet<>(); 123 124 for (N node : sanityCheckSet(graph.nodes())) { 125 assertThat(nodeString).contains(node.toString()); 126 127 if (graph.isDirected()) { 128 assertThat(graph.degree(node)).isEqualTo(graph.inDegree(node) + graph.outDegree(node)); 129 assertThat(graph.predecessors(node)).hasSize(graph.inDegree(node)); 130 assertThat(graph.successors(node)).hasSize(graph.outDegree(node)); 131 } else { 132 int selfLoopCount = graph.adjacentNodes(node).contains(node) ? 1 : 0; 133 assertThat(graph.degree(node)).isEqualTo(graph.adjacentNodes(node).size() + selfLoopCount); 134 assertThat(graph.predecessors(node)).isEqualTo(graph.adjacentNodes(node)); 135 assertThat(graph.successors(node)).isEqualTo(graph.adjacentNodes(node)); 136 assertThat(graph.inDegree(node)).isEqualTo(graph.degree(node)); 137 assertThat(graph.outDegree(node)).isEqualTo(graph.degree(node)); 138 } 139 140 for (N adjacentNode : sanityCheckSet(graph.adjacentNodes(node))) { 141 if (!graph.allowsSelfLoops()) { 142 assertThat(node).isNotEqualTo(adjacentNode); 143 } 144 assertThat( 145 graph.predecessors(node).contains(adjacentNode) 146 || graph.successors(node).contains(adjacentNode)) 147 .isTrue(); 148 } 149 150 for (N predecessor : sanityCheckSet(graph.predecessors(node))) { 151 assertThat(graph.successors(predecessor)).contains(node); 152 assertThat(graph.hasEdgeConnecting(predecessor, node)).isTrue(); 153 assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, predecessor, node)); 154 } 155 156 for (N successor : sanityCheckSet(graph.successors(node))) { 157 allEndpointPairs.add(EndpointPair.of(graph, node, successor)); 158 assertThat(graph.predecessors(successor)).contains(node); 159 assertThat(graph.hasEdgeConnecting(node, successor)).isTrue(); 160 assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, node, successor)); 161 } 162 163 for (EndpointPair<N> endpoints : sanityCheckSet(graph.incidentEdges(node))) { 164 if (graph.isDirected()) { 165 assertThat(graph.hasEdgeConnecting(endpoints.source(), endpoints.target())).isTrue(); 166 } else { 167 assertThat(graph.hasEdgeConnecting(endpoints.nodeU(), endpoints.nodeV())).isTrue(); 168 } 169 } 170 } 171 172 sanityCheckSet(graph.edges()); 173 assertThat(graph.edges()).doesNotContain(EndpointPair.of(graph, new Object(), new Object())); 174 assertThat(graph.edges()).isEqualTo(allEndpointPairs); 175 } 176 177 /** 178 * Verifies that the {@code Set} returned by {@code nodes} has the expected mutability property 179 * (see the {@code Graph} documentation for more information). 180 */ 181 @Test nodes_checkReturnedSetMutability()182 public abstract void nodes_checkReturnedSetMutability(); 183 184 /** 185 * Verifies that the {@code Set} returned by {@code adjacentNodes} has the expected mutability 186 * property (see the {@code Graph} documentation for more information). 187 */ 188 @Test adjacentNodes_checkReturnedSetMutability()189 public abstract void adjacentNodes_checkReturnedSetMutability(); 190 191 /** 192 * Verifies that the {@code Set} returned by {@code predecessors} has the expected mutability 193 * property (see the {@code Graph} documentation for more information). 194 */ 195 @Test predecessors_checkReturnedSetMutability()196 public abstract void predecessors_checkReturnedSetMutability(); 197 198 /** 199 * Verifies that the {@code Set} returned by {@code successors} has the expected mutability 200 * property (see the {@code Graph} documentation for more information). 201 */ 202 @Test successors_checkReturnedSetMutability()203 public abstract void successors_checkReturnedSetMutability(); 204 205 /** 206 * Verifies that the {@code Set} returned by {@code incidentEdges} has the expected mutability 207 * property (see the {@code Graph} documentation for more information). 208 */ 209 @Test incidentEdges_checkReturnedSetMutability()210 public abstract void incidentEdges_checkReturnedSetMutability(); 211 212 @Test nodes_oneNode()213 public void nodes_oneNode() { 214 addNode(N1); 215 assertThat(graph.nodes()).containsExactly(N1); 216 } 217 218 @Test nodes_noNodes()219 public void nodes_noNodes() { 220 assertThat(graph.nodes()).isEmpty(); 221 } 222 223 @Test adjacentNodes_oneEdge()224 public void adjacentNodes_oneEdge() { 225 putEdge(N1, N2); 226 assertThat(graph.adjacentNodes(N1)).containsExactly(N2); 227 assertThat(graph.adjacentNodes(N2)).containsExactly(N1); 228 } 229 230 @Test adjacentNodes_noAdjacentNodes()231 public void adjacentNodes_noAdjacentNodes() { 232 addNode(N1); 233 assertThat(graph.adjacentNodes(N1)).isEmpty(); 234 } 235 236 @Test adjacentNodes_nodeNotInGraph()237 public void adjacentNodes_nodeNotInGraph() { 238 assertNodeNotInGraphErrorMessage( 239 assertThrows(IllegalArgumentException.class, () -> graph.adjacentNodes(NODE_NOT_IN_GRAPH))); 240 } 241 242 @Test predecessors_noPredecessors()243 public void predecessors_noPredecessors() { 244 addNode(N1); 245 assertThat(graph.predecessors(N1)).isEmpty(); 246 } 247 248 @Test predecessors_nodeNotInGraph()249 public void predecessors_nodeNotInGraph() { 250 assertNodeNotInGraphErrorMessage( 251 assertThrows(IllegalArgumentException.class, () -> graph.predecessors(NODE_NOT_IN_GRAPH))); 252 } 253 254 @Test successors_noSuccessors()255 public void successors_noSuccessors() { 256 addNode(N1); 257 assertThat(graph.successors(N1)).isEmpty(); 258 } 259 260 @Test successors_nodeNotInGraph()261 public void successors_nodeNotInGraph() { 262 assertNodeNotInGraphErrorMessage( 263 assertThrows(IllegalArgumentException.class, () -> graph.successors(NODE_NOT_IN_GRAPH))); 264 } 265 266 @Test incidentEdges_noIncidentEdges()267 public void incidentEdges_noIncidentEdges() { 268 addNode(N1); 269 assertThat(graph.incidentEdges(N1)).isEmpty(); 270 } 271 272 @Test incidentEdges_nodeNotInGraph()273 public void incidentEdges_nodeNotInGraph() { 274 assertNodeNotInGraphErrorMessage( 275 assertThrows(IllegalArgumentException.class, () -> graph.incidentEdges(NODE_NOT_IN_GRAPH))); 276 } 277 278 @Test degree_oneEdge()279 public void degree_oneEdge() { 280 putEdge(N1, N2); 281 assertThat(graph.degree(N1)).isEqualTo(1); 282 assertThat(graph.degree(N2)).isEqualTo(1); 283 } 284 285 @Test degree_isolatedNode()286 public void degree_isolatedNode() { 287 addNode(N1); 288 assertThat(graph.degree(N1)).isEqualTo(0); 289 } 290 291 @Test degree_nodeNotInGraph()292 public void degree_nodeNotInGraph() { 293 assertNodeNotInGraphErrorMessage( 294 assertThrows(IllegalArgumentException.class, () -> graph.degree(NODE_NOT_IN_GRAPH))); 295 } 296 297 @Test inDegree_isolatedNode()298 public void inDegree_isolatedNode() { 299 addNode(N1); 300 assertThat(graph.inDegree(N1)).isEqualTo(0); 301 } 302 303 @Test inDegree_nodeNotInGraph()304 public void inDegree_nodeNotInGraph() { 305 assertNodeNotInGraphErrorMessage( 306 assertThrows(IllegalArgumentException.class, () -> graph.inDegree(NODE_NOT_IN_GRAPH))); 307 } 308 309 @Test outDegree_isolatedNode()310 public void outDegree_isolatedNode() { 311 addNode(N1); 312 assertThat(graph.outDegree(N1)).isEqualTo(0); 313 } 314 315 @Test outDegree_nodeNotInGraph()316 public void outDegree_nodeNotInGraph() { 317 assertNodeNotInGraphErrorMessage( 318 assertThrows(IllegalArgumentException.class, () -> graph.outDegree(NODE_NOT_IN_GRAPH))); 319 } 320 321 @Test addNode_newNode()322 public void addNode_newNode() { 323 assume().that(graphIsMutable()).isTrue(); 324 325 assertThat(graphAsMutableGraph.addNode(N1)).isTrue(); 326 assertThat(graph.nodes()).contains(N1); 327 } 328 329 @Test addNode_existingNode()330 public void addNode_existingNode() { 331 assume().that(graphIsMutable()).isTrue(); 332 333 addNode(N1); 334 ImmutableSet<Integer> nodes = ImmutableSet.copyOf(graph.nodes()); 335 assertThat(graphAsMutableGraph.addNode(N1)).isFalse(); 336 assertThat(graph.nodes()).containsExactlyElementsIn(nodes); 337 } 338 339 @Test removeNode_existingNode()340 public void removeNode_existingNode() { 341 assume().that(graphIsMutable()).isTrue(); 342 343 putEdge(N1, N2); 344 putEdge(N4, N1); 345 assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); 346 assertThat(graphAsMutableGraph.removeNode(N1)).isFalse(); 347 assertThat(graph.nodes()).containsExactly(N2, N4); 348 349 assertThat(graph.adjacentNodes(N2)).isEmpty(); 350 assertThat(graph.predecessors(N2)).isEmpty(); 351 assertThat(graph.successors(N2)).isEmpty(); 352 assertThat(graph.incidentEdges(N2)).isEmpty(); 353 assertThat(graph.adjacentNodes(N4)).isEmpty(); 354 assertThat(graph.predecessors(N4)).isEmpty(); 355 assertThat(graph.successors(N4)).isEmpty(); 356 assertThat(graph.incidentEdges(N4)).isEmpty(); 357 358 assertNodeNotInGraphErrorMessage( 359 assertThrows(IllegalArgumentException.class, () -> graph.adjacentNodes(N1))); 360 assertNodeNotInGraphErrorMessage( 361 assertThrows(IllegalArgumentException.class, () -> graph.predecessors(N1))); 362 assertNodeNotInGraphErrorMessage( 363 assertThrows(IllegalArgumentException.class, () -> graph.successors(N1))); 364 assertNodeNotInGraphErrorMessage( 365 assertThrows(IllegalArgumentException.class, () -> graph.incidentEdges(N1))); 366 } 367 368 @Test removeNode_antiparallelEdges()369 public void removeNode_antiparallelEdges() { 370 assume().that(graphIsMutable()).isTrue(); 371 372 putEdge(N1, N2); 373 putEdge(N2, N1); 374 375 assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); 376 assertThat(graph.nodes()).containsExactly(N2); 377 assertThat(graph.edges()).isEmpty(); 378 379 assertThat(graphAsMutableGraph.removeNode(N2)).isTrue(); 380 assertThat(graph.nodes()).isEmpty(); 381 assertThat(graph.edges()).isEmpty(); 382 } 383 384 @Test removeNode_nodeNotPresent()385 public void removeNode_nodeNotPresent() { 386 assume().that(graphIsMutable()).isTrue(); 387 388 addNode(N1); 389 ImmutableSet<Integer> nodes = ImmutableSet.copyOf(graph.nodes()); 390 assertThat(graphAsMutableGraph.removeNode(NODE_NOT_IN_GRAPH)).isFalse(); 391 assertThat(graph.nodes()).containsExactlyElementsIn(nodes); 392 } 393 394 @Test queryAccessorSetAfterElementRemoval()395 public void queryAccessorSetAfterElementRemoval() { 396 assume().that(graphIsMutable()).isTrue(); 397 398 putEdge(N1, N2); 399 putEdge(N2, N1); 400 Set<Integer> n1AdjacentNodes = graph.adjacentNodes(N1); 401 Set<Integer> n2AdjacentNodes = graph.adjacentNodes(N2); 402 Set<Integer> n1Predecessors = graph.predecessors(N1); 403 Set<Integer> n2Predecessors = graph.predecessors(N2); 404 Set<Integer> n1Successors = graph.successors(N1); 405 Set<Integer> n2Successors = graph.successors(N2); 406 Set<EndpointPair<Integer>> n1IncidentEdges = graph.incidentEdges(N1); 407 Set<EndpointPair<Integer>> n2IncidentEdges = graph.incidentEdges(N2); 408 assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); 409 410 // The choice of the size() method to call here is arbitrary. We assume that if any of the Set 411 // methods executes the validation check, they all will, and thus we only need to test one of 412 // them to ensure that the validation check happens and has the expected behavior. 413 assertNodeRemovedFromGraphErrorMessage( 414 assertThrows(IllegalStateException.class, n1AdjacentNodes::size)); 415 assertNodeRemovedFromGraphErrorMessage( 416 assertThrows(IllegalStateException.class, n1Predecessors::size)); 417 assertNodeRemovedFromGraphErrorMessage( 418 assertThrows(IllegalStateException.class, n1Successors::size)); 419 assertNodeRemovedFromGraphErrorMessage( 420 assertThrows(IllegalStateException.class, n1IncidentEdges::size)); 421 422 assertThat(n2AdjacentNodes).isEmpty(); 423 assertThat(n2Predecessors).isEmpty(); 424 assertThat(n2Successors).isEmpty(); 425 assertThat(n2IncidentEdges).isEmpty(); 426 } 427 428 @Test queryGraphAfterElementRemoval()429 public void queryGraphAfterElementRemoval() { 430 assume().that(graphIsMutable()).isTrue(); 431 432 putEdge(N1, N2); 433 putEdge(N2, N1); 434 assertThat(graphAsMutableGraph.removeNode(N1)).isTrue(); 435 assertNodeNotInGraphErrorMessage( 436 assertThrows(IllegalArgumentException.class, () -> graph.adjacentNodes(N1))); 437 } 438 439 @Test removeEdge_existingEdge()440 public void removeEdge_existingEdge() { 441 assume().that(graphIsMutable()).isTrue(); 442 443 putEdge(N1, N2); 444 assertThat(graph.successors(N1)).containsExactly(N2); 445 assertThat(graph.predecessors(N2)).containsExactly(N1); 446 assertThat(graphAsMutableGraph.removeEdge(N1, N2)).isTrue(); 447 assertThat(graphAsMutableGraph.removeEdge(N1, N2)).isFalse(); 448 assertThat(graph.successors(N1)).isEmpty(); 449 assertThat(graph.predecessors(N2)).isEmpty(); 450 } 451 452 @Test removeEdge_oneOfMany()453 public void removeEdge_oneOfMany() { 454 assume().that(graphIsMutable()).isTrue(); 455 456 putEdge(N1, N2); 457 putEdge(N1, N3); 458 putEdge(N1, N4); 459 assertThat(graphAsMutableGraph.removeEdge(N1, N3)).isTrue(); 460 assertThat(graph.adjacentNodes(N1)).containsExactly(N2, N4); 461 } 462 463 @Test removeEdge_nodeNotPresent()464 public void removeEdge_nodeNotPresent() { 465 assume().that(graphIsMutable()).isTrue(); 466 467 putEdge(N1, N2); 468 assertThat(graphAsMutableGraph.removeEdge(N1, NODE_NOT_IN_GRAPH)).isFalse(); 469 assertThat(graph.successors(N1)).contains(N2); 470 } 471 472 @Test removeEdge_edgeNotPresent()473 public void removeEdge_edgeNotPresent() { 474 assume().that(graphIsMutable()).isTrue(); 475 476 putEdge(N1, N2); 477 addNode(N3); 478 479 assertThat(graphAsMutableGraph.removeEdge(N1, N3)).isFalse(); 480 assertThat(graph.successors(N1)).contains(N2); 481 } 482 } 483