[Wasm] Wasi kotlin.test implementation
This commit is contained in:
committed by
Zalim Bashorov
parent
8cc0660693
commit
18a9c1916e
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.test
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.js.*
|
||||
|
||||
// Using 'globalThis.arguments' because 'arguments' can refer to current JS function arguments
|
||||
@JsFun("() => globalThis.arguments?.join?.(' ') ?? ''")
|
||||
private external fun d8Arguments(): String
|
||||
@JsFun("() => (typeof process != 'undefined' && typeof process.argv != 'undefined') ? process.argv.slice(2).join(' ') : ''")
|
||||
private external fun nodeArguments(): String
|
||||
|
||||
internal fun getArguments(): List<String> = (d8Arguments().ifEmpty { nodeArguments() }).split(' ')
|
||||
|
||||
internal class TeamcityAdapterWithPromiseSupport : TeamcityAdapter() {
|
||||
private var scheduleNextTaskAfter: Promise<JsAny?>? = null
|
||||
override fun runOrScheduleNext(block: () -> Unit) {
|
||||
if (scheduleNextTaskAfter == null) {
|
||||
block()
|
||||
} else {
|
||||
scheduleNextTaskAfter = scheduleNextTaskAfter!!.finally(block)
|
||||
}
|
||||
}
|
||||
|
||||
override fun runOrScheduleNextWithResult(block: () -> Any?) {
|
||||
val nextTask = scheduleNextTaskAfter
|
||||
if (nextTask == null) {
|
||||
val result = block()
|
||||
if (result is Promise<JsAny?>)
|
||||
scheduleNextTaskAfter = result
|
||||
} else {
|
||||
scheduleNextTaskAfter = nextTask.then {
|
||||
block() as? Promise<JsAny?>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun tryProcessResult(result: Any?, name: String): Any? {
|
||||
if (result == null) return null
|
||||
if (result !is Promise<*>) return null
|
||||
|
||||
return result.then(
|
||||
onFulfilled = { value ->
|
||||
MessageType.Finished.report(name)
|
||||
value
|
||||
},
|
||||
onRejected = { e ->
|
||||
val throwable = e.toThrowableOrNull()
|
||||
if (throwable != null) {
|
||||
MessageType.Failed.report(name, throwable)
|
||||
} else {
|
||||
MessageType.Failed.report(name, e.toString())
|
||||
}
|
||||
MessageType.Finished.report(name)
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.test
|
||||
|
||||
/**
|
||||
* Overrides current framework adapter with a provided instance of [FrameworkAdapter]. Use in order to support custom test frameworks.
|
||||
*
|
||||
* If this function is not called, the test framework will be detected automatically.
|
||||
*
|
||||
*/
|
||||
internal fun setAdapter(adapter: FrameworkAdapter) {
|
||||
currentAdapter = adapter
|
||||
}
|
||||
|
||||
internal var currentAdapter: FrameworkAdapter? = null
|
||||
|
||||
private fun isJasmine(): Boolean =
|
||||
js("typeof describe === 'function' && typeof it === 'function'")
|
||||
|
||||
internal fun adapter(): FrameworkAdapter {
|
||||
val result = currentAdapter ?: if (isJasmine()) JasmineLikeAdapter() else TeamcityAdapterWithPromiseSupport()
|
||||
currentAdapter = result
|
||||
return result
|
||||
}
|
||||
@@ -6,38 +6,13 @@
|
||||
package kotlin.test
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.js.*
|
||||
|
||||
// Using 'globalThis.arguments' because 'arguments' can refer to current JS function arguments
|
||||
@JsFun("() => globalThis.arguments?.join?.(' ') ?? ''")
|
||||
private external fun d8Arguments(): String
|
||||
@JsFun("() => (typeof process != 'undefined' && typeof process.argv != 'undefined') ? process.argv.slice(2).join(' ') : ''")
|
||||
private external fun nodeArguments(): String
|
||||
internal open class TeamcityAdapter : FrameworkAdapter {
|
||||
protected open fun runOrScheduleNext(block: () -> Unit) = block()
|
||||
protected open fun runOrScheduleNextWithResult(block: () -> Any?) = block()
|
||||
protected open fun tryProcessResult(result: Any?, name: String): Any? = null
|
||||
|
||||
internal class TeamcityAdapter : FrameworkAdapter {
|
||||
|
||||
private var scheduleNextTaskAfter: Promise<JsAny?>? = null
|
||||
private fun runOrScheduleNext(block: () -> Unit) {
|
||||
if (scheduleNextTaskAfter == null) {
|
||||
block()
|
||||
} else {
|
||||
scheduleNextTaskAfter = scheduleNextTaskAfter!!.finally(block)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runOrScheduleNextWithResult(block: () -> Promise<JsAny?>?) {
|
||||
if (scheduleNextTaskAfter == null) {
|
||||
val result = block()
|
||||
if (result != null)
|
||||
scheduleNextTaskAfter = result
|
||||
} else {
|
||||
scheduleNextTaskAfter = scheduleNextTaskAfter!!.then {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class MessageType(val type: String) {
|
||||
internal enum class MessageType(val type: String) {
|
||||
Started("testStarted"),
|
||||
Finished("testFinished"),
|
||||
Failed("testFailed"),
|
||||
@@ -61,11 +36,11 @@ internal class TeamcityAdapter : FrameworkAdapter {
|
||||
|
||||
private val flowId: String = "flowId='wasmTcAdapter${abs(hashCode())}'"
|
||||
|
||||
private fun MessageType.report(name: String) {
|
||||
internal fun MessageType.report(name: String) {
|
||||
println("##teamcity[$type name='${name.tcEscape()}' $flowId]")
|
||||
}
|
||||
|
||||
private fun MessageType.report(name: String, e: Throwable) {
|
||||
internal fun MessageType.report(name: String, e: Throwable) {
|
||||
if (this == MessageType.Failed) {
|
||||
println("##teamcity[$type name='${name.tcEscape()}' message='${e.message.tcEscape()}' details='${e.stackTraceToString().tcEscape()}' $flowId]")
|
||||
} else {
|
||||
@@ -73,7 +48,7 @@ internal class TeamcityAdapter : FrameworkAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageType.report(name: String, errorMessage: String) {
|
||||
internal fun MessageType.report(name: String, errorMessage: String) {
|
||||
println("##teamcity[$type name='${name.tcEscape()}' message='${errorMessage.tcEscape()}' $flowId]")
|
||||
}
|
||||
|
||||
@@ -82,8 +57,7 @@ internal class TeamcityAdapter : FrameworkAdapter {
|
||||
get() {
|
||||
var value = _testArguments
|
||||
if (value == null) {
|
||||
val arguments = d8Arguments().ifEmpty { nodeArguments() }
|
||||
value = FrameworkTestArguments.parse(arguments.split(' '))
|
||||
value = FrameworkTestArguments.parse(getArguments())
|
||||
_testArguments = value
|
||||
}
|
||||
|
||||
@@ -212,24 +186,10 @@ internal class TeamcityAdapter : FrameworkAdapter {
|
||||
} catch (e: Throwable) {
|
||||
MessageType.Failed.report(name, e)
|
||||
}
|
||||
if (result is Promise<*>) {
|
||||
return@runOrScheduleNextWithResult result.then(
|
||||
onFulfilled = { value ->
|
||||
MessageType.Finished.report(name)
|
||||
value
|
||||
},
|
||||
onRejected = { e ->
|
||||
val throwable = e.toThrowableOrNull()
|
||||
if (throwable != null) {
|
||||
MessageType.Failed.report(name, throwable)
|
||||
} else {
|
||||
MessageType.Failed.report(name, e.toString())
|
||||
}
|
||||
MessageType.Finished.report(name)
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val processed = tryProcessResult(result, name)
|
||||
if (processed != null) return@runOrScheduleNextWithResult processed
|
||||
|
||||
MessageType.Finished.report(name)
|
||||
return@runOrScheduleNextWithResult null
|
||||
}
|
||||
|
||||
@@ -5,17 +5,7 @@
|
||||
|
||||
package kotlin.test
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
/**
|
||||
* Overrides current framework adapter with a provided instance of [FrameworkAdapter]. Use in order to support custom test frameworks.
|
||||
*
|
||||
* If this function is not called, the test framework will be detected automatically.
|
||||
*
|
||||
*/
|
||||
internal fun setAdapter(adapter: FrameworkAdapter) {
|
||||
currentAdapter = adapter
|
||||
}
|
||||
import kotlin.wasm.WasmExport
|
||||
|
||||
/**
|
||||
* The functions below are used by the compiler to describe the tests structure, e.g.
|
||||
@@ -37,20 +27,8 @@ internal fun test(name: String, ignored: Boolean, testFn: () -> Any?) {
|
||||
adapter().test(name, ignored, testFn)
|
||||
}
|
||||
|
||||
internal var currentAdapter: FrameworkAdapter? = null
|
||||
|
||||
private fun isJasmine(): Boolean =
|
||||
js("typeof describe === 'function' && typeof it === 'function'")
|
||||
|
||||
internal fun adapter(): FrameworkAdapter {
|
||||
val result = currentAdapter ?: if (isJasmine()) JasmineLikeAdapter() else TeamcityAdapter()
|
||||
currentAdapter = result
|
||||
return result
|
||||
}
|
||||
|
||||
// This is called from the js-launcher alongside wasm start function
|
||||
@JsExport
|
||||
@OptIn(kotlin.js.ExperimentalJsExport::class)
|
||||
@WasmExport
|
||||
internal fun startUnitTests() {
|
||||
// This will be filled with the corresponding code during lowering
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.test
|
||||
|
||||
import kotlin.wasm.WasmImport
|
||||
import kotlin.wasm.unsafe.*
|
||||
|
||||
/**
|
||||
* Read command-line argument data. The size of the array should match that returned by
|
||||
* `args_sizes_get`. Each argument is expected to be `\0` terminated.
|
||||
*/
|
||||
@WasmImport("wasi_snapshot_preview1", "args_get")
|
||||
private external fun wasiRawArgsGet(
|
||||
argvPtr: Int,
|
||||
argvBuf: Int,
|
||||
): Int
|
||||
|
||||
|
||||
/** Return command-line argument data sizes. */
|
||||
@WasmImport("wasi_snapshot_preview1", "args_sizes_get")
|
||||
private external fun wasiRawArgsSizesGet(
|
||||
argumentNumberPtr: Int,
|
||||
argumentStringSizePtr: Int,
|
||||
): Int
|
||||
|
||||
@OptIn(UnsafeWasmMemoryApi::class)
|
||||
internal fun getArguments(): List<String> = withScopedMemoryAllocator { allocator ->
|
||||
val numberOfArgumentsPtr = allocator.allocate(4)
|
||||
val sizeOfArgumentStringPtr = allocator.allocate(4)
|
||||
val argNumRes = wasiRawArgsSizesGet(
|
||||
argumentNumberPtr = numberOfArgumentsPtr.address.toInt(),
|
||||
argumentStringSizePtr = sizeOfArgumentStringPtr.address.toInt()
|
||||
)
|
||||
if (argNumRes != 0) {
|
||||
throw IllegalStateException("Wasi error code $argNumRes")
|
||||
}
|
||||
|
||||
val argumentNumber = numberOfArgumentsPtr.loadInt()
|
||||
if (argumentNumber <= 2) return emptyList()
|
||||
|
||||
val argumentStringSize = sizeOfArgumentStringPtr.loadInt()
|
||||
val stringBufferPtr = allocator.allocate(argumentStringSize)
|
||||
|
||||
val argvPtr = allocator.allocate(argumentNumber * Int.SIZE_BYTES)
|
||||
|
||||
val argRes = wasiRawArgsGet(argvPtr.address.toInt(), stringBufferPtr.address.toInt())
|
||||
if (argRes != 0) {
|
||||
throw IllegalStateException("Wasi error code $argNumRes")
|
||||
}
|
||||
|
||||
val startAddress = (argvPtr + 2 * Int.SIZE_BYTES).loadInt().toUInt()
|
||||
val endAddress = stringBufferPtr.address + argumentStringSize.toUInt()
|
||||
decodeStrings(argumentStringSize, startAddress, endAddress)
|
||||
}
|
||||
|
||||
@OptIn(UnsafeWasmMemoryApi::class)
|
||||
private fun decodeStrings(arraySize: Int, startAddress: UInt, endAddress: UInt): List<String> {
|
||||
val buffer = ByteArray(arraySize)
|
||||
val result = mutableListOf<String>()
|
||||
|
||||
var currentPtr = Pointer(startAddress)
|
||||
var currentIndex = 0
|
||||
while (currentPtr.address < endAddress) {
|
||||
val byte = currentPtr.loadByte()
|
||||
if (byte.toInt() == 0) {
|
||||
result.add(buffer.decodeToString(0, currentIndex))
|
||||
currentIndex = 0
|
||||
} else {
|
||||
buffer[currentIndex] = byte
|
||||
currentIndex++
|
||||
}
|
||||
currentPtr += 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.test
|
||||
|
||||
private val currentAdapter: FrameworkAdapter by lazy { TeamcityAdapter() }
|
||||
|
||||
internal fun adapter(): FrameworkAdapter = currentAdapter
|
||||
Reference in New Issue
Block a user