Update kotlinx-coroutines to 1.6.0

Closes #69
This commit is contained in:
Michael Bull 2022-01-08 13:11:58 +00:00
parent b3f1edc369
commit 72df4c0ff6
10 changed files with 176 additions and 265 deletions

View File

@ -2,8 +2,7 @@ object Versions {
const val dokka = "1.6.10" const val dokka = "1.6.10"
const val kotlin = "1.6.10" const val kotlin = "1.6.10"
const val kotlinBenchmark = "0.4.1" const val kotlinBenchmark = "0.4.1"
const val kotlinCoroutines = "1.5.2" const val kotlinCoroutines = "1.6.0"
const val kotlinCoroutinesTest = "1.5.2"
const val ktor = "2.0.0-beta-1" const val ktor = "2.0.0-beta-1"
const val logback = "1.2.3" const val logback = "1.2.3"
const val versionsPlugin = "0.41.0" const val versionsPlugin = "0.41.0"

View File

@ -24,6 +24,7 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("test-common")) implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common")) implementation(kotlin("test-annotations-common"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}")
} }
} }
@ -31,7 +32,6 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("test-junit")) implementation(kotlin("test-junit"))
implementation(kotlin("test")) implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutinesTest}")
} }
} }
@ -40,30 +40,6 @@ kotlin {
implementation(kotlin("test-js")) 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)
}
} }
} }

View File

@ -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
)

View File

@ -3,19 +3,21 @@ package com.github.michaelbull.result.coroutines.binding
import com.github.michaelbull.result.Err import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import com.github.michaelbull.result.coroutines.runBlockingTest import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ExperimentalCoroutinesApi
class SuspendableBindingTest { class SuspendableBindingTest {
private object BindingError private object BindingError
@Test @Test
fun returnsOkIfAllBindsSuccessful() { fun returnsOkIfAllBindsSuccessful() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(1) delay(1)
return Ok(1) return Ok(1)
@ -26,23 +28,20 @@ class SuspendableBindingTest {
return Ok(2) return Ok(2)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = provideX().bind()
val x = provideX().bind() val y = provideY().bind()
val y = provideY().bind() x + y
x + y
}
assertTrue(result is Ok)
assertEquals(
expected = 3,
actual = result.value
)
} }
assertEquals(
expected = Ok(3),
actual = result
)
} }
@Test @Test
fun returnsOkIfAllBindsOfDifferentTypeAreSuccessful() { fun returnsOkIfAllBindsOfDifferentTypeAreSuccessful() = runTest {
suspend fun provideX(): Result<String, BindingError> { suspend fun provideX(): Result<String, BindingError> {
delay(1) delay(1)
return Ok("1") return Ok("1")
@ -53,23 +52,20 @@ class SuspendableBindingTest {
return Ok(x + 2) return Ok(x + 2)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = provideX().bind()
val x = provideX().bind() val y = provideY(x.toInt()).bind()
val y = provideY(x.toInt()).bind() y
y
}
assertTrue(result is Ok)
assertEquals(
expected = 3,
actual = result.value
)
} }
assertEquals(
expected = Ok(3),
actual = result
)
} }
@Test @Test
fun returnsFirstErrIfBindingFailed() { fun returnsFirstErrIfBindingFailed() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(1) delay(1)
return Ok(1) return Ok(1)
@ -85,27 +81,25 @@ class SuspendableBindingTest {
return Ok(2) return Ok(2)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = provideX().bind()
val x = provideX().bind() val y = provideY().bind()
val y = provideY().bind() val z = provideZ().bind()
val z = provideZ().bind() x + y + z
x + y + z
}
assertTrue(result is Err)
assertEquals(
expected = BindingError,
actual = result.error
)
} }
assertEquals(
expected = Err(BindingError),
actual = result
)
} }
@Test @Test
fun returnsStateChangedUntilFirstBindFailed() { fun returnsStateChangedUntilFirstBindFailed() = runTest {
var xStateChange = false var xStateChange = false
var yStateChange = false var yStateChange = false
var zStateChange = false var zStateChange = false
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(1) delay(1)
xStateChange = true xStateChange = true
@ -124,27 +118,25 @@ class SuspendableBindingTest {
return Err(BindingError) return Err(BindingError)
} }
runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = provideX().bind()
val x = provideX().bind() val y = provideY().bind()
val y = provideY().bind() val z = provideZ().bind()
val z = provideZ().bind() x + y + z
x + y + z
}
assertTrue(result is Err)
assertEquals(
expected = BindingError,
actual = result.error
)
assertTrue(xStateChange)
assertTrue(yStateChange)
assertFalse(zStateChange)
} }
assertEquals(
expected = Err(BindingError),
actual = result
)
assertTrue(xStateChange)
assertTrue(yStateChange)
assertFalse(zStateChange)
} }
@Test @Test
fun returnsFirstErrIfBindingsOfDifferentTypesFailed() { fun returnsFirstErrIfBindingsOfDifferentTypesFailed() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(1) delay(1)
return Ok(1) return Ok(1)
@ -160,19 +152,16 @@ class SuspendableBindingTest {
return Ok(2) return Ok(2)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = provideX().bind()
val x = provideX().bind() val y = provideY().bind()
val y = provideY().bind() val z = provideZ().bind()
val z = provideZ().bind() x + y.toInt() + z
x + y.toInt() + z
}
assertTrue(result is Err)
assertEquals(
expected = BindingError,
actual = result.error
)
} }
assertEquals(
expected = Err(BindingError),
actual = result
)
} }
} }

