From e2bbbf6188bb4631c74faed479afccb7afaeb6d2 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 14 Oct 2021 21:35:28 +0300 Subject: [PATCH] Add more tests for 'runTest' --- .../common/src/TestBuilders.kt | 9 ++- .../common/test/Helpers.kt | 10 +++ .../common/test/RunTestTest.kt | 72 ++++++++++++++++++- kotlinx-coroutines-test/js/test/Helpers.kt | 21 ++++++ kotlinx-coroutines-test/jvm/test/Helpers.kt | 12 ++++ .../native/test/Helpers.kt | 12 ++++ 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 kotlinx-coroutines-test/common/test/Helpers.kt create mode 100644 kotlinx-coroutines-test/js/test/Helpers.kt create mode 100644 kotlinx-coroutines-test/jvm/test/Helpers.kt create mode 100644 kotlinx-coroutines-test/native/test/Helpers.kt diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index a0eab5938b..a84f56d7f6 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -170,9 +170,9 @@ public fun runTest( ): TestResult { if (context[RunningInRunTest] != null) throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") + val testScope = TestCoroutineScope(context + RunningInRunTest()) + val scheduler = testScope.testScheduler return createTestResult { - val testScope = TestCoroutineScope(context + RunningInRunTest()) - val scheduler = testScope.testScheduler val deferred = testScope.async { testScope.testBody() } @@ -197,6 +197,11 @@ public fun runTest( } } } catch (e: TimeoutCancellationException) { + try { + testScope.cleanupTestCoroutines() + } catch (e: UncompletedCoroutinesError) { + // we expect these and will instead throw a more informative exception just below. + } throw UncompletedCoroutinesError("The test coroutine was not completed after waiting for $dispatchTimeoutMs ms") } } diff --git a/kotlinx-coroutines-test/common/test/Helpers.kt b/kotlinx-coroutines-test/common/test/Helpers.kt new file mode 100644 index 0000000000..59a59d4fac --- /dev/null +++ b/kotlinx-coroutines-test/common/test/Helpers.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test + +/** + * Passes [test] as an argument to [block], but as a function returning not a [TestResult] but [Unit]. + */ +expect fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult \ 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 1745c90531..28fdc5b3d9 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -5,10 +5,8 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* import kotlin.coroutines.* import kotlin.test.* -import kotlin.time.* class RunTestTest { @@ -50,4 +48,74 @@ class RunTestTest { } } + /** Tests that even the dispatch timeout of `0` is fine if all the dispatches go through the same scheduler. */ + @Test + fun testRunTestWithZeroTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) { + // below is some arbitrary concurrent code where all dispatches go through the same scheduler. + launch { + delay(2000) + } + val deferred = async { + val job = launch(TestCoroutineDispatcher(testScheduler)) { + launch { + delay(500) + } + delay(1000) + } + job.join() + } + deferred.await() + } + + /** Tests that a dispatch timeout of `0` will fail the test if there are some dispatches outside the scheduler. */ + @Test + fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn -> + assertFailsWith { fn() } + }) { + runTest(dispatchTimeoutMs = 0) { + withContext(Dispatchers.Default) { + delay(10) + 3 + } + throw IllegalStateException("shouldn't be reached") + } + } + + /** Tests that too low of a dispatch timeout causes crashes. */ + @Test + fun testRunTestWithSmallTimeout() = testResultMap({ fn -> + assertFailsWith { fn() } + }) { + runTest(dispatchTimeoutMs = 100) { + withContext(Dispatchers.Default) { + delay(10000) + 3 + } + throw RuntimeException("shouldn't be reached") + } + } + + /** Tests that too low of a dispatch timeout causes crashes. */ + @Test + fun testRunTestWithLargeTimeout() = runTest(dispatchTimeoutMs = 5000) { + withContext(Dispatchers.Default) { + delay(50) + } + } + + /** Tests uncaught exceptions taking priority over dispatch timeout in error reports. */ + @Test + fun testRunTestTimingOutAndThrowing() = testResultMap({ fn -> + assertFailsWith { fn() } + }) { + runTest(dispatchTimeoutMs = 1) { + coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, IllegalArgumentException()) + withContext(Dispatchers.Default) { + delay(10000) + 3 + } + throw RuntimeException("shouldn't be reached") + } + } + } diff --git a/kotlinx-coroutines-test/js/test/Helpers.kt b/kotlinx-coroutines-test/js/test/Helpers.kt new file mode 100644 index 0000000000..42afb599be --- /dev/null +++ b/kotlinx-coroutines-test/js/test/Helpers.kt @@ -0,0 +1,21 @@ +/* + * 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.* + +actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult = + GlobalScope.promise { + val promise = test() + promise.then( + { + block { + } + }, { + block { + throw it + } + }) + } \ No newline at end of file diff --git a/kotlinx-coroutines-test/jvm/test/Helpers.kt b/kotlinx-coroutines-test/jvm/test/Helpers.kt new file mode 100644 index 0000000000..017f2369bb --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/Helpers.kt @@ -0,0 +1,12 @@ +/* + * 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.* + +actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) { + block { + test() + } +} diff --git a/kotlinx-coroutines-test/native/test/Helpers.kt b/kotlinx-coroutines-test/native/test/Helpers.kt new file mode 100644 index 0000000000..017f2369bb --- /dev/null +++ b/kotlinx-coroutines-test/native/test/Helpers.kt @@ -0,0 +1,12 @@ +/* + * 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.* + +actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) { + block { + test() + } +}