mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-06-24 22:23:21 +00:00
![Jonathan Coates](/assets/img/avatar_default.png)
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
63 lines
2.2 KiB
Kotlin
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) }
|
|
}
|
|
}
|
|
}
|