diff --git a/.idea/libraries/instrumentation.jar b/.idea/libraries/instrumentation.jar new file mode 100644 index 0000000000000000000000000000000000000000..02650a5823feaa94ca08dc868d2f6c0c2e0ecb8c Binary files /dev/null and b/.idea/libraries/instrumentation.jar differ diff --git a/.idea/libraries/org_javassist_javassist_3_29_2_GA.xml b/.idea/libraries/org_javassist_javassist_3_29_2_GA.xml new file mode 100644 index 0000000000000000000000000000000000000000..bb45507d06a495ef86dcec92a57632555e5fcb97 --- /dev/null +++ b/.idea/libraries/org_javassist_javassist_3_29_2_GA.xml @@ -0,0 +1,10 @@ +<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 diff --git a/.idea/misc.xml b/.idea/misc.xml index 9adcf7a41db750b7096f4846ea07e37851b7cb0a..690aa52958d8a3bb82f51cde7f720dcee043903d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,7 +9,7 @@ <component name="PreferredVcsStorage"> <preferredVcsName>ApexVCS</preferredVcsName> </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" /> </component> -</project> +</project> \ No newline at end of file diff --git a/.idea/runConfigurations/A_Tests.xml b/.idea/runConfigurations/A_Tests.xml index 8b16bd9f65d0aa5a0ba0817f36d38242a5a5b7d0..116af227697288cb31401958c5c56aa4165e54a8 100644 --- a/.idea/runConfigurations/A_Tests.xml +++ b/.idea/runConfigurations/A_Tests.xml @@ -4,6 +4,7 @@ <option name="MAIN_CLASS_NAME" value="" /> <option name="METHOD_NAME" value="" /> <option name="TEST_OBJECT" value="tags" /> + <option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" /> <option name="PARAMETERS" value="" /> <option name="TEST_SEARCH_SCOPE"> <value defaultName="wholeProject" /> @@ -13,4 +14,4 @@ <option name="Make" enabled="true" /> </method> </configuration> -</component> \ No newline at end of file +</component> diff --git a/.idea/runConfigurations/B_Tests.xml b/.idea/runConfigurations/B_Tests.xml index 417d3543526097de0a40fa4e22c7d421b4b05ea7..519be8d9342e4e1846419edf74be404872cf3afe 100644 --- a/.idea/runConfigurations/B_Tests.xml +++ b/.idea/runConfigurations/B_Tests.xml @@ -4,6 +4,7 @@ <option name="MAIN_CLASS_NAME" value="" /> <option name="METHOD_NAME" value="" /> <option name="TEST_OBJECT" value="tags" /> + <option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" /> <option name="PARAMETERS" value="" /> <option name="TEST_SEARCH_SCOPE"> <value defaultName="wholeProject" /> @@ -13,4 +14,4 @@ <option name="Make" enabled="true" /> </method> </configuration> -</component> \ No newline at end of file +</component> diff --git a/.idea/runConfigurations/C_Tests.xml b/.idea/runConfigurations/C_Tests.xml index d1cfa143e2b01de9466460ff0481f4a813f22ab6..3564619629e35217ed0481c4bf4b3b69d7e79314 100644 --- a/.idea/runConfigurations/C_Tests.xml +++ b/.idea/runConfigurations/C_Tests.xml @@ -4,6 +4,7 @@ <option name="MAIN_CLASS_NAME" value="" /> <option name="METHOD_NAME" value="" /> <option name="TEST_OBJECT" value="tags" /> + <option name="VM_PARAMETERS" value="-noverify -ea -javaagent:.idea/libraries/instrumentation.jar" /> <option name="PARAMETERS" value="" /> <option name="TEST_SEARCH_SCOPE"> <value defaultName="wholeProject" /> diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 6209a3f20c61db22a340bd72534c186e1a937e00..0000000000000000000000000000000000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?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 diff --git a/project03-guitar.iml b/project03-guitar.iml index 3ba9bf360538af064c1595c7671c8f0b0836cd69..9f76aeaa06d3487fd37771962a8c33797149328c 100644 --- a/project03-guitar.iml +++ b/project03-guitar.iml @@ -15,5 +15,6 @@ <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-core:2.2" level="project" /> + <orderEntry type="library" name="org.javassist:javassist:3.29.2-GA" level="project" /> </component> </module> \ No newline at end of file diff --git a/src/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueue.java b/src/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueue.java index 2ff895914bcee17cea22ab9b658e38d9bfa1dd7b..aaba2b5ce2a9a9274e0ef4138122ace28d9a7a27 100644 --- a/src/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueue.java +++ b/src/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueue.java @@ -6,6 +6,10 @@ import java.util.Iterator; public class CircularArrayFixedSizeQueue<E> implements IFixedSizeQueue<E> { + public CircularArrayFixedSizeQueue(int capacity) { + + } + @Override public boolean isFull() { return false; @@ -36,6 +40,16 @@ public class CircularArrayFixedSizeQueue<E> implements IFixedSizeQueue<E> { return 0; } + @Override + public void add(E e) { + + } + + @Override + public void clear() { + + } + @Override public Iterator<E> iterator() { return null; diff --git a/src/edu/caltech/cs2/interfaces/IQueue.java b/src/edu/caltech/cs2/interfaces/IQueue.java index ec9b94c7f234052c0fc2e618b9f62b1eb709a205..a4628d22a40ffbf75da9ee4b6727022823259d1e 100644 --- a/src/edu/caltech/cs2/interfaces/IQueue.java +++ b/src/edu/caltech/cs2/interfaces/IQueue.java @@ -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. * @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. * @param e Element to add diff --git a/tests/edu/caltech/cs2/datastructures/ArrayDequeTests.java b/tests/edu/caltech/cs2/datastructures/ArrayDequeTests.java index fecbcc7b346cff3f3c478ebe3d4ab651a75b69aa..79e554cc6f7f4379cc04fe4960d84dcd99310149 100644 --- a/tests/edu/caltech/cs2/datastructures/ArrayDequeTests.java +++ b/tests/edu/caltech/cs2/datastructures/ArrayDequeTests.java @@ -1,13 +1,13 @@ package edu.caltech.cs2.datastructures; -import edu.caltech.cs2.helpers.Inspection; -import edu.caltech.cs2.helpers.Reflection; -import edu.caltech.cs2.helpers.RuntimeInstrumentation; +import edu.caltech.cs2.helpers.*; import edu.caltech.cs2.interfaces.*; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.*; @@ -21,302 +21,326 @@ import static org.junit.jupiter.api.Assertions.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Tag("C") -public class ArrayDequeTests implements IDequeTests, IStackTests, IQueueTests { - private static String ARRAY_DEQUE_SOURCE ="src/edu/caltech/cs2/datastructures/ArrayDeque.java"; +@ExtendWith(TestExtension.class) +public class ArrayDequeTests { + private static String ARRAY_DEQUE_SOURCE = "src/edu/caltech/cs2/datastructures/ArrayDeque.java"; 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 ---------------------------------------- - @Order(classSpecificTestLevel) - @DisplayName("Does not use or import disallowed classes") - @Test - public void testForInvalidClasses() { - List<String> regexps = List.of("java\\.util\\.(?!Iterator)", "java\\.lang\\.reflect", "java\\.io"); - Inspection.assertNoImportsOf(ARRAY_DEQUE_SOURCE, regexps); - Inspection.assertNoUsageOf(ARRAY_DEQUE_SOURCE, regexps); - } + @DisplayName("Style") + @Nested + class StyleTests implements IStyleTests { + @Order(classSpecificTestLevel) + @DisplayName("There is an integer default capacity static field and an integer default grow factor static field") + @TestDescription("This test checks that you use constants (static, final, private) fields for the default capacity and the growth factor when the array resizes.") + @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) - @DisplayName("There is an integer default capacity static field and an integer default grow factor static field") - @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"); - }); - } + public int getMaxFields() { + return 5; + } - @Order(classSpecificTestLevel) - @DisplayName("The overall number of fields is small") - @Test - public void testSmallNumberOfFields() { - Reflection.assertFieldsLessThan(ArrayDeque.class, "private", 5); - } + public List<String> getPublicInterface() { + return List.of( + "addFront", + "addBack", + "removeFront", + "removeBack", + "enqueue", + "dequeue", + "push", + "pop", + "peek", + "peekFront", + "peekBack", + "iterator", + "size", + "toString" + ); + } - @Order(classSpecificTestLevel) - @DisplayName("There are no public fields") - @Test - public void testNoPublicFields() { - Reflection.assertNoPublicFields(ArrayDeque.class); - } + @Override + public String getSource() { + return ARRAY_DEQUE_SOURCE; + } - @Order(classSpecificTestLevel) - @DisplayName("There are no protected fields") - @Test - public void testNoProtectedFields() { - Reflection.assertNoProtectedFields(ArrayDeque.class); + @Override + public Class<?> getClazz() { + return ArrayDeque.class; + } } - @Order(classSpecificTestLevel) - @DisplayName("All fields in ArrayDeque have modifiers") - @Test - public void testFieldModifiers() { - Reflection.assertFieldModifiers(ArrayDeque.class); - } + // TIME COMPLEXITY TESTS ------------------------------------------------ - @Order(classSpecificTestLevel) - @DisplayName("The public interface is correct") - @Test - public void testPublicInterface() { - Reflection.assertPublicInterface(ArrayDeque.class, List.of( - "addFront", - "addBack", - "removeFront", - "removeBack", - "enqueue", - "dequeue", - "push", - "pop", - "peek", - "peekFront", - "peekBack", - "iterator", - "size", - "toString" - )); - } + @Nested + @DisplayName("Runtime Complexity") + class RuntimeTests { + @Order(complexityTestLevel) + @DisplayName("addFront() and removeFront() take linear time") + @Timeout(value = 20, unit = SECONDS) + @Test() + 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(classSpecificTestLevel) - @DisplayName("Uses this(...) notation in all but one constructor") - @Test - public void testForThisConstructors() { - Inspection.assertConstructorHygiene(ARRAY_DEQUE_SOURCE); - } + @Order(complexityTestLevel) + @DisplayName("addBack() and removeBack() take constant time") + @Timeout(value = 20, unit = SECONDS) + @Test + 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) - @DisplayName("The default capacity of the array is 10") - @Test - public void testArrayDequeDefaultInitialCapacity() throws IllegalAccessException { - ArrayDeque<Integer> impl = new ArrayDeque<>(); + @Order(complexityTestLevel) + @DisplayName("push() and pop() take constant time") + @Timeout(value = 10, unit = SECONDS) + @Test + 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 - // It's actually an Object[] since that's how it (should!) be initialized internally - // Casting it doesn't change the type of the field. - // It's fine since there should only be one array. - Field arr = Reflection.getFieldByType(ArrayDeque.class, Object[].class); - arr.setAccessible(true); - Object[] backingArray = (Object[]) arr.get(impl); + @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 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) - @DisplayName("enqueue should always succeed") - @Test - public void testThatArrayDequeEnqueueAlwaysSucceeds() { - ArrayDeque<Integer> impl = new ArrayDeque<>(); - for (int i = 0; i < 100; i ++) { - assertTrue(impl.enqueue(i), "enqueue() should always succeed for ArrayDeque"); + @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 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) - @DisplayName("push should always succeed") - @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"); + @Nested + @DisplayName("IStack Functionality") + class StackTests implements IStackTests { + @Override + public IStack<Object> newStack() { + return Reflection.newInstance(arrayDequeConstructor); } } - // TOSTRING TESTS --------------------------------------------------- + @Nested + @DisplayName("IQueue Functionality") + class QueueTests implements IQueueTests { - @Order(toStringTestLevel) - @DisplayName("toString is correctly overridden") - @Test - public void testToStringOverride() { - Reflection.assertMethodCorrectlyOverridden(ArrayDeque.class, "toString"); - } + public IQueue<Object> newQueue() { + return Reflection.newInstance(arrayDequeConstructor); + } - @Order(toStringTestLevel) - @DisplayName("toString() matches java.util.ArrayDeque") - @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); + public IQueue<Object> newQueue(int size) { + return newQueue(); } } - // TIME COMPLEXITY TESTS ------------------------------------------------ - - @Order(complexityTestLevel) - @DisplayName("addFront() and removeFront() take linear time") - @Timeout(value = 20, unit = SECONDS) - @Test() - 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); + @Nested + @DisplayName("IDeque Functionality") + class DequeTests implements IDequeTests { + @Override + public IDeque<Object> newDeque() { + return Reflection.newInstance(arrayDequeConstructor); + } } - @Order(complexityTestLevel) - @DisplayName("addBack() and removeBack() take constant time") - @Timeout(value = 20, unit = SECONDS) - @Test - 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); + @Nested + @DisplayName("ICollection Functionality") + class CollectionTests implements ICollectionTests { + @Override + public ICollection<Object> newCollection() { + return Reflection.newInstance(arrayDequeConstructor); + } } - @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); - } + @DisplayName("Implementation Tests") + @Nested + class ImplementationTests { + @Order(implSpecificTestLevel) + @DisplayName("The default capacity of the array in the deque is 10") + @DependsOn({"fields", "constructors"}) + @Test + public void testArrayDequeDefaultInitialCapacity() throws IllegalAccessException { + ArrayDeque<Integer> impl = new ArrayDeque<>(); + + // Reflect and get the backing array + // It's actually an Object[] since that's how it (should!) be initialized internally + // Casting it doesn't change the type of the field. + // It's fine since there should only be one array. + Field arr = Reflection.getFieldByType(ArrayDeque.class, Object[].class); + arr.setAccessible(true); + Object[] backingArray = (Object[]) arr.get(impl); + + assertEquals(10, backingArray.length, "Default initial capacity is not 10"); + } - @Order(complexityTestLevel) - @DisplayName("push() and pop() take constant time") - @Timeout(value = 10, unit = SECONDS) - @Test - 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); + @Order(implSpecificTestLevel) + @DisplayName("enqueue should always succeed") + @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).") + @DependsOn({"fields", "constructors", "enqueue"}) + @Test + public void testThatArrayDequeEnqueueAlwaysSucceeds() { + ArrayDeque<Integer> impl = new ArrayDeque<>(); + for (int i = 0; i < 100; 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) - @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 ArrayDeque<>(); - for (int i = 0; i < numElements; i++) { - q.push(i); + @Order(implSpecificTestLevel) + @DisplayName("Check for excessive array allocation in addFront and removeFront") + @TestDescription("This test is checking that you are not allocating extra arrays in add/remove that are not necessary.") + @DependsOn({"fields", "constructors", "addFront", "removeFront"}) + @Test + public void testForExcessiveArrayAllocationAddFront() { + NewObjectArray.NUM_CALLS = 0; + ArrayDeque<Integer> impl = new ArrayDeque<>(); + 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; - }; - Consumer<IStack<Integer>> peek = (IStack<Integer> q) -> q.peek(); - - RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); - } - - @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); + 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++) { + int before = NewObjectArray.NUM_CALLS; + impl.removeFront(); + int after = NewObjectArray.NUM_CALLS; + assertTrue(before == after, "removeFront() should not allocate any new arrays"); } - return q; - }; - Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront(); - - RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront, 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 ArrayDeque<>(); - for (int i = 0; i < numElements; i++) { - q.addBack(i); + @Order(implSpecificTestLevel) + @DisplayName("Check for excessive array allocation in addBack and removeBack") + @TestDescription("This test is checking that you are not allocating extra arrays in add/remove that are not necessary.") + @DependsOn({"fields", "constructors", "addBack", "removeBack"}) + @Test + public void testForExcessiveArrayAllocationAddBack() { + NewObjectArray.NUM_CALLS = 0; + ArrayDeque<Integer> impl = new ArrayDeque<>(); + 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; - }; - Consumer<IDeque<Integer>> peekBack = (IDeque<Integer> q) -> q.peekBack(); + 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++) { + 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 +} diff --git a/tests/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueueTests.java b/tests/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueueTests.java index c9e2b8236b1a80c320b1a767ca0c6599d4d15b4d..8eb3d9904ae3ce9cfae041aba17dbfa125d01065 100644 --- a/tests/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueueTests.java +++ b/tests/edu/caltech/cs2/datastructures/CircularArrayFixedSizeQueueTests.java @@ -1,20 +1,19 @@ package edu.caltech.cs2.datastructures; -import edu.caltech.cs2.helpers.Inspection; -import edu.caltech.cs2.helpers.Reflection; -import edu.caltech.cs2.helpers.RuntimeInstrumentation; -import edu.caltech.cs2.interfaces.IFixedSizeQueueTests; -import edu.caltech.cs2.interfaces.IFixedSizeQueue; -import edu.caltech.cs2.interfaces.IQueue; +import edu.caltech.cs2.helpers.*; +import edu.caltech.cs2.interfaces.*; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Stream; import static edu.caltech.cs2.project03.Project03TestOrdering.*; import static java.util.concurrent.TimeUnit.SECONDS; @@ -22,174 +21,153 @@ import static org.junit.jupiter.api.Assertions.*; @Tag("B") @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 Constructor circFixedSizeQueueConstructor = Reflection.getConstructor(CircularArrayFixedSizeQueue.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) { return Reflection.newInstance(circFixedSizeQueueConstructor, capacity); } - // FIXED QUEUE-SPECIFIC TESTS ---------------------------------------- - - @Order(classSpecificTestLevel) - @DisplayName("Does not use or import disallowed classes") - @Test - 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); - } + @DisplayName("Style") + @Nested + class StyleTests implements IStyleTests { + public int getMaxFields() { + return 4; + } - @Order(classSpecificTestLevel) - @DisplayName("There are no static fields") - @Test - public void testConstantFields() { - Reflection.assertFieldsEqualTo(CircularArrayFixedSizeQueue.class, "static", 0); - } + public List<String> getPublicInterface() { + return List.of("enqueue", "dequeue", "peek", "iterator", "size", "isFull", "capacity", "toString", "add", "clear"); + } - @Order(classSpecificTestLevel) - @DisplayName("The overall number of fields is small") - @Test - public void testSmallNumberOfFields() { - Reflection.assertFieldsLessThan(CircularArrayFixedSizeQueue.class, "private", 4); - } + @Override + public String getSource() { + return FIXED_QUEUE_SOURCE; + } - @Order(classSpecificTestLevel) - @DisplayName("There are no public fields") - @Test - public void testNoPublicFields() { - Reflection.assertNoPublicFields(CircularArrayFixedSizeQueue.class); + @Override + public Class<?> getClazz() { + return CircularArrayFixedSizeQueue.class; + } } - @Order(classSpecificTestLevel) - @DisplayName("There are no protected fields") - @Test - public void testNoProtectedFields() { - Reflection.assertNoProtectedFields(CircularArrayFixedSizeQueue.class); - } + @Nested + @DisplayName("Runtime Complexity") + class RuntimeTests { + @Order(complexityTestLevel) + @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) - @DisplayName("All fields in CircularArrayFixedSizeQueue have modifiers") - @Test - public void testFieldModifiers() { - Reflection.assertFieldModifiers(CircularArrayFixedSizeQueue.class); + @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; + }; + Consumer<IFixedSizeQueue<Integer>> peek = (IFixedSizeQueue<Integer> q) -> q.peek(); + + RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); + } } - @Order(classSpecificTestLevel) - @DisplayName("The public interface is correct") - @Test - public void testPublicInterface() { - Reflection.assertPublicInterface(CircularArrayFixedSizeQueue.class, - List.of("enqueue", "dequeue", "peek", "iterator", "size", "isFull", "capacity", "toString")); - } + @Nested + @DisplayName("IFixedSizeQueue Functionality") + class QueueTests implements IFixedSizeQueueTests { - @Order(classSpecificTestLevel) - @DisplayName("Uses this(...) notation in all but one constructor") - @Test - public void testForThisConstructors() { - Inspection.assertConstructorHygiene(FIXED_QUEUE_SOURCE); - } + public IFixedSizeQueue<Object> newQueue() { + return Reflection.newInstance(circFixedSizeQueueConstructor); + } - // TOSTRING TESTS --------------------------------------------------- + public IFixedSizeQueue<Object> newQueue(int size) { + return Reflection.newInstance(circFixedSizeQueueConstructor, size); + } - @Order(toStringTestLevel) - @DisplayName("toString is correctly overridden") - @Test - public void testToStringOverride() { - Reflection.assertMethodCorrectlyOverridden(ArrayDeque.class, "toString"); + @Override + public IFixedSizeQueue<Object> newFixedSizeQueue(int capacity) { + return CircularArrayFixedSizeQueueTests.this.newFixedSizeQueue(capacity); + } } - @Order(toStringTestLevel) - @DisplayName("toString() matches java.util.ArrayDeque") - @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>(); - 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); + @Nested + @DisplayName("ICollection Functionality") + class CollectionTests implements ICollectionTests { + @Override + public ICollection<Object> newCollection() { + return Reflection.newInstance(circFixedSizeQueueConstructor, 100000); } } - // TIME COMPLEXITY TESTS ------------------------------------------------ - - @Order(complexityTestLevel) - @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); - } + @DisplayName("Implementation Tests") + @Nested + class ImplementationTests { + @Order(fixedSizeQueueLevel) + @DisplayName("Test iterator matches reference for wraparound values") + @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.") + @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); - @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; - }; - Consumer<IFixedSizeQueue<Integer>> peek = (IFixedSizeQueue<Integer> q) -> q.peek(); - - RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); - } - - @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()); + reference.add(reference.remove()); + assertEquals(reference.peek(), me.peek(), "return values of peek()s are not equal"); + 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"); + } } - for (int i = 0; i < numVals; i++) { - me.enqueue(me.dequeue()); - reference.add(reference.remove()); - assertEquals(reference.peek(), me.peek(), "return values of peek()s are not equal"); - 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(toStringTestLevel) + @DisplayName("toString() matches java.util.ArrayDeque") + @DependsOn({"fields", "constructors", "toString", "enqueue"}) + @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.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); + } } } - } diff --git a/tests/edu/caltech/cs2/datastructures/LinkedDequeTests.java b/tests/edu/caltech/cs2/datastructures/LinkedDequeTests.java index 83fd0af46977ce79f3d9f662cc96638da37cb576..6c336b5e877971357227380c43b1ee3a07179798 100644 --- a/tests/edu/caltech/cs2/datastructures/LinkedDequeTests.java +++ b/tests/edu/caltech/cs2/datastructures/LinkedDequeTests.java @@ -1,357 +1,380 @@ package edu.caltech.cs2.datastructures; -import edu.caltech.cs2.helpers.Inspection; -import edu.caltech.cs2.helpers.NodeChecker; -import edu.caltech.cs2.helpers.Reflection; -import edu.caltech.cs2.helpers.RuntimeInstrumentation; +import edu.caltech.cs2.helpers.*; import edu.caltech.cs2.interfaces.*; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; import java.lang.reflect.Constructor; import java.util.*; -import java.util.ArrayDeque; import java.util.function.Consumer; import java.util.function.Function; import static edu.caltech.cs2.project03.Project03TestOrdering.*; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; @Tag("C") @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 Constructor linkedDequeConstructor = Reflection.getConstructor(LinkedDeque.class); - public ICollection<Object> newCollection() { - return Reflection.newInstance(linkedDequeConstructor); - } + // LINKEDDEQUE-SPECIFIC TESTS ---------------------------------------- + @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() { - return Reflection.newInstance(linkedDequeConstructor); - } + @Override + public String getSource() { + return LINKED_DEQUE_SOURCE; + } - public IStack<Object> newStack() { - return Reflection.newInstance(linkedDequeConstructor); - } + @Override + public Class<?> getClazz() { + return LinkedDeque.class; + } - public IQueue<Object> newQueue() { - return Reflection.newInstance(linkedDequeConstructor); - } + @Override + 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) - @DisplayName("Does not use or import disallowed classes") - @Test - public void testForInvalidClasses() { - List<String> regexps = List.of("java\\.util\\.(?!Iterator)", "java\\.lang\\.reflect", "java\\.io"); - Inspection.assertNoImportsOf(LINKED_DEQUE_SOURCE, regexps); - Inspection.assertNoUsageOf(LINKED_DEQUE_SOURCE, regexps); - } + @Order(complexityTestLevel) + @DisplayName("addBack() and removeBack() take constant time") + @Timeout(value = 10, unit = SECONDS) + @Test + public void testBackDequeOperationComplexity() { + 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>> 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) - @DisplayName("There are no static fields") - @Test - public void testConstantFields() { - Reflection.assertFieldsEqualTo(LinkedDeque.class, "static", 0); - } + @Order(complexityTestLevel) + @DisplayName("enqueue() and dequeue() take constant time") + @Timeout(value = 10, unit = SECONDS) + @Test + 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) - @DisplayName("The overall number of fields is small") - @Test - public void testSmallNumberOfFields() { - Reflection.assertFieldsLessThan(LinkedDeque.class, "private", 4); - } + RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, enqueue, 8); + RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, dequeue, 8); + } - @Order(classSpecificTestLevel) - @DisplayName("There are no public fields") - @Test - public void testNoPublicFields() { - Reflection.assertNoPublicFields(LinkedDeque.class); - } + @Order(complexityTestLevel) + @DisplayName("push() and pop() take constant time") + @Timeout(value = 10, unit = SECONDS) + @Test + 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) - @DisplayName("There are no protected fields") - @Test - public void testNoProtectedFields() { - Reflection.assertNoProtectedFields(LinkedDeque.class); - } + RuntimeInstrumentation.assertAtMost("push", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, push, 8); + RuntimeInstrumentation.assertAtMost("pop", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, pop, 8); + } - @Order(classSpecificTestLevel) - @DisplayName("All fields in LinkedDeque have modifiers") - @Test - public void testFieldModifiers() { - Reflection.assertFieldModifiers(LinkedDeque.class); - } + @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(); - @Order(classSpecificTestLevel) - @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")); - } + RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); + } - @Order(classSpecificTestLevel) - @DisplayName("Uses this(...) notation in all but one constructor") - @Test - public void testForThisConstructors() { - Inspection.assertConstructorHygiene(LINKED_DEQUE_SOURCE); - } + @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 LinkedDeque<>(); + for (int i = 0; i < numElements; i++) { + q.addFront(i); + } + return q; + }; + Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront(); - @Order(classSpecificTestLevel) - @DisplayName("Check that LinkedDeque uses a node class") - @Test - public void testLinkedNode() { - Class[] classes = LinkedDeque.class.getDeclaredClasses(); - for (Class clazz : classes) { - if (Iterator.class.isAssignableFrom(clazz)) { - continue; - } - NodeChecker.isNode(clazz, true); + RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront, + 8); } - } - // 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) - @DisplayName("toString is correctly overridden") - @Test - public void testToStringOverride() { - Reflection.assertMethodCorrectlyOverridden(LinkedDeque.class, "toString"); + RuntimeInstrumentation.assertAtMost("peekBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekBack, + 8); + } } - @Order(toStringTestLevel) - @DisplayName("toString() matches java.util.ArrayDeque") - @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>(); - 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); + @Nested + @DisplayName("IStack Functionality") + class StackTests implements IStackTests { + @Override + public IStack<Object> newStack() { + return Reflection.newInstance(linkedDequeConstructor); } } - // TIME COMPLEXITY TESTS ------------------------------------------------ - - @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); + @Nested + @DisplayName("IQueue Functionality") + class QueueTests implements IQueueTests { + public IQueue<Object> newQueue() { + return Reflection.newInstance(linkedDequeConstructor); + } + public IQueue<Object> newQueue(int size) { + return newQueue(); + } } - @Order(complexityTestLevel) - @DisplayName("addBack() and removeBack() take constant time") - @Timeout(value = 10, unit = SECONDS) - @Test - public void testBackDequeOperationComplexity() { - 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>> 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); + @Nested + @DisplayName("IDeque Functionality") + class DequeTests implements IDequeTests { + @Override + public IDeque<Object> newDeque() { + return Reflection.newInstance(linkedDequeConstructor); + } } - @Order(complexityTestLevel) - @DisplayName("enqueue() and dequeue() take constant time") - @Timeout(value = 10, unit = SECONDS) - @Test - 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(); - - RuntimeInstrumentation.assertAtMost("enqueue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, enqueue, 8); - RuntimeInstrumentation.assertAtMost("dequeue", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, dequeue, 8); + @Nested + @DisplayName("ICollection Functionality") + class CollectionTests implements ICollectionTests { + @Override + public ICollection<Object> newCollection() { + return Reflection.newInstance(linkedDequeConstructor); + } } - @Order(complexityTestLevel) - @DisplayName("push() and pop() take constant time") - @Timeout(value = 10, unit = SECONDS) - @Test - 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); + @DisplayName("Implementation Tests") + @Nested + class ImplementationTests { + @Order(implSpecificTestLevel) + @DisplayName("Check for excessive node allocation in addFront and removeFront") + @TestDescription("This test is checking that you are not allocating extra nodes in add/remove that are not necessary.") + @DependsOn({"fields", "constructors", "addFront", "removeFront"}) + @Test + 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; - }; - 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) - @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); + for (int i = 0; i < 100; i++) { + int before = NewNode.NUM_CALLS; + impl.removeFront(); + int after = NewNode.NUM_CALLS; + assertTrue(before == after, "removeFront() should not allocate any new nodes"); } - return q; - }; - Consumer<IStack<Integer>> peek = (IStack<Integer> q) -> q.peek(); - - RuntimeInstrumentation.assertAtMost("peek", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peek, 8); - } + } - @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 LinkedDeque<>(); - for (int i = 0; i < numElements; i++) { - q.addFront(i); + @Order(implSpecificTestLevel) + @DisplayName("Check for excessive node allocation in addBack and removeBack") + @TestDescription("This test is checking that you are not allocating extra nodes in add/remove that are not necessary.") + @DependsOn({"fields", "constructors", "addBack", "removeBack"}) + @Test + public void testForExcessiveNodeAllocationAddBack() { + 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.addBack(i); + int after = NewNode.NUM_CALLS; + assertTrue(before + 1 >= after, "Each addBack() should create at most one new node"); } - return q; - }; - Consumer<IDeque<Integer>> peekFront = (IDeque<Integer> q) -> q.peekFront(); - - RuntimeInstrumentation.assertAtMost("peekFront", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekFront, - 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); + for (int i = 0; i < 100; i++) { + int before = NewNode.NUM_CALLS; + impl.removeBack(); + int after = NewNode.NUM_CALLS; + assertTrue(before == after, "removeBack() should not allocate any new nodes"); } - return q; - }; - Consumer<IDeque<Integer>> peekBack = (IDeque<Integer> q) -> q.peekBack(); - - RuntimeInstrumentation.assertAtMost("peekBack", RuntimeInstrumentation.ComplexityType.CONSTANT, provide, peekBack, - 8); - } + } - // "LINKED-NESS" TESTS ------------------------------------------------ - - @Order(dequeTestLevel) - @DisplayName("Cycle detection for addFront(...), addBack(...), removeFront(...), and removeBack(...)") - @ParameterizedTest(name = "Test cycles - {1} random numbers with seed = {0}") - @CsvSource({ "69, 2000", "20, 3000" }) - public void checkForCycles(int seed, int size) { - Random r = new Random(seed); - Deque<Object> reference = new 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(); + @Order(classSpecificTestLevel) + @DisplayName("Check that LinkedDeque uses a node class") + @TestDescription("This test is checking that you are are using a doubly-linked-list rather than some other implementation.") + @Test + public void testLinkedNode() { + Class[] classes = LinkedDeque.class.getDeclaredClasses(); + boolean found = false; + for (Class clazz : classes) { + if (Iterator.class.isAssignableFrom(clazz)) { + continue; } + 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) - @DisplayName("Check reverses for addFront(...), addBack(...), removeFront(...), and removeBack(...)") - @ParameterizedTest(name = "Test reverse - {1} random numbers with seed = {0}") - @CsvSource({ "31, 2000", "64, 3000" }) - public void checkReverses(int seed, int size) { - Random r = new Random(seed); - Deque<Object> reference = new 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); + // "LINKED-NESS" TESTS ------------------------------------------------ + + @Order(dequeTestLevel) + @DisplayName("Cycle detection for addFront(...), addBack(...), removeFront(...), and removeBack(...)") + @TestDescription("This test follows all the links in your linked list and checks if any of them result in a cycle.") + @ParameterizedTest(name = "Test cycles - {1} random numbers with seed = {0}") + @CsvSource({"69, 2000", "20, 3000"}) + public void checkForCycles(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(); + } + } + // 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(); - impl.removeFront(); - } else if (num % 7 == 0) { - reference.removeLast(); - impl.removeBack(); + } + + @Order(dequeTestLevel) + @DisplayName("Check reverses for addFront(...), addBack(...), removeFront(...), and removeBack(...)") + @TestDescription("This test follows all the links forwards and backwards to check that your links are consistent.") + @ParameterizedTest(name = "Test reverse - {1} random numbers with seed = {0}") + @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 diff --git a/tests/edu/caltech/cs2/helpers/DependsOn.java b/tests/edu/caltech/cs2/helpers/DependsOn.java new file mode 100644 index 0000000000000000000000000000000000000000..f0c609d595c7b6177b0898cd880caf8bb20f31d0 --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/DependsOn.java @@ -0,0 +1,12 @@ +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(); +} diff --git a/tests/edu/caltech/cs2/helpers/NewNode.java b/tests/edu/caltech/cs2/helpers/NewNode.java new file mode 100644 index 0000000000000000000000000000000000000000..b96f5c28ec45462d39100e2ab4cff37944763cb1 --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/NewNode.java @@ -0,0 +1,5 @@ +package edu.caltech.cs2.helpers; + +public class NewNode { + public static int NUM_CALLS = 0; +} diff --git a/tests/edu/caltech/cs2/helpers/NewObjectArray.java b/tests/edu/caltech/cs2/helpers/NewObjectArray.java new file mode 100644 index 0000000000000000000000000000000000000000..0c5df162990e00250f66fb30db0cd42bf0e28e81 --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/NewObjectArray.java @@ -0,0 +1,5 @@ +package edu.caltech.cs2.helpers; + +public class NewObjectArray { + public static int NUM_CALLS = 0; +} diff --git a/tests/edu/caltech/cs2/helpers/RuntimeCounter.java b/tests/edu/caltech/cs2/helpers/RuntimeCounter.java new file mode 100644 index 0000000000000000000000000000000000000000..960c1697bf234326d29712262d8ad5457dcadbb6 --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/RuntimeCounter.java @@ -0,0 +1,9 @@ +package edu.caltech.cs2.helpers; + +public class RuntimeCounter { + public static int NUM_CALLS = 0; + + public static void inc() { + NUM_CALLS++; + } +} diff --git a/tests/edu/caltech/cs2/helpers/RuntimeInstrumentation.java b/tests/edu/caltech/cs2/helpers/RuntimeInstrumentation.java index d22a40a8c58dc9d9710e64ec551ca0bc6f10c5be..325051294a7625de3bce79d64376d7cb977bed91 100644 --- a/tests/edu/caltech/cs2/helpers/RuntimeInstrumentation.java +++ b/tests/edu/caltech/cs2/helpers/RuntimeInstrumentation.java @@ -9,8 +9,8 @@ import java.util.function.Consumer; import java.util.function.Function; public class RuntimeInstrumentation { - private static final int SKIP = 5; - private static final int ITERATIONS = 100; + private static final int SKIP = 3; + private static final int ITERATIONS = 1; public enum ComplexityType { CONSTANT(0, "constant"), @@ -37,10 +37,11 @@ public class RuntimeInstrumentation { } 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); - long endTime = System.nanoTime(); - return endTime - startTime; + //long endTime = System.nanoTime(); + return RuntimeCounter.NUM_CALLS;//endTime - startTime; } public static <DS> ComplexityType getEmpiricalComplexity(Function<Integer, DS> provideDSOfSize, Consumer<DS> functionToTest, int numberOfDoubles) { @@ -50,9 +51,6 @@ public class RuntimeInstrumentation { long totalTime = 0; for (int i = 0; i < ITERATIONS; i++) { 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); } times.add(Math.round((double)totalTime / ITERATIONS)); @@ -62,6 +60,10 @@ public class RuntimeInstrumentation { 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)) { return ComplexityType.CONSTANT; } @@ -152,13 +154,13 @@ public class RuntimeInstrumentation { // Tune depending on strictness - 0.95 accounts for variations // *Should* actually be like 0.99, but sometimes weird heap operations // 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) { ComplexityType calculated = getEmpiricalComplexity(provideDSOfSize, functionToTest, numberOfDoubles); 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."); } } } diff --git a/tests/edu/caltech/cs2/helpers/TestDescription.java b/tests/edu/caltech/cs2/helpers/TestDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..ac322e2bc23f118095e22e8a2c20357900be8459 --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/TestDescription.java @@ -0,0 +1,12 @@ +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(); +} diff --git a/tests/edu/caltech/cs2/helpers/TestExtension.java b/tests/edu/caltech/cs2/helpers/TestExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..e0524b4b66a30accf2a4b434cc8753afd69a164e --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/TestExtension.java @@ -0,0 +1,42 @@ +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 diff --git a/tests/edu/caltech/cs2/helpers/TestHint.java b/tests/edu/caltech/cs2/helpers/TestHint.java new file mode 100644 index 0000000000000000000000000000000000000000..d924e778395cf4a23b09cf9c70b3676aa621043b --- /dev/null +++ b/tests/edu/caltech/cs2/helpers/TestHint.java @@ -0,0 +1,12 @@ +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 TestHint { + String value(); +} diff --git a/tests/edu/caltech/cs2/interfaces/ICollectionTests.java b/tests/edu/caltech/cs2/interfaces/ICollectionTests.java index dd97298717f56d8b07903450a6fda73915f0439a..65b1937f643955d60a06312d64e6b0ca50d54eb6 100644 --- a/tests/edu/caltech/cs2/interfaces/ICollectionTests.java +++ b/tests/edu/caltech/cs2/interfaces/ICollectionTests.java @@ -1,7 +1,10 @@ package edu.caltech.cs2.interfaces; +import edu.caltech.cs2.helpers.DependsOn; +import edu.caltech.cs2.helpers.TestDescription; import edu.caltech.cs2.interfaces.ICollection; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -23,6 +26,8 @@ public interface ICollectionTests { @Order(collectionTestLevel) @DisplayName("Simple tests of various ICollection functions") @ParameterizedTest(name = "Test add(), size(), isEmpty(), contains(), and clear() on [{arguments}]") + @DependsOn({"constructor", "iterator", "size", "addBack"}) + @TestDescription("This test checks the built-in functions we gave you by using your iterator and size methods.") @ValueSource(strings = { "", "1", @@ -70,6 +75,8 @@ public interface ICollectionTests { @Order(collectionTestLevel) @Test @DisplayName("Test repeated emptying and filling of ICollection with single element") + @DependsOn({"constructor", "size", "addBack", "removeBack"}) + @TestDescription("This test repeatedly adds and clears the data structure.") default void testFillEmptyCollection() { ICollection<Object> impl = newCollection(); for (int i = 0; i < 10; i ++) { @@ -83,6 +90,8 @@ public interface ICollectionTests { @Order(collectionTestLevel) @DisplayName("Stress test for add(...)") @ParameterizedTest(name = "Test add()ing {1} random numbers with seed = {0}") + @DependsOn({"constructor", "size", "addBack", "iterator"}) + @TestDescription("This test adds a ton of random numbers to your data structure and checks that they're all actually in there.") @CsvSource({ "100, 3000", "42, 1000" }) @@ -108,6 +117,8 @@ public interface ICollectionTests { @Order(collectionTestLevel) @DisplayName("Stress test for contains(...)") @ParameterizedTest(name = "Test contains() with {1} random numbers and seed = {0}") + @DependsOn({"constructor", "size", "addBack", "iterator"}) + @TestDescription("This test adds a ton of random numbers to your data structure and checks that they're all actually in there.") @CsvSource({ "100, 3000", "42, 1000" }) diff --git a/tests/edu/caltech/cs2/interfaces/IDequeTests.java b/tests/edu/caltech/cs2/interfaces/IDequeTests.java index 591a20b840727457be9609c37122bc980148141e..91dd3d1becbcbc22d6a69cbd3294b5d80011d16a 100644 --- a/tests/edu/caltech/cs2/interfaces/IDequeTests.java +++ b/tests/edu/caltech/cs2/interfaces/IDequeTests.java @@ -1,5 +1,8 @@ package edu.caltech.cs2.interfaces; +import edu.caltech.cs2.helpers.DependsOn; +import edu.caltech.cs2.helpers.Reflection; +import edu.caltech.cs2.helpers.TestDescription; import org.hamcrest.MatcherAssert; import org.hamcrest.collection.IsEmptyIterable; import org.junit.jupiter.api.DisplayName; @@ -18,12 +21,14 @@ import java.util.Random; import static edu.caltech.cs2.project03.Project03TestOrdering.*; import static org.junit.jupiter.api.Assertions.*; -public interface IDequeTests extends ICollectionTests { +public interface IDequeTests { IDeque<Object> newDeque(); @Order(dequeTestLevel) @DisplayName("Test Deque Iterator") @ParameterizedTest(name = "Test deque iterator on [{arguments}]") + @DependsOn({"constructor", "iterator", "size", "addBack", "removeBack"}) + @TestDescription("This test checks that your iterator works on various sizes of data structures.") @ValueSource(strings = { "", "1", @@ -51,6 +56,8 @@ public interface IDequeTests extends ICollectionTests { @Test @Order(dequeTestLevel) + @DependsOn({"constructor", "iterator", "size", "addFront", "removeFront"}) + @TestDescription("This test checks that small deques work correctly when added at the front and removed at the front.") @DisplayName("Test Deque addFront / removeFront edge cases") default void testRepeatedAddFrontRemoveFront() { ArrayDeque<Object> ref = new ArrayDeque<>(); @@ -74,6 +81,8 @@ public interface IDequeTests extends ICollectionTests { @Test @Order(dequeTestLevel) @DisplayName("Test Deque addFront / removeBack edge cases") + @DependsOn({"constructor", "iterator", "size", "addFront", "removeBack"}) + @TestDescription("This test checks that small deques work correctly when added at the front and removed at the back.") default void testRepeatedAddFrontRemoveBack() { ArrayDeque<Object> ref = new ArrayDeque<>(); IDeque<Object> impl = newDeque(); @@ -96,6 +105,8 @@ public interface IDequeTests extends ICollectionTests { @Test @Order(dequeTestLevel) @DisplayName("Test Deque addBack / removeFront edge cases") + @DependsOn({"constructor", "iterator", "size", "addBack", "removeFront"}) + @TestDescription("This test checks that small deques work correctly when added at the back and removed at the front.") default void testRepeatedAddBackRemoveFront() { ArrayDeque<Object> ref = new ArrayDeque<>(); IDeque<Object> impl = newDeque(); @@ -117,6 +128,8 @@ public interface IDequeTests extends ICollectionTests { @Test @Order(dequeTestLevel) + @DependsOn({"constructor", "iterator", "size", "addBack", "removeBack"}) + @TestDescription("This test checks that small deques work correctly when added at the back and removed at the back.") @DisplayName("Test Deque addBack / removeBack edge cases") default void testRepeatedAddBackRemoveBack() { ArrayDeque<Object> ref = new ArrayDeque<>(); @@ -139,6 +152,8 @@ public interface IDequeTests extends ICollectionTests { @Order(dequeTestLevel) @DisplayName("Stress test for addFront(...) and peekFront(...)") @ParameterizedTest(name = "Test addFront()ing {1} random numbers with seed = {0}") + @DependsOn({"constructor", "iterator", "size", "peekFront", "addFront", "toString"}) + @TestDescription("This test checks uses large amounts of random data to test that your adds work correctly.") @CsvSource({ "100, 300", "42, 500" }) @@ -161,6 +176,8 @@ public interface IDequeTests extends ICollectionTests { @Order(dequeTestLevel) @DisplayName("Stress test for addBack(...) and peekBack(...)") + @DependsOn({"constructor", "iterator", "size", "peekBack", "addBack", "toString"}) + @TestDescription("This test checks uses large amounts of random data to test that your adds work correctly.") @ParameterizedTest(name = "Test addBack()ing {1} random numbers with seed = {0}") @CsvSource({ "100, 300", "42, 500" @@ -185,6 +202,8 @@ public interface IDequeTests extends ICollectionTests { @Order(dequeTestLevel) @DisplayName("Stress test for removeFront(...)") @ParameterizedTest(name = "Test removeFront()ing {1} random numbers with seed = {0}") + @DependsOn({"constructor", "iterator", "size", "peekFront", "addFront", "removeFront", "toString"}) + @TestDescription("This test checks uses large amounts of random data to test that your adds and removes work correctly.") @CsvSource({ "101, 300", "45, 500" }) @@ -211,6 +230,8 @@ public interface IDequeTests extends ICollectionTests { @Order(dequeTestLevel) @DisplayName("Stress test for removeBack(...)") @ParameterizedTest(name = "Test removeBack()ing {1} random numbers with seed = {0}") + @DependsOn({"constructor", "iterator", "size", "peekBack", "addBack", "removeBack", "toString"}) + @TestDescription("This test checks uses large amounts of random data to test that your adds and removes work correctly.") @CsvSource({ "101, 300", "45, 500" }) @@ -237,6 +258,8 @@ public interface IDequeTests extends ICollectionTests { @Order(dequeTestLevel) @DisplayName("Stress test full IDeque") @ParameterizedTest(name = "Test all IDeque methods {1} random numbers with seed = {0}") + @DependsOn({"everything"}) + @TestDescription("This test checks uses large amounts of random data to test that all your methods in the deque work correctly.") @CsvSource({ "102, 300", "52, 500" }) @@ -278,6 +301,8 @@ public interface IDequeTests extends ICollectionTests { @Order(dequeTestLevel) @DisplayName("Test for addAll(...)") @ParameterizedTest(name = "Test addAll with {1} random numbers and seed = {0}") + @DependsOn({"constructor", "iterator", "addBack", "toString"}) + @TestDescription("This test checks that the built-in addAll method works using your implementation of the iterator and addBack.") @CsvSource({ "99, 300", "48, 500" }) @@ -295,4 +320,21 @@ public interface IDequeTests extends ICollectionTests { MatcherAssert.assertThat("IDeque has incorrect elements / order", impl, IsIterableContainingInOrder.contains(expected)); } + + @Order(toStringTestLevel) + @DisplayName("toString() matches java.util.ArrayDeque") + @DependsOn({"fields", "constructors", "toString", "addBack"}) + @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" + }) + default void testToString(String inputs) { + java.util.ArrayDeque<String> reference = new java.util.ArrayDeque<String>(); + edu.caltech.cs2.interfaces.IDeque<Object> me = newDeque(); + for (String value : inputs.trim().split(", ")) { + assertEquals(reference.toString(), me.toString(), "toString outputs should be the same"); + reference.addLast(value); + me.addBack(value); + } + } } diff --git a/tests/edu/caltech/cs2/interfaces/IFixedSizeQueueTests.java b/tests/edu/caltech/cs2/interfaces/IFixedSizeQueueTests.java index b60b17f64187891063878bec2e07f6e54d0d7fde..ce121fac0c39c2aa7d0100e8d4774b1f3f470173 100644 --- a/tests/edu/caltech/cs2/interfaces/IFixedSizeQueueTests.java +++ b/tests/edu/caltech/cs2/interfaces/IFixedSizeQueueTests.java @@ -2,6 +2,7 @@ package edu.caltech.cs2.interfaces; import edu.caltech.cs2.datastructures.CircularArrayFixedSizeQueue; import edu.caltech.cs2.helpers.Reflection; +import edu.caltech.cs2.helpers.TestHint; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; import org.junit.jupiter.params.ParameterizedTest; @@ -19,6 +20,7 @@ public interface IFixedSizeQueueTests extends IQueueTests { @Order(fixedSizeQueueLevel) @DisplayName("Overflow test for enqueue(...)") + @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.") @ParameterizedTest(name = "Test randomly enqueue()ing/dequeue()ing {1} random numbers with seed = {0} and fixed array size = {2}") @CsvSource({ "97, 3000, 100", "38, 5000, 10" diff --git a/tests/edu/caltech/cs2/interfaces/IStyleTests.java b/tests/edu/caltech/cs2/interfaces/IStyleTests.java new file mode 100644 index 0000000000000000000000000000000000000000..a220fbc8ecd90a79d59c0914e01c3ced6ca74ec0 --- /dev/null +++ b/tests/edu/caltech/cs2/interfaces/IStyleTests.java @@ -0,0 +1,88 @@ +package edu.caltech.cs2.interfaces; + +import edu.caltech.cs2.helpers.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static edu.caltech.cs2.project03.Project03TestOrdering.classSpecificTestLevel; + +public interface IStyleTests { + String getSource(); + Class<?> getClazz(); + List<String> getPublicInterface(); + int getMaxFields(); + + @Order(classSpecificTestLevel) + @DisplayName("The overall number of fields is small") + @TestHint("It's good style to use the smallest number of fields that you can in each class.") + @Test + default void testSmallNumberOfFields() { + Reflection.assertFieldsLessThan(getClazz(), "private", getMaxFields()); + } + + + @Order(classSpecificTestLevel) + @DisplayName("The public interface is correct") + @TestDescription("This test checks that you haven't declared any extra public methods.") + @TestHint("You are welcome to make any extra methods you want, but they have to be private!") + @Test + default void testPublicInterface() { + Reflection.assertPublicInterface(getClazz(), getPublicInterface()); + } + + @Order(classSpecificTestLevel) + @DisplayName("Does not use or import disallowed classes") + @TestHint("Remember that you're not allowed to use anything in java.util except Iterator and Random!") + @Test + default void testForInvalidClasses() { + List<String> regexps = List.of("java\\.util\\.(?!Iterator|Random|function.Function)", "java\\.lang\\.reflect", "java\\.io"); + Inspection.assertNoImportsOf(getSource(), regexps); + Inspection.assertNoUsageOf(getSource(), regexps); + } + + @Order(classSpecificTestLevel) + @DisplayName("Does not attempt to get around constructor counts") + @Test + default void testForAvoidCounters() { + List<String> regexps = List.of("NUM_CALLS"); + Inspection.assertNoUsageOf(getSource(), regexps); + } + + @Order(classSpecificTestLevel) + @DisplayName("Uses this(...) notation in all but one constructor") + @TestDescription("This test is checking that all of your constructors except one call the other constructors rather than duplicating code.") + @Test + default void testForThisConstructors() { + Inspection.assertConstructorHygiene(getSource()); + } + + @Order(classSpecificTestLevel) + @DisplayName("All fields have modifiers") + @TestDescription("This test checks that every field is private or public.") + @Test + default void testFieldModifiers() { + Reflection.assertFieldModifiers(getClazz()); + } + + @Order(classSpecificTestLevel) + @DisplayName("There are no public fields") + @TestHint("It's good style to never use public fields whenever avoidable.") + @Test + default void testNoPublicFields() { + Reflection.assertNoPublicFields(getClazz()); + } + + @Order(classSpecificTestLevel) + @DisplayName("There are no protected fields") + @TestHint("It's good style to never use protected fields whenever avoidable.") + @Test + default void testNoProtectedFields() { + Reflection.assertNoProtectedFields(getClazz()); + } + + + +} \ No newline at end of file diff --git a/tests/edu/caltech/cs2/project03/GuitarStringTests.java b/tests/edu/caltech/cs2/project03/GuitarStringTests.java index 81fabe06152bac315ec51b37b2778c18d5464f64..bc812b198c0d0d79741b6b9316a0a4d96bc93bb7 100644 --- a/tests/edu/caltech/cs2/project03/GuitarStringTests.java +++ b/tests/edu/caltech/cs2/project03/GuitarStringTests.java @@ -1,13 +1,15 @@ package edu.caltech.cs2.project03; -import edu.caltech.cs2.datastructures.CircularArrayFixedSizeQueue; -import edu.caltech.cs2.helpers.Inspection; import edu.caltech.cs2.helpers.Reflection; +import edu.caltech.cs2.helpers.TestDescription; +import edu.caltech.cs2.helpers.TestExtension; +import edu.caltech.cs2.helpers.TestHint; import edu.caltech.cs2.interfaces.IFixedSizeQueue; +import edu.caltech.cs2.interfaces.IStyleTests; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; import java.io.File; import java.io.FileNotFoundException; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Random; import java.util.Scanner; import java.util.stream.Stream; -import java.util.function.Function; import static edu.caltech.cs2.project03.Project03TestOrdering.*; import static java.lang.Math.abs; @@ -26,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @Tag("A") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ExtendWith(TestExtension.class) public class GuitarStringTests { private static String STRING_SOURCE = "src/edu/caltech/cs2/project03/CircularArrayFixedSizeQueueGuitarString.java"; @@ -36,203 +38,180 @@ public class GuitarStringTests { public static IFixedSizeQueue<Double> getQueueFromString(CircularArrayFixedSizeQueueGuitarString string) { String queueName = Reflection.getFieldByType(CircularArrayFixedSizeQueueGuitarString.class, IFixedSizeQueue.class) - .getName(); + .getName(); return Reflection.getFieldValue(CircularArrayFixedSizeQueueGuitarString.class, queueName, string); } + @DisplayName("Style") + @Nested + class StyleTests implements IStyleTests { + + @Order(classSpecificTestLevel) + @DisplayName("There are three static fields: the two double constants and a random value generator") + @TestDescription("This test is checking that you have three constant fields for the \"magic numbers\" and the Random instance.") + @Test + public void testStaticFields() { + Reflection.assertFieldsEqualTo(CircularArrayFixedSizeQueueGuitarString.class, "static", 3); + Stream<Field> fields = Reflection.getFields(CircularArrayFixedSizeQueueGuitarString.class); + fields.filter(Reflection.hasModifier("static")).forEach((field) -> { + Reflection.checkFieldModifiers(field, List.of("private", "static")); + assertTrue(Reflection.hasModifier("final").test(field) || field.getType().equals(Random.class), + "non-final static class must be a random value generator"); + }); + } - @Order(classSpecificTestLevel) - @DisplayName("Does not use or import disallowed classes") - @Test - public void testForInvalidClasses() { - List<String> regexps = List.of("java\\.util\\.(?!Random|function.Function)", "java\\.lang\\.reflect", "java\\.io"); - Inspection.assertNoImportsOf(STRING_SOURCE, regexps); - Inspection.assertNoUsageOf(STRING_SOURCE, regexps); - } - - @Order(classSpecificTestLevel) - @DisplayName("Uses this(...) notation in all but one constructor") - @Test - public void testForThisConstructors() { - Inspection.assertConstructorHygiene(STRING_SOURCE); - } - - @Order(classSpecificTestLevel) - @DisplayName("There are three static fields: the two double constants and a random value generator") - @Test - public void testStaticFields() { - Reflection.assertFieldsEqualTo(CircularArrayFixedSizeQueueGuitarString.class, "static", 3); - Stream<Field> fields = Reflection.getFields(CircularArrayFixedSizeQueueGuitarString.class); - fields.filter(Reflection.hasModifier("static")).forEach((field) -> { - Reflection.checkFieldModifiers(field, List.of("private", "static")); - assertTrue(Reflection.hasModifier("final").test(field) || field.getType().equals(Random.class), - "non-final static class must be a random value generator"); - }); - } - - @Order(classSpecificTestLevel) - @DisplayName("The overall number of fields is small") - @Test - public void testSmallNumberOfFields() { - Reflection.assertFieldsLessThan(CircularArrayFixedSizeQueueGuitarString.class, "private", 5); - } - - @Order(classSpecificTestLevel) - @DisplayName("There are no public fields") - @Test - public void testNoPublicFields() { - Reflection.assertNoPublicFields(CircularArrayFixedSizeQueueGuitarString.class); - } - - @Order(classSpecificTestLevel) - @DisplayName("There are no protected fields") - @Test - public void testNoProtectedFields() { - Reflection.assertNoProtectedFields(CircularArrayFixedSizeQueueGuitarString.class); - } - - @Order(classSpecificTestLevel) - @DisplayName("All fields in CircularArrayFixedSizeQueueGuitarString have modifiers") - @Test - public void testFieldModifiers() { - Reflection.assertFieldModifiers(CircularArrayFixedSizeQueueGuitarString.class); - } - - @Order(classSpecificTestLevel) - @DisplayName("The public interface is correct") - @Test - public void testPublicInterface() { - Reflection.assertPublicInterface(CircularArrayFixedSizeQueueGuitarString.class, - List.of("length", "pluck", "tic", "sample")); - } + public int getMaxFields() { + return 3; + } - @Order(classSpecificTestLevel) - @DisplayName("The constructor correctly sets up the queue") - @ParameterizedTest(name = "Test constructor with CircularArrayFixedSizeQueue and a frequency of {0} Hz; expected queue size is {1}") - @CsvSource({ "110, 401", "340, 130", "512, 87", "600.5, 74", "880, 51" }) - public void testConstructor(double frequency, int expectedSize) { - CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); - IFixedSizeQueue<Double> queue = getQueueFromString(string); - - assertEquals(expectedSize, queue.size(), "Queue size is not equal to expected size"); - for (double val : queue) { - assertEquals(0, val, "All values in queue should be equal to 0"); + public List<String> getPublicInterface() { + return List.of("length", "pluck", "tic", "sample"); } - } - @Order(guitarStringTestLevel) - @DisplayName("The pluck() method randomizes the values in the queue") - @ParameterizedTest(name = "Test pluck() with CircularArrayFixedSizeQueue and a frequency of {0} Hz") - @CsvSource({ "100", "50", "10", "8", "5" }) - public void testPluck(double frequency) { - final double DELTA = 0.05; - // Set up class and retrieve queue - CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); - IFixedSizeQueue<Double> queue = getQueueFromString(string); - // Checks all values are initially 0 - for (double val : queue) { - assertEquals(0, val, "initial values must be 0"); + @Override + public String getSource() { + return STRING_SOURCE; } - string.pluck(); - queue = getQueueFromString(string); - double sum = 0; - double absSum = 0; - for (double val : queue) { - sum += val; - absSum += abs(val); + + @Override + public Class<?> getClazz() { + return CircularArrayFixedSizeQueueGuitarString.class; } - assertEquals(0, sum / queue.size(), DELTA, "average value of uniform distribution should be near 0"); - assertEquals(0.25, absSum / queue.size(), DELTA, "average magnitude of uniform distribution should be near 0.25"); } - @Order(guitarStringTestLevel) - @DisplayName("The tic() method correctly applies the Karplus-Strong algorithm") - @ParameterizedTest(name = "Test tic() with CircularArrayFixedSizeQueue and a frequency of {0} Hz; data file {1}.txt") - @CsvSource({ "10000, ticStates1", "8000, ticStates2", "5000, ticStates3" }) - public void testTic(double frequency, String filename) { - // Set up scanner - String filepath = "tests/data/" + filename + ".txt"; - Scanner in; - try { - in = new Scanner(new File(filepath)); - } catch (FileNotFoundException e) { - throw new IllegalArgumentException(filepath + " is not a valid trace file."); + @DisplayName("Implementation Tests") + @Nested + class ImplementationTests { + @Order(classSpecificTestLevel) + @DisplayName("The constructor correctly sets up the queue") + @TestHint("Make sure that your queue is the correct size according to the specification and filled with zeroes.") + @ParameterizedTest(name = "Test constructor with CircularArrayFixedSizeQueue and a frequency of {0} Hz; expected queue size is {1}") + @CsvSource({"110, 401", "340, 130", "512, 87", "600.5, 74", "880, 51"}) + public void testConstructor(double frequency, int expectedSize) { + CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); + IFixedSizeQueue<Double> queue = getQueueFromString(string); + + assertEquals(expectedSize, queue.size(), "Queue size is not equal to expected size"); + for (double val : queue) { + assertEquals(0, val, "All values in queue should be equal to 0"); + } } - // Set up class and retrieve queue - CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); - IFixedSizeQueue<Double> queue = getQueueFromString(string); - // Reinitialize queue with new data - for (int i = 0; i < queue.size(); i++) { - queue.dequeue(); - queue.enqueue(in.nextDouble()); - } - int initSize = queue.size(); - // Pass through the same number of tics as elements in the array - for (int i = 0; i < initSize; i++) { - string.tic(); + + @Order(guitarStringTestLevel) + @DisplayName("The pluck() method randomizes the values in the queue") + @ParameterizedTest(name = "Test pluck() with CircularArrayFixedSizeQueue and a frequency of {0} Hz") + @TestHint("Make sure that you're centering your distribution around the correct number.") + @CsvSource({"100", "50", "10", "8", "5"}) + public void testPluck(double frequency) { + final double DELTA = 0.05; + // Set up class and retrieve queue + CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); + IFixedSizeQueue<Double> queue = getQueueFromString(string); + // Checks all values are initially 0 + for (double val : queue) { + assertEquals(0, val, "initial values must be 0"); + } + string.pluck(); queue = getQueueFromString(string); - assertEquals(initSize, queue.size(), "queue size must remain the same"); + double sum = 0; + double absSum = 0; + for (double val : queue) { + sum += val; + absSum += abs(val); + } + assertEquals(0, sum / queue.size(), DELTA, "average value of uniform distribution should be near 0"); + assertEquals(0.25, absSum / queue.size(), DELTA, "average magnitude of uniform distribution should be near 0.25"); } - // Compare peek() values with the expected values in the files - while (in.hasNext()) { - string.tic(); - queue = getQueueFromString(string); - assertEquals(initSize, queue.size(), "queue size must remain the same"); - assertEquals(in.nextDouble(), queue.peek(), "next expected value not at front of queue"); + + @Order(guitarStringTestLevel) + @DisplayName("The tic() method correctly applies the Karplus-Strong algorithm") + @ParameterizedTest(name = "Test tic() with CircularArrayFixedSizeQueue and a frequency of {0} Hz; data file {1}.txt") + @CsvSource({"10000, ticStates1", "8000, ticStates2", "5000, ticStates3"}) + public void testTic(double frequency, String filename) { + // Set up scanner + String filepath = "tests/data/" + filename + ".txt"; + Scanner in; + try { + in = new Scanner(new File(filepath)); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException(filepath + " is not a valid trace file."); + } + // Set up class and retrieve queue + CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); + IFixedSizeQueue<Double> queue = getQueueFromString(string); + // Reinitialize queue with new data + for (int i = 0; i < queue.size(); i++) { + queue.dequeue(); + queue.enqueue(in.nextDouble()); + } + int initSize = queue.size(); + // Pass through the same number of tics as elements in the array + for (int i = 0; i < initSize; i++) { + string.tic(); + queue = getQueueFromString(string); + assertEquals(initSize, queue.size(), "queue size must remain the same"); + } + // Compare peek() values with the expected values in the files + while (in.hasNext()) { + string.tic(); + queue = getQueueFromString(string); + assertEquals(initSize, queue.size(), "queue size must remain the same"); + assertEquals(in.nextDouble(), queue.peek(), "next expected value not at front of queue"); + } } - } - @Order(guitarStringTestLevel) - @DisplayName("The length() method correctly gives the length of the queue") - @ParameterizedTest(name = "Test length() with CircularArrayFixedSizeQueue and a frequency of {0} Hz; expected length = {1}; iterations = {2}") - @CsvSource({ "110, 401, 1000", "340, 130, 500", "512, 87, 200", "600.5, 74, 150", "880, 51, 100" }) - public void testLength(double frequency, int expectedLength, int iterations) { - // Set up class and retrieve queue - CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); - IFixedSizeQueue<Double> queue = getQueueFromString(string); - - // Pluck and make sure length doesn't change - int initSize = queue.size(); - assertEquals(expectedLength, string.length(), "Length should be same as expected"); - assertEquals(queue.size(), string.length(), "Length should be same as queue size"); - string.pluck(); - queue = getQueueFromString(string); - assertEquals(initSize, string.length(), "Length should not have changed from beginning"); - assertEquals(queue.size(), string.length(), "Length should be same as queue size"); - - // Run through many iterations, making sure both the queue size and length are - // constant - for (int i = 0; i < iterations; i++) { - string.tic(); + @Order(guitarStringTestLevel) + @DisplayName("The length() method correctly gives the length of the queue") + @ParameterizedTest(name = "Test length() with CircularArrayFixedSizeQueue and a frequency of {0} Hz; expected length = {1}; iterations = {2}") + @CsvSource({"110, 401, 1000", "340, 130, 500", "512, 87, 200", "600.5, 74, 150", "880, 51, 100"}) + public void testLength(double frequency, int expectedLength, int iterations) { + // Set up class and retrieve queue + CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); + IFixedSizeQueue<Double> queue = getQueueFromString(string); + + // Pluck and make sure length doesn't change + int initSize = queue.size(); + assertEquals(expectedLength, string.length(), "Length should be same as expected"); + assertEquals(queue.size(), string.length(), "Length should be same as queue size"); + string.pluck(); queue = getQueueFromString(string); assertEquals(initSize, string.length(), "Length should not have changed from beginning"); assertEquals(queue.size(), string.length(), "Length should be same as queue size"); - } - } + // Run through many iterations, making sure both the queue size and length are + // constant + for (int i = 0; i < iterations; i++) { + string.tic(); + queue = getQueueFromString(string); + assertEquals(initSize, string.length(), "Length should not have changed from beginning"); + assertEquals(queue.size(), string.length(), "Length should be same as queue size"); + } + + } - @Order(guitarStringTestLevel) - @DisplayName("The sample() method gives the same values as peek()ing the queue") - @ParameterizedTest(name = "Test sample() with CircularArrayFixedSizeQueue and a frequency of {0} Hz") - @CsvSource({ "110, 1000", "340, 500", "512, 200", "600.5, 150", "880, 100" }) - public void testSample(double frequency, int iterations) { - // Set up class and retrieve queue - CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); - IFixedSizeQueue<Double> queue = getQueueFromString(string); - - // Pluck and make sure initial samples are correct - assertEquals(0, string.sample(), "Sample should return 0 before plucking"); - assertEquals(queue.peek(), string.sample(), "Sample should same as peek()ing queue"); - string.pluck(); - queue = getQueueFromString(string); - assertEquals(queue.peek(), string.sample(), "Sample should same as peek()ing queue"); - - // Run through many iterations, making sure sample() matches peek() - for (int i = 0; i < iterations; i++) { - string.tic(); + @Order(guitarStringTestLevel) + @DisplayName("The sample() method gives the same values as peek()ing the queue") + @ParameterizedTest(name = "Test sample() with CircularArrayFixedSizeQueue and a frequency of {0} Hz") + @CsvSource({"110, 1000", "340, 500", "512, 200", "600.5, 150", "880, 100"}) + public void testSample(double frequency, int iterations) { + // Set up class and retrieve queue + CircularArrayFixedSizeQueueGuitarString string = constructGuitarString(frequency); + IFixedSizeQueue<Double> queue = getQueueFromString(string); + + // Pluck and make sure initial samples are correct + assertEquals(0, string.sample(), "Sample should return 0 before plucking"); + assertEquals(queue.peek(), string.sample(), "Sample should same as peek()ing queue"); + string.pluck(); queue = getQueueFromString(string); assertEquals(queue.peek(), string.sample(), "Sample should same as peek()ing queue"); - } + // Run through many iterations, making sure sample() matches peek() + for (int i = 0; i < iterations; i++) { + string.tic(); + queue = getQueueFromString(string); + assertEquals(queue.peek(), string.sample(), "Sample should same as peek()ing queue"); + } + + } } }