• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 @file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-22237
3 
4 package kotlinx.coroutines
5 
6 import kotlinx.coroutines.testing.*
7 import kotlin.test.*
8 
9 class WithContextTest : TestBase() {
10 
11     @Test
<lambda>null12     fun testThrowException() = runTest {
13         expect(1)
14         try {
15             withContext<Unit>(coroutineContext) {
16                 expect(2)
17                 throw AssertionError()
18             }
19         } catch (e: AssertionError) {
20             expect(3)
21         }
22 
23         yield()
24         finish(4)
25     }
26 
27     @Test
<lambda>null28     fun testThrowExceptionFromWrappedContext() = runTest {
29         expect(1)
30         try {
31             withContext<Unit>(wrapperDispatcher(coroutineContext)) {
32                 expect(2)
33                 throw AssertionError()
34             }
35         } catch (e: AssertionError) {
36             expect(3)
37         }
38 
39         yield()
40         finish(4)
41     }
42 
43     @Test
<lambda>null44     fun testSameContextNoSuspend() = runTest {
45         expect(1)
46         launch(coroutineContext) { // make sure there is not early dispatch here
47             finish(5) // after main exits
48         }
49         expect(2)
50         val result = withContext(coroutineContext) { // same context!
51             expect(3) // still here
52             "OK".wrap()
53         }.unwrap()
54         assertEquals("OK", result)
55         expect(4)
56         // will wait for the first coroutine
57     }
58 
59     @Test
<lambda>null60     fun testSameContextWithSuspend() = runTest {
61         expect(1)
62         launch(coroutineContext) { // make sure there is not early dispatch here
63             expect(4)
64         }
65         expect(2)
66         val result = withContext(coroutineContext) { // same context!
67             expect(3) // still here
68             yield() // now yields to launch!
69             expect(5)
70             "OK".wrap()
71         }.unwrap()
72         assertEquals("OK", result)
73         finish(6)
74     }
75 
76     @Test
<lambda>null77     fun testCancelWithJobNoSuspend() = runTest {
78         expect(1)
79         launch(coroutineContext) { // make sure there is not early dispatch to here
80             finish(6) // after main exits
81         }
82         expect(2)
83         val job = Job()
84         try {
85             withContext(coroutineContext + job) {
86                 // same context + new job
87                 expect(3) // still here
88                 job.cancel() // cancel out job!
89                 try {
90                     yield() // shall throw CancellationException
91                     expectUnreached()
92                 } catch (e: CancellationException) {
93                     expect(4)
94                 }
95                 "OK".wrap()
96             }
97 
98             expectUnreached()
99         } catch (e: CancellationException) {
100             expect(5)
101             // will wait for the first coroutine
102         }
103     }
104 
105     @Test
testCancelWithJobWithSuspendnull106     fun testCancelWithJobWithSuspend() = runTest(
107         expected = { it is CancellationException }
<lambda>null108     ) {
109         expect(1)
110         launch(coroutineContext) { // make sure there is not early dispatch to here
111             expect(4)
112         }
113         expect(2)
114         val job = Job()
115         withContext(coroutineContext + job) { // same context + new job
116             expect(3) // still here
117             yield() // now yields to launch!
118             expect(5)
119             job.cancel() // cancel out job!
120             try {
121                 yield() // shall throw CancellationException
122                 expectUnreached()
123             } catch (e: CancellationException) {
124                 finish(6)
125             }
126             "OK".wrap()
127         }
128         // still fails, because parent job was cancelled
129         expectUnreached()
130     }
131 
132     @Test
testRunCancellableDefaultnull133     fun testRunCancellableDefault() = runTest(
134         expected = { it is CancellationException }
<lambda>null135     ) {
136         val job = Job()
137         job.cancel() // cancel before it has a chance to run
138         withContext(job + wrapperDispatcher(coroutineContext)) {
139             expectUnreached() // will get cancelled
140         }
141     }
142 
143     @Test
<lambda>null144     fun testRunCancellationUndispatchedVsException() = runTest {
145         expect(1)
146         var job: Job? = null
147         job = launch(start = CoroutineStart.UNDISPATCHED) {
148             expect(2)
149             try {
150                 // Same dispatcher, different context
151                 withContext<Unit>(CoroutineName("testRunCancellationUndispatchedVsException")) {
152                     expect(3)
153                     yield() // must suspend
154                     expect(5)
155                     job!!.cancel() // cancel this job _before_ it throws
156                     throw TestException()
157                 }
158             } catch (e: TestException) {
159                 // must have caught TextException
160                 expect(6)
161             }
162         }
163         expect(4)
164         yield() // to coroutineScope
165         finish(7)
166     }
167 
168     @Test
<lambda>null169     fun testRunCancellationDispatchedVsException() = runTest {
170         expect(1)
171         var job: Job? = null
172         job = launch(start = CoroutineStart.UNDISPATCHED) {
173             expect(2)
174             try {
175                 // "Different" dispatcher (schedules execution back and forth)
176                 withContext<Unit>(wrapperDispatcher(coroutineContext)) {
177                     expect(4)
178                     yield() // must suspend
179                     expect(6)
180                     job!!.cancel() // cancel this job _before_ it throws
181                     throw TestException()
182                 }
183             } catch (e: TestException) {
184                 // must have caught TextException
185                 expect(8)
186             }
187         }
188         expect(3)
189         yield() // withContext is next
190         expect(5)
191         yield() // withContext again
192         expect(7)
193         yield() // to catch block
194         finish(9)
195     }
196 
197     @Test
<lambda>null198     fun testRunSelfCancellationWithException() = runTest {
199         expect(1)
200         var job: Job? = null
201         job = launch(Job()) {
202             try {
203                 expect(3)
204                 withContext<Unit>(wrapperDispatcher(coroutineContext)) {
205                     require(isActive)
206                     expect(5)
207                     job!!.cancel()
208                     require(!isActive)
209                     throw TestException() // but throw an exception
210                 }
211             } catch (e: Throwable) {
212                 expect(7)
213                 // make sure TestException, not CancellationException is thrown
214                 assertIs<TestException>(e, "Caught $e")
215             }
216         }
217         expect(2)
218         yield() // to the launched job
219         expect(4)
220         yield() // again to the job
221         expect(6)
222         yield() // again to exception handler
223         finish(8)
224     }
225 
226     @Test
<lambda>null227     fun testRunSelfCancellation() = runTest {
228         expect(1)
229         var job: Job? = null
230         job = launch(Job()) {
231             try {
232                 expect(3)
233                 withContext(wrapperDispatcher(coroutineContext)) {
234                     require(isActive)
235                     expect(5)
236                     job!!.cancel() // cancel itself
237                     require(!isActive)
238                     "OK".wrap()
239                 }
240                 expectUnreached()
241             } catch (e: Throwable) {
242                 expect(7)
243                 // make sure CancellationException is thrown
244                 assertIs<CancellationException>(e, "Caught $e")
245             }
246         }
247 
248         expect(2)
249         yield() // to the launched job
250         expect(4)
251         yield() // again to the job
252         expect(6)
253         yield() // again to exception handler
254         finish(8)
255     }
256 
257     @Test
<lambda>null258     fun testWithContextScopeFailure() = runTest {
259         expect(1)
260         try {
261             withContext(wrapperDispatcher(coroutineContext)) {
262                 expect(2)
263                 // launch a child that fails
264                 launch {
265                     expect(4)
266                     throw TestException()
267                 }
268                 expect(3)
269                 "OK".wrap()
270             }
271             expectUnreached()
272         } catch (e: TestException) {
273             // ensure that we can catch exception outside of the scope
274             expect(5)
275         }
276         finish(6)
277     }
278 
279     @Test
<lambda>null280     fun testWithContextChildWaitSameContext() = runTest {
281         expect(1)
282         withContext(coroutineContext) {
283             expect(2)
284             launch {
285                 // ^^^ schedules to main thread
286                 expect(4) // waits before return
287             }
288             expect(3)
289             "OK".wrap()
290         }.unwrap()
291         finish(5)
292     }
293 
294     @Test
<lambda>null295     fun testWithContextChildWaitWrappedContext() = runTest {
296         expect(1)
297         withContext(wrapperDispatcher(coroutineContext)) {
298             expect(2)
299             launch {
300                 // ^^^ schedules to main thread
301                 expect(4) // waits before return
302             }
303             expect(3)
304             "OK".wrap()
305         }.unwrap()
306         finish(5)
307     }
308 
309     @Test
<lambda>null310     fun testIncompleteWithContextState() = runTest {
311         lateinit var ctxJob: Job
312         withContext(wrapperDispatcher(coroutineContext)) {
313             ctxJob = coroutineContext[Job]!!
314             ctxJob.invokeOnCompletion { }
315         }
316 
317         ctxJob.join()
318         assertTrue(ctxJob.isCompleted)
319         assertFalse(ctxJob.isActive)
320         assertFalse(ctxJob.isCancelled)
321     }
322 
323     @Test
<lambda>null324     fun testWithContextCancelledJob() = runTest {
325         expect(1)
326         val job = Job()
327         job.cancel()
328         try {
329             withContext(job) {
330                 expectUnreached()
331             }
332         } catch (e: CancellationException) {
333             expect(2)
334         }
335         finish(3)
336     }
337 
338     @Test
testWithContextCancelledThisJobnull339     fun testWithContextCancelledThisJob() = runTest(
340         expected = { it is CancellationException }
<lambda>null341     ) {
342         coroutineContext.cancel()
343         withContext(wrapperDispatcher(coroutineContext)) {
344             expectUnreached()
345         }
346         expectUnreached()
347     }
348 
349     @Test
<lambda>null350     fun testSequentialCancellation() = runTest {
351         val job = launch {
352             expect(1)
353             withContext(wrapperDispatcher()) {
354                 expect(2)
355             }
356             expectUnreached()
357         }
358 
359         yield()
360         val job2 = launch {
361             expect(3)
362             job.cancel()
363         }
364 
365         joinAll(job, job2)
366         finish(4)
367     }
368 
369     private class Wrapper(val value: String) : Incomplete {
370         override val isActive: Boolean
371             get() =  error("")
372         override val list: NodeList?
373             get() = error("")
374     }
375 
Stringnull376     private fun String.wrap() = Wrapper(this)
377     private fun Wrapper.unwrap() = value
378 }
379