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