11package com.mapbox.navigation.core.internal.utils
22
3+ import android.os.Handler
4+ import android.os.HandlerThread
5+ import kotlinx.coroutines.CoroutineScope
36import kotlinx.coroutines.Job
47import kotlinx.coroutines.SupervisorJob
8+ import kotlinx.coroutines.android.asCoroutineDispatcher
59import kotlinx.coroutines.cancel
610import kotlinx.coroutines.delay
711import kotlinx.coroutines.launch
12+ import kotlinx.coroutines.runBlocking
813import org.junit.After
14+ import org.junit.Assert.assertEquals
915import org.junit.Assert.assertFalse
16+ import org.junit.Assert.assertNotNull
1017import org.junit.Assert.assertTrue
1118import org.junit.Before
1219import org.junit.Test
20+ import org.junit.runner.RunWith
21+ import org.robolectric.RobolectricTestRunner
22+ import kotlin.coroutines.EmptyCoroutineContext
1323
24+ @RunWith(RobolectricTestRunner ::class )
1425class CoroutineUtilsTest {
1526
27+ private lateinit var parentThread: HandlerThread
28+ private lateinit var parentScope: CoroutineScope
1629 private lateinit var parentJob: Job
1730
1831 @Before
1932 fun setUp () {
33+ parentThread = HandlerThread (" parent thread" ).also { it.start() }
2034 parentJob = SupervisorJob ()
35+ parentScope = CoroutineScope (
36+ SupervisorJob () + Handler (parentThread.looper).asCoroutineDispatcher()
37+ )
2138 }
2239
2340 @After
2441 fun tearDown () {
42+ parentScope.cancel()
2543 parentJob.cancel()
44+ parentThread.quit()
2645 }
2746
2847 @Test
2948 fun createChildScope_parentCancellationCancelsChildren () {
30- val scope1 = CoroutineUtils .createChildScope(parentJob )
31- val scope2 = CoroutineUtils .createChildScope(parentJob )
49+ val scope1 = CoroutineUtils .createChildScope(parentScope )
50+ val scope2 = CoroutineUtils .createChildScope(parentScope )
3251
3352 val job1 = scope1.launch {
3453 delay(5000 )
@@ -40,16 +59,78 @@ class CoroutineUtilsTest {
4059 assertFalse(job1.isCancelled)
4160 assertFalse(job2.isCancelled)
4261
43- parentJob .cancel()
62+ parentScope .cancel()
4463
4564 assertTrue(job1.isCancelled)
4665 assertTrue(job2.isCancelled)
4766 }
4867
4968 @Test
5069 fun createChildScope_childDoesNotCancelOtherChildren () {
51- val scope1 = CoroutineUtils .createChildScope(parentJob)
52- val scope2 = CoroutineUtils .createChildScope(parentJob)
70+ val scope1 = CoroutineUtils .createChildScope(parentScope)
71+ val scope2 = CoroutineUtils .createChildScope(parentScope)
72+
73+ val job1 = scope1.launch {
74+ delay(5000 )
75+ }
76+ val job2 = scope2.launch {
77+ delay(5000 )
78+ }
79+
80+ assertFalse(job1.isCancelled)
81+ assertFalse(job2.isCancelled)
82+
83+ scope1.cancel()
84+
85+ assertTrue(job1.isCancelled)
86+ assertFalse(job2.isCancelled)
87+ }
88+
89+ @Test
90+ fun createChildScope_childrenUseParentDispatcher () = runBlocking {
91+ val childScope = CoroutineUtils .createChildScope(parentScope)
92+
93+ var childThread: Long? = null
94+ var parentThread: Long? = null
95+ val childJob = childScope.launch {
96+ childThread = Thread .currentThread().id
97+ }
98+ val parentJob = parentScope.launch {
99+ parentThread = Thread .currentThread().id
100+ }
101+
102+ parentJob.join()
103+ childJob.join()
104+
105+ assertNotNull(childThread)
106+ assertEquals(parentThread, childThread)
107+ }
108+
109+ @Test
110+ fun createScope_parentCancellationCancelsChildren () {
111+ val scope1 = CoroutineUtils .createScope(parentJob, EmptyCoroutineContext )
112+ val scope2 = CoroutineUtils .createScope(parentJob, EmptyCoroutineContext )
113+
114+ val job1 = scope1.launch {
115+ delay(5000 )
116+ }
117+ val job2 = scope2.launch {
118+ delay(5000 )
119+ }
120+
121+ assertFalse(job1.isCancelled)
122+ assertFalse(job2.isCancelled)
123+
124+ parentJob.cancel()
125+
126+ assertTrue(job1.isCancelled)
127+ assertTrue(job2.isCancelled)
128+ }
129+
130+ @Test
131+ fun createScope_childDoesNotCancelOtherChildren () {
132+ val scope1 = CoroutineUtils .createScope(parentJob, EmptyCoroutineContext )
133+ val scope2 = CoroutineUtils .createScope(parentJob, EmptyCoroutineContext )
53134
54135 val job1 = scope1.launch {
55136 delay(5000 )
@@ -66,4 +147,38 @@ class CoroutineUtilsTest {
66147 assertTrue(job1.isCancelled)
67148 assertFalse(job2.isCancelled)
68149 }
150+
151+ @Test
152+ fun createScope_childrenUseOwnDispatcher () = runBlocking {
153+ val thread1 = HandlerThread (" thread 1" ).also { it.start() }
154+ val thread2 = HandlerThread (" thread 2" ).also { it.start() }
155+ try {
156+ val childScope1 = CoroutineUtils .createScope(
157+ parentJob,
158+ Handler (thread1.looper).asCoroutineDispatcher()
159+ )
160+ val childScope2 = CoroutineUtils .createScope(
161+ parentJob,
162+ Handler (thread2.looper).asCoroutineDispatcher()
163+ )
164+
165+ var childThread1: Thread ? = null
166+ var childThread2: Thread ? = null
167+ val childJob1 = childScope1.launch {
168+ childThread1 = Thread .currentThread()
169+ }
170+ val childJob2 = childScope2.launch {
171+ childThread2 = Thread .currentThread()
172+ }
173+
174+ childJob1.join()
175+ childJob2.join()
176+
177+ assertEquals(thread1, childThread1)
178+ assertEquals(thread2, childThread2)
179+ } finally {
180+ thread1.quit()
181+ thread2.quit()
182+ }
183+ }
69184}
0 commit comments