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 @file:OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
18
19 package com.android.test.tracing.coroutines
20
21 import android.platform.test.annotations.EnableFlags
22 import com.android.app.tracing.coroutines.asyncTraced
23 import com.android.app.tracing.coroutines.flow.collectLatestTraced
24 import com.android.app.tracing.coroutines.flow.collectTraced
25 import com.android.app.tracing.coroutines.flow.filterTraced
26 import com.android.app.tracing.coroutines.flow.mapTraced
27 import com.android.app.tracing.coroutines.flow.transformTraced
28 import com.android.app.tracing.coroutines.launchTraced
29 import com.android.app.tracing.coroutines.withContextTraced
30 import com.android.systemui.Flags.FLAG_COROUTINE_TRACING
31 import kotlin.coroutines.EmptyCoroutineContext
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.DelicateCoroutinesApi
34 import kotlinx.coroutines.ExperimentalCoroutinesApi
35 import kotlinx.coroutines.cancelChildren
36 import kotlinx.coroutines.delay
37 import kotlinx.coroutines.flow.Flow
38 import kotlinx.coroutines.flow.FlowCollector
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.flow
41 import kotlinx.coroutines.flow.shareIn
42 import kotlinx.coroutines.job
43 import kotlinx.coroutines.withContext
44 import org.junit.Assert.assertEquals
45 import org.junit.Test
46
47 /** Tests behavior of default names using reflection */
48 @EnableFlags(FLAG_COROUTINE_TRACING)
49 class DefaultNamingTest : TestBase() {
50
51 @Test
52 fun collectTraced1() {
53 val coldFlow =
54 flow {
55 expect(2, "1^main", "collect:DefaultNamingTest\$collectTraced1$1$1")
56 emit(21) // 21 * 2 = 42
57 expect(6, "1^main", "collect:DefaultNamingTest\$collectTraced1$1$1")
58 }
59 .mapTraced("2x") {
60 expect(3, "1^main", "collect:DefaultNamingTest\$collectTraced1$1$1", "2x")
61 it * 2 // 42
62 }
63 .filterTraced("mod2") {
64 expect(4, "1^main", "collect:DefaultNamingTest\$collectTraced1$1$1", "mod2")
65 it % 2 == 0 // true
66 }
67 runTest(finalEvent = 7) {
68 expect(1, "1^main")
69 coldFlow.collectTraced {
70 assertEquals(42, it) // 21 * 2 = 42
71 expect(
72 5,
73 "1^main",
74 "collect:DefaultNamingTest\$collectTraced1$1$1",
75 "emit:DefaultNamingTest\$collectTraced1$1$1",
76 )
77 }
78 expect(7, "1^main")
79 }
80 }
81
82 @Test
83 fun collectTraced2() {
84 val coldFlow =
85 flow {
86 expect(
87 2,
88 "1^main:1^",
89 "collect:collectLatest:DefaultNamingTest\$collectTraced2$1$1",
90 ) // child scope used by `collectLatest {}`
91 emit(1) // should not get used by collectLatest {}
92 expect(
93 6,
94 "1^main:1^",
95 "collect:collectLatest:DefaultNamingTest\$collectTraced2$1$1",
96 )
97 emit(21) // 21 * 2 = 42
98 expect(
99 10,
100 "1^main:1^",
101 "collect:collectLatest:DefaultNamingTest\$collectTraced2$1$1",
102 )
103 }
104 .filterTraced("mod2") {
105 expect(
106 listOf(3, 7),
107 "1^main:1^",
108 "collect:collectLatest:DefaultNamingTest\$collectTraced2$1$1",
109 "mod2",
110 )
111 it % 2 == 1 // true
112 }
113 .mapTraced("2x") {
114 expect(
115 listOf(4, 8),
116 "1^main:1^",
117 "collect:collectLatest:DefaultNamingTest\$collectTraced2$1$1",
118 "2x",
119 )
120 it * 2 // 42
121 }
122 runTest(finalEvent = 12) {
123 expect(1, "1^main") // top-level scope
124 coldFlow.collectLatestTraced {
125 expectEvent(listOf(5, 9))
126 delay(50)
127 assertEquals(42, it) // 21 * 2 = 42
128 expect(11, "1^main:1^:2^", "DefaultNamingTest\$collectTraced2$1$1")
129 }
130 expect(12, "1^main")
131 }
132 }
133
134 @Test
135 fun collectTraced3() =
136 runTest(finalEvent = 8) {
137 expect(1, "1^main") // top-level scope
138
139 val sharedFlow =
140 flow {
141 expect(2, "1^main:1^")
142 delay(1)
143 emit(22)
144 expect(3, "1^main:1^")
145 delay(1)
146 emit(32)
147 expect(4, "1^main:1^")
148 delay(1)
149 emit(42)
150 expect(5, "1^main:1^")
151 } // there is no API for passing a custom context to the new shared flow, so we
152 // can't pass our custom child name using `CoroutineTraceName()`
153 .shareIn(this, SharingStarted.Eagerly, 4)
154
155 launchTraced("AAAA") {
156 sharedFlow.collectLatestTraced {
157 delay(10)
158 expect(6, "1^main:2^AAAA:1^:3^", "DefaultNamingTest\$collectTraced3$1$1$1")
159 }
160 }
161 launchTraced("BBBB") {
162 sharedFlow.collectLatestTraced {
163 delay(40)
164 assertEquals(42, it)
165 expect(7, "1^main:3^BBBB:1^:3^", "DefaultNamingTest\$collectTraced3$1$2$1")
166 }
167 }
168
169 delay(70)
170 expect(8, "1^main")
171 coroutineContext.job.cancelChildren()
172 }
173
174 @Test
175 fun collectTraced4() =
176 runTest(finalEvent = 5) {
177 expect(1, "1^main")
178 flow {
179 expect(2, "1^main", "collect:DefaultNamingTest\$collectTraced4$1$2")
180 emit(42)
181 expect(4, "1^main", "collect:DefaultNamingTest\$collectTraced4$1$2")
182 }
183 .collectTraced {
184 assertEquals(42, it)
185 expect(
186 3,
187 "1^main",
188 "collect:DefaultNamingTest\$collectTraced4$1$2",
189 "emit:DefaultNamingTest\$collectTraced4$1$2",
190 )
191 }
192 expect(5, "1^main")
193 }
194
195 @Test
196 fun collectTraced5_localFun() {
197 fun localFun(value: Int) {
198 assertEquals(42, value)
199 expect(
200 3,
201 "1^main",
202 "collect:DefaultNamingTest\$collectTraced5_localFun$1$2",
203 "emit:DefaultNamingTest\$collectTraced5_localFun$1$2",
204 )
205 }
206 return runTest(finalEvent = 5) {
207 expect(1, "1^main")
208 flow {
209 expect(2, "1^main", "collect:DefaultNamingTest\$collectTraced5_localFun$1$2")
210 emit(42)
211 expect(4, "1^main", "collect:DefaultNamingTest\$collectTraced5_localFun$1$2")
212 }
213 .collectTraced(::localFun)
214 expect(5, "1^main")
215 }
216 }
217
218 fun memberFun(value: Int) {
219 assertEquals(42, value)
220 expect(
221 3,
222 "1^main",
223 "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2",
224 "emit:DefaultNamingTest\$collectTraced6_memberFun$1$2",
225 )
226 }
227
228 @Test
229 fun collectTraced6_memberFun() =
230 runTest(finalEvent = 5) {
231 expect(1, "1^main")
232 flow {
233 expect(2, "1^main", "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2")
234 emit(42)
235 expect(4, "1^main", "collect:DefaultNamingTest\$collectTraced6_memberFun$1$2")
236 }
237 .collectTraced(::memberFun)
238 expect(5, "1^main")
239 }
240
241 @Test
242 fun collectTraced7_topLevelFun() =
243 runTest(finalEvent = 4) {
244 expect(1, "1^main")
245 flow {
246 expect(2, "1^main", "collect:DefaultNamingTest\$collectTraced7_topLevelFun$1$2")
247 emit(42)
248 expect(3, "1^main", "collect:DefaultNamingTest\$collectTraced7_topLevelFun$1$2")
249 }
250 .collectTraced(::topLevelFun)
251 expect(4, "1^main")
252 }
253
254 @Test
255 fun collectTraced8_localFlowObject() =
256 runTest(finalEvent = 5) {
257 expect(1, "1^main")
258 val flowObj =
259 object : Flow<Int> {
260 override suspend fun collect(collector: FlowCollector<Int>) {
261 expect(
262 2,
263 "1^main",
264 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
265 )
266 collector.emit(42)
267 expect(
268 4,
269 "1^main",
270 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
271 )
272 }
273 }
274 flowObj.collectTraced {
275 assertEquals(42, it)
276 expect(
277 3,
278 "1^main",
279 "collect:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
280 "emit:DefaultNamingTest\$collectTraced8_localFlowObject$1$1",
281 )
282 }
283 expect(5, "1^main")
284 }
285
286 @Test
287 fun collectTraced9_flowObjectWithClassName() =
288 runTest(finalEvent = 5) {
289 expect(1, "1^main")
290 FlowWithName(this@DefaultNamingTest).collectTraced {
291 assertEquals(42, it)
292 expect(
293 3,
294 "1^main",
295 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
296 "emit:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
297 )
298 }
299 expect(5, "1^main")
300 }
301
302 @Test
303 fun collectTraced10_flowCollectorObjectWithClassName() =
304 runTest(finalEvent = 5) {
305 expect(1, "1^main")
306 flow {
307 expect(2, "1^main", "collect:FlowCollectorWithName")
308 emit(42)
309 expect(4, "1^main", "collect:FlowCollectorWithName")
310 }
311 .collectTraced(FlowCollectorWithName(this@DefaultNamingTest))
312 expect(5, "1^main")
313 }
314
315 @Test
316 fun collectTraced11_transform() =
317 runTest(finalEvent = 8) {
318 expect(1, "1^main")
319 flow {
320 expect(2, "1^main", "collect:COLLECT")
321 emit(42)
322 expect(7, "1^main", "collect:COLLECT")
323 }
324 .transformTraced("TRANSFORM") {
325 expect(3, "1^main", "collect:COLLECT", "TRANSFORM")
326 emit(it)
327 emit(it * 2)
328 emit(it * 4)
329 }
330 .collectTraced("COLLECT") {
331 expect(
332 listOf(4, 5, 6),
333 "1^main",
334 "collect:COLLECT",
335 "TRANSFORM",
336 "emit:COLLECT",
337 )
338 }
339 expect(8, "1^main")
340 }
341
342 @Test
343 fun collectTraced12_badTransform() =
344 runTest(
345 finalEvent = 2,
346 isExpectedException = { e ->
347 e is java.lang.IllegalStateException &&
348 (e.message?.startsWith("Flow invariant is violated") ?: false)
349 },
350 block = {
351 val thread1 = bgThread1
352 expect(1, "1^main")
353 flow {
354 expect(2, "1^main", "collect:COLLECT")
355 emit(42)
356 }
357 .transformTraced("TRANSFORM") {
358 // throws IllegalStateException:
359 withContext(thread1) { emit(it * 2) } // <-- Flow invariant is violated
360 }
361 .collectTraced("COLLECT") {}
362 },
363 )
364
365 @Test
366 fun coroutineBuilder_defaultNames() {
367 val localFun: suspend CoroutineScope.() -> Unit = {
368 expectAny(
369 arrayOf("1^main:4^DefaultNamingTest\$coroutineBuilder_defaultNames\$localFun$1"),
370 arrayOf("1^main", "DefaultNamingTest\$coroutineBuilder_defaultNames\$localFun$1"),
371 arrayOf("1^main:2^DefaultNamingTest\$coroutineBuilder_defaultNames\$localFun$1"),
372 )
373 }
374 runTest(totalEvents = 6) {
375 launchTraced { expect("1^main:1^DefaultNamingTest\$coroutineBuilder_defaultNames$1$1") }
376 .join()
377 launchTraced(block = localFun).join()
378 asyncTraced { expect("1^main:3^DefaultNamingTest\$coroutineBuilder_defaultNames$1$2") }
379 .await()
380 asyncTraced(block = localFun).await()
381 withContextTraced(context = EmptyCoroutineContext) {
382 expect("1^main", "DefaultNamingTest\$coroutineBuilder_defaultNames$1$3")
383 }
384 withContextTraced(context = EmptyCoroutineContext, block = localFun)
385 }
386 }
387 }
388
topLevelFunnull389 fun topLevelFun(value: Int) {
390 assertEquals(42, value)
391 }
392
393 class FlowWithName(private val test: TestBase) : Flow<Int> {
collectnull394 override suspend fun collect(collector: FlowCollector<Int>) {
395 test.expect(
396 2,
397 "1^main",
398 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
399 )
400 collector.emit(42)
401 test.expect(
402 4,
403 "1^main",
404 "collect:DefaultNamingTest\$collectTraced9_flowObjectWithClassName$1$1",
405 )
406 }
407 }
408
409 class FlowCollectorWithName(private val test: TestBase) : FlowCollector<Int> {
emitnull410 override suspend fun emit(value: Int) {
411 assertEquals(42, value)
412 test.expect(3, "1^main", "collect:FlowCollectorWithName", "emit:FlowCollectorWithName")
413 }
414 }
415