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 java.util.ArrayList; 20 import java.util.List; 21 import java.util.Optional; 22 import java.util.concurrent.TimeUnit; 23 import java.util.function.Function; 24 import java.util.stream.Collectors; 25 26 /** 27 * Collection of functional interfaces and classes representing assertions and their associated 28 * results. Assertions are functions that are applied over a single trace entry and returns a result 29 * which includes a detailed reason if the assertion fails. 30 */ 31 public class Assertions { 32 /** 33 * Checks assertion on a single trace entry. 34 * 35 * @param <T> trace entry type to perform the assertion on. 36 */ 37 @FunctionalInterface 38 public interface TraceAssertion<T> extends Function<T, Result> { 39 /** 40 * Returns an assertion that represents the logical negation of this assertion. 41 * 42 * @return a assertion that represents the logical negation of this assertion 43 */ negate()44 default TraceAssertion<T> negate() { 45 return (T t) -> apply(t).negate(); 46 } 47 } 48 49 /** Checks assertion on a single layers trace entry. */ 50 @FunctionalInterface 51 public interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {} 52 53 /** 54 * Utility class to store assertions with an identifier to help generate more useful debug data 55 * when dealing with multiple assertions. 56 */ 57 public static class NamedAssertion<T> implements TraceAssertion<T> { 58 private final TraceAssertion<T> assertion; 59 private final String name; 60 NamedAssertion(TraceAssertion<T> assertion, String name)61 public NamedAssertion(TraceAssertion<T> assertion, String name) { 62 this.assertion = assertion; 63 this.name = name; 64 } 65 getName()66 public String getName() { 67 return this.name; 68 } 69 70 @Override apply(T t)71 public Result apply(T t) { 72 return this.assertion.apply(t); 73 } 74 75 @Override toString()76 public String toString() { 77 return "Assertion(" + this.name + ")"; 78 } 79 } 80 81 public static class CompoundAssertion<T> extends NamedAssertion<T> { 82 private final List<NamedAssertion<T>> assertions = new ArrayList<>(); 83 CompoundAssertion(TraceAssertion<T> assertion, String name)84 public CompoundAssertion(TraceAssertion<T> assertion, String name) { 85 super(assertion, name); 86 87 add(assertion, name); 88 } 89 add(TraceAssertion<T> assertion, String name)90 public void add(TraceAssertion<T> assertion, String name) { 91 this.assertions.add(new NamedAssertion<>(assertion, name)); 92 } 93 94 @Override getName()95 public String getName() { 96 return this.assertions.stream().map(p -> p.name).collect(Collectors.joining(" and ")); 97 } 98 99 @Override apply(T t)100 public Result apply(T t) { 101 List<Result> assertionResults = 102 this.assertions.stream().map(p -> p.apply(t)).collect(Collectors.toList()); 103 104 boolean passed = assertionResults.stream().allMatch(Result::passed); 105 String reason = 106 assertionResults 107 .stream() 108 .filter(p -> !p.passed()) 109 .map(p -> p.reason) 110 .collect(Collectors.joining(" and ")); 111 112 Optional<Long> timestamp = assertionResults.stream().map(p -> p.timestamp).findFirst(); 113 114 return timestamp 115 .map(p -> new Result(passed, p, this.getName(), reason)) 116 .orElseGet(() -> new Result(passed, reason)); 117 } 118 119 @Override toString()120 public String toString() { 121 return "CompoundAssertion(" + this.getName() + ")"; 122 } 123 } 124 125 /** Contains the result of an assertion including the reason for failed assertions. */ 126 public static class Result { 127 public static final String NEGATION_PREFIX = "!"; 128 public final boolean success; 129 public final long timestamp; 130 public final String assertionName; 131 public final String reason; 132 Result(boolean success, long timestamp, String assertionName, String reason)133 public Result(boolean success, long timestamp, String assertionName, String reason) { 134 this.success = success; 135 this.timestamp = timestamp; 136 this.assertionName = assertionName; 137 this.reason = reason; 138 } 139 Result(boolean success, String reason)140 public Result(boolean success, String reason) { 141 this.success = success; 142 this.reason = reason; 143 this.assertionName = ""; 144 this.timestamp = 0; 145 } 146 147 /** Returns the negated {@code Result} and adds a negation prefix to the assertion name. */ negate()148 public Result negate() { 149 String negatedAssertionName; 150 if (this.assertionName.startsWith(NEGATION_PREFIX)) { 151 negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1); 152 } else { 153 negatedAssertionName = NEGATION_PREFIX + this.assertionName; 154 } 155 return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason); 156 } 157 passed()158 public boolean passed() { 159 return this.success; 160 } 161 failed()162 public boolean failed() { 163 return !this.success; 164 } 165 166 @Override toString()167 public String toString() { 168 return "Timestamp: " 169 + prettyTimestamp(timestamp) 170 + "\nAssertion: " 171 + assertionName 172 + "\nReason: " 173 + reason; 174 } 175 prettyTimestamp(long timestamp_ns)176 private String prettyTimestamp(long timestamp_ns) { 177 StringBuilder prettyTimestamp = new StringBuilder(); 178 TimeUnit[] timeUnits = { 179 TimeUnit.DAYS, 180 TimeUnit.HOURS, 181 TimeUnit.MINUTES, 182 TimeUnit.SECONDS, 183 TimeUnit.MILLISECONDS 184 }; 185 String[] unitSuffixes = {"d", "h", "m", "s", "ms"}; 186 187 for (int i = 0; i < timeUnits.length; i++) { 188 long convertedTime = timeUnits[i].convert(timestamp_ns, TimeUnit.NANOSECONDS); 189 timestamp_ns -= TimeUnit.NANOSECONDS.convert(convertedTime, timeUnits[i]); 190 prettyTimestamp.append(convertedTime).append(unitSuffixes[i]); 191 } 192 193 return prettyTimestamp.toString(); 194 } 195 } 196 } 197