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 com.google.common.truth.Fact 20 import kotlin.math.max 21 22 /** 23 * Runs sequences of assertions on sequences of subjects. 24 * 25 * Starting at the first assertion and first trace entry, executes the assertions iteratively 26 * on the trace until all assertions and trace entries succeed. 27 * 28 * @param <T> trace entry type </T> 29 */ 30 class AssertionsChecker<T : FlickerSubject> { 31 private val assertions = mutableListOf<CompoundAssertion<T>>() 32 private var skipUntilFirstAssertion = false 33 addnull34 fun add(name: String, assertion: Assertion<T>) { 35 assertions.add(CompoundAssertion(assertion, name)) 36 } 37 38 /** 39 * Append [assertion] to the last existing set of assertions. 40 */ appendnull41 fun append(name: String, assertion: Assertion<T>) { 42 assertions.last().add(assertion, name) 43 } 44 45 /** 46 * Filters trace entries then runs assertions returning a list of failures. 47 * 48 * @param entries list of entries to perform assertions on 49 * @return list of failed assertion results 50 */ testnull51 fun test(entries: List<T>): Unit = assertChanges(entries) 52 53 /** 54 * Steps through each trace entry checking if provided assertions are true in the order they are 55 * added. Each assertion must be true for at least a single trace entry. 56 * 57 * 58 * This can be used to check for asserting a change in property over a trace. Such as 59 * visibility for a window changes from true to false or top-most window changes from A to B and 60 * back to A again. 61 * 62 * 63 * It is also possible to ignore failures on initial elements, until the first assertion 64 * passes, this allows the trace to be recorded for longer periods, and the checks to happen 65 * only after some time. 66 */ 67 private fun assertChanges(entries: List<T>) { 68 if (assertions.isEmpty() || entries.isEmpty()) { 69 return 70 } 71 72 val failures = mutableListOf<Throwable>() 73 var entryIndex = 0 74 var assertionIndex = 0 75 var lastPassedAssertionIndex = -1 76 while (assertionIndex < assertions.size && entryIndex < entries.size) { 77 val currentAssertion = assertions[assertionIndex] 78 val currEntry = entries[entryIndex] 79 try { 80 currentAssertion.invoke(currEntry) 81 lastPassedAssertionIndex = assertionIndex 82 entryIndex++ 83 } catch (e: Throwable) { 84 val ignoreFailure = skipUntilFirstAssertion && lastPassedAssertionIndex == -1 85 if (ignoreFailure) { 86 entryIndex++ 87 continue 88 } 89 if (lastPassedAssertionIndex != assertionIndex) { 90 val prevEntry = entries[max(entryIndex - 1, 0)] 91 prevEntry.fail(e) 92 } 93 assertionIndex++ 94 if (assertionIndex == assertions.size) { 95 val prevEntry = entries[max(entryIndex - 1, 0)] 96 prevEntry.fail(e) 97 } 98 } 99 } 100 if (lastPassedAssertionIndex == -1 && assertions.isNotEmpty() && failures.isEmpty()) { 101 entries.first().fail("Assertion never passed", assertions.first()) 102 } 103 104 if (failures.isEmpty() && assertionIndex != assertions.lastIndex) { 105 val reason = listOf( 106 Fact.fact("Assertion never became false", assertions[assertionIndex]), 107 Fact.fact("Passed assertions", assertions.take(assertionIndex).joinToString(",")), 108 Fact.fact("Untested assertions", 109 assertions.drop(assertionIndex + 1).joinToString(",")) 110 ) 111 entries.first().fail(reason) 112 } 113 } 114 115 /** 116 * Ignores the first entries in the trace, until the first assertion passes. If it reaches the 117 * end of the trace without passing any assertion, return a failure with the name/reason from 118 * the first assertion 119 */ skipUntilFirstAssertionnull120 fun skipUntilFirstAssertion() { 121 skipUntilFirstAssertion = true 122 } 123 } 124