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