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 13 class RunTestTest { 14 15 /** Tests that [withContext] that sends work to other threads works in [runTest]. */ 16 @Test 17 fun testWithContextDispatching() = runTest { 18 var counter = 0 19 withContext(Dispatchers.Default) { 20 counter += 1 21 } 22 assertEquals(counter, 1) 23 } 24 25 /** Tests that joining [GlobalScope.launch] works in [runTest]. */ 26 @Test 27 fun testJoiningForkedJob() = runTest { 28 var counter = 0 29 val job = GlobalScope.launch { 30 counter += 1 31 } 32 job.join() 33 assertEquals(counter, 1) 34 } 35 36 /** Tests [suspendCoroutine] not failing [runTest]. */ 37 @Test 38 fun testSuspendCoroutine() = runTest { 39 val answer = suspendCoroutine<Int> { 40 it.resume(42) 41 } 42 assertEquals(42, answer) 43 } 44 45 /** Tests that [runTest] attempts to detect it being run inside another [runTest] and failing in such scenarios. */ 46 @Test 47 fun testNestedRunTestForbidden() = runTest { 48 assertFailsWith<IllegalStateException> { 49 runTest { } 50 } 51 } 52 53 /** Tests that even the dispatch timeout of `0` is fine if all the dispatches go through the same scheduler. */ 54 @Test 55 fun testRunTestWithZeroTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) { 56 // below is some arbitrary concurrent code where all dispatches go through the same scheduler. 57 launch { 58 delay(2000) 59 } 60 val deferred = async { 61 val job = launch(StandardTestDispatcher(testScheduler)) { 62 launch { 63 delay(500) 64 } 65 delay(1000) 66 } 67 job.join() 68 } 69 deferred.await() 70 } 71 72 /** Tests that a dispatch timeout of `0` will fail the test if there are some dispatches outside the scheduler. */ 73 @Test 74 @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native 75 fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn -> 76 assertFailsWith<UncompletedCoroutinesError> { fn() } 77 }) { 78 runTest(dispatchTimeoutMs = 0) { 79 withContext(Dispatchers.Default) { 80 delay(10) 81 3 82 } 83 fail("shouldn't be reached") 84 } 85 } 86 87 /** Tests that too low of a dispatch timeout causes crashes. */ 88 @Test 89 @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native 90 fun testRunTestWithSmallTimeout() = testResultMap({ fn -> 91 assertFailsWith<UncompletedCoroutinesError> { fn() } 92 }) { 93 runTest(dispatchTimeoutMs = 100) { 94 withContext(Dispatchers.Default) { 95 delay(10000) 96 3 97 } 98 fail("shouldn't be reached") 99 } 100 } 101 102 /** Tests that, on timeout, the names of the active coroutines are listed, 103 * whereas the names of the completed ones are not. */ 104 @Test 105 @NoJs 106 @NoNative 107 fun testListingActiveCoroutinesOnTimeout(): TestResult { 108 val name1 = "GoodUniqueName" 109 val name2 = "BadUniqueName" 110 return testResultMap({ 111 try { 112 it() 113 fail("unreached") 114 } catch (e: UncompletedCoroutinesError) { 115 assertTrue((e.message ?: "").contains(name1)) 116 assertFalse((e.message ?: "").contains(name2)) 117 } 118 }) { 119 runTest(dispatchTimeoutMs = 10) { 120 launch(CoroutineName(name1)) { 121 CompletableDeferred<Unit>().await() 122 } 123 launch(CoroutineName(name2)) { 124 } 125 } 126 } 127 } 128 129 /** Tests that the [UncompletedCoroutinesError] suppresses an exception with which the coroutine is completing. */ 130 @Test 131 fun testFailureWithPendingCoroutine() = testResultMap({ 132 try { 133 it() 134 fail("unreached") 135 } catch (e: UncompletedCoroutinesError) { 136 @Suppress("INVISIBLE_MEMBER") 137 val suppressed = unwrap(e).suppressedExceptions 138 assertEquals(1, suppressed.size) 139 assertIs<TestException>(suppressed[0]).also { 140 assertEquals("A", it.message) 141 } 142 } 143 }) { 144 runTest(dispatchTimeoutMs = 10) { 145 launch { 146 withContext(NonCancellable) { 147 awaitCancellation() 148 } 149 } 150 yield() 151 throw TestException("A") 152 } 153 } 154 155 /** Tests that real delays can be accounted for with a large enough dispatch timeout. */ 156 @Test 157 fun testRunTestWithLargeTimeout() = runTest(dispatchTimeoutMs = 5000) { 158 withContext(Dispatchers.Default) { 159 delay(50) 160 } 161 } 162 163 /** Tests uncaught exceptions being suppressed by the dispatch timeout error. */ 164 @Test 165 @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native 166 fun testRunTestTimingOutAndThrowing() = testResultMap({ fn -> 167 try { 168 fn() 169 fail("unreached") 170 } catch (e: UncompletedCoroutinesError) { 171 @Suppress("INVISIBLE_MEMBER") 172 val suppressed = unwrap(e).suppressedExceptions 173 assertEquals(1, suppressed.size) 174 assertIs<TestException>(suppressed[0]).also { 175 assertEquals("A", it.message) 176 } 177 } 178 }) { 179 runTest(dispatchTimeoutMs = 1) { 180 coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A")) 181 withContext(Dispatchers.Default) { 182 delay(10000) 183 3 184 } 185 fail("shouldn't be reached") 186 } 187 } 188 189 /** Tests that passing invalid contexts to [runTest] causes it to fail (on JS, without forking). */ 190 @Test 191 fun testRunTestWithIllegalContext() { 192 for (ctx in TestScopeTest.invalidContexts) { 193 assertFailsWith<IllegalArgumentException> { 194 runTest(ctx) { } 195 } 196 } 197 } 198 199 /** Tests that throwing exceptions in [runTest] fails the test with them. */ 200 @Test 201 fun testThrowingInRunTestBody() = testResultMap({ 202 assertFailsWith<RuntimeException> { it() } 203 }) { 204 runTest { 205 throw RuntimeException() 206 } 207 } 208 209 /** Tests that throwing exceptions in pending tasks [runTest] fails the test with them. */ 210 @Test 211 fun testThrowingInRunTestPendingTask() = testResultMap({ 212 assertFailsWith<RuntimeException> { it() } 213 }) { 214 runTest { 215 launch { 216 delay(SLOW) 217 throw RuntimeException() 218 } 219 } 220 } 221 222 @Test 223 fun reproducer2405() = runTest { 224 val dispatcher = StandardTestDispatcher(testScheduler) 225 var collectedError = false 226 withContext(dispatcher) { 227 flow { emit(1) } 228 .combine( 229 flow<String> { throw IllegalArgumentException() } 230 ) { int, string -> int.toString() + string } 231 .catch { emit("error") } 232 .collect { 233 assertEquals("error", it) 234 collectedError = true 235 } 236 } 237 assertTrue(collectedError) 238 } 239 240 /** Tests that, once the test body has thrown, the child coroutines are cancelled. */ 241 @Test 242 fun testChildrenCancellationOnTestBodyFailure(): TestResult { 243 var job: Job? = null 244 return testResultMap({ 245 assertFailsWith<AssertionError> { it() } 246 assertTrue(job!!.isCancelled) 247 }) { 248 runTest { 249 job = launch { 250 while (true) { 251 delay(1000) 252 } 253 } 254 throw AssertionError() 255 } 256 } 257 } 258 259 /** Tests that [runTest] reports [TimeoutCancellationException]. */ 260 @Test 261 fun testTimeout() = testResultMap({ 262 assertFailsWith<TimeoutCancellationException> { it() } 263 }) { 264 runTest { 265 withTimeout(50) { 266 launch { 267 delay(1000) 268 } 269 } 270 } 271 } 272 273 /** Checks that [runTest] throws the root cause and not [JobCancellationException] when a child coroutine throws. */ 274 @Test 275 fun testRunTestThrowsRootCause() = testResultMap({ 276 assertFailsWith<TestException> { it() } 277 }) { 278 runTest { 279 launch { 280 throw TestException() 281 } 282 } 283 } 284 285 /** Tests that [runTest] completes its job. */ 286 @Test 287 fun testCompletesOwnJob(): TestResult { 288 var handlerCalled = false 289 return testResultMap({ 290 it() 291 assertTrue(handlerCalled) 292 }) { 293 runTest { 294 coroutineContext.job.invokeOnCompletion { 295 handlerCalled = true 296 } 297 } 298 } 299 } 300 301 /** Tests that [runTest] doesn't complete the job that was passed to it as an argument. */ 302 @Test 303 fun testDoesNotCompleteGivenJob(): TestResult { 304 var handlerCalled = false 305 val job = Job() 306 job.invokeOnCompletion { 307 handlerCalled = true 308 } 309 return testResultMap({ 310 it() 311 assertFalse(handlerCalled) 312 assertEquals(0, job.children.filter { it.isActive }.count()) 313 }) { 314 runTest(job) { 315 assertTrue(coroutineContext.job in job.children) 316 } 317 } 318 } 319 320 /** Tests that, when the test body fails, the reported exceptions are suppressed. */ 321 @Test 322 fun testSuppressedExceptions() = testResultMap({ 323 try { 324 it() 325 fail("should not be reached") 326 } catch (e: TestException) { 327 assertEquals("w", e.message) 328 val suppressed = e.suppressedExceptions + 329 (e.suppressedExceptions.firstOrNull()?.suppressedExceptions ?: emptyList()) 330 assertEquals(3, suppressed.size) 331 assertEquals("x", suppressed[0].message) 332 assertEquals("y", suppressed[1].message) 333 assertEquals("z", suppressed[2].message) 334 } 335 }) { 336 runTest { 337 launch(SupervisorJob()) { throw TestException("x") } 338 launch(SupervisorJob()) { throw TestException("y") } 339 launch(SupervisorJob()) { throw TestException("z") } 340 throw TestException("w") 341 } 342 } 343 344 /** Tests that [TestCoroutineScope.runTest] does not inherit the exception handler and works. */ 345 @Test 346 fun testScopeRunTestExceptionHandler(): TestResult { 347 val scope = TestScope() 348 return testResultMap({ 349 try { 350 it() 351 fail("should not be reached") 352 } catch (e: TestException) { 353 // expected 354 } 355 }) { 356 scope.runTest { 357 launch(SupervisorJob()) { throw TestException("x") } 358 } 359 } 360 } 361 362 /** 363 * Tests that if the main coroutine is completed without a dispatch, [runTest] will not consider this to be 364 * inactivity. 365 * 366 * The test will hang if this is not the case. 367 */ 368 @Test 369 fun testCoroutineCompletingWithoutDispatch() = runTest(dispatchTimeoutMs = Long.MAX_VALUE) { 370 launch(Dispatchers.Default) { delay(100) } 371 } 372 } 373