Skip to content

Commit

Permalink
Merge branch 'master' into feature/GH-29-improve-exception-conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
rickclephas committed Feb 20, 2022
2 parents dade444 + c6b134a commit 3874964
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 31 deletions.
4 changes: 3 additions & 1 deletion buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ object Dependencies {
}

object Kotlinx {
const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt"
private const val coroutinesVersion = "1.6.0-native-mt"
const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
const val coroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"

const val atomicfu = "org.jetbrains.kotlinx:atomicfu:0.17.1"
}
Expand Down
1 change: 1 addition & 0 deletions kmp-nativecoroutines-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ kotlin {
dependencies {
implementation(kotlin("test"))
implementation(Dependencies.Kotlinx.atomicfu)
implementation(Dependencies.Kotlinx.coroutinesTest)
}
}
val nativeCoroutinesMain by creating {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.rickclephas.kmp.nativecoroutines

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.CoroutineContext

@Suppress("ACTUAL_WITHOUT_EXPECT")
actual typealias TestResult = Unit

internal actual fun runTest(
context: CoroutineContext,
block: suspend CoroutineScope.() -> Unit
): TestResult = runBlocking(context, block)

@Suppress("SuspendFunctionOnCoroutineScope")
internal actual suspend fun CoroutineScope.runCurrent() {
coroutineContext[Job]?.children?.forEach { it.join() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,45 @@ import kotlin.test.*
class NativeFlowTests {

@Test
fun ensureCompletionCallbackIsInvoked() = runBlocking {
fun ensureCompletionCallbackIsInvoked() = runTest {
val flow = flow<RandomValue> { }
val job = Job()
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
val nativeFlow = flow.asNativeFlow(this)
val completionCount = atomic(0)
nativeFlow({ _, _ -> }, { error, _ ->
assertNull(error, "Flow should complete without an error")
completionCount.incrementAndGet()
})
job.children.forEach { it.join() } // Waits for the collection to complete
runCurrent()
assertEquals(1, completionCount.value, "Completion callback should be called once")
}

@Test
fun ensureExceptionsAreReceivedAsErrors() = runBlocking {
fun ensureExceptionsAreReceivedAsErrors() = runTest {
val exception = RandomException()
val flow = flow<RandomValue> { throw exception }
val job = Job()
val nativeFlow = flow.asNativeFlow(CoroutineScope(job), arrayOf(RandomException::class))
val nativeFlow = flow.asNativeFlow(this)
val completionCount = atomic(0)
nativeFlow({ _, _ -> }, { error, _ ->
assertNotNull(error, "Flow should complete with an error")
val kotlinException = error.kotlinCause
assertSame(exception, kotlinException, "Kotlin exception should be the same exception")
completionCount.incrementAndGet()
})
job.children.forEach { it.join() } // Waits for the collection to complete
runCurrent()
assertEquals(1, completionCount.value, "Completion callback should be called once")
}

@Test
fun ensureValuesAreReceived() = runBlocking {
fun ensureValuesAreReceived() = runTest {
val values = listOf(RandomValue(), RandomValue(), RandomValue(), RandomValue())
val flow = flow { values.forEach { emit(it) } }
val job = Job()
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
val nativeFlow = flow.asNativeFlow(this)
val receivedValueCount = atomic(0)
nativeFlow({ value, _ ->
assertSame(values[receivedValueCount.value], value, "Received incorrect value")
receivedValueCount.incrementAndGet()
}, { _, _ -> })
job.children.forEach { it.join() } // Waits for the collection to complete
runCurrent()
assertEquals(
values.size,
receivedValueCount.value,
Expand All @@ -58,10 +55,9 @@ class NativeFlowTests {
}

@Test
fun ensureCollectionIsCancelled() = runBlocking {
fun ensureCollectionIsCancelled() = runTest {
val flow = MutableSharedFlow<RandomValue>()
val job = Job()
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
val nativeFlow = flow.asNativeFlow(this)
val completionCount = atomic(0)
val cancel = nativeFlow({ _, _ -> }, { error, _ ->
assertNotNull(error, "Flow should complete with an error")
Expand All @@ -71,7 +67,7 @@ class NativeFlowTests {
})
delay(100) // Gives the collection some time to start
cancel()
job.children.forEach { it.join() } // Waits for the collection to complete
runCurrent()
assertEquals(1, completionCount.value, "Completion callback should be called once")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ class NativeSuspendTests {
}

@Test
fun ensureCorrectResultIsReceived() = runBlocking {
fun ensureCorrectResultIsReceived() = runTest {
val value = RandomValue()
val job = Job()
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndReturn(100, value) }
val nativeSuspend = nativeSuspend(this) { delayAndReturn(100, value) }
val receivedResultCount = atomic(0)
val receivedErrorCount = atomic(0)
nativeSuspend({ receivedValue, _ ->
Expand All @@ -29,18 +28,15 @@ class NativeSuspendTests {
}, { _, _ ->
receivedErrorCount.incrementAndGet()
})
job.children.forEach { it.join() } // Waits for the function to complete
runCurrent()
assertEquals(1, receivedResultCount.value, "Result callback should be called once")
assertEquals(0, receivedErrorCount.value, "Error callback shouldn't be called")
}

@Test
fun ensureExceptionsAreReceivedAsErrors() = runBlocking {
fun ensureExceptionsAreReceivedAsErrors() = runTest {
val exception = RandomException()
val job = Job()
val nativeSuspend = nativeSuspend(CoroutineScope(job), arrayOf(RandomException::class)) {
delayAndThrow(100, exception)
}
val nativeSuspend = nativeSuspend(this) { delayAndThrow(100, exception) }
val receivedResultCount = atomic(0)
val receivedErrorCount = atomic(0)
nativeSuspend({ _, _ ->
Expand All @@ -51,15 +47,14 @@ class NativeSuspendTests {
assertSame(exception, kotlinException, "Kotlin exception should be the same exception")
receivedErrorCount.incrementAndGet()
})
job.children.forEach { it.join() } // Waits for the function to complete
runCurrent()
assertEquals(1, receivedErrorCount.value, "Error callback should be called once")
assertEquals(0, receivedResultCount.value, "Result callback shouldn't be called")
}

@Test
fun ensureFunctionIsCancelled() = runBlocking {
val job = Job()
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndReturn(5_000, RandomValue()) }
fun ensureFunctionIsCancelled() = runTest {
val nativeSuspend = nativeSuspend(this) { delayAndReturn(5_000, RandomValue()) }
val receivedResultCount = atomic(0)
val receivedErrorCount = atomic(0)
val cancel = nativeSuspend({ _, _ ->
Expand All @@ -72,7 +67,7 @@ class NativeSuspendTests {
})
delay(100) // Gives the function some time to start
cancel()
job.children.forEach { it.join() } // Waits for the function to complete
runCurrent()
assertEquals(1, receivedErrorCount.value, "Error callback should be called once")
assertEquals(0, receivedResultCount.value, "Result callback shouldn't be called")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.rickclephas.kmp.nativecoroutines

import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

@Suppress("NO_ACTUAL_FOR_EXPECT")
expect class TestResult

/**
* There is a freezing [issue](https://github.com/Kotlin/kotlinx.coroutines/issues/3195)
* with the [kotlinx.coroutines.test.runTest] function.
*
* As a temporary workaround this function uses `runBlocking` on native targets instead.
* This is kind of hacky, but once we drop the old memory model this is no longer needed.
*/
// TODO: Remove temporary fix for runTest freezing issue
internal expect fun runTest(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> Unit
): TestResult

internal expect suspend fun CoroutineScope.runCurrent()

0 comments on commit 3874964

Please sign in to comment.