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