• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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