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