1 /*
<lambda>null2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.test.tracing.coroutines
18
19 import android.platform.test.annotations.EnableFlags
20 import com.android.app.tracing.coroutines.createCoroutineTracingContext
21 import com.android.app.tracing.coroutines.flow.collectLatestTraced
22 import com.android.app.tracing.coroutines.flow.collectTraced
23 import com.android.app.tracing.coroutines.flow.filterTraced
24 import com.android.app.tracing.coroutines.flow.flowName
25 import com.android.app.tracing.coroutines.flow.mapTraced
26 import com.android.app.tracing.coroutines.flow.transformTraced
27 import com.android.app.tracing.coroutines.launchTraced
28 import com.android.systemui.Flags.FLAG_COROUTINE_TRACING
29 import kotlin.coroutines.CoroutineContext
30 import kotlinx.coroutines.DelicateCoroutinesApi
31 import kotlinx.coroutines.ExperimentalCoroutinesApi
32 import kotlinx.coroutines.cancel
33 import kotlinx.coroutines.delay
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.FlowCollector
36 import kotlinx.coroutines.flow.SharingStarted
37 import kotlinx.coroutines.flow.flow
38 import kotlinx.coroutines.flow.shareIn
39 import kotlinx.coroutines.newSingleThreadContext
40 import kotlinx.coroutines.withContext
41 import org.junit.Assert.assertEquals
42 import org.junit.Test
43
44 /** Tests behavior of default names, whether that's via stack walking or reflection */
45 @EnableFlags(FLAG_COROUTINE_TRACING)
46 class DefaultNamingTest : TestBase() {
47
48 override val extraCoroutineContext: CoroutineContext
49 get() = createCoroutineTracingContext("main", includeParentNames = true, strictMode = true)
50
51 fun namedCollectFun() {}
52
53 @Test
54 fun collectTraced1() = runTest {
55 expect(1, "main:1^")
56 flow {
57 expect(2, "main:1^", "collect:DefaultNamingTest\$collectTraced1$1$4")
58 emit(21) // 21 * 2 = 42
59 expect(6, "main:1^", "collect:DefaultNamingTest\$collectTraced1$1$4")
60 }
61 .mapTraced("2x") {
62 expect(
63 3,
64 "main:1^",
65 "collect:DefaultNamingTest\$collectTraced1$1$4",
66 "map:2x:transform",
67 )
68 it * 2 // 42
69 }
70 .flowName("UNUSED_NAME") // unused because scope is unchanged
71 .filterTraced("mod2") {
72 expect(
73 4,
74 "main:1^",
75 "collect:DefaultNamingTest\$collectTraced1$1$4",
76 "map:2x:emit",
77 "filter:mod2:predicate",
78 )
79 it % 2 == 0 // true
80 }
81 .collectTraced {
82 assertEquals(42, it) // 21 * 2 = 42
83 expect(
84 5,
85 "main:1^",
86 "collect:DefaultNamingTest\$collectTraced1$1$4",
87 "map:2x:emit",
88 "filter:mod2:emit",
89 "collect:DefaultNamingTest\$collectTraced1$1$4:emit",
90 )
91 }
92 finish(7, "main:1^")
93 }
94
95 @Test
96 fun collectTraced2() = runTest {
97 expect(1, "main:1^") // top-level scope
98
99 flow {
100 expect(2, "main:1^:1^") // child scope used by `collectLatest {}`
101 emit(1) // should not get used by collectLatest {}
102 expect(6, "main:1^:1^")
103 emit(21) // 21 * 2 = 42
104 expect(10, "main:1^:1^")
105 }
106 .filterTraced("mod2") {
107 expect(listOf(3, 7), "main:1^:1^", "filter:mod2:predicate")
108 it % 2 == 1 // true
109 }
110 .mapTraced("2x") {
111 expect(listOf(4, 8), "main:1^:1^", "filter:mod2:emit", "map:2x:transform")
112 it * 2 // 42
113 }
114 // this name won't be used because it's not passed the scope used by mapLatest{}, which
115 // is an internal implementation detail in kotlinx
116 .flowName("UNUSED_NAME")
117 .collectLatestTraced {
118 expectEvent(listOf(5, 9))
119 delay(10)
120 assertEquals(42, it) // 21 * 2 = 42
121 expect(
122 11,
123 "main:1^:1^:2^",
124 "collectLatest:DefaultNamingTest\$collectTraced2$1$4:action",
125 )
126 }
127 finish(12, "main:1^")
128 }
129
130 @Test
131 fun collectTraced3() = runTest {
132 expect(1, "main:1^") // top-level scope
133
134 val sharedFlow =
135 flow {
136 expect(2, "main:1^:1^")
137 delay(1)
138 emit(22)
139 expect(3, "main:1^:1^")
140 delay(1)
141 emit(32)
142 expect(4, "main:1^:1^")
143 delay(1)
144 emit(42)
145 expect(5, "main:1^:1^")
146 } // there is no API for passing a custom context to the new shared flow, so weg
147 // can't pass our custom child name using `nameCoroutine()`
148 .shareIn(this, SharingStarted.Eagerly, 4)
149
150 launchTraced("AAAA") {
151 sharedFlow.collectLatestTraced {
152 delay(10)
153 expect(
154 6,
155 "main:1^:2^AAAA:1^:3^",
156 "collectLatest:DefaultNamingTest\$collectTraced3$1$1$1:action",
157 )
158 }
159 }
160 launchTraced("BBBB") {
161 sharedFlow.collectLatestTraced {
162 delay(40)
163 assertEquals(42, it)
164 expect(
165 7,
166 "main:1^:3^BBBB:1^:3^",
167 "collectLatest:DefaultNamingTest\$collectTraced3$1$2$1:action",
168 )
169 }
170 }
171
172 delay(50)
173 finish(8, "main:1^")
174 cancel()
175 }
176
177 @Test
178 fun collectTraced4() = runTest {
179 expect(1, "main:1^")
180 flow {
181 expect(2, "main:1^", "collect:DefaultNamingTest\$collectTraced4$1$2")
182 emit(42)
183 expect(4, "main:1^", "collect:DefaultNamingTest\$collectTraced4$1$2")
184 }
185 .collectTraced {
186 assertEquals(42, it)
187 expect(
188 3,
189 "main:1^",
190 "collect:DefaultNamingTest\$collectTraced4$1$2",
191 "collect:DefaultNamingTest\$collectTraced4$1$2:emit",
192 )
193 }
194 finish(5, "main:1^")
195 }
196
197 @Test
198 fun collectTraced5_localFun() {
199 fun localFun(value: Int) {
200 assertEquals(42, value)
201 expect(
202 3,
203 "main:1^",
204 "collect:DefaultNamingTest\$collectTraced5_localFun$1$2",
205 "collect:DefaultNamingTest\$collectTraced5_localFun$1$2:emit",
206 )
207 }
208 return runTest {
209 expect(1, "main:1^")
210 flow {
211 expect(2, "main:1^", "collect:DefaultNamingTest\$collectTraced5_localFun$1$2")
212 emit(42)
213 expect(4, "main:1^", "collect:DefaultNamingTest\$collectTraced5_localFun$1$2")
214 }
215 .collectTraced(::localFun)
216 finish(5, "main:1^")
217 }
218 }
219
220 fun memberFun(value: Int) {
221 assertEquals(42, value)
222 expect(
223 3,
224 "main:1^",
225 "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2",
226 "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2:emit",
227 )
228 }
229
230 @Test
231 fun collectTraced6_memberFun() = runTest {
232 expect(1, "main:1^")
233 flow {
234 expect(2, "main:1^", "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2")
235 emit(42)
236 expect(4, "main:1^", "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2")
237 }
238 .collectTraced(::memberFun)
239 finish(5, "main:1^")
240 }
241
242 @Test
243 fun collectTraced7_topLevelFun() = runTest {
244 expect(1, "main:1^")
245 flow {
246 expect(2, "main:1^", "collect:DefaultNamingTest\$collectTraced7_topLevelFun$1$2")
247 emit(42)
248 expect(3, "main:1^", "collect:DefaultNamingTest\$collectTraced7_topLevelFun$1$2")
249 }
250 .collectTraced(::topLevelFun)
251 finish(4, "main:1^")
252 }
253
254 @Test
255 fun collectTraced8_localFlowObject() = runTest {
256 expect(1, "main:1^")
257 val flowObj =
258 object : Flow<Int> {
259 override suspend fun collect(collector: FlowCollector<Int>) {
260 expect(
261 2,
262 "main:1^",
263 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
264 )
265 collector.emit(42)
266 expect(
267 4,
268 "main:1^",
269 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
270 )
271 }
272 }
273 flowObj.collectTraced {
274 assertEquals(42, it)
275 expect(
276 3,
277 "main:1^",
278 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
279 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1:emit",
280 )
281 }
282 finish(5, "main:1^")
283 }
284
285 @Test
286 fun collectTraced9_flowObjectWithClassName() = runTest {
287 expect(1, "main:1^")
288 FlowWithName(this@DefaultNamingTest).collectTraced {
289 assertEquals(42, it)
290 expect(
291 3,
292 "main:1^",
293 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
294 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1:emit",
295 )
296 }
297 finish(5, "main:1^")
298 }
299
300 @Test
301 fun collectTraced10_flowCollectorObjectWithClassName() = runTest {
302 expect(1, "main:1^")
303 flow {
304 expect(2, "main:1^", "collect:FlowCollectorWithName")
305 emit(42)
306 expect(4, "main:1^", "collect:FlowCollectorWithName")
307 }
308 .collectTraced(FlowCollectorWithName(this@DefaultNamingTest))
309 finish(5, "main:1^")
310 }
311
312 @Test
313 fun collectTraced11_transform() = runTest {
314 expect(1, "main:1^")
315 flow {
316 expect(2, "main:1^", "collect:COLLECT")
317 emit(42)
318 expect(7, "main:1^", "collect:COLLECT")
319 }
320 .transformTraced("TRANSFORM") {
321 expect(3, "main:1^", "collect:COLLECT", "TRANSFORM:emit")
322 emit(it)
323 emit(it * 2)
324 emit(it * 4)
325 }
326 .collectTraced("COLLECT") {
327 expect(
328 listOf(4, 5, 6),
329 "main:1^",
330 "collect:COLLECT",
331 "TRANSFORM:emit",
332 "collect:COLLECT:emit",
333 )
334 }
335 finish(8, "main:1^")
336 }
337
338 @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
339 @Test
340 fun collectTraced12_badTransform() =
341 runTest(
342 expectedException = { e ->
343 return@runTest e is java.lang.IllegalStateException &&
344 (e.message?.startsWith("Flow invariant is violated") ?: false)
345 }
346 ) {
347 val thread1 = newSingleThreadContext("thread-#1")
348 expect(1, "main:1^")
349 flow {
350 expect(2, "main:1^", "collect:COLLECT")
351 emit(42)
352 expect(4, "main:1^", "collect:COLLECT")
353 }
354 .transformTraced("TRANSFORM") {
355 // SHOULD THROW AN EXCEPTION:
356 withContext(thread1) { emit(it * 2) }
357 }
358 .collectTraced("COLLECT") {}
359 finish(5, "main:1^")
360 }
361 }
362
topLevelFunnull363 fun topLevelFun(value: Int) {
364 assertEquals(42, value)
365 }
366
367 class FlowWithName(private val test: TestBase) : Flow<Int> {
collectnull368 override suspend fun collect(collector: FlowCollector<Int>) {
369 test.expect(
370 2,
371 "main:1^",
372 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
373 )
374 collector.emit(42)
375 test.expect(
376 4,
377 "main:1^",
378 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
379 )
380 }
381 }
382
383 class FlowCollectorWithName(private val test: TestBase) : FlowCollector<Int> {
emitnull384 override suspend fun emit(value: Int) {
385 assertEquals(42, value)
386 test.expect(
387 3,
388 "main:1^",
389 "collect:FlowCollectorWithName",
390 "collect:FlowCollectorWithName:emit",
391 )
392 }
393 }
394