• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 android.tools.flicker.subject.layers
18 
19 import android.tools.flicker.subject.FlickerTraceSubject
20 import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
21 import android.tools.flicker.subject.exceptions.InvalidElementException
22 import android.tools.flicker.subject.exceptions.InvalidPropertyException
23 import android.tools.flicker.subject.region.RegionTraceSubject
24 import android.tools.function.AssertionPredicate
25 import android.tools.io.Reader
26 import android.tools.traces.component.ComponentNameMatcher
27 import android.tools.traces.component.IComponentMatcher
28 import android.tools.traces.component.IComponentNameMatcher
29 import android.tools.traces.component.SurfaceViewBackgroundMatcher
30 import android.tools.traces.region.RegionTrace
31 import android.tools.traces.surfaceflinger.Layer
32 import android.tools.traces.surfaceflinger.LayersTrace
33 import java.util.function.Predicate
34 
35 /**
36  * Subject for [LayersTrace] objects, used to make assertions over behaviors that occur throughout a
37  * whole trace
38  *
39  * To make assertions over a trace it is recommended to create a subject using
40  * [LayersTraceSubject](myTrace).
41  *
42  * Example:
43  * ```
44  *    val trace = LayersTraceParser().parse(myTraceFile)
45  *    val subject = LayersTraceSubject(trace)
46  *        .contains("ValidLayer")
47  *        .notContains("ImaginaryLayer")
48  *        .coversExactly(DISPLAY_AREA)
49  *        .forAllEntries()
50  * ```
51  *
52  * Example2:
53  * ```
54  *    val trace = LayersTraceParser().parse(myTraceFile)
55  *    val subject = LayersTraceSubject(trace) {
56  *        check("Custom check") { myCustomAssertion(this) }
57  *    }
58  * ```
59  */
60 class LayersTraceSubject
61 @JvmOverloads
62 constructor(val trace: LayersTrace, override val reader: Reader? = null) :
63     FlickerTraceSubject<LayerTraceEntrySubject>(),
64     ILayerSubject<LayersTraceSubject, RegionTraceSubject> {
65 
66     override val subjects by lazy {
67         trace.entries.map { LayerTraceEntrySubject(it, reader, trace) }
68     }
69 
70     /** {@inheritDoc} */
71     override fun then(): LayersTraceSubject = apply { super.then() }
72 
73     /** {@inheritDoc} */
74     override fun isEmpty(): LayersTraceSubject = apply {
75         check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true)
76     }
77 
78     /** {@inheritDoc} */
79     override fun isNotEmpty(): LayersTraceSubject = apply {
80         check { "Trace is not empty" }.that(trace.entries.isNotEmpty()).isEqual(true)
81     }
82 
83     /** {@inheritDoc} */
84     override fun layer(name: String, frameNumber: Long): LayerSubject {
85         val result = subjects.firstNotNullOfOrNull { it.layer(name, frameNumber) }
86 
87         if (result == null) {
88             val errorMsgBuilder =
89                 ExceptionMessageBuilder()
90                     .forSubject(this)
91                     .forInvalidElement(name, expectElementExists = true)
92                     .addExtraDescription("Frame number", frameNumber)
93             throw InvalidElementException(errorMsgBuilder)
94         }
95 
96         return result
97     }
98 
99     /** @return List of [LayerSubject]s matching [name] in the order they appear on the trace */
100     fun layers(name: String): List<LayerSubject> =
101         subjects.mapNotNull { it.layer { layer -> layer.name.contains(name) } }
102 
103     /**
104      * @return List of [LayerSubject]s matching [predicate] in the order they appear on the trace
105      */
106     fun layers(predicate: Predicate<Layer>): List<LayerSubject> =
107         subjects.mapNotNull { it.layer { layer -> predicate.test(layer) } }
108 
109     /** Checks that all visible layers are shown for more than one consecutive entry */
110     fun visibleLayersShownMoreThanOneConsecutiveEntry(
111         ignoreLayers: List<IComponentMatcher> = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
112     ): LayersTraceSubject = apply {
113         visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
114             subject.entry.visibleLayers
115                 .filter { visibleLayer ->
116                     ignoreLayers.none { matcher -> matcher.layerMatchesAnyOf(visibleLayer) }
117                 }
118                 .map { it.name }
119                 .toSet()
120         }
121     }
122 
123     /** {@inheritDoc} */
124     override fun notContains(componentMatcher: IComponentMatcher): LayersTraceSubject =
125         notContains(componentMatcher, isOptional = false)
126 
127     /** See [notContains] */
128     fun notContains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
129         apply {
130             addAssertion("notContains(${componentMatcher.toLayerIdentifier()})", isOptional) {
131                 it.notContains(componentMatcher)
132             }
133         }
134 
135     /** {@inheritDoc} */
136     override fun contains(componentMatcher: IComponentMatcher): LayersTraceSubject =
137         contains(componentMatcher, isOptional = false)
138 
139     /** See [contains] */
140     fun contains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
141         apply {
142             addAssertion("contains(${componentMatcher.toLayerIdentifier()})", isOptional) {
143                 it.contains(componentMatcher)
144             }
145         }
146 
147     /** {@inheritDoc} */
148     override fun isVisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
149         isVisible(componentMatcher, isOptional = false)
150 
151     /** See [isVisible] */
152     fun isVisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
153         apply {
154             addAssertion("isVisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
155                 it.isVisible(componentMatcher)
156             }
157         }
158 
159     /** {@inheritDoc} */
160     override fun isInvisible(
161         componentMatcher: IComponentMatcher,
162         mustExist: Boolean,
163     ): LayersTraceSubject = isInvisible(componentMatcher, mustExist, isOptional = false)
164 
165     /** See [isInvisible] */
166     fun isInvisible(
167         componentMatcher: IComponentMatcher,
168         mustExist: Boolean = false,
169         isOptional: Boolean,
170     ): LayersTraceSubject = apply {
171         addAssertion("isInvisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
172             it.isInvisible(componentMatcher, mustExist)
173         }
174     }
175 
176     /** {@inheritDoc} */
177     override fun isSplashScreenVisibleFor(
178         componentMatcher: IComponentNameMatcher
179     ): LayersTraceSubject = isSplashScreenVisibleFor(componentMatcher, isOptional = false)
180 
181     /** {@inheritDoc} */
182     override fun hasColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
183         addAssertion("hasColor(${componentMatcher.toLayerIdentifier()})") {
184             it.hasColor(componentMatcher)
185         }
186     }
187 
188     /** {@inheritDoc} */
189     override fun hasNoColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
190         addAssertion("hasNoColor(${componentMatcher.toLayerIdentifier()})") {
191             it.hasNoColor(componentMatcher)
192         }
193     }
194 
195     /** {@inheritDoc} */
196     override fun hasRoundedCorners(componentMatcher: IComponentMatcher): LayersTraceSubject =
197         apply {
198             addAssertion("hasRoundedCorners(${componentMatcher.toLayerIdentifier()})") {
199                 it.hasRoundedCorners(componentMatcher)
200             }
201         }
202 
203     /** {@inheritDoc} */
204     override fun hasNoRoundedCorners(componentMatcher: IComponentMatcher): LayersTraceSubject =
205         apply {
206             addAssertion("hasNoRoundedCorners(${componentMatcher.toLayerIdentifier()})") {
207                 it.hasNoRoundedCorners(componentMatcher)
208             }
209         }
210 
211     /** See [isSplashScreenVisibleFor] */
212     fun isSplashScreenVisibleFor(
213         componentMatcher: IComponentNameMatcher,
214         isOptional: Boolean,
215     ): LayersTraceSubject = apply {
216         addAssertion(
217             "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})",
218             isOptional,
219         ) {
220             it.isSplashScreenVisibleFor(componentMatcher)
221         }
222     }
223 
224     /** See [visibleRegion] */
225     fun visibleRegion(): RegionTraceSubject =
226         visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = false)
227 
228     /** See [visibleRegion] */
229     fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject =
230         visibleRegion(componentMatcher, useCompositionEngineRegionOnly = false)
231 
232     /** {@inheritDoc} */
233     override fun visibleRegion(
234         componentMatcher: IComponentMatcher?,
235         useCompositionEngineRegionOnly: Boolean,
236     ): RegionTraceSubject {
237         val regionTrace =
238             RegionTrace(
239                 componentMatcher,
240                 subjects.map {
241                     it.visibleRegion(componentMatcher, useCompositionEngineRegionOnly).regionEntry
242                 },
243             )
244         return RegionTraceSubject(regionTrace, reader)
245     }
246 
247     fun atLeastOneEntryContainsOneDisplayOn(): LayersTraceSubject = apply {
248         isNotEmpty()
249         val anyEntryWithDisplayOn =
250             subjects.any { it.entry.displays.any { display -> display.isOn } }
251         if (!anyEntryWithDisplayOn) {
252             val errorMsgBuilder =
253                 ExceptionMessageBuilder()
254                     .forInvalidProperty("Display")
255                     .setExpected("At least one display On in any entry")
256                     .setActual("No displays on in any entry")
257             throw InvalidPropertyException(errorMsgBuilder)
258         }
259     }
260 
261     override fun containsAtLeastOneDisplay(): LayersTraceSubject = apply {
262         addAssertion("containAtLeastOneDisplay", isOptional = false) {
263             it.containsAtLeastOneDisplay()
264         }
265     }
266 
267     /** Executes a custom [assertion] on the current subject */
268     @JvmOverloads
269     operator fun invoke(
270         name: String,
271         isOptional: Boolean = false,
272         assertion: AssertionPredicate<LayerTraceEntrySubject>,
273     ): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) }
274 
275     fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject =
276         hasFrameSequence(ComponentNameMatcher("", name), frameNumbers)
277 
278     fun hasFrameSequence(
279         componentMatcher: IComponentMatcher,
280         frameNumbers: Iterable<Long>,
281     ): LayersTraceSubject = apply {
282         val firstFrame = frameNumbers.first()
283         val entries =
284             trace.entries
285                 .asSequence()
286                 // map entry to buffer layers with name
287                 .map { it.getLayerWithBuffer(componentMatcher) }
288                 // removing all entries without the layer
289                 .filterNotNull()
290                 // removing all entries with the same frame number
291                 .distinctBy { it.currFrame }
292                 // drop until the first frame we are interested in
293                 .dropWhile { layer -> layer.currFrame != firstFrame }
294 
295         var numFound = 0
296         val frameNumbersMatch =
297             entries
298                 .zip(frameNumbers.asSequence()) { layer, frameNumber ->
299                     numFound++
300                     layer.currFrame == frameNumber
301                 }
302                 .all { it }
303         val allFramesFound = frameNumbers.count() == numFound
304         if (!allFramesFound || !frameNumbersMatch) {
305             val errorMsgBuilder =
306                 ExceptionMessageBuilder()
307                     .forSubject(this)
308                     .forInvalidElement(
309                         componentMatcher.toLayerIdentifier(),
310                         expectElementExists = true,
311                     )
312                     .addExtraDescription("With frame sequence", frameNumbers.joinToString(","))
313             throw InvalidElementException(errorMsgBuilder)
314         }
315     }
316 
317     /** Run the assertions for all trace entries within the specified time range */
318     fun forSystemUpTimeRange(startTime: Long, endTime: Long) {
319         val subjectsInRange =
320             subjects.filter { it.entry.timestamp.systemUptimeNanos in startTime..endTime }
321         assertionsChecker.test(subjectsInRange)
322     }
323 
324     /**
325      * User-defined entry point for the trace entry with [timestamp]
326      *
327      * @param timestamp of the entry
328      */
329     @JvmOverloads
330     fun getEntryBySystemUpTime(
331         timestamp: Long,
332         byElapsedTimestamp: Boolean = false,
333     ): LayerTraceEntrySubject {
334         return if (byElapsedTimestamp) {
335             subjects.first { it.entry.elapsedTimestamp == timestamp }
336         } else {
337             subjects.first { it.entry.timestamp.systemUptimeNanos == timestamp }
338         }
339     }
340 
341     companion object {
342         @JvmField
343         val VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS =
344             listOf(
345                 ComponentNameMatcher.SPLASH_SCREEN,
346                 ComponentNameMatcher.SNAPSHOT,
347                 ComponentNameMatcher.IME_SNAPSHOT,
348                 ComponentNameMatcher.PIP_CONTENT_OVERLAY,
349                 ComponentNameMatcher.EDGE_BACK_GESTURE_HANDLER,
350                 ComponentNameMatcher.COLOR_FADE,
351                 ComponentNameMatcher.TRANSITION_SNAPSHOT,
352                 ComponentNameMatcher.FLOATING_ROTATION_BUTTON,
353                 ComponentNameMatcher.WIRED_CHARGING_ANIMATION,
354                 SurfaceViewBackgroundMatcher(),
355             )
356     }
357 }
358