Commit b2b8d83f authored by Adam Blank's avatar Adam Blank
Browse files

Merge branch 'second_half_test_updates' into 'master'

Second half test updates

See merge request !1
1 merge request!1Second half test updates
Pipeline #77194 failed with stage
in 0 seconds
Showing with 849 additions and 598 deletions
+849 -598
static: static:
script: /testers/cs2/project05/static/test script: /testers/cs2/project05/static/test
"A+":
script: /testers/cs2/project05/suite/test
A: A:
script: /testers/cs2/project05/suite/test script: /testers/cs2/project05/suite/test
B: B:
......
File added
<component name="libraryTable">
<library name="org.javassist:javassist:3.29.2-GA" type="repository">
<properties maven-id="org.javassist:javassist:3.29.2-GA" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.29.2-GA/javassist-3.29.2-GA.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
\ No newline at end of file
<component name="libraryTable"> <component name="libraryTable">
<library name="org.junit.jupiter:junit-jupiter:5.6.0-M1" type="repository"> <library name="org.junit.jupiter:junit-jupiter:5.6.0-M1j" type="repository">
<properties maven-id="org.junit.jupiter:junit-jupiter:5.6.0-M1" /> <properties maven-id="org.junit.jupiter:junit-jupiter:5.9.2" />
<CLASSES> <CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.6.0-M1/junit-jupiter-5.6.0-M1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.9.2/junit-jupiter-5.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.0-M1/junit-jupiter-api-5.6.0-M1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.9.2/junit-jupiter-api-5.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.0-M1/junit-platform-commons-1.6.0-M1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.9.2/junit-platform-commons-1.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.6.0-M1/junit-jupiter-params-5.6.0-M1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.6.0-M1/junit-jupiter-engine-5.6.0-M1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.9.2/junit-jupiter-params-5.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.0-M1/junit-platform-engine-1.6.0-M1.jar!/" /> <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.9.2/junit-jupiter-engine-5.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.9.2/junit-platform-engine-1.9.2.jar!/" />
</CLASSES> </CLASSES>
<JAVADOC /> <JAVADOC />
<SOURCES /> <SOURCES />
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<component name="PreferredVcsStorage"> <component name="PreferredVcsStorage">
<preferredVcsName>ApexVCS</preferredVcsName> <preferredVcsName>ApexVCS</preferredVcsName>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_15" project-jdk-name="19" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
\ No newline at end of file
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="A Tests" type="JUnit" factoryName="JUnit"> <configuration default="false" name="A Tests" type="JUnit" factoryName="JUnit">
<module name="project05-markov" /> <module name="project05-markov" />
<option name="ALTERNATIVE_JRE_PATH" value="17" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" /> <option name="TEST_OBJECT" value="tags" />
<option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" />
<option name="PARAMETERS" value="" /> <option name="PARAMETERS" value="" />
<option name="TEST_SEARCH_SCOPE"> <option name="TEST_SEARCH_SCOPE">
<value defaultName="wholeProject" /> <value defaultName="wholeProject" />
......
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="A+ Tests" type="JUnit" factoryName="JUnit">
<module name="project05-markov" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" />
<option name="PARAMETERS" value="" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="wholeProject" />
</option>
<tag value="A+" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="B Tests" type="JUnit" factoryName="JUnit"> <configuration default="false" name="B Tests" type="JUnit" factoryName="JUnit">
<module name="project05-markov" /> <module name="project05-markov" />
<option name="ALTERNATIVE_JRE_PATH" value="17" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="tags" /> <option name="TEST_OBJECT" value="tags" />
<option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" />
<option name="PARAMETERS" value="" /> <option name="PARAMETERS" value="" />
<option name="TEST_SEARCH_SCOPE"> <option name="TEST_SEARCH_SCOPE">
<value defaultName="wholeProject" /> <value defaultName="wholeProject" />
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.datastructures.ChainingHashDictionaryTests" /> <option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.datastructures.ChainingHashDictionaryTests" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" /> <option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.datastructures.MoveToFrontDictionaryTests" /> <option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.datastructures.MoveToFrontDictionaryTests" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" /> <option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.datastructures.NGramTests" /> <option name="MAIN_CLASS_NAME" value="edu.caltech.cs2.datastructures.NGramTests" />
<option name="METHOD_NAME" value="" /> <option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" /> <option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" />
<method v="2"> <method v="2">
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="org.hamcrest:hamcrest-all:1.3" level="project" />
<orderEntry type="library" name="org.hamcrest:hamcrest:2.2" level="project" /> <orderEntry type="library" name="org.hamcrest:hamcrest:2.2" level="project" />
<orderEntry type="library" name="org.hamcrest:hamcrest-core:2.2" level="project" /> <orderEntry type="library" name="org.hamcrest:hamcrest-core:2.2" level="project" />
<orderEntry type="library" name="org.junit.jupiter:junit-jupiter:5.6.0-M1" level="project" /> <orderEntry type="library" name="org.junit.jupiter:junit-jupiter:5.6.0-M1j" level="project" />
<orderEntry type="library" name="com.github.javaparser:javaparser-core:3.5.12" level="project" /> <orderEntry type="library" name="com.github.javaparser:javaparser-core:3.5.12" level="project" />
<orderEntry type="library" name="org.javassist:javassist:3.29.2-GA" level="project" />
</component> </component>
</module> </module>
\ No newline at end of file
package edu.caltech.cs2.datastructures;
public class AVLTreeDictionary<K extends Comparable<? super K>, V>
extends BSTDictionary<K, V> {
/**
* A subclass of BSTNode representing a node in the AVLTree
*/
private static class AVLNode<K, V> extends BSTNode<K, V> {
public int height;
/**
* Constructor invokes the BSTNode constructor and initializes the height
*/
public AVLNode(K key, V value, int height) {
super(key, value);
this.height = height;
}
}
/**
* Overrides the remove method in BST
*
* @param key
* @return The value of the removed BSTNode if it exists, null otherwise
*/
@Override
public V remove(K key) {
throw new UnsupportedOperationException();
}
/**
* Overrides the put method in BST to create AVLNode<K, V> instances
*
* @param key
* @param value
* @return the previous value corresponding to key in the AVL tree
*/
@Override
public V put(K key, V value) {
return null;
}
@Override
public String toString() {
return super.toString();
}
}
...@@ -9,13 +9,13 @@ import java.util.Iterator; ...@@ -9,13 +9,13 @@ import java.util.Iterator;
public class BSTDictionary<K extends Comparable<? super K>, V> public class BSTDictionary<K extends Comparable<? super K>, V>
implements IDictionary<K, V> { implements IDictionary<K, V> {
protected BSTNode<K, V> root; private BSTNode<K, V> root;
protected int size; private int size;
/** /**
* Class representing an individual node in the Binary Search Tree * Class representing an individual node in the Binary Search Tree
*/ */
protected static class BSTNode<K, V> { private static class BSTNode<K, V> {
public final K key; public final K key;
public V value; public V value;
...@@ -83,7 +83,7 @@ public class BSTDictionary<K extends Comparable<? super K>, V> ...@@ -83,7 +83,7 @@ public class BSTDictionary<K extends Comparable<? super K>, V>
} }
/** /**
* @return number of nodes in the BST * @return number of key/value pairs in the BST
*/ */
@Override @Override
public int size() { public int size() {
......
package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.Inspection;
import edu.caltech.cs2.helpers.Reflection;
import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.IDictionary;
import org.junit.jupiter.api.*;
import java.util.List;
import java.util.Map;
import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel;
import static org.junit.jupiter.api.Assertions.assertEquals;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("A+")
// Yes, strictly this should extend BSTDictionary. I don't have the time to reason through it at the moment, though.
public class AVLTreeDictionaryTests implements IDictionaryNGramTests {
private static String DICTIONARY_SOURCE =
"src/edu/caltech/cs2/datastructures/AVLTreeDictionary.java";
@Override
public IDictionary<Object, Object> newIDictionary() {
return (IDictionary<Object, Object>) (IDictionary<? extends Object, Object>) newAVLTreeDictionary();
}
public AVLTreeDictionary<Comparable<Object>, Object> newAVLTreeDictionary() {
return new AVLTreeDictionary<>();
}
@Override
public int SINGLE_OP_TIMEOUT_MS() {
return 60;
}
@Override
public int CONTAINS_VALUE_TIMEOUT_MS() {
return 120;
}
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexity() {
return RuntimeInstrumentation.ComplexityType.LOGARITHMIC;
}
@Order(0)
@DisplayName("Does not use or import disallowed classes")
@Test
public void testForInvalidClasses() {
List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io");
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps);
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, regexps);
}
@Order(classSpecificTestLevel)
@DisplayName("Does not use or import disallowed classes from java.util")
@Test
public void testForInvalidImportsJavaUtil() {
List<String> allowed = List.of("Iterator");
Inspection.assertNoImportsOfExcept(DICTIONARY_SOURCE, "java\\.util", allowed);
List<String> bannedUsages = List.of("java\\.util\\.(?!" + String.join("|", allowed) + ")");
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, bannedUsages);
}
@Order(0)
@DisplayName("There are no public fields")
@Test
public void testNoPublicFields() {
Reflection.assertNoPublicFields(AVLTreeDictionary.class);
}
@Order(1)
@DisplayName("The AVL tree self-balances")
@Test
public void testBalance() {
AVLTreeDictionary<String, Integer> avl = new AVLTreeDictionary<>();
// Left rotation
avl.put("m", 1);
avl.put("s", 2);
avl.put("x", 3);
assertEquals("{s: 2, m: 1, x: 3}", avl.toString());
// Right rotation
avl.put("i", 4);
avl.put("a", 5);
assertEquals("{s: 2, i: 4, x: 3, a: 5, m: 1}",
avl.toString());
// Left-right rotation
avl.put("p", 6);
assertEquals("{m: 1, i: 4, s: 2, a: 5, p: 6, x: 3}",
avl.toString());
// Right-left rotation
avl.put("u", 7);
avl.put("y", 8);
avl.put("t", 9);
assertEquals("{m: 1, i: 4, u: 7, a: 5, s: 2, x: 3, p: 6, t: 9, y: 8}",
avl.toString());
}
// Do not test AVL remove
@Override
public void smokeTestIDictionaryRemove(Map<Object, Object> base) {}
@Override
public void stressTestIDictionaryRemove(int seed, int size) {}
}
\ No newline at end of file
package edu.caltech.cs2.datastructures; package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.Inspection; import edu.caltech.cs2.helpers.*;
import edu.caltech.cs2.helpers.Reflection;
import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.IDictionary; import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.interfaces.IStyleTests;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
import static edu.caltech.cs2.project05.Project05TestOrdering.*; import static edu.caltech.cs2.project05.Project05TestOrdering.*;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Order(2)
@Tag("A") @Tag("A")
public class BSTDictionaryTests implements IDictionaryNGramTests { public class BSTDictionaryTests extends IDictionaryNGramTests {
@Override @Override
public int SINGLE_OP_TIMEOUT_MS() { public int SINGLE_OP_TIMEOUT_MS() {
return 50; return 50;
...@@ -29,6 +31,16 @@ public class BSTDictionaryTests implements IDictionaryNGramTests { ...@@ -29,6 +31,16 @@ public class BSTDictionaryTests implements IDictionaryNGramTests {
return RuntimeInstrumentation.ComplexityType.LOGARITHMIC; return RuntimeInstrumentation.ComplexityType.LOGARITHMIC;
} }
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexityWorst() {
return RuntimeInstrumentation.ComplexityType.LINEAR;
}
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexityBest() {
return RuntimeInstrumentation.ComplexityType.CONSTANT;
}
@Override @Override
public IDictionary<Object, Object> newIDictionary() { public IDictionary<Object, Object> newIDictionary() {
return (IDictionary<Object, Object>) (IDictionary<? extends Object, Object>) newBSTDictionary(); return (IDictionary<Object, Object>) (IDictionary<? extends Object, Object>) newBSTDictionary();
...@@ -41,51 +53,137 @@ public class BSTDictionaryTests implements IDictionaryNGramTests { ...@@ -41,51 +53,137 @@ public class BSTDictionaryTests implements IDictionaryNGramTests {
private static String DICTIONARY_SOURCE = private static String DICTIONARY_SOURCE =
"src/edu/caltech/cs2/datastructures/BSTDictionary.java"; "src/edu/caltech/cs2/datastructures/BSTDictionary.java";
@Order(classSpecificTestLevel) @DisplayName("Style")
@DisplayName("Does not use or import disallowed classes") @Nested
@Test @Order(100)
public void testForInvalidClasses() { @Tag("A")
List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io"); class StyleTests implements IStyleTests {
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps); public void testNoProtectedFields() {
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, regexps); }
@Override
public String getSource() {
return DICTIONARY_SOURCE;
}
@Override
public Class<?> getClazz() {
return BSTDictionary.class;
}
@Override
public List<String> getPublicInterface() {
return List.of("containsKey", "containsValue", "get", "iterator", "keys", "put", "remove", "size", "values", "toString");
}
@Override
public int getMaxFields() {
return 3;
}
@Override
public List<String> methodsToBanSelf() {
return List.of("put", "get", "remove", "keys", "values");
}
} }
@Order(classSpecificTestLevel) @DisplayName("Implementation")
@DisplayName("Does not use or import disallowed classes from java.util") @Nested
@Test @Order(0)
public void testForInvalidImportsJavaUtil() { @Tag("A")
List<String> allowed = List.of("Iterator"); class ImplementationTests {
Inspection.assertNoImportsOfExcept(DICTIONARY_SOURCE, "java\\.util", allowed); @Order(classSpecificTestLevel)
@DisplayName("Check for excessive node allocation in put")
List<String> bannedUsages = List.of("java\\.util\\.(?!" + String.join("|", allowed) + ")"); @Test
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, bannedUsages); @TestDescription("This test is checking that you are not allocating extra nodes in put that are not necessary.")
} @DependsOn({"put"})
public void testForExcessiveNodeAllocationPut() {
NewNode.NUM_CALLS = 0;
@Order(classSpecificTestLevel) BSTDictionary<Integer, Integer> impl = new BSTDictionary<>();
@DisplayName("There are no public fields")
@Test for (int j = 0; j < 100; j++) {
public void testNoPublicFields() { int before = NewNode.NUM_CALLS;
Reflection.assertNoPublicFields(BSTDictionary.class); impl.put(j, j);
} int after = NewNode.NUM_CALLS;
assertTrue(before + 1 >= after, "Each put() should create at most one new node");
@Test }
@Order(sanityTestLevel) }
@DisplayName("Test nonbalancing BST Implementation")
public void testActualBST() { @Order(classSpecificTestLevel)
BSTDictionary<String, Integer> bst = new BSTDictionary<>(); @DisplayName("Check for excessive node allocation in get")
@Test
bst.put("m", 1); @TestDescription("This test is checking that you are not allocating extra nodes in get that are not necessary.")
bst.put("s", 2); @DependsOn({"put", "get"})
bst.put("x", 3); @TestHint("get should not be affecting the dictionary in any way, it should simply find and return a value.")
bst.put("i", 4); public void testForExcessiveNodeAllocationGet() {
bst.put("a", 5); NewNode.NUM_CALLS = 0;
bst.put("p", 6); BSTDictionary<Integer, Integer> impl = new BSTDictionary<>();
bst.put("u", 7);
bst.put("y", 8); for (int j = 0; j < 100; j++) {
bst.put("t", 9); impl.put(j, j);
}
assertEquals("{m: 1, i: 4, s: 2, a: 5, p: 6, x: 3, u: 7, y: 8, t: 9}", bst.toString(), "Incorrect binary search tree implementation");
for (int j = 0; j < 100; j++) {
int before = NewNode.NUM_CALLS;
impl.get(j);
int after = NewNode.NUM_CALLS;
assertEquals(after, before, "get() should not allocate any new node");
}
}
@Order(classSpecificTestLevel)
@DisplayName("Check for excessive Node allocation in remove")
@Test
@TestDescription("This test is checking that you are not allocating extra nodes in remove that are not necessary.")
@DependsOn({"put", "remove"})
public void testForExcessiveNodeAllocationRemove() {
NewObjectArray.NUM_CALLS = 0;
BSTDictionary<Integer, Integer> impl = new BSTDictionary<>();
for (int j = 0; j < 100; j++) {
impl.put(j, j);
}
for (int j = 0; j < 100; j++) {
int before = NewNode.NUM_CALLS;
impl.remove(j);
int after = NewNode.NUM_CALLS;
assertEquals(after, before, "remove() should not allocate any new node");
}
}
/*
TODO: this test does not work to the confidence level Adam is happy with.
pls fix next year.
@Order(classSpecificTestLevel)
@DisplayName("Check that remove() is implemented recursively")
@Test
public void testForRecursiveRemove() {
Method solveMethod = Reflection.getMethod(BSTDictionary.class, "remove", Object.class);
Inspection.assertRecursive(DICTIONARY_SOURCE, solveMethod);
}
*/
@Test
@Order(classSpecificTestLevel)
@DisplayName("Test BST Implementation")
@TestDescription("This test is testing sample keys and values to make sure the BST stores the values correctly.")
@DependsOn({"put", "toString"})
public void testActualBST() {
BSTDictionary<String, Integer> bst = new BSTDictionary<>();
bst.put("m", 1);
bst.put("s", 2);
bst.put("x", 3);
bst.put("i", 4);
bst.put("a", 5);
bst.put("p", 6);
bst.put("u", 7);
bst.put("y", 8);
bst.put("t", 9);
assertEquals("{m: 1, i: 4, s: 2, a: 5, p: 6, x: 3, u: 7, y: 8, t: 9}", bst.toString(), "Incorrect binary search tree implementation");
}
} }
} }
package edu.caltech.cs2.datastructures; package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.Inspection; import edu.caltech.cs2.helpers.*;
import edu.caltech.cs2.helpers.Reflection;
import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.IDictionary; import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.interfaces.IStyleTests;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -15,11 +14,13 @@ import java.util.function.Supplier; ...@@ -15,11 +14,13 @@ import java.util.function.Supplier;
import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel; import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel;
import static edu.caltech.cs2.project05.Project05TestOrdering.specialTestLevel; import static edu.caltech.cs2.project05.Project05TestOrdering.specialTestLevel;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestClassOrder(ClassOrderer.OrderAnnotation.class)
@Tag("B") @Tag("B")
public class ChainingHashDictionaryTests implements IDictionaryNGramTests { @Order(1)
public class ChainingHashDictionaryTests extends IDictionaryNGramTests {
private static String DICTIONARY_SOURCE = "src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java"; private static String DICTIONARY_SOURCE = "src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java";
public Counter supplierCounter = new Counter(); public Counter supplierCounter = new Counter();
...@@ -39,6 +40,16 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -39,6 +40,16 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
return RuntimeInstrumentation.ComplexityType.CONSTANT; return RuntimeInstrumentation.ComplexityType.CONSTANT;
} }
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexityWorst() {
return RuntimeInstrumentation.ComplexityType.CONSTANT;
}
@Override
public RuntimeInstrumentation.ComplexityType getAndPutComplexityBest() {
return RuntimeInstrumentation.ComplexityType.CONSTANT;
}
@Override @Override
public IDictionary<Object, Object> newIDictionary() { public IDictionary<Object, Object> newIDictionary() {
Supplier<IDictionary<Object, Object>> sup = () -> { Supplier<IDictionary<Object, Object>> sup = () -> {
...@@ -48,102 +59,170 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -48,102 +59,170 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
return new ChainingHashDictionary<>(sup); return new ChainingHashDictionary<>(sup);
} }
@Order(classSpecificTestLevel) @DisplayName("Style")
@DisplayName("Does not use or import disallowed classes") @Nested
@Test @Order(100)
public void testForInvalidClasses() { @Tag("C")
List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io"); class StyleTests implements IStyleTests {
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps); @Override
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, regexps); public String getSource() {
} return DICTIONARY_SOURCE;
}
@Order(classSpecificTestLevel) @Override
@DisplayName("Does not use or import disallowed classes from java.util") public Class<?> getClazz() {
@Test return ChainingHashDictionary.class;
public void testForInvalidImportsJavaUtil() { }
List<String> allowed = List.of("Iterator", "function\\.Supplier", "stream\\.Stream");
Inspection.assertNoImportsOfExcept(DICTIONARY_SOURCE, "java\\.util", allowed);
List<String> bannedUsages = List.of("java\\.util\\.(?!" + String.join("|", allowed) + ")"); @Override
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, bannedUsages); public List<String> getPublicInterface() {
} return List.of("containsKey", "containsValue", "get", "iterator", "keys", "put", "remove", "size", "values", "toString");
}
@Order(classSpecificTestLevel) @Override
@DisplayName("There are no public fields") public int getMaxFields() {
@Test return 5;
public void testNoPublicFieldsCHD() { }
Reflection.assertNoPublicFields(ChainingHashDictionary.class);
}
@Order(classSpecificTestLevel) @Override
@DisplayName("There are no protected fields") public List<String> methodsToBanSelf() {
@Test return List.of("put", "get", "remove", "keys", "values");
public void testNoProtectedFieldsCHD() { }
Reflection.assertNoProtectedFields(ChainingHashDictionary.class);
} }
@Order(classSpecificTestLevel) @DisplayName("Implementation")
@DisplayName("All fields in ChainingHashDictionary have modifiers") @Nested
@Test @Order(0)
public void testFieldModifiersCHD() { @Tag("C")
Reflection.assertFieldModifiers(ChainingHashDictionary.class); class ImplementationTests {
} @Order(classSpecificTestLevel)
@DisplayName("Check for excessive array allocation in put")
@TestDescription("This test checks that you are not allocating extra arrays in put that are not necessary.")
@DependsOn({"put"})
@TestHint("You should be creating a new array only if you need to rehash to maintain the load factor")
public void testForExcessiveArrayAllocationPut() {
NewObjectArray.NUM_CALLS = 0;
IDictionary<Object, Object> dict = new ChainingHashDictionary<>(MoveToFrontDictionary::new);
for (int j = 0; j < 100; j++) {
int before = NewObjectArray.NUM_CALLS;
dict.put(j, j);
int after = NewObjectArray.NUM_CALLS;
assertTrue(before + 1 >= after, "Each put() should create at most one new array");
}
}
@Order(specialTestLevel) @Order(classSpecificTestLevel)
@DisplayName("Test that resizing and initializing is fast") @DisplayName("Check for excessive array allocation in get")
@Timeout(value = 20, unit = SECONDS) @Test
@Test @TestDescription("This test checks that you are not allocating any extra arrays in get that are not necessary.")
public void testGrowthCapability() { @DependsOn({"put", "get"})
for (int i = 0; i < 25; i++) { @TestHint("get should not be affecting the dictionary in any way, it should simply find and return a value.")
public void testForExcessiveArrayAllocationGet() {
NewObjectArray.NUM_CALLS = 0;
IDictionary<Object, Object> dict = new ChainingHashDictionary<>(MoveToFrontDictionary::new); IDictionary<Object, Object> dict = new ChainingHashDictionary<>(MoveToFrontDictionary::new);
for (int j = 0; j < 500000; j++) {
for (int j = 0; j < 100; j++) {
dict.put(j, j); dict.put(j, j);
} }
// This _should_ get GC'd with a smaller heap size...
for (int j = 0; j < 100; j++) {
int before = NewObjectArray.NUM_CALLS;
dict.get(j);
int after = NewObjectArray.NUM_CALLS;
assertEquals(after, before, "get() should not allocate any new array");
}
} }
}
@Order(specialTestLevel) @Order(classSpecificTestLevel)
@DisplayName("Test that hash map is resized appropriately") @DisplayName("Check for excessive array allocation in remove")
@Test @Test
public void testResize() { @TestDescription("This test checks that you are not allocating any extra arrays in get that are not necessary.")
List<Counter> values = new ArrayList<>(); @DependsOn({"put", "remove"})
IDictionary<Object, Object> impl = newIDictionary(); public void testForExcessiveArrayAllocationRemove() {
for (int i = 0; i < 100000; i++) { NewObjectArray.NUM_CALLS = 0;
Counter c = new Counter(i); IDictionary<Object, Object> dict = new ChainingHashDictionary<>(MoveToFrontDictionary::new);
impl.put(c, i);
// Insert newer counters at front so number of touches is ascending
values.add(0, c);
}
// All counters should have been touched at least once
int currMaxTouches = 1;
for (Counter c : values) {
assertTrue(c.touches >= currMaxTouches, "Key was not hashed enough times when rebuilding dictionary");
currMaxTouches = Math.max(c.touches, currMaxTouches);
}
assertTrue(currMaxTouches > 10, "Dictionary was not resized enough times when building");
// Compare to 2 - last insertion could have triggered resize (unlikely, but
// might as well - important part
// is ensuring an upper bound.
assertTrue(values.get(0).touches <= 2, "Most recent key inserted was hashed too many times");
}
@Order(specialTestLevel) for (int j = 0; j < 100; j++) {
@DisplayName("Test that supplier function is used in resize") dict.put(j, j);
@Test }
public void testSupplierUsage() {
// Reset from any previous tests
this.supplierCounter.resetCounter();
IDictionary<Object, Object> impl = newIDictionary();
for (int i = 0; i < 100000; i++) { for (int j = 0; j < 100; j++) {
impl.put(i, i); int before = NewObjectArray.NUM_CALLS;
dict.remove(j);
int after = NewObjectArray.NUM_CALLS;
assertEquals(after, before, "remove() should not allocate any new array");
}
} }
// Number of resized was arbitrarily chosen, but this *should* be fine?
assertTrue(this.supplierCounter.touches > 50000, "Supplier was not used enough during resizes"); @Order(specialTestLevel)
@DisplayName("Test that resizing and initializing is fast")
@Timeout(value = 20, unit = SECONDS)
@Test
@TestDescription("This test checks that your ChainingHashDictionary is able to rehash and create buckets quickly.")
@TestHint("Make sure you are hardcoding primes into your structure and not regenerating the list at every rehash. Make sure resizing" +
" is only done when the load factor is greater than 1. Lastly, go back to NGram and make sure you compare strings and not" +
" characters.")
@DependsOn({"put"})
public void testGrowthCapability() {
for (int i = 0; i < 25; i++) {
IDictionary<Object, Object> dict = new ChainingHashDictionary<>(MoveToFrontDictionary::new);
for (int j = 0; j < 500000; j++) {
dict.put(j, j);
}
// This _should_ get GC'd with a smaller heap size...
}
}
@Order(specialTestLevel)
@DisplayName("Test that hash map is resized appropriately")
@Test
@TestDescription("This test checks that you are not resizing the dictionary too much or too little.")
@DependsOn({"put"})
@TestHint("Make sure that you are resizing if and only if the load factor is greater than 1.")
public void testResize() {
List<Counter> values = new ArrayList<>();
IDictionary<Object, Object> impl = newIDictionary();
for (int i = 0; i < 100000; i++) {
Counter c = new Counter(i);
impl.put(c, i);
// Insert newer counters at front so number of touches is ascending
values.add(0, c);
}
// All counters should have been touched at least once
int currMaxTouches = 1;
for (Counter c : values) {
assertTrue(c.touches >= currMaxTouches, "Key was not hashed enough times when rebuilding dictionary");
currMaxTouches = Math.max(c.touches, currMaxTouches);
}
assertTrue(currMaxTouches > 10, "Dictionary was not resized enough times when building");
// Compare to 2 - last insertion could have triggered resize (unlikely, but
// might as well - important part
// is ensuring an upper bound.
assertTrue(values.get(0).touches <= 2, "Most recent key inserted was hashed too many times");
}
@Order(specialTestLevel)
@DisplayName("Test that supplier function is used in resize")
@Test
@TestDescription("This test checks that you use the provided supplier function when resizing the dictionary.")
@TestHint("Make sure you are initializing all the buckets of the resized dictionary appropriately with the supplier function. Do not use streams.")
@DependsOn({"put", "remove"})
public void testSupplierUsage() {
// Reset from any previous tests
supplierCounter.resetCounter();
IDictionary<Object, Object> impl = newIDictionary();
for (int i = 0; i < 100000; i++) {
impl.put(i, i);
}
// Number of resized was arbitrarily chosen, but this *should* be fine?
assertTrue(supplierCounter.touches > 50000, "Supplier was not used enough during resizes");
}
} }
private static class Counter { private static class Counter {
......
...@@ -3,15 +3,13 @@ package edu.caltech.cs2.datastructures; ...@@ -3,15 +3,13 @@ package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.RuntimeInstrumentation; import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.IDictionary; import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.textgenerator.NGram; import edu.caltech.cs2.textgenerator.NGram;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
...@@ -22,10 +20,11 @@ import static java.util.concurrent.TimeUnit.SECONDS; ...@@ -22,10 +20,11 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
// Wrapper class for all IDictionaries that will be tested using NGram keys to reduce code repetition // Wrapper class for all IDictionaries that will be tested using NGram keys to reduce code repetition
public interface IDictionaryNGramTests extends IDictionaryTests { @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public abstract class IDictionaryNGramTests extends IDictionaryTests {
@Override @Override
default Stream<Arguments> iDictionarySmokeDataSource() { public Stream<Arguments> iDictionarySmokeDataSource() {
return Stream.of( return Stream.of(
Arguments.of(createReferenceMap(new String[] { "a", "ab", "abc", "abcd", "abcde" }, Arguments.of(createReferenceMap(new String[] { "a", "ab", "abc", "abcd", "abcde" },
new Integer[] { 1, 2, 3, 4, 5 })), new Integer[] { 1, 2, 3, 4, 5 })),
...@@ -39,7 +38,7 @@ public interface IDictionaryNGramTests extends IDictionaryTests { ...@@ -39,7 +38,7 @@ public interface IDictionaryNGramTests extends IDictionaryTests {
} }
@Override @Override
default Map<Object, Object> createReferenceMap(String[] keys, Object[] vals) { public Map<Object, Object> createReferenceMap(String[] keys, Object[] vals) {
Map<Object, Object> ref = new HashMap<>(); Map<Object, Object> ref = new HashMap<>();
for (int i = 0; i < keys.length; i++) { for (int i = 0; i < keys.length; i++) {
ref.put(NGramTests.stringToNGram(keys[i]), vals[i]); ref.put(NGramTests.stringToNGram(keys[i]), vals[i]);
...@@ -48,7 +47,7 @@ public interface IDictionaryNGramTests extends IDictionaryTests { ...@@ -48,7 +47,7 @@ public interface IDictionaryNGramTests extends IDictionaryTests {
} }
@Override @Override
default Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength, public Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength,
int maxKeyLength) { int maxKeyLength) {
Map<Object, Object> base = new HashMap<>(); Map<Object, Object> base = new HashMap<>();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
...@@ -65,7 +64,7 @@ public interface IDictionaryNGramTests extends IDictionaryTests { ...@@ -65,7 +64,7 @@ public interface IDictionaryNGramTests extends IDictionaryTests {
@Order(sanityTestLevel) @Order(sanityTestLevel)
@DisplayName("Test that keys are not compared using reference equality") @DisplayName("Test that keys are not compared using reference equality")
@Test @Test
default void testNoReferenceEquality() { public void testNoReferenceEquality() {
IDictionary<Object, Object> t = newIDictionary(); IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
t.put(new NGram(new String[] {"" + i}), 0); t.put(new NGram(new String[] {"" + i}), 0);
...@@ -76,58 +75,83 @@ public interface IDictionaryNGramTests extends IDictionaryTests { ...@@ -76,58 +75,83 @@ public interface IDictionaryNGramTests extends IDictionaryTests {
} }
} }
@Order(specialTestLevel) @DisplayName("Runtime Complexity (NGram keys)")
@DisplayName("Test get() -- complexity") @Nested
@Test class ComplexityTestsNGram {
@Timeout(value = 20, unit = SECONDS) @Order(specialTestLevel)
default void testGetComplexity() { @DisplayName("Test get() complexity with NGram keys")
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> { @Test
IDictionary<Object, Object> t = newIDictionary(); @Timeout(value = 20, unit = SECONDS)
for (int i = 0; i < numElements - 4; i++) { public void testGetComplexity() {
t.put(new NGram(new String[] { "" + i }), 0); Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
} IDictionary<Object, Object> t = newIDictionary();
return t; for (int i = 0; i < numElements - 4; i++) {
}; t.put(new NGram(new String[]{"" + i}), 0);
}
return t;
};
Consumer<IDictionary<Object, Object>> get = (IDictionary<Object, Object> t) -> { BiConsumer<Integer, IDictionary<Object, Object>> get = (Integer size, IDictionary<Object, Object> t) -> {
t.get(new NGram(new String[] { "0" })); t.get(new NGram(new String[]{"0"}));
}; };
RuntimeInstrumentation.assertAtMost("get", getAndPutComplexity(), provide, get, 8); RuntimeInstrumentation.assertAtMost("get", getAndPutComplexity(), provide, get, 10);
} }
@Order(specialTestLevel) @Order(specialTestLevel)
@DisplayName("Test put() -- complexity") @DisplayName("Test put() complexity with NGram keys")
@Test @Test
@Timeout(value = 20, unit = SECONDS) @Timeout(value = 20, unit = SECONDS)
default void testPutComplexity() { public void testPutComplexity() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> { Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary(); IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) { for (int i = 0; i < numElements - 4; i++) {
t.put(new NGram(new String[] { "" + i }), 0); t.put(new NGram(new String[]{"" + i}), 0);
} }
return t; return t;
}; };
Consumer<IDictionary<Object, Object>> put = (IDictionary<Object, Object> t) -> { BiConsumer<Integer, IDictionary<Object, Object>> put = (Integer size, IDictionary<Object, Object> t) -> {
t.put(new NGram(new String[] { "0" }), 0); t.put(new NGram(new String[]{"0"}), 0);
}; };
RuntimeInstrumentation.assertAtMost("put", getAndPutComplexity(), provide, put, 8); RuntimeInstrumentation.assertAtMost("put", getAndPutComplexity(), provide, put, 10);
} }
@Order(specialTestLevel) @Order(specialTestLevel)
@DisplayName("Test size() -- complexity") @DisplayName("Test remove() complexity with NGram keys")
@Test @Test
@Timeout(value = 20, unit = SECONDS) @Timeout(value = 20, unit = SECONDS)
default void testSizeComplexity() { public void testRemoveComplexity() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> { Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary(); IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) { for (int i = 0; i < numElements - 4; i++) {
t.put(new NGram(new String[] { "" + i }), 0); t.put(new NGram(new String[]{"" + i}), 0);
} }
return t; return t;
}; };
Consumer<IDictionary<Object, Object>> size = IDictionary::size; BiConsumer<Integer, IDictionary<Object, Object>> remove = (Integer size, IDictionary<Object, Object> t) -> {
RuntimeInstrumentation.assertAtMost("size", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, size, 8); t.remove(new NGram(new String[]{"0"}));
};
RuntimeInstrumentation.assertAtMost("remove", getAndPutComplexity(), provide, remove, 10);
}
@Order(specialTestLevel)
@DisplayName("Test size() complexity with NGram keys")
@Test
@Timeout(value = 20, unit = SECONDS)
public void testSizeComplexity() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(new NGram(new String[]{"" + i}), 0);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> size = (Integer s, IDictionary<Object, Object> t) -> {
t.size();
};
RuntimeInstrumentation.assertAtMost("size", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, size, 8);
}
} }
} }
...@@ -2,49 +2,61 @@ package edu.caltech.cs2.datastructures; ...@@ -2,49 +2,61 @@ package edu.caltech.cs2.datastructures;
import java.time.Duration; import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import edu.caltech.cs2.helpers.DependsOn;
import edu.caltech.cs2.helpers.RuntimeInstrumentation; import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.helpers.TestDescription;
import edu.caltech.cs2.helpers.TestHint;
import edu.caltech.cs2.interfaces.IDictionary; import edu.caltech.cs2.interfaces.IDictionary;
import edu.caltech.cs2.textgenerator.NGram;
import org.hamcrest.MatcherAssert; import org.hamcrest.MatcherAssert;
import org.hamcrest.collection.IsIterableContainingInAnyOrder; import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.*;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import static edu.caltech.cs2.project05.Project05TestOrdering.*; import static edu.caltech.cs2.project05.Project05TestOrdering.*;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
// This allows putting the burden of creating the reference map on the // This allows putting the burden of creating the reference map on the
// implementer, which enables control over key subclass / implementation. // implementer, which enables control over key subclass / implementation.
// Nothing test-related is stored in instance variables, so this does not have (known) side effects. // Nothing test-related is stored in instance variables, so this does not have (known) side effects.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
public interface IDictionaryTests { public abstract class IDictionaryTests {
int SINGLE_OP_TIMEOUT_MS(); abstract int SINGLE_OP_TIMEOUT_MS();
int CONTAINS_VALUE_TIMEOUT_MS(); abstract int CONTAINS_VALUE_TIMEOUT_MS();
RuntimeInstrumentation.ComplexityType getAndPutComplexity(); abstract RuntimeInstrumentation.ComplexityType getAndPutComplexity();
abstract RuntimeInstrumentation.ComplexityType getAndPutComplexityWorst();
abstract RuntimeInstrumentation.ComplexityType getAndPutComplexityBest();
IDictionary<Object, Object> newIDictionary(); abstract IDictionary<Object, Object> newIDictionary();
// This allows the subclass to require specific formats required for keys // This allows the subclass to require specific formats required for keys
// e.g. IDictionary<K extends Comparable, V>, or IDictionary<K extends Iterable, // e.g. IDictionary<K extends Comparable, V>, or IDictionary<K extends Iterable,
// V> // V>
Stream<Arguments> iDictionarySmokeDataSource(); abstract Stream<Arguments> iDictionarySmokeDataSource();
Map<Object, Object> createReferenceMap(String[] keys, Object[] vals); abstract Map<Object, Object> createReferenceMap(String[] keys, Object[] vals);
Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength, abstract Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength,
int maxKeyLength); int maxKeyLength);
default Map<Object, Object> generateRandomTestData(int size, Random rand) { public Map<Object, Object> generateRandomTestData(int size, Random rand) {
return generateRandomTestData(size, rand, 10, 1, 20); return generateRandomTestData(size, rand, 10, 1, 20);
} }
default void iDictionarySmokeTestHelper(Map<Object, Object> base, boolean testRemove) { public void iDictionarySmokeTestHelper(Map<Object, Object> base, boolean testRemove) {
IDictionary<Object, Object> impl = newIDictionary(); IDictionary<Object, Object> impl = newIDictionary();
Set<Object> seenValues = new HashSet<>(); Set<Object> seenValues = new HashSet<>();
...@@ -101,7 +113,7 @@ public interface IDictionaryTests { ...@@ -101,7 +113,7 @@ public interface IDictionaryTests {
} }
} }
default void iDictionaryStressTestHelper(int seed, int size, boolean testRemove) { public void iDictionaryStressTestHelper(int seed, int size, boolean testRemove) {
Random rand = new Random(seed); Random rand = new Random(seed);
Map<Object, Object> excluded = new HashMap<>(); Map<Object, Object> excluded = new HashMap<>();
Set<Object> includedKeys = new HashSet<>(); Set<Object> includedKeys = new HashSet<>();
...@@ -189,36 +201,146 @@ public interface IDictionaryTests { ...@@ -189,36 +201,146 @@ public interface IDictionaryTests {
} }
@Order(sanityTestLevel) @Order(1)
@DisplayName("Smoke test all IDictionary methods") @DisplayName("IDictionary Functionality")
@ParameterizedTest(name = "Test IDictionary interface on {0}") @Nested
@MethodSource("iDictionarySmokeDataSource") @TestInstance(TestInstance.Lifecycle.PER_CLASS)
default void smokeTestIDictionaryNoRemove(Map<Object, Object> base) { class IDictionaryTestsX {
iDictionarySmokeTestHelper(base, false); public Stream<Arguments> iDictionarySmokeDataSource() {
} return IDictionaryTests.this.iDictionarySmokeDataSource();
}
@Order(sanityTestLevel) @Order(0)
@DisplayName("Smoke test all IDictionary methods, with remove") @DisplayName("Smoke test all IDictionary methods")
@ParameterizedTest(name = "Test IDictionary interface on {0}") @ParameterizedTest(name = "Test IDictionary interface on {0}")
@MethodSource("iDictionarySmokeDataSource") @MethodSource("iDictionarySmokeDataSource")
default void smokeTestIDictionaryRemove(Map<Object, Object> base) { @TestDescription("Tests functionality of all IDictionary methods, excluding remove")
iDictionarySmokeTestHelper(base, true); @TestHint("If you are failing this in BSTDictionary, make sure you pass the value you found up through your recursion and returning it.")
} @DependsOn({"isEmpty", "get", "containsKey", "containsValue", "put", "size", "keys", "values", "iterator"})
void smokeTestIDictionaryNoRemove(Map<Object, Object> base) {
iDictionarySmokeTestHelper(base, false);
}
@Order(stressTestLevel) @Order(2)
@DisplayName("Stress test all IDictionary methods") @DisplayName("Smoke test all IDictionary methods, with remove")
@ParameterizedTest(name = "Test IDictionary interface with seed={0} and size={1}") @TestDescription("Tests functionality of all IDictionary methods, including remove")
@CsvSource({ "24589, 3000", "96206, 5000" }) @TestHint("If you are failing this in BSTDictionary, make sure you pass the value you found up through your recursion and returning it.")
default void stressTestIDictionaryNoRemove(int seed, int size) { @DependsOn({"isEmpty", "get", "containsKey", "containsValue", "put", "size", "keys", "values", "iterator", "remove"})
iDictionaryStressTestHelper(seed, size, false); @ParameterizedTest(name = "Test IDictionary interface on {0}")
} @MethodSource("iDictionarySmokeDataSource")
void smokeTestIDictionaryRemove(Map<Object, Object> base) {
iDictionarySmokeTestHelper(base, true);
}
@Order(stressTestLevel) @Order(1)
@DisplayName("Stress test all IDictionary methods, with remove") @DisplayName("Stress test all IDictionary methods")
@ParameterizedTest(name = "Test IDictionary interface with seed={0} and size={1}") @ParameterizedTest(name = "Test IDictionary interface with seed={0} and size={1}")
@CsvSource({ "24589, 3000", "96206, 5000" }) @CsvSource({"24589, 3000", "96206, 5000"})
default void stressTestIDictionaryRemove(int seed, int size) { @TestDescription("Creates random data to test the stability of the IDictionary, excluding remove.")
iDictionaryStressTestHelper(seed, size, true); @DependsOn({"put", "size", "get", "keys", "iterator", "values", "containsKey", "containsValue"})
void stressTestIDictionaryNoRemove(int seed, int size) {
iDictionaryStressTestHelper(seed, size, false);
}
@Order(3)
@DisplayName("Stress test all IDictionary methods, with remove")
@ParameterizedTest(name = "Test IDictionary interface with seed={0} and size={1}")
@CsvSource({"24589, 3000", "96206, 5000"})
@TestDescription("Creates random data to test the stability of the IDictionary, including remove.")
@DependsOn({"put", "size", "get", "keys", "iterator", "values", "containsKey", "containsValue"})
void stressTestIDictionaryRemove(int seed, int size) {
iDictionaryStressTestHelper(seed, size, true);
}
} }
@DisplayName("Runtime Complexity (int keys)")
@Nested
class ComplexityTests {
@Order(3)
@DisplayName("Test get() complexity with int keys")
@DependsOn({"put", "get"})
@TestHint("If these tests are timing out in BSTDictionary, make sure you are not calling hashCode() in either your equals() or compareTo() in NGram." +
" Also make sure that you are not comparing strings more than once and not calling equals() in your compareTo().")
@Timeout(value = 20, unit = SECONDS)
void testGetComplexityInt() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(i, i + 1);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> get = (Integer size, IDictionary<Object, Object> t) -> {
t.get(5);
};
RuntimeInstrumentation.assertAtMost("get", getAndPutComplexityWorst(), provide, get, 8);
}
@Order(specialTestLevel)
@DisplayName("Test put() complexity with int keys")
@Test
@DependsOn({"put"})
@TestHint("If these tests are timing out in BSTDictionary, make sure you are not calling hashCode() in either your equals() or compareTo() in NGram." +
" Also make sure that you are not comparing strings more than once and not calling equals() in your compareTo().")
@Timeout(value = 20, unit = SECONDS)
void testPutComplexityInt() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(i, i + 1);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> put = (Integer size, IDictionary<Object, Object> t) -> {
t.put(size - 5, -1);
};
RuntimeInstrumentation.assertAtMost("put", getAndPutComplexityWorst(), provide, put, 8);
}
@Order(specialTestLevel)
@DisplayName("Test remove() complexity with int keys")
@Test
@TestHint("If these tests are timing out in BSTDictionary, make sure you are not calling hashCode() in either your equals() or compareTo() in NGram." +
" Also make sure that you are not comparing strings more than once and not calling equals() in your compareTo().")
@DependsOn({"put", "remove"})
@Timeout(value = 20, unit = SECONDS)
void testRemoveComplexityInt() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(i, i + 1);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> remove = (Integer size, IDictionary<Object, Object> t) -> {
t.remove(size - 5);
};
RuntimeInstrumentation.assertAtMost("remove", getAndPutComplexityWorst(), provide, remove, 8);
}
@Order(specialTestLevel)
@DisplayName("Test size() complexity with int keys")
@Test
@TestHint("If these tests are timing out in BSTDictionary, make sure you are not calling hashCode() in either your equals() or compareTo() in NGram." +
" Also make sure that you are not comparing strings more than once and not calling equals() in your compareTo().")
@DependsOn({"put", "size"})
@Timeout(value = 20, unit = SECONDS)
void testSizeComplexityInt() {
Function<Integer, IDictionary<Object, Object>> provide = (Integer numElements) -> {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < numElements - 4; i++) {
t.put(new NGram(new String[]{"" + i}), 0);
}
return t;
};
BiConsumer<Integer, IDictionary<Object, Object>> sizez = (Integer size, IDictionary<Object, Object> t) -> {
t.size();
};
RuntimeInstrumentation.assertAtMost("size", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, sizez, 8);
}
}
} }
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