• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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