Skip to content

Commit b234c9f

Browse files
committed
Further restrict DSL.
Use a scope interface to hide Kotest internals and only allow part scopes to contain tests defined by the DSL.
1 parent c74eb5e commit b234c9f

File tree

3 files changed

+49
-65
lines changed

3 files changed

+49
-65
lines changed

aockt-test/api/aockt-test.api

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,9 @@ public abstract class io/github/jadarma/aockt/test/AdventSpec : io/kotest/core/s
2222
public final fun threads ()Ljava/lang/Integer;
2323
}
2424

25-
public final class io/github/jadarma/aockt/test/AdventSpecExampleContainerScope : io/kotest/core/spec/style/scopes/ContainerScope {
26-
public fun <init> (Lkotlin/jvm/functions/Function1;Lio/kotest/core/spec/style/scopes/FunSpecContainerScope;)V
27-
public fun afterAny (Lkotlin/jvm/functions/Function2;)V
28-
public fun afterContainer (Lkotlin/jvm/functions/Function2;)V
29-
public fun afterEach (Lkotlin/jvm/functions/Function2;)V
30-
public fun afterTest (Lkotlin/jvm/functions/Function2;)V
31-
public fun beforeAny (Lkotlin/jvm/functions/Function2;)V
32-
public fun beforeContainer (Lkotlin/jvm/functions/Function2;)V
33-
public fun beforeEach (Lkotlin/jvm/functions/Function2;)V
34-
public fun beforeTest (Lkotlin/jvm/functions/Function2;)V
35-
public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext;
36-
public fun getTestCase ()Lio/kotest/core/test/TestCase;
37-
public fun hasChildren ()Z
38-
public fun registerContainer (Lio/kotest/core/names/TestName;ZLio/kotest/core/test/config/TestConfig;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
39-
public fun registerTest (Lio/kotest/core/names/TestName;ZLio/kotest/core/test/config/TestConfig;Lio/kotest/core/test/TestType;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
40-
public fun registerTest (Lio/kotest/core/names/TestName;ZLio/kotest/core/test/config/TestConfig;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
41-
public fun registerTestCase (Lio/kotest/core/test/NestedTest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
42-
public final fun shouldAllOutput (Ljava/lang/Iterable;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
43-
public final fun shouldOutput (Ljava/lang/String;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
25+
public abstract interface class io/github/jadarma/aockt/test/AdventSpec$PartScope {
26+
public abstract fun shouldAllOutput (Ljava/lang/Iterable;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
27+
public abstract fun shouldOutput (Ljava/lang/String;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
4428
}
4529

4630
public final class io/github/jadarma/aockt/test/AocKtExtension : kotlin/coroutines/AbstractCoroutineContextElement, io/kotest/core/extensions/DisplayNameFormatterExtension, io/kotest/core/extensions/TestCaseExtension {

aockt-test/src/main/kotlin/AdventSpec.kt

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.github.jadarma.aockt.test.internal.AdventDayID
55
import io.github.jadarma.aockt.test.internal.AdventDayPart
66
import io.github.jadarma.aockt.test.internal.AdventDayPart.One
77
import io.github.jadarma.aockt.test.internal.AdventDayPart.Two
8+
import io.github.jadarma.aockt.test.internal.AdventSpecPartScope
89
import io.github.jadarma.aockt.test.internal.AocktDsl
910
import io.github.jadarma.aockt.test.internal.ConfigurationException
1011
import io.github.jadarma.aockt.test.internal.DuplicatePartDefinitionException
@@ -110,21 +111,47 @@ public abstract class AdventSpec<T : Solution>(
110111
private var isPartOneDefined: Boolean = false
111112
private var isPartTwoDefined: Boolean = false
112113

114+
/** A DSL scope for defining example assertions for puzzle parts. */
115+
@AocktDsl
116+
public interface PartScope {
117+
118+
/**
119+
* Creates a new test that asserts that when given this string as input, it gets the correct [expected] answer.
120+
* The [expected] value can be anything and will be tested against its `.toString()` value.
121+
*
122+
* @receiver The example puzzle input.
123+
* @param expected The correct answer to the puzzle for the given input.
124+
*/
125+
public suspend infix fun String.shouldOutput(expected: Any)
126+
127+
/**
128+
* For each of the values given creates a new test that asserts that when given as input, it gets the correct
129+
* [expected] answer.
130+
* The [expected] value can be anything and will be tested against its `.toString()` value.
131+
*
132+
* _NOTE:_ This should be equivalent to calling [shouldOutput] for every input.
133+
*
134+
* @receiver The example puzzle inputs.
135+
* @param expected The correct answer to the puzzle for all given inputs.
136+
*/
137+
public suspend infix fun Iterable<String>.shouldAllOutput(expected: Any)
138+
}
139+
113140
/**
114141
* Provides a context to test the implementation of one of a [Solution]'s part function.
115142
*
116143
* Will create a context with two tests:
117144
* - Verifies the output, given the input file has been added to the test resources.
118145
* If the solution is known as well, also validates the answer matches it.
119-
* - Verifies the given examples in an [AdventSpecExampleContainerScope], useful for a TDD approach when
146+
* - Verifies the given examples in an [AdventSpec.PartScope], useful for a TDD approach when
120147
* implementing the solution for the first time.
121148
*
122149
* @param part The part selector.
123150
* @param enabled If set to false, part one will not be tested.
124151
* @param expensive This part is known to produce answers in a longer timespan.
125152
* @param executionMode Specifies which tests defined for this part will be enabled.
126153
* @param efficiencyBenchmark The maximum amount of time a solution can take to finish to be considered efficient.
127-
* @param examples Test the solution against example inputs defined in this [AdventSpecExampleContainerScope].
154+
* @param examples Test the solution against example inputs defined in this [AdventSpec.PartScope].
128155
*/
129156
@Suppress("LongParameterList", "ThrowsCount", "LongMethod", "CyclomaticComplexMethod")
130157
private fun partTest(
@@ -133,7 +160,7 @@ public abstract class AdventSpec<T : Solution>(
133160
expensive: Boolean,
134161
executionMode: ExecMode?,
135162
efficiencyBenchmark: Duration?,
136-
examples: (suspend AdventSpecExampleContainerScope.() -> Unit)?,
163+
examples: (suspend PartScope.() -> Unit)?,
137164
) {
138165
if (efficiencyBenchmark != null && !efficiencyBenchmark.isPositive()) {
139166
throw ConfigurationException("Efficiency benchmark must be a positive value, but was: $efficiencyBenchmark")
@@ -163,7 +190,7 @@ public abstract class AdventSpec<T : Solution>(
163190

164191
if (examples != null) {
165192
context("Validates the examples").config(enabled = execMode != ExecMode.SkipExamples) {
166-
AdventSpecExampleContainerScope(partFunction, this).examples()
193+
AdventSpecPartScope(partFunction, this).examples()
167194
}
168195
}
169196

@@ -226,29 +253,22 @@ public abstract class AdventSpec<T : Solution>(
226253
* Will create a context with two tests:
227254
* - Verifies the output, given the input file has been added to the test resources.
228255
* If the solution is known as well, also validates the answer matches it.
229-
* - Verifies the given examples in an [AdventSpecExampleContainerScope], useful for a TDD approach when
256+
* - Verifies the given examples in an [AdventSpec.PartScope], useful for a TDD approach when
230257
* implementing the solution for the first time.
231258
*
232259
* @param enabled If set to false, part one will not be tested.
233260
* @param expensive This part is known to produce answers in a longer timespan.
234261
* @param executionMode Specifies which tests defined for this part will be enabled.
235262
* @param efficiencyBenchmark The maximum amount of time a solution can take to finish to be considered efficient.
236-
* @param test Test the solution against example inputs defined in this [AdventSpecExampleContainerScope].
263+
* @param test Test the solution against example inputs defined in this [AdventSpec.PartScope].
237264
*/
238265
public fun partOne(
239266
enabled: Boolean = true,
240267
expensive: Boolean = false,
241268
executionMode: ExecMode? = null,
242269
efficiencyBenchmark: Duration? = null,
243-
test: (suspend AdventSpecExampleContainerScope.() -> Unit)? = null,
244-
): Unit = partTest(
245-
part = One,
246-
enabled = enabled,
247-
expensive = expensive,
248-
executionMode = executionMode,
249-
efficiencyBenchmark = efficiencyBenchmark,
250-
examples = test,
251-
)
270+
test: (suspend PartScope.() -> Unit)? = null,
271+
): Unit = partTest(One, enabled, expensive, executionMode, efficiencyBenchmark, test)
252272

253273
/**
254274
* Provides a context to test the implementation a [Solution.partTwo] function.
@@ -257,27 +277,20 @@ public abstract class AdventSpec<T : Solution>(
257277
* Will create a context with two tests:
258278
* - Verifies the output, given the input file has been added to the test resources.
259279
* If the solution is known as well, also validates the answer matches it.
260-
* - Verifies the given examples in an [AdventSpecExampleContainerScope], useful for a TDD approach when
280+
* - Verifies the given examples in an [AdventSpec.PartScope], useful for a TDD approach when
261281
* implementing the solution for the first time.
262282
*
263283
* @param enabled If set to false, part one will not be tested.
264284
* @param expensive This part is known to produce answers in a longer timespan.
265285
* @param executionMode Specifies which tests defined for this part will be enabled.
266286
* @param efficiencyBenchmark The maximum amount of time a solution can take to finish to be considered efficient.
267-
* @param test Test the solution against example inputs defined in this [AdventSpecExampleContainerScope].
287+
* @param test Test the solution against example inputs defined in this [AdventSpec.PartScope].
268288
*/
269289
public fun partTwo(
270290
enabled: Boolean = true,
271291
expensive: Boolean = false,
272292
executionMode: ExecMode? = null,
273293
efficiencyBenchmark: Duration? = null,
274-
test: (suspend AdventSpecExampleContainerScope.() -> Unit)? = null,
275-
): Unit = partTest(
276-
part = Two,
277-
enabled = enabled,
278-
expensive = expensive,
279-
executionMode = executionMode,
280-
efficiencyBenchmark = efficiencyBenchmark,
281-
examples = test,
282-
)
294+
test: (suspend PartScope.() -> Unit)? = null,
295+
): Unit = partTest(Two, enabled, expensive, executionMode, efficiencyBenchmark, test)
283296
}

aockt-test/src/main/kotlin/AdventSpecExampleContainerScope.kt renamed to aockt-test/src/main/kotlin/internal/AdventSpecPartScope.kt

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package io.github.jadarma.aockt.test
1+
package io.github.jadarma.aockt.test.internal
22

3-
import io.github.jadarma.aockt.test.internal.AocktDsl
4-
import io.github.jadarma.aockt.test.internal.PuzzleInput
3+
import io.github.jadarma.aockt.test.AdventSpec
54
import io.kotest.assertions.throwables.shouldNotThrowAny
65
import io.kotest.assertions.withClue
7-
import io.kotest.core.spec.KotestTestScope
86
import io.kotest.core.spec.style.scopes.ContainerScope
97
import io.kotest.core.spec.style.scopes.FunSpecContainerScope
108
import io.kotest.matchers.shouldBe
@@ -15,40 +13,29 @@ import io.kotest.matchers.shouldBe
1513
* @param implementation The function to be tested.
1614
* @param context The parent context in which to append these test cases.
1715
*/
18-
@KotestTestScope
19-
@AocktDsl
20-
public class AdventSpecExampleContainerScope(
16+
internal class AdventSpecPartScope(
2117
private val implementation: (String) -> Any,
2218
private val context: FunSpecContainerScope,
23-
) : ContainerScope by context {
19+
) : AdventSpec.PartScope, ContainerScope by context {
2420

2521
/** Incrementing counter for the example test cases. */
2622
private var exampleNumber: Int = 0
2723

28-
/**
29-
* Creates a new test that asserts that when given this string as input, we get the correct [expected] answer.
30-
* The [expected] value can be anything and will be tested against its String value.
31-
*/
32-
public suspend infix fun String.shouldOutput(expected: Any) {
24+
override suspend infix fun String.shouldOutput(expected: Any) {
3325
exampleNumber++
3426
val input = PuzzleInput(this)
3527
val preview = input.preview()
3628
context.test("Example #$exampleNumber") {
3729
withClue("Expected answer '$expected' for input: $preview") {
3830
val answer = shouldNotThrowAny {
39-
this@AdventSpecExampleContainerScope.implementation(input.toString()).toString()
31+
this@AdventSpecPartScope.implementation(input.toString()).toString()
4032
}
4133
answer shouldBe expected.toString()
4234
}
4335
}
4436
}
4537

46-
/**
47-
* For each of the values given creates a new test that asserts that when given as input, we get the correct
48-
* [expected] answer.
49-
* The [expected] value can be anything and will be tested against its String value.
50-
*/
51-
public suspend infix fun Iterable<String>.shouldAllOutput(expected: Any) {
38+
override suspend infix fun Iterable<String>.shouldAllOutput(expected: Any) {
5239
forEach { it shouldOutput expected }
5340
}
5441
}

0 commit comments

Comments
 (0)