mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-15 03:35:42 +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
|
@Nullable
|
||||||
IPeripheral getPeripheral( ComputerSide side );
|
IPeripheral getPeripheral( ComputerSide side );
|
||||||
|
|
||||||
|
@Nullable
|
||||||
String getLabel();
|
String getLabel();
|
||||||
|
|
||||||
void setLabel( @Nullable String label );
|
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