View File

@ -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)
}
}

View File

@ -3,14 +3,16 @@ package com.github.michaelbull.result.coroutines.binding
import com.github.michaelbull.result.Err import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import com.github.michaelbull.result.coroutines.runBlockingTest import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ExperimentalCoroutinesApi
class AsyncSuspendableBindingTest { class AsyncSuspendableBindingTest {
private sealed class BindingError { private sealed class BindingError {
@ -19,7 +21,7 @@ class AsyncSuspendableBindingTest {
} }
@Test @Test
fun returnsOkIfAllBindsSuccessful(): dynamic { fun returnsOkIfAllBindsSuccessful() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(100) delay(100)
return Ok(1) return Ok(1)
@ -30,22 +32,20 @@ class AsyncSuspendableBindingTest {
return Ok(2) return Ok(2)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = async { provideX().bind() }
val x = async { provideX().bind() } val y = async { provideY().bind() }
val y = async { provideY().bind() } x.await() + y.await()
x.await() + y.await()
}
assertTrue(result is Ok)
assertEquals(
expected = 3,
actual = result.value
)
} }
assertEquals(
expected = Ok(3),
actual = result
)
} }
@Test @Test
fun returnsFirstErrIfBindingFailed(): dynamic { fun returnsFirstErrIfBindingFailed() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(10) delay(10)
return Ok(1) return Ok(1)
@ -61,27 +61,25 @@ class AsyncSuspendableBindingTest {
return Err(BindingError.BindingErrorB) return Err(BindingError.BindingErrorB)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = async { provideX().bind() }
val x = async { provideX().bind() } val y = async { provideY().bind() }
val y = async { provideY().bind() } val z = async { provideZ().bind() }
val z = async { provideZ().bind() } x.await() + y.await() + z.await()
x.await() + y.await() + z.await()
}
assertTrue(result is Err)
assertEquals(
expected = BindingError.BindingErrorB,
actual = result.error
)
} }
assertEquals(
expected = Err(BindingError.BindingErrorB),
actual = result
)
} }
@Test @Test
fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding(): dynamic { fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding() = runTest {
var xStateChange = false var xStateChange = false
var yStateChange = false var yStateChange = false
var zStateChange = false var zStateChange = false
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(20) delay(20)
xStateChange = true xStateChange = true
@ -100,23 +98,21 @@ class AsyncSuspendableBindingTest {
return Err(BindingError.BindingErrorB) return Err(BindingError.BindingErrorB)
} }
return runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = async { provideX().bind() }
val x = async { provideX().bind() } val y = async { provideY().bind() }
val y = async { provideY().bind() } val z = async { provideZ().bind() }
val z = async { provideZ().bind() }
x.await() + y.await() + z.await() x.await() + y.await() + z.await()
}
assertTrue(result is Err)
assertEquals(
expected = BindingError.BindingErrorB,
actual = result.error
)
assertFalse(xStateChange)
assertFalse(yStateChange)
assertTrue(zStateChange)
} }
assertEquals(
expected = Err(BindingError.BindingErrorB),
actual = result
)
assertFalse(xStateChange)
assertFalse(yStateChange)
assertTrue(zStateChange)
} }
} }

