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

Merge branch 'fixes' into 'master'

Fixes

See merge request !1
1 merge request!1Fixes
Pipeline #76080 failed with stage
in 0 seconds
Showing with 852 additions and 744 deletions
+852 -744
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
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<component name="PreferredVcsStorage"> <component name="PreferredVcsStorage">
<preferredVcsName>ApexVCS</preferredVcsName> <preferredVcsName>ApexVCS</preferredVcsName>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_15" default="true" 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
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<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" />
...@@ -13,4 +14,4 @@ ...@@ -13,4 +14,4 @@
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
</configuration> </configuration>
</component> </component>
\ No newline at end of file
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<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" />
...@@ -13,4 +14,4 @@ ...@@ -13,4 +14,4 @@
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
</configuration> </configuration>
</component> </component>
\ No newline at end of file
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<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" />
......
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="59dc1d65-5f60-42d1-aafd-1f41614da1fe" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/caltech/cs2/datastructures/ArrayDeque.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/caltech/cs2/datastructures/ArrayDeque.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/edu/caltech/cs2/datastructures/LinkedDeque.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/edu/caltech/cs2/datastructures/LinkedDeque.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/tests/edu/caltech/cs2/helpers/Inspection.java" beforeDir="false" afterPath="$PROJECT_DIR$/tests/edu/caltech/cs2/helpers/Inspection.java" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="RunManager" selected="JUnit.C Tests">
<list>
<item itemvalue="JUnit.C Tests" />
<item itemvalue="JUnit.B Tests" />
<item itemvalue="JUnit.A Tests" />
</list>
</component>
<component name="TaskManager">
<servers />
</component>
</project>
\ No newline at end of file
...@@ -15,5 +15,6 @@ ...@@ -15,5 +15,6 @@
<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-M1" 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.javassist:javassist:3.29.2-GA" level="project" />
</component> </component>
</module> </module>
\ No newline at end of file
...@@ -6,6 +6,10 @@ import java.util.Iterator; ...@@ -6,6 +6,10 @@ import java.util.Iterator;
public class CircularArrayFixedSizeQueue<E> implements IFixedSizeQueue<E> { public class CircularArrayFixedSizeQueue<E> implements IFixedSizeQueue<E> {
public CircularArrayFixedSizeQueue(int capacity) {
}
@Override @Override
public boolean isFull() { public boolean isFull() {
return false; return false;
...@@ -36,6 +40,16 @@ public class CircularArrayFixedSizeQueue<E> implements IFixedSizeQueue<E> { ...@@ -36,6 +40,16 @@ public class CircularArrayFixedSizeQueue<E> implements IFixedSizeQueue<E> {
return 0; return 0;
} }
@Override
public void add(E e) {
}
@Override
public void clear() {
}
@Override @Override
public Iterator<E> iterator() { public Iterator<E> iterator() {
return null; return null;
......
...@@ -4,7 +4,7 @@ package edu.caltech.cs2.interfaces; ...@@ -4,7 +4,7 @@ package edu.caltech.cs2.interfaces;
* This interface represents a queue - a data structure that can add elements at one end and remove them from the other. * This interface represents a queue - a data structure that can add elements at one end and remove them from the other.
* @param <E> Element type * @param <E> Element type
*/ */
public interface IQueue<E> extends Iterable<E> { public interface IQueue<E> extends Iterable<E>, ICollection<E> {
/** /**
* Adds an element to the back of the queue. * Adds an element to the back of the queue.
* @param e Element to add * @param e Element to add
......
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.*; import edu.caltech.cs2.interfaces.*;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.*; import java.util.*;
...@@ -21,302 +21,326 @@ import static org.junit.jupiter.api.Assertions.*; ...@@ -21,302 +21,326 @@ import static org.junit.jupiter.api.Assertions.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Tag("C") @Tag("C")
public class ArrayDequeTests implements IDequeTests, IStackTests, IQueueTests { @ExtendWith(TestExtension.class)
private static String ARRAY_DEQUE_SOURCE ="src/edu/caltech/cs2/datastructures/ArrayDeque.java"; public class ArrayDequeTests {
private static String ARRAY_DEQUE_SOURCE = "src/edu/caltech/cs2/datastructures/ArrayDeque.java";
private Constructor arrayDequeConstructor = Reflection.getConstructor(ArrayDeque.class); private Constructor arrayDequeConstructor = Reflection.getConstructor(ArrayDeque.class);
public ICollection<Object> newCollection() {
return Reflection.newInstance(arrayDequeConstructor);
}
public IDeque<Object> newDeque() {
return Reflection.newInstance(arrayDequeConstructor);
}
public IStack<Object> newStack() {
return Reflection.newInstance(arrayDequeConstructor);
}
public IQueue<Object> newQueue() {
return Reflection.newInstance(arrayDequeConstructor);
}
public IQueue<Object> newQueue(int size) {
return newQueue();
}
// ARRAYDEQUE-SPECIFIC TESTS ---------------------------------------- // ARRAYDEQUE-SPECIFIC TESTS ----------------------------------------
@Order(classSpecificTestLevel) @DisplayName("Style")
@DisplayName("Does not use or import disallowed classes") @Nested
@Test class StyleTests implements IStyleTests {
public void testForInvalidClasses() { @Order(classSpecificTestLevel)
List<String> regexps = List.of("java\\.util\\.(?!Iterator)", "java\\.lang\\.reflect", "java\\.io"); @DisplayName("There is an integer default capacity static field and an integer default grow factor static field")
Inspection.assertNoImportsOf(ARRAY_DEQUE_SOURCE, regexps); @TestDescription("This test checks that you use constants (static, final, private) fields for the default capacity and the growth factor when the array resizes.")
Inspection.assertNoUsageOf(ARRAY_DEQUE_SOURCE, regexps); @Test
} public void testConstantFields() {
Reflection.assertFieldsEqualTo(ArrayDeque.class, "static", 2);
Stream<Field> fields = Reflection.getFields(ArrayDeque.class);
fields.filter(Reflection.hasModifier("static")).forEach((field) -> {
Reflection.checkFieldModifiers(field, List.of("private", "static", "final"));
assertEquals(int.class, field.getType(), "static fields must be of type int");
});
}
@Order(classSpecificTestLevel) public int getMaxFields() {
@DisplayName("There is an integer default capacity static field and an integer default grow factor static field") return 5;
@Test }
public void testConstantFields() {
Reflection.assertFieldsEqualTo(ArrayDeque.class, "static", 2);
Stream<Field> fields = Reflection.getFields(ArrayDeque.class);
fields.filter(Reflection.hasModifier("static")).forEach((field) -> {
Reflection.checkFieldModifiers(field, List.of("private", "static", "final"));
assertEquals(int.class, field.getType(), "static fields must be of type int");
});
}
@Order(classSpecificTestLevel) public List<String> getPublicInterface() {
@DisplayName("The overall number of fields is small") return List.of(
@Test "addFront",
public void testSmallNumberOfFields() { "addBack",
Reflection.assertFieldsLessThan(ArrayDeque.class, "private", 5); "removeFront",
} "removeBack",
"enqueue",
"dequeue",
"push",
"pop",
"peek",
"peekFront",
"peekBack",
"iterator",
"size",
"toString"
);
}
@Order(classSpecificTestLevel) @Override
@DisplayName("There are no public fields") public String getSource() {
@Test return ARRAY_DEQUE_SOURCE;
public void testNoPublicFields() { }
Reflection.assertNoPublicFields(ArrayDeque.class);
}
@Order(classSpecificTestLevel) @Override
@DisplayName("There are no protected fields") public Class<?> getClazz() {
@Test return ArrayDeque.class;
public void testNoProtectedFields() { }
Reflection.assertNoProtectedFields(ArrayDeque.class);
} }
@Order(classSpecificTestLevel) // TIME COMPLEXITY TESTS ------------------------------------------------
@DisplayName("All fields in ArrayDeque have modifiers")
@Test
public void testFieldModifiers() {
Reflection.assertFieldModifiers(ArrayDeque.class);
}
@Order(classSpecificTestLevel) @Nested
@DisplayName("The public interface is correct") @DisplayName("Runtime Complexity")
@Test class RuntimeTests {
public void testPublicInterface() { @Order(complexityTestLevel)
Reflection.assertPublicInterface(ArrayDeque.class, List.of( @DisplayName("addFront() and removeFront() take linear time")
"addFront", @Timeout(value = 20, unit = SECONDS)
"addBack", @Test()
"removeFront", public void testFrontDequeOperationComplexity() {
"removeBack", Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
"enqueue", IDeque<Integer> q = new ArrayDeque<>();
"dequeue", for (int i = 0; i < numElements; i++) {
"push", q.addFront(i);
"pop", }
"peek", return q;
"peekFront", };
"peekBack", Consumer<IDeque<Integer>> addFront = (IDeque<Integer> q) -> q.addFront(0);
"iterator", Consumer<IDeque<Integer>> removeFront = (IDeque<Integer> q) -> q.removeFront();
"size",
"toString" RuntimeInstrumentation.assertAtMost("addFront", RuntimeInstrumentation.ComplexityType.LINEAR, provide, addFront, 8);
)); RuntimeInstrumentation.assertAtMost("removeFront", RuntimeInstrumentation.ComplexityType.LINEAR, provide, removeFront, 8);
} }
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("Uses this(...) notation in all but one constructor") @DisplayName("addBack() and removeBack() take constant time")
@Test @Timeout(value = 20, unit = SECONDS)
public void testForThisConstructors() { @Test
Inspection.assertConstructorHygiene(ARRAY_DEQUE_SOURCE); public void testBackDequeOperationComplexity() {
} Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.addBack(i);
}
return q;
};
Consumer<IDeque<Integer>> addBack = (IDeque<Integer> q) -> q.addBack(0);
Consumer<IDeque<Integer>> removeBack = (IDeque<Integer> q) -> q.removeBack();
RuntimeInstrumentation.assertAtMost("addBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, addBack, 8);
RuntimeInstrumentation.assertAtMost("removeBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, removeBack, 8);
}
// ARRAYDEQUE TESTS --------------------------------------------------- @Order(complexityTestLevel)
@DisplayName("enqueue() and dequeue() take linear time")
@Timeout(value = 20, unit = SECONDS)
@Test
public void testQueueOperationComplexity() {
Function<Integer, IQueue<Integer>> provide = (Integer numElements) -> {
IQueue<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.enqueue(i);
}
return q;
};
Consumer<IQueue<Integer>> enqueue = (IQueue<Integer> q) -> q.enqueue(0);
Consumer<IQueue<Integer>> dequeue = (IQueue<Integer> q) -> q.dequeue();
RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.LINEAR, provide, enqueue, 8);
RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.LINEAR, provide, dequeue, 8);
}
@Order(implSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("The default capacity of the array is 10") @DisplayName("push() and pop() take constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testArrayDequeDefaultInitialCapacity() throws IllegalAccessException { @Test
ArrayDeque<Integer> impl = new ArrayDeque<>(); public void testStackOperationComplexity() {
Function<Integer, IStack<Integer>> provide = (Integer numElements) -> {
IStack<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.push(i);
}
return q;
};
Consumer<IStack<Integer>> push = (IStack<Integer> q) -> q.push(0);
Consumer<IStack<Integer>> pop = (IStack<Integer> q) -> q.pop();
RuntimeInstrumentation.assertAtMost("push", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, push, 8);
RuntimeInstrumentation.assertAtMost("pop", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, pop, 8);
}
// Reflect and get the backing array @Order(complexityTestLevel)
// It's actually an Object[] since that's how it (should!) be initialized internally @DisplayName("peek() takes constant time")
// Casting it doesn't change the type of the field. @Timeout(value = 10, unit = SECONDS)
// It's fine since there should only be one array. @Test
Field arr = Reflection.getFieldByType(ArrayDeque.class, Object[].class); public void testPeekComplexity() {
arr.setAccessible(true); Function<Integer, IStack<Integer>> provide = (Integer numElements) -> {
Object[] backingArray = (Object[]) arr.get(impl); IStack<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.push(i);
}
return q;
};
Consumer<IStack<Integer>> peek = (IStack<Integer> q) -> q.peek();
RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8);
}
assertEquals(10, backingArray.length, "Default initial capacity is not 10"); @Order(complexityTestLevel)
} @DisplayName("peekFront() takes constant time")
@Timeout(value = 10, unit = SECONDS)
@Test()
public void testPeekFrontComplexity() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.addFront(i);
}
return q;
};
Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront();
RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront, 8);
}
@Order(implSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("enqueue should always succeed") @DisplayName("peekBack() takes constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testThatArrayDequeEnqueueAlwaysSucceeds() { @Test
ArrayDeque<Integer> impl = new ArrayDeque<>(); public void testPeekBackComplexity() {
for (int i = 0; i < 100; i ++) { Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
assertTrue(impl.enqueue(i), "enqueue() should always succeed for ArrayDeque"); IDeque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.addBack(i);
}
return q;
};
Consumer<IDeque<Integer>> peekBack = (IDeque<Integer> q) -> q.peekBack();
RuntimeInstrumentation.assertAtMost("peekBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekBack, 8);
} }
} }
@Order(implSpecificTestLevel) @Nested
@DisplayName("push should always succeed") @DisplayName("IStack Functionality")
@Test class StackTests implements IStackTests {
public void testThatArrayDequePushAlwaysSucceeds() { @Override
ArrayDeque<Integer> impl = new ArrayDeque<>(); public IStack<Object> newStack() {
for (int i = 0; i < 100; i ++) { return Reflection.newInstance(arrayDequeConstructor);
assertTrue(impl.push(i), "push() should always succeed for ArrayDeque");
} }
} }
// TOSTRING TESTS --------------------------------------------------- @Nested
@DisplayName("IQueue Functionality")
class QueueTests implements IQueueTests {
@Order(toStringTestLevel) public IQueue<Object> newQueue() {
@DisplayName("toString is correctly overridden") return Reflection.newInstance(arrayDequeConstructor);
@Test }
public void testToStringOverride() {
Reflection.assertMethodCorrectlyOverridden(ArrayDeque.class, "toString");
}
@Order(toStringTestLevel) public IQueue<Object> newQueue(int size) {
@DisplayName("toString() matches java.util.ArrayDeque") return newQueue();
@ParameterizedTest(name = "Test toString() on [{arguments}]")
@ValueSource(strings = {
"0, 1, 2, 3", "5, 4, 3, 2, 1", "8, 3, 5, 7, 4, 3, 12, 12, 1"
})
public void testToString(String inputs) {
java.util.ArrayDeque<String> reference = new java.util.ArrayDeque<String>();
edu.caltech.cs2.datastructures.ArrayDeque<String> me = new edu.caltech.cs2.datastructures.ArrayDeque<>();
for (String value : inputs.trim().split(", ")) {
assertEquals(reference.toString(), me.toString(), "toString outputs should be the same");
reference.addLast(value);
me.addBack(value);
} }
} }
// TIME COMPLEXITY TESTS ------------------------------------------------ @Nested
@DisplayName("IDeque Functionality")
@Order(complexityTestLevel) class DequeTests implements IDequeTests {
@DisplayName("addFront() and removeFront() take linear time") @Override
@Timeout(value = 20, unit = SECONDS) public IDeque<Object> newDeque() {
@Test() return Reflection.newInstance(arrayDequeConstructor);
public void testFrontDequeOperationComplexity() { }
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.addFront(i);
}
return q;
};
Consumer<IDeque<Integer>> addFront = (IDeque<Integer> q) -> q.addFront(0);
Consumer<IDeque<Integer>> removeFront = (IDeque<Integer> q) -> q.removeFront();
RuntimeInstrumentation.assertAtMost("addFront", RuntimeInstrumentation.ComplexityType.LINEAR, provide, addFront, 8);
RuntimeInstrumentation.assertAtMost("removeFront", RuntimeInstrumentation.ComplexityType.LINEAR, provide, removeFront, 8);
} }
@Order(complexityTestLevel) @Nested
@DisplayName("addBack() and removeBack() take constant time") @DisplayName("ICollection Functionality")
@Timeout(value = 20, unit = SECONDS) class CollectionTests implements ICollectionTests {
@Test @Override
public void testBackDequeOperationComplexity() { public ICollection<Object> newCollection() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> { return Reflection.newInstance(arrayDequeConstructor);
IDeque<Integer> q = new ArrayDeque<>(); }
for (int i = 0; i < numElements; i++) {
q.addBack(i);
}
return q;
};
Consumer<IDeque<Integer>> addBack = (IDeque<Integer> q) -> q.addBack(0);
Consumer<IDeque<Integer>> removeBack = (IDeque<Integer> q) -> q.removeBack();
RuntimeInstrumentation.assertAtMost("addBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, addBack, 8);
RuntimeInstrumentation.assertAtMost("removeBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, removeBack, 8);
} }
@Order(complexityTestLevel) @DisplayName("Implementation Tests")
@DisplayName("enqueue() and dequeue() take linear time") @Nested
@Timeout(value = 20, unit = SECONDS) class ImplementationTests {
@Test @Order(implSpecificTestLevel)
public void testQueueOperationComplexity() { @DisplayName("The default capacity of the array in the deque is 10")
Function<Integer, IQueue<Integer>> provide = (Integer numElements) -> { @DependsOn({"fields", "constructors"})
IQueue<Integer> q = new ArrayDeque<>(); @Test
for (int i = 0; i < numElements; i++) { public void testArrayDequeDefaultInitialCapacity() throws IllegalAccessException {
q.enqueue(i); ArrayDeque<Integer> impl = new ArrayDeque<>();
}
return q; // Reflect and get the backing array
}; // It's actually an Object[] since that's how it (should!) be initialized internally
Consumer<IQueue<Integer>> enqueue = (IQueue<Integer> q) -> q.enqueue(0); // Casting it doesn't change the type of the field.
Consumer<IQueue<Integer>> dequeue = (IQueue<Integer> q) -> q.dequeue(); // It's fine since there should only be one array.
Field arr = Reflection.getFieldByType(ArrayDeque.class, Object[].class);
RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.LINEAR, provide, enqueue, 8); arr.setAccessible(true);
RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.LINEAR, provide, dequeue, 8); Object[] backingArray = (Object[]) arr.get(impl);
}
assertEquals(10, backingArray.length, "Default initial capacity is not 10");
}
@Order(complexityTestLevel) @Order(implSpecificTestLevel)
@DisplayName("push() and pop() take constant time") @DisplayName("enqueue should always succeed")
@Timeout(value = 10, unit = SECONDS) @TestDescription("This test is checking every enqueue in your ArrayDeque returns true.\nThe only time an enqueue might return false is if it doesn't resize (like in the B Tests).")
@Test @DependsOn({"fields", "constructors", "enqueue"})
public void testStackOperationComplexity() { @Test
Function<Integer, IStack<Integer>> provide = (Integer numElements) -> { public void testThatArrayDequeEnqueueAlwaysSucceeds() {
IStack<Integer> q = new ArrayDeque<>(); ArrayDeque<Integer> impl = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) { for (int i = 0; i < 100; i++) {
q.push(i); assertTrue(impl.enqueue(i), "enqueue() should always succeed for ArrayDeque");
} }
return q; }
};
Consumer<IStack<Integer>> push = (IStack<Integer> q) -> q.push(0);
Consumer<IStack<Integer>> pop = (IStack<Integer> q) -> q.pop();
RuntimeInstrumentation.assertAtMost("push", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, push, 8);
RuntimeInstrumentation.assertAtMost("pop", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, pop, 8);
}
@Order(complexityTestLevel) @Order(implSpecificTestLevel)
@DisplayName("peek() takes constant time") @DisplayName("Check for excessive array allocation in addFront and removeFront")
@Timeout(value = 10, unit = SECONDS) @TestDescription("This test is checking that you are not allocating extra arrays in add/remove that are not necessary.")
@Test @DependsOn({"fields", "constructors", "addFront", "removeFront"})
public void testPeekComplexity() { @Test
Function<Integer, IStack<Integer>> provide = (Integer numElements) -> { public void testForExcessiveArrayAllocationAddFront() {
IStack<Integer> q = new ArrayDeque<>(); NewObjectArray.NUM_CALLS = 0;
for (int i = 0; i < numElements; i++) { ArrayDeque<Integer> impl = new ArrayDeque<>();
q.push(i); for (int i = 0; i < 100; i++) {
int before = NewObjectArray.NUM_CALLS;
impl.addFront(i);
int after = NewObjectArray.NUM_CALLS;
assertTrue(before + 1 >= after, "Each addFront() should create at most one new array");
} }
return q; assertTrue(NewObjectArray.NUM_CALLS < 2 * ((int) (Math.log(100) / Math.log(2))), "addFront() should not use an excessive number of new arrays");
}; for (int i = 0; i < 100; i++) {
Consumer<IStack<Integer>> peek = (IStack<Integer> q) -> q.peek(); int before = NewObjectArray.NUM_CALLS;
impl.removeFront();
RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); int after = NewObjectArray.NUM_CALLS;
} assertTrue(before == after, "removeFront() should not allocate any new arrays");
@Order(complexityTestLevel)
@DisplayName("peekFront() takes constant time")
@Timeout(value = 10, unit = SECONDS)
@Test()
public void testPeekFrontComplexity() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < numElements; i++) {
q.addFront(i);
} }
return q; }
};
Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront();
RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront, 8);
}
@Order(complexityTestLevel) @Order(implSpecificTestLevel)
@DisplayName("peekBack() takes constant time") @DisplayName("Check for excessive array allocation in addBack and removeBack")
@Timeout(value = 10, unit = SECONDS) @TestDescription("This test is checking that you are not allocating extra arrays in add/remove that are not necessary.")
@Test @DependsOn({"fields", "constructors", "addBack", "removeBack"})
public void testPeekBackComplexity() { @Test
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> { public void testForExcessiveArrayAllocationAddBack() {
IDeque<Integer> q = new ArrayDeque<>(); NewObjectArray.NUM_CALLS = 0;
for (int i = 0; i < numElements; i++) { ArrayDeque<Integer> impl = new ArrayDeque<>();
q.addBack(i); for (int i = 0; i < 100; i++) {
int before = NewObjectArray.NUM_CALLS;
impl.addBack(i);
int after = NewObjectArray.NUM_CALLS;
assertTrue(before + 1 >= after, "Each addBack() should create at most one new array");
} }
return q; assertTrue(NewObjectArray.NUM_CALLS < 2 * ((int) (Math.log(100) / Math.log(2))), "addBack() should not use an excessive number of new arrays");
}; for (int i = 0; i < 100; i++) {
Consumer<IDeque<Integer>> peekBack = (IDeque<Integer> q) -> q.peekBack(); int before = NewObjectArray.NUM_CALLS;
impl.removeBack();
int after = NewObjectArray.NUM_CALLS;
assertTrue(before == after, "removeBack() should not allocate any new arrays");
}
}
RuntimeInstrumentation.assertAtMost("peekBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekBack, 8); @Order(implSpecificTestLevel)
@DisplayName("push should always succeed")
@TestDescription("This test is checking every push in your ArrayDeque returns true.\nThe only time a push might return false is if it doesn't resize (like in the B Tests).")
@DependsOn({"fields", "constructors", "push"})
@Test
public void testThatArrayDequePushAlwaysSucceeds() {
ArrayDeque<Integer> impl = new ArrayDeque<>();
for (int i = 0; i < 100; i++) {
assertTrue(impl.push(i), "push() should always succeed for ArrayDeque");
}
}
} }
}
}
\ 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.interfaces.*;
import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.IFixedSizeQueueTests;
import edu.caltech.cs2.interfaces.IFixedSizeQueue;
import edu.caltech.cs2.interfaces.IQueue;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
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 org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*; import java.util.*;
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 static edu.caltech.cs2.project03.Project03TestOrdering.*; import static edu.caltech.cs2.project03.Project03TestOrdering.*;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
...@@ -22,174 +21,153 @@ import static org.junit.jupiter.api.Assertions.*; ...@@ -22,174 +21,153 @@ import static org.junit.jupiter.api.Assertions.*;
@Tag("B") @Tag("B")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CircularArrayFixedSizeQueueTests implements IFixedSizeQueueTests { @ExtendWith(TestExtension.class)
public class CircularArrayFixedSizeQueueTests {
private static String FIXED_QUEUE_SOURCE = "src/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueue.java"; private static String FIXED_QUEUE_SOURCE = "src/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueue.java";
private Constructor circFixedSizeQueueConstructor = Reflection.getConstructor(CircularArrayFixedSizeQueue.class, private Constructor circFixedSizeQueueConstructor = Reflection.getConstructor(CircularArrayFixedSizeQueue.class,
int.class); int.class);
private int DEFAULT_CAPACITY = 10;
public IQueue<Object> newQueue() {
return Reflection.newInstance(circFixedSizeQueueConstructor, DEFAULT_CAPACITY);
}
public IQueue<Object> newQueue(int capacity) {
return Reflection.newInstance(circFixedSizeQueueConstructor, capacity);
}
public IFixedSizeQueue<Object> newFixedSizeQueue(int capacity) { public IFixedSizeQueue<Object> newFixedSizeQueue(int capacity) {
return Reflection.newInstance(circFixedSizeQueueConstructor, capacity); return Reflection.newInstance(circFixedSizeQueueConstructor, capacity);
} }
// FIXED QUEUE-SPECIFIC TESTS ---------------------------------------- @DisplayName("Style")
@Nested
@Order(classSpecificTestLevel) class StyleTests implements IStyleTests {
@DisplayName("Does not use or import disallowed classes") public int getMaxFields() {
@Test return 4;
public void testForInvalidClasses() { }
List<String> regexps = List.of("java\\.util\\.(?!Iterator)", "java\\.lang\\.reflect", "java\\.io");
Inspection.assertNoImportsOf(FIXED_QUEUE_SOURCE, regexps);
Inspection.assertNoUsageOf(FIXED_QUEUE_SOURCE, regexps);
}
@Order(classSpecificTestLevel) public List<String> getPublicInterface() {
@DisplayName("There are no static fields") return List.of("enqueue", "dequeue", "peek", "iterator", "size", "isFull", "capacity", "toString", "add", "clear");
@Test }
public void testConstantFields() {
Reflection.assertFieldsEqualTo(CircularArrayFixedSizeQueue.class, "static", 0);
}
@Order(classSpecificTestLevel) @Override
@DisplayName("The overall number of fields is small") public String getSource() {
@Test return FIXED_QUEUE_SOURCE;
public void testSmallNumberOfFields() { }
Reflection.assertFieldsLessThan(CircularArrayFixedSizeQueue.class, "private", 4);
}
@Order(classSpecificTestLevel) @Override
@DisplayName("There are no public fields") public Class<?> getClazz() {
@Test return CircularArrayFixedSizeQueue.class;
public void testNoPublicFields() { }
Reflection.assertNoPublicFields(CircularArrayFixedSizeQueue.class);
} }
@Order(classSpecificTestLevel) @Nested
@DisplayName("There are no protected fields") @DisplayName("Runtime Complexity")
@Test class RuntimeTests {
public void testNoProtectedFields() { @Order(complexityTestLevel)
Reflection.assertNoProtectedFields(CircularArrayFixedSizeQueue.class); @DisplayName("enqueue() and dequeue() take constant time")
} @Timeout(value = 10, unit = SECONDS)
@Test()
public void testQueueOperationComplexity() {
Function<Integer, IFixedSizeQueue<Integer>> provide = (Integer numElements) -> {
Constructor c = Reflection.getConstructor(CircularArrayFixedSizeQueue.class, int.class);
IFixedSizeQueue<Integer> q = Reflection.newInstance(c, numElements * 2);
for (int i = 0; i < numElements; i++) {
q.enqueue(i);
}
return q;
};
Consumer<IFixedSizeQueue<Integer>> enqueue = (IFixedSizeQueue<Integer> q) -> q.enqueue(0);
Consumer<IFixedSizeQueue<Integer>> dequeue = (IFixedSizeQueue<Integer> q) -> q.dequeue();
RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, enqueue, 8);
RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, dequeue, 8);
}
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("All fields in CircularArrayFixedSizeQueue have modifiers") @DisplayName("peek() takes constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testFieldModifiers() { @Test()
Reflection.assertFieldModifiers(CircularArrayFixedSizeQueue.class); public void testPeekComplexity() {
Function<Integer, IFixedSizeQueue<Integer>> provide = (Integer numElements) -> {
Constructor c = Reflection.getConstructor(CircularArrayFixedSizeQueue.class, int.class);
IFixedSizeQueue<Integer> q = Reflection.newInstance(c, numElements * 2);
for (int i = 0; i < numElements; i++) {
q.enqueue(i);
}
return q;
};
Consumer<IFixedSizeQueue<Integer>> peek = (IFixedSizeQueue<Integer> q) -> q.peek();
RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8);
}
} }
@Order(classSpecificTestLevel) @Nested
@DisplayName("The public interface is correct") @DisplayName("IFixedSizeQueue Functionality")
@Test class QueueTests implements IFixedSizeQueueTests {
public void testPublicInterface() {
Reflection.assertPublicInterface(CircularArrayFixedSizeQueue.class,
List.of("enqueue", "dequeue", "peek", "iterator", "size", "isFull", "capacity", "toString"));
}
@Order(classSpecificTestLevel) public IFixedSizeQueue<Object> newQueue() {
@DisplayName("Uses this(...) notation in all but one constructor") return Reflection.newInstance(circFixedSizeQueueConstructor);
@Test }
public void testForThisConstructors() {
Inspection.assertConstructorHygiene(FIXED_QUEUE_SOURCE);
}
// TOSTRING TESTS --------------------------------------------------- public IFixedSizeQueue<Object> newQueue(int size) {
return Reflection.newInstance(circFixedSizeQueueConstructor, size);
}
@Order(toStringTestLevel) @Override
@DisplayName("toString is correctly overridden") public IFixedSizeQueue<Object> newFixedSizeQueue(int capacity) {
@Test return CircularArrayFixedSizeQueueTests.this.newFixedSizeQueue(capacity);
public void testToStringOverride() { }
Reflection.assertMethodCorrectlyOverridden(ArrayDeque.class, "toString");
} }
@Order(toStringTestLevel) @Nested
@DisplayName("toString() matches java.util.ArrayDeque") @DisplayName("ICollection Functionality")
@ParameterizedTest(name = "Test toString() on [{arguments}]") class CollectionTests implements ICollectionTests {
@ValueSource(strings = { "0, 1, 2, 3", "5, 4, 3, 2, 1", "8, 3, 5, 7, 4, 3, 12, 12, 1" }) @Override
public void testToString(String inputs) { public ICollection<Object> newCollection() {
java.util.ArrayDeque<String> reference = new java.util.ArrayDeque<String>(); return Reflection.newInstance(circFixedSizeQueueConstructor, 100000);
Constructor c = Reflection.getConstructor(CircularArrayFixedSizeQueue.class, int.class);
IFixedSizeQueue<String> me = Reflection.newInstance(c, inputs.length());
for (String value : inputs.trim().split(", ")) {
assertEquals(reference.toString(), me.toString(), "toString outputs should be the same");
reference.addLast(value);
me.enqueue(value);
} }
} }
// TIME COMPLEXITY TESTS ------------------------------------------------ @DisplayName("Implementation Tests")
@Nested
@Order(complexityTestLevel) class ImplementationTests {
@DisplayName("enqueue() and dequeue() take constant time") @Order(fixedSizeQueueLevel)
@Timeout(value = 10, unit = SECONDS) @DisplayName("Test iterator matches reference for wraparound values")
@Test() @TestHint("This test is likely failing because you don't have correct wrap-around behavior.\n Remember that everything should advance; so, it's possible that your start wraps around.")
public void testQueueOperationComplexity() { @ParameterizedTest(name = "Test iterator and wraparound behavior with {1} random values with seed = {0} and fixed array size = {2}")
Function<Integer, IFixedSizeQueue<Integer>> provide = (Integer numElements) -> { @CsvSource({"69, 200, 20", "21, 300, 200"})
Constructor c = Reflection.getConstructor(CircularArrayFixedSizeQueue.class, int.class); public void testWrapAround(int seed, int numVals, int queueSize) {
IFixedSizeQueue<Integer> q = Reflection.newInstance(c, numElements * 2); Random r = new Random(seed);
for (int i = 0; i < numElements; i++) { IFixedSizeQueue<Object> me = newFixedSizeQueue(queueSize);
q.enqueue(i); Queue<Object> reference = new java.util.ArrayDeque<>();
} assertEquals(queueSize, me.capacity(), "capacity does not match expected value");
return q; for (int i = 0; i < queueSize; i++) {
}; int num = r.nextInt();
Consumer<IFixedSizeQueue<Integer>> enqueue = (IFixedSizeQueue<Integer> q) -> q.enqueue(0); assertFalse(me.isFull(), "queue should not be full");
Consumer<IFixedSizeQueue<Integer>> dequeue = (IFixedSizeQueue<Integer> q) -> q.dequeue(); assertTrue(me.enqueue(num), "enqueue should be successful");
reference.add(num);
RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, enqueue, 8);
RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, dequeue, 8);
}
@Order(complexityTestLevel)
@DisplayName("peek() takes constant time")
@Timeout(value = 10, unit = SECONDS)
@Test()
public void testPeekComplexity() {
Function<Integer, IFixedSizeQueue<Integer>> provide = (Integer numElements) -> {
Constructor c = Reflection.getConstructor(CircularArrayFixedSizeQueue.class, int.class);
IFixedSizeQueue<Integer> q = Reflection.newInstance(c, numElements * 2);
for (int i = 0; i < numElements; i++) {
q.enqueue(i);
} }
return q; for (int i = 0; i < numVals; i++) {
}; me.enqueue(me.dequeue());
Consumer<IFixedSizeQueue<Integer>> peek = (IFixedSizeQueue<Integer> q) -> q.peek(); reference.add(reference.remove());
assertEquals(reference.peek(), me.peek(), "return values of peek()s are not equal");
RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); assertEquals(reference.size(), me.size(), "size()s are not equal");
} assertEquals(queueSize, me.capacity(), "capacity of a fixed size queue should not change");
assertIterableEquals(reference, me, "Reference and implemented queues are not equal");
@Order(fixedSizeQueueLevel) }
@DisplayName("Test iterator matches reference for wraparound values")
@ParameterizedTest(name = "Test iterator and wraparound behavior with {1} random values with seed = {0} and fixed array size = {2}")
@CsvSource({ "69, 200, 20", "21, 300, 200" })
public void testWrapAround(int seed, int numVals, int queueSize) {
Random r = new Random(seed);
IFixedSizeQueue<Object> me = newFixedSizeQueue(queueSize);
Queue<Object> reference = new java.util.ArrayDeque<>();
assertEquals(queueSize, me.capacity(), "capacity does not match expected value");
for (int i = 0; i < queueSize; i++) {
int num = r.nextInt();
assertFalse(me.isFull(), "queue should not be full");
assertTrue(me.enqueue(num), "enqueue should be successful");
reference.add(num);
} }
for (int i = 0; i < numVals; i++) {
me.enqueue(me.dequeue()); @Order(toStringTestLevel)
reference.add(reference.remove()); @DisplayName("toString() matches java.util.ArrayDeque")
assertEquals(reference.peek(), me.peek(), "return values of peek()s are not equal"); @DependsOn({"fields", "constructors", "toString", "enqueue"})
assertEquals(reference.size(), me.size(), "size()s are not equal"); @ParameterizedTest(name = "Test toString() on [{arguments}]")
assertEquals(queueSize, me.capacity(), "capacity of a fixed size queue should not change"); @ValueSource(strings = {
assertIterableEquals(reference, me, "Reference and implemented queues are not equal"); "0, 1, 2, 3", "5, 4, 3, 2, 1", "8, 3, 5, 7, 4, 3, 12, 12, 1"
})
public void testToString(String inputs) {
java.util.ArrayDeque<String> reference = new java.util.ArrayDeque<String>();
edu.caltech.cs2.interfaces.IFixedSizeQueue<Object> me = new CircularArrayFixedSizeQueue<>(10);
for (String value : inputs.trim().split(", ")) {
assertEquals(reference.toString(), me.toString(), "toString outputs should be the same");
reference.addLast(value);
me.enqueue(value);
}
} }
} }
} }
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.NodeChecker;
import edu.caltech.cs2.helpers.Reflection;
import edu.caltech.cs2.helpers.RuntimeInstrumentation;
import edu.caltech.cs2.interfaces.*; import edu.caltech.cs2.interfaces.*;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
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 org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.*; import java.util.*;
import java.util.ArrayDeque;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import static edu.caltech.cs2.project03.Project03TestOrdering.*; import static edu.caltech.cs2.project03.Project03TestOrdering.*;
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.*;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("C") @Tag("C")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LinkedDequeTests implements IDequeTests, IStackTests, IQueueTests { @ExtendWith(TestExtension.class)
public class LinkedDequeTests {
private static String LINKED_DEQUE_SOURCE = "src/edu/caltech/cs2/datastructures/LinkedDeque.java"; private static String LINKED_DEQUE_SOURCE = "src/edu/caltech/cs2/datastructures/LinkedDeque.java";
private Constructor linkedDequeConstructor = Reflection.getConstructor(LinkedDeque.class); private Constructor linkedDequeConstructor = Reflection.getConstructor(LinkedDeque.class);
public ICollection<Object> newCollection() { // LINKEDDEQUE-SPECIFIC TESTS ----------------------------------------
return Reflection.newInstance(linkedDequeConstructor); @DisplayName("Style")
} @Nested
class StyleTests implements IStyleTests {
@Order(classSpecificTestLevel)
@DisplayName("There are no static fields")
@TestHint("Remember that static fields belong to the class instead of the instance. For this class, you shouldn't need any.")
@Test
public void testConstantFields() {
Reflection.assertFieldsEqualTo(LinkedDeque.class, "static", 0);
}
public IDeque<Object> newDeque() { @Override
return Reflection.newInstance(linkedDequeConstructor); public String getSource() {
} return LINKED_DEQUE_SOURCE;
}
public IStack<Object> newStack() { @Override
return Reflection.newInstance(linkedDequeConstructor); public Class<?> getClazz() {
} return LinkedDeque.class;
}
public IQueue<Object> newQueue() { @Override
return Reflection.newInstance(linkedDequeConstructor); public List<String> getPublicInterface() {
} return List.of("addFront", "addBack", "removeFront", "removeBack",
"enqueue", "dequeue", "push", "pop", "peek", "peekFront", "peekBack", "iterator", "size", "toString");
}
@Override
public int getMaxFields() {
return 4;
}
public IQueue<Object> newQueue(int size) {
return newQueue();
} }
// LINKEDDEQUE-SPECIFIC TESTS ---------------------------------------- @Nested
@DisplayName("Runtime Complexity")
class RuntimeTests {
@Order(complexityTestLevel)
@DisplayName("addFront() and removeFront() take constant time")
@Timeout(value = 10, unit = SECONDS)
@Test
public void testFrontDequeOperationComplexity() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.addFront(i);
}
return q;
};
Consumer<IDeque<Integer>> addFront = (IDeque<Integer> q) -> q.addFront(0);
Consumer<IDeque<Integer>> removeFront = (IDeque<Integer> q) -> q.removeFront();
RuntimeInstrumentation.assertAtMost("addFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, addFront,
8);
RuntimeInstrumentation.assertAtMost("removeFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide,
removeFront, 8);
}
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("Does not use or import disallowed classes") @DisplayName("addBack() and removeBack() take constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testForInvalidClasses() { @Test
List<String> regexps = List.of("java\\.util\\.(?!Iterator)", "java\\.lang\\.reflect", "java\\.io"); public void testBackDequeOperationComplexity() {
Inspection.assertNoImportsOf(LINKED_DEQUE_SOURCE, regexps); Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
Inspection.assertNoUsageOf(LINKED_DEQUE_SOURCE, regexps); IDeque<Integer> q = new LinkedDeque<>();
} for (int i = 0; i < numElements; i++) {
q.addBack(i);
}
return q;
};
Consumer<IDeque<Integer>> addBack = (IDeque<Integer> q) -> q.addBack(0);
Consumer<IDeque<Integer>> removeBack = (IDeque<Integer> q) -> q.removeBack();
RuntimeInstrumentation.assertAtMost("addBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, addBack, 8);
RuntimeInstrumentation.assertAtMost("removeBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide,
removeBack, 8);
}
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("There are no static fields") @DisplayName("enqueue() and dequeue() take constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testConstantFields() { @Test
Reflection.assertFieldsEqualTo(LinkedDeque.class, "static", 0); public void testQueueOperationComplexity() {
} Function<Integer, IQueue<Integer>> provide = (Integer numElements) -> {
IQueue<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.enqueue(i);
}
return q;
};
Consumer<IQueue<Integer>> enqueue = (IQueue<Integer> q) -> q.enqueue(0);
Consumer<IQueue<Integer>> dequeue = (IQueue<Integer> q) -> q.dequeue();
@Order(classSpecificTestLevel) RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, enqueue, 8);
@DisplayName("The overall number of fields is small") RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, dequeue, 8);
@Test }
public void testSmallNumberOfFields() {
Reflection.assertFieldsLessThan(LinkedDeque.class, "private", 4);
}
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("There are no public fields") @DisplayName("push() and pop() take constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testNoPublicFields() { @Test
Reflection.assertNoPublicFields(LinkedDeque.class); public void testStackOperationComplexity() {
} Function<Integer, IStack<Integer>> provide = (Integer numElements) -> {
IStack<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.push(i);
}
return q;
};
Consumer<IStack<Integer>> push = (IStack<Integer> q) -> q.push(0);
Consumer<IStack<Integer>> pop = (IStack<Integer> q) -> q.pop();
@Order(classSpecificTestLevel) RuntimeInstrumentation.assertAtMost("push", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, push, 8);
@DisplayName("There are no protected fields") RuntimeInstrumentation.assertAtMost("pop", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, pop, 8);
@Test }
public void testNoProtectedFields() {
Reflection.assertNoProtectedFields(LinkedDeque.class);
}
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("All fields in LinkedDeque have modifiers") @DisplayName("peek() takes constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testFieldModifiers() { @Test
Reflection.assertFieldModifiers(LinkedDeque.class); public void testPeekComplexity() {
} Function<Integer, IStack<Integer>> provide = (Integer numElements) -> {
IStack<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.push(i);
}
return q;
};
Consumer<IStack<Integer>> peek = (IStack<Integer> q) -> q.peek();
@Order(classSpecificTestLevel) RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8);
@DisplayName("The public interface is correct") }
@Test
public void testPublicInterface() {
Reflection.assertPublicInterface(LinkedDeque.class, List.of("addFront", "addBack", "removeFront", "removeBack",
"enqueue", "dequeue", "push", "pop", "peek", "peekFront", "peekBack", "iterator", "size", "toString"));
}
@Order(classSpecificTestLevel) @Order(complexityTestLevel)
@DisplayName("Uses this(...) notation in all but one constructor") @DisplayName("peekFront() takes constant time")
@Test @Timeout(value = 10, unit = SECONDS)
public void testForThisConstructors() { @Test
Inspection.assertConstructorHygiene(LINKED_DEQUE_SOURCE); public void testPeekFrontComplexity() {
} Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.addFront(i);
}
return q;
};
Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront();
@Order(classSpecificTestLevel) RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront,
@DisplayName("Check that LinkedDeque uses a node class") 8);
@Test
public void testLinkedNode() {
Class[] classes = LinkedDeque.class.getDeclaredClasses();
for (Class clazz : classes) {
if (Iterator.class.isAssignableFrom(clazz)) {
continue;
}
NodeChecker.isNode(clazz, true);
} }
}
// TOSTRING TESTS --------------------------------------------------- @Order(complexityTestLevel)
@DisplayName("peekBack() takes constant time")
@Timeout(value = 10, unit = SECONDS)
@Test
public void testPeekBackComplexity() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.addBack(i);
}
return q;
};
Consumer<IDeque<Integer>> peekBack = (IDeque<Integer> q) -> q.peekBack();
@Order(toStringTestLevel) RuntimeInstrumentation.assertAtMost("peekBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekBack,
@DisplayName("toString is correctly overridden") 8);
@Test }
public void testToStringOverride() {
Reflection.assertMethodCorrectlyOverridden(LinkedDeque.class, "toString");
} }
@Order(toStringTestLevel) @Nested
@DisplayName("toString() matches java.util.ArrayDeque") @DisplayName("IStack Functionality")
@ParameterizedTest(name = "Test toString() on [{arguments}]") class StackTests implements IStackTests {
@ValueSource(strings = { "0, 1, 2, 3", "5, 4, 3, 2, 1", "8, 3, 5, 7, 4, 3, 12, 12, 1" }) @Override
public void testToString(String inputs) { public IStack<Object> newStack() {
java.util.ArrayDeque<String> reference = new java.util.ArrayDeque<String>(); return Reflection.newInstance(linkedDequeConstructor);
LinkedDeque<String> me = new LinkedDeque<>();
for (String value : inputs.trim().split(", ")) {
assertEquals(reference.toString(), me.toString(), "toString outputs should be the same");
reference.addLast(value);
me.addBack(value);
} }
} }
// TIME COMPLEXITY TESTS ------------------------------------------------ @Nested
@DisplayName("IQueue Functionality")
@Order(complexityTestLevel) class QueueTests implements IQueueTests {
@DisplayName("addFront() and removeFront() take constant time") public IQueue<Object> newQueue() {
@Timeout(value = 10, unit = SECONDS) return Reflection.newInstance(linkedDequeConstructor);
@Test }
public void testFrontDequeOperationComplexity() { public IQueue<Object> newQueue(int size) {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> { return newQueue();
IDeque<Integer> q = new LinkedDeque<>(); }
for (int i = 0; i < numElements; i++) {
q.addFront(i);
}
return q;
};
Consumer<IDeque<Integer>> addFront = (IDeque<Integer> q) -> q.addFront(0);
Consumer<IDeque<Integer>> removeFront = (IDeque<Integer> q) -> q.removeFront();
RuntimeInstrumentation.assertAtMost("addFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, addFront,
8);
RuntimeInstrumentation.assertAtMost("removeFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide,
removeFront, 8);
} }
@Order(complexityTestLevel) @Nested
@DisplayName("addBack() and removeBack() take constant time") @DisplayName("IDeque Functionality")
@Timeout(value = 10, unit = SECONDS) class DequeTests implements IDequeTests {
@Test @Override
public void testBackDequeOperationComplexity() { public IDeque<Object> newDeque() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> { return Reflection.newInstance(linkedDequeConstructor);
IDeque<Integer> q = new LinkedDeque<>(); }
for (int i = 0; i < numElements; i++) {
q.addBack(i);
}
return q;
};
Consumer<IDeque<Integer>> addBack = (IDeque<Integer> q) -> q.addBack(0);
Consumer<IDeque<Integer>> removeBack = (IDeque<Integer> q) -> q.removeBack();
RuntimeInstrumentation.assertAtMost("addBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, addBack, 8);
RuntimeInstrumentation.assertAtMost("removeBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide,
removeBack, 8);
} }
@Order(complexityTestLevel) @Nested
@DisplayName("enqueue() and dequeue() take constant time") @DisplayName("ICollection Functionality")
@Timeout(value = 10, unit = SECONDS) class CollectionTests implements ICollectionTests {
@Test @Override
public void testQueueOperationComplexity() { public ICollection<Object> newCollection() {
Function<Integer, IQueue<Integer>> provide = (Integer numElements) -> { return Reflection.newInstance(linkedDequeConstructor);
IQueue<Integer> q = new LinkedDeque<>(); }
for (int i = 0; i < numElements; i++) {
q.enqueue(i);
}
return q;
};
Consumer<IQueue<Integer>> enqueue = (IQueue<Integer> q) -> q.enqueue(0);
Consumer<IQueue<Integer>> dequeue = (IQueue<Integer> q) -> q.dequeue();
RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, enqueue, 8);
RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, dequeue, 8);
} }
@Order(complexityTestLevel) @DisplayName("Implementation Tests")
@DisplayName("push() and pop() take constant time") @Nested
@Timeout(value = 10, unit = SECONDS) class ImplementationTests {
@Test @Order(implSpecificTestLevel)
public void testStackOperationComplexity() { @DisplayName("Check for excessive node allocation in addFront and removeFront")
Function<Integer, IStack<Integer>> provide = (Integer numElements) -> { @TestDescription("This test is checking that you are not allocating extra nodes in add/remove that are not necessary.")
IStack<Integer> q = new LinkedDeque<>(); @DependsOn({"fields", "constructors", "addFront", "removeFront"})
for (int i = 0; i < numElements; i++) { @Test
q.push(i); public void testForExcessiveNodeAllocationAddFront() {
NewNode.NUM_CALLS = 0;
edu.caltech.cs2.datastructures.LinkedDeque<Integer> impl = new edu.caltech.cs2.datastructures.LinkedDeque<>();
for (int i = 0; i < 100; i++) {
int before = NewNode.NUM_CALLS;
impl.addFront(i);
int after = NewNode.NUM_CALLS;
assertTrue(before + 1 >= after, "Each addFront() should create at most one new node");
} }
return q; for (int i = 0; i < 100; i++) {
}; int before = NewNode.NUM_CALLS;
Consumer<IStack<Integer>> push = (IStack<Integer> q) -> q.push(0); impl.removeFront();
Consumer<IStack<Integer>> pop = (IStack<Integer> q) -> q.pop(); int after = NewNode.NUM_CALLS;
assertTrue(before == after, "removeFront() should not allocate any new nodes");
RuntimeInstrumentation.assertAtMost("push", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, push, 8);
RuntimeInstrumentation.assertAtMost("pop", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, pop, 8);
}
@Order(complexityTestLevel)
@DisplayName("peek() takes constant time")
@Timeout(value = 10, unit = SECONDS)
@Test
public void testPeekComplexity() {
Function<Integer, IStack<Integer>> provide = (Integer numElements) -> {
IStack<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.push(i);
} }
return q; }
};
Consumer<IStack<Integer>> peek = (IStack<Integer> q) -> q.peek();
RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8);
}
@Order(complexityTestLevel) @Order(implSpecificTestLevel)
@DisplayName("peekFront() takes constant time") @DisplayName("Check for excessive node allocation in addBack and removeBack")
@Timeout(value = 10, unit = SECONDS) @TestDescription("This test is checking that you are not allocating extra nodes in add/remove that are not necessary.")
@Test @DependsOn({"fields", "constructors", "addBack", "removeBack"})
public void testPeekFrontComplexity() { @Test
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> { public void testForExcessiveNodeAllocationAddBack() {
IDeque<Integer> q = new LinkedDeque<>(); NewNode.NUM_CALLS = 0;
for (int i = 0; i < numElements; i++) { edu.caltech.cs2.datastructures.LinkedDeque<Integer> impl = new edu.caltech.cs2.datastructures.LinkedDeque<>();
q.addFront(i); for (int i = 0; i < 100; i++) {
int before = NewNode.NUM_CALLS;
impl.addBack(i);
int after = NewNode.NUM_CALLS;
assertTrue(before + 1 >= after, "Each addBack() should create at most one new node");
} }
return q; for (int i = 0; i < 100; i++) {
}; int before = NewNode.NUM_CALLS;
Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront(); impl.removeBack();
int after = NewNode.NUM_CALLS;
RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront, assertTrue(before == after, "removeBack() should not allocate any new nodes");
8);
}
@Order(complexityTestLevel)
@DisplayName("peekBack() takes constant time")
@Timeout(value = 10, unit = SECONDS)
@Test
public void testPeekBackComplexity() {
Function<Integer, IDeque<Integer>> provide = (Integer numElements) -> {
IDeque<Integer> q = new LinkedDeque<>();
for (int i = 0; i < numElements; i++) {
q.addBack(i);
} }
return q; }
};
Consumer<IDeque<Integer>> peekBack = (IDeque<Integer> q) -> q.peekBack();
RuntimeInstrumentation.assertAtMost("peekBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekBack,
8);
}
// "LINKED-NESS" TESTS ------------------------------------------------ @Order(classSpecificTestLevel)
@DisplayName("Check that LinkedDeque uses a node class")
@Order(dequeTestLevel) @TestDescription("This test is checking that you are are using a doubly-linked-list rather than some other implementation.")
@DisplayName("Cycle detection for addFront(...), addBack(...), removeFront(...), and removeBack(...)") @Test
@ParameterizedTest(name = "Test cycles - {1} random numbers with seed = {0}") public void testLinkedNode() {
@CsvSource({ "69, 2000", "20, 3000" }) Class[] classes = LinkedDeque.class.getDeclaredClasses();
public void checkForCycles(int seed, int size) { boolean found = false;
Random r = new Random(seed); for (Class clazz : classes) {
Deque<Object> reference = new ArrayDeque<>(); if (Iterator.class.isAssignableFrom(clazz)) {
IDeque<Object> impl = new LinkedDeque<>(); continue;
// Test that first peek is null
assertNull(impl.peekFront(), "empty peek should return null");
// Randomly add / remove elements to the front / back
for (int i = 0; i < size; i++) {
int num = r.nextInt();
if (num % 2 == 0) {
reference.addLast(num);
impl.addBack(num);
} else {
reference.addFirst(num);
impl.addFront(num);
}
if (reference.size() > 1 && impl.size() > 1) {
if (num % 5 == 0) {
reference.removeFirst();
impl.removeFront();
} else if (num % 7 == 0) {
reference.removeLast();
impl.removeBack();
} }
NodeChecker.isNode(clazz, true);
found = true;
}
if (!found) {
fail("There is no node class defined in LinkedDeque.");
} }
// After each operation, check whether cycles have formed
NodeChecker.cycleDetection(impl, true);
// Sanity checks, though these aren't super necessary
assertEquals(reference.size(), impl.size(), "size()s are not equal");
assertEquals(reference.toString(), impl.toString(), "toStrings()s are not equal");
} }
}
@Order(dequeTestLevel) // "LINKED-NESS" TESTS ------------------------------------------------
@DisplayName("Check reverses for addFront(...), addBack(...), removeFront(...), and removeBack(...)")
@ParameterizedTest(name = "Test reverse - {1} random numbers with seed = {0}") @Order(dequeTestLevel)
@CsvSource({ "31, 2000", "64, 3000" }) @DisplayName("Cycle detection for addFront(...), addBack(...), removeFront(...), and removeBack(...)")
public void checkReverses(int seed, int size) { @TestDescription("This test follows all the links in your linked list and checks if any of them result in a cycle.")
Random r = new Random(seed); @ParameterizedTest(name = "Test cycles - {1} random numbers with seed = {0}")
Deque<Object> reference = new ArrayDeque<>(); @CsvSource({"69, 2000", "20, 3000"})
IDeque<Object> impl = new LinkedDeque<>(); public void checkForCycles(int seed, int size) {
// Test that first peek is null Random r = new Random(seed);
assertNull(impl.peekFront(), "empty peek should return null"); Deque<Object> reference = new java.util.ArrayDeque<>();
// Randomly add / remove elements to the front / back IDeque<Object> impl = new LinkedDeque<>();
for (int i = 0; i < size; i++) { // Test that first peek is null
int num = r.nextInt(); assertNull(impl.peekFront(), "empty peek should return null");
if (num % 2 == 0) { // Randomly add / remove elements to the front / back
reference.addLast(num); for (int i = 0; i < size; i++) {
impl.addBack(num); int num = r.nextInt();
} else { if (num % 2 == 0) {
reference.addFirst(num); reference.addLast(num);
impl.addFront(num); impl.addBack(num);
} else {
reference.addFirst(num);
impl.addFront(num);
}
if (reference.size() > 1 && impl.size() > 1) {
if (num % 5 == 0) {
reference.removeFirst();
impl.removeFront();
} else if (num % 7 == 0) {
reference.removeLast();
impl.removeBack();
}
}
// After each operation, check whether cycles have formed
NodeChecker.cycleDetection(impl, true);
// Sanity checks, though these aren't super necessary
assertEquals(reference.size(), impl.size(), "size()s are not equal");
assertEquals(reference.toString(), impl.toString(), "toStrings()s are not equal");
} }
if (reference.size() > 1 && impl.size() > 1) { }
if (num % 5 == 0) {
reference.removeFirst(); @Order(dequeTestLevel)
impl.removeFront(); @DisplayName("Check reverses for addFront(...), addBack(...), removeFront(...), and removeBack(...)")
} else if (num % 7 == 0) { @TestDescription("This test follows all the links forwards and backwards to check that your links are consistent.")
reference.removeLast(); @ParameterizedTest(name = "Test reverse - {1} random numbers with seed = {0}")
impl.removeBack(); @CsvSource({"31, 2000", "64, 3000"})
public void checkReverses(int seed, int size) {
Random r = new Random(seed);
Deque<Object> reference = new java.util.ArrayDeque<>();
IDeque<Object> impl = new LinkedDeque<>();
// Test that first peek is null
assertNull(impl.peekFront(), "empty peek should return null");
// Randomly add / remove elements to the front / back
for (int i = 0; i < size; i++) {
int num = r.nextInt();
if (num % 2 == 0) {
reference.addLast(num);
impl.addBack(num);
} else {
reference.addFirst(num);
impl.addFront(num);
} }
if (reference.size() > 1 && impl.size() > 1) {
if (num % 5 == 0) {
reference.removeFirst();
impl.removeFront();
} else if (num % 7 == 0) {
reference.removeLast();
impl.removeBack();
}
}
// Check that forwards and backwards iteration are sane
NodeChecker.checkReverse(impl);
assertEquals(reference.size(), impl.size(), "size()s are not equal");
assertEquals(reference.toString(), impl.toString(), "toStrings()s are not equal");
} }
// Check that forwards and backwards iteration are sane
NodeChecker.checkReverse(impl);
assertEquals(reference.size(), impl.size(), "size()s are not equal");
assertEquals(reference.toString(), impl.toString(), "toStrings()s are not equal");
} }
} }
} }
\ No newline at end of file
package edu.caltech.cs2.helpers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DependsOn {
String[] value();
}
package edu.caltech.cs2.helpers;
public class NewNode {
public static int NUM_CALLS = 0;
}
package edu.caltech.cs2.helpers;
public class NewObjectArray {
public static int NUM_CALLS = 0;
}
package edu.caltech.cs2.helpers;
public class RuntimeCounter {
public static int NUM_CALLS = 0;
public static void inc() {
NUM_CALLS++;
}
}
...@@ -9,8 +9,8 @@ import java.util.function.Consumer; ...@@ -9,8 +9,8 @@ import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
public class RuntimeInstrumentation { public class RuntimeInstrumentation {
private static final int SKIP = 5; private static final int SKIP = 3;
private static final int ITERATIONS = 100; private static final int ITERATIONS = 1;
public enum ComplexityType { public enum ComplexityType {
CONSTANT(0, "constant"), CONSTANT(0, "constant"),
...@@ -37,10 +37,11 @@ public class RuntimeInstrumentation { ...@@ -37,10 +37,11 @@ public class RuntimeInstrumentation {
} }
public static <DS> long timeFunction(DS ds, Consumer<DS> function) { public static <DS> long timeFunction(DS ds, Consumer<DS> function) {
long startTime = System.nanoTime(); RuntimeCounter.NUM_CALLS = 0;
//long startTime = System.nanoTime();
function.accept(ds); function.accept(ds);
long endTime = System.nanoTime(); //long endTime = System.nanoTime();
return endTime - startTime; return RuntimeCounter.NUM_CALLS;//endTime - startTime;
} }
public static <DS> ComplexityType getEmpiricalComplexity(Function<Integer, DS> provideDSOfSize, Consumer<DS> functionToTest, int numberOfDoubles) { public static <DS> ComplexityType getEmpiricalComplexity(Function<Integer, DS> provideDSOfSize, Consumer<DS> functionToTest, int numberOfDoubles) {
...@@ -50,9 +51,6 @@ public class RuntimeInstrumentation { ...@@ -50,9 +51,6 @@ public class RuntimeInstrumentation {
long totalTime = 0; long totalTime = 0;
for (int i = 0; i < ITERATIONS; i++) { for (int i = 0; i < ITERATIONS; i++) {
DS ds = provideDSOfSize.apply(currentSize); DS ds = provideDSOfSize.apply(currentSize);
// Bring ds into cache! Make sure we're only clocking
// the function, and not JVM operations on the heap / cache
timeFunction(ds, functionToTest);
totalTime += timeFunction(ds, functionToTest); totalTime += timeFunction(ds, functionToTest);
} }
times.add(Math.round((double)totalTime / ITERATIONS)); times.add(Math.round((double)totalTime / ITERATIONS));
...@@ -62,6 +60,10 @@ public class RuntimeInstrumentation { ...@@ -62,6 +60,10 @@ public class RuntimeInstrumentation {
times.remove(0); times.remove(0);
} }
if (times.stream().allMatch(((x) -> x == 0))) {
fail("Infrastructure is broken or method is unimplemented! See course staff!");
}
if (isApproximately(ComplexityType.CONSTANT, times)) { if (isApproximately(ComplexityType.CONSTANT, times)) {
return ComplexityType.CONSTANT; return ComplexityType.CONSTANT;
} }
...@@ -152,13 +154,13 @@ public class RuntimeInstrumentation { ...@@ -152,13 +154,13 @@ public class RuntimeInstrumentation {
// Tune depending on strictness - 0.95 accounts for variations // Tune depending on strictness - 0.95 accounts for variations
// *Should* actually be like 0.99, but sometimes weird heap operations // *Should* actually be like 0.99, but sometimes weird heap operations
// happen and make certain runs take longer // happen and make certain runs take longer
return rSq >= 0.92; return rSq >= 0.98;
} }
public static <DS> void assertAtMost(String whatIsBeingTested, ComplexityType expected, Function<Integer, DS> provideDSOfSize, Consumer<DS> functionToTest, int numberOfDoubles) { public static <DS> void assertAtMost(String whatIsBeingTested, ComplexityType expected, Function<Integer, DS> provideDSOfSize, Consumer<DS> functionToTest, int numberOfDoubles) {
ComplexityType calculated = getEmpiricalComplexity(provideDSOfSize, functionToTest, numberOfDoubles); ComplexityType calculated = getEmpiricalComplexity(provideDSOfSize, functionToTest, numberOfDoubles);
if (calculated.isSlowerThan(expected)) { if (calculated.isSlowerThan(expected)) {
fail(whatIsBeingTested + " is expected to be " + expected + " time or better. The actual calculated time is " + calculated + ".\nThis test is non-deterministic which means it might not always be correct. If you run it multiple times and it usually passes, that's probably fine."); fail(whatIsBeingTested + " is expected to be " + expected + " time or better. The actual calculated time is " + calculated + ".");//"\nThis test is non-deterministic which means it might not always be correct. If you run it multiple times and it usually passes, that's probably fine.");
} }
} }
} }
package edu.caltech.cs2.helpers;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestDescription {
String value();
}
package edu.caltech.cs2.helpers;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.LifecycleMethodExecutionExceptionHandler;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import java.util.Arrays;
public class TestExtension implements TestExecutionExceptionHandler {
public static final String TEXT_RED = "\u001B[31m";
public static final String TEXT_BLACK = "\u001B[30m";
public static final String TEXT_GREEN = "\u001B[32m";
public static final String TEXT_BLUE = "\u001B[34m";
public static final String TEXT_RESET = "\u001B[0m";
public static final String TEXT_PURPLE = "\u001B[35m";
public static final String TEXT_CYAN = "\u001B[36m";
public static final String TEXT_YELLOW = "\u001B[33m";
public static final String TEXT_WHITE = "\u001B[37m";
@Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable)
throws Throwable {
DependsOn d = context.getTestMethod().get().getAnnotation(DependsOn.class);
System.out.print(TEXT_YELLOW);
System.out.println(context.getDisplayName());
System.out.println("=".repeat(context.getDisplayName().length()));
TestDescription t2 = context.getTestMethod().get().getAnnotation(TestDescription.class);
if (t2 != null) {
System.out.println(t2.value());
}
System.out.print(TEXT_PURPLE);
if (d != null) {
System.out.println("Hint: This test depends on the following being implemented correctly:\n - " + String.join("\n - ", d.value()));
}
TestHint t = context.getTestMethod().get().getAnnotation(TestHint.class);
if (t != null) {
System.out.println("Hint: " + t.value());
}
System.out.print(TEXT_RESET);
throw throwable;
}
}
\ No newline at end of file
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