diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 7cbd492..b9876ac 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -2,8 +2,7 @@ object Versions { const val dokka = "1.6.10" const val kotlin = "1.6.10" const val kotlinBenchmark = "0.4.1" - const val kotlinCoroutines = "1.5.2" - const val kotlinCoroutinesTest = "1.5.2" + const val kotlinCoroutines = "1.6.0" const val ktor = "2.0.0-beta-1" const val logback = "1.2.3" const val versionsPlugin = "0.41.0" diff --git a/kotlin-result-coroutines/build.gradle.kts b/kotlin-result-coroutines/build.gradle.kts index 0d32ab8..d166973 100644 --- a/kotlin-result-coroutines/build.gradle.kts +++ b/kotlin-result-coroutines/build.gradle.kts @@ -24,6 +24,7 @@ kotlin { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}") } } @@ -31,7 +32,6 @@ kotlin { dependencies { implementation(kotlin("test-junit")) implementation(kotlin("test")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutinesTest}") } } @@ -40,30 +40,6 @@ kotlin { implementation(kotlin("test-js")) } } - - val nativeTest by creating { - dependsOn(commonTest) - } - - val linuxX64Test by getting { - dependsOn(nativeTest) - } - - val mingwX64Test by getting { - dependsOn(nativeTest) - } - - val macosX64Test by getting { - dependsOn(nativeTest) - } - - val iosX64Test by getting { - dependsOn(nativeTest) - } - - val iosArm64Test by getting { - dependsOn(nativeTest) - } } } diff --git a/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/RunBlockingTest.kt b/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/RunBlockingTest.kt deleted file mode 100644 index f57fa3f..0000000 --- a/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/RunBlockingTest.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.michaelbull.result.coroutines - -import kotlinx.coroutines.CoroutineScope -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -/** - * Workaround to use suspending functions in unit tests for multiplatform/native projects. - * Solution was found here: https://github.com/Kotlin/kotlinx.coroutines/issues/885#issuecomment-446586161 - */ -expect fun runBlockingTest( - context: CoroutineContext = EmptyCoroutineContext, - testBody: suspend CoroutineScope.() -> Unit -) diff --git a/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/binding/SuspendableBindingTest.kt b/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/binding/SuspendableBindingTest.kt index a1f3b66..9671538 100644 --- a/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/binding/SuspendableBindingTest.kt +++ b/kotlin-result-coroutines/src/commonTest/kotlin/com.github.michaelbull.result.coroutines/binding/SuspendableBindingTest.kt @@ -3,19 +3,21 @@ package com.github.michaelbull.result.coroutines.binding import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result -import com.github.michaelbull.result.coroutines.runBlockingTest +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +@ExperimentalCoroutinesApi class SuspendableBindingTest { private object BindingError @Test - fun returnsOkIfAllBindsSuccessful() { + fun returnsOkIfAllBindsSuccessful() = runTest { suspend fun provideX(): Result { delay(1) return Ok(1) @@ -26,23 +28,20 @@ class SuspendableBindingTest { return Ok(2) } - return runBlockingTest { - val result = binding { - val x = provideX().bind() - val y = provideY().bind() - x + y - } - - assertTrue(result is Ok) - assertEquals( - expected = 3, - actual = result.value - ) + val result = binding { + val x = provideX().bind() + val y = provideY().bind() + x + y } + + assertEquals( + expected = Ok(3), + actual = result + ) } @Test - fun returnsOkIfAllBindsOfDifferentTypeAreSuccessful() { + fun returnsOkIfAllBindsOfDifferentTypeAreSuccessful() = runTest { suspend fun provideX(): Result { delay(1) return Ok("1") @@ -53,23 +52,20 @@ class SuspendableBindingTest { return Ok(x + 2) } - return runBlockingTest { - val result = binding { - val x = provideX().bind() - val y = provideY(x.toInt()).bind() - y - } - - assertTrue(result is Ok) - assertEquals( - expected = 3, - actual = result.value - ) + val result = binding { + val x = provideX().bind() + val y = provideY(x.toInt()).bind() + y } + + assertEquals( + expected = Ok(3), + actual = result + ) } @Test - fun returnsFirstErrIfBindingFailed() { + fun returnsFirstErrIfBindingFailed() = runTest { suspend fun provideX(): Result { delay(1) return Ok(1) @@ -85,27 +81,25 @@ class SuspendableBindingTest { return Ok(2) } - return runBlockingTest { - val result = binding { - val x = provideX().bind() - val y = provideY().bind() - val z = provideZ().bind() - x + y + z - } - - assertTrue(result is Err) - assertEquals( - expected = BindingError, - actual = result.error - ) + val result = binding { + val x = provideX().bind() + val y = provideY().bind() + val z = provideZ().bind() + x + y + z } + + assertEquals( + expected = Err(BindingError), + actual = result + ) } @Test - fun returnsStateChangedUntilFirstBindFailed() { + fun returnsStateChangedUntilFirstBindFailed() = runTest { var xStateChange = false var yStateChange = false var zStateChange = false + suspend fun provideX(): Result { delay(1) xStateChange = true @@ -124,27 +118,25 @@ class SuspendableBindingTest { return Err(BindingError) } - runBlockingTest { - val result = binding { - val x = provideX().bind() - val y = provideY().bind() - val z = provideZ().bind() - x + y + z - } - - assertTrue(result is Err) - assertEquals( - expected = BindingError, - actual = result.error - ) - assertTrue(xStateChange) - assertTrue(yStateChange) - assertFalse(zStateChange) + val result = binding { + val x = provideX().bind() + val y = provideY().bind() + val z = provideZ().bind() + x + y + z } + + assertEquals( + expected = Err(BindingError), + actual = result + ) + + assertTrue(xStateChange) + assertTrue(yStateChange) + assertFalse(zStateChange) } @Test - fun returnsFirstErrIfBindingsOfDifferentTypesFailed() { + fun returnsFirstErrIfBindingsOfDifferentTypesFailed() = runTest { suspend fun provideX(): Result { delay(1) return Ok(1) @@ -160,19 +152,16 @@ class SuspendableBindingTest { return Ok(2) } - return runBlockingTest { - val result = binding { - val x = provideX().bind() - val y = provideY().bind() - val z = provideZ().bind() - x + y.toInt() + z - } - - assertTrue(result is Err) - assertEquals( - expected = BindingError, - actual = result.error - ) + val result = binding { + val x = provideX().bind() + val y = provideY().bind() + val z = provideZ().bind() + x + y.toInt() + z } + + assertEquals( + expected = Err(BindingError), + actual = result + ) } } diff --git a/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/RunBlockingTest.kt b/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/RunBlockingTest.kt deleted file mode 100644 index 267fb25..0000000 --- a/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/RunBlockingTest.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.michaelbull.result.coroutines - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.promise - -import kotlin.coroutines.CoroutineContext - -actual fun runBlockingTest(context: CoroutineContext, testBody: suspend CoroutineScope.() -> Unit): dynamic { - return GlobalScope.promise(context) { - testBody(this) - } -} diff --git a/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/binding/AsyncSuspendableBindingTests.kt b/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/binding/AsyncSuspendableBindingTest.kt similarity index 54% rename from kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/binding/AsyncSuspendableBindingTests.kt rename to kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/binding/AsyncSuspendableBindingTest.kt index 24b4004..4fcd315 100644 --- a/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/binding/AsyncSuspendableBindingTests.kt +++ b/kotlin-result-coroutines/src/jsTest/kotlin/com.github.michaelbull.result.coroutines/binding/AsyncSuspendableBindingTest.kt @@ -3,14 +3,16 @@ package com.github.michaelbull.result.coroutines.binding import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result -import com.github.michaelbull.result.coroutines.runBlockingTest +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +@ExperimentalCoroutinesApi class AsyncSuspendableBindingTest { private sealed class BindingError { @@ -19,7 +21,7 @@ class AsyncSuspendableBindingTest { } @Test - fun returnsOkIfAllBindsSuccessful(): dynamic { + fun returnsOkIfAllBindsSuccessful() = runTest { suspend fun provideX(): Result { delay(100) return Ok(1) @@ -30,22 +32,20 @@ class AsyncSuspendableBindingTest { return Ok(2) } - return runBlockingTest { - val result = binding { - val x = async { provideX().bind() } - val y = async { provideY().bind() } - x.await() + y.await() - } - assertTrue(result is Ok) - assertEquals( - expected = 3, - actual = result.value - ) + val result = binding { + val x = async { provideX().bind() } + val y = async { provideY().bind() } + x.await() + y.await() } + + assertEquals( + expected = Ok(3), + actual = result + ) } @Test - fun returnsFirstErrIfBindingFailed(): dynamic { + fun returnsFirstErrIfBindingFailed() = runTest { suspend fun provideX(): Result { delay(10) return Ok(1) @@ -61,27 +61,25 @@ class AsyncSuspendableBindingTest { return Err(BindingError.BindingErrorB) } - return runBlockingTest { - val result = binding { - val x = async { provideX().bind() } - val y = async { provideY().bind() } - val z = async { provideZ().bind() } - x.await() + y.await() + z.await() - } - - assertTrue(result is Err) - assertEquals( - expected = BindingError.BindingErrorB, - actual = result.error - ) + val result = binding { + val x = async { provideX().bind() } + val y = async { provideY().bind() } + val z = async { provideZ().bind() } + x.await() + y.await() + z.await() } + + assertEquals( + expected = Err(BindingError.BindingErrorB), + actual = result + ) } @Test - fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding(): dynamic { + fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding() = runTest { var xStateChange = false var yStateChange = false var zStateChange = false + suspend fun provideX(): Result { delay(20) xStateChange = true @@ -100,23 +98,21 @@ class AsyncSuspendableBindingTest { return Err(BindingError.BindingErrorB) } - return runBlockingTest { - val result = binding { - val x = async { provideX().bind() } - val y = async { provideY().bind() } - val z = async { provideZ().bind() } + val result = binding { + val x = async { provideX().bind() } + val y = async { provideY().bind() } + val z = async { provideZ().bind() } - x.await() + y.await() + z.await() - } - - assertTrue(result is Err) - assertEquals( - expected = BindingError.BindingErrorB, - actual = result.error - ) - assertFalse(xStateChange) - assertFalse(yStateChange) - assertTrue(zStateChange) + x.await() + y.await() + z.await() } + + assertEquals( + expected = Err(BindingError.BindingErrorB), + actual = result + ) + + assertFalse(xStateChange) + assertFalse(yStateChange) + assertTrue(zStateChange) } } diff --git a/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunBlockingTest.kt b/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunBlockingTest.kt deleted file mode 100644 index 6a242b9..0000000 --- a/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunBlockingTest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.michaelbull.result.coroutines - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking -import kotlin.coroutines.CoroutineContext - -actual fun runBlockingTest(context: CoroutineContext, testBody: suspend CoroutineScope.() -> Unit) { - return runBlocking { - testBody(this) - } -} diff --git a/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunSuspendCatchingTest.kt b/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunSuspendCatchingTest.kt index 2310930..3fb9d4c 100644 --- a/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunSuspendCatchingTest.kt +++ b/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/RunSuspendCatchingTest.kt @@ -8,7 +8,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull @@ -17,7 +18,7 @@ import kotlin.test.assertNull class RunSuspendCatchingTest { @Test - fun propagatesCoroutineCancellation() = runBlockingTest { + fun propagatesCoroutineCancellation() = runTest(UnconfinedTestDispatcher()) { var value: String? = null launch(CoroutineName("outer scope")) { @@ -31,7 +32,8 @@ class RunSuspendCatchingTest { result.onSuccess { value = it } } - advanceTimeBy(2_000) + testScheduler.advanceTimeBy(2_000) + testScheduler.runCurrent() // Cancel outer scope, which should cancel inner scope cancel() @@ -41,7 +43,7 @@ class RunSuspendCatchingTest { } @Test - fun returnsOkIfInvocationSuccessful() = runBlockingTest { + fun returnsOkIfInvocationSuccessful() = runTest { val block = { "example" } val result = runSuspendCatching(block) @@ -52,7 +54,7 @@ class RunSuspendCatchingTest { } @Test - fun returnsErrIfInvocationFailsWithAnythingOtherThanCancellationException() = runBlockingTest { + fun returnsErrIfInvocationFailsWithAnythingOtherThanCancellationException() = runTest { val exception = IllegalArgumentException("throw me") val block = { throw exception } val result = runSuspendCatching(block) diff --git a/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/binding/AsyncSuspendableBindingTest.kt b/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/binding/AsyncSuspendableBindingTest.kt index ef4950b..ef84555 100644 --- a/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/binding/AsyncSuspendableBindingTest.kt +++ b/kotlin-result-coroutines/src/jvmTest/kotlin/com/github/michaelbull/result/coroutines/binding/AsyncSuspendableBindingTest.kt @@ -5,11 +5,10 @@ import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async -import kotlinx.coroutines.launch import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -24,7 +23,7 @@ class AsyncSuspendableBindingTest { } @Test - fun returnsOkIfAllBindsSuccessful() { + fun returnsOkIfAllBindsSuccessful() = runTest { suspend fun provideX(): Result { delay(100) return Ok(1) @@ -35,23 +34,20 @@ class AsyncSuspendableBindingTest { return Ok(2) } - runBlockingTest { - val result = binding { - val x = async { provideX().bind() } - val y = async { provideY().bind() } - x.await() + y.await() - } - - assertTrue(result is Ok) - assertEquals( - expected = 3, - actual = result.value - ) + val result = binding { + val x = async { provideX().bind() } + val y = async { provideY().bind() } + x.await() + y.await() } + + assertEquals( + expected = Ok(3), + actual = result + ) } @Test - fun returnsFirstErrIfBindingFailed() { + fun returnsFirstErrIfBindingFailed() = runTest { suspend fun provideX(): Result { delay(3) return Ok(1) @@ -67,24 +63,21 @@ class AsyncSuspendableBindingTest { return Err(BindingError.BindingErrorB) } - runBlockingTest { - val result = binding { - val x = async { provideX().bind() } - val y = async { provideY().bind() } - val z = async { provideZ().bind() } - x.await() + y.await() + z.await() - } - - assertTrue(result is Err) - assertEquals( - expected = BindingError.BindingErrorB, - actual = result.error - ) + val result = binding { + val x = async { provideX().bind() } + val y = async { provideY().bind() } + val z = async { provideZ().bind() } + x.await() + y.await() + z.await() } + + assertEquals( + expected = Err(BindingError.BindingErrorB), + actual = result + ) } @Test - fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding() { + fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding() = runTest { var xStateChange = false var yStateChange = false @@ -100,29 +93,30 @@ class AsyncSuspendableBindingTest { return Err(BindingError.BindingErrorB) } - val dispatcherA = TestCoroutineDispatcher() - val dispatcherB = TestCoroutineDispatcher() + val dispatcherA = StandardTestDispatcher(testScheduler) + val dispatcherB = StandardTestDispatcher(testScheduler) - runBlocking { - val result = binding { - val x = async(dispatcherA) { provideX().bind() } - val y = async(dispatcherB) { provideY().bind() } - dispatcherA.advanceTimeBy(2) - x.await() + y.await() - } + val result = binding { + val x = async(dispatcherA) { provideX().bind() } + val y = async(dispatcherB) { provideY().bind() } - assertTrue(result is Err) - assertEquals( - expected = BindingError.BindingErrorA, - actual = result.error - ) - assertTrue(xStateChange) - assertFalse(yStateChange) + testScheduler.advanceTimeBy(2) + testScheduler.runCurrent() + + x.await() + y.await() } + + assertEquals( + expected = Err(BindingError.BindingErrorA), + actual = result + ) + + assertTrue(xStateChange) + assertFalse(yStateChange) } @Test - fun returnsStateChangedForOnlyTheFirstLaunchBindFailWhenEagerlyCancellingBinding() { + fun returnsStateChangedForOnlyTheFirstLaunchBindFailWhenEagerlyCancellingBinding() = runTest { var xStateChange = false var yStateChange = false var zStateChange = false @@ -145,27 +139,31 @@ class AsyncSuspendableBindingTest { return Err(BindingError.BindingErrorB) } - val dispatcherA = TestCoroutineDispatcher() - val dispatcherB = TestCoroutineDispatcher() - val dispatcherC = TestCoroutineDispatcher() + val dispatcherA = StandardTestDispatcher(testScheduler) + val dispatcherB = StandardTestDispatcher(testScheduler) + val dispatcherC = StandardTestDispatcher(testScheduler) - runBlocking { - val result = binding { - launch(dispatcherA) { provideX().bind() } - dispatcherA.advanceTimeBy(20) - launch(dispatcherB) { provideY().bind() } - dispatcherB.advanceTimeBy(20) - launch(dispatcherC) { provideZ().bind() } - } + val result = binding { + launch(dispatcherA) { provideX().bind() } - assertTrue(result is Err) - assertEquals( - expected = BindingError.BindingErrorA, - actual = result.error - ) - assertTrue(xStateChange) - assertTrue(yStateChange) - assertFalse(zStateChange) + testScheduler.advanceTimeBy(20) + testScheduler.runCurrent() + + launch(dispatcherB) { provideY().bind() } + + testScheduler.advanceTimeBy(20) + testScheduler.runCurrent() + + launch(dispatcherC) { provideZ().bind() } } + + assertEquals( + expected = Err(BindingError.BindingErrorA), + actual = result + ) + + assertTrue(xStateChange) + assertTrue(yStateChange) + assertFalse(zStateChange) } } diff --git a/kotlin-result-coroutines/src/nativeTest/kotlin/com/github/michaelbull/result/coroutines/RunBlockingTest.kt b/kotlin-result-coroutines/src/nativeTest/kotlin/com/github/michaelbull/result/coroutines/RunBlockingTest.kt deleted file mode 100644 index ee1f583..0000000 --- a/kotlin-result-coroutines/src/nativeTest/kotlin/com/github/michaelbull/result/coroutines/RunBlockingTest.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.michaelbull.result.coroutines - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking -import kotlin.coroutines.CoroutineContext - -actual fun runBlockingTest(context: CoroutineContext, testBody: suspend CoroutineScope.() -> Unit) { - runBlocking { - testBody(this) - } -}