1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-24 22:23:21 +00:00
CC-Tweaked/src/test/java/dan200/computercraft/ingame/mod/TestRunner.kt
Jonathan Coates 331031be45 Run integration tests in-game
Name a more iconic duo than @SquidDev and over-engineered test
frameworks.

This uses Minecraft's test core[1] plus a home-grown framework to run
tests against computers in-world.

The general idea is:
 - Build a structure in game.
 - Save the structure to a file. This will be spawned in every time the
   test is run.
 - Write some code which asserts the structure behaves in a particular
   way. This is done in Kotlin (shock, horror), as coroutines give us a
   nice way to run asynchronous code while still running on the main
   thread.

As with all my testing efforts, I still haven't actually written any
tests!  It'd be good to go through some of the historic ones and write
some tests though. Turtle block placing and computer redstone
interactions are probably a good place to start.

[1]: https://www.youtube.com/watch?v=vXaWOJTCYNg
2021-01-09 19:50:27 +00:00

63 lines
2.2 KiB
Kotlin

package dan200.computercraft.ingame.mod
import dan200.computercraft.ingame.api.TestContext
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.minecraft.test.TestCollection
import net.minecraft.test.TestTrackerHolder
import java.lang.reflect.Method
import java.util.*
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.function.Consumer
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.Continuation
import kotlin.coroutines.ContinuationInterceptor
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.full.callSuspend
import kotlin.reflect.jvm.kotlinFunction
internal class TestRunner(private val name: String, private val method: Method) : Consumer<TestTrackerHolder> {
override fun accept(t: TestTrackerHolder) {
GlobalScope.launch(MainThread + CoroutineName(name)) {
val testContext = TestContext(t)
try {
val instance = method.declaringClass.newInstance()
val function = method.kotlinFunction;
if (function == null) {
method.invoke(instance, testContext)
} else {
function.callSuspend(instance, testContext)
}
testContext.ok()
} catch (e: Exception) {
testContext.fail(e)
}
}
}
}
/**
* A coroutine scope which runs everything on the main thread.
*/
internal object MainThread : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
private val queue: Queue<() -> Unit> = ConcurrentLinkedDeque()
fun tick() {
while (true) {
val q = queue.poll() ?: break;
q.invoke()
}
}
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = MainThreadInterception(continuation)
private class MainThreadInterception<T>(val cont: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext get() = cont.context
override fun resumeWith(result: Result<T>) {
queue.add { cont.resumeWith(result) }
}
}
}