View File

@ -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)
}
}

View File

@ -8,7 +8,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch 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.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
@ -17,7 +18,7 @@ import kotlin.test.assertNull
class RunSuspendCatchingTest { class RunSuspendCatchingTest {
@Test @Test
fun propagatesCoroutineCancellation() = runBlockingTest { fun propagatesCoroutineCancellation() = runTest(UnconfinedTestDispatcher()) {
var value: String? = null var value: String? = null
launch(CoroutineName("outer scope")) { launch(CoroutineName("outer scope")) {
@ -31,7 +32,8 @@ class RunSuspendCatchingTest {
result.onSuccess { value = it } result.onSuccess { value = it }
} }
advanceTimeBy(2_000) testScheduler.advanceTimeBy(2_000)
testScheduler.runCurrent()
// Cancel outer scope, which should cancel inner scope // Cancel outer scope, which should cancel inner scope
cancel() cancel()
@ -41,7 +43,7 @@ class RunSuspendCatchingTest {
} }
@Test @Test
fun returnsOkIfInvocationSuccessful() = runBlockingTest { fun returnsOkIfInvocationSuccessful() = runTest {
val block = { "example" } val block = { "example" }
val result = runSuspendCatching(block) val result = runSuspendCatching(block)
@ -52,7 +54,7 @@ class RunSuspendCatchingTest {
} }
@Test @Test
fun returnsErrIfInvocationFailsWithAnythingOtherThanCancellationException() = runBlockingTest { fun returnsErrIfInvocationFailsWithAnythingOtherThanCancellationException() = runTest {
val exception = IllegalArgumentException("throw me") val exception = IllegalArgumentException("throw me")
val block = { throw exception } val block = { throw exception }
val result = runSuspendCatching(block) val result = runSuspendCatching(block)

View File

@ -5,11 +5,10 @@ import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.runTest
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
@ -24,7 +23,7 @@ class AsyncSuspendableBindingTest {
} }
@Test @Test
fun returnsOkIfAllBindsSuccessful() { fun returnsOkIfAllBindsSuccessful() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(100) delay(100)
return Ok(1) return Ok(1)
@ -35,23 +34,20 @@ class AsyncSuspendableBindingTest {
return Ok(2) return Ok(2)
} }
runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = async { provideX().bind() }
val x = async { provideX().bind() } val y = async { provideY().bind() }
val y = async { provideY().bind() } x.await() + y.await()
x.await() + y.await()
}
assertTrue(result is Ok)
assertEquals(
expected = 3,
actual = result.value
)
} }
assertEquals(
expected = Ok(3),
actual = result
)
} }
@Test @Test
fun returnsFirstErrIfBindingFailed() { fun returnsFirstErrIfBindingFailed() = runTest {
suspend fun provideX(): Result<Int, BindingError> { suspend fun provideX(): Result<Int, BindingError> {
delay(3) delay(3)
return Ok(1) return Ok(1)
@ -67,24 +63,21 @@ class AsyncSuspendableBindingTest {
return Err(BindingError.BindingErrorB) return Err(BindingError.BindingErrorB)
} }
runBlockingTest { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = async { provideX().bind() }
val x = async { provideX().bind() } val y = async { provideY().bind() }
val y = async { provideY().bind() } val z = async { provideZ().bind() }
val z = async { provideZ().bind() } x.await() + y.await() + z.await()
x.await() + y.await() + z.await()
}
assertTrue(result is Err)
assertEquals(
expected = BindingError.BindingErrorB,
actual = result.error
)
} }
assertEquals(
expected = Err(BindingError.BindingErrorB),
actual = result
)
} }
@Test @Test
fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding() { fun returnsStateChangedForOnlyTheFirstAsyncBindFailWhenEagerlyCancellingBinding() = runTest {
var xStateChange = false var xStateChange = false
var yStateChange = false var yStateChange = false
@ -100,29 +93,30 @@ class AsyncSuspendableBindingTest {
return Err(BindingError.BindingErrorB) return Err(BindingError.BindingErrorB)
} }
val dispatcherA = TestCoroutineDispatcher() val dispatcherA = StandardTestDispatcher(testScheduler)
val dispatcherB = TestCoroutineDispatcher() val dispatcherB = StandardTestDispatcher(testScheduler)
runBlocking { val result = binding<Int, BindingError> {
val result = binding<Int, BindingError> { val x = async(dispatcherA) { provideX().bind() }
val x = async(dispatcherA) { provideX().bind() } val y = async(dispatcherB) { provideY().bind() }
val y = async(dispatcherB) { provideY().bind() }
dispatcherA.advanceTimeBy(2)
x.await() + y.await()
}
assertTrue(result is Err) testScheduler.advanceTimeBy(2)
assertEquals( testScheduler.runCurrent()
expected = BindingError.BindingErrorA,
actual = result.error x.await() + y.await()
)
assertTrue(xStateChange)
assertFalse(yStateChange)
} }
assertEquals(
expected = Err(BindingError.BindingErrorA),
actual = result
)
assertTrue(xStateChange)
assertFalse(yStateChange)
} }
@Test @Test
fun returnsStateChangedForOnlyTheFirstLaunchBindFailWhenEagerlyCancellingBinding() { fun returnsStateChangedForOnlyTheFirstLaunchBindFailWhenEagerlyCancellingBinding() = runTest {
var xStateChange = false var xStateChange = false
var yStateChange = false var yStateChange = false
var zStateChange = false var zStateChange = false
@ -145,27 +139,31 @@ class AsyncSuspendableBindingTest {
return Err(BindingError.BindingErrorB) return Err(BindingError.BindingErrorB)
} }
val dispatcherA = TestCoroutineDispatcher() val dispatcherA = StandardTestDispatcher(testScheduler)
val dispatcherB = TestCoroutineDispatcher() val dispatcherB = StandardTestDispatcher(testScheduler)
val dispatcherC = TestCoroutineDispatcher() val dispatcherC = StandardTestDispatcher(testScheduler)
runBlocking { val result = binding<Unit, BindingError> {
val result = binding<Unit, BindingError> { launch(dispatcherA) { provideX().bind() }
launch(dispatcherA) { provideX().bind() }
dispatcherA.advanceTimeBy(20)
launch(dispatcherB) { provideY().bind() }
dispatcherB.advanceTimeBy(20)
launch(dispatcherC) { provideZ().bind() }
}
assertTrue(result is Err) testScheduler.advanceTimeBy(20)
assertEquals( testScheduler.runCurrent()
expected = BindingError.BindingErrorA,
actual = result.error launch(dispatcherB) { provideY().bind() }
)
assertTrue(xStateChange) testScheduler.advanceTimeBy(20)
assertTrue(yStateChange) testScheduler.runCurrent()
assertFalse(zStateChange)
launch(dispatcherC) { provideZ().bind() }
} }
assertEquals(
expected = Err(BindingError.BindingErrorA),
actual = result
)
assertTrue(xStateChange)
assertTrue(yStateChange)
assertFalse(zStateChange)
} }
} }

View File

@ -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)
}
}