Skip to content

Commit 9d5abcd

Browse files
committed
MPP: Common withContext + more tests
1 parent 45c1a73 commit 9d5abcd

File tree

18 files changed

+327
-239
lines changed

18 files changed

+327
-239
lines changed

common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonBuilders.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ public expect fun launch(
1111
block: suspend CoroutineScope.() -> Unit
1212
): Job
1313

14+
@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER")
15+
public expect suspend fun <T> withContext(
16+
context: CoroutineContext,
17+
start: CoroutineStart = CoroutineStart.DEFAULT,
18+
block: suspend () -> T
19+
): T
20+
1421
@Suppress("EXPECTED_DECLARATION_WITH_DEFAULT_PARAMETER")
1522
public expect fun <T> runBlocking(
1623
context: CoroutineContext = EmptyCoroutineContext,

common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/CommonCoroutineDispatcher.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package kotlinx.coroutines.experimental
22

3-
import kotlin.coroutines.experimental.ContinuationInterceptor
4-
import kotlin.coroutines.experimental.CoroutineContext
3+
import kotlin.coroutines.experimental.*
54

6-
public expect abstract class CoroutineDispatcher : ContinuationInterceptor {
5+
public expect abstract class CoroutineDispatcher constructor() : AbstractCoroutineContextElement, ContinuationInterceptor {
76
public open fun isDispatchNeeded(context: CoroutineContext): Boolean
87
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
8+
public override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
99
}
1010

1111
public expect interface Runnable {

core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandlerTest.kt renamed to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutineExceptionHandlerTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package kotlinx.coroutines.experimental
22

3-
import org.junit.Test
3+
import kotlin.test.*
44

5-
class CoroutineExceptionHandlerTest : TestBase() {
5+
class CommonCoroutineExceptionHandlerTest : TestBase() {
66
@Test
77
fun testCoroutineExceptionHandlerCreator() = runBlocking {
88
expect(1)
@@ -19,6 +19,6 @@ class CoroutineExceptionHandlerTest : TestBase() {
1919
finish(4)
2020
check(coroutineException is TestException)
2121
}
22-
}
2322

24-
private class TestException: RuntimeException()
23+
private class TestException: RuntimeException()
24+
}

common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonCoroutinesTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,33 @@ class CommonCoroutinesTest : TestBase() {
308308
finish(6)
309309
}
310310

311+
@Test
312+
fun testParentCrashCancelsChildren() = runTest(
313+
unhandled = listOf({ it -> it is TestException })
314+
) {
315+
expect(1)
316+
val parent = launch(coroutineContext + Job()) {
317+
expect(4)
318+
throw TestException("Crashed")
319+
}
320+
launch(coroutineContext + parent, CoroutineStart.UNDISPATCHED) {
321+
expect(2)
322+
try {
323+
yield() // to test
324+
} finally {
325+
expect(5)
326+
withContext(NonCancellable) { yield() } // to test
327+
expect(7)
328+
}
329+
expectUnreached() // will get cancelled, because parent crashes
330+
}
331+
expect(3)
332+
yield() // to parent
333+
expect(6)
334+
parent.join() // make sure crashed parent still waits for its child
335+
finish(8)
336+
}
337+
311338
private fun throwTestException() { throw TestException() }
312339

313340
private class TestException : Exception {
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package kotlinx.coroutines.experimental
2+
3+
import kotlin.test.*
4+
5+
class CommonJobTest : TestBase() {
6+
@Test
7+
fun testState() {
8+
val job = Job()
9+
check(job.isActive)
10+
job.cancel()
11+
check(!job.isActive)
12+
}
13+
14+
@Test
15+
fun testHandler() {
16+
val job = Job()
17+
var fireCount = 0
18+
job.invokeOnCompletion { fireCount++ }
19+
check(job.isActive)
20+
assertEquals(0, fireCount)
21+
// cancel once
22+
job.cancel()
23+
check(!job.isActive)
24+
assertEquals(1, fireCount)
25+
// cancel again
26+
job.cancel()
27+
check(!job.isActive)
28+
assertEquals(1, fireCount)
29+
}
30+
31+
@Test
32+
fun testManyHandlers() {
33+
val job = Job()
34+
val n = 100 * stressTestMultiplier
35+
val fireCount = IntArray(n)
36+
for (i in 0 until n) job.invokeOnCompletion { fireCount[i]++ }
37+
check(job.isActive)
38+
for (i in 0 until n) assertEquals(0, fireCount[i])
39+
// cancel once
40+
job.cancel()
41+
check(!job.isActive)
42+
for (i in 0 until n) assertEquals(1, fireCount[i])
43+
// cancel again
44+
job.cancel()
45+
check(!job.isActive)
46+
for (i in 0 until n) assertEquals(1, fireCount[i])
47+
}
48+
49+
@Test
50+
fun testUnregisterInHandler() {
51+
val job = Job()
52+
val n = 100 * stressTestMultiplier
53+
val fireCount = IntArray(n)
54+
for (i in 0 until n) {
55+
var registration: DisposableHandle? = null
56+
registration = job.invokeOnCompletion {
57+
fireCount[i]++
58+
registration!!.dispose()
59+
}
60+
}
61+
check(job.isActive)
62+
for (i in 0 until n) assertEquals(0, fireCount[i])
63+
// cancel once
64+
job.cancel()
65+
check(!job.isActive)
66+
for (i in 0 until n) assertEquals(1, fireCount[i])
67+
// cancel again
68+
job.cancel()
69+
check(!job.isActive)
70+
for (i in 0 until n) assertEquals(1, fireCount[i])
71+
}
72+
73+
@Test
74+
fun testManyHandlersWithUnregister() {
75+
val job = Job()
76+
val n = 100 * stressTestMultiplier
77+
val fireCount = IntArray(n)
78+
val registrations = Array<DisposableHandle>(n) { i -> job.invokeOnCompletion { fireCount[i]++ } }
79+
check(job.isActive)
80+
fun unreg(i: Int) = i % 4 <= 1
81+
for (i in 0 until n) if (unreg(i)) registrations[i].dispose()
82+
for (i in 0 until n) assertEquals(0, fireCount[i])
83+
job.cancel()
84+
check(!job.isActive)
85+
for (i in 0 until n) assertEquals(if (unreg(i)) 0 else 1, fireCount[i])
86+
}
87+
88+
@Test
89+
fun testExceptionsInHandler() {
90+
val job = Job()
91+
val n = 100 * stressTestMultiplier
92+
val fireCount = IntArray(n)
93+
class TestException : Throwable()
94+
for (i in 0 until n) job.invokeOnCompletion {
95+
fireCount[i]++
96+
throw TestException()
97+
}
98+
check(job.isActive)
99+
for (i in 0 until n) assertEquals(0, fireCount[i])
100+
val tryCancel = Try<Unit> { job.cancel() }
101+
check(!job.isActive)
102+
for (i in 0 until n) assertEquals(1, fireCount[i])
103+
check(tryCancel.exception is CompletionHandlerException)
104+
check(tryCancel.exception!!.cause is TestException)
105+
}
106+
107+
@Test
108+
fun testCancelledParent() {
109+
val parent = Job()
110+
parent.cancel()
111+
check(!parent.isActive)
112+
val child = Job(parent)
113+
check(!child.isActive)
114+
}
115+
116+
@Test
117+
fun testDisposeSingleHandler() {
118+
val job = Job()
119+
var fireCount = 0
120+
val handler = job.invokeOnCompletion { fireCount++ }
121+
handler.dispose()
122+
job.cancel()
123+
assertEquals(0, fireCount)
124+
}
125+
126+
@Test
127+
fun testDisposeMultipleHandler() {
128+
val job = Job()
129+
val handlerCount = 10
130+
var fireCount = 0
131+
val handlers = Array(handlerCount) { job.invokeOnCompletion { fireCount++ } }
132+
handlers.forEach { it.dispose() }
133+
job.cancel()
134+
assertEquals(0, fireCount)
135+
}
136+
}

core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/LaunchLazyTest.kt renamed to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonLaunchLazyTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
package kotlinx.coroutines.experimental
1818

19-
import org.junit.Test
19+
import kotlin.test.*
2020

21-
class LaunchLazyTest : TestBase() {
21+
class CommonLaunchLazyTest : TestBase() {
2222
@Test
2323
fun testLaunchAndYieldJoin() = runTest {
2424
expect(1)

core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/RunTest.kt renamed to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/CommonWithContextTest.kt

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,13 @@
1-
/*
2-
* Copyright 2016-2017 JetBrains s.r.o.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
1+
2+
@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
163

174
package kotlinx.coroutines.experimental
185

19-
import org.hamcrest.MatcherAssert.assertThat
20-
import org.hamcrest.core.IsEqual
21-
import org.junit.Test
22-
import java.io.IOException
6+
import kotlin.test.*
237
import kotlin.coroutines.experimental.ContinuationInterceptor
248
import kotlin.coroutines.experimental.CoroutineContext
259

26-
class RunTest : TestBase() {
10+
class CommonWithContextTest : TestBase() {
2711
@Test
2812
fun testSameContextNoSuspend() = runTest {
2913
expect(1)
@@ -35,7 +19,7 @@ class RunTest : TestBase() {
3519
expect(3) // still here
3620
"OK"
3721
}
38-
assertThat(result, IsEqual("OK"))
22+
assertEquals("OK", result)
3923
expect(4)
4024
// will wait for the first coroutine
4125
}
@@ -53,7 +37,7 @@ class RunTest : TestBase() {
5337
expect(5)
5438
"OK"
5539
}
56-
assertThat(result, IsEqual("OK"))
40+
assertEquals("OK", result)
5741
finish(6)
5842
}
5943

@@ -76,7 +60,7 @@ class RunTest : TestBase() {
7660
}
7761
"OK"
7862
}
79-
assertThat(result, IsEqual("OK"))
63+
assertEquals("OK", result)
8064
expect(5)
8165
// will wait for the first coroutine
8266
}
@@ -95,41 +79,17 @@ class RunTest : TestBase() {
9579
expect(5)
9680
job.cancel() // cancel out job!
9781
try {
98-
yield() // shall throw CancellationExpcetion
82+
yield() // shall throw CancellationException
9983
expectUnreached()
10084
} catch (e: CancellationException) {
10185
expect(6)
10286
}
10387
"OK"
10488
}
105-
assertThat(result, IsEqual("OK"))
89+
assertEquals("OK", result)
10690
finish(7)
10791
}
10892

109-
@Test
110-
fun testCommonPoolNoSuspend() = runTest {
111-
expect(1)
112-
val result = withContext(CommonPool) {
113-
expect(2)
114-
"OK"
115-
}
116-
assertThat(result, IsEqual("OK"))
117-
finish(3)
118-
}
119-
120-
@Test
121-
fun testCommonPoolWithSuspend() = runTest {
122-
expect(1)
123-
val result = withContext(CommonPool) {
124-
expect(2)
125-
delay(100)
126-
expect(3)
127-
"OK"
128-
}
129-
assertThat(result, IsEqual("OK"))
130-
finish(4)
131-
}
132-
13393
@Test
13494
fun testRunCancellableDefault() = runTest(
13595
expected = { it is JobCancellationException }
@@ -179,12 +139,12 @@ class RunTest : TestBase() {
179139
withContext(wrapperDispatcher(coroutineContext)) {
180140
expect(5)
181141
job!!.cancel() // cancel itself
182-
throw IOException() // but throw a different exception
142+
throw TestException() // but throw a different exception
183143
}
184144
} catch (e: Throwable) {
185145
expect(7)
186146
// make sure IOException, not CancellationException is thrown!
187-
check(e is IOException)
147+
check(e is TestException)
188148
}
189149
}
190150
expect(2)
@@ -204,4 +164,6 @@ class RunTest : TestBase() {
204164
}
205165
}
206166
}
167+
168+
private class TestException : Exception()
207169
}

core/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt renamed to common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/Try.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ public class Try<out T> private constructor(private val _value: Any?) {
2323

2424
public companion object {
2525
public operator fun <T> invoke(block: () -> T): Try<T> =
26-
try { Success(block()) } catch(e: Throwable) { Failure<T>(e) }
26+
try {
27+
Success(block())
28+
} catch(e: Throwable) {
29+
Failure<T>(e)
30+
}
2731
public fun <T> Success(value: T) = Try<T>(value)
2832
public fun <T> Failure(exception: Throwable) = Try<T>(Fail(exception))
2933
}

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Builders.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
2929
*
3030
* The [context] for the new coroutine can be explicitly specified.
3131
* See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`.
32-
* The [context][CoroutineScope.context] of the parent coroutine from its [scope][CoroutineScope] may be used,
32+
* The [context][CoroutineScope.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used,
3333
* in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine.
3434
* The parent job may be also explicitly specified using [parent] parameter.
3535
*
@@ -100,7 +100,7 @@ public fun launch(context: CoroutineContext, start: Boolean, block: suspend Coro
100100
* Other options can be specified via `start` parameter. See [CoroutineStart] for details.
101101
* A value of [CoroutineStart.LAZY] is not supported and produces [IllegalArgumentException].
102102
*/
103-
public suspend fun <T> withContext(
103+
public actual suspend fun <T> withContext(
104104
context: CoroutineContext,
105105
start: CoroutineStart = CoroutineStart.DEFAULT,
106106
block: suspend () -> T

0 commit comments

Comments
 (0)