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