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