1 /* 2 * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.coroutines.debug.junit4 6 7 import kotlinx.coroutines.debug.* 8 import org.junit.runner.* 9 import org.junit.runners.model.* 10 import java.util.concurrent.* 11 12 internal class CoroutinesTimeoutStatement( 13 testStatement: Statement, 14 private val testDescription: Description, 15 private val testTimeoutMs: Long, 16 private val cancelOnTimeout: Boolean = false 17 ) : Statement() { 18 19 private val testStartedLatch = CountDownLatch(1) 20 <lambda>null21 private val testResult = FutureTask<Unit> { 22 testStartedLatch.countDown() 23 testStatement.evaluate() 24 } 25 26 /* 27 * We are using hand-rolled thread instead of single thread executor 28 * in order to be able to safely interrupt thread in the end of a test 29 */ <lambda>null30 private val testThread = Thread(testResult, "Timeout test thread").apply { isDaemon = true } 31 evaluatenull32 override fun evaluate() { 33 DebugProbes.install() 34 testThread.start() 35 // Await until test is started to take only test execution time into account 36 testStartedLatch.await() 37 try { 38 testResult.get(testTimeoutMs, TimeUnit.MILLISECONDS) 39 return 40 } catch (e: TimeoutException) { 41 handleTimeout(testDescription) 42 } catch (e: ExecutionException) { 43 throw e.cause ?: e 44 } finally { 45 DebugProbes.uninstall() 46 } 47 } 48 handleTimeoutnull49 private fun handleTimeout(description: Description) { 50 val units = 51 if (testTimeoutMs % 1000 == 0L) 52 "${testTimeoutMs / 1000} seconds" 53 else "$testTimeoutMs milliseconds" 54 55 System.err.println("\nTest ${description.methodName} timed out after $units\n") 56 System.err.flush() 57 58 DebugProbes.dumpCoroutines() 59 System.out.flush() // Synchronize serr/sout 60 61 /* 62 * Order is important: 63 * 1) Create exception with a stacktrace of hang test 64 * 2) Cancel all coroutines via debug agent API (changing system state!) 65 * 3) Throw created exception 66 */ 67 val exception = createTimeoutException(testThread) 68 cancelIfNecessary() 69 // If timed out test throws an exception, we can't do much except ignoring it 70 throw exception 71 } 72 cancelIfNecessarynull73 private fun cancelIfNecessary() { 74 if (cancelOnTimeout) { 75 DebugProbes.dumpCoroutinesInfo().forEach { 76 it.job?.cancel() 77 } 78 } 79 } 80 createTimeoutExceptionnull81 private fun createTimeoutException(thread: Thread): Exception { 82 val stackTrace = thread.stackTrace 83 val exception = TestTimedOutException(testTimeoutMs, TimeUnit.MILLISECONDS) 84 exception.stackTrace = stackTrace 85 thread.interrupt() 86 return exception 87 } 88 } 89