1 /* 2 * Copyright 2016-2020 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.coroutines.* 9 10 /** 11 * A scope which provides detailed control over the execution of coroutines for tests. 12 */ 13 @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0 14 public interface TestCoroutineScope: CoroutineScope, UncaughtExceptionCaptor, DelayController { 15 /** 16 * Call after the test completes. 17 * Calls [UncaughtExceptionCaptor.cleanupTestCoroutines] and [DelayController.cleanupTestCoroutines]. 18 * 19 * @throws Throwable the first uncaught exception, if there are any uncaught exceptions. 20 * @throws UncompletedCoroutinesError if any pending tasks are active, however it will not throw for suspended 21 * coroutines. 22 */ cleanupTestCoroutinesnull23 public override fun cleanupTestCoroutines() 24 } 25 26 private class TestCoroutineScopeImpl ( 27 override val coroutineContext: CoroutineContext 28 ): 29 TestCoroutineScope, 30 UncaughtExceptionCaptor by coroutineContext.uncaughtExceptionCaptor, 31 DelayController by coroutineContext.delayController 32 { 33 override fun cleanupTestCoroutines() { 34 coroutineContext.uncaughtExceptionCaptor.cleanupTestCoroutines() 35 coroutineContext.delayController.cleanupTestCoroutines() 36 } 37 } 38 39 /** 40 * A scope which provides detailed control over the execution of coroutines for tests. 41 * 42 * If the provided context does not provide a [ContinuationInterceptor] (Dispatcher) or [CoroutineExceptionHandler], the 43 * scope adds [TestCoroutineDispatcher] and [TestCoroutineExceptionHandler] automatically. 44 * 45 * @param context an optional context that MAY provide [UncaughtExceptionCaptor] and/or [DelayController] 46 */ 47 @Suppress("FunctionName") 48 @ExperimentalCoroutinesApi // Since 1.2.1, tentatively till 1.3.0 TestCoroutineScopenull49public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope { 50 var safeContext = context 51 if (context[ContinuationInterceptor] == null) safeContext += TestCoroutineDispatcher() 52 if (context[CoroutineExceptionHandler] == null) safeContext += TestCoroutineExceptionHandler() 53 return TestCoroutineScopeImpl(safeContext) 54 } 55 56 private inline val CoroutineContext.uncaughtExceptionCaptor: UncaughtExceptionCaptor 57 get() { 58 val handler = this[CoroutineExceptionHandler] 59 return handler as? UncaughtExceptionCaptor ?: throw IllegalArgumentException( 60 "TestCoroutineScope requires a UncaughtExceptionCaptor such as " + 61 "TestCoroutineExceptionHandler as the CoroutineExceptionHandler" 62 ) 63 } 64 65 private inline val CoroutineContext.delayController: DelayController 66 get() { 67 val handler = this[ContinuationInterceptor] 68 return handler as? DelayController ?: throw IllegalArgumentException( 69 "TestCoroutineScope requires a DelayController such as TestCoroutineDispatcher as " + 70 "the ContinuationInterceptor (Dispatcher)" 71 ) 72 } 73