• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import kotlin.js.*
8 
9 public actual val isStressTest: Boolean = false
10 public actual val stressTestMultiplier: Int = 1
11 public actual val stressTestMultiplierSqrt: Int = 1
12 
13 @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE")
14 public actual typealias TestResult = Promise<Unit>
15 
16 public actual val isNative = false
17 
18 public actual open class TestBase actual constructor() {
19     public actual val isBoundByJsTestTimeout = true
20     private var actionIndex = 0
21     private var finished = false
22     private var error: Throwable? = null
23     private var lastTestPromise: Promise<*>? = null
24 
25     /**
26      * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
27      * complete successfully even if this exception is consumed somewhere in the test.
28      */
29     @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
30     public actual fun error(message: Any, cause: Throwable? = null): Nothing {
31         if (cause != null) console.log(cause)
32         val exception = IllegalStateException(
33             if (cause == null) message.toString() else "$message; caused by $cause")
34         if (error == null) error = exception
35         throw exception
36     }
37 
38     private fun printError(message: String, cause: Throwable) {
39         if (error == null) error = cause
40         println("$message: $cause")
41         console.log(cause)
42     }
43 
44     /**
45      * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
46      */
47     public actual fun expect(index: Int) {
48         val wasIndex = ++actionIndex
49         check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
50     }
51 
52     /**
53      * Asserts that this line is never executed.
54      */
55     public actual fun expectUnreached() {
56         error("Should not be reached")
57     }
58 
59     /**
60      * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
61      */
62     public actual fun finish(index: Int) {
63         expect(index)
64         check(!finished) { "Should call 'finish(...)' at most once" }
65         finished = true
66     }
67 
68     /**
69      * Asserts that [finish] was invoked
70      */
71     public actual fun ensureFinished() {
72         require(finished) { "finish(...) should be caller prior to this check" }
73     }
74 
75     public actual fun reset() {
76         check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
77         actionIndex = 0
78         finished = false
79     }
80 
81     @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
82     public actual fun runTest(
83         expected: ((Throwable) -> Boolean)? = null,
84         unhandled: List<(Throwable) -> Boolean> = emptyList(),
85         block: suspend CoroutineScope.() -> Unit
86     ): TestResult {
87         var exCount = 0
88         var ex: Throwable? = null
89         /*
90          * This is an additional sanity check against `runTest` mis-usage on JS.
91          * The only way to write an async test on JS is to return Promise from the test function.
92          * _Just_ launching promise and returning `Unit` won't suffice as the underlying test framework
93          * won't be able to detect an asynchronous failure in a timely manner.
94          * We cannot detect such situations, but we can detect the most common erroneous pattern
95          * in our code base, an attempt to use multiple `runTest` in the same `@Test` method,
96          * which typically is a premise to the same error:
97          * ```
98          * @Test
99          * fun incorrectTestForJs() { // <- promise is not returned
100          *     for (parameter in parameters) {
101          *         runTest {
102          *             runTestForParameter(parameter)
103          *         }
104          *     }
105          * }
106          * ```
107          */
108         if (lastTestPromise != null) {
109             error("Attempt to run multiple asynchronous test within one @Test method")
110         }
111         val result = GlobalScope.promise(block = block, context = CoroutineExceptionHandler { _, e ->
112             if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
113             exCount++
114             when {
115                 exCount > unhandled.size ->
116                     printError("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e)
117                 !unhandled[exCount - 1](e) ->
118                     printError("Unhandled exception was unexpected: $e", e)
119             }
120         }).catch { e ->
121             ex = e
122             if (expected != null) {
123                 if (!expected(e))
124                     error("Unexpected exception", e)
125             } else
126                 throw e
127         }.finally {
128             if (ex == null && expected != null) error("Exception was expected but none produced")
129             if (exCount < unhandled.size)
130                 error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
131             error?.let { throw it }
132             check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
133         }
134         lastTestPromise = result
135         return result
136     }
137 }
138 
finallynull139 private fun <T> Promise<T>.finally(block: () -> Unit): Promise<T> =
140     then(onFulfilled = { value -> block(); value }, onRejected = { ex -> block(); throw ex })
141