We’re just migrating to Kotest 5.x, and I found so...
# kotest
w
We’re just migrating to Kotest 5.x, and I found some behavior that I can’t figure out if it’s a bug or not. I managed to trim it down to the following repro, and as far as I can see it’s triggered by two tests having the same name (as seen in the test). When I change one of the duplicated tests names, the test pass. Code and error details in 🧵
The test fails with
Copy code
Apr 05, 2022 10:24:55 PM org.junit.platform.launcher.core.CompositeTestExecutionListener lambda$notifyEach$19
WARNING: TestExecutionListener [org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener] threw exception for method: executionFinished(TestIdentifier [uniqueId = [engine:kotest]/[spec:com.example.Repro]/[test:test]/[test:foo]/[test:THIS TEST NAME IS DUPLICATED], parentId = [engine:kotest]/[spec:com.example.Repro]/[test:test]/[test:foo], displayName = 'THIS TEST NAME IS DUPLICATED', legacyReportingName = 'THIS TEST NAME IS DUPLICATED', source = ClassSource [className = 'com.example.Repro', filePosition = null], tags = [], type = CONTAINER], TestExecutionResult [status = SUCCESSFUL, throwable = null])
java.lang.AssertionError
	at org.gradle.api.internal.tasks.testing.processors.TestOutputRedirector.setOutputOwner(TestOutputRedirector.java:49)
	at org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor.completed(CaptureTestOutputTestResultProcessor.java:80)
	at org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor.completed(AttachParentTestResultProcessor.java:56)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.actor.internal.DefaultActorFactory$BlockingActor.dispatch(DefaultActorFactory.java:128)
	at org.gradle.internal.actor.internal.DefaultActorFactory$BlockingActor.dispatch(DefaultActorFactory.java:100)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at jdk.proxy2/jdk.proxy2.$Proxy6.completed(Unknown Source)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener.executionFinished(JUnitPlatformTestExecutionListener.java:113)
	at org.junit.platform.launcher.core.CompositeTestExecutionListener.lambda$executionFinished$10(CompositeTestExecutionListener.java:69)
	at org.junit.platform.launcher.core.CompositeTestExecutionListener.lambda$notifyEach$19(CompositeTestExecutionListener.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at org.junit.platform.launcher.core.CompositeTestExecutionListener.notifyEach(CompositeTestExecutionListener.java:93)
	at org.junit.platform.launcher.core.CompositeTestExecutionListener.executionFinished(CompositeTestExecutionListener.java:69)
	at org.junit.platform.launcher.core.ExecutionListenerAdapter.executionFinished(ExecutionListenerAdapter.java:56)
	at org.junit.platform.launcher.core.DelegatingEngineExecutionListener.executionFinished(DelegatingEngineExecutionListener.java:46)
	at org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener.executionFinished(OutcomeDelayingEngineExecutionListener.java:63)
	at io.kotest.runner.junit.platform.SynchronizedEngineExecutionListener.executionFinished(SynchronizedEngineExecutionListener.kt:12)
	at io.kotest.runner.junit.platform.JUnitTestEngineListener.testFinished(JUnitTestEngineListener.kt:253)
	at io.kotest.engine.listener.PinnedSpecTestEngineListener.testFinished(PinnedSpecTestEngineListener.kt:92)
	at io.kotest.engine.listener.ThreadSafeTestEngineListener.testFinished(ThreadSafeTestEngineListener.kt:43)
	at io.kotest.engine.listener.PinnedSpecTestEngineListener.testFinished(PinnedSpecTestEngineListener.kt:92)
	at io.kotest.engine.listener.ThreadSafeTestEngineListener.testFinished(ThreadSafeTestEngineListener.kt:43)
	at io.kotest.engine.listener.CompositeTestEngineListener.testFinished(CompositeTestEngineListener.kt:35)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner$run$4$testExecutor$1.testFinished(InstancePerLeafSpecRunner.kt:150)
	at io.kotest.engine.test.interceptors.TestFinishedInterceptor.intercept(TestFinishedInterceptor.kt:26)
	at io.kotest.engine.test.TestCaseExecutor$execute$2$1.invokeSuspend(TestCaseExecutor.kt:83)
	at io.kotest.engine.test.TestCaseExecutor$execute$2$1.invoke(TestCaseExecutor.kt)
	at io.kotest.engine.test.TestCaseExecutor$execute$2$1.invoke(TestCaseExecutor.kt)
	at io.kotest.engine.test.TestCaseExecutor.execute(TestCaseExecutor.kt:84)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner$run$4.invokeSuspend(InstancePerLeafSpecRunner.kt:158)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner$run$4.invoke(InstancePerLeafSpecRunner.kt)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner$run$4.invoke(InstancePerLeafSpecRunner.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner.run(InstancePerLeafSpecRunner.kt:102)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner.access$run(InstancePerLeafSpecRunner.kt:27)
	at io.kotest.engine.spec.runners.InstancePerLeafSpecRunner$run$4$context$1.registerTestCase(InstancePerLeafSpecRunner.kt:117)
	at io.kotest.engine.test.scopes.DuplicateNameHandlingTestScope.registerTestCase(DuplicateNameHandlingTestScope.kt:21)
	at io.kotest.engine.test.scopes.TestScopeWithCoroutineContext.registerTestCase(scopes.kt)
	at io.kotest.core.spec.style.scopes.AbstractContainerScope.registerTestCase$suspendImpl(ContainerScope.kt:209)
	at io.kotest.core.spec.style.scopes.AbstractContainerScope.registerTestCase(ContainerScope.kt)
	at io.kotest.core.spec.style.scopes.ContainerScope$DefaultImpls.registerContainer(ContainerScope.kt:58)
	at io.kotest.core.spec.style.scopes.AbstractContainerScope.registerContainer(ContainerScope.kt:205)
	at io.kotest.core.spec.style.scopes.DescribeSpecContainerScope.describe(DescribeSpecContainerScope.kt:62)
with composite exception pointing out to the
FakeService#complete
failing because the
deferred
is
null
. However, given
InstancePerLeaf
isolation mode, it looks to me like each
complete
is properly preceded by a
load
. The failure somewhere in JUnit code as well as the fact it only fails for tests with the same names makes me suspect it’s a bug in Kotest, but I’d love to hear your opinion
Of course, Kotest 4.6.3 doesn’t exhibit this behavior
e
@wasyl you can configure this behaviour
Copy code
object Config : AbstractProjectConfig() {
    override val duplicateTestNameMode = DuplicateTestNameMode.Warn
}
w
I thought that
duplicate test name
refers to duplicated test names on the same level, that is :
Copy code
describe("foo") {
  it("bar") { }
  it("bar") { }
}
in this case,
bar
test would be a duplicated name. But I don’t think it should apply to nested tests which use the same name as some parent. And in any case, I think the default is silent (or warn) which means the test name should be adjusted, and I observe that adjustment for the case with duplicated name on the same level. In this case, the test just stops running as expected — the
InstancePerLeaf
behavior looks broken
I went ahead and opened https://github.com/kotest/kotest/issues/2915 to track this outside of Slack
👍 1