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