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 18 19 import com.android.server.wm.flicker.assertions.Assertion 20 import com.android.server.wm.flicker.assertions.AssertionsChecker 21 import com.android.server.wm.flicker.assertions.FlickerSubject 22 import com.android.server.wm.traces.common.prettyTimestamp 23 import com.google.common.truth.Fact 24 import com.google.common.truth.FailureMetadata 25 26 /** 27 * Base subject for flicker trace assertions 28 */ 29 abstract class FlickerTraceSubject<EntrySubject : FlickerSubject>( 30 fm: FailureMetadata, 31 data: Any? 32 ) : FlickerSubject(fm, data) { 33 override val timestamp: Long get() = subjects.firstOrNull()?.timestamp ?: 0L 34 override val selfFacts by lazy { 35 val firstTimestamp = subjects.firstOrNull()?.timestamp ?: 0L 36 val lastTimestamp = subjects.lastOrNull()?.timestamp ?: 0L 37 val first = "${prettyTimestamp(firstTimestamp)} (timestamp=$firstTimestamp)" 38 val last = "${prettyTimestamp(lastTimestamp)} (timestamp=$lastTimestamp)" 39 listOf(Fact.fact("Trace start", first), 40 Fact.fact("Trace end", last)) 41 } 42 43 protected val assertionsChecker = AssertionsChecker<EntrySubject>() 44 private var newAssertionBlock = true 45 46 abstract val subjects: List<EntrySubject> 47 48 /** 49 * Empty the subject's list of assertions. 50 */ 51 internal fun clear() { 52 assertionsChecker.clear() 53 newAssertionBlock = true 54 } 55 56 /** 57 * Adds a new assertion block (if preceded by [then]) or appends an assertion to the 58 * latest existing assertion block 59 * 60 * @param name Assertion name 61 * @param isOptional If this assertion is optional or must pass 62 */ 63 protected fun addAssertion( 64 name: String, 65 isOptional: Boolean = false, 66 assertion: Assertion<EntrySubject> 67 ) { 68 if (newAssertionBlock) { 69 assertionsChecker.add(name, isOptional, assertion) 70 } else { 71 assertionsChecker.append(name, isOptional, assertion) 72 } 73 newAssertionBlock = false 74 } 75 76 /** 77 * Run the assertions for all trace entries 78 */ 79 fun forAllEntries() { 80 assertionsChecker.test(subjects) 81 } 82 83 /** 84 * User-defined entry point for the first trace entry 85 */ 86 fun first(): EntrySubject = subjects.firstOrNull() ?: error("Trace is empty") 87 88 /** 89 * User-defined entry point for the last trace entry 90 */ 91 fun last(): EntrySubject = subjects.lastOrNull() ?: error("Trace is empty") 92 93 /** 94 * Signal that the last assertion set is complete. The next assertion added will start a new 95 * set of assertions. 96 * 97 * E.g.: checkA().then().checkB() 98 * 99 * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked 100 * after checkA passes. 101 */ 102 open fun then(): FlickerTraceSubject<EntrySubject> = apply { 103 startAssertionBlock() 104 } 105 106 /** 107 * Ignores the first entries in the trace, until the first assertion passes. If it reaches the 108 * end of the trace without passing any assertion, return a failure with the name/reason from 109 * the first assertion 110 * 111 * @return 112 */ 113 open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> = 114 apply { assertionsChecker.skipUntilFirstAssertion() } 115 116 /** 117 * Signal that the last assertion set is complete. The next assertion added will start a new 118 * set of assertions. 119 * 120 * E.g.: checkA().then().checkB() 121 * 122 * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked 123 * after checkA passes. 124 */ 125 private fun startAssertionBlock() { 126 newAssertionBlock = true 127 } 128 129 /** 130 * Checks whether all the trace entries on the list are visible for more than one consecutive 131 * entry 132 * 133 * Ignore the first and last trace subjects. This is necessary because WM and SF traces 134 * log entries only when a change occurs. 135 * 136 * If the trace starts immediately before an animation or if it stops immediately after one, 137 * the first and last entry may contain elements that are visible only for that entry. 138 * Those elements, however, are not flickers, since they existed on the screen before or after 139 * the test. 140 * 141 * @param [visibleEntriesProvider] a list of all the entries with their name and index 142 */ 143 protected fun visibleEntriesShownMoreThanOneConsecutiveTime( 144 visibleEntriesProvider: (EntrySubject) -> Set<String> 145 ) { 146 if (subjects.isEmpty()) { 147 return 148 } 149 // Duplicate the first and last trace subjects to prevent them from triggering failures 150 // since WM and SF traces log entries only when a change occurs 151 val firstState = subjects.first() 152 val lastState = subjects.last() 153 val subjects = subjects.toMutableList().also { 154 it.add(lastState) 155 it.add(0, firstState) 156 } 157 var lastVisible = visibleEntriesProvider(subjects.first()) 158 val lastNew = lastVisible.toMutableSet() 159 160 // first subject was already taken 161 subjects.drop(1).forEachIndexed { index, entrySubject -> 162 val currentVisible = visibleEntriesProvider(entrySubject) 163 val newVisible = currentVisible.filter { it !in lastVisible } 164 lastNew.removeAll(currentVisible) 165 166 if (lastNew.isNotEmpty()) { 167 val prevEntry = subjects[index] 168 prevEntry.fail("$lastNew is not visible for 2 entries") 169 } 170 lastNew.addAll(newVisible) 171 lastVisible = currentVisible 172 } 173 174 if (lastNew.isNotEmpty()) { 175 val lastEntry = subjects.last() 176 lastEntry.fail("$lastNew is not visible for 2 entries") 177 } 178 } 179 180 override fun toString(): String = "${this::class.simpleName}" + 181 "(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})" 182 } 183