From 82879fd9941fef921e589b9bff6df3cdd3292535 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 8 Nov 2021 12:42:09 +0300 Subject: [PATCH 1/3] Eagerly enter `launch` and `async` blocks with unconfined dispatcher. Also, fix `Dispatchers.Main` not delegating `Delay` methods and discover that, on JS, `Dispatchers.Main` gets reset during the test if it is reset in `AfterTest`. --- .../common/src/TestBuilders.kt | 2 +- .../common/src/TestCoroutineDispatchers.kt | 55 +++++++++++++++---- .../common/src/TestCoroutineScope.kt | 9 +-- .../common/src/internal/TestMainDispatcher.kt | 11 +++- .../common/test/StandardTestDispatcherTest.kt | 13 +++++ .../common/test/TestDispatchersTest.kt | 44 ++++++++++++++- .../test/UnconfinedTestDispatcherTest.kt | 30 ++++++++++ 7 files changed, 141 insertions(+), 23 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index db15bbfdbf..e1f7c0a908 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -219,7 +219,7 @@ public fun runTest( return createTestResult { /** TODO: moving this [AbstractCoroutine.start] call outside [createTestResult] fails on Native with * [TestCoroutineDispatcher], because the event loop is not started. */ - testScope.start(CoroutineStart.DEFAULT, testScope) { + testScope.start(CoroutineStart.UNDISPATCHED, testScope) { testBody() } var completed = false diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt index 6e18bf348e..0152c9a21b 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt @@ -7,6 +7,8 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* +import kotlinx.coroutines.test.internal.* +import kotlinx.coroutines.test.internal.TestMainDispatcher import kotlin.coroutines.* /** @@ -15,10 +17,32 @@ import kotlin.coroutines.* * This dispatcher is similar to [Dispatchers.Unconfined]: the tasks that it executes are not confined to any particular * thread and form an event loop; it's different in that it skips delays, as all [TestDispatcher]s do. * + * Like [Dispatchers.Unconfined], this one does not provide guarantees about the execution order when several coroutines + * are queued in this dispatcher. However, we ensure that the [launch] and [async] blocks at the top level of [runTest] + * are entered eagerly. This allows launching child coroutines and not calling [runCurrent] for them to start executing. + * + * ``` + * @Test + * fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) { + * var entered = false + * val deferred = CompletableDeferred() + * var completed = false + * launch { + * entered = true + * deferred.await() + * completed = true + * } + * assertTrue(entered) // `entered = true` already executed. + * assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued. + * deferred.complete(Unit) // resume the coroutine. + * assertTrue(completed) // now the child coroutine is immediately completed. + * } + * ``` + * * Using this [TestDispatcher] can greatly simplify writing tests where it's not important which thread is used when and * in which order the queued coroutines are executed. - * The typical use case for this is launching child coroutines that are resumed immediately, without going through a - * dispatch; this can be helpful for testing [Channel] and [StateFlow] usages. + * Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without + * going through a dispatch; this can be helpful for testing [Channel] and [StateFlow] usages. * * ``` * @Test @@ -40,14 +64,16 @@ import kotlin.coroutines.* * } * ``` * - * However, please be aware that, like [Dispatchers.Unconfined], this is a specific dispatcher with execution order + * Please be aware that, like [Dispatchers.Unconfined], this is a specific dispatcher with execution order * guarantees that are unusual and not shared by most other dispatchers, so it can only be used reliably for testing * functionality, not the specific order of actions. * See [Dispatchers.Unconfined] for a discussion of the execution order guarantees. * * In order to support delay skipping, this dispatcher is linked to a [TestCoroutineScheduler], which is used to control - * the virtual time and can be shared among many test dispatchers. If no [scheduler] is passed as an argument, a new one - * is created. + * the virtual time and can be shared among many test dispatchers. + * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a + * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if + * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created. * * Additionally, [name] can be set to distinguish each dispatcher instance when debugging. * @@ -56,14 +82,14 @@ import kotlin.coroutines.* @ExperimentalCoroutinesApi @Suppress("FunctionName") public fun UnconfinedTestDispatcher( - scheduler: TestCoroutineScheduler = TestCoroutineScheduler(), + scheduler: TestCoroutineScheduler? = null, name: String? = null -): TestDispatcher = UnconfinedTestDispatcherImpl(scheduler, name) +): TestDispatcher = UnconfinedTestDispatcherImpl(scheduler ?: mainTestScheduler ?: TestCoroutineScheduler(), name) private class UnconfinedTestDispatcherImpl( override val scheduler: TestCoroutineScheduler, private val name: String? = null -): TestDispatcher() { +) : TestDispatcher() { override fun isDispatchNeeded(context: CoroutineContext): Boolean = false @@ -103,7 +129,9 @@ private class UnconfinedTestDispatcherImpl( * run these pending tasks, which will block until there are no more tasks scheduled at this point in time, or, when * inside [runTest], call [yield] to yield the (only) thread used by [runTest] to the newly-launched coroutines. * - * If a [scheduler] is not passed as an argument, a new one is created. + * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a + * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if + * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created. * * One can additionally pass a [name] in order to more easily distinguish this dispatcher during debugging. * @@ -111,14 +139,14 @@ private class UnconfinedTestDispatcherImpl( */ @Suppress("FunctionName") public fun StandardTestDispatcher( - scheduler: TestCoroutineScheduler = TestCoroutineScheduler(), + scheduler: TestCoroutineScheduler? = null, name: String? = null -): TestDispatcher = StandardTestDispatcherImpl(scheduler, name) +): TestDispatcher = StandardTestDispatcherImpl(scheduler ?: mainTestScheduler ?: TestCoroutineScheduler(), name) private class StandardTestDispatcherImpl( override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler(), private val name: String? = null -): TestDispatcher() { +) : TestDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { checkSchedulerInContext(scheduler, context) @@ -127,3 +155,6 @@ private class StandardTestDispatcherImpl( override fun toString(): String = "${name ?: "StandardTestDispatcher"}[scheduler=$scheduler]" } + +private val mainTestScheduler + get() = ((Dispatchers.Main as? TestMainDispatcher)?.delegate as? TestDispatcher)?.scheduler \ No newline at end of file diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt index f7b38dcc01..01a6aa4b88 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScope.kt @@ -6,8 +6,6 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.test.internal.* -import kotlinx.coroutines.test.internal.TestMainDispatcher import kotlin.coroutines.* /** @@ -172,12 +170,7 @@ public fun createTestCoroutineScope(context: CoroutineContext = EmptyCoroutineCo } dispatcher } - null -> { - val mainDispatcherScheduler = - ((Dispatchers.Main as? TestMainDispatcher)?.delegate as? TestDispatcher)?.scheduler - scheduler = context[TestCoroutineScheduler] ?: mainDispatcherScheduler ?: TestCoroutineScheduler() - StandardTestDispatcher(scheduler) - } + null -> StandardTestDispatcher(context[TestCoroutineScheduler]).also { scheduler = it.scheduler } else -> throw IllegalArgumentException("Dispatcher must implement TestDispatcher: $dispatcher") } var scope: TestCoroutineScopeImpl? = null diff --git a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt index f9225584e3..3810c06536 100644 --- a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt @@ -12,10 +12,13 @@ import kotlin.coroutines.* */ internal class TestMainDispatcher(var delegate: CoroutineDispatcher): MainCoroutineDispatcher(), - Delay by (delegate as? Delay ?: defaultDelay) + Delay { private val mainDispatcher = delegate // the initial value passed to the constructor + private val delay + get() = delegate as? Delay ?: defaultDelay + override val immediate: MainCoroutineDispatcher get() = (delegate as? MainCoroutineDispatcher)?.immediate ?: this @@ -28,6 +31,12 @@ internal class TestMainDispatcher(var delegate: CoroutineDispatcher): fun resetDispatcher() { delegate = mainDispatcher } + + override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) = + delay.scheduleResumeAfterDelay(timeMillis, continuation) + + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = + delay.invokeOnTimeout(timeMillis, block, context) } @Suppress("INVISIBLE_MEMBER") diff --git a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt index 7e8a6ad158..d00b50d90c 100644 --- a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt +++ b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt @@ -54,4 +54,17 @@ class StandardTestDispatcherTest: OrderedExecutionTestBase() { expect(5) }.void() + /** Tests that the [TestCoroutineScheduler] used for [Dispatchers.Main] gets used by default. */ + @Test + fun testSchedulerReuse() { + val dispatcher1 = StandardTestDispatcher() + Dispatchers.setMain(dispatcher1) + try { + val dispatcher2 = StandardTestDispatcher() + assertSame(dispatcher1.scheduler, dispatcher2.scheduler) + } finally { + Dispatchers.resetMain() + } + } + } \ No newline at end of file diff --git a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt index 0c3cac71ae..cce705a371 100644 --- a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt +++ b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* +import kotlinx.coroutines.test.internal.* import kotlin.coroutines.* import kotlin.test.* @@ -11,9 +12,48 @@ class TestDispatchersTest: OrderedExecutionTestBase() { @BeforeTest fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + } + + @AfterTest + fun tearDown() { Dispatchers.resetMain() } + /** Tests that asynchronous execution of tests does not happen concurrently with [AfterTest] (in fact, it does). */ + @Ignore // fails on JS. + @Test + fun testMainMocking() = runTest { + val mainAtStart = mainTestDispatcher + assertNotNull(mainAtStart) + withContext(Dispatchers.Main) { + delay(10) + } + withContext(Dispatchers.Default) { + delay(10) + } + withContext(Dispatchers.Main) { + delay(10) + } + assertSame(mainAtStart, mainTestDispatcher) + } + + /** Tests that the mocked [Dispatchers.Main] correctly forwards [Delay] methods. */ + @Test + fun testMockedMainImplementsDelay() = runTest { + val main = Dispatchers.Main + withContext(main) { + delay(10) + } + withContext(Dispatchers.Default) { + delay(10) + } + withContext(main) { + delay(10) + } + } + + /** Tests that [Distpachers.setMain] fails when called with [Dispatchers.Main]. */ @Test fun testSelfSet() { assertFailsWith { Dispatchers.setMain(Dispatchers.Main) } @@ -55,4 +95,6 @@ class TestDispatchersTest: OrderedExecutionTestBase() { block.run() } } -} \ No newline at end of file +} + +private val mainTestDispatcher get() = ((Dispatchers.Main as? TestMainDispatcher)?.delegate as? TestDispatcher) diff --git a/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt index 3e1c48c717..719698e843 100644 --- a/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt +++ b/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt @@ -134,4 +134,34 @@ class UnconfinedTestDispatcherTest { assertEquals(listOf(0, 1, 2, 3), values) } + /** Tests that child coroutines are eagerly entered. */ + @Test + fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) { + var entered = false + val deferred = CompletableDeferred() + var completed = false + launch { + entered = true + deferred.await() + completed = true + } + assertTrue(entered) // `entered = true` already executed. + assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued. + deferred.complete(Unit) // resume the coroutine. + assertTrue(completed) // now the child coroutine is immediately completed. + } + + /** Tests that the [TestCoroutineScheduler] used for [Dispatchers.Main] gets used by default. */ + @Test + fun testSchedulerReuse() { + val dispatcher1 = StandardTestDispatcher() + Dispatchers.setMain(dispatcher1) + try { + val dispatcher2 = UnconfinedTestDispatcher() + assertSame(dispatcher1.scheduler, dispatcher2.scheduler) + } finally { + Dispatchers.resetMain() + } + } + } \ No newline at end of file From caacdc5e69b3e80903a68f7379d3223dc0e3f68f Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 8 Nov 2021 15:29:11 +0300 Subject: [PATCH 2/3] Mark `DefaultDelay` as `PublishedApi` --- kotlinx-coroutines-core/common/src/CoroutineContext.common.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt index e17833218f..da094e152d 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt @@ -12,6 +12,7 @@ import kotlin.coroutines.* */ public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext +@PublishedApi @Suppress("PropertyName") internal expect val DefaultDelay: Delay From 61461a3d0a1715beb7cfc3bb2411b14253a84cc4 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Mon, 8 Nov 2021 17:17:40 +0300 Subject: [PATCH 3/3] Improve platform-specific tests in the test module --- .../common/test/Helpers.kt | 6 +++ .../common/test/RunTestTest.kt | 36 +++++++++--------- .../common/test/TestDispatchersTest.kt | 4 +- .../js/test/FailingTests.kt | 37 +++++++++++++++++++ kotlinx-coroutines-test/js/test/Helpers.kt | 4 ++ .../native/test/FailingTests.kt | 25 +++++++++++++ .../native/test/Helpers.kt | 4 ++ 7 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 kotlinx-coroutines-test/js/test/FailingTests.kt create mode 100644 kotlinx-coroutines-test/native/test/FailingTests.kt diff --git a/kotlinx-coroutines-test/common/test/Helpers.kt b/kotlinx-coroutines-test/common/test/Helpers.kt index 8d3cf6a023..db5fd162ee 100644 --- a/kotlinx-coroutines-test/common/test/Helpers.kt +++ b/kotlinx-coroutines-test/common/test/Helpers.kt @@ -66,3 +66,9 @@ open class OrderedExecutionTestBase { } internal fun T.void() { } + +@OptionalExpectation +expect annotation class NoJs() + +@OptionalExpectation +expect annotation class NoNative() \ No newline at end of file diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index a679a7cf6e..20e24d448a 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -84,7 +84,7 @@ class RunTestTest { /** Tests that too low of a dispatch timeout causes crashes. */ @Test - @Ignore // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native + @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native fun testRunTestWithSmallTimeout() = testResultMap({ fn -> assertFailsWith { fn() } }) { @@ -107,7 +107,7 @@ class RunTestTest { /** Tests uncaught exceptions taking priority over dispatch timeout in error reports. */ @Test - @Ignore // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native + @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native fun testRunTestTimingOutAndThrowing() = testResultMap({ fn -> assertFailsWith { fn() } }) { @@ -174,12 +174,12 @@ class RunTestTest { /** Tests that, once the test body has thrown, the child coroutines are cancelled. */ @Test - fun testChildrenCancellationOnTestBodyFailure() { + fun testChildrenCancellationOnTestBodyFailure(): TestResult { var job: Job? = null - testResultMap({ + return testResultMap({ assertFailsWith { it() } assertTrue(job!!.isCancelled) - }, { + }) { runTest { job = launch { while (true) { @@ -188,14 +188,14 @@ class RunTestTest { } throw AssertionError() } - }) + } } /** Tests that [runTest] reports [TimeoutCancellationException]. */ @Test fun testTimeout() = testResultMap({ assertFailsWith { it() } - }, { + }) { runTest { withTimeout(50) { launch { @@ -203,19 +203,19 @@ class RunTestTest { } } } - }) + } /** Checks that [runTest] throws the root cause and not [JobCancellationException] when a child coroutine throws. */ @Test fun testRunTestThrowsRootCause() = testResultMap({ assertFailsWith { it() } - }, { + }) { runTest { launch { throw TestException() } } - }) + } /** Tests that [runTest] completes its job. */ @Test @@ -224,13 +224,13 @@ class RunTestTest { return testResultMap({ it() assertTrue(handlerCalled) - }, { + }) { runTest { coroutineContext.job.invokeOnCompletion { handlerCalled = true } } - }) + } } /** Tests that [runTest] doesn't complete the job that was passed to it as an argument. */ @@ -245,11 +245,11 @@ class RunTestTest { it() assertFalse(handlerCalled) assertEquals(0, job.children.filter { it.isActive }.count()) - }, { + }) { runTest(job) { assertTrue(coroutineContext.job in job.children) } - }) + } } /** Tests that, when the test body fails, the reported exceptions are suppressed. */ @@ -267,14 +267,14 @@ class RunTestTest { assertEquals("y", suppressed[1].message) assertEquals("z", suppressed[2].message) } - }, { + }) { runTest { launch(SupervisorJob()) { throw TestException("x") } launch(SupervisorJob()) { throw TestException("y") } launch(SupervisorJob()) { throw TestException("z") } throw TestException("w") } - }) + } /** Tests that [TestCoroutineScope.runTest] does not inherit the exception handler and works. */ @Test @@ -287,10 +287,10 @@ class RunTestTest { } catch (e: TestException) { scope.cleanupTestCoroutines() // should not fail } - }, { + }) { scope.runTest { launch(SupervisorJob()) { throw TestException("x") } } - }) + } } } diff --git a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt index cce705a371..789744cec0 100644 --- a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt +++ b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt @@ -20,8 +20,8 @@ class TestDispatchersTest: OrderedExecutionTestBase() { Dispatchers.resetMain() } - /** Tests that asynchronous execution of tests does not happen concurrently with [AfterTest] (in fact, it does). */ - @Ignore // fails on JS. + /** Tests that asynchronous execution of tests does not happen concurrently with [AfterTest]. */ + @NoJs @Test fun testMainMocking() = runTest { val mainAtStart = mainTestDispatcher diff --git a/kotlinx-coroutines-test/js/test/FailingTests.kt b/kotlinx-coroutines-test/js/test/FailingTests.kt new file mode 100644 index 0000000000..54d6aed855 --- /dev/null +++ b/kotlinx-coroutines-test/js/test/FailingTests.kt @@ -0,0 +1,37 @@ +package kotlinx.coroutines.test + +import kotlinx.coroutines.* +import kotlinx.coroutines.test.internal.* +import kotlin.test.* + +/** These are tests that we want to fail. They are here so that, when the issue is fixed, their failure indicates that + * everything is better now. */ +class FailingTests { + + private var tearDownEntered = false + + @BeforeTest + fun setUp() { + Dispatchers.setMain(StandardTestDispatcher()) + } + + @AfterTest + fun tearDown() { + Dispatchers.resetMain() + tearDownEntered = true + } + + /** [TestDispatchersTest.testMainMocking]. */ + @Test + fun testAfterTestIsConcurrent() = runTest { + try { + val mainAtStart = (Dispatchers.Main as? TestMainDispatcher)?.delegate as? TestDispatcher ?: return@runTest + withContext(Dispatchers.Default) { + // context switch + } + assertNotSame(mainAtStart, (Dispatchers.Main as TestMainDispatcher).delegate) + } finally { + assertTrue(tearDownEntered) + } + } +} diff --git a/kotlinx-coroutines-test/js/test/Helpers.kt b/kotlinx-coroutines-test/js/test/Helpers.kt index b0a767c5df..5f19d1ac58 100644 --- a/kotlinx-coroutines-test/js/test/Helpers.kt +++ b/kotlinx-coroutines-test/js/test/Helpers.kt @@ -4,6 +4,8 @@ package kotlinx.coroutines.test +import kotlin.test.* + actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult = test().then( { @@ -14,3 +16,5 @@ actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): T throw it } }) + +actual typealias NoJs = Ignore diff --git a/kotlinx-coroutines-test/native/test/FailingTests.kt b/kotlinx-coroutines-test/native/test/FailingTests.kt new file mode 100644 index 0000000000..9fb77ce7c8 --- /dev/null +++ b/kotlinx-coroutines-test/native/test/FailingTests.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test + +import kotlinx.coroutines.* +import kotlin.test.* + +/** These are tests that we want to fail. They are here so that, when the issue is fixed, their failure indicates that + * everything is better now. */ +class FailingTests { + @Test + fun testRunTestLoopShutdownOnTimeout() = testResultMap({ fn -> + assertFailsWith { fn() } + }) { + runTest(dispatchTimeoutMs = 1) { + withContext(Dispatchers.Default) { + delay(10000) + } + fail("shouldn't be reached") + } + } + +} \ No newline at end of file diff --git a/kotlinx-coroutines-test/native/test/Helpers.kt b/kotlinx-coroutines-test/native/test/Helpers.kt index e9aa3ff747..ef478b7eb1 100644 --- a/kotlinx-coroutines-test/native/test/Helpers.kt +++ b/kotlinx-coroutines-test/native/test/Helpers.kt @@ -3,8 +3,12 @@ */ package kotlinx.coroutines.test +import kotlin.test.* + actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) { block { test() } } + +actual typealias NoNative = Ignore