The version is 1.0-groovy-2.4
import spock.lang.Specification
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Semaphore
class CodeRunnerSpec extends Specification {
class CodeRunner {
Runnable code
Runnable fallback
Semaphore semaphore = new Semaphore(1)
void log(String message) {
println Thread.currentThread().getName() + ' ' + message
}
/**
* Runs the runnable in a new (!) thread.
* If the runnable is already running, runs fallback in the current (!) thread
*/
void runCode() {
log '=== runCode(): begin ==='
log 'trying to acquire the semaphore'
if (semaphore.tryAcquire()) {
log 'starting the code in a new thread'
Thread.start {
log 'we are in the thread, starting the code'
code.run()
log 'code has finished working, releasing the semaphore'
semaphore.release()
}
log 'the thread has been started'
} else {
log 'the semaphore is busy, running the fallback'
fallback.run()
log 'the fallback has finished'
}
log '=== runCode(): end ==='
}
}
def "Should run the fallback the code is already running"() {
given:
CountDownLatch latch = new CountDownLatch(1)
CodeRunner codeRunner = new CodeRunner();
codeRunner.code = Stub(Runnable) {
run() >> {
codeRunner.log 'waiting...'
latch.await()
codeRunner.log 'finished waiting, exiting'
}
}
codeRunner.fallback = Mock(Runnable)
when:
// the closure will start in a new (!) thread and start to wait on the latch
codeRunner.runCode()
Thread.sleep(10)
// the closure is already running, so the fallback should be run in the current (!) thread
codeRunner.runCode()
///////////////////////////////////////////////////////////////////////////////////
// it won't get to here, as it will be waiting on entering a synchronized method:
//
// org.spockframework.mock.runtime.MockController#handle
//
///////////////////////////////////////////////////////////////////////////////////
then:
1 * codeRunner.fallback.run()
cleanup:
latch.countDown()
}
}
The version is 1.0-groovy-2.4