Search code examples
testinggroovyspock

Sleep in Spock's Mock waits when run by CompletableFuture


When I separately run the runAsyncWithMock test, it waits for 3 seconds until the mock's execution is finalised, rather than get terminated like the other 2 tests.

I was not able to figure out why.

It is interesting that:

  1. When multiple Runnables are executed by CompletableFuture.runAsync in a row in the runAsyncWithMock test, only the first one waits, the others not.
  2. When having multiple duplicated runAsyncWithMock tests, each and every of them runs for 3s when the whole specification is executed.
  3. When using Class instance rather than a Mock, the test is finalised immediately.

Any idea what I got wrong?

My configuration:

  • macOS Mojave 10.14.6
  • Spock 1.3-groovy-2.4
  • Groovy 2.4.15
  • JDK 1.8.0_201

The repo containing the whole Gradle project for reproduction:

https://github.com/lobodpav/CompletableFutureMisbehavingTestInSpock

The problematic test's code:

@Stepwise
class SpockCompletableFutureTest extends Specification {
    def runnable = Stub(Runnable) {
        run() >> {
            println "${Date.newInstance()} BEGIN1 in thread ${Thread.currentThread()}"
            sleep(3000)
            println "${Date.newInstance()} END1   in thread ${Thread.currentThread()}"
        }
    }

    def "runAsyncWithMock"() {
        when:
        CompletableFuture.runAsync(runnable)

        then:
        true
    }

    def "runAsyncWithMockAndClosure"() {
        when:
        CompletableFuture.runAsync({ runnable.run() })

        then:
        true
    }

    def "runAsyncWithClass"() {
        when:
        CompletableFuture.runAsync(new Runnable() {
            void run() {
                println "${Date.newInstance()} BEGIN2 in thread ${Thread.currentThread()}"
                sleep(3000)
                println "${Date.newInstance()} END2   in thread ${Thread.currentThread()}"
            }
        })

        then:
        true
    }
}

Solution

  • This is caused by the synchronized methods in https://github.com/spockframework/spock/blob/master/spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java when a mock is executed it delegates through the handle method. The Specification also uses the synchronized methods, in this case probably leaveScope, and is thus blocked by the sleeping Stub method.

    Since this is a thread interleaving problem I guess that additional closure in runAsyncWithMockAndClosure moves the execution of the stub method behind the leaveScope and thus changes the ordering/blocking.