blob: bb5af793dcd65044022cb803d4b238693907e67c [file] [log] [blame]
Roman Elizarov9d5abcd2017-12-21 16:54:30 +03001
Roman Elizarova7db8ec2017-12-21 22:45:12 +03002/*
3 * Copyright 2016-2017 JetBrains s.r.o.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030018@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
Roman Elizarov4fe18012017-03-07 17:32:48 +030019
20package kotlinx.coroutines.experimental
21
Roman Elizarov9fe5f462018-02-21 19:05:52 +030022import kotlin.coroutines.experimental.*
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030023import kotlin.test.*
Roman Elizarov4fe18012017-03-07 17:32:48 +030024
Roman Elizarovaa461cf2018-04-11 13:20:29 +030025class WithContextTest : TestBase() {
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +030026
Roman Elizarov4fe18012017-03-07 17:32:48 +030027 @Test
Roman Elizarov8b38fa22017-09-27 17:44:31 +030028 fun testSameContextNoSuspend() = runTest {
Roman Elizarov4fe18012017-03-07 17:32:48 +030029 expect(1)
Roman Elizarov43e3af72017-07-21 16:01:31 +030030 launch(coroutineContext) { // make sure there is not early dispatch here
Roman Elizarov8b38fa22017-09-27 17:44:31 +030031 finish(5) // after main exits
Roman Elizarov4fe18012017-03-07 17:32:48 +030032 }
33 expect(2)
Roman Elizarovf9e13f52017-12-21 12:23:15 +030034 val result = withContext(coroutineContext) { // same context!
Roman Elizarov4fe18012017-03-07 17:32:48 +030035 expect(3) // still here
36 "OK"
37 }
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030038 assertEquals("OK", result)
Roman Elizarov8b38fa22017-09-27 17:44:31 +030039 expect(4)
40 // will wait for the first coroutine
Roman Elizarov4fe18012017-03-07 17:32:48 +030041 }
42
43 @Test
Roman Elizarov8b38fa22017-09-27 17:44:31 +030044 fun testSameContextWithSuspend() = runTest {
Roman Elizarov4fe18012017-03-07 17:32:48 +030045 expect(1)
Roman Elizarov43e3af72017-07-21 16:01:31 +030046 launch(coroutineContext) { // make sure there is not early dispatch here
Roman Elizarov4fe18012017-03-07 17:32:48 +030047 expect(4)
48 }
49 expect(2)
Roman Elizarovf9e13f52017-12-21 12:23:15 +030050 val result = withContext(coroutineContext) { // same context!
Roman Elizarov4fe18012017-03-07 17:32:48 +030051 expect(3) // still here
52 yield() // now yields to launch!
53 expect(5)
54 "OK"
55 }
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030056 assertEquals("OK", result)
Roman Elizarov4fe18012017-03-07 17:32:48 +030057 finish(6)
58 }
59
60 @Test
Roman Elizarov8b38fa22017-09-27 17:44:31 +030061 fun testCancelWithJobNoSuspend() = runTest {
Roman Elizarov4fe18012017-03-07 17:32:48 +030062 expect(1)
Roman Elizarov43e3af72017-07-21 16:01:31 +030063 launch(coroutineContext) { // make sure there is not early dispatch to here
Roman Elizarov8b38fa22017-09-27 17:44:31 +030064 finish(6) // after main exits
Roman Elizarov4fe18012017-03-07 17:32:48 +030065 }
66 expect(2)
67 val job = Job()
Roman Elizarovf9e13f52017-12-21 12:23:15 +030068 val result = withContext(coroutineContext + job) { // same context + new job
Roman Elizarov4fe18012017-03-07 17:32:48 +030069 expect(3) // still here
70 job.cancel() // cancel out job!
71 try {
72 yield() // shall throw CancellationException
73 expectUnreached()
74 } catch (e: CancellationException) {
75 expect(4)
76 }
77 "OK"
78 }
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030079 assertEquals("OK", result)
Roman Elizarov8b38fa22017-09-27 17:44:31 +030080 expect(5)
81 // will wait for the first coroutine
Roman Elizarov4fe18012017-03-07 17:32:48 +030082 }
83
84 @Test
Roman Elizarov8b38fa22017-09-27 17:44:31 +030085 fun testCancelWithJobWithSuspend() = runTest {
Roman Elizarov4fe18012017-03-07 17:32:48 +030086 expect(1)
Roman Elizarov43e3af72017-07-21 16:01:31 +030087 launch(coroutineContext) { // make sure there is not early dispatch to here
Roman Elizarov4fe18012017-03-07 17:32:48 +030088 expect(4)
89 }
90 expect(2)
91 val job = Job()
Roman Elizarovf9e13f52017-12-21 12:23:15 +030092 val result = withContext(coroutineContext + job) { // same context + new job
Roman Elizarov4fe18012017-03-07 17:32:48 +030093 expect(3) // still here
94 yield() // now yields to launch!
95 expect(5)
96 job.cancel() // cancel out job!
97 try {
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030098 yield() // shall throw CancellationException
Roman Elizarov4fe18012017-03-07 17:32:48 +030099 expectUnreached()
100 } catch (e: CancellationException) {
101 expect(6)
102 }
103 "OK"
104 }
Roman Elizarov9d5abcd2017-12-21 16:54:30 +0300105 assertEquals("OK", result)
Roman Elizarov4fe18012017-03-07 17:32:48 +0300106 finish(7)
107 }
108
109 @Test
Roman Elizarov8b38fa22017-09-27 17:44:31 +0300110 fun testRunCancellableDefault() = runTest(
111 expected = { it is JobCancellationException }
112 ) {
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300113 val job = Job()
114 job.cancel() // cancel before it has a chance to run
Roman Elizarovf9e13f52017-12-21 12:23:15 +0300115 withContext(job + wrapperDispatcher(coroutineContext)) {
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300116 expectUnreached() // will get cancelled
117 }
118 }
119
Roman Elizarov8b38fa22017-09-27 17:44:31 +0300120 @Test
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300121 fun testRunAtomicTryCancel() = runTest {
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300122 expect(1)
123 val job = Job()
124 job.cancel() // try to cancel before it has a chance to run
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300125
126 try {
127 withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.ATOMIC) {
128 require(isActive)
129 // but start atomically
130 expect(2)
131 yield() // but will cancel here
132 expectUnreached()
133 }
134 } catch (e: JobCancellationException) {
135 // This block should be invoked *after* context body
136 finish(3)
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300137 }
138 }
139
Roman Elizarov8b38fa22017-09-27 17:44:31 +0300140 @Test
141 fun testRunUndispatchedTryCancel() = runTest(
142 expected = { it is JobCancellationException }
143 ) {
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300144 expect(1)
145 val job = Job()
146 job.cancel() // try to cancel before it has a chance to run
Roman Elizarovf9e13f52017-12-21 12:23:15 +0300147 withContext(job + wrapperDispatcher(coroutineContext), CoroutineStart.UNDISPATCHED) { // but start atomically
Vsevolod Tolstopyatovf3a50132018-04-16 19:41:20 +0300148 require(isActive)
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300149 finish(2)
150 yield() // but will cancel here
151 expectUnreached()
152 }
153 }
154
Roman Elizarovbcdd8e12017-10-20 16:42:06 +0800155 @Test
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +0300156 fun testRunSelfCancellationWithException() = runTest {
Roman Elizarovbcdd8e12017-10-20 16:42:06 +0800157 expect(1)
158 var job: Job? = null
159 job = launch(coroutineContext) {
160 try {
161 expect(3)
Roman Elizarovf9e13f52017-12-21 12:23:15 +0300162 withContext(wrapperDispatcher(coroutineContext)) {
Vsevolod Tolstopyatovf3a50132018-04-16 19:41:20 +0300163 require(isActive)
Roman Elizarovbcdd8e12017-10-20 16:42:06 +0800164 expect(5)
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300165 require(job!!.cancel()) // cancel itself
166 require(!job!!.cancel(AssertionError())) // cancel again, no success here
167 require(!isActive)
Roman Elizarov9d5abcd2017-12-21 16:54:30 +0300168 throw TestException() // but throw a different exception
Roman Elizarovbcdd8e12017-10-20 16:42:06 +0800169 }
170 } catch (e: Throwable) {
171 expect(7)
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300172 // make sure TestException, not CancellationException or AssertionError is thrown
Roman Elizarov0d7323a2018-01-29 20:59:15 +0300173 assertTrue(e is TestException, "Caught $e")
Roman Elizarovbcdd8e12017-10-20 16:42:06 +0800174 }
175 }
176 expect(2)
177 yield() // to the launched job
178 expect(4)
179 yield() // again to the job
180 expect(6)
181 yield() // again to exception handler
182 finish(8)
183 }
184
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +0300185 @Test
186 fun testRunSelfCancellation() = runTest {
187 expect(1)
188 var job: Job? = null
189 job = launch(coroutineContext) {
190 try {
191 expect(3)
192 withContext(wrapperDispatcher(coroutineContext)) {
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300193 require(isActive)
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +0300194 expect(5)
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300195 require(job!!.cancel()) // cancel itself
196 require(!job!!.cancel(AssertionError())) // cancel again, no success here
197 require(!isActive)
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +0300198 }
199 } catch (e: Throwable) {
Vsevolod Tolstopyatovf3a50132018-04-16 19:41:20 +0300200 expect(7)
Vsevolod Tolstopyatov4aa18aa2018-04-17 15:43:12 +0300201 // make sure TestException, not CancellationException or AssertionError is thrown!
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +0300202 assertTrue(e is JobCancellationException, "Caught $e")
203 }
204 }
205
206 expect(2)
207 yield() // to the launched job
208 expect(4)
209 yield() // again to the job
210 expect(6)
211 yield() // again to exception handler
212 finish(8)
213 }
214
Roman Elizarov4eae2a82017-05-17 20:55:27 +0300215 private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext {
216 val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher
217 return object : CoroutineDispatcher() {
218 override fun dispatch(context: CoroutineContext, block: Runnable) {
219 dispatcher.dispatch(context, block)
220 }
221 }
222 }
Roman Elizarov9d5abcd2017-12-21 16:54:30 +0300223
224 private class TestException : Exception()
Vsevolod Tolstopyatov931587a2018-04-16 17:51:12 +0300225}