• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 package kotlinx.coroutines.testing
2 
3 import kotlinx.coroutines.scheduling.*
4 import java.io.*
5 import java.util.*
6 import kotlin.coroutines.*
7 import kotlinx.coroutines.*
8 import kotlin.test.*
9 
10 actual val VERBOSE = try {
11     System.getProperty("test.verbose")?.toBoolean() ?: false
12 } catch (e: SecurityException) {
13     false
14 }
15 
16 /**
17  * Is `true` when running in a nightly stress test mode.
18  */
19 actual val isStressTest = System.getProperty("stressTest")?.toBoolean() ?: false
20 
21 actual val stressTestMultiplierSqrt = if (isStressTest) 5 else 1
22 
23 private const val SHUTDOWN_TIMEOUT = 1_000L // 1s at most to wait per thread
24 
25 /**
26  * Multiply various constants in stress tests by this factor, so that they run longer during nightly stress test.
27  */
28 actual val stressTestMultiplier = stressTestMultiplierSqrt * stressTestMultiplierSqrt
29 
30 
31 @Suppress("ACTUAL_WITHOUT_EXPECT")
32 actual typealias TestResult = Unit
33 
lastResortReportExceptionnull34 internal actual fun lastResortReportException(error: Throwable) {
35     System.err.println("${error.message}${error.cause?.let { ": $it" } ?: ""}")
36     error.cause?.printStackTrace(System.err)
37     System.err.println("--- Detected at ---")
38     Throwable().printStackTrace(System.err)
39 }
40 
41 /**
42  * Base class for tests, so that tests for predictable scheduling of actions in multiple coroutines sharing a single
43  * thread can be written. Use it like this:
44  *
45  * ```
46  * class MyTest : TestBase() {
47  *     @Test
48  *     fun testSomething() = runBlocking { // run in the context of the main thread
49  *         expect(1) // initiate action counter
50  *         launch { // use the context of the main thread
51  *             expect(3) // the body of this coroutine in going to be executed in the 3rd step
52  *         }
53  *         expect(2) // launch just scheduled coroutine for execution later, so this line is executed second
54  *         yield() // yield main thread to the launched job
55  *         finish(4) // fourth step is the last one. `finish` must be invoked or test fails
56  *     }
57  * }
58  * ```
59  */
60 @Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS")
61 actual open class TestBase(
62     private var disableOutCheck: Boolean,
63     private val errorCatching: ErrorCatching.Impl = ErrorCatching.Impl()
64 ): OrderedExecutionTestBase(), ErrorCatching by errorCatching {
65 
66     actual constructor(): this(false)
67 
68     // Shutdown sequence
69     private lateinit var threadsBefore: Set<Thread>
70     private val uncaughtExceptions = Collections.synchronizedList(ArrayList<Throwable>())
71     private var originalUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null
72     /*
73      * System.out that we redefine in order to catch any debugging/diagnostics
74      * 'println' from main source set.
75      * NB: We do rely on the name 'previousOut' in the FieldWalker in order to skip its
76      * processing
77      */
78     private lateinit var previousOut: PrintStream
79 
80     private object TestOutputStream : PrintStream(object : OutputStream() {
writenull81         override fun write(b: Int) {
82             error("Detected unexpected call to 'println' from source code")
83         }
84     })
85 
printlnnull86     actual fun println(message: Any?) {
87         if (disableOutCheck) kotlin.io.println(message)
88         else previousOut.println(message)
89     }
90 
91     @BeforeTest
beforenull92     fun before() {
93         initPoolsBeforeTest()
94         threadsBefore = currentThreads()
95         originalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
96         Thread.setDefaultUncaughtExceptionHandler { t, e ->
97             println("Exception in thread $t: $e") // The same message as in default handler
98             e.printStackTrace()
99             uncaughtExceptions.add(e)
100         }
101         if (!disableOutCheck) {
102             previousOut = System.out
103             System.setOut(TestOutputStream)
104         }
105     }
106 
107     @AfterTest
onCompletionnull108     fun onCompletion() {
109         // onCompletion should not throw exceptions before it finishes all cleanup, so that other tests always
110         // start in a clear, restored state
111         checkFinishCall()
112         if (!disableOutCheck) { // Restore global System.out first
113             System.setOut(previousOut)
114         }
115         // Shutdown all thread pools
116         shutdownPoolsAfterTest()
117         // Check that are now leftover threads
118         runCatching {
119             checkTestThreads(threadsBefore)
120         }.onFailure {
121             reportError(it)
122         }
123         // Restore original uncaught exception handler after the main shutdown sequence
124         Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler)
125         if (uncaughtExceptions.isNotEmpty()) {
126             error("Expected no uncaught exceptions, but got $uncaughtExceptions")
127         }
128         // The very last action -- throw error if any was detected
129         errorCatching.close()
130     }
131 
runTestnull132     actual fun runTest(
133         expected: ((Throwable) -> Boolean)?,
134         unhandled: List<(Throwable) -> Boolean>,
135         block: suspend CoroutineScope.() -> Unit
136     ): TestResult {
137         var exCount = 0
138         var ex: Throwable? = null
139         try {
140             runBlocking(block = block, context = CoroutineExceptionHandler { _, e ->
141                 if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
142                 exCount++
143                 when {
144                     exCount > unhandled.size ->
145                         error("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
146                     !unhandled[exCount - 1](e) ->
147                         error("Unhandled exception was unexpected: $e", e)
148                 }
149             })
150         } catch (e: Throwable) {
151             ex = e
152             if (expected != null) {
153                 if (!expected(e))
154                     error("Unexpected exception: $e", e)
155             } else {
156                 throw e
157             }
158         } finally {
159             if (ex == null && expected != null) error("Exception was expected but none produced")
160         }
161         if (exCount < unhandled.size)
162             error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
163     }
164 
currentDispatchernull165     protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!!
166 }
167 
168 @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
169 fun initPoolsBeforeTest() {
170     DefaultScheduler.usePrivateScheduler()
171 }
172 
173 @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
shutdownPoolsAfterTestnull174 fun shutdownPoolsAfterTest() {
175     DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT)
176     DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT)
177     DefaultScheduler.restore()
178 }
179 
180 actual val isNative = false
181 
182 actual val isBoundByJsTestTimeout = false
183 
184 /*
185  * We ignore tests that test **real** non-virtualized tests with time on Windows, because
186  * our CI Windows is virtualized itself (oh, the irony) and its clock resolution is dozens of ms,
187  * which makes such tests flaky.
188  */
189 actual val isJavaAndWindows: Boolean = System.getProperty("os.name")!!.contains("Windows")
190 
191 actual val usesSharedEventLoop: Boolean = false
192