|
| 1 | +/* |
| 2 | + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
| 3 | + */ |
| 4 | + |
| 5 | +package kotlinx.coroutines |
| 6 | + |
| 7 | +import org.junit.* |
| 8 | +import org.junit.Test |
| 9 | +import org.junit.runner.* |
| 10 | +import org.junit.runners.* |
| 11 | +import java.util.concurrent.* |
| 12 | +import kotlin.coroutines.* |
| 13 | +import kotlin.test.* |
| 14 | + |
| 15 | +@RunWith(Parameterized::class) |
| 16 | +class FailingCoroutinesMachineryTest( |
| 17 | + private val element: CoroutineContext.Element, |
| 18 | + private val dispatcher: CoroutineDispatcher |
| 19 | +) : TestBase() { |
| 20 | + |
| 21 | + private var caught: Throwable? = null |
| 22 | + private val latch = CountDownLatch(1) |
| 23 | + private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t;latch.countDown() } |
| 24 | + private val lazyOuterDispatcher = lazy { newFixedThreadPoolContext(1, "") } |
| 25 | + |
| 26 | + private object FailingUpdate : ThreadContextElement<Unit> { |
| 27 | + private object Key : CoroutineContext.Key<MyElement> |
| 28 | + |
| 29 | + override val key: CoroutineContext.Key<*> get() = Key |
| 30 | + |
| 31 | + override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) { |
| 32 | + } |
| 33 | + |
| 34 | + override fun updateThreadContext(context: CoroutineContext) { |
| 35 | + throw TestException("Prevent a coroutine from starting right here for some reason") |
| 36 | + } |
| 37 | + |
| 38 | + override fun toString() = "FailingUpdate" |
| 39 | + } |
| 40 | + |
| 41 | + private object FailingRestore : ThreadContextElement<Unit> { |
| 42 | + private object Key : CoroutineContext.Key<MyElement> |
| 43 | + |
| 44 | + override val key: CoroutineContext.Key<*> get() = Key |
| 45 | + |
| 46 | + override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) { |
| 47 | + throw TestException("Prevent a coroutine from starting right here for some reason") |
| 48 | + } |
| 49 | + |
| 50 | + override fun updateThreadContext(context: CoroutineContext) { |
| 51 | + } |
| 52 | + |
| 53 | + override fun toString() = "FailingRestore" |
| 54 | + } |
| 55 | + |
| 56 | + private object ThrowingDispatcher : CoroutineDispatcher() { |
| 57 | + override fun dispatch(context: CoroutineContext, block: Runnable) { |
| 58 | + throw TestException() |
| 59 | + } |
| 60 | + |
| 61 | + override fun toString() = "ThrowingDispatcher" |
| 62 | + } |
| 63 | + |
| 64 | + private object ThrowingDispatcher2 : CoroutineDispatcher() { |
| 65 | + override fun dispatch(context: CoroutineContext, block: Runnable) { |
| 66 | + block.run() |
| 67 | + } |
| 68 | + |
| 69 | + override fun isDispatchNeeded(context: CoroutineContext): Boolean { |
| 70 | + throw TestException() |
| 71 | + } |
| 72 | + |
| 73 | + override fun toString() = "ThrowingDispatcher2" |
| 74 | + } |
| 75 | + |
| 76 | + @After |
| 77 | + fun tearDown() { |
| 78 | + runCatching { (dispatcher as? ExecutorCoroutineDispatcher)?.close() } |
| 79 | + if (lazyOuterDispatcher.isInitialized()) lazyOuterDispatcher.value.close() |
| 80 | + } |
| 81 | + |
| 82 | + companion object { |
| 83 | + @JvmStatic |
| 84 | + @Parameterized.Parameters(name = "Element: {0}, dispatcher: {1}") |
| 85 | + fun dispatchers(): List<Array<Any>> { |
| 86 | + val elements = listOf<Any>(FailingRestore, FailingUpdate) |
| 87 | + val dispatchers = listOf<Any>( |
| 88 | + Dispatchers.Unconfined, |
| 89 | + Dispatchers.Default, |
| 90 | + Executors.newFixedThreadPool(1).asCoroutineDispatcher(), |
| 91 | + Executors.newScheduledThreadPool(1).asCoroutineDispatcher(), |
| 92 | + ThrowingDispatcher, ThrowingDispatcher2 |
| 93 | + ) |
| 94 | + |
| 95 | + return elements.flatMap { element -> |
| 96 | + dispatchers.map { dispatcher -> |
| 97 | + arrayOf(element, dispatcher) |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + @Test |
| 104 | + fun testElement() = runTest { |
| 105 | + launch(NonCancellable + dispatcher + exceptionHandler + element) {} |
| 106 | + checkException() |
| 107 | + } |
| 108 | + |
| 109 | + @Test |
| 110 | + fun testNestedElement() = runTest { |
| 111 | + launch(NonCancellable + dispatcher + exceptionHandler) { |
| 112 | + launch(element) { } |
| 113 | + } |
| 114 | + checkException() |
| 115 | + } |
| 116 | + |
| 117 | + @Test |
| 118 | + fun testNestedDispatcherAndElement() = runTest { |
| 119 | + launch(lazyOuterDispatcher.value + NonCancellable + exceptionHandler) { |
| 120 | + launch(element + dispatcher) { } |
| 121 | + } |
| 122 | + checkException() |
| 123 | + } |
| 124 | + |
| 125 | + private fun checkException() { |
| 126 | + latch.await(2, TimeUnit.SECONDS) |
| 127 | + val e = caught |
| 128 | + assertNotNull(e) |
| 129 | + // First condition -- failure in context element |
| 130 | + val firstCondition = e is DispatchException && e.cause is TestException |
| 131 | + // Second condition -- failure from isDispatchNeeded (#880) |
| 132 | + val secondCondition = e is TestException |
| 133 | + assertTrue(firstCondition xor secondCondition) |
| 134 | + } |
| 135 | +} |
0 commit comments