Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
| 3 | */ |
| 4 | |
Roman Elizarov | 0950dfa | 2018-07-13 10:33:25 +0300 | [diff] [blame^] | 5 | package kotlinx.coroutines |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 6 | |
Roman Elizarov | 0950dfa | 2018-07-13 10:33:25 +0300 | [diff] [blame^] | 7 | import kotlinx.coroutines.internal.* |
| 8 | import kotlin.coroutines.* |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 9 | import kotlin.test.* |
| 10 | |
| 11 | class CoroutineScopeTest : TestBase() { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 12 | @Test |
| 13 | fun testScope() = runTest { |
| 14 | suspend fun callJobScoped() = coroutineScope { |
| 15 | expect(2) |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 16 | launch { |
| 17 | expect(4) |
| 18 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 19 | launch { |
| 20 | expect(5) |
| 21 | |
| 22 | launch { |
| 23 | expect(7) |
| 24 | } |
| 25 | |
| 26 | expect(6) |
| 27 | |
| 28 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 29 | expect(3) |
| 30 | 42 |
| 31 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 32 | expect(1) |
| 33 | val result = callJobScoped() |
| 34 | assertEquals(42, result) |
| 35 | yield() // Check we're not cancelled |
| 36 | finish(8) |
| 37 | } |
| 38 | |
| 39 | @Test |
| 40 | fun testScopeCancelledFromWithin() = runTest { |
| 41 | expect(1) |
| 42 | suspend fun callJobScoped() = coroutineScope { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 43 | launch { |
| 44 | expect(2) |
| 45 | delay(Long.MAX_VALUE) |
| 46 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 47 | launch { |
| 48 | expect(3) |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 49 | throw TestException2() |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 50 | } |
| 51 | } |
| 52 | |
| 53 | try { |
| 54 | callJobScoped() |
| 55 | expectUnreached() |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 56 | } catch (e: TestException2) { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 57 | expect(4) |
| 58 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 59 | yield() // Check we're not cancelled |
| 60 | finish(5) |
| 61 | } |
| 62 | |
| 63 | @Test |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 64 | fun testExceptionFromWithin() = runTest { |
| 65 | expect(1) |
| 66 | try { |
| 67 | expect(2) |
| 68 | coroutineScope { |
| 69 | expect(3) |
| 70 | throw TestException1() |
| 71 | } |
| 72 | expectUnreached() |
| 73 | } catch (e: TestException1) { |
| 74 | finish(4) |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | @Test |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 79 | fun testScopeBlockThrows() = runTest { |
| 80 | expect(1) |
| 81 | suspend fun callJobScoped(): Unit = coroutineScope { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 82 | launch { |
| 83 | expect(2) |
| 84 | delay(Long.MAX_VALUE) |
| 85 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 86 | yield() // let launch sleep |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 87 | throw TestException1() |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 88 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 89 | try { |
| 90 | callJobScoped() |
| 91 | expectUnreached() |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 92 | } catch (e: TestException1) { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 93 | expect(3) |
| 94 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 95 | yield() // Check we're not cancelled |
| 96 | finish(4) |
| 97 | } |
| 98 | |
| 99 | @Test |
| 100 | fun testOuterJobIsCancelled() = runTest { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 101 | suspend fun callJobScoped() = coroutineScope { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 102 | launch { |
| 103 | expect(3) |
| 104 | try { |
| 105 | delay(Long.MAX_VALUE) |
| 106 | } finally { |
| 107 | expect(4) |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | expect(2) |
| 112 | delay(Long.MAX_VALUE) |
| 113 | 42 |
| 114 | } |
| 115 | |
Roman Elizarov | 6e3ffb1 | 2018-09-14 13:46:58 +0300 | [diff] [blame] | 116 | val outerJob = launch(NonCancellable) { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 117 | expect(1) |
| 118 | try { |
| 119 | callJobScoped() |
| 120 | expectUnreached() |
Vsevolod Tolstopyatov | a2d8088 | 2018-09-24 19:51:49 +0300 | [diff] [blame] | 121 | } catch (e: CancellationException) { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 122 | expect(5) |
| 123 | assertNull(e.cause) |
| 124 | } |
| 125 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 126 | repeat(3) { yield() } // let everything to start properly |
| 127 | outerJob.cancel() |
| 128 | outerJob.join() |
| 129 | finish(6) |
| 130 | } |
| 131 | |
| 132 | @Test |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 133 | fun testAsyncCancellationFirst() = runTest { |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 134 | try { |
| 135 | expect(1) |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 136 | failedConcurrentSumFirst() |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 137 | expectUnreached() |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 138 | } catch (e: TestException1) { |
| 139 | finish(6) |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 140 | } |
| 141 | } |
| 142 | |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 143 | // First async child fails -> second is cancelled |
| 144 | private suspend fun failedConcurrentSumFirst(): Int = coroutineScope { |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 145 | val one = async<Int> { |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 146 | expect(3) |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 147 | throw TestException1() |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 148 | } |
| 149 | val two = async<Int>(start = CoroutineStart.ATOMIC) { |
| 150 | try { |
| 151 | expect(4) |
| 152 | delay(Long.MAX_VALUE) // Emulates very long computation |
| 153 | 42 |
| 154 | } finally { |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 155 | expect(5) |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 156 | } |
| 157 | } |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 158 | expect(2) |
| 159 | one.await() + two.await() |
| 160 | } |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 161 | |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 162 | @Test |
| 163 | fun testAsyncCancellationSecond() = runTest { |
| 164 | try { |
| 165 | expect(1) |
| 166 | failedConcurrentSumSecond() |
| 167 | expectUnreached() |
| 168 | } catch (e: TestException1) { |
| 169 | finish(6) |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | // Second async child fails -> fist is cancelled |
| 174 | private suspend fun failedConcurrentSumSecond(): Int = coroutineScope { |
| 175 | val one = async<Int> { |
| 176 | try { |
| 177 | expect(3) |
| 178 | delay(Long.MAX_VALUE) // Emulates very long computation |
| 179 | 42 |
| 180 | } finally { |
| 181 | expect(5) |
| 182 | } |
| 183 | } |
| 184 | val two = async<Int>(start = CoroutineStart.ATOMIC) { |
| 185 | expect(4) |
| 186 | throw TestException1() |
| 187 | } |
Roman Elizarov | c32579e | 2018-09-09 19:21:43 +0300 | [diff] [blame] | 188 | expect(2) |
| 189 | one.await() + two.await() |
| 190 | } |
| 191 | |
| 192 | @Test |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 193 | @Suppress("UNREACHABLE_CODE") |
| 194 | fun testDocumentationExample() = runTest { |
| 195 | suspend fun loadData() = coroutineScope { |
| 196 | expect(1) |
| 197 | val data = async { |
| 198 | try { |
| 199 | delay(Long.MAX_VALUE) |
| 200 | } finally { |
| 201 | expect(3) |
| 202 | } |
| 203 | } |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 204 | yield() |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 205 | // UI updater |
| 206 | withContext(coroutineContext) { |
| 207 | expect(2) |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 208 | throw TestException1() |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 209 | data.await() // Actually unreached |
| 210 | expectUnreached() |
| 211 | } |
| 212 | } |
| 213 | |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 214 | try { |
| 215 | loadData() |
| 216 | expectUnreached() |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 217 | } catch (e: TestException1) { |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 218 | finish(4) |
| 219 | } |
| 220 | } |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 221 | |
Andrew Mikhaylov | 3de2be4 | 2018-09-13 16:30:55 +0300 | [diff] [blame] | 222 | @Test |
Roman Elizarov | 0f94304 | 2018-10-08 16:13:01 +0300 | [diff] [blame] | 223 | fun testCoroutineScopeCancellationVsException() = runTest { |
| 224 | expect(1) |
| 225 | var job: Job? = null |
| 226 | job = launch(start = CoroutineStart.UNDISPATCHED) { |
| 227 | expect(2) |
| 228 | try { |
| 229 | coroutineScope { |
| 230 | expect(3) |
| 231 | yield() // must suspend |
| 232 | expect(5) |
| 233 | job!!.cancel() // cancel this job _before_ it throws |
| 234 | throw TestException1() |
| 235 | } |
| 236 | } catch (e: TestException1) { |
| 237 | // must have caught TextException |
| 238 | expect(6) |
| 239 | } |
| 240 | } |
| 241 | expect(4) |
| 242 | yield() // to coroutineScope |
| 243 | finish(7) |
| 244 | } |
| 245 | |
| 246 | @Test |
Andrew Mikhaylov | 3de2be4 | 2018-09-13 16:30:55 +0300 | [diff] [blame] | 247 | fun testScopePlusContext() { |
| 248 | assertSame(EmptyCoroutineContext, scopePlusContext(EmptyCoroutineContext, EmptyCoroutineContext)) |
| 249 | assertSame(Dispatchers.Default, scopePlusContext(EmptyCoroutineContext, Dispatchers.Default)) |
| 250 | assertSame(Dispatchers.Default, scopePlusContext(Dispatchers.Default, EmptyCoroutineContext)) |
| 251 | assertSame(Dispatchers.Default, scopePlusContext(Dispatchers.Default, Dispatchers.Default)) |
| 252 | assertSame(Dispatchers.Default, scopePlusContext(Dispatchers.Unconfined, Dispatchers.Default)) |
| 253 | assertSame(Dispatchers.Unconfined, scopePlusContext(Dispatchers.Default, Dispatchers.Unconfined)) |
| 254 | assertSame(Dispatchers.Unconfined, scopePlusContext(Dispatchers.Unconfined, Dispatchers.Unconfined)) |
| 255 | } |
| 256 | |
| 257 | private fun scopePlusContext(c1: CoroutineContext, c2: CoroutineContext) = |
Roman Elizarov | 5c44afb | 2018-09-25 14:53:08 +0300 | [diff] [blame] | 258 | (ContextScope(c1) + c2).coroutineContext |
Roman Elizarov | 52bfdab | 2018-09-27 19:17:16 +0300 | [diff] [blame] | 259 | |
| 260 | private class TestException1 : Exception() |
| 261 | private class TestException2 : Exception() |
Vsevolod Tolstopyatov | 79414ec | 2018-08-30 16:50:56 +0300 | [diff] [blame] | 262 | } |