blob: 39b40c13248c07d96a3065cf3c9ee4759453e30e [file] [log] [blame]
Roman Elizarova7db8ec2017-12-21 22:45:12 +03001/*
Roman Elizarov1f74a2d2018-06-29 19:19:45 +03002 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
Roman Elizarova7db8ec2017-12-21 22:45:12 +03003 */
4
Roman Elizarov8bff72b2017-12-20 12:55:38 +03005package kotlinx.coroutines.experimental
6
Roman Elizarov1bb8c1c2018-01-11 10:39:46 +03007import kotlin.js.*
8
Roman Elizarov8bff72b2017-12-20 12:55:38 +03009public actual open class TestBase actual constructor() {
10 public actual val isStressTest: Boolean = false
11 public actual val stressTestMultiplier: Int = 1
12
13 private var actionIndex = 0
14 private var finished = false
15 private var error: Throwable? = null
16
17 /**
18 * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not
19 * complete successfully even if this exception is consumed somewhere in the test.
20 */
Ilya Gorbunovf0cd1802018-04-17 19:59:31 +030021 @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
Roman Elizarov8bff72b2017-12-20 12:55:38 +030022 public actual fun error(message: Any, cause: Throwable? = null): Nothing {
Roman Elizarovaa461cf2018-04-11 13:20:29 +030023 if (cause != null) console.log(cause)
Roman Elizarov8bff72b2017-12-20 12:55:38 +030024 val exception = IllegalStateException(
25 if (cause == null) message.toString() else "$message; caused by $cause")
26 if (error == null) error = exception
27 throw exception
28 }
29
30 /**
31 * Asserts that this invocation is `index`-th in the execution sequence (counting from one).
32 */
33 public actual fun expect(index: Int) {
34 val wasIndex = ++actionIndex
35 check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" }
36 }
37
38 /**
39 * Asserts that this line is never executed.
40 */
41 public actual fun expectUnreached() {
42 error("Should not be reached")
43 }
44
45 /**
46 * Asserts that this it the last action in the test. It must be invoked by any test that used [expect].
47 */
48 public actual fun finish(index: Int) {
49 expect(index)
50 check(!finished) { "Should call 'finish(...)' at most once" }
51 finished = true
52 }
53
Roman Elizarov4b9ae982018-01-25 11:51:25 +030054 // todo: The dynamic (promise) result is a work-around for missing suspend tests, see KT-22228
Ilya Gorbunovf0cd1802018-04-17 19:59:31 +030055 @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
Roman Elizarov8bff72b2017-12-20 12:55:38 +030056 public actual fun runTest(
57 expected: ((Throwable) -> Boolean)? = null,
58 unhandled: List<(Throwable) -> Boolean> = emptyList(),
59 block: suspend CoroutineScope.() -> Unit
Roman Elizarov4b9ae982018-01-25 11:51:25 +030060 ): dynamic {
Roman Elizarov8bff72b2017-12-20 12:55:38 +030061 var exCount = 0
62 var ex: Throwable? = null
Roman Elizarovf066fe92018-01-25 11:38:43 +030063 return promise(block = block, context = CoroutineExceptionHandler { context, e ->
Roman Elizarov1bb8c1c2018-01-11 10:39:46 +030064 if (e is CancellationException) return@CoroutineExceptionHandler // are ignored
65 exCount++
66 if (exCount > unhandled.size)
67 error("Too many unhandled exceptions $exCount, expected ${unhandled.size}", e)
68 if (!unhandled[exCount - 1](e))
69 error("Unhandled exception was unexpected", e)
70 context[Job]?.cancel(e)
71 }).catch { e ->
Roman Elizarov8bff72b2017-12-20 12:55:38 +030072 ex = e
73 if (expected != null) {
74 if (!expected(e))
75 error("Unexpected exception", e)
76 } else
77 throw e
Roman Elizarov1bb8c1c2018-01-11 10:39:46 +030078 }.finally {
Roman Elizarov8bff72b2017-12-20 12:55:38 +030079 if (ex == null && expected != null) error("Exception was expected but none produced")
Roman Elizarov1bb8c1c2018-01-11 10:39:46 +030080 if (exCount < unhandled.size)
81 error("Too few unhandled exceptions $exCount, expected ${unhandled.size}")
82 error?.let { throw it }
83 check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" }
Roman Elizarov8bff72b2017-12-20 12:55:38 +030084 }
Roman Elizarov8bff72b2017-12-20 12:55:38 +030085 }
86}
Roman Elizarov1bb8c1c2018-01-11 10:39:46 +030087
88private fun <T> Promise<T>.finally(block: () -> Unit): Promise<T> =
89 then(onFulfilled = { value -> block(); value }, onRejected = { ex -> block(); throw ex })