1 /* <lambda>null2 * Copyright (C) 2021 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 com.android.server.wm.flicker.traces.layers 18 19 import com.android.server.wm.flicker.assertions.Assertion 20 import com.android.server.wm.flicker.assertions.FlickerSubject 21 import com.android.server.wm.flicker.traces.FlickerFailureStrategy 22 import com.android.server.wm.flicker.traces.FlickerSubjectException 23 import com.android.server.wm.flicker.traces.region.RegionSubject 24 import com.android.server.wm.traces.common.FlickerComponentName 25 import com.android.server.wm.traces.common.layers.Layer 26 import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry 27 import com.android.server.wm.traces.common.layers.LayersTrace 28 import com.google.common.truth.ExpectFailure 29 import com.google.common.truth.Fact 30 import com.google.common.truth.FailureMetadata 31 import com.google.common.truth.FailureStrategy 32 import com.google.common.truth.StandardSubjectBuilder 33 import com.google.common.truth.Subject 34 35 /** 36 * Truth subject for [BaseLayerTraceEntry] objects, used to make assertions over behaviors that 37 * occur on a single SurfaceFlinger state. 38 * 39 * To make assertions over a specific state from a trace it is recommended to create a subject 40 * using [LayersTraceSubject.assertThat](myTrace) and select the specific state using: 41 * [LayersTraceSubject.first] 42 * [LayersTraceSubject.last] 43 * [LayersTraceSubject.entry] 44 * 45 * Alternatively, it is also possible to use [LayerTraceEntrySubject.assertThat](myState) or 46 * Truth.assertAbout([LayerTraceEntrySubject.getFactory]), however they will provide less debug 47 * information because it uses Truth's default [FailureStrategy]. 48 * 49 * Example: 50 * val trace = LayersTraceParser.parseFromTrace(myTraceFile) 51 * val subject = LayersTraceSubject.assertThat(trace).first() 52 * .contains("ValidLayer") 53 * .notContains("ImaginaryLayer") 54 * .coversExactly(DISPLAY_AREA) 55 * .invoke { myCustomAssertion(this) } 56 */ 57 class LayerTraceEntrySubject private constructor( 58 fm: FailureMetadata, 59 val entry: BaseLayerTraceEntry, 60 val trace: LayersTrace?, 61 override val parent: FlickerSubject? 62 ) : FlickerSubject(fm, entry) { 63 override val timestamp: Long get() = entry.timestamp 64 override val selfFacts = listOf(Fact.fact("Entry", entry)) 65 66 val subjects by lazy { 67 entry.flattenedLayers.map { LayerSubject.assertThat(it, this, timestamp) } 68 } 69 70 /** 71 * Executes a custom [assertion] on the current subject 72 */ 73 operator fun invoke(assertion: Assertion<BaseLayerTraceEntry>): LayerTraceEntrySubject = 74 apply { 75 assertion(this.entry) 76 } 77 78 /** 79 * Asserts that the current SurfaceFlinger state doesn't contain layers 80 */ 81 fun isEmpty(): LayerTraceEntrySubject = apply { 82 check("Entry should not be empty") 83 .that(entry.flattenedLayers) 84 .isEmpty() 85 } 86 87 /** 88 * Asserts that the current SurfaceFlinger state contains layers 89 */ 90 fun isNotEmpty(): LayerTraceEntrySubject = apply { 91 check("Entry should not be empty") 92 .that(entry.flattenedLayers) 93 .isNotEmpty() 94 } 95 96 /** 97 * Obtains the region occupied by all layers with name containing [components] 98 * 99 * @param components Components to search for 100 * @param useCompositionEngineRegionOnly If true, uses only the region calculated from the 101 * Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise calculates 102 * the visible region when the information is not available from the CE 103 */ 104 @JvmOverloads 105 fun visibleRegion( 106 vararg components: FlickerComponentName, 107 useCompositionEngineRegionOnly: Boolean = true 108 ): RegionSubject { 109 val layerNames = components.map { it.toLayerName() } 110 val selectedLayers = if (components.isEmpty()) { 111 // No filters so use all subjects 112 subjects 113 } else { 114 subjects.filter { 115 subject -> layerNames.any { 116 layerName -> subject.name.contains(layerName) 117 } 118 } 119 } 120 121 if (selectedLayers.isEmpty()) { 122 val str = if (layerNames.isNotEmpty()) layerNames.joinToString() else "<any>" 123 fail(listOf( 124 Fact.fact(ASSERTION_TAG, "visibleRegion($str)"), 125 Fact.fact("Use composition engine region", useCompositionEngineRegionOnly), 126 Fact.fact("Could not find layers", str)) 127 ) 128 } 129 130 val visibleLayers = selectedLayers.filter { it.isVisible } 131 return if (useCompositionEngineRegionOnly) { 132 val visibleAreas = visibleLayers.mapNotNull { it.layer?.visibleRegion }.toTypedArray() 133 RegionSubject.assertThat(visibleAreas, this, timestamp) 134 } else { 135 val visibleAreas = visibleLayers.mapNotNull { it.layer?.screenBounds }.toTypedArray() 136 RegionSubject.assertThat(visibleAreas, this, timestamp) 137 } 138 } 139 140 /** 141 * Asserts the state contains a [Layer] with [Layer.name] containing [component]. 142 * 143 * @param component Name of the layers to search 144 */ 145 fun contains(component: FlickerComponentName): LayerTraceEntrySubject = apply { 146 val layerName = component.toLayerName() 147 val found = entry.flattenedLayers.any { it.name.contains(layerName) } 148 if (!found) { 149 fail(Fact.fact(ASSERTION_TAG, "contains(${component.toLayerName()})"), 150 Fact.fact("Could not find", layerName)) 151 } 152 } 153 154 /** 155 * Asserts the state doesn't contain a [Layer] with [Layer.name] containing any of 156 * 157 * @param component Name of the layers to search 158 */ 159 fun notContains(component: FlickerComponentName): LayerTraceEntrySubject = apply { 160 val layerName = component.toLayerName() 161 val foundEntry = subjects.firstOrNull { it.name.contains(layerName) } 162 foundEntry?.fail(Fact.fact(ASSERTION_TAG, "notContains(${component.toLayerName()})"), 163 Fact.fact("Could find", foundEntry)) 164 } 165 166 /** 167 * Asserts that a [Layer] with [Layer.name] containing [component] is visible. 168 * 169 * @param component Name of the layers to search 170 */ 171 fun isVisible(component: FlickerComponentName): LayerTraceEntrySubject = apply { 172 contains(component) 173 var target: FlickerSubject? = null 174 var reason = listOf<Fact>() 175 val layerName = component.toLayerName() 176 val filteredLayers = subjects 177 .filter { it.name.contains(layerName) } 178 for (layer in filteredLayers) { 179 if (layer.layer?.isHiddenByParent == true) { 180 reason = listOf(Fact.fact("Hidden by parent", layer.layer.parent?.name)) 181 target = layer 182 continue 183 } 184 if (layer.isInvisible) { 185 reason = layer.layer?.visibilityReason 186 ?.map { Fact.fact("Is Invisible", it) } 187 ?: emptyList() 188 target = layer 189 continue 190 } 191 reason = emptyList() 192 target = null 193 break 194 } 195 196 if (reason.isNotEmpty()) { 197 target?.fail( 198 Fact.fact(ASSERTION_TAG, "isVisible(${component.toLayerName()})"), 199 *reason.toTypedArray()) 200 } 201 } 202 203 /** 204 * Asserts that a [Layer] with [Layer.name] containing [component] doesn't exist or 205 * is invisible. 206 * 207 * @param component Name of the layers to search 208 */ 209 fun isInvisible(component: FlickerComponentName): LayerTraceEntrySubject = apply { 210 try { 211 isVisible(component) 212 } catch (e: FlickerSubjectException) { 213 val cause = e.cause 214 require(cause is AssertionError) 215 ExpectFailure.assertThat(cause).factKeys().isNotEmpty() 216 return@apply 217 } 218 val layerName = component.toLayerName() 219 val foundEntry = subjects 220 .firstOrNull { it.name.contains(layerName) && it.isVisible } 221 foundEntry?.fail(Fact.fact(ASSERTION_TAG, "isInvisible(${component.toLayerName()})"), 222 Fact.fact("Is visible", foundEntry)) 223 } 224 225 /** 226 * Asserts that the entry contains a visible splash screen [Layer] for a [layer] with 227 * [Layer.name] containing any of [component]. 228 * 229 * @param component Name of the layer to search 230 */ 231 fun isSplashScreenVisibleFor(component: FlickerComponentName): LayerTraceEntrySubject = apply { 232 var target: FlickerSubject? = null 233 var reason: Fact? = null 234 val layerActivityRecordFilter = component.toActivityRecordFilter() 235 val filteredLayers = subjects 236 .filter { layerActivityRecordFilter.containsMatchIn(it.name) } 237 238 if (filteredLayers.isEmpty()) { 239 fail(Fact.fact(ASSERTION_TAG, "isSplashScreenVisibleFor(${component.toLayerName()})"), 240 Fact.fact("Could not find Activity Record layer", component.toShortWindowName())) 241 return this 242 } 243 244 // Check the matched activity record layers for containing splash screens 245 for (layer in filteredLayers) { 246 val splashScreenContainers = 247 layer.layer?.children?.filter { it.name.contains("Splash Screen") } 248 val splashScreenLayers = splashScreenContainers?.flatMap { 249 it.children.filter { childLayer -> 250 childLayer.name.contains("Splash Screen") 251 } 252 } 253 254 if (splashScreenLayers?.all { it.isHiddenByParent || !it.isVisible } ?: true) { 255 reason = Fact.fact("No splash screen visible for", layer.name) 256 target = layer 257 continue 258 } 259 reason = null 260 target = null 261 break 262 } 263 264 reason?.run { 265 target?.fail( 266 Fact.fact(ASSERTION_TAG, "isSplashScreenVisibleFor(${component.toLayerName()})"), 267 reason) 268 } 269 } 270 271 /** 272 * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name] 273 * containing [component]. 274 * Always returns a subject, event when the layer doesn't exist. To verify if layer 275 * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist] 276 * 277 * @return LayerSubject that can be used to make assertions on a single layer matching 278 */ 279 fun layer(component: FlickerComponentName): LayerSubject { 280 val name = component.toLayerName() 281 return layer { 282 it.name.contains(name) 283 } 284 } 285 286 /** 287 * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name] 288 * containing [name] in [frameNumber]. 289 * 290 * Always returns a subject, event when the layer doesn't exist. To verify if layer 291 * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist] 292 * 293 * @return LayerSubject that can be used to make assertions on a single layer matching 294 * [name] and [frameNumber]. 295 */ 296 fun layer(name: String, frameNumber: Long): LayerSubject { 297 return layer(name) { 298 it.name.contains(name) && it.currFrame == frameNumber 299 } 300 } 301 302 /** 303 * Obtains a [LayerSubject] for the first occurrence of a [Layer] matching [predicate] 304 * 305 * Always returns a subject, event when the layer doesn't exist. To verify if layer 306 * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist] 307 * 308 * @param predicate to search for a layer 309 * @param name Name of the subject to use when not found (optional) 310 * 311 * @return [LayerSubject] that can be used to make assertions 312 */ 313 @JvmOverloads 314 fun layer(name: String = "", predicate: (Layer) -> Boolean): LayerSubject { 315 return subjects.firstOrNull { 316 it.layer?.run { predicate(this) } ?: false 317 } ?: LayerSubject.assertThat(name, this, timestamp) 318 } 319 320 override fun toString(): String { 321 return "LayerTraceEntrySubject($entry)" 322 } 323 324 companion object { 325 /** 326 * Boiler-plate Subject.Factory for LayersTraceSubject 327 */ 328 private fun getFactory( 329 trace: LayersTrace?, 330 parent: FlickerSubject? 331 ): Factory<Subject, BaseLayerTraceEntry> = 332 Factory { fm, subject -> LayerTraceEntrySubject(fm, subject, trace, parent) } 333 334 /** 335 * Creates a [LayerTraceEntrySubject] to representing a SurfaceFlinger state[entry], 336 * which can be used to make assertions. 337 * 338 * @param entry SurfaceFlinger trace entry 339 * @param parent Trace that contains this entry (optional) 340 */ 341 @JvmStatic 342 @JvmOverloads 343 fun assertThat( 344 entry: BaseLayerTraceEntry, 345 trace: LayersTrace? = null, 346 parent: FlickerSubject? = null 347 ): LayerTraceEntrySubject { 348 val strategy = FlickerFailureStrategy() 349 val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy) 350 .about(getFactory(trace, parent)) 351 .that(entry) as LayerTraceEntrySubject 352 strategy.init(subject) 353 return subject 354 } 355 356 /** 357 * Static method for getting the subject factory (for use with assertAbout()) 358 */ 359 @JvmStatic 360 @JvmOverloads 361 fun entries( 362 trace: LayersTrace? = null, 363 parent: FlickerSubject? = null 364 ): Factory<Subject, BaseLayerTraceEntry> = getFactory(trace, parent) 365 } 366 }