1 /*
2  * Copyright (C) 2018 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;
18 
19 import com.android.server.wm.flicker.Assertions.NamedAssertion;
20 import com.android.server.wm.flicker.Assertions.CompoundAssertion;
21 import com.android.server.wm.flicker.Assertions.Result;
22 
23 import java.util.ArrayList;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.stream.Collectors;
27 
28 /**
29  * Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject} used
30  * to filter trace entries and combine multiple assertions.
31  *
32  * @param <T> trace entry type
33  */
34 public class AssertionsChecker<T extends ITraceEntry> {
35     private boolean mFilterEntriesByRange = false;
36     private long mFilterStartTime = 0;
37     private long mFilterEndTime = 0;
38     private boolean mSkipUntilFirstAssertion = false;
39     private AssertionOption mOption = AssertionOption.NONE;
40     private List<CompoundAssertion<T>> mAssertions = new LinkedList<>();
41 
add(Assertions.TraceAssertion<T> assertion, String name)42     public void add(Assertions.TraceAssertion<T> assertion, String name) {
43         mAssertions.add(new CompoundAssertion<>(assertion, name));
44     }
45 
append(Assertions.TraceAssertion<T> assertion, String name)46     public void append(Assertions.TraceAssertion<T> assertion, String name) {
47         CompoundAssertion<T> lastAssertion = mAssertions.get(mAssertions.size() - 1);
48         lastAssertion.add(assertion, name);
49     }
50 
51     /**
52      * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
53      * end of the trace without passing any assertion, return a failure with the name/reason from
54      * the first assertion
55      *
56      * @return
57      */
skipUntilFirstAssertion()58     public void skipUntilFirstAssertion() {
59         mSkipUntilFirstAssertion = true;
60     }
61 
filterByRange(long startTime, long endTime)62     public void filterByRange(long startTime, long endTime) {
63         mFilterEntriesByRange = true;
64         mFilterStartTime = startTime;
65         mFilterEndTime = endTime;
66     }
67 
setOption(AssertionOption option)68     private void setOption(AssertionOption option) {
69         if (mOption != AssertionOption.NONE && option != mOption) {
70             throw new IllegalArgumentException(
71                     "Cannot use " + mOption + " option with " + option + " option.");
72         }
73         mOption = option;
74     }
75 
checkFirstEntry()76     public void checkFirstEntry() {
77         setOption(AssertionOption.CHECK_FIRST_ENTRY);
78     }
79 
checkLastEntry()80     public void checkLastEntry() {
81         setOption(AssertionOption.CHECK_LAST_ENTRY);
82     }
83 
checkChangingAssertions()84     public void checkChangingAssertions() {
85         setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS);
86     }
87 
88     /**
89      * Filters trace entries then runs assertions returning a list of failures.
90      *
91      * @param entries list of entries to perform assertions on
92      * @return list of failed assertion results
93      */
test(List<T> entries)94     public List<Result> test(List<T> entries) {
95         List<T> filteredEntries;
96 
97         if (mFilterEntriesByRange) {
98             filteredEntries =
99                     entries.stream()
100                             .filter(
101                                     e ->
102                                             ((e.getTimestamp() >= mFilterStartTime)
103                                                     && (e.getTimestamp() <= mFilterEndTime)))
104                             .collect(Collectors.toList());
105         } else {
106             filteredEntries = entries;
107         }
108 
109         switch (mOption) {
110             case CHECK_CHANGING_ASSERTIONS:
111                 return assertChanges(filteredEntries);
112             case CHECK_FIRST_ENTRY:
113                 return assertEntry(filteredEntries.get(0));
114             case CHECK_LAST_ENTRY:
115                 return assertEntry(filteredEntries.get(filteredEntries.size() - 1));
116         }
117         return assertAll(filteredEntries);
118     }
119 
120     /**
121      * Steps through each trace entry checking if provided assertions are true in the order they are
122      * added. Each assertion must be true for at least a single trace entry.
123      *
124      * <p>This can be used to check for asserting a change in property over a trace. Such as
125      * visibility for a window changes from true to false or top-most window changes from A to B and
126      * back to A again.
127      *
128      * <p>It is also possible to ignore failures on initial elements, until the first assertion
129      * passes, this allows the trace to be recorded for longer periods, and the checks to happen
130      * only after some time.
131      */
assertChanges(List<T> entries)132     private List<Result> assertChanges(List<T> entries) {
133         List<Result> failures = new ArrayList<>();
134         int entryIndex = 0;
135         int assertionIndex = 0;
136         int lastPassedAssertionIndex = -1;
137 
138         if (mAssertions.size() == 0) {
139             return failures;
140         }
141 
142         while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) {
143             NamedAssertion<T> currentAssertion = mAssertions.get(assertionIndex);
144             Result result = currentAssertion.apply(entries.get(entryIndex));
145             boolean ignoreFailure = mSkipUntilFirstAssertion && lastPassedAssertionIndex == -1;
146 
147             if (result.passed()) {
148                 lastPassedAssertionIndex = assertionIndex;
149                 entryIndex++;
150                 continue;
151             }
152 
153             if (ignoreFailure) {
154                 entryIndex++;
155                 continue;
156             }
157 
158             if (lastPassedAssertionIndex != assertionIndex) {
159                 failures.add(result);
160                 break;
161             }
162             assertionIndex++;
163 
164             if (assertionIndex == mAssertions.size()) {
165                 failures.add(result);
166                 break;
167             }
168         }
169 
170         if (lastPassedAssertionIndex == -1 && mAssertions.size() > 0 && failures.isEmpty()) {
171             failures.add(new Result(false /* success */, mAssertions.get(0).getName()));
172         }
173 
174         if (failures.isEmpty()) {
175             if (assertionIndex != mAssertions.size() - 1) {
176                 String reason =
177                         "\nAssertion "
178                                 + mAssertions.get(assertionIndex).getName()
179                                 + " never became false";
180                 reason +=
181                         "\nPassed assertions: "
182                                 + mAssertions
183                                         .stream()
184                                         .limit(assertionIndex)
185                                         .map(NamedAssertion::getName)
186                                         .collect(Collectors.joining(","));
187                 reason +=
188                         "\nUntested assertions: "
189                                 + mAssertions
190                                         .stream()
191                                         .skip(assertionIndex + 1)
192                                         .map(NamedAssertion::getName)
193                                         .collect(Collectors.joining(","));
194 
195                 Result result =
196                         new Result(
197                                 false /* success */,
198                                 0 /* timestamp */,
199                                 "assertChanges",
200                                 "Not all assertions passed." + reason);
201                 failures.add(result);
202             }
203         }
204         return failures;
205     }
206 
assertEntry(T entry)207     private List<Result> assertEntry(T entry) {
208         List<Result> failures = new ArrayList<>();
209         for (NamedAssertion<T> assertion : mAssertions) {
210             Result result = assertion.apply(entry);
211             if (result.failed()) {
212                 failures.add(result);
213             }
214         }
215         return failures;
216     }
217 
assertAll(List<T> entries)218     private List<Result> assertAll(List<T> entries) {
219         return mAssertions
220                 .stream()
221                 .flatMap(assertion -> entries.stream().map(assertion).filter(Result::failed))
222                 .collect(Collectors.toList());
223     }
224 
225     private enum AssertionOption {
226         NONE,
227         CHECK_CHANGING_ASSERTIONS,
228         CHECK_FIRST_ENTRY,
229         CHECK_LAST_ENTRY,
230     }
231 }
232