mbonnin
11/29/2024, 2:42 PM@Test
fun test() = runTest {
CoroutineScope(Dispatchers.Default).launch {
delay(10.seconds)
println("done")
}
}
JVM: completes immediately, without printing "done".
JS: takes 10s and prints done.
Is there a fundamental reason this is like this? Could JS just ignore the launched coroutine? (Or if it's a problem because it has side effects for other tests, shouldn't the bahaviour be the same for JVM?)Oliver.O
11/30/2024, 12:20 AMGlobalScope
. So there is no coroutine parent waiting for it. On the JVM, it most probably will land in a second thread which is terminated silently when your (only) test completes and the JVM exits. On JS, it basically works the same coroutine-wise, but all on a single thread. I'd guess that the JS engine's scheduler is waiting for all async jobs to complete (up to some timeout).
I'm working on an experimental test framework which is treats coroutines as first-class citizens. I've tried your example on that one and got the same results you had.mbonnin
11/30/2024, 7:57 AMmbonnin
11/30/2024, 7:58 AMOliver.O
11/30/2024, 5:26 PMMaybe the test framework launches a new js engine every timeYes, a Gradle
jsNodeTest
or jsBrowserTest
invocation makes KGP fire up a JS engine running Mocha (on the browser via Karma), which runs the tests.
and the js engine awaits all promises before shutting down?That's what I suspect: The JS engine just enqueues your coroutine in GlobalScope on its event loop. It will terminate only after all "tasks" (JS lingo) on the event loop have completed. I agree that it is not consistent across platforms, but does it have to be? What's your intention in writing the above code in contrast to, say, something like this?
@Test
fun test() = runTest {
withContext(Dispatchers.Default) {
launch {
delay(10.seconds)
println("done")
}
}
}
mbonnin
11/30/2024, 5:34 PMmakes KGP fire up a JS engine running MochaBut is it fired for each and every test? (after thinking a bit...) I thought it would wait for each test but maybe not actually, it just wait at the very end, I’ll double check that.
What’s your intention in writing the above code in contrast to, say, something like this?I’m testing a network client that launches coroutines for background work. Those coroutines usually timeout and get collected. But if each test now has to wait for them, it makes the test ultra long (but maybe it’s not each and every test, I’ll double check that)
Oliver.O
11/30/2024, 5:37 PMOliver.O
11/30/2024, 5:39 PMmbonnin
11/30/2024, 5:40 PMmbonnin
11/30/2024, 5:44 PMI’ll bet it just waits at the end.Yup, that’s it. I guess there’s no such thing as a “daemon” Promise
mbonnin
11/30/2024, 5:45 PMOliver.O
11/30/2024, 5:45 PMI guess there’s no such thing as a “daemon” PromiseProbably not. The JS folks would expect you to use Web Workers for that.
Oliver.O
11/30/2024, 5:57 PMval BackgroundJobTests by suite {
// Register a lambda wrapping around all tests within this suite
aroundAll { testActions ->
withContext(Dispatchers.Default) {
val backgroundJob = launch {
delay(10.seconds)
println("done")
}
testActions() // execute tests here
backgroundJob.cancel()
}
}
test("test1") {
delay(1.seconds)
}
test("test2") {
delay(2.seconds)
}
}
mbonnin
11/30/2024, 6:00 PMbackgroundJob
in test1
, etc...mbonnin
11/30/2024, 6:01 PMresource.use {}
in tests so it’s not a big deal either. I can do my hoework and cleanup after each testmbonnin
11/30/2024, 6:02 PMOliver.O
11/30/2024, 6:30 PMbackgroundJob
in test1
, etc...
Then it would probably look more like this (will finish immediately, unless you uncomment the line with await
):
val BackgroundJobTests by suite {
val myThing = fixture {
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.async {
delay(10.seconds)
"done"
}
} closeWith {
cancel()
}
test("test1") {
delay(1.seconds)
// println(myThing().await())
}
test("test2") {
delay(2.seconds)
}
}
(Edited: Specifying Dispatchers.Default
would be unnecessary, as coroutines would run there by default, even in tests. Using the test dispatcher is optional.)CLOVIS
12/03/2024, 10:23 AMI’m testing a network client that launches coroutines for background work.Do note that Kotlinx-coroutines-test has
backgroundScope
specifically for thatOliver.O
12/03/2024, 10:37 AMtestScope.backgroundScope
. Useful when background jobs have a single-test lifetime. When background jobs are shared across tests, you'd use the above approach.