kotest: withClue() fails with EmptyStackException if a coroutine switches threads
Version: kotest-assertions-core:4.6.2
As described in the coroutine docs on thread-local data, a coroutine may switch threads at suspension points.
withClue() relies on thread-local data via ThreadLocalErrorCollector, without taking coroutine thread-switching into account. It fails if its block contains a coroutine resuming on a different thread.
Example
import io.kotest.assertions.withClue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Test
class TestCase {
@Test
fun `coroutine changing threads`() = runBlocking(Dispatchers.Unconfined) {
withClue("Hello") {
Thread.currentThread().run { println("withClue() block begins on $name, id $id") }
delay(10) // First suspension makes the Unconfined dispatcher resume on a different thread
Thread.currentThread().run { println("withClue() block ends on $name, id $id") }
}
}
}
Build script
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30"
application
}
group = "me.oliver"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1")
testImplementation(kotlin("test"))
testImplementation("io.kotest:kotest-assertions-core:4.6.2")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
}
application {
mainClass.set("MainKt")
}
Result
> Task :test FAILED
withClue() block begins on Test worker @coroutine#1, id 12
withClue() block ends on kotlinx.coroutines.DefaultExecutor @coroutine#1, id 15
java.util.EmptyStackException
at java.base/java.util.Stack.peek(Stack.java:102)
at java.base/java.util.Stack.pop(Stack.java:84)
at io.kotest.assertions.ThreadLocalErrorCollector.popClue(ErrorCollector.kt:32)
at TestCase$coroutine changing threads$1.invokeSuspend(TestCase.kt:23)
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 32 (32 by maintainers)
Commits related to this issue
- add test for #2447 — committed to kotest/kotest by jschneidereit 3 years ago
- add test for #2447 — committed to OliverO2/kotest by jschneidereit 3 years ago
- Fix withClue and assertSoftly for coroutines switching threads (#2447) — committed to OliverO2/kotest by OliverO2 3 years ago
- Fix withClue and assertSoftly for coroutines switching threads (#2447) (#2500) * add test for #2447 * Fix withClue and assertSoftly for coroutines switching threads (#2447) * BasicErrorCollecto... — committed to kotest/kotest by OliverO2 3 years ago
Sorry, I had some things come up. I can provide code review though.
Yes we’ve mullled over this jim and also doing the same for soft assertions mode