• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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