<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 @OptionalExpectation
210 expect annotation class NoWasmJs()
211
212 @OptionalExpectation
213 expect annotation class NoWasmWasi()
214
215 expect val isStressTest: Boolean
216 expect val stressTestMultiplier: Int
217 expect val stressTestMultiplierSqrt: Int
218
219 /**
220 * The result of a multiplatform asynchronous test.
221 * Aliases into Unit on K/JVM and K/N, and into Promise on K/JS.
222 */
223 @Suppress("NO_ACTUAL_FOR_EXPECT")
224 public expect class TestResult
225
226 public expect open class TestBase(): OrderedExecutionTestBase, ErrorCatching {
printlnnull227 public fun println(message: Any?)
228
229 public fun runTest(
230 expected: ((Throwable) -> Boolean)? = null,
231 unhandled: List<(Throwable) -> Boolean> = emptyList(),
232 block: suspend CoroutineScope.() -> Unit
233 ): TestResult
234
235 override fun hasError(): Boolean
236 override fun reportError(error: Throwable)
237 }
238
239 public suspend inline fun hang(onCancellation: () -> Unit) {
240 try {
241 suspendCancellableCoroutine<Unit> { }
242 } finally {
243 onCancellation()
244 }
245 }
246
assertFailsWithnull247 suspend inline fun <reified T : Throwable> assertFailsWith(flow: Flow<*>) = assertFailsWith<T> { flow.collect() }
248
accnull249 public suspend fun Flow<Int>.sum() = fold(0) { acc, value -> acc + value }
accnull250 public suspend fun Flow<Long>.longSum() = fold(0L) { acc, value -> acc + value }
251
252 // data is added to avoid stacktrace recovery because CopyableThrowable is not accessible from common modules
253 public class TestException(message: String? = null, private val data: Any? = null) : Throwable(message)
254 public class TestException1(message: String? = null, private val data: Any? = null) : Throwable(message)
255 public class TestException2(message: String? = null, private val data: Any? = null) : Throwable(message)
256 public class TestException3(message: String? = null, private val data: Any? = null) : Throwable(message)
257 public class TestCancellationException(message: String? = null, private val data: Any? = null) :
258 CancellationException(message)
259
260 public class TestRuntimeException(message: String? = null, private val data: Any? = null) : RuntimeException(message)
261 public class RecoverableTestException(message: String? = null) : RuntimeException(message)
262 public class RecoverableTestCancellationException(message: String? = null) : CancellationException(message)
263
264 // Erases identity and equality checks for tests
wrapperDispatchernull265 public fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
266 val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
267 return object : CoroutineDispatcher() {
268 override fun isDispatchNeeded(context: CoroutineContext): Boolean =
269 dispatcher.isDispatchNeeded(context)
270
271 override fun dispatch(context: CoroutineContext, block: Runnable) =
272 dispatcher.dispatch(context, block)
273 }
274 }
275
wrapperDispatchernull276 public suspend fun wrapperDispatcher(): CoroutineContext = wrapperDispatcher(coroutineContext)
277 class BadClass {
278 override fun equals(other: Any?): Boolean = error("equals")
279 override fun hashCode(): Int = error("hashCode")
280 override fun toString(): String = error("toString")
281 }
282
283 public expect val isJavaAndWindows: Boolean
284
285 public expect val isNative: Boolean
286
287 /*
288 * In common tests we emulate parameterized tests
289 * by iterating over parameters space in the single @Test method.
290 * This kind of tests is too slow for JS and does not fit into
291 * the default Mocha timeout, so we're using this flag to bail-out
292 * and run such tests only on JVM and K/N.
293 */
294 public expect val isBoundByJsTestTimeout: Boolean
295
296 /**
297 * `true` if this platform has the same event loop for `DefaultExecutor` and [Dispatchers.Unconfined]
298 */
299 public expect val usesSharedEventLoop: Boolean
300