Pavel Lobodinský
Pavel Lobodinský

Reputation: 1158

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:

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
    }
}

Upvotes: 0

Views: 4207

Answers (2)

Leonard Brünings
Leonard Brünings

Reputation: 13242

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.

Upvotes: 2

kriegaex
kriegaex

Reputation: 67417

Oh, just now after writing my last comment I saw a difference:

You use @Stepwise (I didn't when I tried at first), an annotation I almost never use because it creates dependencies between feature methods (bad, bad testing practice). While I cannot say why this has the effect described by you only when running the first method, I can tell you that removing the annotation fixes it.

P.S.: With @Stepwise you cannot even execute the second or third method separately because the runner will always run the preceding one(s) first, because - well, the specification is said to be executed step-wise. ;-)


Update: I could briefly reproduce the problem with @Stepwise, but after recompilation now it does not happen anymore, neither with or without that annotation.

Upvotes: 0

Related Questions