blob: de3b1fbf8e05d9adf49e3e8c84e488acabb50ffd [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tools.common.flicker.subject.layers
import android.tools.common.flicker.subject.FlickerTraceSubject
import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder
import android.tools.common.flicker.subject.exceptions.InvalidElementException
import android.tools.common.flicker.subject.exceptions.InvalidPropertyException
import android.tools.common.flicker.subject.region.RegionTraceSubject
import android.tools.common.io.IReader
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.EdgeExtensionComponentMatcher
import android.tools.common.traces.component.IComponentMatcher
import android.tools.common.traces.component.IComponentNameMatcher
import android.tools.common.traces.region.RegionTrace
import android.tools.common.traces.surfaceflinger.Layer
import android.tools.common.traces.surfaceflinger.LayersTrace
/**
* Subject for [LayersTrace] objects, used to make assertions over behaviors that occur throughout a
* whole trace
*
* To make assertions over a trace it is recommended to create a subject using
* [LayersTraceSubject](myTrace).
*
* Example:
* ```
* val trace = LayersTraceParser().parse(myTraceFile)
* val subject = LayersTraceSubject(trace)
* .contains("ValidLayer")
* .notContains("ImaginaryLayer")
* .coversExactly(DISPLAY_AREA)
* .forAllEntries()
* ```
*
* Example2:
* ```
* val trace = LayersTraceParser().parse(myTraceFile)
* val subject = LayersTraceSubject(trace) {
* check("Custom check") { myCustomAssertion(this) }
* }
* ```
*/
class LayersTraceSubject(val trace: LayersTrace, override val reader: IReader? = null) :
FlickerTraceSubject<LayerTraceEntrySubject>(),
ILayerSubject<LayersTraceSubject, RegionTraceSubject> {
override val subjects by lazy {
trace.entries.map { LayerTraceEntrySubject(it, reader, trace) }
}
/** {@inheritDoc} */
override fun then(): LayersTraceSubject = apply { super.then() }
/** {@inheritDoc} */
override fun isEmpty(): LayersTraceSubject = apply {
check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true)
}
/** {@inheritDoc} */
override fun isNotEmpty(): LayersTraceSubject = apply {
check { "Trace is not empty" }.that(trace.entries.isNotEmpty()).isEqual(true)
}
/** {@inheritDoc} */
override fun layer(name: String, frameNumber: Long): LayerSubject {
val result = subjects.firstNotNullOfOrNull { it.layer(name, frameNumber) }
if (result == null) {
val errorMsgBuilder =
ExceptionMessageBuilder()
.forSubject(this)
.forInvalidElement(name, expectElementExists = true)
.addExtraDescription("Frame number", frameNumber)
throw InvalidElementException(errorMsgBuilder)
}
return result
}
/** @return List of [LayerSubject]s matching [name] in the order they appear on the trace */
fun layers(name: String): List<LayerSubject> =
subjects.mapNotNull { it.layer { layer -> layer.name.contains(name) } }
/**
* @return List of [LayerSubject]s matching [predicate] in the order they appear on the trace
*/
fun layers(predicate: (Layer) -> Boolean): List<LayerSubject> =
subjects.mapNotNull { it.layer { layer -> predicate(layer) } }
/** Checks that all visible layers are shown for more than one consecutive entry */
fun visibleLayersShownMoreThanOneConsecutiveEntry(
ignoreLayers: List<IComponentMatcher> =
listOf(
ComponentNameMatcher.SPLASH_SCREEN,
ComponentNameMatcher.SNAPSHOT,
ComponentNameMatcher.IME_SNAPSHOT,
ComponentNameMatcher.PIP_CONTENT_OVERLAY,
ComponentNameMatcher.EDGE_BACK_GESTURE_HANDLER,
ComponentNameMatcher.COLOR_FADE_BLAST,
EdgeExtensionComponentMatcher()
)
): LayersTraceSubject = apply {
visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
subject.entry.visibleLayers
.filter { visibleLayer ->
ignoreLayers.none { matcher -> matcher.layerMatchesAnyOf(visibleLayer) }
}
.map { it.name }
.toSet()
}
}
/** {@inheritDoc} */
override fun notContains(componentMatcher: IComponentMatcher): LayersTraceSubject =
notContains(componentMatcher, isOptional = false)
/** See [notContains] */
fun notContains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
apply {
addAssertion("notContains(${componentMatcher.toLayerIdentifier()})", isOptional) {
it.notContains(componentMatcher)
}
}
/** {@inheritDoc} */
override fun contains(componentMatcher: IComponentMatcher): LayersTraceSubject =
contains(componentMatcher, isOptional = false)
/** See [contains] */
fun contains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
apply {
addAssertion("contains(${componentMatcher.toLayerIdentifier()})", isOptional) {
it.contains(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isVisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
isVisible(componentMatcher, isOptional = false)
/** See [isVisible] */
fun isVisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
apply {
addAssertion("isVisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
it.isVisible(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isInvisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
isInvisible(componentMatcher, isOptional = false)
/** See [isInvisible] */
fun isInvisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
apply {
addAssertion("isInvisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
it.isInvisible(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isSplashScreenVisibleFor(
componentMatcher: IComponentNameMatcher
): LayersTraceSubject = isSplashScreenVisibleFor(componentMatcher, isOptional = false)
/** {@inheritDoc} */
override fun hasColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
addAssertion("hasColor(${componentMatcher.toLayerIdentifier()})") {
it.hasColor(componentMatcher)
}
}
/** {@inheritDoc} */
override fun hasNoColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
addAssertion("hasNoColor(${componentMatcher.toLayerIdentifier()})") {
it.hasNoColor(componentMatcher)
}
}
/** {@inheritDoc} */
override fun hasRoundedCorners(componentMatcher: IComponentMatcher): LayersTraceSubject =
apply {
addAssertion("hasRoundedCorners(${componentMatcher.toLayerIdentifier()})") {
it.hasRoundedCorners(componentMatcher)
}
}
/** See [isSplashScreenVisibleFor] */
fun isSplashScreenVisibleFor(
componentMatcher: IComponentNameMatcher,
isOptional: Boolean
): LayersTraceSubject = apply {
addAssertion(
"isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})",
isOptional
) {
it.isSplashScreenVisibleFor(componentMatcher)
}
}
/** See [visibleRegion] */
fun visibleRegion(): RegionTraceSubject =
visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = false)
/** See [visibleRegion] */
fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject =
visibleRegion(componentMatcher, useCompositionEngineRegionOnly = false)
/** {@inheritDoc} */
override fun visibleRegion(
componentMatcher: IComponentMatcher?,
useCompositionEngineRegionOnly: Boolean
): RegionTraceSubject {
val regionTrace =
RegionTrace(
componentMatcher,
subjects
.map {
it.visibleRegion(componentMatcher, useCompositionEngineRegionOnly)
.regionEntry
}
.toTypedArray()
)
return RegionTraceSubject(regionTrace, reader)
}
fun atLeastOneEntryContainsOneDisplayOn(): LayersTraceSubject = apply {
isNotEmpty()
val anyEntryWithDisplayOn =
subjects.any { it.entry.displays.any { display -> display.isOn } }
if (!anyEntryWithDisplayOn) {
val errorMsgBuilder =
ExceptionMessageBuilder()
.forInvalidProperty("Display")
.setExpected("At least one display On in any entry")
.setActual("No displays on in any entry")
throw InvalidPropertyException(errorMsgBuilder)
}
}
override fun containsAtLeastOneDisplay(): LayersTraceSubject = apply {
addAssertion("containAtLeastOneDisplay", isOptional = false) {
it.containsAtLeastOneDisplay()
}
}
/** Executes a custom [assertion] on the current subject */
operator fun invoke(
name: String,
isOptional: Boolean = false,
assertion: (LayerTraceEntrySubject) -> Unit
): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) }
fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject =
hasFrameSequence(ComponentNameMatcher("", name), frameNumbers)
fun hasFrameSequence(
componentMatcher: IComponentMatcher,
frameNumbers: Iterable<Long>
): LayersTraceSubject = apply {
val firstFrame = frameNumbers.first()
val entries =
trace.entries
.asSequence()
// map entry to buffer layers with name
.map { it.getLayerWithBuffer(componentMatcher) }
// removing all entries without the layer
.filterNotNull()
// removing all entries with the same frame number
.distinctBy { it.currFrame }
// drop until the first frame we are interested in
.dropWhile { layer -> layer.currFrame != firstFrame }
var numFound = 0
val frameNumbersMatch =
entries
.zip(frameNumbers.asSequence()) { layer, frameNumber ->
numFound++
layer.currFrame == frameNumber
}
.all { it }
val allFramesFound = frameNumbers.count() == numFound
if (!allFramesFound || !frameNumbersMatch) {
val errorMsgBuilder =
ExceptionMessageBuilder()
.forSubject(this)
.forInvalidElement(
componentMatcher.toLayerIdentifier(),
expectElementExists = true
)
.addExtraDescription("With frame sequence", frameNumbers.joinToString(","))
throw InvalidElementException(errorMsgBuilder)
}
}
/** Run the assertions for all trace entries within the specified time range */
fun forSystemUpTimeRange(startTime: Long, endTime: Long) {
val subjectsInRange =
subjects.filter { it.entry.timestamp.systemUptimeNanos in startTime..endTime }
assertionsChecker.test(subjectsInRange)
}
/**
* User-defined entry point for the trace entry with [timestamp]
*
* @param timestamp of the entry
*/
fun getEntryBySystemUpTime(
timestamp: Long,
byElapsedTimestamp: Boolean = false
): LayerTraceEntrySubject {
return if (byElapsedTimestamp) {
subjects.first { it.entry.elapsedTimestamp == timestamp }
} else {
subjects.first { it.entry.timestamp.systemUptimeNanos == timestamp }
}
}
}