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