Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare tests for kotlinx-coroutines-test #46

Merged
merged 7 commits into from
Feb 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
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,16 +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)) { delayAndThrow(100, exception) }
val nativeSuspend = nativeSuspend(this) { delayAndThrow(100, exception) }
val receivedResultCount = atomic(0)
val receivedErrorCount = atomic(0)
nativeSuspend({ _, _ ->
Expand All @@ -49,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 @@ -70,8 +67,8 @@ 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")
}
}
}
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()