• 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.channels.*
9 import kotlinx.coroutines.flow.*
10 import kotlin.test.*
11 
12 /** Copy of [RunTestTest], but for [runBlockingTestOnTestScope], where applicable. */
13 @Suppress("DEPRECATION")
14 class RunBlockingTestOnTestScopeTest {
15 
16     @Test
17     fun testRunTestWithIllegalContext() {
18         for (ctx in TestScopeTest.invalidContexts) {
19             assertFailsWith<IllegalArgumentException> {
20                 runBlockingTestOnTestScope(ctx) { }
21             }
22         }
23     }
24 
25     @Test
26     fun testThrowingInRunTestBody() {
27         assertFailsWith<RuntimeException> {
28             runBlockingTestOnTestScope {
29                 throw RuntimeException()
30             }
31         }
32     }
33 
34     @Test
35     fun testThrowingInRunTestPendingTask() {
36         assertFailsWith<RuntimeException> {
37             runBlockingTestOnTestScope {
38                 launch {
39                     delay(SLOW)
40                     throw RuntimeException()
41                 }
42             }
43         }
44     }
45 
46     @Test
47     fun reproducer2405() = runBlockingTestOnTestScope {
48         val dispatcher = StandardTestDispatcher(testScheduler)
49         var collectedError = false
50         withContext(dispatcher) {
51             flow { emit(1) }
52                 .combine(
53                     flow<String> { throw IllegalArgumentException() }
54                 ) { int, string -> int.toString() + string }
55                 .catch { emit("error") }
56                 .collect {
57                     assertEquals("error", it)
58                     collectedError = true
59                 }
60         }
61         assertTrue(collectedError)
62     }
63 
64     @Test
65     fun testChildrenCancellationOnTestBodyFailure() {
66         var job: Job? = null
67         assertFailsWith<AssertionError> {
68             runBlockingTestOnTestScope {
69                 job = launch {
70                     while (true) {
71                         delay(1000)
72                     }
73                 }
74                 throw AssertionError()
75             }
76         }
77         assertTrue(job!!.isCancelled)
78     }
79 
80     @Test
81     fun testTimeout() {
82         assertFailsWith<TimeoutCancellationException> {
83             runBlockingTestOnTestScope {
84                 withTimeout(50) {
85                     launch {
86                         delay(1000)
87                     }
88                 }
89             }
90         }
91     }
92 
93     @Test
94     fun testRunTestThrowsRootCause() {
95         assertFailsWith<TestException> {
96             runBlockingTestOnTestScope {
97                 launch {
98                     throw TestException()
99                 }
100             }
101         }
102     }
103 
104     @Test
105     fun testCompletesOwnJob() {
106         var handlerCalled = false
107         runBlockingTestOnTestScope {
108             coroutineContext.job.invokeOnCompletion {
109                 handlerCalled = true
110             }
111         }
112         assertTrue(handlerCalled)
113     }
114 
115     @Test
116     fun testDoesNotCompleteGivenJob() {
117         var handlerCalled = false
118         val job = Job()
119         job.invokeOnCompletion {
120             handlerCalled = true
121         }
122         runBlockingTestOnTestScope(job) {
123             assertTrue(coroutineContext.job in job.children)
124         }
125         assertFalse(handlerCalled)
126         assertEquals(0, job.children.filter { it.isActive }.count())
127     }
128 
129     @Test
130     fun testSuppressedExceptions() {
131         try {
132             runBlockingTestOnTestScope {
133                 launch(SupervisorJob()) { throw TestException("x") }
134                 launch(SupervisorJob()) { throw TestException("y") }
135                 launch(SupervisorJob()) { throw TestException("z") }
136                 throw TestException("w")
137             }
138             fail("should not be reached")
139         } catch (e: TestException) {
140             assertEquals("w", e.message)
141             val suppressed = e.suppressedExceptions +
142                 (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList())
143             assertEquals(3, suppressed.size)
144             assertEquals("x", suppressed[0].message)
145             assertEquals("y", suppressed[1].message)
146             assertEquals("z", suppressed[2].message)
147         }
148     }
149 
150     @Test
151     fun testScopeRunTestExceptionHandler(): TestResult {
152         val scope = TestCoroutineScope()
153         return testResultMap({
154             try {
155                 it()
156                 fail("should not be reached")
157             } catch (e: TestException) {
158                 // expected
159             }
160         }) {
161             scope.runTest {
162                 launch(SupervisorJob()) { throw TestException("x") }
163             }
164         }
165     }
166 
167     @Test
168     fun testBackgroundWorkBeingRun() = runBlockingTestOnTestScope {
169         var i = 0
170         var j = 0
171         backgroundScope.launch {
172             yield()
173             ++i
174         }
175         backgroundScope.launch {
176             yield()
177             delay(10)
178             ++j
179         }
180         assertEquals(0, i)
181         assertEquals(0, j)
182         delay(1)
183         assertEquals(1, i)
184         assertEquals(0, j)
185         delay(10)
186         assertEquals(1, i)
187         assertEquals(1, j)
188     }
189 
190     @Test
191     fun testBackgroundWorkCancelled() {
192         var cancelled = false
193         runBlockingTestOnTestScope {
194             var i = 0
195             backgroundScope.launch {
196                 yield()
197                 try {
198                     while (isActive) {
199                         ++i
200                         yield()
201                     }
202                 } catch (e: CancellationException) {
203                     cancelled = true
204                 }
205             }
206             repeat(5) {
207                 assertEquals(i, it)
208                 yield()
209             }
210         }
211         assertTrue(cancelled)
212     }
213 
214     @Test
215     fun testBackgroundWorkTimeControl(): TestResult = runBlockingTestOnTestScope {
216         var i = 0
217         var j = 0
218         backgroundScope.launch {
219             yield()
220             while (true) {
221                 ++i
222                 delay(100)
223             }
224         }
225         backgroundScope.launch {
226             yield()
227             while (true) {
228                 ++j
229                 delay(50)
230             }
231         }
232         advanceUntilIdle() // should do nothing, as only background work is left.
233         assertEquals(0, i)
234         assertEquals(0, j)
235         val job = launch {
236             delay(1)
237             // the background work scheduled for earlier gets executed before the normal work scheduled for later does
238             assertEquals(1, i)
239             assertEquals(1, j)
240         }
241         job.join()
242         advanceTimeBy(199) // should work the same for the background tasks
243         assertEquals(2, i)
244         assertEquals(4, j)
245         advanceUntilIdle() // once again, should do nothing
246         assertEquals(2, i)
247         assertEquals(4, j)
248         runCurrent() // should behave the same way as for the normal work
249         assertEquals(3, i)
250         assertEquals(5, j)
251         launch {
252             delay(1001)
253             assertEquals(13, i)
254             assertEquals(25, j)
255         }
256         advanceUntilIdle() // should execute the normal work, and with that, the background one, too
257     }
258 
259     @Test
260     fun testBackgroundWorkErrorReporting() {
261         var testFinished = false
262         val exception = RuntimeException("x")
263         try {
264             runBlockingTestOnTestScope {
265                 backgroundScope.launch {
266                     throw exception
267                 }
268                 delay(1000)
269                 testFinished = true
270             }
271             fail("unreached")
272         } catch (e: Throwable) {
273             assertSame(e, exception)
274             assertTrue(testFinished)
275         }
276     }
277 
278     @Test
279     fun testBackgroundWorkFinalizing() {
280         var taskEnded = 0
281         val nTasks = 10
282         try {
283             runBlockingTestOnTestScope {
284                 repeat(nTasks) {
285                     backgroundScope.launch {
286                         try {
287                             while (true) {
288                                 delay(1)
289                             }
290                         } finally {
291                             ++taskEnded
292                             if (taskEnded <= 2)
293                                 throw TestException()
294                         }
295                     }
296                 }
297                 delay(100)
298                 throw TestException()
299             }
300             fail("unreached")
301         } catch (e: TestException) {
302             assertEquals(2, e.suppressedExceptions.size)
303             assertEquals(nTasks, taskEnded)
304         }
305     }
306 
307     @Test
308     fun testExampleBackgroundJob1() = runBlockingTestOnTestScope {
309         val myFlow = flow {
310             yield()
311             var i = 0
312             while (true) {
313                 emit(++i)
314                 delay(1)
315             }
316         }
317         val stateFlow = myFlow.stateIn(backgroundScope, SharingStarted.Eagerly, 0)
318         var j = 0
319         repeat(100) {
320             assertEquals(j++, stateFlow.value)
321             delay(1)
322         }
323     }
324 
325     @Test
326     fun testExampleBackgroundJob2() = runBlockingTestOnTestScope {
327         val channel = Channel<Int>()
328         backgroundScope.launch {
329             var i = 0
330             while (true) {
331                 channel.send(i++)
332             }
333         }
334         repeat(100) {
335             assertEquals(it, channel.receive())
336         }
337     }
338 
339     @Test
340     fun testAsyncFailureInBackgroundReported() =
341         try {
342             runBlockingTestOnTestScope {
343                 backgroundScope.async {
344                     throw TestException("x")
345                 }
346                 backgroundScope.produce<Unit> {
347                     throw TestException("y")
348                 }
349                 delay(1)
350                 throw TestException("z")
351             }
352             fail("unreached")
353         } catch (e: TestException) {
354             assertEquals("z", e.message)
355             assertEquals(setOf("x", "y"), e.suppressedExceptions.map { it.message }.toSet())
356         }
357 
358     @Test
359     fun testNoDuplicateExceptions() =
360         try {
361             runBlockingTestOnTestScope {
362                 backgroundScope.launch {
363                     throw TestException("x")
364                 }
365                 delay(1)
366                 throw TestException("y")
367             }
368             fail("unreached")
369         } catch (e: TestException) {
370             assertEquals("y", e.message)
371             assertEquals(listOf("x"), e.suppressedExceptions.map { it.message })
372         }
373 }
374