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