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