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