package graphs.shortestpaths; import graphs.BaseEdge; import graphs.Graph; import org.assertj.core.api.AbstractBooleanAssert; import org.assertj.core.api.AbstractDoubleAssert; import org.assertj.core.api.AbstractObjectAssert; import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.ListAssert; import org.assertj.core.api.MapAssert; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; /** * Asserts for ShortestPathFinders. */ public class ShortestPathFinderAssert, V, E extends BaseEdge> extends AbstractObjectAssert, SPTShortestPathFinder> { public ShortestPathFinderAssert(SPTShortestPathFinder actual) { super(actual, ShortestPathFinderAssert.class); } public ShortestPathAssert findingShortestPath(G graph, V start, V end) { return new ShortestPathAssert<>(actual.findShortestPath(graph, start, end), graph, start, end); } public MapAssert constructingShortestPathsTree(G graph, V start, V end) { return assertThat(actual.constructShortestPathsTree(graph, start, end)); } public ShortestPathAssert extractingShortestPathFromShortestPathsTree(G graph, Map spt, V start, V end) { return new ShortestPathAssert<>(actual.extractShortestPath(spt, start, end), graph, start, end); } public static class ShortestPathAssert> extends AbstractObjectAssert, ShortestPath> { public static final double EPSILON = .0001; private final Graph graph; private final V start; private final V end; public ShortestPathAssert(ShortestPath actual, Graph graph, V start, V end) { super(actual, ShortestPathAssert.class); this.graph = graph; this.start = start; this.end = end; } public ShortestPath getActual() { return this.actual; } private AbstractBooleanAssert extractExists() { return extracting(ShortestPath::exists, InstanceOfAssertFactories.BOOLEAN) .as("result.exists()"); } private AbstractDoubleAssert extractWeight() { return extracting(ShortestPath::totalWeight, InstanceOfAssertFactories.DOUBLE) .as("result.totalWeight()"); } // The types in this method aren't perfect, but Object is good enough for our test code. private ListAssert extractVertices() { return extracting(ShortestPath::vertices, InstanceOfAssertFactories.LIST) .as("result.vertices()"); } public ShortestPathAssert doesNotExist() { extractExists().isFalse(); return this; } public ShortestPathAssert exists() { extractExists().isTrue(); return this; } @SafeVarargs public final ShortestPathAssert hasVertices(V... vertices) { exists(); pathIsValid(); extractVertices().containsExactly(vertices); return this; } public ShortestPathAssert hasSolutionEquivalentTo(ShortestPath expected) { extractExists().isEqualTo(expected.exists()); if (expected.exists()) { hasWeightCloseTo(expected.totalWeight()); pathIsValid(); } return this; } public ShortestPathAssert hasWeightCloseTo(double expected) { extractWeight().isCloseTo(expected, within(EPSILON)); return this; } public ShortestPathAssert pathIsValid() { if (!actual.exists()) { // skip if not solved; validity check only matters when solution exists return this; } // check start and end match extractVertices().first().as(describe("first vertex")).isEqualTo(this.start); extractVertices().last().as(describe("last vertex")).isEqualTo(this.end); // check transitions are valid according to graph checkHasValidTransitions(); return this; } private void checkHasValidTransitions() { V prev = null; boolean firstIteration = true; for (E edge : actual.edges()) { V from = edge.from(); V to = edge.to(); if (!firstIteration) { assertThat(from).as("currEdge.from() should be prevEdge.to()").isEqualTo(prev); } if (!graph.outgoingEdgesFrom(from).contains(edge)) { as(describe("solution")). failWithMessage("Invalid edge found in solution: " + describeFromTo(from, to)); } prev = to; firstIteration = false; } } private String describe(String s) { return s + " of shortest path " + describeFromTo(this.start, this.end); } private String describeFromTo(V from, V to) { String fromString = from.toString().stripTrailing(); String toString = to.toString().stripTrailing(); if (fromString.contains("\n") || toString.contains("\n")) { return String.format("from:%n%s%n%nto:%s", fromString, toString); } return String.format("from <%s> to <%s>", fromString, toString); } } }