blob: 3c65d3c341b33511811b9931ae0f58fd4873c1f6 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm.flicker;
import com.android.server.wm.flicker.Assertions.NamedAssertion;
import com.android.server.wm.flicker.Assertions.Result;
import com.android.server.wm.flicker.Assertions.TraceAssertion;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject}
* used to filter trace entries and combine multiple assertions.
*
* @param <T> trace entry type
*/
public class AssertionsChecker<T extends ITraceEntry> {
private boolean mFilterEntriesByRange = false;
private long mFilterStartTime = 0;
private long mFilterEndTime = 0;
private AssertionOption mOption = AssertionOption.NONE;
private List<NamedAssertion<T>> mAssertions = new LinkedList<>();
void add(Assertions.TraceAssertion<T> assertion, String name) {
mAssertions.add(new NamedAssertion<>(assertion, name));
}
void filterByRange(long startTime, long endTime) {
mFilterEntriesByRange = true;
mFilterStartTime = startTime;
mFilterEndTime = endTime;
}
private void setOption(AssertionOption option) {
if (mOption != AssertionOption.NONE && option != mOption) {
throw new IllegalArgumentException("Cannot use " + mOption + " option with "
+ option + " option.");
}
mOption = option;
}
public void checkFirstEntry() {
setOption(AssertionOption.CHECK_FIRST_ENTRY);
}
public void checkLastEntry() {
setOption(AssertionOption.CHECK_LAST_ENTRY);
}
public void checkChangingAssertions() {
setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS);
}
/**
* Filters trace entries then runs assertions returning a list of failures.
*
* @param entries list of entries to perform assertions on
* @return list of failed assertion results
*/
List<Result> test(List<T> entries) {
List<T> filteredEntries;
List<Result> failures;
if (mFilterEntriesByRange) {
filteredEntries = entries.stream()
.filter(e -> ((e.getTimestamp() >= mFilterStartTime)
&& (e.getTimestamp() <= mFilterEndTime)))
.collect(Collectors.toList());
} else {
filteredEntries = entries;
}
switch (mOption) {
case CHECK_CHANGING_ASSERTIONS:
return assertChanges(filteredEntries);
case CHECK_FIRST_ENTRY:
return assertEntry(filteredEntries.get(0));
case CHECK_LAST_ENTRY:
return assertEntry(filteredEntries.get(filteredEntries.size() - 1));
}
return assertAll(filteredEntries);
}
/**
* Steps through each trace entry checking if provided assertions are true in the order they
* are added. Each assertion must be true for at least a single trace entry.
*
* This can be used to check for asserting a change in property over a trace. Such as visibility
* for a window changes from true to false or top-most window changes from A to Bb and back to A
* again.
*/
private List<Result> assertChanges(List<T> entries) {
List<Result> failures = new ArrayList<>();
int entryIndex = 0;
int assertionIndex = 0;
int lastPassedAssertionIndex = -1;
if (mAssertions.size() == 0) {
return failures;
}
while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) {
TraceAssertion<T> currentAssertion = mAssertions.get(assertionIndex).assertion;
Result result = currentAssertion.apply(entries.get(entryIndex));
if (result.passed()) {
lastPassedAssertionIndex = assertionIndex;
entryIndex++;
continue;
}
if (lastPassedAssertionIndex != assertionIndex) {
failures.add(result);
break;
}
assertionIndex++;
if (assertionIndex == mAssertions.size()) {
failures.add(result);
break;
}
}
if (failures.isEmpty()) {
if (assertionIndex != mAssertions.size() - 1) {
String reason = "\nAssertion " + mAssertions.get(assertionIndex).name
+ " never became false";
reason += "\nPassed assertions: " + mAssertions.stream().limit(assertionIndex)
.map(assertion -> assertion.name).collect(Collectors.joining(","));
reason += "\nUntested assertions: " + mAssertions.stream().skip(assertionIndex + 1)
.map(assertion -> assertion.name).collect(Collectors.joining(","));
Result result = new Result(false /* success */, 0 /* timestamp */,
"assertChanges", "Not all assertions passed." + reason);
failures.add(result);
}
}
return failures;
}
private List<Result> assertEntry(T entry) {
List<Result> failures = new ArrayList<>();
for (NamedAssertion<T> assertion : mAssertions) {
Result result = assertion.assertion.apply(entry);
if (result.failed()) {
failures.add(result);
}
}
return failures;
}
private List<Result> assertAll(List<T> entries) {
return mAssertions.stream().flatMap(
assertion -> entries.stream()
.map(assertion.assertion)
.filter(Result::failed))
.collect(Collectors.toList());
}
private enum AssertionOption {
NONE,
CHECK_CHANGING_ASSERTIONS,
CHECK_FIRST_ENTRY,
CHECK_LAST_ENTRY,
}
}