77package kotlinx.coroutines.experimental
88
99import kotlinx.coroutines.experimental.channels.*
10+ import kotlin.coroutines.experimental.*
1011import kotlin.test.*
1112
1213/* *
1314 * Systematically tests that various builders cancel parent on failure.
1415 */
1516class ParentCancellationTest : TestBase () {
1617 @Test
17- @Ignore // todo: shall be passing in Supervisor branch
1818 fun testJobChild () = runTest {
19- testParentCancellation { fail ->
19+ testParentCancellation(expectUnhandled = true ) { fail ->
2020 val child = Job (coroutineContext[Job ])
2121 CoroutineScope (coroutineContext + child).fail()
2222 }
2323 }
2424
25+ @Test
26+ fun testSupervisorJobChild () = runTest {
27+ testParentCancellation(expectParentActive = true , expectUnhandled = true ) { fail ->
28+ val child = SupervisorJob (coroutineContext[Job ])
29+ CoroutineScope (coroutineContext + child).fail()
30+ }
31+ }
32+
2533 @Test
2634 fun testCompletableDeferredChild () = runTest {
2735 testParentCancellation { fail ->
@@ -58,37 +66,48 @@ class ParentCancellationTest : TestBase() {
5866 }
5967 }
6068
69+ @Test
70+ fun testSupervisorChild () = runTest {
71+ testParentCancellation(expectParentActive = true , expectUnhandled = true ) { fail ->
72+ supervisorScope { fail() }
73+ }
74+ }
75+
6176 @Test
6277 fun testCoroutineScopeChild () = runTest {
63- testParentCancellation(expectRethrows = true ) { fail ->
78+ testParentCancellation(expectParentActive = true , expectRethrows = true ) { fail ->
6479 coroutineScope { fail() }
6580 }
6681 }
6782
6883 @Test
6984 fun testWithContextChild () = runTest {
70- testParentCancellation(expectRethrows = true ) { fail ->
85+ testParentCancellation(expectParentActive = true , expectRethrows = true ) { fail ->
7186 withContext(CoroutineName (" fail" )) { fail() }
7287 }
7388 }
7489
7590 @Test
7691 fun testWithTimeoutChild () = runTest {
77- testParentCancellation(expectRethrows = true ) { fail ->
92+ testParentCancellation(expectParentActive = true , expectRethrows = true ) { fail ->
7893 withTimeout(1000 ) { fail() }
7994 }
8095 }
8196
8297 private suspend fun CoroutineScope.testParentCancellation (
98+ expectParentActive : Boolean = false,
8399 expectRethrows : Boolean = false,
100+ expectUnhandled : Boolean = false,
84101 child : suspend CoroutineScope .(block: suspend CoroutineScope .() -> Unit ) -> Unit
85102 ) {
86- testWithException(expectRethrows, TestException (), child)
87- testWithException(expectRethrows, CancellationException (" Test" ), child)
103+ testWithException(expectParentActive, expectRethrows, expectUnhandled , TestException (), child)
104+ testWithException(true , expectRethrows, false , CancellationException (" Test" ), child)
88105 }
89106
90107 private suspend fun CoroutineScope.testWithException (
108+ expectParentActive : Boolean ,
91109 expectRethrows : Boolean ,
110+ expectUnhandled : Boolean ,
92111 throwException : Throwable ,
93112 child : suspend CoroutineScope .(block: suspend CoroutineScope .() -> Unit ) -> Unit
94113 ) {
@@ -99,10 +118,17 @@ class ParentCancellationTest : TestBase() {
99118 try {
100119 scope.child {
101120 // launch failing grandchild
102- val grandchild = launch {
121+ var unhandledException: Throwable ? = null
122+ val handler = CoroutineExceptionHandler { _, e -> unhandledException = e }
123+ val grandchild = launch(handler) {
103124 throw throwException
104125 }
105126 grandchild.join()
127+ if (expectUnhandled) {
128+ assertSame(throwException, unhandledException)
129+ } else {
130+ assertNull(unhandledException)
131+ }
106132 }
107133 if (expectRethrows && throwException !is CancellationException ) {
108134 expectUnreached()
@@ -117,8 +143,7 @@ class ParentCancellationTest : TestBase() {
117143 expectUnreached()
118144 }
119145 }
120- if (expectRethrows || throwException is CancellationException ) {
121- // Note: parent is not cancelled on CancellationException or when primitive rethrows it
146+ if (expectParentActive) {
122147 assertTrue(parent.isActive)
123148 } else {
124149 parent.join()
0 commit comments