spock icon indicating copy to clipboard operation
spock copied to clipboard

`@Retry` with mode `SETUP_FEATURE_CLEANUP` is not fully parallel-safe (swallowing failures or throwing `NullPointerException`)

Open Vampire opened this issue 2 years ago • 0 comments

Describe the bug

The RetryExtension with mode SETUP_FEATURE_CLEANUP uses an outer and an inner interceptor. The outer is used as iteration interceptor the inner as feature method interceptor. The outer in its before-action sets a ThreadLocal to a collection where exceptions are collected. The inner adds exceptions that happened to this collection.

The problem is, if you have a work-around for junit-team/junit5#3108 in place, or it will be done like that in JUnit Platform later on, the problem is, that the feature method can be run on a different thread than the iteration interceptor.

This causes one of two problems.

Either the thread-local collection for the thread the iteration is running on was initialized by some interceptor for another test, then failures happening are just swallowed and never reported and the test looks like it is successful while it actually was failing.

Or the thread-local collection for the thread the iteration is running on was not yet initialized and you get this NullPointerException:

java.lang.NullPointerException
	at org.spockframework.runtime.extension.builtin.RetryIterationInterceptor$IterationState.failIteration(RetryIterationInterceptor.java:89)
	at org.spockframework.runtime.extension.builtin.RetryIterationInterceptor$InnerRetryInterceptor.intercept(RetryIterationInterceptor.java:68)
	at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:101)
	at net.kautler.command.integ.test.spock.CDIExtension.visitSpec_closure1$_closure2(CDIExtension.groovy:44)
	at groovy.lang.Closure.call(Closure.java:412)
	at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:101)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156)
	at org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:102)
	at groovy.lang.Closure.call(Closure.java:412)
	at groovy.lang.Closure.call(Closure.java:406)
	at groovy.lang.Closure.run(Closure.java:493)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

To Reproduce

Add an iteration interceptor that is run after the retry interceptor with the following implementation:

new Thread({ invocation.proceed() }).tap {
    it.start()
    it.join()
}

and use Retry with mode SETUP_FEATURE_CLEANUP.

Expected behavior

Retrying working as usual

Actual behavior

NullPointerException thrown

Java version

8

Buildtool version

Gradle 8.1.1

What operating system are you using

Windows

Dependencies

Spock 2.3-groovy-3.0

Additional context

No response

Vampire avatar Jun 19 '23 12:06 Vampire