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