1 /* 2 * 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.assertions 18 19 import android.util.Log 20 import com.android.server.wm.flicker.FLICKER_TAG 21 import com.google.common.truth.Fact 22 import kotlin.math.max 23 24 /** 25 * Runs sequences of assertions on sequences of subjects. 26 * 27 * Starting at the first assertion and first trace entry, executes the assertions iteratively 28 * on the trace until all assertions and trace entries succeed. 29 * 30 * @param <T> trace entry type </T> 31 */ 32 class AssertionsChecker<T : FlickerSubject> { 33 private val assertions = mutableListOf<CompoundAssertion<T>>() 34 private var skipUntilFirstAssertion = false 35 36 /** 37 * Empty the list of assertions. 38 */ clearnull39 internal fun clear() { 40 assertions.clear() 41 } 42 43 /** 44 * Add [assertion] to a new [CompoundAssertion] block. 45 */ addnull46 fun add(name: String, isOptional: Boolean = false, assertion: Assertion<T>) { 47 assertions.add(CompoundAssertion(assertion, name, isOptional)) 48 } 49 50 /** 51 * Append [assertion] to the last existing set of assertions. 52 */ appendnull53 fun append(name: String, isOptional: Boolean = false, assertion: Assertion<T>) { 54 assertions.last().add(assertion, name, isOptional) 55 } 56 57 /** 58 * Filters trace entries then runs assertions returning a list of failures. 59 * 60 * @param entries list of entries to perform assertions on 61 * @return list of failed assertion results 62 */ testnull63 fun test(entries: List<T>): Unit = assertChanges(entries) 64 65 /** 66 * Steps through each trace entry checking if provided assertions are true in the order they are 67 * added. Each assertion must be true for at least a single trace entry. 68 * 69 * 70 * This can be used to check for asserting a change in property over a trace. Such as 71 * visibility for a window changes from true to false or top-most window changes from A to B and 72 * back to A again. 73 * 74 * 75 * It is also possible to ignore failures on initial elements, until the first assertion 76 * passes, this allows the trace to be recorded for longer periods, and the checks to happen 77 * only after some time. 78 */ 79 private fun assertChanges(entries: List<T>) { 80 if (assertions.isEmpty() || entries.isEmpty()) { 81 return 82 } 83 84 val failures = mutableListOf<Throwable>() 85 var entryIndex = 0 86 var assertionIndex = 0 87 var lastPassedAssertionIndex = -1 88 val assertionTrace = mutableListOf<String>() 89 while (assertionIndex < assertions.size && entryIndex < entries.size) { 90 val currentAssertion = assertions[assertionIndex] 91 val currEntry = entries[entryIndex] 92 try { 93 val log = "${assertionIndex + 1}/${assertions.size}:[${currentAssertion.name}]\t" + 94 "Entry: ${entryIndex + 1}/${entries.size} $currEntry" 95 Log.v(FLICKER_TAG, "Checking Assertion: $log") 96 assertionTrace.add(log) 97 currentAssertion.invoke(currEntry) 98 lastPassedAssertionIndex = assertionIndex 99 entryIndex++ 100 } catch (e: Throwable) { 101 // ignore errors at the start of the trace 102 val ignoreFailure = skipUntilFirstAssertion && lastPassedAssertionIndex == -1 103 if (ignoreFailure) { 104 entryIndex++ 105 continue 106 } 107 // failure is an optional assertion, just consider it passed skip it 108 if (currentAssertion.isOptional) { 109 lastPassedAssertionIndex = assertionIndex 110 assertionIndex++ 111 continue 112 } 113 if (lastPassedAssertionIndex != assertionIndex) { 114 val prevEntry = entries[max(entryIndex - 1, 0)] 115 prevEntry.fail(e) 116 } 117 assertionIndex++ 118 if (assertionIndex == assertions.size) { 119 val prevEntry = entries[max(entryIndex - 1, 0)] 120 prevEntry.fail(e) 121 } 122 } 123 } 124 // Didn't pass any assertions 125 if (lastPassedAssertionIndex == -1 && assertions.isNotEmpty() && failures.isEmpty()) { 126 entries.first().fail("Assertion never passed", assertions.first()) 127 } 128 129 val untestedAssertions = assertions.drop(assertionIndex + 1) 130 if (failures.isEmpty() && untestedAssertions.any { !it.isOptional }) { 131 val passedAssertionsFacts = assertions.take(assertionIndex) 132 .map { Fact.fact("Passed", it) } 133 val untestedAssertionsFacts = untestedAssertions 134 .map { Fact.fact("Untested", it) } 135 val trace = assertionTrace.map { Fact.fact("Trace", it) } 136 val reason = mutableListOf<Fact>() 137 reason.addAll(passedAssertionsFacts) 138 reason.add(Fact.fact("Assertion never failed", assertions[assertionIndex])) 139 reason.addAll(untestedAssertionsFacts) 140 reason.addAll(trace) 141 entries.first().fail(reason) 142 } 143 } 144 145 /** 146 * Ignores the first entries in the trace, until the first assertion passes. If it reaches the 147 * end of the trace without passing any assertion, return a failure with the name/reason from 148 * the first assertion 149 */ skipUntilFirstAssertionnull150 fun skipUntilFirstAssertion() { 151 skipUntilFirstAssertion = true 152 } 153 } 154