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.io.Reader 25 import android.tools.traces.component.ComponentNameMatcher 26 import android.tools.traces.component.EdgeExtensionComponentMatcher 27 import android.tools.traces.component.IComponentMatcher 28 import android.tools.traces.component.IComponentNameMatcher 29 import android.tools.traces.region.RegionTrace 30 import android.tools.traces.surfaceflinger.Layer 31 import android.tools.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.map { 224 it.visibleRegion(componentMatcher, useCompositionEngineRegionOnly).regionEntry 225 } 226 ) 227 return RegionTraceSubject(regionTrace, reader) 228 } 229 230 fun atLeastOneEntryContainsOneDisplayOn(): LayersTraceSubject = apply { 231 isNotEmpty() 232 val anyEntryWithDisplayOn = 233 subjects.any { it.entry.displays.any { display -> display.isOn } } 234 if (!anyEntryWithDisplayOn) { 235 val errorMsgBuilder = 236 ExceptionMessageBuilder() 237 .forInvalidProperty("Display") 238 .setExpected("At least one display On in any entry") 239 .setActual("No displays on in any entry") 240 throw InvalidPropertyException(errorMsgBuilder) 241 } 242 } 243 244 override fun containsAtLeastOneDisplay(): LayersTraceSubject = apply { 245 addAssertion("containAtLeastOneDisplay", isOptional = false) { 246 it.containsAtLeastOneDisplay() 247 } 248 } 249 250 /** Executes a custom [assertion] on the current subject */ 251 operator fun invoke( 252 name: String, 253 isOptional: Boolean = false, 254 assertion: (LayerTraceEntrySubject) -> Unit 255 ): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) } 256 257 fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject = 258 hasFrameSequence(ComponentNameMatcher("", name), frameNumbers) 259 260 fun hasFrameSequence( 261 componentMatcher: IComponentMatcher, 262 frameNumbers: Iterable<Long> 263 ): LayersTraceSubject = apply { 264 val firstFrame = frameNumbers.first() 265 val entries = 266 trace.entries 267 .asSequence() 268 // map entry to buffer layers with name 269 .map { it.getLayerWithBuffer(componentMatcher) } 270 // removing all entries without the layer 271 .filterNotNull() 272 // removing all entries with the same frame number 273 .distinctBy { it.currFrame } 274 // drop until the first frame we are interested in 275 .dropWhile { layer -> layer.currFrame != firstFrame } 276 277 var numFound = 0 278 val frameNumbersMatch = 279 entries 280 .zip(frameNumbers.asSequence()) { layer, frameNumber -> 281 numFound++ 282 layer.currFrame == frameNumber 283 } 284 .all { it } 285 val allFramesFound = frameNumbers.count() == numFound 286 if (!allFramesFound || !frameNumbersMatch) { 287 val errorMsgBuilder = 288 ExceptionMessageBuilder() 289 .forSubject(this) 290 .forInvalidElement( 291 componentMatcher.toLayerIdentifier(), 292 expectElementExists = true 293 ) 294 .addExtraDescription("With frame sequence", frameNumbers.joinToString(",")) 295 throw InvalidElementException(errorMsgBuilder) 296 } 297 } 298 299 /** Run the assertions for all trace entries within the specified time range */ 300 fun forSystemUpTimeRange(startTime: Long, endTime: Long) { 301 val subjectsInRange = 302 subjects.filter { it.entry.timestamp.systemUptimeNanos in startTime..endTime } 303 assertionsChecker.test(subjectsInRange) 304 } 305 306 /** 307 * User-defined entry point for the trace entry with [timestamp] 308 * 309 * @param timestamp of the entry 310 */ 311 fun getEntryBySystemUpTime( 312 timestamp: Long, 313 byElapsedTimestamp: Boolean = false 314 ): LayerTraceEntrySubject { 315 return if (byElapsedTimestamp) { 316 subjects.first { it.entry.elapsedTimestamp == timestamp } 317 } else { 318 subjects.first { it.entry.timestamp.systemUptimeNanos == timestamp } 319 } 320 } 321 322 companion object { 323 val VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS = 324 listOf( 325 ComponentNameMatcher.SPLASH_SCREEN, 326 ComponentNameMatcher.SNAPSHOT, 327 ComponentNameMatcher.IME_SNAPSHOT, 328 ComponentNameMatcher.PIP_CONTENT_OVERLAY, 329 ComponentNameMatcher.EDGE_BACK_GESTURE_HANDLER, 330 ComponentNameMatcher.COLOR_FADE, 331 ComponentNameMatcher.TRANSITION_SNAPSHOT, 332 ComponentNameMatcher.FLOATING_ROTATION_BUTTON, 333 ComponentNameMatcher.WIRED_CHARGING_ANIMATION, 334 EdgeExtensionComponentMatcher() 335 ) 336 } 337 } 338