Skip to content

Commit 3874964

Browse files
committed
Merge branch 'master' into feature/GH-29-improve-exception-conversion
2 parents dade444 + c6b134a commit 3874964

File tree

6 files changed

+67
-31
lines changed

6 files changed

+67
-31
lines changed

buildSrc/src/main/kotlin/Dependencies.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ object Dependencies {
88
}
99

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

1315
const val atomicfu = "org.jetbrains.kotlinx:atomicfu:0.17.1"
1416
}

kmp-nativecoroutines-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ kotlin {
3636
dependencies {
3737
implementation(kotlin("test"))
3838
implementation(Dependencies.Kotlinx.atomicfu)
39+
implementation(Dependencies.Kotlinx.coroutinesTest)
3940
}
4041
}
4142
val nativeCoroutinesMain by creating {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.rickclephas.kmp.nativecoroutines
2+
3+
import kotlinx.coroutines.CoroutineScope
4+
import kotlinx.coroutines.Job
5+
import kotlinx.coroutines.runBlocking
6+
import kotlin.coroutines.CoroutineContext
7+
8+
@Suppress("ACTUAL_WITHOUT_EXPECT")
9+
actual typealias TestResult = Unit
10+
11+
internal actual fun runTest(
12+
context: CoroutineContext,
13+
block: suspend CoroutineScope.() -> Unit
14+
): TestResult = runBlocking(context, block)
15+
16+
@Suppress("SuspendFunctionOnCoroutineScope")
17+
internal actual suspend fun CoroutineScope.runCurrent() {
18+
coroutineContext[Job]?.children?.forEach { it.join() }
19+
}

kmp-nativecoroutines-core/src/nativeCoroutinesTest/kotlin/com/rickclephas/kmp/nativecoroutines/NativeFlowTests.kt

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,45 @@ import kotlin.test.*
88
class NativeFlowTests {
99

1010
@Test
11-
fun ensureCompletionCallbackIsInvoked() = runBlocking {
11+
fun ensureCompletionCallbackIsInvoked() = runTest {
1212
val flow = flow<RandomValue> { }
13-
val job = Job()
14-
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
13+
val nativeFlow = flow.asNativeFlow(this)
1514
val completionCount = atomic(0)
1615
nativeFlow({ _, _ -> }, { error, _ ->
1716
assertNull(error, "Flow should complete without an error")
1817
completionCount.incrementAndGet()
1918
})
20-
job.children.forEach { it.join() } // Waits for the collection to complete
19+
runCurrent()
2120
assertEquals(1, completionCount.value, "Completion callback should be called once")
2221
}
2322

2423
@Test
25-
fun ensureExceptionsAreReceivedAsErrors() = runBlocking {
24+
fun ensureExceptionsAreReceivedAsErrors() = runTest {
2625
val exception = RandomException()
2726
val flow = flow<RandomValue> { throw exception }
28-
val job = Job()
29-
val nativeFlow = flow.asNativeFlow(CoroutineScope(job), arrayOf(RandomException::class))
27+
val nativeFlow = flow.asNativeFlow(this)
3028
val completionCount = atomic(0)
3129
nativeFlow({ _, _ -> }, { error, _ ->
3230
assertNotNull(error, "Flow should complete with an error")
3331
val kotlinException = error.kotlinCause
3432
assertSame(exception, kotlinException, "Kotlin exception should be the same exception")
3533
completionCount.incrementAndGet()
3634
})
37-
job.children.forEach { it.join() } // Waits for the collection to complete
35+
runCurrent()
3836
assertEquals(1, completionCount.value, "Completion callback should be called once")
3937
}
4038

4139
@Test
42-
fun ensureValuesAreReceived() = runBlocking {
40+
fun ensureValuesAreReceived() = runTest {
4341
val values = listOf(RandomValue(), RandomValue(), RandomValue(), RandomValue())
4442
val flow = flow { values.forEach { emit(it) } }
45-
val job = Job()
46-
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
43+
val nativeFlow = flow.asNativeFlow(this)
4744
val receivedValueCount = atomic(0)
4845
nativeFlow({ value, _ ->
4946
assertSame(values[receivedValueCount.value], value, "Received incorrect value")
5047
receivedValueCount.incrementAndGet()
5148
}, { _, _ -> })
52-
job.children.forEach { it.join() } // Waits for the collection to complete
49+
runCurrent()
5350
assertEquals(
5451
values.size,
5552
receivedValueCount.value,
@@ -58,10 +55,9 @@ class NativeFlowTests {
5855
}
5956

6057
@Test
61-
fun ensureCollectionIsCancelled() = runBlocking {
58+
fun ensureCollectionIsCancelled() = runTest {
6259
val flow = MutableSharedFlow<RandomValue>()
63-
val job = Job()
64-
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
60+
val nativeFlow = flow.asNativeFlow(this)
6561
val completionCount = atomic(0)
6662
val cancel = nativeFlow({ _, _ -> }, { error, _ ->
6763
assertNotNull(error, "Flow should complete with an error")
@@ -71,7 +67,7 @@ class NativeFlowTests {
7167
})
7268
delay(100) // Gives the collection some time to start
7369
cancel()
74-
job.children.forEach { it.join() } // Waits for the collection to complete
70+
runCurrent()
7571
assertEquals(1, completionCount.value, "Completion callback should be called once")
7672
}
7773
}

kmp-nativecoroutines-core/src/nativeCoroutinesTest/kotlin/com/rickclephas/kmp/nativecoroutines/NativeSuspendTests.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ class NativeSuspendTests {
1717
}
1818

1919
@Test
20-
fun ensureCorrectResultIsReceived() = runBlocking {
20+
fun ensureCorrectResultIsReceived() = runTest {
2121
val value = RandomValue()
22-
val job = Job()
23-
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndReturn(100, value) }
22+
val nativeSuspend = nativeSuspend(this) { delayAndReturn(100, value) }
2423
val receivedResultCount = atomic(0)
2524
val receivedErrorCount = atomic(0)
2625
nativeSuspend({ receivedValue, _ ->
@@ -29,18 +28,15 @@ class NativeSuspendTests {
2928
}, { _, _ ->
3029
receivedErrorCount.incrementAndGet()
3130
})
32-
job.children.forEach { it.join() } // Waits for the function to complete
31+
runCurrent()
3332
assertEquals(1, receivedResultCount.value, "Result callback should be called once")
3433
assertEquals(0, receivedErrorCount.value, "Error callback shouldn't be called")
3534
}
3635

3736
@Test
38-
fun ensureExceptionsAreReceivedAsErrors() = runBlocking {
37+
fun ensureExceptionsAreReceivedAsErrors() = runTest {
3938
val exception = RandomException()
40-
val job = Job()
41-
val nativeSuspend = nativeSuspend(CoroutineScope(job), arrayOf(RandomException::class)) {
42-
delayAndThrow(100, exception)
43-
}
39+
val nativeSuspend = nativeSuspend(this) { delayAndThrow(100, exception) }
4440
val receivedResultCount = atomic(0)
4541
val receivedErrorCount = atomic(0)
4642
nativeSuspend({ _, _ ->
@@ -51,15 +47,14 @@ class NativeSuspendTests {
5147
assertSame(exception, kotlinException, "Kotlin exception should be the same exception")
5248
receivedErrorCount.incrementAndGet()
5349
})
54-
job.children.forEach { it.join() } // Waits for the function to complete
50+
runCurrent()
5551
assertEquals(1, receivedErrorCount.value, "Error callback should be called once")
5652
assertEquals(0, receivedResultCount.value, "Result callback shouldn't be called")
5753
}
5854

5955
@Test
60-
fun ensureFunctionIsCancelled() = runBlocking {
61-
val job = Job()
62-
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndReturn(5_000, RandomValue()) }
56+
fun ensureFunctionIsCancelled() = runTest {
57+
val nativeSuspend = nativeSuspend(this) { delayAndReturn(5_000, RandomValue()) }
6358
val receivedResultCount = atomic(0)
6459
val receivedErrorCount = atomic(0)
6560
val cancel = nativeSuspend({ _, _ ->
@@ -72,7 +67,7 @@ class NativeSuspendTests {
7267
})
7368
delay(100) // Gives the function some time to start
7469
cancel()
75-
job.children.forEach { it.join() } // Waits for the function to complete
70+
runCurrent()
7671
assertEquals(1, receivedErrorCount.value, "Error callback should be called once")
7772
assertEquals(0, receivedResultCount.value, "Result callback shouldn't be called")
7873
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.rickclephas.kmp.nativecoroutines
2+
3+
import kotlinx.coroutines.CoroutineScope
4+
import kotlin.coroutines.CoroutineContext
5+
import kotlin.coroutines.EmptyCoroutineContext
6+
7+
@Suppress("NO_ACTUAL_FOR_EXPECT")
8+
expect class TestResult
9+
10+
/**
11+
* There is a freezing [issue](https://github.com/Kotlin/kotlinx.coroutines/issues/3195)
12+
* with the [kotlinx.coroutines.test.runTest] function.
13+
*
14+
* As a temporary workaround this function uses `runBlocking` on native targets instead.
15+
* This is kind of hacky, but once we drop the old memory model this is no longer needed.
16+
*/
17+
// TODO: Remove temporary fix for runTest freezing issue
18+
internal expect fun runTest(
19+
context: CoroutineContext = EmptyCoroutineContext,
20+
block: suspend CoroutineScope.() -> Unit
21+
): TestResult
22+
23+
internal expect suspend fun CoroutineScope.runCurrent()

0 commit comments

Comments
 (0)