• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines.test
6 
7 import kotlinx.coroutines.*
8 import kotlinx.coroutines.internal.*
9 import kotlinx.coroutines.flow.*
10 import kotlin.coroutines.*
11 import kotlin.test.*
12 import kotlin.time.*
13 import kotlin.time.Duration.Companion.milliseconds
14 
15 class RunTestTest {
16 
17     /** Tests that [withContext] that sends work to other threads works in [runTest]. */
18     @Test
19     fun testWithContextDispatching() = runTest {
20         var counter = 0
21         withContext(Dispatchers.Default) {
22             counter += 1
23         }
24         assertEquals(counter, 1)
25     }
26 
27     /** Tests that joining [GlobalScope.launch] works in [runTest]. */
28     @Test
29     fun testJoiningForkedJob() = runTest {
30         var counter = 0
31         val job = GlobalScope.launch {
32             counter += 1
33         }
34         job.join()
35         assertEquals(counter, 1)
36     }
37 
38     /** Tests [suspendCoroutine] not failing [runTest]. */
39     @Test
40     fun testSuspendCoroutine() = runTest {
41         val answer = suspendCoroutine<Int> {
42             it.resume(42)
43         }
44         assertEquals(42, answer)
45     }
46 
47     /** Tests that [runTest] attempts to detect it being run inside another [runTest] and failing in such scenarios. */
48     @Test
49     fun testNestedRunTestForbidden() = runTest {
50         assertFailsWith<IllegalStateException> {
51             runTest { }
52         }
53     }
54 
55     /** Tests that even the dispatch timeout of `0` is fine if all the dispatches go through the same scheduler. */
56     @Test
57     fun testRunTestWithZeroDispatchTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) {
58         // below is some arbitrary concurrent code where all dispatches go through the same scheduler.
59         launch {
60             delay(2000)
61         }
62         val deferred = async {
63             val job = launch(StandardTestDispatcher(testScheduler)) {
64                 launch {
65                     delay(500)
66                 }
67                 delay(1000)
68             }
69             job.join()
70         }
71         deferred.await()
72     }
73 
74     /** Tests that too low of a dispatch timeout causes crashes. */
75     @Test
76     fun testRunTestWithSmallDispatchTimeout() = testResultMap({ fn ->
77         try {
78             fn()
79             fail("shouldn't be reached")
80         } catch (e: Throwable) {
81             assertIs<UncompletedCoroutinesError>(e)
82         }
83     }) {
84         runTest(dispatchTimeoutMs = 100) {
85             withContext(Dispatchers.Default) {
86                 delay(10000)
87                 3
88             }
89             fail("shouldn't be reached")
90         }
91     }
92 
93     /**
94      * Tests that [runTest] times out after the specified time.
95      */
96     @Test
97     fun testRunTestWithSmallTimeout() = testResultMap({ fn ->
98         try {
99             fn()
100             fail("shouldn't be reached")
101         } catch (e: Throwable) {
102             assertIs<UncompletedCoroutinesError>(e)
103         }
104     }) {
105         runTest(timeout = 100.milliseconds) {
106             withContext(Dispatchers.Default) {
107                 delay(10000)
108                 3
109             }
110             fail("shouldn't be reached")
111         }
112     }
113 
114     /** Tests that [runTest] times out after the specified time, even if the test framework always knows the test is
115      * still doing something. */
116     @Test
117     fun testRunTestWithSmallTimeoutAndManyDispatches() = testResultMap({ fn ->
118         try {
119             fn()
120             fail("shouldn't be reached")
121         } catch (e: Throwable) {
122             assertIs<UncompletedCoroutinesError>(e)
123         }
124     }) {
125         runTest(timeout = 100.milliseconds) {
126             while (true) {
127                 withContext(Dispatchers.Default) {
128                     delay(10)
129                     3
130                 }
131             }
132         }
133     }
134 
135     /** Tests that, on timeout, the names of the active coroutines are listed,
136      * whereas the names of the completed ones are not. */
137     @Test
138     @NoJs
139     @NoNative
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")
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")
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