CompletableFuture 运行 时在 Spock 的 Mock 等待中睡眠

Sleep in Spock's Mock waits when run by CompletableFuture

当我单独 运行 runAsyncWithMock 测试时,它会等待 3 秒直到模拟的执行完成,而不是像其他 2 个测试那样被终止。

我不明白为什么。

有趣的是:

  1. runAsyncWithMock测试中,当CompletableFuture.runAsync连续执行多个Runnables时,只有第一个等待,其他的不等待。
  2. 当有多个重复的 runAsyncWithMock 测试时,当整个规范被执行时,每个测试 运行s 持续 3s。
  3. 当使用 Class 实例而不是 Mock 时,测试会立即完成。

知道我错了什么吗?

我的配置:

包含整个 Gradle 复制项目的回购:

https://github.com/lobodpav/CompletableFutureMisbehavingTestInSpock

有问题的测试代码:

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

哦,刚刚写完我的最后一条评论,我看到了不同之处:

您使用 @Stepwise(我一开始尝试时没有),这是一个我几乎从不使用的注解,因为它会在特征方法之间创建依赖关系(糟糕、糟糕的测试实践)。虽然我不能说为什么这只有在 运行 使用第一种方法时才会产生你描述的效果,但我可以告诉你删除注释可以修复它。

P.S.: 使用 @Stepwise 你甚至不能单独执行第二种或第三种方法,因为 运行ner 总是先 运行 前面的方法,因为 - 好吧,据说规范是逐步执行的。 ;-)


更新: 我可以用 @Stepwise 简单地重现这个问题,但是在重新编译之后它不再发生了,不管有没有那个注释。

这是由 https://github.com/spockframework/spock/blob/master/spock-core/src/main/java/org/spockframework/mock/runtime/MockController.java 中的 synchronized 方法引起的,当执行模拟时,它通过 handle 方法委托。该规范还使用 synchronized 方法,在本例中可能是 leaveScope,因此被休眠存根方法阻塞。

由于这是一个线程交错问题,我猜 runAsyncWithMockAndClosure 中的附加闭包将存根方法的执行移到了 leaveScope 后面,从而改变了 ordering/blocking.