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