1 /*
<lambda>null2  * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines
6 
7 public actual val isStressTest: Boolean = false
8 public actual val stressTestMultiplier: Int = 1
9 
10 public actual open class TestBase actual constructor() {
11     private var actionIndex = 0
12     private var finished = false
13     private var error: Throwable? = null
14 
15     /**
16      * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
17      * complete successfully even if this exception is consumed somewhere in the test.
18      */
19     @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
20     public actual fun error(message: Any, cause: Throwable? = null): Nothing {
21         val exception = IllegalStateException(message.toString(), cause)
22         if (error == null) error = exception
23         throw exception
24     }
25 
26     private fun printError(message: String, cause: Throwable) {
27         if (error == null) error = cause
28         println("$message: $cause")
29     }
30 
31     /**
32      * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
33      */
34     public actual fun expect(index: Int) {
35         val wasIndex = ++actionIndex
36         check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
37     }
38 
39     /**
40      * Asserts that this line is never executed.
41      */
42     public actual fun expectUnreached() {
43         error("Should not be reached")
44     }
45 
46     /**
47      * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
48      */
49     public actual fun finish(index: Int) {
50         expect(index)
51         check(!finished) { "Should call 'finish(...)' at most once" }
52         finished = true
53     }
54 
55     /**
56      * Asserts that [finish] was invoked
57      */
58     public actual fun ensureFinished() {
59         require(finished) { "finish(...) should be caller prior to this check" }
60     }
61 
62     public actual fun reset() {
63         check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
64         actionIndex = 0
65         finished = false
66     }
67 
68     @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
69     public actual fun runTest(
70         expected: ((Throwable) -> Boolean)? = null,
71         unhandled: List<(Throwable) -> Boolean> = emptyList(),
72         block: suspend CoroutineScope.() -> Unit
73     ) {
74         var exCount = 0
75         var ex: Throwable? = null
76         try {
77             runBlocking(block = block, context = CoroutineExceptionHandler { context, e ->
78                 if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
79                 exCount++
80                 when {
81                     exCount > unhandled.size ->
82                         printError("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
83                     !unhandled[exCount - 1](e) ->
84                         printError("Unhandled exception was unexpected: $e", e)
85                 }
86             })
87         } catch (e: Throwable) {
88             ex = e
89             if (expected != null) {
90                 if (!expected(e))
91                     error("Unexpected exception: $e", e)
92             } else
93                 throw e
94         } finally {
95             if (ex == null && expected != null) error("Exception was expected but none produced")
96         }
97         if (exCount < unhandled.size)
98             error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
99     }
100 }
101