diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/src/main/kotlin/Annotations.kt b/src/main/kotlin/Annotations.kt index dfae84b..f128bb8 100644 --- a/src/main/kotlin/Annotations.kt +++ b/src/main/kotlin/Annotations.kt @@ -307,6 +307,26 @@ fun Method.isCompare() = isAnnotationPresent(Compare::class.java) class CompareException(message: String? = null) : RuntimeException(message) +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ShouldContinue { + companion object { + val name: String = ShouldContinue::class.java.simpleName + fun validate(method: Method) { + check(method.isPrivate()) { "@$name methods must be private" } + check(!method.isStatic()) { "@$name methods must not be static" } + check(method.returnType.name == "boolean") { + "@$name methods must return a boolean" + } + check(method.parameterTypes.isEmpty()) { + "@$name methods must not accept parameters" + } + method.isAccessible = true + } + } +} +fun Method.isShouldContinue() = isAnnotationPresent(ShouldContinue::class.java) + @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class Configure(val strictOutput: Boolean = false) @@ -351,7 +371,9 @@ fun Executable.isJenisol() = setOf( Verify::class.java, Both::class.java, FilterParameters::class.java, - InstanceValidator::class.java + InstanceValidator::class.java, + Compare::class.java, + ShouldContinue::class.java ).any { isAnnotationPresent(it) } diff --git a/src/main/kotlin/Solution.kt b/src/main/kotlin/Solution.kt index 4daf8b1..66736be 100644 --- a/src/main/kotlin/Solution.kt +++ b/src/main/kotlin/Solution.kt @@ -108,11 +108,19 @@ class Solution(val solution: Class<*>) { val instanceValidator = solution.declaredMethods.filter { it.isInstanceValidator() }.also { - checkDesign(it.size <= 1) { "Solution has multiple instance validators" } + checkDesign(it.size <= 1) { "Solution has multiple methods annotated with @InstanceValidator" } }.firstOrNull()?.also { checkDesign { InstanceValidator.validate(it) } } + val shouldContinue = solution.declaredMethods.filter { + it.isShouldContinue() + }.also { + checkDesign(it.size <= 1) { "Solution has multiple methods annotated with @ShouldContinue" } + }.firstOrNull()?.also { + checkDesign { ShouldContinue.validate(it) } + } + val customCompares = solution.declaredMethods.filter { it.isCompare() }.filterNotNull().let { compareMethods -> diff --git a/src/main/kotlin/Testing.kt b/src/main/kotlin/Testing.kt index e35b717..faa0d8b 100644 --- a/src/main/kotlin/Testing.kt +++ b/src/main/kotlin/Testing.kt @@ -254,7 +254,8 @@ class TestRunner( val failed: Boolean get() = testResults.any { it.failed } val ready: Boolean - get() = testResults.none { it.failed } && receivers != null + get() = testResults.none { it.failed } && receivers != null && shouldContinue + private var shouldContinue = true var lastComplexity: Complexity? = null @@ -583,6 +584,11 @@ class TestRunner( initialized = true } else { run(methodIterator.first(), stepCount) + if (submission.solution.shouldContinue != null && receivers != null) { + shouldContinue = unwrap { + submission.solution.shouldContinue.invoke(receivers!!.solution) + } as Boolean + } } return ready } diff --git a/src/main/resources/edu.illinois.cs.cs125.jenisol.core.version b/src/main/resources/edu.illinois.cs.cs125.jenisol.core.version index cd1934c..7b1907c 100644 --- a/src/main/resources/edu.illinois.cs.cs125.jenisol.core.version +++ b/src/main/resources/edu.illinois.cs.cs125.jenisol.core.version @@ -1 +1 @@ -version=2021.6.9 \ No newline at end of file +version=2021.7.0 \ No newline at end of file diff --git a/src/test/java/edu/illinois/cs/cs125/jenisol/core/TestJavaExamples.kt b/src/test/java/edu/illinois/cs/cs125/jenisol/core/TestJavaExamples.kt index abf1d9c..b7eff27 100644 --- a/src/test/java/edu/illinois/cs/cs125/jenisol/core/TestJavaExamples.kt +++ b/src/test/java/edu/illinois/cs/cs125/jenisol/core/TestJavaExamples.kt @@ -298,6 +298,9 @@ class TestJavaExamples : StringSpec( examples.java.receiver.rejectstaticfield.Correct::class.java.also { "${it.testName()}" { it.test() } } + examples.java.receiver.withshouldcontinue.Correct::class.java.also { + "${it.testName()}" { it.test() } + } examples.java.receiver.timeouttest.Correct::class.java.also { "${it.testName()}" { val runnable = object : Runnable { diff --git a/src/test/java/examples/java/receiver/withshouldcontinue/Correct.java b/src/test/java/examples/java/receiver/withshouldcontinue/Correct.java new file mode 100644 index 0000000..2dd45ff --- /dev/null +++ b/src/test/java/examples/java/receiver/withshouldcontinue/Correct.java @@ -0,0 +1,14 @@ +package examples.java.receiver.withshouldcontinue; + +import edu.illinois.cs.cs125.jenisol.core.ShouldContinue; + +public class Correct { + @ShouldContinue + private boolean shouldContinue() { + return false; + } + + public int getValue() { + return 0; + } +} diff --git a/src/test/java/examples/java/receiver/withshouldcontinue/Correct0.java b/src/test/java/examples/java/receiver/withshouldcontinue/Correct0.java new file mode 100644 index 0000000..158d235 --- /dev/null +++ b/src/test/java/examples/java/receiver/withshouldcontinue/Correct0.java @@ -0,0 +1,9 @@ +package examples.java.receiver.withshouldcontinue; + +public class Correct0 { + private int value = 0; + + public int getValue() { + return value--; + } +} diff --git a/src/test/java/examples/java/receiver/withshouldcontinue/Incorrect0.java b/src/test/java/examples/java/receiver/withshouldcontinue/Incorrect0.java new file mode 100644 index 0000000..2323982 --- /dev/null +++ b/src/test/java/examples/java/receiver/withshouldcontinue/Incorrect0.java @@ -0,0 +1,7 @@ +package examples.java.receiver.withshouldcontinue; + +public class Incorrect0 { + public int getValue() { + return 1; + } +}