blob: 55127e5c0bfe92f38e37bf7656956ab51d7a3aae [file] [log] [blame]
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-22237
package kotlinx.coroutines
import kotlin.test.*
class WithContextTest : TestBase() {
@Test
fun testThrowException() = runTest {
expect(1)
try {
withContext<Unit>(coroutineContext) {
expect(2)
throw AssertionError()
}
} catch (e: AssertionError) {
expect(3)
}
yield()
finish(4)
}
@Test
fun testThrowExceptionFromWrappedContext() = runTest {
expect(1)
try {
withContext<Unit>(wrapperDispatcher(coroutineContext)) {
expect(2)
throw AssertionError()
}
} catch (e: AssertionError) {
expect(3)
}
yield()
finish(4)
}
@Test
fun testSameContextNoSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch here
finish(5) // after main exits
}
expect(2)
val result = withContext(coroutineContext) { // same context!
expect(3) // still here
"OK".wrap()
}.unwrap()
assertEquals("OK", result)
expect(4)
// will wait for the first coroutine
}
@Test
fun testSameContextWithSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch here
expect(4)
}
expect(2)
val result = withContext(coroutineContext) { // same context!
expect(3) // still here
yield() // now yields to launch!
expect(5)
"OK".wrap()
}.unwrap()
assertEquals("OK", result)
finish(6)
}
@Test
fun testCancelWithJobNoSuspend() = runTest {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch to here
finish(6) // after main exits
}
expect(2)
val job = Job()
try {
withContext(coroutineContext + job) {
// same context + new job
expect(3) // still here
job.cancel() // cancel out job!
try {
yield() // shall throw CancellationException
expectUnreached()
} catch (e: CancellationException) {
expect(4)
}
"OK".wrap()
}
expectUnreached()
} catch (e: CancellationException) {
expect(5)
// will wait for the first coroutine
}
}
@Test
fun testCancelWithJobWithSuspend() = runTest(
expected = { it is CancellationException }
) {
expect(1)
launch(coroutineContext) { // make sure there is not early dispatch to here
expect(4)
}
expect(2)
val job = Job()
withContext(coroutineContext + job) { // same context + new job
expect(3) // still here
yield() // now yields to launch!
expect(5)
job.cancel() // cancel out job!
try {
yield() // shall throw CancellationException
expectUnreached()
} catch (e: CancellationException) {
finish(6)
}
"OK".wrap()
}
// still fails, because parent job was cancelled
expectUnreached()
}
@Test
fun testRunCancellableDefault() = runTest(
expected = { it is CancellationException }
) {
val job = Job()
job.cancel() // cancel before it has a chance to run
withContext(job + wrapperDispatcher(coroutineContext)) {
expectUnreached() // will get cancelled
}
}
@Test
fun testRunCancellationUndispatchedVsException() = runTest {
expect(1)
var job: Job? = null
job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
try {
// Same dispatcher, different context
withContext<Unit>(CoroutineName("testRunCancellationUndispatchedVsException")) {
expect(3)
yield() // must suspend
expect(5)
job!!.cancel() // cancel this job _before_ it throws
throw TestException()
}
} catch (e: TestException) {
// must have caught TextException
expect(6)
}
}
expect(4)
yield() // to coroutineScope
finish(7)
}
@Test
fun testRunCancellationDispatchedVsException() = runTest {
expect(1)
var job: Job? = null
job = launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
try {
// "Different" dispatcher (schedules execution back and forth)
withContext<Unit>(wrapperDispatcher(coroutineContext)) {
expect(4)
yield() // must suspend
expect(6)
job!!.cancel() // cancel this job _before_ it throws
throw TestException()
}
} catch (e: TestException) {
// must have caught TextException
expect(8)
}
}
expect(3)
yield() // withContext is next
expect(5)
yield() // withContext again
expect(7)
yield() // to catch block
finish(9)
}
@Test
fun testRunSelfCancellationWithException() = runTest {
expect(1)
var job: Job? = null
job = launch(Job()) {
try {
expect(3)
withContext<Unit>(wrapperDispatcher(coroutineContext)) {
require(isActive)
expect(5)
job!!.cancel()
require(!isActive)
throw TestException() // but throw an exception
}
} catch (e: Throwable) {
expect(7)
// make sure TestException, not CancellationException is thrown
assertTrue(e is TestException, "Caught $e")
}
}
expect(2)
yield() // to the launched job
expect(4)
yield() // again to the job
expect(6)
yield() // again to exception handler
finish(8)
}
@Test
fun testRunSelfCancellation() = runTest {
expect(1)
var job: Job? = null
job = launch(Job()) {
try {
expect(3)
withContext(wrapperDispatcher(coroutineContext)) {
require(isActive)
expect(5)
job!!.cancel() // cancel itself
require(!isActive)
"OK".wrap()
}
expectUnreached()
} catch (e: Throwable) {
expect(7)
// make sure CancellationException is thrown
assertTrue(e is CancellationException, "Caught $e")
}
}
expect(2)
yield() // to the launched job
expect(4)
yield() // again to the job
expect(6)
yield() // again to exception handler
finish(8)
}
@Test
fun testWithContextScopeFailure() = runTest {
expect(1)
try {
withContext(wrapperDispatcher(coroutineContext)) {
expect(2)
// launch a child that fails
launch {
expect(4)
throw TestException()
}
expect(3)
"OK".wrap()
}
expectUnreached()
} catch (e: TestException) {
// ensure that we can catch exception outside of the scope
expect(5)
}
finish(6)
}
@Test
fun testWithContextChildWaitSameContext() = runTest {
expect(1)
withContext(coroutineContext) {
expect(2)
launch {
// ^^^ schedules to main thread
expect(4) // waits before return
}
expect(3)
"OK".wrap()
}.unwrap()
finish(5)
}
@Test
fun testWithContextChildWaitWrappedContext() = runTest {
expect(1)
withContext(wrapperDispatcher(coroutineContext)) {
expect(2)
launch {
// ^^^ schedules to main thread
expect(4) // waits before return
}
expect(3)
"OK".wrap()
}.unwrap()
finish(5)
}
@Test
fun testIncompleteWithContextState() = runTest {
lateinit var ctxJob: Job
withContext(wrapperDispatcher(coroutineContext)) {
ctxJob = coroutineContext[Job]!!
ctxJob.invokeOnCompletion { }
}
ctxJob.join()
assertTrue(ctxJob.isCompleted)
assertFalse(ctxJob.isActive)
assertFalse(ctxJob.isCancelled)
}
@Test
fun testWithContextCancelledJob() = runTest {
expect(1)
val job = Job()
job.cancel()
try {
withContext(job) {
expectUnreached()
}
} catch (e: CancellationException) {
expect(2)
}
finish(3)
}
@Test
fun testWithContextCancelledThisJob() = runTest(
expected = { it is CancellationException }
) {
coroutineContext.cancel()
withContext(wrapperDispatcher(coroutineContext)) {
expectUnreached()
}
expectUnreached()
}
@Test
fun testSequentialCancellation() = runTest {
val job = launch {
expect(1)
withContext(wrapperDispatcher()) {
expect(2)
}
expectUnreached()
}
yield()
val job2 = launch {
expect(3)
job.cancel()
}
joinAll(job, job2)
finish(4)
}
private class Wrapper(val value: String) : Incomplete {
override val isActive: Boolean
get() = error("")
override val list: NodeList?
get() = error("")
}
private fun String.wrap() = Wrapper(this)
private fun Wrapper.unwrap() = value
}