blob: 08f3577822dcc69a6d01d2b5a4b09b9cc6325384 [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 com.android.server.wm.traces.common.subjects
import com.android.server.wm.traces.common.CrossPlatform
import com.android.server.wm.traces.common.assertions.AssertionsChecker
import com.android.server.wm.traces.common.assertions.Fact
/** Base subject for flicker trace assertions */
abstract class FlickerTraceSubject<EntrySubject : FlickerSubject> : FlickerSubject() {
override val timestamp
get() = subjects.firstOrNull()?.timestamp ?: CrossPlatform.timestamp.empty()
override val selfFacts by lazy {
listOf(
Fact("Trace start", subjects.firstOrNull()?.timestamp),
Fact("Trace end", subjects.lastOrNull()?.timestamp)
)
}
protected val assertionsChecker = AssertionsChecker<EntrySubject>()
private var newAssertionBlock = true
abstract val subjects: List<EntrySubject>
fun hasAssertions() = !assertionsChecker.isEmpty()
/**
* Adds a new assertion block (if preceded by [then]) or appends an assertion to the latest
* existing assertion block
*
* @param name Assertion name
* @param isOptional If this assertion is optional or must pass
*/
protected fun addAssertion(
name: String,
isOptional: Boolean = false,
assertion: (EntrySubject) -> Unit
) {
if (newAssertionBlock) {
assertionsChecker.add(name, isOptional, assertion)
} else {
assertionsChecker.append(name, isOptional, assertion)
}
newAssertionBlock = false
}
/** Run the assertions for all trace entries */
fun forAllEntries() {
require(subjects.isNotEmpty()) { "Trace is empty" }
assertionsChecker.test(subjects)
}
/** User-defined entry point for the first trace entry */
fun first(): EntrySubject = subjects.firstOrNull() ?: error("Trace is empty")
/** User-defined entry point for the last trace entry */
fun last(): EntrySubject = subjects.lastOrNull() ?: error("Trace is empty")
/**
* Signal that the last assertion set is complete. The next assertion added will start a new set
* of assertions.
*
* E.g.: checkA().then().checkB()
*
* Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
* after checkA passes.
*/
open fun then(): FlickerTraceSubject<EntrySubject> = apply { startAssertionBlock() }
/**
* Ignores the first entries in the trace, until the first assertion passes. If it reaches the
* end of the trace without passing any assertion, return a failure with the name/reason from
* the first assertion
*
* @return
*/
open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> = apply {
assertionsChecker.skipUntilFirstAssertion()
}
/**
* Signal that the last assertion set is complete. The next assertion added will start a new set
* of assertions.
*
* E.g.: checkA().then().checkB()
*
* Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
* after checkA passes.
*/
private fun startAssertionBlock() {
newAssertionBlock = true
}
/**
* Checks whether all the trace entries on the list are visible for more than one consecutive
* entry
*
* Ignore the first and last trace subjects. This is necessary because WM and SF traces log
* entries only when a change occurs.
*
* If the trace starts immediately before an animation or if it stops immediately after one, the
* first and last entry may contain elements that are visible only for that entry. Those
* elements, however, are not flickers, since they existed on the screen before or after the
* test.
*
* @param [visibleEntriesProvider] a list of all the entries with their name and index
*/
protected fun visibleEntriesShownMoreThanOneConsecutiveTime(
visibleEntriesProvider: (EntrySubject) -> Set<String>
) {
if (subjects.isEmpty()) {
return
}
// Duplicate the first and last trace subjects to prevent them from triggering failures
// since WM and SF traces log entries only when a change occurs
val firstState = subjects.first()
val lastState = subjects.last()
val subjects =
subjects.toMutableList().also {
it.add(lastState)
it.add(0, firstState)
}
var lastVisible = visibleEntriesProvider(subjects.first())
val lastNew = lastVisible.toMutableSet()
// first subject was already taken
subjects.drop(1).forEachIndexed { index, entrySubject ->
val currentVisible = visibleEntriesProvider(entrySubject)
val newVisible = currentVisible.filter { it !in lastVisible }
lastNew.removeAll(currentVisible)
if (lastNew.isNotEmpty()) {
val prevEntry = subjects[index]
prevEntry.fail("$lastNew is not visible for 2 entries")
}
lastNew.addAll(newVisible)
lastVisible = currentVisible
}
if (lastNew.isNotEmpty()) {
val lastEntry = subjects.last()
lastEntry.fail("$lastNew is not visible for 2 entries")
}
}
override fun toString(): String =
"${this::class.simpleName}" +
"(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})"
}