1 /*
2  * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 @file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED", "DEPRECATION") // KT-21913
6 
7 package kotlinx.coroutines
8 
9 import kotlin.test.*
10 
11 class CompletableDeferredTest : TestBase() {
12     @Test
13     fun testFresh() {
14         val c = CompletableDeferred<String>()
15         checkFresh(c)
16     }
17 
18     @Test
19     fun testComplete() {
20         val c = CompletableDeferred<String>()
21         assertEquals(true, c.complete("OK"))
22         checkCompleteOk(c)
23         assertEquals("OK", c.getCompleted())
24         assertEquals(false, c.complete("OK"))
25         checkCompleteOk(c)
26         assertEquals("OK", c.getCompleted())
27     }
28 
29     @Test
30     fun testCompleteWithIncompleteResult() {
31         val c = CompletableDeferred<DisposableHandle>()
32         assertEquals(true, c.complete(c.invokeOnCompletion {  }))
33         checkCompleteOk(c)
34         assertEquals(false,  c.complete(c.invokeOnCompletion {  }))
35         checkCompleteOk(c)
36         assertTrue(c.getCompleted() is Incomplete)
37     }
38 
39     private fun checkFresh(c: CompletableDeferred<*>) {
40         assertEquals(true, c.isActive)
41         assertEquals(false, c.isCancelled)
42         assertEquals(false, c.isCompleted)
43         assertThrows<IllegalStateException> { c.getCancellationException() }
44         assertThrows<IllegalStateException> { c.getCompleted() }
45         assertThrows<IllegalStateException> { c.getCompletionExceptionOrNull() }
46     }
47 
48     private fun checkCompleteOk(c: CompletableDeferred<*>) {
49         assertEquals(false, c.isActive)
50         assertEquals(false, c.isCancelled)
51         assertEquals(true, c.isCompleted)
52         assertTrue(c.getCancellationException() is JobCancellationException)
53         assertEquals(null, c.getCompletionExceptionOrNull())
54     }
55 
56     private fun checkCancel(c: CompletableDeferred<String>) {
57         assertEquals(false, c.isActive)
58         assertEquals(true, c.isCancelled)
59         assertEquals(true, c.isCompleted)
60         assertThrows<CancellationException> { c.getCompleted() }
61         assertTrue(c.getCompletionExceptionOrNull() is CancellationException)
62     }
63 
64     @Test
65     fun testCancelWithException() {
66         val c = CompletableDeferred<String>()
67         assertEquals(true, c.completeExceptionally(TestException()))
68         checkCancelWithException(c)
69         assertEquals(false, c.completeExceptionally(TestException()))
70         checkCancelWithException(c)
71     }
72 
73     private fun checkCancelWithException(c: CompletableDeferred<String>) {
74         assertEquals(false, c.isActive)
75         assertEquals(true, c.isCancelled)
76         assertEquals(true, c.isCompleted)
77         assertTrue(c.getCancellationException() is JobCancellationException)
78         assertThrows<TestException> { c.getCompleted() }
79         assertTrue(c.getCompletionExceptionOrNull() is TestException)
80     }
81 
82     @Test
83     fun testParentCancelsChild() {
84         val parent = Job()
85         val c = CompletableDeferred<String>(parent)
86         checkFresh(c)
87         parent.cancel()
88         assertEquals(false, parent.isActive)
89         assertEquals(true, parent.isCancelled)
90         assertEquals(false, c.isActive)
91         assertEquals(true, c.isCancelled)
92         assertEquals(true, c.isCompleted)
93         assertThrows<CancellationException> { c.getCompleted() }
94         assertTrue(c.getCompletionExceptionOrNull() is CancellationException)
95     }
96 
97     @Test
98     fun testParentActiveOnChildCompletion() {
99         val parent = Job()
100         val c = CompletableDeferred<String>(parent)
101         checkFresh(c)
102         assertEquals(true, parent.isActive)
103         assertEquals(true, c.complete("OK"))
104         checkCompleteOk(c)
105         assertEquals(true, parent.isActive)
106     }
107 
108     @Test
109     fun testParentCancelledOnChildException() {
110         val parent = Job()
111         val c = CompletableDeferred<String>(parent)
112         checkFresh(c)
113         assertEquals(true, parent.isActive)
114         assertEquals(true, c.completeExceptionally(TestException()))
115         checkCancelWithException(c)
116         assertEquals(false, parent.isActive)
117         assertEquals(true, parent.isCancelled)
118     }
119 
120     @Test
121     fun testParentActiveOnChildCancellation() {
122         val parent = Job()
123         val c = CompletableDeferred<String>(parent)
124         checkFresh(c)
125         assertEquals(true, parent.isActive)
126         c.cancel()
127         checkCancel(c)
128         assertEquals(true, parent.isActive)
129     }
130 
131     @Test
132     fun testAwait() = runTest {
133         expect(1)
134         val c = CompletableDeferred<String>()
135         launch(start = CoroutineStart.UNDISPATCHED) {
136             expect(2)
137             assertEquals("OK", c.await()) // suspends
138             expect(5)
139             assertEquals("OK", c.await()) // does not suspend
140             expect(6)
141         }
142         expect(3)
143         c.complete("OK")
144         expect(4)
145         yield() // to launch
146         finish(7)
147     }
148 
149     @Test
150     fun testCancelAndAwaitParentWaitChildren() = runTest {
151         expect(1)
152         val parent = CompletableDeferred<String>()
153         launch(parent, start = CoroutineStart.UNDISPATCHED) {
154             expect(2)
155             try {
156                 yield() // will get cancelled
157             } finally {
158                 expect(5)
159             }
160         }
161         expect(3)
162         parent.cancel()
163         expect(4)
164         try {
165             parent.await()
166         } catch (e: CancellationException) {
167             finish(6)
168         }
169     }
170 
171     @Test
172     fun testCompleteAndAwaitParentWaitChildren() = runTest {
173         expect(1)
174         val parent = CompletableDeferred<String>()
175         launch(parent, start = CoroutineStart.UNDISPATCHED) {
176             expect(2)
177             try {
178                 yield() // will get cancelled
179             } finally {
180                 expect(5)
181             }
182         }
183         expect(3)
184         parent.complete("OK")
185         expect(4)
186         assertEquals("OK", parent.await())
187         finish(6)
188     }
189 
190     private inline fun <reified T: Throwable> assertThrows(block: () -> Unit) {
191         try {
192             block()
193             fail("Should not complete normally")
194         } catch (e: Throwable) {
195             assertTrue(e is T)
196         }
197     }
198 }