mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-14 19:25:43 +00:00
Add a fancy test system for async methods
Written in order to ~avoid working on~ test #695. Sadly, no luck there.
This commit is contained in:
parent
3a80b51a9f
commit
d50db8a6f3
@ -62,6 +62,7 @@ public interface IAPIEnvironment
|
||||
@Nullable
|
||||
IPeripheral getPeripheral( ComputerSide side );
|
||||
|
||||
@Nullable
|
||||
String getLabel();
|
||||
|
||||
void setLabel( @Nullable String label );
|
||||
|
121
src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt
Normal file
121
src/test/java/dan200/computercraft/core/apis/AsyncRunner.kt
Normal file
@ -0,0 +1,121 @@
|
||||
package dan200.computercraft.core.apis
|
||||
|
||||
import dan200.computercraft.ComputerCraft
|
||||
import dan200.computercraft.api.lua.ILuaAPI
|
||||
import dan200.computercraft.api.lua.MethodResult
|
||||
import dan200.computercraft.api.peripheral.IPeripheral
|
||||
import dan200.computercraft.api.peripheral.IWorkMonitor
|
||||
import dan200.computercraft.core.computer.BasicEnvironment
|
||||
import dan200.computercraft.core.computer.ComputerSide
|
||||
import dan200.computercraft.core.computer.IComputerEnvironment
|
||||
import dan200.computercraft.core.filesystem.FileSystem
|
||||
import dan200.computercraft.core.terminal.Terminal
|
||||
import dan200.computercraft.core.tracking.TrackingField
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.seconds
|
||||
|
||||
|
||||
abstract class NullApiEnvironment : IAPIEnvironment {
|
||||
private val computerEnv = BasicEnvironment()
|
||||
|
||||
override fun getComputerID(): Int = 0
|
||||
override fun getComputerEnvironment(): IComputerEnvironment = computerEnv
|
||||
override fun getMainThreadMonitor(): IWorkMonitor = throw IllegalStateException("Work monitor not available")
|
||||
override fun getTerminal(): Terminal = throw IllegalStateException("Terminal not available")
|
||||
override fun getFileSystem(): FileSystem = throw IllegalStateException("Terminal not available")
|
||||
override fun shutdown() {}
|
||||
override fun reboot() {}
|
||||
override fun setOutput(side: ComputerSide?, output: Int) {}
|
||||
override fun getOutput(side: ComputerSide?): Int = 0
|
||||
override fun getInput(side: ComputerSide?): Int = 0
|
||||
override fun setBundledOutput(side: ComputerSide?, output: Int) {}
|
||||
override fun getBundledOutput(side: ComputerSide?): Int = 0
|
||||
override fun getBundledInput(side: ComputerSide?): Int = 0
|
||||
override fun setPeripheralChangeListener(listener: IAPIEnvironment.IPeripheralChangeListener?) {}
|
||||
override fun getPeripheral(side: ComputerSide?): IPeripheral? = null
|
||||
override fun getLabel(): String? = null
|
||||
override fun setLabel(label: String?) {}
|
||||
override fun startTimer(ticks: Long): Int = 0
|
||||
override fun cancelTimer(id: Int) {}
|
||||
override fun addTrackingChange(field: TrackingField, change: Long) {}
|
||||
}
|
||||
|
||||
class EventResult(val name: String, val args: Array<Any?>)
|
||||
|
||||
class AsyncRunner : NullApiEnvironment() {
|
||||
private val eventStream: Channel<Array<Any?>> = Channel(Int.MAX_VALUE)
|
||||
private val apis: MutableList<ILuaAPI> = mutableListOf()
|
||||
|
||||
override fun queueEvent(event: String?, vararg args: Any?) {
|
||||
ComputerCraft.log.debug("Queue event $event ${args.contentToString()}")
|
||||
if (!eventStream.offer(arrayOf(event, *args))) {
|
||||
throw IllegalStateException("Queue is full")
|
||||
}
|
||||
}
|
||||
|
||||
override fun shutdown() {
|
||||
super.shutdown()
|
||||
eventStream.close()
|
||||
apis.forEach { it.shutdown() }
|
||||
}
|
||||
|
||||
fun <T : ILuaAPI> addApi(api: T): T {
|
||||
apis.add(api)
|
||||
api.startup()
|
||||
return api
|
||||
}
|
||||
|
||||
suspend fun resultOf(toRun: MethodResult): Array<Any?> {
|
||||
var running = toRun
|
||||
while (running.callback != null) running = runOnce(running)
|
||||
return running.result ?: empty
|
||||
}
|
||||
|
||||
private suspend fun runOnce(obj: MethodResult): MethodResult {
|
||||
val callback = obj.callback ?: throw NullPointerException("Callback cannot be null")
|
||||
|
||||
val result = obj.result
|
||||
val filter: String? = if (result.isNullOrEmpty() || result[0] !is String) {
|
||||
null
|
||||
} else {
|
||||
result[0] as String
|
||||
}
|
||||
|
||||
return callback.resume(pullEventImpl(filter))
|
||||
}
|
||||
|
||||
private suspend fun pullEventImpl(filter: String?): Array<Any?> {
|
||||
for (event in eventStream) {
|
||||
ComputerCraft.log.debug("Pulled event ${event.contentToString()}")
|
||||
val eventName = event[0] as String
|
||||
if (filter == null || eventName == filter || eventName == "terminate") return event
|
||||
}
|
||||
|
||||
throw IllegalStateException("No more events")
|
||||
}
|
||||
|
||||
suspend fun pullEvent(filter: String? = null): EventResult {
|
||||
val result = pullEventImpl(filter)
|
||||
return EventResult(result[0] as String, result.copyOfRange(1, result.size))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val empty: Array<Any?> = arrayOf()
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun runTest(timeout: Duration = 5.seconds, fn: suspend AsyncRunner.() -> Unit) {
|
||||
runBlocking {
|
||||
val runner = AsyncRunner()
|
||||
try {
|
||||
withTimeout(timeout) { fn(runner) }
|
||||
} finally {
|
||||
runner.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package dan200.computercraft.core.apis.http.options
|
||||
|
||||
import dan200.computercraft.ComputerCraft
|
||||
import dan200.computercraft.core.apis.AsyncRunner
|
||||
import dan200.computercraft.core.apis.HTTPAPI
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
|
||||
@Disabled("Requires some setup locally.")
|
||||
class TestHttpApi {
|
||||
companion object {
|
||||
private const val WS_ADDRESS = "ws://127.0.0.1:8080"
|
||||
|
||||
@JvmStatic
|
||||
@BeforeAll
|
||||
fun before() {
|
||||
ComputerCraft.httpRules = listOf(AddressRule.parse("*", null, Action.ALLOW.toPartial()))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@AfterAll
|
||||
fun after() {
|
||||
ComputerCraft.httpRules = Collections.unmodifiableList(
|
||||
listOf(
|
||||
AddressRule.parse("\$private", null, Action.DENY.toPartial()),
|
||||
AddressRule.parse("*", null, Action.ALLOW.toPartial())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Connects to websocket`() {
|
||||
AsyncRunner.runTest {
|
||||
val httpApi = addApi(HTTPAPI(this))
|
||||
|
||||
val result = httpApi.websocket(WS_ADDRESS, Optional.empty())
|
||||
assertArrayEquals(arrayOf(true), result, "Should have created websocket")
|
||||
|
||||
val event = pullEvent()
|
||||
assertEquals("websocket_success", event.name) {
|
||||
"Websocket failed to connect: ${event.args.contentToString()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user