<lambda>null1 package kotlinx.coroutines.test
2
3 import kotlinx.coroutines.*
4 import kotlinx.coroutines.internal.*
5 import kotlinx.coroutines.flow.*
6 import kotlinx.coroutines.testing.*
7 import kotlin.coroutines.*
8 import kotlin.test.*
9 import kotlin.test.assertFailsWith
10 import kotlin.time.*
11 import kotlin.time.Duration.Companion.milliseconds
12
13 class RunTestTest {
14
15 /** Tests that [withContext] that sends work to other threads works in [runTest]. */
16 @Test
17 fun testWithContextDispatching() = runTest {
18 var counter = 0
19 withContext(Dispatchers.Default) {
20 counter += 1
21 }
22 assertEquals(counter, 1)
23 }
24
25 /** Tests that joining [GlobalScope.launch] works in [runTest]. */
26 @Test
27 fun testJoiningForkedJob() = runTest {
28 var counter = 0
29 val job = GlobalScope.launch {
30 counter += 1
31 }
32 job.join()
33 assertEquals(counter, 1)
34 }
35
36 /** Tests [suspendCoroutine] not failing [runTest]. */
37 @Test
38 fun testSuspendCoroutine() = runTest {
39 val answer = suspendCoroutine<Int> {
40 it.resume(42)
41 }
42 assertEquals(42, answer)
43 }
44
45 /** Tests that [runTest] attempts to detect it being run inside another [runTest] and failing in such scenarios. */
46 @Test
47 fun testNestedRunTestForbidden() = runTest {
48 assertFailsWith<IllegalStateException> {
49 runTest { }
50 }
51 }
52
53 /** Tests that even the dispatch timeout of `0` is fine if all the dispatches go through the same scheduler. */
54 @Test
55 fun testRunTestWithZeroDispatchTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) {
56 // below is some arbitrary concurrent code where all dispatches go through the same scheduler.
57 launch {
58 delay(2000)
59 }
60 val deferred = async {
61 val job = launch(StandardTestDispatcher(testScheduler)) {
62 launch {
63 delay(500)
64 }
65 delay(1000)
66 }
67 job.join()
68 }
69 deferred.await()
70 }
71
72 /** Tests that too low of a dispatch timeout causes crashes. */
73 @Test
74 fun testRunTestWithSmallDispatchTimeout() = testResultMap({ fn ->
75 try {
76 fn()
77 fail("shouldn't be reached")
78 } catch (e: Throwable) {
79 assertIs<UncompletedCoroutinesError>(e)
80 }
81 }) {
82 runTest(dispatchTimeoutMs = 100) {
83 withContext(Dispatchers.Default) {
84 delay(10000)
85 3
86 }
87 fail("shouldn't be reached")
88 }
89 }
90
91 /**
92 * Tests that [runTest] times out after the specified time.
93 */
94 @Test
95 fun testRunTestWithSmallTimeout() = testResultMap({ fn ->
96 try {
97 fn()
98 fail("shouldn't be reached")
99 } catch (e: Throwable) {
100 assertIs<UncompletedCoroutinesError>(e)
101 }
102 }) {
103 runTest(timeout = 100.milliseconds) {
104 withContext(Dispatchers.Default) {
105 delay(10000)
106 3
107 }
108 fail("shouldn't be reached")
109 }
110 }
111
112 /** Tests that [runTest] times out after the specified time, even if the test framework always knows the test is
113 * still doing something. */
114 @Test
115 fun testRunTestWithSmallTimeoutAndManyDispatches() = testResultMap({ fn ->
116 try {
117 fn()
118 fail("shouldn't be reached")
119 } catch (e: Throwable) {
120 assertIs<UncompletedCoroutinesError>(e)
121 }
122 }) {
123 runTest(timeout = 100.milliseconds) {
124 while (true) {
125 withContext(Dispatchers.Default) {
126 delay(10)
127 3
128 }
129 }
130 }
131 }
132
133 /** Tests that, on timeout, the names of the active coroutines are listed,
134 * whereas the names of the completed ones are not. */
135 @Test
136 @NoJs
137 @NoNative
138 @NoWasmWasi
139 @NoWasmJs
140 fun testListingActiveCoroutinesOnTimeout(): TestResult {
141 val name1 = "GoodUniqueName"
142 val name2 = "BadUniqueName"
143 return testResultMap({
144 try {
145 it()
146 fail("unreached")
147 } catch (e: UncompletedCoroutinesError) {
148 assertContains(e.message ?: "", name1)
149 assertFalse((e.message ?: "").contains(name2))
150 }
151 }) {
152 runTest(dispatchTimeoutMs = 10) {
153 launch(CoroutineName(name1)) {
154 CompletableDeferred<Unit>().await()
155 }
156 launch(CoroutineName(name2)) {
157 }
158 }
159 }
160 }
161
162 /** Tests that the [UncompletedCoroutinesError] suppresses an exception with which the coroutine is completing. */
163 @Test
164 fun testFailureWithPendingCoroutine() = testResultMap({
165 try {
166 it()
167 fail("unreached")
168 } catch (e: UncompletedCoroutinesError) {
169 @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2
170 val suppressed = unwrap(e).suppressedExceptions
171 assertEquals(1, suppressed.size, "$suppressed")
172 assertIs<TestException>(suppressed[0]).also {
173 assertEquals("A", it.message)
174 }
175 }
176 }) {
177 runTest(timeout = 10.milliseconds) {
178 launch(start = CoroutineStart.UNDISPATCHED) {
179 withContext(NonCancellable + Dispatchers.Default) {
180 delay(100.milliseconds)
181 }
182 }
183 throw TestException("A")
184 }
185 }
186
187 /** Tests that real delays can be accounted for with a large enough dispatch timeout. */
188 @Test
189 fun testRunTestWithLargeDispatchTimeout() = runTest(dispatchTimeoutMs = 5000) {
190 withContext(Dispatchers.Default) {
191 delay(50)
192 }
193 }
194
195 /** Tests that delays can be accounted for with a large enough timeout. */
196 @Test
197 fun testRunTestWithLargeTimeout() = runTest(timeout = 5000.milliseconds) {
198 withContext(Dispatchers.Default) {
199 delay(50)
200 }
201 }
202
203 /** Tests uncaught exceptions being suppressed by the dispatch timeout error. */
204 @Test
205 fun testRunTestTimingOutAndThrowing() = testResultMap({ fn ->
206 try {
207 fn()
208 fail("unreached")
209 } catch (e: UncompletedCoroutinesError) {
210 @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2
211 val suppressed = unwrap(e).suppressedExceptions
212 assertEquals(1, suppressed.size, "$suppressed")
213 assertIs<TestException>(suppressed[0]).also {
214 assertEquals("A", it.message)
215 }
216 }
217 }) {
218 runTest(timeout = 100.milliseconds) {
219 coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A"))
220 withContext(Dispatchers.Default) {
221 delay(10000)
222 3
223 }
224 fail("shouldn't be reached")
225 }
226 }
227
228 /** Tests that passing invalid contexts to [runTest] causes it to fail (on JS, without forking). */
229 @Test
230 fun testRunTestWithIllegalContext() {
231 for (ctx in TestScopeTest.invalidContexts) {
232 assertFailsWith<IllegalArgumentException> {
233 runTest(ctx) { }
234 }
235 }
236 }
237
238 /** Tests that throwing exceptions in [runTest] fails the test with them. */
239 @Test
240 fun testThrowingInRunTestBody() = testResultMap({
241 assertFailsWith<RuntimeException> { it() }
242 }) {
243 runTest {
244 throw RuntimeException()
245 }
246 }
247
248 /** Tests that throwing exceptions in pending tasks [runTest] fails the test with them. */
249 @Test
250 fun testThrowingInRunTestPendingTask() = testResultMap({
251 assertFailsWith<RuntimeException> { it() }
252 }) {
253 runTest {
254 launch {
255 delay(SLOW)
256 throw RuntimeException()
257 }
258 }
259 }
260
261 @Test
262 fun reproducer2405() = runTest {
263 val dispatcher = StandardTestDispatcher(testScheduler)
264 var collectedError = false
265 withContext(dispatcher) {
266 flow { emit(1) }
267 .combine(
268 flow<String> { throw IllegalArgumentException() }
269 ) { int, string -> int.toString() + string }
270 .catch { emit("error") }
271 .collect {
272 assertEquals("error", it)
273 collectedError = true
274 }
275 }
276 assertTrue(collectedError)
277 }
278
279 /** Tests that, once the test body has thrown, the child coroutines are cancelled. */
280 @Test
281 fun testChildrenCancellationOnTestBodyFailure(): TestResult {
282 var job: Job? = null
283 return testResultMap({
284 assertFailsWith<AssertionError> { it() }
285 assertTrue(job!!.isCancelled)
286 }) {
287 runTest {
288 job = launch {
289 while (true) {
290 delay(1000)
291 }
292 }
293 throw AssertionError()
294 }
295 }
296 }
297
298 /** Tests that [runTest] reports [TimeoutCancellationException]. */
299 @Test
300 fun testTimeout() = testResultMap({
301 assertFailsWith<TimeoutCancellationException> { it() }
302 }) {
303 runTest {
304 withTimeout(50) {
305 launch {
306 delay(1000)
307 }
308 }
309 }
310 }
311
312 /** Checks that [runTest] throws the root cause and not [JobCancellationException] when a child coroutine throws. */
313 @Test
314 fun testRunTestThrowsRootCause() = testResultMap({
315 assertFailsWith<TestException> { it() }
316 }) {
317 runTest {
318 launch {
319 throw TestException()
320 }
321 }
322 }
323
324 /** Tests that [runTest] completes its job. */
325 @Test
326 fun testCompletesOwnJob(): TestResult {
327 var handlerCalled = false
328 return testResultMap({
329 it()
330 assertTrue(handlerCalled)
331 }) {
332 runTest {
333 coroutineContext.job.invokeOnCompletion {
334 handlerCalled = true
335 }
336 }
337 }
338 }
339
340 /** Tests that [runTest] doesn't complete the job that was passed to it as an argument. */
341 @Test
342 fun testDoesNotCompleteGivenJob(): TestResult {
343 var handlerCalled = false
344 val job = Job()
345 job.invokeOnCompletion {
346 handlerCalled = true
347 }
348 return testResultMap({
349 it()
350 assertFalse(handlerCalled)
351 assertEquals(0, job.children.filter { it.isActive }.count())
352 }) {
353 runTest(job) {
354 assertTrue(coroutineContext.job in job.children)
355 }
356 }
357 }
358
359 /** Tests that, when the test body fails, the reported exceptions are suppressed. */
360 @Test
361 fun testSuppressedExceptions() = testResultMap({
362 try {
363 it()
364 fail("should not be reached")
365 } catch (e: TestException) {
366 assertEquals("w", e.message)
367 val suppressed = e.suppressedExceptions +
368 (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList())
369 assertEquals(3, suppressed.size)
370 assertEquals("x", suppressed[0].message)
371 assertEquals("y", suppressed[1].message)
372 assertEquals("z", suppressed[2].message)
373 }
374 }) {
375 runTest {
376 launch(SupervisorJob()) { throw TestException("x") }
377 launch(SupervisorJob()) { throw TestException("y") }
378 launch(SupervisorJob()) { throw TestException("z") }
379 throw TestException("w")
380 }
381 }
382
383 /** Tests that [TestScope.runTest] does not inherit the exception handler and works. */
384 @Test
385 fun testScopeRunTestExceptionHandler(): TestResult {
386 val scope = TestScope()
387 return testResultMap({
388 try {
389 it()
390 fail("should not be reached")
391 } catch (e: TestException) {
392 // expected
393 }
394 }) {
395 scope.runTest {
396 launch(SupervisorJob()) { throw TestException("x") }
397 }
398 }
399 }
400
401 /**
402 * Tests that if the main coroutine is completed without a dispatch, [runTest] will not consider this to be
403 * inactivity.
404 *
405 * The test will hang if this is not the case.
406 */
407 @Test
408 fun testCoroutineCompletingWithoutDispatch() = runTest(timeout = Duration.INFINITE) {
409 launch(Dispatchers.Default) { delay(100) }
410 }
411
412 /**
413 * Tests that [runTest] cleans up the exception handler even if it threw on initialization.
414 *
415 * This test must be run manually, because it writes garbage to the log.
416 *
417 * The JVM-only source set contains a test equivalent to this one that isn't ignored.
418 */
419 @Test
420 @Ignore
421 fun testExceptionCaptorCleanedUpOnPreliminaryExit(): TestResult = testResultChain({
422 // step 1: installing the exception handler
423 println("step 1")
424 runTest { }
425 }, {
426 it.getOrThrow()
427 // step 2: throwing an uncaught exception to be caught by the exception-handling system
428 println("step 2")
429 createTestResult {
430 launch(NonCancellable) { throw TestException("A") }
431 }
432 }, {
433 it.getOrThrow()
434 // step 3: trying to run a test should immediately fail, even before entering the test body
435 println("step 3")
436 try {
437 runTest {
438 fail("unreached")
439 }
440 fail("unreached")
441 } catch (e: UncaughtExceptionsBeforeTest) {
442 val cause = e.suppressedExceptions.single()
443 assertIs<TestException>(cause)
444 assertEquals("A", cause.message)
445 }
446 // step 4: trying to run a test again should not fail with an exception
447 println("step 4")
448 runTest {
449 }
450 }, {
451 it.getOrThrow()
452 // step 5: throwing an uncaught exception to be caught by the exception-handling system, again
453 println("step 5")
454 createTestResult {
455 launch(NonCancellable) { throw TestException("B") }
456 }
457 }, {
458 it.getOrThrow()
459 // step 6: trying to run a test should immediately fail, again
460 println("step 6")
461 try {
462 runTest {
463 fail("unreached")
464 }
465 fail("unreached")
466 } catch (e: Exception) {
467 val cause = e.suppressedExceptions.single()
468 assertIs<TestException>(cause)
469 assertEquals("B", cause.message)
470 }
471 // step 7: trying to run a test again should not fail with an exception, again
472 println("step 7")
473 runTest {
474 }
475 })
476
477 @Test
478 fun testCancellingTestScope() = testResultMap({
479 try {
480 it()
481 fail("unreached")
482 } catch (e: CancellationException) {
483 // expected
484 }
485 }) {
486 runTest {
487 cancel(CancellationException("Oh no", TestException()))
488 }
489 }
490 }
491