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