1 /* 2 * 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 kotlin.test.* 9 import kotlin.time.* 10 import kotlin.time.Duration.Companion.seconds 11 12 class TestCoroutineSchedulerTest { 13 /** Tests that `TestCoroutineScheduler` attempts to detect if there are several instances of it. */ 14 @Test <lambda>null15 fun testContextElement() = runTest { 16 assertFailsWith<IllegalStateException> { 17 withContext(StandardTestDispatcher()) { 18 } 19 } 20 } 21 22 /** Tests that, as opposed to [DelayController.advanceTimeBy] or [TestCoroutineScope.advanceTimeBy], 23 * [TestCoroutineScheduler.advanceTimeBy] doesn't run the tasks scheduled at the target moment. */ 24 @Test <lambda>null25 fun testAdvanceTimeByDoesNotRunCurrent() = runTest { 26 var entered = false 27 launch { 28 delay(15) 29 entered = true 30 } 31 testScheduler.advanceTimeBy(15) 32 assertFalse(entered) 33 testScheduler.runCurrent() 34 assertTrue(entered) 35 } 36 37 /** Tests that [TestCoroutineScheduler.advanceTimeBy] doesn't accept negative delays. */ 38 @Test testAdvanceTimeByWithNegativeDelaynull39 fun testAdvanceTimeByWithNegativeDelay() { 40 val scheduler = TestCoroutineScheduler() 41 assertFailsWith<IllegalArgumentException> { 42 scheduler.advanceTimeBy(-1) 43 } 44 } 45 46 /** Tests that if [TestCoroutineScheduler.advanceTimeBy] encounters an arithmetic overflow, all the tasks scheduled 47 * until the moment [Long.MAX_VALUE] get run. */ 48 @Test <lambda>null49 fun testAdvanceTimeByEnormousDelays() = forTestDispatchers { 50 assertRunsFast { 51 with (TestScope(it)) { 52 launch { 53 val initialDelay = 10L 54 delay(initialDelay) 55 assertEquals(initialDelay, currentTime) 56 var enteredInfinity = false 57 launch { 58 delay(Long.MAX_VALUE - 1) // delay(Long.MAX_VALUE) does nothing 59 assertEquals(Long.MAX_VALUE, currentTime) 60 enteredInfinity = true 61 } 62 var enteredNearInfinity = false 63 launch { 64 delay(Long.MAX_VALUE - initialDelay - 1) 65 assertEquals(Long.MAX_VALUE - 1, currentTime) 66 enteredNearInfinity = true 67 } 68 testScheduler.advanceTimeBy(Long.MAX_VALUE) 69 assertFalse(enteredInfinity) 70 assertTrue(enteredNearInfinity) 71 assertEquals(Long.MAX_VALUE, currentTime) 72 testScheduler.runCurrent() 73 assertTrue(enteredInfinity) 74 } 75 testScheduler.advanceUntilIdle() 76 } 77 } 78 } 79 80 /** Tests the basic functionality of [TestCoroutineScheduler.advanceTimeBy]. */ 81 @Test <lambda>null82 fun testAdvanceTimeBy() = runTest { 83 assertRunsFast { 84 var stage = 1 85 launch { 86 delay(1_000) 87 assertEquals(1_000, currentTime) 88 stage = 2 89 delay(500) 90 assertEquals(1_500, currentTime) 91 stage = 3 92 delay(501) 93 assertEquals(2_001, currentTime) 94 stage = 4 95 } 96 assertEquals(1, stage) 97 assertEquals(0, currentTime) 98 advanceTimeBy(2_000) 99 assertEquals(3, stage) 100 assertEquals(2_000, currentTime) 101 advanceTimeBy(2) 102 assertEquals(4, stage) 103 assertEquals(2_002, currentTime) 104 } 105 } 106 107 /** Tests the basic functionality of [TestCoroutineScheduler.runCurrent]. */ 108 @Test <lambda>null109 fun testRunCurrent() = runTest { 110 var stage = 0 111 launch { 112 delay(1) 113 ++stage 114 delay(1) 115 stage += 10 116 } 117 launch { 118 delay(1) 119 ++stage 120 delay(1) 121 stage += 10 122 } 123 testScheduler.advanceTimeBy(1) 124 assertEquals(0, stage) 125 runCurrent() 126 assertEquals(2, stage) 127 testScheduler.advanceTimeBy(1) 128 assertEquals(2, stage) 129 runCurrent() 130 assertEquals(22, stage) 131 } 132 133 /** Tests that [TestCoroutineScheduler.runCurrent] will not run new tasks after the current time has advanced. */ 134 @Test <lambda>null135 fun testRunCurrentNotDrainingQueue() = forTestDispatchers { 136 assertRunsFast { 137 val scheduler = it.scheduler 138 val scope = TestScope(it) 139 var stage = 1 140 scope.launch { 141 delay(SLOW) 142 launch { 143 delay(SLOW) 144 stage = 3 145 } 146 scheduler.advanceTimeBy(SLOW) 147 stage = 2 148 } 149 scheduler.advanceTimeBy(SLOW) 150 assertEquals(1, stage) 151 scheduler.runCurrent() 152 assertEquals(2, stage) 153 scheduler.runCurrent() 154 assertEquals(3, stage) 155 } 156 } 157 158 /** Tests that [TestCoroutineScheduler.advanceUntilIdle] doesn't hang when itself running in a scheduler task. */ 159 @Test <lambda>null160 fun testNestedAdvanceUntilIdle() = forTestDispatchers { 161 assertRunsFast { 162 val scheduler = it.scheduler 163 val scope = TestScope(it) 164 var executed = false 165 scope.launch { 166 launch { 167 delay(SLOW) 168 executed = true 169 } 170 scheduler.advanceUntilIdle() 171 } 172 scheduler.advanceUntilIdle() 173 assertTrue(executed) 174 } 175 } 176 177 /** Tests [yield] scheduling tasks for future execution and not executing immediately. */ 178 @Test <lambda>null179 fun testYield() = forTestDispatchers { 180 val scope = TestScope(it) 181 var stage = 0 182 scope.launch { 183 yield() 184 assertEquals(1, stage) 185 stage = 2 186 } 187 scope.launch { 188 yield() 189 assertEquals(2, stage) 190 stage = 3 191 } 192 assertEquals(0, stage) 193 stage = 1 194 scope.runCurrent() 195 } 196 197 /** Tests that dispatching the delayed tasks is ordered by their waking times. */ 198 @Test <lambda>null199 fun testDelaysPriority() = forTestDispatchers { 200 val scope = TestScope(it) 201 var lastMeasurement = 0L 202 fun checkTime(time: Long) { 203 assertTrue(lastMeasurement < time) 204 assertEquals(time, scope.currentTime) 205 lastMeasurement = scope.currentTime 206 } 207 scope.launch { 208 launch { 209 delay(100) 210 checkTime(100) 211 val deferred = async { 212 delay(70) 213 checkTime(170) 214 } 215 delay(1) 216 checkTime(101) 217 deferred.await() 218 delay(1) 219 checkTime(171) 220 } 221 launch { 222 delay(200) 223 checkTime(200) 224 } 225 launch { 226 delay(150) 227 checkTime(150) 228 delay(22) 229 checkTime(172) 230 } 231 delay(201) 232 } 233 scope.advanceUntilIdle() 234 checkTime(201) 235 } 236 TestScopenull237 private fun TestScope.checkTimeout( 238 timesOut: Boolean, timeoutMillis: Long = SLOW, block: suspend () -> Unit 239 ) = assertRunsFast { 240 var caughtException = false 241 asSpecificImplementation().enter() 242 launch { 243 try { 244 withTimeout(timeoutMillis) { 245 block() 246 } 247 } catch (e: TimeoutCancellationException) { 248 caughtException = true 249 } 250 } 251 advanceUntilIdle() 252 asSpecificImplementation().leave().throwAll() 253 if (timesOut) 254 assertTrue(caughtException) 255 else 256 assertFalse(caughtException) 257 } 258 259 /** Tests that timeouts get triggered. */ 260 @Test <lambda>null261 fun testSmallTimeouts() = forTestDispatchers { 262 val scope = TestScope(it) 263 scope.checkTimeout(true) { 264 val half = SLOW / 2 265 delay(half) 266 delay(SLOW - half) 267 } 268 } 269 270 /** Tests that timeouts don't get triggered if the code finishes in time. */ 271 @Test <lambda>null272 fun testLargeTimeouts() = forTestDispatchers { 273 val scope = TestScope(it) 274 scope.checkTimeout(false) { 275 val half = SLOW / 2 276 delay(half) 277 delay(SLOW - half - 1) 278 } 279 } 280 281 /** Tests that timeouts get triggered if the code fails to finish in time asynchronously. */ 282 @Test <lambda>null283 fun testSmallAsynchronousTimeouts() = forTestDispatchers { 284 val scope = TestScope(it) 285 val deferred = CompletableDeferred<Unit>() 286 scope.launch { 287 val half = SLOW / 2 288 delay(half) 289 delay(SLOW - half) 290 deferred.complete(Unit) 291 } 292 scope.checkTimeout(true) { 293 deferred.await() 294 } 295 } 296 297 /** Tests that timeouts don't get triggered if the code finishes in time, even if it does so asynchronously. */ 298 @Test <lambda>null299 fun testLargeAsynchronousTimeouts() = forTestDispatchers { 300 val scope = TestScope(it) 301 val deferred = CompletableDeferred<Unit>() 302 scope.launch { 303 val half = SLOW / 2 304 delay(half) 305 delay(SLOW - half - 1) 306 deferred.complete(Unit) 307 } 308 scope.checkTimeout(false) { 309 deferred.await() 310 } 311 } 312 313 @Test 314 @ExperimentalTime <lambda>null315 fun testAdvanceTimeSource() = runTest { 316 val expected = 1.seconds 317 val actual = testTimeSource.measureTime { 318 delay(expected) 319 } 320 assertEquals(expected, actual) 321 } 322 forTestDispatchersnull323 private fun forTestDispatchers(block: (TestDispatcher) -> Unit): Unit = 324 @Suppress("DEPRECATION") 325 listOf( 326 StandardTestDispatcher(), 327 UnconfinedTestDispatcher() 328 ).forEach { 329 try { 330 block(it) 331 } catch (e: Throwable) { 332 throw RuntimeException("Test failed for dispatcher $it", e) 333 } 334 } 335 } 336