[Wasm] Add Promise and support tests that return Promise
Co-authored-by: Ilya Gorbunov <Ilya.Gorbunov@jetbrains.com> Merge-request: KT-MR-7717 Merged-by: Svyatoslav Kuzmich <svyatoslav.kuzmich@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
002f6bd34a
commit
7ec6608e29
@@ -7,6 +7,7 @@ package kotlin.test
|
|||||||
|
|
||||||
import kotlin.test.FrameworkAdapter
|
import kotlin.test.FrameworkAdapter
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.js.*
|
||||||
|
|
||||||
@JsFun("() => (typeof arguments !== 'undefined' && typeof arguments.join !== 'undefined') ? arguments.join(' ') : '' ")
|
@JsFun("() => (typeof arguments !== 'undefined' && typeof arguments.join !== 'undefined') ? arguments.join(' ') : '' ")
|
||||||
private external fun d8Arguments(): String
|
private external fun d8Arguments(): String
|
||||||
@@ -14,6 +15,28 @@ private external fun d8Arguments(): String
|
|||||||
private external fun nodeArguments(): String
|
private external fun nodeArguments(): String
|
||||||
|
|
||||||
internal class TeamcityAdapter : FrameworkAdapter {
|
internal class TeamcityAdapter : FrameworkAdapter {
|
||||||
|
|
||||||
|
private var scheduleNextTaskAfter: Promise<Dynamic?>? = null
|
||||||
|
private fun runOrScheduleNext(block: () -> Unit) {
|
||||||
|
if (scheduleNextTaskAfter == null) {
|
||||||
|
block()
|
||||||
|
} else {
|
||||||
|
scheduleNextTaskAfter = scheduleNextTaskAfter!!.finally(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runOrScheduleNextWithResult(block: () -> Promise<Dynamic?>?) {
|
||||||
|
if (scheduleNextTaskAfter == null) {
|
||||||
|
val result = block()
|
||||||
|
if (result != null)
|
||||||
|
scheduleNextTaskAfter = result
|
||||||
|
} else {
|
||||||
|
scheduleNextTaskAfter = scheduleNextTaskAfter!!.then {
|
||||||
|
block() as Dynamic?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum class MessageType(val type: String) {
|
private enum class MessageType(val type: String) {
|
||||||
Started("testStarted"),
|
Started("testStarted"),
|
||||||
Finished("testFinished"),
|
Finished("testFinished"),
|
||||||
@@ -50,25 +73,40 @@ internal class TeamcityAdapter : FrameworkAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MessageType.report(name: String, errorMessage: String) {
|
||||||
|
println("##teamcity[$type name='${name.tcEscape()}' message='${errorMessage.tcEscape()}' $flowId]")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private val testArguments: FrameworkTestArguments by lazy {
|
private val testArguments: FrameworkTestArguments by lazy {
|
||||||
val arguments = d8Arguments().takeIf { it.isNotEmpty() } ?: nodeArguments()
|
val arguments = d8Arguments().takeIf { it.isNotEmpty() } ?: nodeArguments()
|
||||||
FrameworkTestArguments.parse(arguments.split(' '))
|
FrameworkTestArguments.parse(arguments.split(' '))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runSuite(name: String, suiteFn: () -> Unit) {
|
private fun runSuite(name: String, suiteFn: () -> Unit) {
|
||||||
MessageType.SuiteStarted.report(name)
|
runOrScheduleNext {
|
||||||
|
MessageType.SuiteStarted.report(name)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
suiteFn()
|
suiteFn()
|
||||||
MessageType.SuiteFinished.report(name)
|
runOrScheduleNext {
|
||||||
|
MessageType.SuiteFinished.report(name)
|
||||||
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
MessageType.SuiteFinished.report(name, e)
|
runOrScheduleNext {
|
||||||
|
MessageType.SuiteFinished.report(name, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runIgnoredSuite(name: String, suiteFn: () -> Unit) {
|
private fun runIgnoredSuite(name: String, suiteFn: () -> Unit) {
|
||||||
MessageType.SuiteStarted.report(name)
|
runOrScheduleNext {
|
||||||
|
MessageType.SuiteStarted.report(name)
|
||||||
|
}
|
||||||
suiteFn()
|
suiteFn()
|
||||||
MessageType.SuiteFinished.report(name)
|
runOrScheduleNext {
|
||||||
|
MessageType.SuiteFinished.report(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isUnderIgnoredSuit: Boolean = false
|
private var isUnderIgnoredSuit: Boolean = false
|
||||||
@@ -117,45 +155,77 @@ internal class TeamcityAdapter : FrameworkAdapter {
|
|||||||
runSuite(name, suiteFn)
|
runSuite(name, suiteFn)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when (testArguments.ignoredTestSuites) {
|
runOrScheduleNext {
|
||||||
IgnoredTestSuitesReporting.reportAsIgnoredTest -> {
|
when (testArguments.ignoredTestSuites) {
|
||||||
MessageType.Ignored.report(name)
|
IgnoredTestSuitesReporting.reportAsIgnoredTest -> {
|
||||||
}
|
MessageType.Ignored.report(name)
|
||||||
IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored -> {
|
|
||||||
var oldIsUnderIgnoredSuit = isUnderIgnoredSuit
|
|
||||||
isUnderIgnoredSuit = true
|
|
||||||
try {
|
|
||||||
runIgnoredSuite(name, suiteFn)
|
|
||||||
} finally {
|
|
||||||
isUnderIgnoredSuit = oldIsUnderIgnoredSuit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored -> {
|
||||||
|
var oldIsUnderIgnoredSuit = isUnderIgnoredSuit
|
||||||
|
isUnderIgnoredSuit = true
|
||||||
|
try {
|
||||||
|
runIgnoredSuite(name, suiteFn)
|
||||||
|
} finally {
|
||||||
|
isUnderIgnoredSuit = oldIsUnderIgnoredSuit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IgnoredTestSuitesReporting.skip -> {}
|
||||||
}
|
}
|
||||||
IgnoredTestSuitesReporting.skip -> { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun test(name: String, ignored: Boolean, testFn: () -> Any?) {
|
override fun test(name: String, ignored: Boolean, testFn: () -> Any?) {
|
||||||
if (isUnderIgnoredSuit) {
|
if (isUnderIgnoredSuit) {
|
||||||
MessageType.Ignored.report(name)
|
runOrScheduleNext {
|
||||||
|
MessageType.Ignored.report(name)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ignored) {
|
if (ignored) {
|
||||||
MessageType.Ignored.report(name)
|
runOrScheduleNext {
|
||||||
|
MessageType.Ignored.report(name)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
enterIfIncluded(name, false) {
|
enterIfIncluded(name, false) {
|
||||||
try {
|
runOrScheduleNextWithResult {
|
||||||
MessageType.Started.report(name)
|
MessageType.Started.report(name)
|
||||||
if (!testArguments.dryRun) {
|
|
||||||
testFn()
|
val result = try {
|
||||||
|
if (!testArguments.dryRun) {
|
||||||
|
testFn()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
MessageType.Failed.report(name, e)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
if (result is Promise<*>) {
|
||||||
MessageType.Failed.report(name, e)
|
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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MessageType.Finished.report(name)
|
||||||
|
return@runOrScheduleNextWithResult null
|
||||||
}
|
}
|
||||||
MessageType.Finished.report(name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,4 +201,36 @@ fun <T> Nothing?.unsafeCast(): Dynamic? = null
|
|||||||
/**
|
/**
|
||||||
* Reinterprets Dynamic type value as a value of the specified type [T] without any actual type checking.
|
* Reinterprets Dynamic type value as a value of the specified type [T] without any actual type checking.
|
||||||
*/
|
*/
|
||||||
fun <T> Dynamic.unsafeCast(): T = this as T
|
fun <T> Dynamic.unsafeCast(): T = this as T
|
||||||
|
|
||||||
|
@JsFun("e => { throw e; }")
|
||||||
|
private external fun jsThrow(e: Dynamic)
|
||||||
|
|
||||||
|
@JsFun("""(f) => {
|
||||||
|
let result = null;
|
||||||
|
try {
|
||||||
|
f();
|
||||||
|
} catch (e) {
|
||||||
|
result = e;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}""")
|
||||||
|
private external fun jsCatch(f: () -> Unit): Dynamic?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a Dynamic value caught in JS, returns the corresponding [Throwable]
|
||||||
|
* if it was thrown from Kotlin, or null otherwise.
|
||||||
|
*/
|
||||||
|
public fun Dynamic.toThrowableOrNull(): Throwable? {
|
||||||
|
val thisAny: Any = this
|
||||||
|
if (thisAny is Throwable) return thisAny
|
||||||
|
var result: Throwable? = null
|
||||||
|
jsCatch {
|
||||||
|
try {
|
||||||
|
jsThrow(this)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
result = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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.js
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes the JavaScript [Promise object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) to Kotlin.
|
||||||
|
*/
|
||||||
|
public external class Promise<out T>(executor: (resolve: (Dynamic?) -> Unit, reject: (Dynamic) -> Unit) -> Unit) {
|
||||||
|
public open fun then(onFulfilled: (Dynamic?) -> Dynamic?): Promise<Dynamic?>
|
||||||
|
public open fun then(onFulfilled: (Dynamic?) -> Dynamic?, onRejected: (Dynamic) -> Dynamic?): Promise<Dynamic?>
|
||||||
|
public open fun catch(onRejected: (Dynamic) -> Dynamic?): Promise<Dynamic?>
|
||||||
|
public open fun finally(onFinally: () -> Unit): Promise<Dynamic?>
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public fun reject(e: Dynamic): Promise<Dynamic?>
|
||||||
|
public fun resolve(e: Dynamic): Promise<Dynamic?>
|
||||||
|
public fun resolve(e: Promise<Dynamic?>): Promise<Dynamic?>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
package kotlin.js
|
package kotlin.js
|
||||||
|
|
||||||
external class Promise<T>
|
|
||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal val undefined: Nothing? = null
|
internal val undefined: Nothing? = null
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package test.js
|
||||||
|
|
||||||
|
import kotlin.js.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
@JsFun("async () => 'foo'")
|
||||||
|
internal external fun jsAsyncFoo(): Promise<Dynamic?>
|
||||||
|
|
||||||
|
@JsFun("() => 'foo'")
|
||||||
|
internal external fun jsFoo(): Dynamic
|
||||||
|
|
||||||
|
var state: Dynamic? = null
|
||||||
|
|
||||||
|
class MyThrowable : Throwable()
|
||||||
|
|
||||||
|
class AsyncTest {
|
||||||
|
@Test
|
||||||
|
fun test1(): Promise<Dynamic?> {
|
||||||
|
var thenExecuted = false
|
||||||
|
val p = jsAsyncFoo().then { value ->
|
||||||
|
state = value
|
||||||
|
thenExecuted = true
|
||||||
|
null
|
||||||
|
}
|
||||||
|
assertFalse(thenExecuted)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test2(): Promise<Dynamic?> {
|
||||||
|
assertEquals(state, jsFoo())
|
||||||
|
var thenExecuted = false
|
||||||
|
val p = jsAsyncFoo().then {
|
||||||
|
assertEquals(state, jsFoo())
|
||||||
|
thenExecuted = true
|
||||||
|
null
|
||||||
|
}.then {
|
||||||
|
assertEquals(state, jsFoo())
|
||||||
|
thenExecuted = true
|
||||||
|
null
|
||||||
|
}
|
||||||
|
assertFalse(thenExecuted)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testJsValueToThrowableOrNull1(): Promise<Dynamic?> {
|
||||||
|
val p = jsAsyncFoo().then {
|
||||||
|
throw MyThrowable()
|
||||||
|
null
|
||||||
|
}.catch { e ->
|
||||||
|
assert(e.toThrowableOrNull() is MyThrowable)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testJsValueToThrowableOrNull2() {
|
||||||
|
val e = MyThrowable()
|
||||||
|
assertEquals((e as Dynamic).toThrowableOrNull(), e)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user