a-maze-ing / CSE 373 PT / mazes / test / graphs / shortestpaths / ShortestPathFinderAssert.java
ShortestPathFinderAssert.java
Raw
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<G extends Graph<V, E>, V, E extends BaseEdge<V, E>>
    extends AbstractObjectAssert<ShortestPathFinderAssert<G, V, E>, SPTShortestPathFinder<G, V, E>> {

    public ShortestPathFinderAssert(SPTShortestPathFinder<G, V, E> actual) {
        super(actual, ShortestPathFinderAssert.class);
    }

    public ShortestPathAssert<V, E> findingShortestPath(G graph, V start, V end) {
        return new ShortestPathAssert<>(actual.findShortestPath(graph, start, end), graph, start, end);
    }

    public MapAssert<V, E> constructingShortestPathsTree(G graph, V start, V end) {
        return assertThat(actual.constructShortestPathsTree(graph, start, end));
    }

    public ShortestPathAssert<V, E> extractingShortestPathFromShortestPathsTree(G graph,
                                                                                Map<V, E> spt,
                                                                                V start,
                                                                                V end) {
        return new ShortestPathAssert<>(actual.extractShortestPath(spt, start, end), graph, start, end);
    }

    public static class ShortestPathAssert<V, E extends BaseEdge<V, E>>
        extends AbstractObjectAssert<ShortestPathAssert<V, E>, ShortestPath<V, E>> {

        public static final double EPSILON = .0001;
        private final Graph<V, E> graph;
        private final V start;
        private final V end;

        public ShortestPathAssert(ShortestPath<V, E> actual,
                                  Graph<V, E> graph,
                                  V start,
                                  V end) {
            super(actual, ShortestPathAssert.class);
            this.graph = graph;
            this.start = start;
            this.end = end;
        }

        public ShortestPath<V, E> 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<Object> extractVertices() {
            return extracting(ShortestPath::vertices, InstanceOfAssertFactories.LIST)
                .as("result.vertices()");
        }

        public ShortestPathAssert<V, E> doesNotExist() {
            extractExists().isFalse();
            return this;
        }

        public ShortestPathAssert<V, E> exists() {
            extractExists().isTrue();
            return this;
        }

        @SafeVarargs
        public final ShortestPathAssert<V, E> hasVertices(V... vertices) {
            exists();
            pathIsValid();
            extractVertices().containsExactly(vertices);
            return this;
        }

        public ShortestPathAssert<V, E> hasSolutionEquivalentTo(ShortestPath<V, E> expected) {
            extractExists().isEqualTo(expected.exists());
            if (expected.exists()) {
                hasWeightCloseTo(expected.totalWeight());
                pathIsValid();
            }
            return this;
        }

        public ShortestPathAssert<V, E> hasWeightCloseTo(double expected) {
            extractWeight().isCloseTo(expected, within(EPSILON));
            return this;
        }

        public ShortestPathAssert<V, E> 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);
        }
    }
}