blob: 806592079c9d90275935a934d5694492e3f30de9 [file] [log] [blame]
Steve Elliottca095be2022-07-25 14:26:10 +00001/*
2 * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5package kotlinx.coroutines.test
6
7import kotlinx.coroutines.*
8import kotlinx.coroutines.channels.*
9import kotlinx.coroutines.flow.*
10import kotlin.test.*
11
12/** Copy of [RunTestTest], but for [runBlockingTestOnTestScope], where applicable. */
13@Suppress("DEPRECATION")
14class 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}