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