<lambda>null1 @file:Suppress("unused")
2 package kotlinx.coroutines.testing
3
4 import kotlinx.atomicfu.*
5 import kotlinx.coroutines.flow.*
6 import kotlinx.coroutines.*
7 import kotlinx.coroutines.internal.*
8 import kotlin.coroutines.*
9 import kotlin.test.*
10 import kotlin.time.*
11 import kotlin.time.Duration.Companion.seconds
12
13 /**
14 * The number of milliseconds that is sure not to pass [assertRunsFast].
15 */
16 const val SLOW = 100_000L
17
18 /**
19 * Asserts that a block completed within [timeout].
20 */
21 inline fun <T> assertRunsFast(timeout: Duration, block: () -> T): T {
22 val result: T
23 val elapsed = TimeSource.Monotonic.measureTime { result = block() }
24 assertTrue("Should complete in $timeout, but took $elapsed") { elapsed < timeout }
25 return result
26 }
27
28 /**
29 * Asserts that a block completed within two seconds.
30 */
assertRunsFastnull31 inline fun <T> assertRunsFast(block: () -> T): T = assertRunsFast(2.seconds, block)
32
33 /**
34 * Whether the tests should trace their calls to `expect` and `finish` with `println`.
35 * `false` by default. On the JVM, can be set to `true` by setting the `test.verbose` system property.
36 */
37 expect val VERBOSE: Boolean
38
39 interface OrderedExecution {
40 /** Expect the next action to be [index] in order. */
41 fun expect(index: Int)
42
43 /** Expect this action to be final, with the given [index]. */
44 fun finish(index: Int)
45
46 /** * Asserts that this line is never executed. */
47 fun expectUnreached()
48
49 /**
50 * Checks that [finish] was called.
51 *
52 * By default, it is allowed to not call [finish] if [expect] was not called.
53 * This is useful for tests that don't check the ordering of events.
54 * When [allowNotUsingExpect] is set to `false`, it is an error to not call [finish] in any case.
55 */
56 fun checkFinishCall(allowNotUsingExpect: Boolean = true)
57
58 class Impl : OrderedExecution {
59 private val actionIndex = atomic(0)
60
61 override fun expect(index: Int) {
62 val wasIndex = actionIndex.incrementAndGet()
63 if (VERBOSE) println("expect($index), wasIndex=$wasIndex")
64 check(index == wasIndex) {
65 if (wasIndex < 0) "Expecting action index $index but it is actually finished"
66 else "Expecting action index $index but it is actually $wasIndex"
67 }
68 }
69
70 override fun finish(index: Int) {
71 val wasIndex = actionIndex.getAndSet(Int.MIN_VALUE) + 1
72 if (VERBOSE) println("finish($index), wasIndex=${if (wasIndex < 0) "finished" else wasIndex}")
73 check(index == wasIndex) {
74 if (wasIndex < 0) "Finished more than once"
75 else "Finishing with action index $index but it is actually $wasIndex"
76 }
77 }
78
79 override fun expectUnreached() {
80 error("Should not be reached, ${
81 actionIndex.value.let {
82 when {
83 it < 0 -> "already finished"
84 it == 0 -> "'expect' was not called yet"
85 else -> "the last executed action was $it"
86 }
87 }
88 }")
89 }
90
91 override fun checkFinishCall(allowNotUsingExpect: Boolean) {
92 actionIndex.value.let {
93 assertTrue(
94 it < 0 || allowNotUsingExpect && it == 0,
95 "Expected `finish(${actionIndex.value + 1})` to be called, but the test finished"
96 )
97 }
98 }
99 }
100 }
101
102 interface ErrorCatching {
103 /**
104 * Returns `true` if errors were logged in the test.
105 */
hasErrornull106 fun hasError(): Boolean
107
108 /**
109 * Directly reports an error to the test catching facilities.
110 */
111 fun reportError(error: Throwable)
112
113 class Impl : ErrorCatching {
114
115 private val errors = mutableListOf<Throwable>()
116 private val lock = SynchronizedObject()
117 private var closed = false
118
119 override fun hasError(): Boolean = synchronized(lock) {
120 errors.isNotEmpty()
121 }
122
123 override fun reportError(error: Throwable) {
124 synchronized(lock) {
125 if (closed) {
126 lastResortReportException(error)
127 } else {
128 errors.add(error)
129 }
130 }
131 }
132
133 fun close() {
134 synchronized(lock) {
135 if (closed) {
136 lastResortReportException(IllegalStateException("ErrorCatching closed more than once"))
137 }
138 closed = true
139 errors.firstOrNull()?.let {
140 for (error in errors.drop(1))
141 it.addSuppressed(error)
142 throw it
143 }
144 }
145 }
146 }
147 }
148
149 /**
150 * Reports an error *somehow* so that it doesn't get completely forgotten.
151 */
lastResortReportExceptionnull152 internal expect fun lastResortReportException(error: Throwable)
153
154 /**
155 * Throws [IllegalStateException] when `value` is false, like `check` in stdlib, but also ensures that the
156 * test will not complete successfully even if this exception is consumed somewhere in the test.
157 */
158 public inline fun ErrorCatching.check(value: Boolean, lazyMessage: () -> Any) {
159 if (!value) error(lazyMessage())
160 }
161
162 /**
163 * Throws [IllegalStateException], like `error` in stdlib, but also ensures that the test will not
164 * complete successfully even if this exception is consumed somewhere in the test.
165 */
ErrorCatchingnull166 fun ErrorCatching.error(message: Any, cause: Throwable? = null): Nothing {
167 throw IllegalStateException(message.toString(), cause).also {
168 reportError(it)
169 }
170 }
171
172 /**
173 * A class inheriting from which allows to check the execution order inside tests.
174 *
175 * @see TestBase
176 */
177 open class OrderedExecutionTestBase : OrderedExecution
178 {
179 // TODO: move to by-delegation when [reset] is no longer needed.
180 private var orderedExecutionDelegate = OrderedExecution.Impl()
181
182 @AfterTest
checkFinishednull183 fun checkFinished() { orderedExecutionDelegate.checkFinishCall() }
184
185 /** Resets counter and finish flag. Workaround for parametrized tests absence in common */
resetnull186 public fun reset() {
187 orderedExecutionDelegate.checkFinishCall()
188 orderedExecutionDelegate = OrderedExecution.Impl()
189 }
190
expectnull191 override fun expect(index: Int) = orderedExecutionDelegate.expect(index)
192
193 override fun finish(index: Int) = orderedExecutionDelegate.finish(index)
194
195 override fun expectUnreached() = orderedExecutionDelegate.expectUnreached()
196
197 override fun checkFinishCall(allowNotUsingExpect: Boolean) =
198 orderedExecutionDelegate.checkFinishCall(allowNotUsingExpect)
199 }
200
201 fun <T> T.void() {}
202
203 @OptionalExpectation
204 expect annotation class NoJs()
205
206 @OptionalExpectation
207 expect annotation class NoNative()
208
209 expect val isStressTest: Boolean
210 expect val stressTestMultiplier: Int
211 expect val stressTestMultiplierSqrt: Int
212
213 /**
214 * The result of a multiplatform asynchronous test.
215 * Aliases into Unit on K/JVM and K/N, and into Promise on K/JS.
216 */
217 @Suppress("NO_ACTUAL_FOR_EXPECT")
218 public expect class TestResult
219
220 public expect open class TestBase(): OrderedExecutionTestBase, ErrorCatching {
printlnnull221 public fun println(message: Any?)
222
223 public fun runTest(
224 expected: ((Throwable) -> Boolean)? = null,
225 unhandled: List<(Throwable) -> Boolean> = emptyList(),
226 block: suspend CoroutineScope.() -> Unit
227 ): TestResult
228 }
229
230 public suspend inline fun hang(onCancellation: () -> Unit) {
231 try {
232 suspendCancellableCoroutine<Unit> { }
233 } finally {
234 onCancellation()
235 }
236 }
237
assertFailsWithnull238 suspend inline fun <reified T : Throwable> assertFailsWith(flow: Flow<*>) = assertFailsWith<T> { flow.collect() }
239
accnull240 public suspend fun Flow<Int>.sum() = fold(0) { acc, value -> acc + value }
accnull241 public suspend fun Flow<Long>.longSum() = fold(0L) { acc, value -> acc + value }
242
243 // data is added to avoid stacktrace recovery because CopyableThrowable is not accessible from common modules
244 public class TestException(message: String? = null, private val data: Any? = null) : Throwable(message)
245 public class TestException1(message: String? = null, private val data: Any? = null) : Throwable(message)
246 public class TestException2(message: String? = null, private val data: Any? = null) : Throwable(message)
247 public class TestException3(message: String? = null, private val data: Any? = null) : Throwable(message)
248 public class TestCancellationException(message: String? = null, private val data: Any? = null) :
249 CancellationException(message)
250
251 public class TestRuntimeException(message: String? = null, private val data: Any? = null) : RuntimeException(message)
252 public class RecoverableTestException(message: String? = null) : RuntimeException(message)
253 public class RecoverableTestCancellationException(message: String? = null) : CancellationException(message)
254
wrapperDispatchernull255 public fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
256 val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
257 return object : CoroutineDispatcher() {
258 override fun isDispatchNeeded(context: CoroutineContext): Boolean =
259 dispatcher.isDispatchNeeded(context)
260
261 override fun dispatch(context: CoroutineContext, block: Runnable) =
262 dispatcher.dispatch(context, block)
263 }
264 }
265
wrapperDispatchernull266 public suspend fun wrapperDispatcher(): CoroutineContext = wrapperDispatcher(coroutineContext)
267 class BadClass {
268 override fun equals(other: Any?): Boolean = error("equals")
269 override fun hashCode(): Int = error("hashCode")
270 override fun toString(): String = error("toString")
271 }
272
273 public expect val isJavaAndWindows: Boolean
274
275 public expect val isNative: Boolean
276
277 /*
278 * In common tests we emulate parameterized tests
279 * by iterating over parameters space in the single @Test method.
280 * This kind of tests is too slow for JS and does not fit into
281 * the default Mocha timeout, so we're using this flag to bail-out
282 * and run such tests only on JVM and K/N.
283 */
284 public expect val isBoundByJsTestTimeout: Boolean
285