Commit 183c06a4 authored by Ethan Ordentlich's avatar Ethan Ordentlich
Browse files

Merge branch 'tests' into 'master'

Review Tests for Project 8

Closes #2, #3, #4, #5, #6, #9, and #10

See merge request !2
1 merge request!2Review Tests for Project 8
Pipeline #46666 failed with stage
in 0 seconds
Showing with 395 additions and 168 deletions
+395 -168
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="A Tests" type="JUnit" factoryName="JUnit">
<module name="project08-beavermaps" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" />
<option name="PARAMETERS" value="" />
<tag value="A" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="B Tests" type="JUnit" factoryName="JUnit">
<module name="project08-beavermaps" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" />
<option name="PARAMETERS" value="" />
<tag value="B" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BeaverMapsGraph Tests (C)" type="JUnit" factoryName="JUnit">
<module name="project08-beavermaps" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" />
<tag value="C &amp; Beavermap" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="C Tests" type="JUnit" factoryName="JUnit">
<module name="project08-beavermaps" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" />
<option name="PARAMETERS" value="" />
<tag value="C" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Graph Tests (C)" type="JUnit" factoryName="JUnit">
<module name="project08-beavermaps" />
<option name="PACKAGE_NAME" value="edu.caltech.cs2.project08" />
<option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.project08.GraphTests" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Marvel Tests (C)" type="JUnit" factoryName="JUnit">
<module name="project08-beavermaps" />
<option name="PACKAGE_NAME" value="edu.caltech.cs2.project08" />
<option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.project08.MarvelTests" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
......@@ -6,14 +6,11 @@ import edu.caltech.cs2.interfaces.IDeque;
import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.interfaces.ISet;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class BeaverMapsGraph extends Graph<Long, Double> {
private static JsonParser JSON_PARSER = new JsonParser();
private IDictionary<Long, Location> ids;
private ISet<Location> buildings;
......@@ -78,8 +75,7 @@ public class BeaverMapsGraph extends Graph<Long, Double> {
}
/**
* Returns a set of locations which are no more than threshold feet
* away from start.
* Returns a set of locations which are reachable along a path that goes no further than `threshold` feet from start
* @param start the location to search around
* @param threshold the number of feet in the search radius
* @return
......@@ -117,12 +113,8 @@ public class BeaverMapsGraph extends Graph<Long, Double> {
* @return the JSON data from filename
*/
private static JsonElement fromFile(String filename) {
try {
return JSON_PARSER.parse(
new FileReader(
new File(filename)
)
);
try (FileReader reader = new FileReader(filename)) {
return JsonParser.parseReader(reader);
} catch (IOException e) {
return null;
}
......
package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.interfaces.*;
import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.interfaces.IGraph;
import edu.caltech.cs2.interfaces.ISet;
public class Graph<V, E> extends IGraph<V, E> {
public class Graph<V, E> implements IGraph<V, E> {
@Override
public boolean addVertex(V vertex) {
......
......@@ -23,8 +23,8 @@ public interface IDeque<E> extends ICollection<E> {
* @param e Element to add
*/
@Override
default public void add(E e){
this.addFront(e);
default public void add(E e) {
this.addBack(e);
}
/**
......
package edu.caltech.cs2.interfaces;
import edu.caltech.cs2.datastructures.LinkedDeque;
public interface IDictionary<K, V> extends Iterable<K> {
public static class Entry<K, V> {
......
package edu.caltech.cs2.interfaces;
public abstract class IGraph<V, E> {
public interface IGraph<V, E> {
/**
* Add a vertex to the graph.
* @param vertex The vertex to add
* @return true if vertex was not present already.
*/
public abstract boolean addVertex(V vertex);
public boolean addVertex(V vertex);
/**
* Adds edge e to the graph.
*
* @param e The edge to add.
* @throws IllegalArgumentException
* If e is not a valid edge (eg. refers to vertices not in the graph).
* @return true If e was not already present; false if it was (in which case the graph is still updated).
* If src and dest are not valid vertices (eg. refers to vertices not in the graph).
* @return true if an edge from src to dest did not exist previously.
* false if an edge from src to dest did exist previously, in which case the edge value is updated
*/
public abstract boolean addEdge(V src, V dest, E e);
public boolean addEdge(V src, V dest, E e);
/**
* Adds edge e to the graph in both directionns.
*
* @param e The edge to add.
* @throws IllegalArgumentException
* If e is not a valid edge (eg. refers to vertices not in the graph).
* @return true If e was not already present in either direction; false if it was (in which case the graph is still updated).
* If v1 and v2 are not valid vertices (eg. refers to vertices not in the graph).
* @return true if an edge between src and dest did not exist in either direction.
* false if an edge between src and dest existed in either direction, in which case the edge value is
* updated.
*/
public abstract boolean addUndirectedEdge(V src, V dest, E e);
public boolean addUndirectedEdge(V v1, V v2, E e);
/**
* Remove an edge from src to dest from the graph.
*
* @throws IllegalArgumentException if src or dest is not in the graph.
* @return true if an edge from src to dest edge was present.
* @return true if an edge from src to dest was present.
*/
public abstract boolean removeEdge(V src, V dest);
public boolean removeEdge(V src, V dest);
/**
* Returns the set of vertices in the graph.
* @return The set of all vertices in the graph.
*/
public abstract ISet<V> vertices();
public ISet<V> vertices();
/**
* Tests if vertices i and j are adjacent, returning the edge between
......@@ -50,7 +53,7 @@ public abstract class IGraph<V, E> {
* @return The edge from i to j if it exists in the graph;
* null otherwise.
*/
public abstract E adjacent(V i, V j);
public E adjacent(V i, V j);
/**
* Return the neighbours of a given vertex when this graph is treated as
......@@ -60,5 +63,5 @@ public abstract class IGraph<V, E> {
* @throws IllegalArgumentException if vertex is not in the graph.
* @return The set of neighbors of vertex.
*/
public abstract ISet<V> neighbors(V vertex);
public ISet<V> neighbors(V vertex);
}
\ No newline at end of file
......@@ -5,6 +5,7 @@ import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import edu.caltech.cs2.datastructures.BeaverMapsGraph;
import edu.caltech.cs2.datastructures.LinkedDeque;
import edu.caltech.cs2.datastructures.Location;
import edu.caltech.cs2.interfaces.IDeque;
import edu.caltech.cs2.interfaces.ISet;
......
package edu.caltech.cs2.project08;
import edu.caltech.cs2.datastructures.LinkedDeque;
import edu.caltech.cs2.datastructures.Location;
import edu.caltech.cs2.datastructures.TrieMap;
import edu.caltech.cs2.interfaces.IDeque;
import edu.caltech.cs2.interfaces.ISet;
import edu.caltech.cs2.interfaces.ITrieMap;
......
......@@ -18,11 +18,11 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.fail;
public class Inspection {
private static String getUsageOf(List<String> regexps, List<? extends Node> codeObjects) {
private static String[] getUsageOf(List<String> regexps, List<? extends Node> codeObjects) {
for (Node d : codeObjects) {
for (String regex : regexps) {
if (d.toString().replaceAll("\\R", "").matches(".*" + regex + ".*")) {
return regex;
return new String[]{d.toString().replaceAll("\\R", ""), regex};
}
}
}
......@@ -32,9 +32,25 @@ public class Inspection {
public static void assertNoImportsOf(String filePath, List<String> regexps) {
try {
CompilationUnit cu = JavaParser.parse(new File(filePath));
String usage = getUsageOf(regexps, cu.getImports());
String[] usage = getUsageOf(regexps, cu.getImports());
if (usage != null) {
fail("You may not import " + usage + " in " + Paths.get(filePath).getFileName() + ".");
fail(usage[0] + " is a banned import under " + usage[1] + " in " + Paths.get(filePath).getFileName()
+ ".");
}
} catch (FileNotFoundException e) {
fail("Missing Java file: " + Paths.get(filePath).getFileName());
}
}
public static void assertNoImportsOfExcept(String filePath, String bannedImport, List<String> allowedClasses) {
try {
CompilationUnit cu = JavaParser.parse(new File(filePath));
String bannedRegex = bannedImport + "\\.(?!" + String.join("|", allowedClasses) + ")";
String[] usage = getUsageOf(List.of(bannedRegex), cu.getImports());
if (usage != null) {
fail(usage[0] + " is a banned import under " + bannedImport +
" and is not an allowed import " +
allowedClasses + " in " + Paths.get(filePath).getFileName() + ".");
}
} catch (FileNotFoundException e) {
fail("Missing Java file: " + Paths.get(filePath).getFileName());
......@@ -66,16 +82,16 @@ public class Inspection {
List<ConstructorDeclaration> constructors = new ArrayList<>();
CONSTRUCTOR_COLLECTOR.visit(cu, constructors);
String usage = getUsageOf(regexps, constructors);
String[] usage = getUsageOf(regexps, constructors);
if (usage != null) {
fail("You may not use " + usage + " in " + Paths.get(filePath).getFileName() + ".");
fail("You may not use " + usage[1] + " in " + Paths.get(filePath).getFileName() + ".");
}
List<MethodDeclaration> methods = new ArrayList<>();
METHOD_COLLECTOR.visit(cu, methods);
usage = getUsageOf(regexps, methods);
if (usage != null) {
fail("You may not use " + usage + " in " + Paths.get(filePath).getFileName() + ".");
fail("You may not use " + usage[1] + " in " + Paths.get(filePath).getFileName() + ".");
}
} catch (FileNotFoundException e) {
fail("Missing Java file: " + Paths.get(filePath).getFileName());
......@@ -93,8 +109,7 @@ public class Inspection {
List<Statement> statements = body.getStatements();
if (statements.size() != 1) {
nonThisConstructors++;
}
else if (!statements.get(0).toString().startsWith("this(")) {
} else if (!statements.get(0).toString().startsWith("this(")) {
nonThisConstructors++;
}
......
......@@ -9,7 +9,6 @@ import edu.caltech.cs2.datastructures.Location;
import edu.caltech.cs2.helpers.Inspection;
import edu.caltech.cs2.helpers.Reflection;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
......@@ -22,7 +21,7 @@ import edu.caltech.cs2.interfaces.ISet;
import org.hamcrest.MatcherAssert;
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.hamcrest.core.IsCollectionContaining;
import org.hamcrest.core.IsIterableContaining;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestMethodOrder;
......@@ -34,33 +33,65 @@ import static org.junit.jupiter.api.Assertions.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BeavermapTests {
private static String BEAVERMAP_GRAPH_SOURCE = "src/edu/caltech/cs2/datastructures/BeaverMapsGraph.java";
private static JsonParser JSON_PARSER = new JsonParser();
private static JsonElement fromFile(String filename) {
try {
return JSON_PARSER.parse(
new FileReader(
new File(filename)
)
);
} catch (IOException e) {
try (FileReader reader = new FileReader(filename)) {
return JsonParser.parseReader(reader);
}
catch (IOException e) {
return null;
}
}
@Tag("C")
@Tag("Beavermap")
@DisplayName("BeaverMapsGraph implements required public methods")
@Test
@Order(0)
public void testMethodsBeaverMapsGraph() {
SortedSet<String> expected = new TreeSet<>(List.of(
"getLocationByName", "getBuildings", "getClosestBuilding", "dfs", "dijkstra", "addVertex"
));
SortedSet<String> actual = new TreeSet<>(
Stream.of(BeaverMapsGraph.class.getDeclaredMethods())
.filter(Reflection.hasModifier("public"))
.map(x -> x.getName())
.collect(Collectors.toList()));
MatcherAssert.assertThat(new ArrayList<>(actual),
IsIterableContaining.hasItems((expected.toArray())));
}
@Tag("C")
@Tag("Beavermap")
@Order(1)
@DisplayName("Does not use or import disallowed classes")
@Test
public void testForInvalidClasses() {
List<String> graphDisallow = List.of("java\\.util\\.(?!Iterator)", "java\\.lang\\.reflect");
List<String> graphDisallow = List.of("java\\.lang\\.reflect");
Inspection.assertNoImportsOf(BEAVERMAP_GRAPH_SOURCE, graphDisallow);
Inspection.assertNoUsageOf(BEAVERMAP_GRAPH_SOURCE, graphDisallow);
}
@Order(2)
@DisplayName("Does not use or import disallowed classes from java.util")
@Test
@Tag("C")
@Tag("Beavermap")
public void testForInvalidImportsJavaUtil() {
List<String> allowed = List.of("Iterator");
Inspection.assertNoImportsOfExcept(BEAVERMAP_GRAPH_SOURCE, "java\\.util", allowed);
List<String> bannedUsages = List.of("java\\.util\\.(?!" + String.join("|", allowed) + ")");
Inspection.assertNoUsageOf(BEAVERMAP_GRAPH_SOURCE, bannedUsages);
}
// Only use Caltech map and buildings to test for correctness
@Tag("C")
@Tag("Beavermap")
@Test
@Order(3)
@DisplayName("Test getLocationById()")
public void testGetLocationByID() {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/caltech/caltech.buildings.json",
......@@ -74,7 +105,10 @@ public class BeavermapTests {
}
@Tag("C")
@Tag("Beavermap")
@Test
@Order(4)
@DisplayName("Test getLocationByName()")
public void testGetLocationByName() {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/caltech/caltech.buildings.json",
......@@ -90,12 +124,14 @@ public class BeavermapTests {
}
@Tag("C")
@Tag("Beavermap")
@DisplayName("Test getBuildings()")
@ParameterizedTest(name = "Test getBuildings() on {0}")
@CsvSource({
"caltech/caltech.buildings.json, caltech/caltech.waypoints.json, caltech/caltech.roads.json",
"pasadena/pasadena.buildings.json, pasadena/pasadena.waypoints.json, pasadena/pasadena.roads.json",
})
@Order(5)
public void testGetBuildings(String buildingsFile, String waypointsFile, String roadsFile) {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/" + buildingsFile, "data/" + waypointsFile, "data/" + roadsFile);
......@@ -110,12 +146,14 @@ public class BeavermapTests {
}
@Tag("C")
@Tag("Beavermap")
@DisplayName("Test getClosestBuilding()")
@ParameterizedTest(name = "Test getClosestBuilding() on {0}")
@CsvSource({
"caltech/caltech.buildings.json, caltech/caltech.waypoints.json, caltech/caltech.roads.json, caltech/caltech.closest_trace.json",
"pasadena/pasadena.buildings.json, pasadena/pasadena.waypoints.json, pasadena/pasadena.roads.json, pasadena/pasadena.closest_trace.json",
})
@Order(6)
public void testGetClosestLocation(String buildingsFile, String waypointsFile, String roadsFile, String traceFile) {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/" + buildingsFile, "data/" + waypointsFile, "data/" + roadsFile);
......@@ -131,29 +169,34 @@ public class BeavermapTests {
}
@Tag("C")
@DisplayName("BeaverMapsGraph implements required public methods")
@Tag("Beavermap")
@DisplayName("Test addVertex() updates BeaverMapsGraph and underlying Graph properly")
@Test
public void testMethodsBeaverMapsGraph() {
SortedSet<String> expected = new TreeSet<>(List.of(
"getLocationByName", "getBuildings", "getClosestBuilding", "dfs", "dijkstra", "addVertex"
));
SortedSet<String> actual = new TreeSet<>(
Stream.of(BeaverMapsGraph.class.getDeclaredMethods())
.filter(Reflection.hasModifier("public"))
.map(x -> x.getName())
.collect(Collectors.toList()));
MatcherAssert.assertThat(new ArrayList<>(actual),
IsCollectionContaining.hasItems((expected.toArray())));
@Order(7)
public void testAddVertexBeaverMapsGraph() {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/caltech/caltech.buildings.json",
"data/caltech/caltech.waypoints.json",
"data/caltech/caltech.roads.json");
JsonElement bs = fromFile("data/pasadena/pasadena.buildings.json");
for (JsonElement b : bs.getAsJsonArray()) {
Location loc = new Location(b.getAsJsonObject());
bmg.addVertex(loc);
assertNotNull(bmg.getLocationByID(loc.id), "Location id " + loc.id + " not found by id");
// Test that the vertex was actually added to the graph
bmg.neighbors(loc.id);
}
}
// Note: Pasadena map is WAY TOO LARGE to test all edges, don't try
@Tag("C")
@DisplayName("Tests nodes and edges in map for filename")
@Tag("Beavermap")
@DisplayName("Completely check all nodes and edges in BeaverMapsGraph loaded from files")
@ParameterizedTest(name = "Test nodes in file {0}")
@CsvSource({
"caltech/caltech.buildings.json, caltech/caltech.waypoints.json, caltech/caltech.roads.json, caltech/caltech.neighbors_trace.json"
})
@Order(8)
public void testNodesEdgesInMap(String bFile, String wFile, String roadsFile, String traceFile) {
BeaverMapsGraph bmg = new BeaverMapsGraph("data/" + bFile, "data/" + wFile, "data/" + roadsFile);
......@@ -181,6 +224,7 @@ public class BeavermapTests {
}
// Use this instead of MatcherAssert to provide better errors (though I doubt they'll be needed)
// Should use Truth instead... but not this year.
if (missingNeighbors.size() > 0) {
fail(locID + " missing neighbors " + missingNeighbors);
} else if (actualNeighbors.size() != 0) {
......@@ -198,6 +242,7 @@ public class BeavermapTests {
"caltech/caltech.buildings.json, caltech/caltech.waypoints.json, caltech/caltech.roads.json, caltech/caltech.radius_trace.json",
"pasadena/pasadena.buildings.json, pasadena/pasadena.waypoints.json, pasadena/pasadena.roads.json, pasadena/pasadena.radius_trace.json",
})
@Order(9)
public void testDFSRadius(String buildingsFile, String waypointsFile, String roadsFile, String traceFile) {
BeaverMapsGraph bmg = new BeaverMapsGraph(
......@@ -226,6 +271,34 @@ public class BeavermapTests {
}
}
@Tag("A")
@DisplayName("Test buildings are ignored in dijkstra path")
@Test
@Order(10)
public void testDijkstraIgnoreBuildings() {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/caltech/caltech.buildings.json",
"data/caltech/caltech.waypoints.json",
"data/caltech/caltech.roads.json");
JsonElement s = fromFile("data/caltech/caltech.paths_trace.json");
for (JsonElement b : s.getAsJsonArray()) {
JsonObject curr = b.getAsJsonObject();
Location start = bmg.getLocationByID(curr.get("start").getAsLong());
Location target = bmg.getLocationByID(curr.get("target").getAsLong());
IDeque<Location> actualPath = bmg.dijkstra(start, target);
if (actualPath == null) {
continue;
}
for (Location loc : actualPath){
if (loc.id == start.id || loc.id == target.id) {
continue;
}
ISet<Location> buildings = Reflection.getFieldValue(BeaverMapsGraph.class, "buildings", bmg);
assertFalse(buildings.contains(loc), "Location " + loc.id + " in path is a building");
}
}
}
@Tag("A")
@DisplayName("Test Dijkstra")
......@@ -234,6 +307,7 @@ public class BeavermapTests {
"caltech/caltech.buildings.json, caltech/caltech.waypoints.json, caltech/caltech.roads.json, caltech/caltech.paths_trace.json",
"pasadena/pasadena.buildings.json, pasadena/pasadena.waypoints.json, pasadena/pasadena.roads.json, pasadena/pasadena.paths_trace.json",
})
@Order(11)
public void testDijkstraBeaverMap(String buildingsFile, String waypointsFile, String roadsFile, String traceFile) throws FileNotFoundException {
BeaverMapsGraph bmg = new BeaverMapsGraph(
"data/" + buildingsFile, "data/" + waypointsFile, "data/" + roadsFile);
......@@ -252,7 +326,12 @@ public class BeavermapTests {
IDeque<Location> actualPath = bmg.dijkstra(start, target);
List<Long> actualPathIDs = new ArrayList<>();
if (actualPath != null) {
if (expectedPathIDs.size() == 0) {
assertNull(actualPath, "Path does not exist from " + start.id + " to " + target.id + " but a path was found");
}
else {
assertNotNull(actualPath, "Path exists from " + start.id + " to " + target.id + " but a path was not found");
for (Location l : actualPath) {
actualPathIDs.add(l.id);
}
......@@ -260,9 +339,6 @@ public class BeavermapTests {
MatcherAssert.assertThat(actualPathIDs,
IsIterableContainingInOrder.contains(expectedPathIDs.toArray()));
}
else if (expectedPathIDs.size() != 0) {
fail("Found path from " + start.id + " to " + target.id + " where there is none.");
}
}
}
......
......@@ -7,10 +7,7 @@ import edu.caltech.cs2.helpers.Reflection;
import edu.caltech.cs2.interfaces.IDeque;
import edu.caltech.cs2.interfaces.IGraph;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
......@@ -25,13 +22,14 @@ import java.util.Scanner;
import static org.junit.jupiter.api.Assertions.*;
@Tag("A")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DijkstraTest {
@Order(0)
@DisplayName("Loop path should be singleton")
@Test
public void dijkstraShortTripTest() {
IDeque<Location> res = GraphMaker.transformToLocations(GraphMaker.graph4(10)).dijkstra(new Location(1), new Location(1));
IDeque<Location> res = GraphMaker.transformToLocations(GraphMaker.completeGraph(10)).dijkstra(new Location(1), new Location(1));
assertEquals(1, res.size(), "Path from a node to itself should only include the node once");
assertEquals(res.peek(), new Location(1), "Path from a node to itself should only include the node once");
}
......@@ -40,27 +38,27 @@ public class DijkstraTest {
@DisplayName("Disconnected graph should not have a path")
@Test
public void dijkstraDisconnectedTest() {
IDeque<Location> res = GraphMaker.transformToLocations(GraphMaker.graph5(10)).dijkstra(new Location(1), new Location(9));
IDeque<Location> res = GraphMaker.transformToLocations(GraphMaker.disjointCompleteGraphs(10)).dijkstra(new Location(1), new Location(9));
assertNull(res, "Disconnected graph should give null path");
}
@Order(2)
@ParameterizedTest
@ParameterizedTest(name = "Graph: {0}, start: {1}, end: {2}, trace file: {3}")
@DisplayName("Tests correctness of Dijkstra implementation")
@CsvSource({
"graph1, 1, 3, simple_1_3",
"graph2, 0, 1, line_0_1",
"graph2, 0, 5, line_0_5",
"graph2, 0, 20, line_0_20",
"graph2, 0, 99, line_0_99",
"graph2, 1, 0, line_1_0",
"graph3, 0, 1, graph3_0_1",
"graph3, 99, 0, graph3_99_0"
"simpleGraph, 1, 3, simple_1_3",
"linearGraph, 0, 1, line_0_1",
"linearGraph, 0, 5, line_0_5",
"linearGraph, 0, 20, line_0_20",
"linearGraph, 0, 99, line_0_99",
"linearGraph, 1, 0, line_1_0",
"tournamentGraph, 0, 1, graph3_0_1",
"tournamentGraph, 99, 0, graph3_99_0"
})
public void dijkstraTestGraph(String graphName, int start, int end, String traceFile)
throws IllegalAccessException, InvocationTargetException, FileNotFoundException {
BeaverMapsGraph bmg;
if (graphName.equals("graph1")) {
if (graphName.equals("simpleGraph")) {
Method graphGen = Reflection.getMethod(GraphMaker.class, graphName);
bmg = GraphMaker.transformToLocations(
(IGraph<Integer, Integer>) graphGen.invoke(null));
......@@ -77,10 +75,12 @@ public class DijkstraTest {
Scanner s = new Scanner(new File("data/dijkstra/" + traceFile));
if (res == null) {
assertEquals(s.nextLine(), "null", "Path exists but was not found");
String line = s.nextLine();
if (line.equals("null")) {
assertNull(res, "Path does not exist from " + start + " to " + end + " but a path was found");
}
else {
assertNotNull(res, "Path exists from " + start + " to " + end + " but a path was not found");
for (Location l : res) {
if (prev != null) {
pathLen += bmg.adjacent(prev.id, l.id);
......@@ -88,7 +88,7 @@ public class DijkstraTest {
prev = l;
}
double expectedLen = s.nextDouble();
double expectedLen = Double.parseDouble(line);
assertEquals(expectedLen, pathLen, "Path lengths are not equivalent");
}
......@@ -97,7 +97,7 @@ public class DijkstraTest {
@Order(3)
@DisplayName("Tests Dijkstra on random graph and paths")
@Test
public void dijkstraStressTest() throws FileNotFoundException{
public void dijkstraStressTest() throws FileNotFoundException {
final int num_tests = 1000;
IGraph<Integer, Integer> refg = new Graph<Integer, Integer>();
......@@ -119,6 +119,7 @@ public class DijkstraTest {
two = (two + 1) % num_vertices;
int dice = r.nextInt(5);
// TODO: y
if (dice <= 4) {
refg.addEdge(one, two, r.nextInt(100));
} else if (dice <= 5) {
......@@ -131,10 +132,12 @@ public class DijkstraTest {
BeaverMapsGraph bmg = GraphMaker.transformToLocations(refg);
IDeque<Location> res = bmg.dijkstra(new Location(startvertex), new Location(endvertex));
if (res == null) {
assertEquals(s.nextLine(), "null", "Path exists but was not found");
String line = s.nextLine();
if (line.equals("null")) {
assertNull(res, "Path does not exist from " + startvertex + " to " + endvertex + " but a path was found");
}
else {
assertNotNull(res, "Path exists from " + startvertex + " to " + endvertex + " but a path was not found");
double pathLen = 0;
Location prev = null;
for (Location l : res) {
......@@ -144,9 +147,8 @@ public class DijkstraTest {
prev = l;
}
double expectedLen = s.nextDouble();
s.nextLine();
assertEquals(expectedLen, pathLen, "Path lengths are not equivalent");
double expectedLen = Double.parseDouble(line);
assertEquals(expectedLen, pathLen, "Path is not the shortest path by length");
}
}
}
......
......@@ -23,8 +23,11 @@ public class GraphMaker {
return ng;
}
// A very simple graph.
public static IGraph<Integer, Integer> graph1() {
/**
* Generate a simple graph
* @return graph
*/
public static IGraph<Integer, Integer> simpleGraph() {
IGraph<Integer, Integer> g = new Graph<>();
g.addVertex(1);
g.addVertex(2);
......@@ -35,8 +38,12 @@ public class GraphMaker {
return g;
}
// A linear graph on n vertices.
public static IGraph<Integer, Integer> graph2(int n) {
/**
* Generate a directed linear graph
* @param n - number of vertices in the linear graph
* @return graph, with edges labelled with the source vertex
*/
public static IGraph<Integer, Integer> linearGraph(int n) {
IGraph<Integer, Integer> g = new Graph<>();
for (int i = 0; i < n; i++) {
assertTrue(g.addVertex(i), "Adding a new vertex should return true");
......@@ -56,7 +63,7 @@ public class GraphMaker {
}
// Verify that a graph we're given looks reasonably like graph 2
public static void graph2Test(IGraph<Integer, Integer> g, int n) {
public static void verifyLinearGraph(IGraph<Integer, Integer> g, int n) {
vertexTest(g, n);
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1; j++) {
......@@ -69,8 +76,12 @@ public class GraphMaker {
}
}
// A tournament on n vertices; has an edge from i to j iff j<i
public static IGraph<Integer, Integer> graph3(int n) {
/**
* Construct a tournament graph on n vertices; has an edge from i to j iff j<i
* @param n - number of vertices
* @return graph
*/
public static IGraph<Integer, Integer> tournamentGraph(int n) {
IGraph<Integer, Integer> g = new Graph<>();
for (int i = 0; i < n; i++) {
assertTrue(g.addVertex(i), "Adding a new vertex should return true");
......@@ -84,7 +95,7 @@ public class GraphMaker {
}
// Verify that a graph we're given looks reasonably like graph 3
public static void graph3Test(IGraph<Integer, Integer> g, int n) {
public static void verifyTournamentGraph(IGraph<Integer, Integer> g, int n) {
vertexTest(g, n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
......@@ -99,8 +110,12 @@ public class GraphMaker {
}
}
// A complete graph on n vertices
public static IGraph<Integer, Integer> graph4(int n) {
/**
* Construct a complete graph on n vertices
* @param n - number of vertices
* @return graph
*/
public static IGraph<Integer, Integer> completeGraph(int n) {
IGraph<Integer, Integer> g = new Graph<>();
for (int i = 0; i < n; i++) {
assertTrue(g.addVertex(i), "Adding a new vertex should return true");
......@@ -117,7 +132,7 @@ public class GraphMaker {
}
// Verify that a graph we're given looks reasonably like graph 4
public static void graph4Test(IGraph<Integer, Integer> g, int n) {
public static void verifyCompleteGraph(IGraph<Integer, Integer> g, int n) {
vertexTest(g, n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
......@@ -133,8 +148,12 @@ public class GraphMaker {
}
}
// Two disjoint complete graphs.
public static IGraph<Integer, Integer> graph5(int n) {
/**
* Construct a graph that consists of 2 disjoint complete graphs, each with n vertices
* @param n - number of vertices in each complete component
* @return graph
*/
public static IGraph<Integer, Integer> disjointCompleteGraphs(int n) {
IGraph<Integer, Integer> g = new Graph<>();
for (int i = 0; i < n; i++) {
assertTrue(g.addVertex(i), "Adding a new vertex should return true");
......@@ -162,7 +181,7 @@ public class GraphMaker {
}
// Verify that a graph we're given looks reasonably like graph 5
public static void graph5Test(IGraph<Integer, Integer> g, int n) {
public static void verifyDisjointCompleteGraphs(IGraph<Integer, Integer> g, int n) {
vertexTest(g, n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
......@@ -176,22 +195,4 @@ public class GraphMaker {
}
}
}
/* A graph on which Dijkstra's should fail on a search from
* vertex 0 to vertex 2. Optimal path is 0->3->2 (length 1), but Dijkstra's
* should give us 0->1->2 (length 2).
*/
public static IGraph<Integer, Integer> dijkstraFail() {
IGraph<Integer, Integer> g = new Graph<>();
g.addVertex(0);
g.addVertex(1);
g.addVertex(2);
g.addVertex(3);
g.addEdge(0, 1, 1);
g.addEdge(1, 2, 1);
g.addEdge(0, 3, 5);
g.addEdge(3, 2, -4);
return g;
}
}
\ No newline at end of file
......@@ -10,27 +10,40 @@ import edu.caltech.cs2.helpers.Inspection;
import edu.caltech.cs2.interfaces.IGraph;
import org.hamcrest.MatcherAssert;
import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
@Tag("C")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class GraphTests {
private static final String GRAPH_SOURCE = "src/edu/caltech/cs2/datastructures/Graph.java";
private static final int SIMPLE_OP_TIMEOUT_MS = 300;
private static final int MEDIUM_OP_TIMEOUT_MS = 500;
private static final int STRESS_OP_TIMEOUT_MS = 2200;
@Order(0)
@DisplayName("Does not use or import disallowed classes")
@Test
public void testForInvalidClasses() {
List<String> graphDisallow = List.of("java\\.util\\.(?!Iterator)", "java\\.io", "java\\.lang\\.reflect");
List<String> graphDisallow = List.of("java\\.io", "java\\.lang\\.reflect");
Inspection.assertNoImportsOf(GRAPH_SOURCE, graphDisallow);
Inspection.assertNoUsageOf(GRAPH_SOURCE, graphDisallow);
}
@Order(1)
@DisplayName("Does not use or import disallowed classes from java.util")
@Test
public void testForInvalidImportsJavaUtil() {
List<String> allowed = List.of("Iterator");
Inspection.assertNoImportsOfExcept(GRAPH_SOURCE, "java\\.util", allowed);
List<String> bannedUsages = List.of("java\\.util\\.(?!" + String.join("|", allowed) + ")");
Inspection.assertNoUsageOf(GRAPH_SOURCE, bannedUsages);
}
@Order(2)
@DisplayName("Test empty graph")
@Test
public void emptyGraphTest() {
IGraph<String, String> g = new Graph<>();
......@@ -39,9 +52,24 @@ public class GraphTests {
);
}
@Test
@Order(3)
@DisplayName("Test creating various graphs")
public void creationTest() {
assertTimeout(Duration.ofMillis(MEDIUM_OP_TIMEOUT_MS), () -> {
GraphMaker.simpleGraph();
GraphMaker.verifyLinearGraph(GraphMaker.linearGraph(100), 100);
GraphMaker.verifyTournamentGraph(GraphMaker.tournamentGraph(100), 100);
GraphMaker.verifyCompleteGraph(GraphMaker.completeGraph(100), 100);
GraphMaker.verifyDisjointCompleteGraphs(GraphMaker.disjointCompleteGraphs(100), 100);
});
}
/**
* Ensure that we can create a small graph with some edges.
*/
@Order(3)
@DisplayName("Test creating a small directed graph")
@Test
public void secondCreateTest() {
IGraph<String, Integer> g = new Graph<>();
......@@ -69,7 +97,9 @@ public class GraphTests {
});
}
@Order(4)
@Test
@DisplayName("Adding an edge with both endpoints missing should fail")
public void addIllegalEdgeTest() {
// Test adding edge where neither src, dest exists
IGraph<String, Integer> g = new Graph<>();
......@@ -77,6 +107,8 @@ public class GraphTests {
}
@Test
@Order(4)
@DisplayName("Adding an edge with dest missing should fail")
public void addIllegalEdgeTest2() {
// Test adding edge where dest does not exist
IGraph<String, Integer> g = new Graph<>();
......@@ -85,6 +117,8 @@ public class GraphTests {
}
@Test
@Order(4)
@DisplayName("Adding an edge with src missing should fail")
public void addIllegalEdgeTest3() {
// Test adding edge where src does not exist
IGraph<String, Integer> g = new Graph<>();
......@@ -93,6 +127,8 @@ public class GraphTests {
}
@Test
@Order(5)
@DisplayName("Test building a simple graph with edges and failures to add edges")
public void simpleAddTest() {
assertTimeout(Duration.ofMillis(SIMPLE_OP_TIMEOUT_MS), () -> {
IGraph<String, Integer> g = new Graph<>();
......@@ -125,6 +161,8 @@ public class GraphTests {
}
@Test
@Order(6)
@DisplayName("Test removing edges from the graph")
public void simpleRemoveTest() {
assertTimeout(Duration.ofMillis(SIMPLE_OP_TIMEOUT_MS), () -> {
IGraph<String, Integer> g = new Graph<>();
......@@ -139,33 +177,36 @@ public class GraphTests {
}
@Test
public void creationTest() {
assertTimeout(Duration.ofMillis(MEDIUM_OP_TIMEOUT_MS), () -> {
GraphMaker.graph1();
GraphMaker.graph2Test(GraphMaker.graph2(100), 100);
GraphMaker.graph3Test(GraphMaker.graph3(100), 100);
GraphMaker.graph4Test(GraphMaker.graph4(100), 100);
GraphMaker.graph5Test(GraphMaker.graph5(100), 100);
});
}
@Order(7)
@DisplayName("Test removing edges from the graph again")
public void stringEdgeTest() {
assertTimeout(Duration.ofMillis(SIMPLE_OP_TIMEOUT_MS), () -> {
IGraph<Integer, Integer> g = new Graph<>();
/* Make sure that they test .equals, rather than ==, to determine
* if an edge is already there. */
@Test
public void equalsTest() {
IGraph<Integer, Integer> g = new Graph<>();
// Actually prevent the JVM from caching these values.
// This should be tested in the backing DSs anyway...
Integer i1 = 1000;
Integer i2 = 1000;
assertTrue(g.addVertex(i1), "Attempting to add missing vertex returned false");
assertFalse(g.addVertex(i2), "Attempting to add present vertex returned true");
assertTrue(g.addVertex(1), "Should be able to add vertex");
assertTrue(g.addVertex(2), "Should be able to add vertex");
assertTrue(g.addVertex(3), "Should be able to add vertex");
assertTrue(g.addEdge(1, 2, 0), "Should be able to add edge");
assertTrue(g.addEdge(1, 3, 0), "Should be able to add edge");
assertTrue(g.addEdge(2, 3, 0), "Should be able to add edge");
assertNotNull(g.adjacent(1, 2), "Added edge should be present in graph");
assertNotNull(g.adjacent(1, 3), "Added edge should be present in graph");
assertNotNull(g.adjacent(2, 3), "Added edge should be present in graph");
assertTrue(g.removeEdge(1, 2), "Should be able to remove an edge from the graph");
assertFalse(g.removeEdge(1, 2), "Should not be able to remove already-removed edge");
assertTrue(g.removeEdge(1, 3), "Should be able to remove an edge from the graph");
assertFalse(g.removeEdge(1, 3), "Should not be able to remove already-removed edge");
assertTrue(g.removeEdge(2, 3), "Should be able to remove an edge from the graph");
assertFalse(g.removeEdge(2, 3), "Should not be able to remove already-removed edge");
});
}
@Test
@Order(8)
@DisplayName("Test that all edges in a complete graph are present")
public void adjacentStressTest() {
assertTimeout(Duration.ofMillis(STRESS_OP_TIMEOUT_MS), () -> {
IGraph<Integer, Integer> g = GraphMaker.graph4(400);
IGraph<Integer, Integer> g = GraphMaker.completeGraph(400);
for (int i = 0; i < 400; i++)
for (int j = 0; j < 400; j++)
if (i != j)
......@@ -174,39 +215,49 @@ public class GraphTests {
}
@Test
@Order(9)
@DisplayName("Test that directed edges in a tournament graph are correct")
public void testNeighbors() {
assertTimeout(Duration.ofMillis(SIMPLE_OP_TIMEOUT_MS), () -> {
IGraph<Integer, Integer> g = GraphMaker.graph3(10);
IGraph<Integer, Integer> g = GraphMaker.tournamentGraph(10);
Set<Integer> vertices = new HashSet<Integer>();
for (int i = 0; i < 10; i++) {
MatcherAssert.assertThat(g.neighbors(i),
IsIterableContainingInAnyOrder.containsInAnyOrder(vertices.toArray()));
MatcherAssert.assertThat("neighbors", g.neighbors(i),
IsIterableContainingInAnyOrder.containsInAnyOrder(vertices.toArray()));
// In a tournament graph, vertices have edges to smaller ones
vertices.add(i);
}
});
}
@Test
public void stringEdgeTest() {
@Order(10)
@DisplayName("Test adding undirected edges")
public void undirectedEdgeTest() {
assertTimeout(Duration.ofMillis(SIMPLE_OP_TIMEOUT_MS), () -> {
IGraph<Integer, Integer> g = new Graph<>();
IGraph<String, Integer> g = new Graph<>();
assertTrue(g.addVertex(1), "Should be able to add vertex");
assertTrue(g.addVertex(2), "Should be able to add vertex");
assertTrue(g.addVertex(3), "Should be able to add vertex");
assertTrue(g.addEdge(1, 2, 0), "Should be able to add edge");
assertTrue(g.addEdge(1, 3, 0), "Should be able to add edge");
assertTrue(g.addEdge(2, 3, 0), "Should be able to add edge");
assertNotNull(g.adjacent(1, 2), "Added edge should be present in graph");
assertNotNull(g.adjacent(1, 3), "Added edge should be present in graph");
assertNotNull(g.adjacent(2, 3), "Added edge should be present in graph");
assertTrue(g.removeEdge(1, 2), "Should be able to remove an edge from the graph");
assertFalse(g.removeEdge(1, 2), "Should not be able to remove already-removed edge");
assertTrue(g.removeEdge(1, 3), "Should be able to remove an edge from the graph");
assertFalse(g.removeEdge(1, 3), "Should not be able to remove already-removed edge");
assertTrue(g.removeEdge(2, 3), "Should be able to remove an edge from the graph");
assertFalse(g.removeEdge(2, 3), "Should not be able to remove already-removed edge");
assertTrue(g.addVertex("1"), "Should be able to add a vertex");
assertEquals(1, g.vertices().size(), "Should have correct number of vertices");
assertTrue(g.addVertex("2"), "Should be able to add a vertex");
assertEquals(2, g.vertices().size(), "Should have correct number of vertices");
assertTrue(g.addVertex("3"), "Should be able to add a vertex");
assertEquals(3, g.vertices().size(), "Should have correct number of vertices");
assertTrue(g.addUndirectedEdge("1", "2", 1), "Adding undirected edge when neither direction exists should return true");
assertFalse(g.addEdge("1", "2", 1), "Should not be able to add directed edge");
assertFalse(g.addEdge("2", "1", 1), "Should not be able to add directed edge");
assertTrue(g.addEdge("1", "3", 1), "Should be able to add new edge");
assertFalse(g.addUndirectedEdge("3", "1", 2), "Adding an undirected edge when one direction exists should return false");
assertEquals(g.adjacent("3", "1"), 2, "Adding an undirected edge when one direction exists should update existing edges");
assertEquals(g.adjacent("1", "3"), 2, "Adding an undirected edge when one direction exists should update existing edges");
assertTrue(g.addEdge("2", "3", 1), "Should be able to add new directed edge");
assertFalse(g.addUndirectedEdge("2", "3", 2), "Adding an undirected edge when one direction exists should return false");
assertEquals(g.adjacent("3", "2"), 2, "Adding an undirected edge when one direction exists should update existing edges");
assertEquals(g.adjacent("2", "3"), 2, "Adding an undirected edge when one direction exists should update existing edges");
});
}
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
@Tag("C")
@DisplayName("Test graph of shared appearances in Marvel comic books (MarvelTests)")
public class MarvelTests {
private static Graph<String, Integer> MARVEL_GRAPH;
private static final int COMMON_TIMEOUT_MS = 1500;
......@@ -52,6 +53,7 @@ public class MarvelTests {
@Test
@Order(0)
@DisplayName("Test that characters with no co-appearances do not have neighbors - neighbor isEmpty")
public void testSingletons() {
List<String> singletons = new ArrayList<>();
for (String c : MARVEL_GRAPH.vertices()) {
......@@ -64,6 +66,7 @@ public class MarvelTests {
@Test
@Order(1)
@DisplayName("Look for characters that appear with many other characters - neighbor size")
public void testPopular() {
List<String> populars = new ArrayList<>();
for (String c : MARVEL_GRAPH.vertices()) {
......@@ -76,6 +79,7 @@ public class MarvelTests {
@Test
@Order(2)
@DisplayName("Test that there are no loops (self-edges) in the graph")
public void testNoLoops() {
for (String c : MARVEL_GRAPH.vertices()) {
assertNull(MARVEL_GRAPH.adjacent(c, c),
......@@ -87,6 +91,7 @@ public class MarvelTests {
// THIS TEST MUST BE RUN LAST - IT HAS SIDE EFFECTS ON THE STATIC GRAPH
@Test
@Order(3)
@DisplayName("Look for co-appearances that appear in many issues together - edge weights")
public void testCommon() {
Assertions.assertTimeout(Duration.ofMillis(COMMON_TIMEOUT_MS), this::runCommon);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment