Commit 749c1ba2 authored by Ethan Ordentlich's avatar Ethan Ordentlich
Browse files

Merge branch 'tests' into 'master'

Review Tests for Project 5

Closes #1, #6, #8, #12, #13, #16, #17, #18, #5, #3, #7, and #4

See merge request !1
1 merge request!1Review Tests for Project 5
Pipeline #45394 canceled with stage
Showing with 625 additions and 773 deletions
+625 -773
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff # User-specific stuff
#.idea/**/workspace.xml .idea/**/workspace.xml
.idea/**/tasks.xml .idea/**/tasks.xml
.idea/**/usage.statistics.xml .idea/**/usage.statistics.xml
.idea/**/dictionaries .idea/**/dictionaries
......
<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">
<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">
<configuration default="false" name="B 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="B" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
...@@ -89,7 +89,7 @@ public class BSTDictionary<K extends Comparable<? super K>, V> ...@@ -89,7 +89,7 @@ public class BSTDictionary<K extends Comparable<? super K>, V>
} }
@Override @Override
public ICollection<K> keySet() { public ICollection<K> keys() {
return null; return null;
} }
...@@ -104,7 +104,7 @@ public class BSTDictionary<K extends Comparable<? super K>, V> ...@@ -104,7 +104,7 @@ public class BSTDictionary<K extends Comparable<? super K>, V>
@Override @Override
public Iterator<K> iterator() { public Iterator<K> iterator() {
return keySet().iterator(); return keys().iterator();
} }
@Override @Override
......
...@@ -59,7 +59,7 @@ public class ChainingHashDictionary<K, V> implements IDictionary<K, V> { ...@@ -59,7 +59,7 @@ public class ChainingHashDictionary<K, V> implements IDictionary<K, V> {
} }
@Override @Override
public ICollection<K> keySet() { public ICollection<K> keys() {
return null; return null;
} }
......
...@@ -4,7 +4,6 @@ import edu.caltech.cs2.interfaces.ICollection; ...@@ -4,7 +4,6 @@ import edu.caltech.cs2.interfaces.ICollection;
import edu.caltech.cs2.interfaces.IDictionary; import edu.caltech.cs2.interfaces.IDictionary;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException;
public class MoveToFrontDictionary<K, V> implements IDictionary<K,V> { public class MoveToFrontDictionary<K, V> implements IDictionary<K,V> {
...@@ -38,7 +37,7 @@ public class MoveToFrontDictionary<K, V> implements IDictionary<K,V> { ...@@ -38,7 +37,7 @@ public class MoveToFrontDictionary<K, V> implements IDictionary<K,V> {
} }
@Override @Override
public ICollection<K> keySet() { public ICollection<K> keys() {
return null; return null;
} }
......
...@@ -24,7 +24,7 @@ public interface IDeque<E> extends ICollection<E> { ...@@ -24,7 +24,7 @@ public interface IDeque<E> extends ICollection<E> {
*/ */
@Override @Override
default public void add(E e){ default public void add(E e){
this.addFront(e); this.addBack(e);
} }
/** /**
......
...@@ -71,7 +71,7 @@ public interface IDictionary<K, V> extends Iterable<K> { ...@@ -71,7 +71,7 @@ public interface IDictionary<K, V> extends Iterable<K> {
* *
* @return a collection of the keys contained in this map * @return a collection of the keys contained in this map
*/ */
public ICollection<K> keySet(); public ICollection<K> keys();
/** /**
* Returns a {@link ICollection} of the values contained in this map * Returns a {@link ICollection} of the values contained in this map
......
...@@ -13,7 +13,7 @@ public class NGram implements Iterable<String>, Comparable<NGram> { ...@@ -13,7 +13,7 @@ public class NGram implements Iterable<String>, Comparable<NGram> {
private IDeque<String> data; private IDeque<String> data;
public static String normalize(String s) { public static String normalize(String s) {
return s.replaceAll(REGEX_TO_FILTER, "").strip(); return s.replaceAll(REGEX_TO_FILTER, "").strip();
} }
public NGram(IDeque<String> x) { public NGram(IDeque<String> x) {
...@@ -47,7 +47,7 @@ public class NGram implements Iterable<String>, Comparable<NGram> { ...@@ -47,7 +47,7 @@ public class NGram implements Iterable<String>, Comparable<NGram> {
this.data.addBack(this.data.removeFront()); this.data.addBack(this.data.removeFront());
data[data.length - 1] = word; data[data.length - 1] = word;
return new NGram(data); return new NGram(data);
} }
public String toString() { public String toString() {
String result = ""; String result = "";
......
...@@ -4,16 +4,12 @@ import edu.caltech.cs2.helpers.Inspection; ...@@ -4,16 +4,12 @@ import edu.caltech.cs2.helpers.Inspection;
import edu.caltech.cs2.helpers.Reflection; import edu.caltech.cs2.helpers.Reflection;
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 org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
...@@ -54,11 +50,23 @@ public class AVLTreeDictionaryTests implements IDictionaryNGramTests { ...@@ -54,11 +50,23 @@ public class AVLTreeDictionaryTests implements IDictionaryNGramTests {
@DisplayName("Does not use or import disallowed classes") @DisplayName("Does not use or import disallowed classes")
@Test @Test
public void testForInvalidClasses() { public void testForInvalidClasses() {
List<String> regexps = List.of("java\\.util\\.^(?!Iterator)(?!Supplier)(?!Stream)", "java\\.lang\\.reflect", "java\\.io"); List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io");
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps); Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps);
Inspection.assertNoUsageOf(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) @Order(0)
@DisplayName("There are no public fields") @DisplayName("There are no public fields")
@Test @Test
......
...@@ -8,8 +8,7 @@ import org.junit.jupiter.api.*; ...@@ -8,8 +8,7 @@ import org.junit.jupiter.api.*;
import java.util.List; import java.util.List;
import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel; import static edu.caltech.cs2.project05.Project05TestOrdering.*;
import static edu.caltech.cs2.project05.Project05TestOrdering.specialTestLevel;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
...@@ -46,11 +45,23 @@ public class BSTDictionaryTests implements IDictionaryNGramTests { ...@@ -46,11 +45,23 @@ public class BSTDictionaryTests implements IDictionaryNGramTests {
@DisplayName("Does not use or import disallowed classes") @DisplayName("Does not use or import disallowed classes")
@Test @Test
public void testForInvalidClasses() { public void testForInvalidClasses() {
List<String> regexps = List.of("java\\.util\\.^(?!Iterator)(?!Supplier)(?!Stream)", "java\\.lang\\.reflect", "java\\.io"); List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io");
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps); Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps);
Inspection.assertNoUsageOf(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(classSpecificTestLevel) @Order(classSpecificTestLevel)
@DisplayName("There are no public fields") @DisplayName("There are no public fields")
@Test @Test
...@@ -59,7 +70,7 @@ public class BSTDictionaryTests implements IDictionaryNGramTests { ...@@ -59,7 +70,7 @@ public class BSTDictionaryTests implements IDictionaryNGramTests {
} }
@Test @Test
@Order(specialTestLevel) @Order(sanityTestLevel)
@DisplayName("Test nonbalancing BST Implementation") @DisplayName("Test nonbalancing BST Implementation")
public void testActualBST() { public void testActualBST() {
BSTDictionary<String, Integer> bst = new BSTDictionary<>(); BSTDictionary<String, Integer> bst = new BSTDictionary<>();
......
...@@ -7,18 +7,20 @@ import edu.caltech.cs2.interfaces.IDictionary; ...@@ -7,18 +7,20 @@ import edu.caltech.cs2.interfaces.IDictionary;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Supplier; 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 org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("B") @Tag("B")
public class ChainingHashDictionaryTests implements IDictionaryNGramTests { public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
private static String DICTIONARY_SOURCE = private static String DICTIONARY_SOURCE = "src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java";
"src/edu/caltech/cs2/datastructures/ChainingHashDictionary.java";
public Counter supplierCounter = new Counter(); public Counter supplierCounter = new Counter();
...@@ -50,18 +52,58 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -50,18 +52,58 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
@DisplayName("Does not use or import disallowed classes") @DisplayName("Does not use or import disallowed classes")
@Test @Test
public void testForInvalidClasses() { public void testForInvalidClasses() {
List<String> regexps = List.of("java\\.util\\.^(?!Iterator)(?!Supplier)(?!Stream)", "java\\.lang\\.reflect", "java\\.io"); List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io");
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps); Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps);
Inspection.assertNoUsageOf(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", "Supplier", "Stream");
Inspection.assertNoImportsOfExcept(DICTIONARY_SOURCE, "java\\.util", allowed);
List<String> bannedUsages = List.of("java\\.util\\.(?!" + String.join("|", allowed) + ")");
Inspection.assertNoUsageOf(DICTIONARY_SOURCE, bannedUsages);
}
@Order(classSpecificTestLevel) @Order(classSpecificTestLevel)
@DisplayName("There are no public fields") @DisplayName("There are no public fields")
@Test @Test
public void testNoPublicFields() { public void testNoPublicFieldsCHD() {
Reflection.assertNoPublicFields(ChainingHashDictionary.class); Reflection.assertNoPublicFields(ChainingHashDictionary.class);
} }
@Order(classSpecificTestLevel)
@DisplayName("There are no protected fields")
@Test
public void testNoProtectedFieldsCHD() {
Reflection.assertNoProtectedFields(ChainingHashDictionary.class);
}
@Order(classSpecificTestLevel)
@DisplayName("All fields in ChainingHashDictionary have modifiers")
@Test
public void testFieldModifiersCHD() {
Reflection.assertFieldModifiers(ChainingHashDictionary.class);
}
@Order(specialTestLevel)
@DisplayName("Test that resizing and initializing is fast")
@Timeout(value = 20, unit = SECONDS)
@Test
public void testGrowthCapability() {
Set<IDictionary<Object, Object>> dicts = new HashSet<>();
for (int i = 0; i < 50; i++) {
dicts.add(new ChainingHashDictionary<>(MoveToFrontDictionary::new));
}
for (IDictionary<Object, Object> dict : dicts) {
for (int i = 0; i < 400000; i++) {
dict.put(i, i);
}
}
}
@Order(specialTestLevel) @Order(specialTestLevel)
@DisplayName("Test that hash map is resized appropriately") @DisplayName("Test that hash map is resized appropriately")
...@@ -69,7 +111,7 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -69,7 +111,7 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
public void testResize() { public void testResize() {
List<Counter> values = new ArrayList<>(); List<Counter> values = new ArrayList<>();
IDictionary<Object, Object> impl = newIDictionary(); IDictionary<Object, Object> impl = newIDictionary();
for (int i = 0; i < 100000; i ++) { for (int i = 0; i < 100000; i++) {
Counter c = new Counter(i); Counter c = new Counter(i);
impl.put(c, i); impl.put(c, i);
// Insert newer counters at front so number of touches is ascending // Insert newer counters at front so number of touches is ascending
...@@ -84,7 +126,8 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -84,7 +126,8 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
} }
assertTrue(currMaxTouches > 10, "Dictionary was not resized enough times when building"); 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 // Compare to 2 - last insertion could have triggered resize (unlikely, but
// might as well - important part
// is ensuring an upper bound. // is ensuring an upper bound.
assertTrue(values.get(0).touches <= 2, "Most recent key inserted was hashed too many times"); assertTrue(values.get(0).touches <= 2, "Most recent key inserted was hashed too many times");
} }
...@@ -97,7 +140,7 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -97,7 +140,7 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
this.supplierCounter.resetCounter(); this.supplierCounter.resetCounter();
IDictionary<Object, Object> impl = newIDictionary(); IDictionary<Object, Object> impl = newIDictionary();
for (int i = 0; i < 100000; i ++) { for (int i = 0; i < 100000; i++) {
impl.put(i, i); impl.put(i, i);
} }
...@@ -123,7 +166,7 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -123,7 +166,7 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
} }
public void touchCounter() { public void touchCounter() {
this.touches ++; this.touches++;
} }
@Override @Override
...@@ -137,15 +180,14 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests { ...@@ -137,15 +180,14 @@ public class ChainingHashDictionaryTests implements IDictionaryNGramTests {
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == null) { if (o == null) {
return false; return false;
} } else if (!(o instanceof Counter)) {
else if (!(o instanceof Counter)) {
return false; return false;
} }
Counter c = (Counter) o; Counter c = (Counter) o;
if (this.data == null || c.data == null) { if (this.data == null || c.data == null) {
return (this.data == null && c.data == null); return (this.data == null && c.data == null);
} }
return this.data.equals(c.data); return this.data.equals(c.data);
} }
@Override @Override
......
package edu.caltech.cs2.datastructures; package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.RuntimeInstrumentation;
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.Order;
import org.junit.jupiter.api.Test;
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.Consumer;
import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import static edu.caltech.cs2.project05.Project05TestOrdering.sanityTestLevel;
import static edu.caltech.cs2.project05.Project05TestOrdering.specialTestLevel;
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 { public interface IDictionaryNGramTests extends IDictionaryTests {
@Override @Override
default Stream<Arguments> iDictionarySmokeDataSource() { default Stream<Arguments> iDictionarySmokeDataSource() {
return Stream.of( return Stream.of(
Arguments.of(createReferenceMap(new String[] { "a", "ab", "abc", "abcd", "abcde" },
new Integer[] { 1, 2, 3, 4, 5 })),
Arguments.of(createReferenceMap(new String[] { "abcde", "abcd", "abc", "ab", "a" },
new Integer[] { 1, 2, 3, 4, 5 })),
Arguments.of(createReferenceMap(new String[] { "a", "add", "app" },
new String[] { "hello", "1 + 1", "for a phone" })),
Arguments.of(createReferenceMap( Arguments.of(createReferenceMap(
new String[]{"a", "ab", "abc", "abcd", "abcde"}, new String[] { "adam", "add", "app", "bad", "bag", "bags", "beds", "bee", "cab" },
new Integer[]{1, 2, 3, 4, 5})), new Integer[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 })));
Arguments.of(createReferenceMap(
new String[]{"abcde", "abcd", "abc", "ab", "a"},
new Integer[]{1, 2, 3, 4, 5})),
Arguments.of(createReferenceMap(
new String[]{"a", "add", "app"},
new String[]{"hello", "1 + 1", "for a phone"})),
Arguments.of(createReferenceMap(
new String[]{"adam", "add", "app", "bad", "bag", "bags", "beds", "bee", "cab"},
new Integer[]{0, 1, 2, 3, 4, 5, 6, 7, 8}))
);
} }
@Override @Override
default Map<Object, Object> createReferenceMap(String[] keys, Object[] vals) { default 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]);
} }
return ref; return ref;
} }
@Override @Override
default Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength, int maxKeyLength) { default Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength,
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++) {
int keyLength = minKeyLength + rand.nextInt(maxKeyLength-minKeyLength); int keyLength = minKeyLength + rand.nextInt(maxKeyLength - minKeyLength);
String[] key = new String[keyLength]; String[] key = new String[keyLength];
for (int j = 0; j < keyLength; j ++) { for (int j = 0; j < keyLength; j++) {
key[j] = String.valueOf(rand.nextInt(maxNodeDegree)); key[j] = String.valueOf(rand.nextInt(maxNodeDegree));
} }
base.put(new NGram(key), rand.nextInt()); base.put(new NGram(key), rand.nextInt());
...@@ -52,4 +60,69 @@ public interface IDictionaryNGramTests extends IDictionaryTests { ...@@ -52,4 +60,69 @@ public interface IDictionaryNGramTests extends IDictionaryTests {
return base; return base;
} }
@Order(sanityTestLevel)
@DisplayName("Test that keys are not compared using reference equality")
@Test
default void testNoReferenceEquality() {
IDictionary<Object, Object> t = newIDictionary();
for (int i = 0; i < 10; i++) {
t.put(new NGram(new String[] {"" + i}), 0);
}
for (int i = 0; i < 10; i ++) {
assertTrue(t.containsKey(new NGram(new String[] {new String("" + i)})),
"NGram that is a distinct object with same data should be a contained key, but is not");
}
}
@Order(specialTestLevel)
@DisplayName("Test get() -- complexity")
@Test
default void testGetComplexity() {
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;
};
Consumer<IDictionary<Object, Object>> get = (IDictionary<Object, Object> t) -> {
t.get(new NGram(new String[] { "0" }));
};
RuntimeInstrumentation.assertAtMost("get", getAndPutComplexity(), provide, get, 8);
}
@Order(specialTestLevel)
@DisplayName("Test put() -- complexity")
@Test
default void testPutComplexity() {
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;
};
Consumer<IDictionary<Object, Object>> put = (IDictionary<Object, Object> t) -> {
t.put(new NGram(new String[] { "0" }), 0);
};
RuntimeInstrumentation.assertAtMost("put", getAndPutComplexity(), provide, put, 8);
}
@Order(specialTestLevel)
@DisplayName("Test size() -- complexity")
@Test
default 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;
};
Consumer<IDictionary<Object, Object>> size = IDictionary::size;
RuntimeInstrumentation.assertAtMost("size", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, size, 8);
}
} }
...@@ -2,13 +2,10 @@ package edu.caltech.cs2.datastructures; ...@@ -2,13 +2,10 @@ package edu.caltech.cs2.datastructures;
import java.time.Duration; import java.time.Duration;
import java.util.*; import java.util.*;
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.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 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.*;
...@@ -26,16 +23,23 @@ import static org.junit.jupiter.api.Assertions.*; ...@@ -26,16 +23,23 @@ import static org.junit.jupiter.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
public interface IDictionaryTests { public interface IDictionaryTests {
int SINGLE_OP_TIMEOUT_MS(); int SINGLE_OP_TIMEOUT_MS();
int CONTAINS_VALUE_TIMEOUT_MS(); int CONTAINS_VALUE_TIMEOUT_MS();
RuntimeInstrumentation.ComplexityType getAndPutComplexity(); RuntimeInstrumentation.ComplexityType getAndPutComplexity();
IDictionary<Object, Object> newIDictionary(); 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, V> // e.g. IDictionary<K extends Comparable, V>, or IDictionary<K extends Iterable,
// V>
Stream<Arguments> iDictionarySmokeDataSource(); Stream<Arguments> iDictionarySmokeDataSource();
Map<Object, Object> createReferenceMap(String[] keys, Object[] vals); Map<Object, Object> createReferenceMap(String[] keys, Object[] vals);
Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength, int maxKeyLength);
Map<Object, Object> generateRandomTestData(int size, Random rand, int maxNodeDegree, int minKeyLength,
int maxKeyLength);
default Map<Object, Object> generateRandomTestData(int size, Random rand) { default Map<Object, Object> generateRandomTestData(int size, Random rand) {
return generateRandomTestData(size, rand, 10, 1, 20); return generateRandomTestData(size, rand, 10, 1, 20);
} }
...@@ -51,7 +55,7 @@ public interface IDictionaryTests { ...@@ -51,7 +55,7 @@ public interface IDictionaryTests {
// Negative key + value tests // Negative key + value tests
assertFalse(impl.containsKey(k), "containsKey returns true for missing key " + k); assertFalse(impl.containsKey(k), "containsKey returns true for missing key " + k);
if (! seenValues.contains(base.get(k))) { if (!seenValues.contains(base.get(k))) {
seenValues.add(base.get(k)); seenValues.add(base.get(k));
assertFalse(impl.containsValue(base.get(k)), "containsValue returns true for missing value"); assertFalse(impl.containsValue(base.get(k)), "containsValue returns true for missing value");
} }
...@@ -59,7 +63,7 @@ public interface IDictionaryTests { ...@@ -59,7 +63,7 @@ public interface IDictionaryTests {
// Put the key in // Put the key in
assertNull(impl.put(k, ""), "Putting a new key " + k + " returns a non null value"); assertNull(impl.put(k, ""), "Putting a new key " + k + " returns a non null value");
expectedSize ++; expectedSize++;
assertEquals(expectedSize, impl.size(), "Incorrect size"); assertEquals(expectedSize, impl.size(), "Incorrect size");
// Existing key tests // Existing key tests
...@@ -71,7 +75,7 @@ public interface IDictionaryTests { ...@@ -71,7 +75,7 @@ public interface IDictionaryTests {
assertTrue(impl.containsValue(base.get(k)), "containsValue returns false for present value"); assertTrue(impl.containsValue(base.get(k)), "containsValue returns false for present value");
} }
MatcherAssert.assertThat("keySet", impl.keySet(), MatcherAssert.assertThat("keys", impl.keys(),
IsIterableContainingInAnyOrder.containsInAnyOrder(base.keySet().toArray())); IsIterableContainingInAnyOrder.containsInAnyOrder(base.keySet().toArray()));
MatcherAssert.assertThat("iterator", impl, MatcherAssert.assertThat("iterator", impl,
IsIterableContainingInAnyOrder.containsInAnyOrder(base.keySet().toArray())); IsIterableContainingInAnyOrder.containsInAnyOrder(base.keySet().toArray()));
...@@ -83,7 +87,7 @@ public interface IDictionaryTests { ...@@ -83,7 +87,7 @@ public interface IDictionaryTests {
for (Object k : keys) { for (Object k : keys) {
Object v = base.remove(k); Object v = base.remove(k);
assertEquals(v, impl.remove(k), "Removing existing key returns wrong value"); assertEquals(v, impl.remove(k), "Removing existing key returns wrong value");
expectedSize --; expectedSize--;
assertEquals(expectedSize, impl.size(), "Removing existing key did not decrease size"); assertEquals(expectedSize, impl.size(), "Removing existing key did not decrease size");
assertNull(impl.remove(k), "Removing missing key returns nonnull"); assertNull(impl.remove(k), "Removing missing key returns nonnull");
...@@ -125,7 +129,7 @@ public interface IDictionaryTests { ...@@ -125,7 +129,7 @@ public interface IDictionaryTests {
// If testRemove, excluded keys will be removed later // If testRemove, excluded keys will be removed later
if (testRemove || !excluded.containsKey(e.getKey())) { if (testRemove || !excluded.containsKey(e.getKey())) {
assertNull(impl.put(e.getKey(), ""), "Putting new key returns incorrect value"); assertNull(impl.put(e.getKey(), ""), "Putting new key returns incorrect value");
expectedSize ++; expectedSize++;
assertEquals(expectedSize, impl.size(), "Adding new key did not appropriately change size"); assertEquals(expectedSize, impl.size(), "Adding new key did not appropriately change size");
assertEquals("", impl.put(e.getKey(), e.getValue()), "Putting old key returns different value"); assertEquals("", impl.put(e.getKey(), e.getValue()), "Putting old key returns different value");
assertEquals(e.getValue(), impl.get(e.getKey()), "Getting an updated key returns old value"); assertEquals(e.getValue(), impl.get(e.getKey()), "Getting an updated key returns old value");
...@@ -137,7 +141,7 @@ public interface IDictionaryTests { ...@@ -137,7 +141,7 @@ public interface IDictionaryTests {
if (testRemove) { if (testRemove) {
for (Object k : excluded.keySet()) { for (Object k : excluded.keySet()) {
assertEquals(base.remove(k), impl.remove(k), "Removing existing key returns wrong value"); assertEquals(base.remove(k), impl.remove(k), "Removing existing key returns wrong value");
expectedSize --; expectedSize--;
assertEquals(expectedSize, impl.size(), "Removing existing key did not decrease size"); assertEquals(expectedSize, impl.size(), "Removing existing key did not decrease size");
assertNull(impl.remove(k), "Removing missing key returns nonnull"); assertNull(impl.remove(k), "Removing missing key returns nonnull");
...@@ -152,7 +156,7 @@ public interface IDictionaryTests { ...@@ -152,7 +156,7 @@ public interface IDictionaryTests {
} }
// Iterable checks // Iterable checks
MatcherAssert.assertThat("keySet", impl.keySet(), MatcherAssert.assertThat("keys", impl.keys(),
IsIterableContainingInAnyOrder.containsInAnyOrder(includedKeys.toArray())); IsIterableContainingInAnyOrder.containsInAnyOrder(includedKeys.toArray()));
MatcherAssert.assertThat("iterator", impl, MatcherAssert.assertThat("iterator", impl,
IsIterableContainingInAnyOrder.containsInAnyOrder(includedKeys.toArray())); IsIterableContainingInAnyOrder.containsInAnyOrder(includedKeys.toArray()));
...@@ -204,10 +208,7 @@ public interface IDictionaryTests { ...@@ -204,10 +208,7 @@ public interface IDictionaryTests {
@Order(stressTestLevel) @Order(stressTestLevel)
@DisplayName("Stress test all IDictionary methods") @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({ @CsvSource({ "24589, 3000", "96206, 5000" })
"24589, 3000",
"96206, 5000"
})
default void stressTestIDictionaryNoRemove(int seed, int size) { default void stressTestIDictionaryNoRemove(int seed, int size) {
iDictionaryStressTestHelper(seed, size, false); iDictionaryStressTestHelper(seed, size, false);
} }
...@@ -215,67 +216,9 @@ public interface IDictionaryTests { ...@@ -215,67 +216,9 @@ public interface IDictionaryTests {
@Order(stressTestLevel) @Order(stressTestLevel)
@DisplayName("Stress test all IDictionary methods, with remove") @DisplayName("Stress test all IDictionary methods, with remove")
@ParameterizedTest(name = "Test IDictionary interface with seed={0} and size={1}") @ParameterizedTest(name = "Test IDictionary interface with seed={0} and size={1}")
@CsvSource({ @CsvSource({ "24589, 3000", "96206, 5000" })
"24589, 3000",
"96206, 5000"
})
default void stressTestIDictionaryRemove(int seed, int size) { default void stressTestIDictionaryRemove(int seed, int size) {
iDictionaryStressTestHelper(seed, size, false); iDictionaryStressTestHelper(seed, size, false);
} }
@Order(specialTestLevel)
@DisplayName("Test get() -- complexity")
@Test
default void testGetComplexity() {
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;
};
Consumer<IDictionary<Object, Object>> get = (IDictionary<Object, Object> t) -> {
t.get(new NGram(new String[]{"0"}));
};
RuntimeInstrumentation.assertAtMost("get",
getAndPutComplexity(), provide, get, 8);
}
@Order(specialTestLevel)
@DisplayName("Test put() -- complexity")
@Test
default void testPutComplexity() {
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;
};
Consumer<IDictionary<Object, Object>> put = (IDictionary<Object, Object> t) -> {
t.put(new NGram(new String[]{"0"}), 0);
};
RuntimeInstrumentation.assertAtMost("put",
getAndPutComplexity(), provide, put, 8);
}
@Order(specialTestLevel)
@DisplayName("Test size() -- complexity")
@Test
default 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;
};
Consumer<IDictionary<Object, Object>> size = IDictionary::size;
RuntimeInstrumentation.assertAtMost("size",
RuntimeInstrumentation.ComplexityType.CONSTANT, provide, size, 8);
}
} }
package edu.caltech.cs2.datastructures; package edu.caltech.cs2.datastructures;
import edu.caltech.cs2.helpers.Inspection; import edu.caltech.cs2.helpers.Inspection;
import edu.caltech.cs2.helpers.MoveToFrontChecker;
import edu.caltech.cs2.helpers.Reflection;
import edu.caltech.cs2.helpers.RuntimeInstrumentation; import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.ICollection;
import edu.caltech.cs2.interfaces.IDictionary; import edu.caltech.cs2.interfaces.IDictionary;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel; import static edu.caltech.cs2.project05.Project05TestOrdering.classSpecificTestLevel;
...@@ -15,13 +20,13 @@ import static org.junit.jupiter.api.Assertions.*; ...@@ -15,13 +20,13 @@ import static org.junit.jupiter.api.Assertions.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("B") @Tag("B")
public class MoveToFrontDictionaryTests implements IDictionaryNGramTests { public class MoveToFrontDictionaryTests implements IDictionaryNGramTests {
private static String DICTIONARY_SOURCE = private static String DICTIONARY_SOURCE = "src/edu/caltech/cs2/datastructures/MoveToFrontDictionary.java";
"src/edu/caltech/cs2/datastructures/MoveToFrontDictionary.java";
public int CONSTANT_TIMEOUT_MS = 15; public int CONSTANT_TIMEOUT_MS = 15;
public int SINGLE_OP_TIMEOUT_MS() { public int SINGLE_OP_TIMEOUT_MS() {
return 100; return 100;
} }
public int CONTAINS_VALUE_TIMEOUT_MS() { public int CONTAINS_VALUE_TIMEOUT_MS() {
return 100; return 100;
} }
...@@ -37,24 +42,76 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests { ...@@ -37,24 +42,76 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests {
return new MoveToFrontDictionary<>(); return new MoveToFrontDictionary<>();
} }
@Order(classSpecificTestLevel) @Order(classSpecificTestLevel)
@DisplayName("Does not use or import disallowed classes") @DisplayName("Does not use or import disallowed classes")
@Test @Test
public void testForInvalidClasses() { public void testForInvalidClasses() {
List<String> regexps = List.of("java\\.util\\.^(?!Iterator)(?!Supplier)(?!Stream)", "java\\.lang\\.reflect", "java\\.io"); List<String> regexps = List.of("java\\.lang\\.reflect", "java\\.io");
Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps); Inspection.assertNoImportsOf(DICTIONARY_SOURCE, regexps);
Inspection.assertNoUsageOf(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(classSpecificTestLevel)
@DisplayName("There are no public fields")
@Test
public void testNoPublicFieldsMTF() {
Reflection.assertNoPublicFields(MoveToFrontDictionary.class);
}
@Order(classSpecificTestLevel)
@DisplayName("There are no protected fields")
@Test
public void testNoProtectedFieldsMTF() {
Reflection.assertNoProtectedFields(MoveToFrontDictionary.class);
}
@Order(classSpecificTestLevel)
@DisplayName("All fields in MoveToFrontDictionary have modifiers")
@Test @Test
@DisplayName("Sanity check that accessing keys in various locations works") public void testFieldModifiersMTF() {
Reflection.assertFieldModifiers(MoveToFrontDictionary.class);
}
@Order(classSpecificTestLevel)
@DisplayName("Check for linked node class")
@Test
public void testLinkedNode() {
Class[] classes = MoveToFrontDictionary.class.getDeclaredClasses();
for (Class clazz : classes) {
if (Iterator.class.isAssignableFrom(clazz)) {
continue;
}
MoveToFrontChecker.isNode(clazz);
}
}
// TODO: what the fuck
@Order(classSpecificTestLevel)
@DisplayName("Check MoveToFrontDictionary class is properly implemented")
@Test
public void checkMTF() {
MoveToFrontChecker.checkClass(MoveToFrontDictionary.class);
}
@Test
@DisplayName("Sanity check that accessing keys in various locations in the dictionary works")
@Order(classSpecificTestLevel) @Order(classSpecificTestLevel)
public void testDataLocations() { public void testDataLocations() {
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>(); MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
HashMap<Integer, Integer> ref = new HashMap<>(); HashMap<Integer, Integer> ref = new HashMap<>();
for (int i = 0; i < 10; i ++) { for (int i = 0; i < 10; i++) {
impl.put(i, i); impl.put(i, i);
ref.put(i, i); ref.put(i, i);
} }
...@@ -63,25 +120,25 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests { ...@@ -63,25 +120,25 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests {
assertEquals(ref.get(9), impl.get(9), "Getting an element from the front failed."); assertEquals(ref.get(9), impl.get(9), "Getting an element from the front failed.");
// Check accessing whatever element is at the back // Check accessing whatever element is at the back
for (int i = 0; i < 10; i ++) { for (int i = 0; i < 10; i++) {
assertEquals(ref.get(i), impl.get(i), "Getting an element from the back returns incorrect result."); assertEquals(ref.get(i), impl.get(i), "Getting an element from the back returns incorrect result.");
assertEquals(ref.get(i), impl.get(i), "Key that was just gotten is now missing."); assertEquals(ref.get(i), impl.get(i), "Key that was just gotten is now missing.");
} }
// Check removing element at the front // Check removing element at the front
for (int i = 9; i >= 0; i --) { for (int i = 9; i >= 0; i--) {
assertEquals(ref.remove(i), impl.remove(i), "Removing an element from the front failed."); assertEquals(ref.remove(i), impl.remove(i), "Removing an element from the front failed.");
} }
// Repopulate to make sure that emptying it didn't bork it // Repopulate to make sure that emptying it didn't bork it
for (int i = 0; i < 10; i ++) { for (int i = 0; i < 10; i++) {
impl.put(i, i); impl.put(i, i);
ref.put(i, i); ref.put(i, i);
} }
// And repeat. // And repeat.
assertEquals(ref.get(9), impl.get(9)); assertEquals(ref.get(9), impl.get(9));
for (int i = 0; i < 10; i ++) { for (int i = 0; i < 10; i++) {
assertEquals(ref.get(i), impl.get(i), "Getting an element from the back failed."); assertEquals(ref.get(i), impl.get(i), "Getting an element from the back failed.");
} }
} }
...@@ -92,13 +149,27 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests { ...@@ -92,13 +149,27 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests {
public void testMoveToFrontProperty() { public void testMoveToFrontProperty() {
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>(); MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
final int DICT_SIZE = 30000; final int DICT_SIZE = 30000;
for (int i = 0; i <= DICT_SIZE; i ++) { for (int i = 0; i <= DICT_SIZE; i++) {
impl.put(i, i); impl.put(i, i);
} }
double totalTimeRetrieveFront = 0; double totalTimeRetrieveFront = 0;
long startTime, endTime; long startTime, endTime;
for (int i = 0; i <= DICT_SIZE; i ++) { for (int i = 0; i <= DICT_SIZE; i++) {
// Get key from back to move to front
impl.containsKey(i);
// Clock retrieval of key from front.
startTime = System.nanoTime();
impl.get(i);
endTime = System.nanoTime();
totalTimeRetrieveFront += (endTime - startTime);
}
assertTrue(CONSTANT_TIMEOUT_MS > totalTimeRetrieveFront / 1000000,
"get(key) after calling containsKey(key) takes too long.");
totalTimeRetrieveFront = 0;
for (int i = 0; i <= DICT_SIZE; i++) {
// Get key from back to move to front // Get key from back to move to front
impl.get(i); impl.get(i);
...@@ -108,14 +179,63 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests { ...@@ -108,14 +179,63 @@ public class MoveToFrontDictionaryTests implements IDictionaryNGramTests {
endTime = System.nanoTime(); endTime = System.nanoTime();
totalTimeRetrieveFront += (endTime - startTime); totalTimeRetrieveFront += (endTime - startTime);
} }
assertTrue(CONSTANT_TIMEOUT_MS > totalTimeRetrieveFront / 1000000, "Retrieving keys after touching them once takes too long."); assertTrue(CONSTANT_TIMEOUT_MS > totalTimeRetrieveFront / 1000000,
"get(key) after calling get(key) takes too long.");
} }
// MoveToFrontDictionary takes quadratic time to build, so don't run standard complexity tests. @Order(specialTestLevel)
@DisplayName("Test removing from the front has the desired behavior")
@Test
public void testFrontRemove() {
MoveToFrontDictionary<Integer, Integer> impl = new MoveToFrontDictionary<>();
final int DICT_SIZE = 1000;
for (int i = 0; i <= DICT_SIZE; i++) {
impl.put(i, i);
}
Class nodeClass = MoveToFrontChecker.getNodeClass(MoveToFrontDictionary.class);
Field head = null;
for (Field field : MoveToFrontDictionary.class.getDeclaredFields()) {
if (field.getType().equals(nodeClass)) {
field.setAccessible(true);
head = field;
break;
}
}
assertNotNull(head, "There is no head field in MoveToFrontDictionary");
for (int i = DICT_SIZE; i >= 0; i--) {
assertEquals(i, impl.get(i), "Getting a key does not return the correct value");
assertEquals(i, impl.remove(i), "Removing a key does not return the correct value");
try {
if (i != 0) {
assertNotNull(head.get(impl), "Removing from front leaves a null head pointer");
} else {
assertNull(head.get(impl), "Removing last key does not set head pointer to null");
}
ICollection<Integer> values = impl.values();
assertNull(impl.get(i), "Getting a removed key does not return null");
assertIterableEquals(values, impl.values(), "Getting a removed key changes the value list");
if (i != 0) {
assertNotNull(head.get(impl), "Getting a removed key leaves a null head pointer");
} else {
assertNull(head.get(impl), "Getting the last key does leave a null head pointer");
}
} catch (IllegalAccessException ex) {
fail("There is no head field in MoveToFrontDictionary");
}
}
}
// MoveToFrontDictionary takes quadratic time to build, so don't run standard
// complexity tests.
@Override @Override
public void testSizeComplexity() {} public void testSizeComplexity() {
}
@Override @Override
public void testGetComplexity() {} public void testGetComplexity() {
}
@Override @Override
public void testPutComplexity() {} public void testPutComplexity() {
}
} }
...@@ -5,6 +5,8 @@ import org.junit.jupiter.api.*; ...@@ -5,6 +5,8 @@ import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.CsvSource;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
public class NGramTests { public class NGramTests {
...@@ -145,7 +147,17 @@ public class NGramTests { ...@@ -145,7 +147,17 @@ public class NGramTests {
}) })
public void testEquals(String A, String B, boolean expected) { public void testEquals(String A, String B, boolean expected) {
assertEquals(expected, equalsStrings(A, B)); assertEquals(expected, equalsStrings(A, B));
}
@Test
@Tag("B")
@DisplayName("Check NGrams that are the same string split differently")
public void testDifferentStringSplits() {
NGram ng_oneword = new NGram(new String[] {"horsepower"});
NGram ng_twoword = new NGram(new String[] {"horse", "power"});
assertNotEquals(ng_twoword, ng_oneword, "NGrams that are the same string split differently are equal");
assertNotEquals(ng_twoword.hashCode(), ng_oneword.hashCode(), "NGrams that are the same string split differently have the same hashcode");
} }
public static int[] generateHashCodes(int n) { public static int[] generateHashCodes(int n) {
...@@ -226,5 +238,27 @@ public class NGramTests { ...@@ -226,5 +238,27 @@ public class NGramTests {
assertTrue(std > 1e7, "The standard deviation of simple hashcodes is too small"); assertTrue(std > 1e7, "The standard deviation of simple hashcodes is too small");
} }
public String generateRandomAlphaNum(int length) {
String SALTCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
StringBuilder salt = new StringBuilder();
Random rnd = new Random();
while (salt.length() < length) { // length of the random string.
int index = (int) (rnd.nextFloat() * SALTCHARS.length());
salt.append(SALTCHARS.charAt(index));
}
return salt.toString();
}
@Test
@Tag("B")
@DisplayName("Check compareTo for longer strings")
public void testCompareTo() {
int NUM_TRIALS = 3000;
for (int i = 0; i < NUM_TRIALS; i ++) {
String s1 = generateRandomAlphaNum(32);
String s2 = generateRandomAlphaNum(32);
assertEquals(compareStrings(s1, s2), Integer.signum(s1.compareTo(s2)),
"NGram.compareTo is incorrect for longer strings. This may have been caused by comparing hash codes.");
}
}
} }
...@@ -18,11 +18,11 @@ import java.util.List; ...@@ -18,11 +18,11 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
public class Inspection { 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 (Node d : codeObjects) {
for (String regex : regexps) { for (String regex : regexps) {
if (d.toString().replaceAll("\\R", "").matches(".*" + regex + ".*")) { if (d.toString().replaceAll("\\R", "").matches(".*" + regex + ".*")) {
return regex; return new String[]{d.toString().replaceAll("\\R", ""), regex};
} }
} }
} }
...@@ -32,9 +32,25 @@ public class Inspection { ...@@ -32,9 +32,25 @@ public class Inspection {
public static void assertNoImportsOf(String filePath, List<String> regexps) { public static void assertNoImportsOf(String filePath, List<String> regexps) {
try { try {
CompilationUnit cu = JavaParser.parse(new File(filePath)); CompilationUnit cu = JavaParser.parse(new File(filePath));
String usage = getUsageOf(regexps, cu.getImports()); String[] usage = getUsageOf(regexps, cu.getImports());
if (usage != null) { 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) { } catch (FileNotFoundException e) {
fail("Missing Java file: " + Paths.get(filePath).getFileName()); fail("Missing Java file: " + Paths.get(filePath).getFileName());
...@@ -66,16 +82,16 @@ public class Inspection { ...@@ -66,16 +82,16 @@ public class Inspection {
List<ConstructorDeclaration> constructors = new ArrayList<>(); List<ConstructorDeclaration> constructors = new ArrayList<>();
CONSTRUCTOR_COLLECTOR.visit(cu, constructors); CONSTRUCTOR_COLLECTOR.visit(cu, constructors);
String usage = getUsageOf(regexps, constructors); String[] usage = getUsageOf(regexps, constructors);
if (usage != null) { 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<>(); List<MethodDeclaration> methods = new ArrayList<>();
METHOD_COLLECTOR.visit(cu, methods); METHOD_COLLECTOR.visit(cu, methods);
usage = getUsageOf(regexps, methods); usage = getUsageOf(regexps, methods);
if (usage != null) { 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) { } catch (FileNotFoundException e) {
fail("Missing Java file: " + Paths.get(filePath).getFileName()); fail("Missing Java file: " + Paths.get(filePath).getFileName());
...@@ -93,8 +109,7 @@ public class Inspection { ...@@ -93,8 +109,7 @@ public class Inspection {
List<Statement> statements = body.getStatements(); List<Statement> statements = body.getStatements();
if (statements.size() != 1) { if (statements.size() != 1) {
nonThisConstructors++; nonThisConstructors++;
} } else if (!statements.get(0).toString().startsWith("this(")) {
else if (!statements.get(0).toString().startsWith("this(")) {
nonThisConstructors++; nonThisConstructors++;
} }
......
package edu.caltech.cs2.helpers;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Archie Shahidullah <archie@caltech.edu>
*/
public class MoveToFrontChecker {
/**
* This method checks whether a given class is a linked node or not.
*
* @param clazz the class you want to check
*/
public static void isNode(Class clazz) {
// Check if class is private
if (!Modifier.isPrivate(clazz.getModifiers())) {
fail("Class " + clazz.getTypeName() + " is not private");
}
// Check if class is static
if (!Modifier.isStatic(clazz.getModifiers())) {
fail("Class " + clazz.getTypeName() + " is not static");
}
// Get fields
SortedSet<String> fields = new TreeSet<>(Stream.of(clazz.getDeclaredFields())
.map(x -> x.getName())
.collect(Collectors.toList()));
int hasData = 0;
boolean hasNode = false;
// Check fields
for (String field : fields) {
Field f = null;
try {
f = clazz.getDeclaredField(field);
f.setAccessible(true);
} catch (NoSuchFieldException ex) {
ex.printStackTrace();
}
if (f.getType().toString().equals("class " + clazz.getTypeName())) {
if (hasNode) {
// Returns false is the list is doubly linked
fail("Class \"" + clazz.getName() + "\" is a doubly linked node");
return;
}
// Linked to another node
hasNode = true;
} else if (f.getType().toString().equals("class java.lang.Object")) {
// Value can't be final! It's a _dictionary_, so we need to be able to update the value at a given Node
// if (!Modifier.isFinal(f.getModifiers())) {
// fail("Field \"" + field + "\" in class \"" + clazz.getName() + "\" is not final");
// }
// Has a generic type to store data
if (hasData > 2) {
// Checks for multiple data fields
fail("Class \"" + clazz.getName() + "\" has too many generic fields: \"" + field + "\"");
return;
}
hasData++;
} else {
fail("Field \"" + field + "\" is not a generic type or " + clazz.getTypeName());
}
}
if (hasData != 2) {
fail(clazz.getName() + " does not have two generic types as key and value");
}
// Get constructors
Constructor[] constructors = clazz.getConstructors();
// Checks arguments to the constructors
for (Constructor c : constructors) {
int hasConstructor = 0;
for (Class type : c.getParameterTypes()) {
if (type.toString().equals("class java.lang.Object")) {
if (hasConstructor > 2) {
// Checks for multiple arguments
fail("Constructor \"" + c.getName() + "\" has too many generic arguments");
return;
}
hasConstructor++;
} else if (!type.toString().equals("class " + clazz.getTypeName())) {
// Check for invalid argument types
fail("Constructor \"" + c.getName() + "\" has an argument that is not a " + "generic type or "
+ clazz.getTypeName());
}
}
if (hasConstructor != 2) {
fail("Constructor \"" + c.getName() + "\" does not have two generic type arguments");
}
}
}
public static Class getNodeClass(Class clazz) {
Class[] classes = clazz.getDeclaredClasses();
for (Class c : classes) {
try {
isNode(c);
} catch (AssertionError e) {
// Is not a node class so continue searching
continue;
}
return c;
}
fail("There are no valid node classes in " + clazz.getName() +
". Make sure the test \"Check for linked node class\" passes.");
// Should never reach here
return null;
}
public static void checkClass(Class clazz) {
// Get fields
SortedSet<String> fields = new TreeSet<>(Stream.of(clazz.getDeclaredFields())
.map(x -> x.getName())
.collect(Collectors.toList()));
// Get node class
Class nodeClass = getNodeClass(clazz);
boolean hasHead = false;
boolean hasSize = false;
/*
* Ensure the implementation only has a head and int field
* Primarily to deter keeping separate key and value lists
*/
for (String field : fields) {
Field f = null;
try {
f = clazz.getDeclaredField(field);
f.setAccessible(true);
} catch (NoSuchFieldException ex) {
ex.printStackTrace();
}
if (f.getType().toString().equals(int.class.getName())) {
if (hasSize) {
fail("Class \"" + clazz.getName() + "\" has multiple int fields");
}
hasSize = true;
}
else if (f.getType().toString().equals("class " + nodeClass.getName())) {
if (hasHead) {
fail("Class \"" + clazz.getName() + "\" has multiple \"" +
nodeClass.getName() + "\" fields");
}
hasHead = true;
}
else {
fail("\"" + field + "\" is not a node object or int");
}
}
}
public static List<Field> getFields(Class clazz, Class type) {
Field[] fields = clazz.getDeclaredFields();
List<Field> namedFields = new ArrayList<>();
for (Field f : fields) {
f.setAccessible(true);
if (f.getType().toString().equals("class " + type.getTypeName())) {
namedFields.add(f);
}
}
return namedFields;
}
}
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