Skip to content

Commit 823ec70

Browse files
authored
Merge pull request #45 from ankushg/feat/supportedTarget
Create intermediate sourceset to share between Apple and non-Apple targets
2 parents cfe0201 + 7de411f commit 823ec70

File tree

16 files changed

+89
-54
lines changed

16 files changed

+89
-54
lines changed

kmp-nativecoroutines-core/build.gradle.kts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@ kotlin {
3838
implementation(Dependencies.Kotlinx.atomicfu)
3939
}
4040
}
41-
val appleMain by creating {
41+
val nativeCoroutinesMain by creating {
4242
dependsOn(commonMain)
4343
}
44-
val appleTest by creating {
44+
val nativeCoroutinesTest by creating {
4545
dependsOn(commonTest)
4646
}
47+
val appleMain by creating {
48+
dependsOn(nativeCoroutinesMain)
49+
}
50+
val appleTest by creating {
51+
dependsOn(nativeCoroutinesTest)
52+
}
4753
listOf(
4854
macosX64, macosArm64,
4955
iosArm64, iosX64, iosSimulatorArm64,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.rickclephas.kmp.nativecoroutines
2+
3+
import kotlin.native.concurrent.freeze
4+
5+
actual fun <T> T.freeze(): T = this.freeze()

kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NSError.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import platform.Foundation.NSError
66
import platform.Foundation.NSLocalizedDescriptionKey
77
import kotlin.native.concurrent.freeze
88

9+
actual typealias NativeError = NSError
10+
911
/**
1012
* Converts a [Throwable] to a [NSError].
1113
*
@@ -15,12 +17,12 @@ import kotlin.native.concurrent.freeze
1517
* The Kotlin throwable can be retrieved from the [NSError.userInfo] with the key `KotlinException`.
1618
*/
1719
@OptIn(UnsafeNumber::class)
18-
internal fun Throwable.asNSError(): NSError {
20+
internal actual fun Throwable.asNativeError(): NativeError {
1921
val userInfo = mutableMapOf<Any?, Any>()
2022
userInfo["KotlinException"] = this.freeze()
2123
val message = message
2224
if (message != null) {
2325
userInfo[NSLocalizedDescriptionKey] = message
2426
}
2527
return NSError.errorWithDomain("KotlinException", 0.convert(), userInfo)
26-
}
28+
}

kmp-nativecoroutines-core/src/appleTest/kotlin/com/rickclephas/kmp/nativecoroutines/NSErrorTests.kt

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,26 @@ package com.rickclephas.kmp.nativecoroutines
22

33
import kotlinx.cinterop.UnsafeNumber
44
import kotlinx.cinterop.convert
5-
import kotlin.native.concurrent.isFrozen
65
import kotlin.test.*
76

8-
class NSErrorTests {
9-
10-
@Test
11-
fun `ensure frozen`() {
12-
val exception = RandomException()
13-
assertFalse(exception.isFrozen, "Exception shouldn't be frozen yet")
14-
val nsError = exception.asNSError()
15-
assertTrue(nsError.isFrozen, "NSError should be frozen")
16-
assertTrue(exception.isFrozen, "Exception should be frozen")
17-
}
7+
internal actual val NativeError.kotlinCause
8+
get() = this.userInfo["KotlinException"] as? Throwable
189

10+
class NSErrorTests {
1911
@Test
2012
@OptIn(UnsafeNumber::class)
2113
fun `ensure NSError domain and code are correct`() {
2214
val exception = RandomException()
23-
val nsError = exception.asNSError()
15+
val nsError = exception.asNativeError()
2416
assertEquals("KotlinException", nsError.domain, "Incorrect NSError domain")
2517
assertEquals(0.convert(), nsError.code, "Incorrect NSError code")
2618
}
2719

2820
@Test
2921
fun `ensure localizedDescription is set to message`() {
3022
val exception = RandomException()
31-
val nsError = exception.asNSError()
23+
val nsError = exception.asNativeError()
3224
assertEquals(exception.message, nsError.localizedDescription,
3325
"Localized description isn't set to message")
3426
}
35-
36-
@Test
37-
fun `ensure exception is part of user info`() {
38-
val exception = RandomException()
39-
val nsError = exception.asNSError()
40-
assertSame(exception, nsError.userInfo["KotlinException"], "Exception isn't part of the user info")
41-
}
4227
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.rickclephas.kmp.nativecoroutines
2+
3+
internal expect fun <T> T.freeze(): T

kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCallback.kt renamed to kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCallback.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.rickclephas.kmp.nativecoroutines
22

3-
import kotlin.native.concurrent.freeze
4-
53
/**
64
* A callback with a single argument.
75
*

kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCancellable.kt renamed to kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeCancellable.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.rickclephas.kmp.nativecoroutines
22

33
import kotlinx.coroutines.Job
4-
import kotlin.native.concurrent.freeze
54

65
/**
76
* A function that cancels the coroutines [Job].
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.rickclephas.kmp.nativecoroutines
2+
3+
/**
4+
* Represents an error in a way that the specific platform is able to handle
5+
*/
6+
expect class NativeError
7+
8+
/**
9+
* Converts a [Throwable] to a [NativeError].
10+
*/
11+
internal expect fun Throwable.asNativeError(): NativeError

kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeFlow.kt renamed to kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeFlow.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ import kotlinx.coroutines.CoroutineScope
55
import kotlinx.coroutines.flow.Flow
66
import kotlinx.coroutines.flow.collect
77
import kotlinx.coroutines.launch
8-
import platform.Foundation.NSError
9-
import kotlin.native.concurrent.freeze
108

119
/**
1210
* A function that collects a [Flow] via callbacks.
1311
*
1412
* The function takes an `onItem` and `onComplete` callback
1513
* and returns a cancellable that can be used to cancel the collection.
1614
*/
17-
typealias NativeFlow<T> = (onItem: NativeCallback<T>, onComplete: NativeCallback<NSError?>) -> NativeCancellable
15+
typealias NativeFlow<T> = (onItem: NativeCallback<T>, onComplete: NativeCallback<NativeError?>) -> NativeCancellable
1816

1917
/**
2018
* Creates a [NativeFlow] for this [Flow].
@@ -25,7 +23,7 @@ typealias NativeFlow<T> = (onItem: NativeCallback<T>, onComplete: NativeCallback
2523
*/
2624
fun <T> Flow<T>.asNativeFlow(scope: CoroutineScope? = null): NativeFlow<T> {
2725
val coroutineScope = scope ?: defaultCoroutineScope
28-
return (collect@{ onItem: NativeCallback<T>, onComplete: NativeCallback<NSError?> ->
26+
return (collect@{ onItem: NativeCallback<T>, onComplete: NativeCallback<NativeError?> ->
2927
val job = coroutineScope.launch {
3028
try {
3129
collect { onItem(it) }
@@ -35,13 +33,13 @@ fun <T> Flow<T>.asNativeFlow(scope: CoroutineScope? = null): NativeFlow<T> {
3533
// this is required since the job could be cancelled before it is started
3634
throw e
3735
} catch (e: Throwable) {
38-
onComplete(e.asNSError())
36+
onComplete(e.asNativeError())
3937
}
4038
}
4139
job.invokeOnCompletion { cause ->
4240
// Only handle CancellationExceptions, all other exceptions should be handled inside the job
4341
if (cause !is CancellationException) return@invokeOnCompletion
44-
onComplete(cause.asNSError())
42+
onComplete(cause.asNativeError())
4543
}
4644
return@collect job.asNativeCancellable()
4745
}).freeze()

kmp-nativecoroutines-core/src/appleMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeSuspend.kt renamed to kmp-nativecoroutines-core/src/nativeCoroutinesMain/kotlin/com/rickclephas/kmp/nativecoroutines/NativeSuspend.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@ package com.rickclephas.kmp.nativecoroutines
33
import kotlinx.coroutines.CancellationException
44
import kotlinx.coroutines.CoroutineScope
55
import kotlinx.coroutines.launch
6-
import platform.Foundation.NSError
7-
import kotlin.native.concurrent.freeze
86

97
/**
108
* A function that awaits a suspend function via callbacks.
119
*
1210
* The function takes an `onResult` and `onError` callback
1311
* and returns a cancellable that can be used to cancel the suspend function.
1412
*/
15-
typealias NativeSuspend<T> = (onResult: NativeCallback<T>, onError: NativeCallback<NSError>) -> NativeCancellable
13+
typealias NativeSuspend<T> = (onResult: NativeCallback<T>, onError: NativeCallback<NativeError>) -> NativeCancellable
1614

1715
/**
1816
* Creates a [NativeSuspend] for the provided suspend [block].
@@ -22,7 +20,7 @@ typealias NativeSuspend<T> = (onResult: NativeCallback<T>, onError: NativeCallba
2220
*/
2321
fun <T> nativeSuspend(scope: CoroutineScope? = null, block: suspend () -> T): NativeSuspend<T> {
2422
val coroutineScope = scope ?: defaultCoroutineScope
25-
return (collect@{ onResult: NativeCallback<T>, onError: NativeCallback<NSError> ->
23+
return (collect@{ onResult: NativeCallback<T>, onError: NativeCallback<NativeError> ->
2624
val job = coroutineScope.launch {
2725
try {
2826
onResult(block())
@@ -31,13 +29,13 @@ fun <T> nativeSuspend(scope: CoroutineScope? = null, block: suspend () -> T): Na
3129
// this is required since the job could be cancelled before it is started
3230
throw e
3331
} catch (e: Throwable) {
34-
onError(e.asNSError())
32+
onError(e.asNativeError())
3533
}
3634
}
3735
job.invokeOnCompletion { cause ->
3836
// Only handle CancellationExceptions, all other exceptions should be handled inside the job
3937
if (cause !is CancellationException) return@invokeOnCompletion
40-
onError(cause.asNSError())
38+
onError(cause.asNativeError())
4139
}
4240
return@collect job.asNativeCancellable()
4341
}).freeze()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.rickclephas.kmp.nativecoroutines
2+
3+
import kotlin.native.concurrent.isFrozen
4+
import kotlin.test.*
5+
6+
/**
7+
* Get the [Throwable] that is represented by the given [NativeError]
8+
*/
9+
internal expect val NativeError.kotlinCause: Throwable?
10+
11+
class NativeErrorTests {
12+
@Test
13+
fun `ensure frozen`() {
14+
val exception = RandomException()
15+
assertFalse(exception.isFrozen, "Exception shouldn't be frozen yet")
16+
val nsError = exception.asNativeError()
17+
assertTrue(nsError.isFrozen, "NSError should be frozen")
18+
assertTrue(exception.isFrozen, "Exception should be frozen")
19+
}
20+
21+
@Test
22+
fun `ensure exception is part of user info`() {
23+
val exception = RandomException()
24+
val nsError = exception.asNativeError()
25+
assertSame(exception, nsError.kotlinCause, "Exception isn't part of the NativeError")
26+
}
27+
}

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ class NativeFlowTests {
1111

1212
@Test
1313
fun `ensure frozen`() {
14-
val flow = flow<RandomValue> { }
14+
val flow = flow<RandomValue> { }
1515
assertFalse(flow.isFrozen, "Flow shouldn't be frozen yet")
1616
val nativeFlow = flow.asNativeFlow()
1717
assertTrue(nativeFlow.isFrozen, "NativeFlow should be frozen")
1818
assertTrue(flow.isFrozen, "Flow should be frozen")
1919
}
2020

2121
@Test
22-
fun `ensure completion callback is invoked`() = runBlocking {
23-
val flow = flow<RandomValue> { }
22+
fun `ensure completion callback is invoked`() = kotlinx.coroutines.runBlocking {
23+
val flow = flow<RandomValue> { }
2424
val job = Job()
2525
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
2626
val completionCount = atomic(0)
@@ -33,15 +33,15 @@ class NativeFlowTests {
3333
}
3434

3535
@Test
36-
fun `ensure exceptions are received as errors`() = runBlocking {
36+
fun `ensure exceptions are received as errors`() = kotlinx.coroutines.runBlocking {
3737
val exception = RandomException()
3838
val flow = flow<RandomValue> { throw exception }
3939
val job = Job()
4040
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
4141
val completionCount = atomic(0)
4242
nativeFlow({ _, _ -> }, { error, _ ->
4343
assertNotNull(error, "Flow should complete with an error")
44-
val kotlinException = error.userInfo["KotlinException"]
44+
val kotlinException = error.kotlinCause
4545
assertSame(exception, kotlinException, "Kotlin exception should be the same exception")
4646
completionCount.incrementAndGet()
4747
})
@@ -50,7 +50,7 @@ class NativeFlowTests {
5050
}
5151

5252
@Test
53-
fun `ensure values are received`() = runBlocking {
53+
fun `ensure values are received`() = kotlinx.coroutines.runBlocking {
5454
val values = listOf(RandomValue(), RandomValue(), RandomValue(), RandomValue())
5555
val flow = flow { values.forEach { emit(it) } }
5656
val job = Job()
@@ -61,18 +61,22 @@ class NativeFlowTests {
6161
receivedValueCount.incrementAndGet()
6262
}, { _, _ -> })
6363
job.children.forEach { it.join() } // Waits for the collection to complete
64-
assertEquals(values.size, receivedValueCount.value, "Item callback should be called for every value")
64+
assertEquals(
65+
values.size,
66+
receivedValueCount.value,
67+
"Item callback should be called for every value"
68+
)
6569
}
6670

6771
@Test
68-
fun `ensure collection is cancelled`() = runBlocking {
72+
fun `ensure collection is cancelled`() = kotlinx.coroutines.runBlocking {
6973
val flow = MutableSharedFlow<RandomValue>()
7074
val job = Job()
7175
val nativeFlow = flow.asNativeFlow(CoroutineScope(job))
7276
val completionCount = atomic(0)
7377
val cancel = nativeFlow({ _, _ -> }, { error, _ ->
7478
assertNotNull(error, "Flow should complete with an error")
75-
val exception = error.userInfo["KotlinException"]
79+
val exception = error.kotlinCause
7680
assertIs<CancellationException>(exception, "Error should contain CancellationException")
7781
completionCount.incrementAndGet()
7882
})

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import kotlinx.atomicfu.atomic
44
import kotlinx.coroutines.CoroutineScope
55
import kotlinx.coroutines.Job
66
import kotlinx.coroutines.delay
7-
import kotlinx.coroutines.runBlocking
87
import kotlin.coroutines.cancellation.CancellationException
98
import kotlin.native.concurrent.isFrozen
109
import kotlin.test.*
@@ -31,7 +30,7 @@ class NativeSuspendTests {
3130
}
3231

3332
@Test
34-
fun `ensure correct result is received`() = runBlocking {
33+
fun `ensure correct result is received`() = kotlinx.coroutines.runBlocking {
3534
val value = RandomValue()
3635
val job = Job()
3736
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndReturn(100, value) }
@@ -49,7 +48,7 @@ class NativeSuspendTests {
4948
}
5049

5150
@Test
52-
fun `ensure exceptions are received as errors`() = runBlocking {
51+
fun `ensure exceptions are received as errors`() = kotlinx.coroutines.runBlocking {
5352
val exception = RandomException()
5453
val job = Job()
5554
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndThrow(100, exception) }
@@ -59,7 +58,7 @@ class NativeSuspendTests {
5958
receivedResultCount.incrementAndGet()
6059
}, { error, _ ->
6160
assertNotNull(error, "Function should complete with an error")
62-
val kotlinException = error.userInfo["KotlinException"]
61+
val kotlinException = error.kotlinCause
6362
assertSame(exception, kotlinException, "Kotlin exception should be the same exception")
6463
receivedErrorCount.incrementAndGet()
6564
})
@@ -69,7 +68,7 @@ class NativeSuspendTests {
6968
}
7069

7170
@Test
72-
fun `ensure function is cancelled`() = runBlocking {
71+
fun `ensure function is cancelled`() = kotlinx.coroutines.runBlocking {
7372
val job = Job()
7473
val nativeSuspend = nativeSuspend(CoroutineScope(job)) { delayAndReturn(5_000, RandomValue()) }
7574
val receivedResultCount = atomic(0)
@@ -78,7 +77,7 @@ class NativeSuspendTests {
7877
receivedResultCount.incrementAndGet()
7978
}, { error, _ ->
8079
assertNotNull(error, "Function should complete with an error")
81-
val exception = error.userInfo["KotlinException"]
80+
val exception = error.kotlinCause
8281
assertIs<CancellationException>(exception, "Error should contain CancellationException")
8382
receivedErrorCount.incrementAndGet()
8483
})

0 commit comments

Comments
 (0)