1 /* 2 * Copyright (C) 2020 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 package com.android.launcher3.tapl; 17 18 import android.os.SystemClock; 19 20 import com.android.launcher3.testing.TestProtocol; 21 22 import java.util.ArrayList; 23 import java.util.HashMap; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.regex.Pattern; 27 28 /** 29 * Utility class to verify expected events. 30 */ 31 public class LogEventChecker { 32 33 private final LauncherInstrumentation mLauncher; 34 35 // Map from an event sequence name to an ordered list of expected events in that sequence. 36 private final ListMap<Pattern> mExpectedEvents = new ListMap<>(); 37 LogEventChecker(LauncherInstrumentation launcher)38 LogEventChecker(LauncherInstrumentation launcher) { 39 mLauncher = launcher; 40 } 41 start()42 boolean start() { 43 mExpectedEvents.clear(); 44 return mLauncher.getTestInfo(TestProtocol.REQUEST_START_EVENT_LOGGING) != null; 45 } 46 expectPattern(String sequence, Pattern pattern)47 void expectPattern(String sequence, Pattern pattern) { 48 mExpectedEvents.add(sequence, pattern); 49 } 50 51 // Waits for the expected number of events and returns them. finishSync(long waitForExpectedCountMs)52 private ListMap<String> finishSync(long waitForExpectedCountMs) { 53 final long startTime = SystemClock.uptimeMillis(); 54 // Event strings with '/' separating the sequence and the event. 55 ArrayList<String> rawEvents; 56 57 while (true) { 58 rawEvents = mLauncher.getTestInfo(TestProtocol.REQUEST_GET_TEST_EVENTS) 59 .getStringArrayList(TestProtocol.TEST_INFO_RESPONSE_FIELD); 60 final int expectedCount = mExpectedEvents.entrySet() 61 .stream().mapToInt(e -> e.getValue().size()).sum(); 62 if (rawEvents.size() >= expectedCount 63 || SystemClock.uptimeMillis() > startTime + waitForExpectedCountMs) { 64 break; 65 } 66 SystemClock.sleep(100); 67 } 68 69 finishNoWait(); 70 71 // Parse raw events into a map. 72 final ListMap<String> eventSequences = new ListMap<>(); 73 for (String rawEvent : rawEvents) { 74 final String[] split = rawEvent.split("/"); 75 eventSequences.add(split[0], split[1]); 76 } 77 return eventSequences; 78 } 79 finishNoWait()80 void finishNoWait() { 81 mLauncher.getTestInfo(TestProtocol.REQUEST_STOP_EVENT_LOGGING); 82 } 83 verify(long waitForExpectedCountMs, boolean successfulGesture)84 String verify(long waitForExpectedCountMs, boolean successfulGesture) { 85 final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs); 86 87 final StringBuilder sb = new StringBuilder(); 88 boolean hasMismatches = false; 89 for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) { 90 String sequence = expectedEvents.getKey(); 91 92 List<String> actual = new ArrayList<>(actualEvents.getNonNull(sequence)); 93 final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual); 94 hasMismatches = hasMismatches 95 || mismatchPosition != -1 && !ignoreMistatch(successfulGesture, sequence); 96 formatSequenceWithMismatch( 97 sb, 98 sequence, 99 expectedEvents.getValue(), 100 actual, 101 mismatchPosition); 102 } 103 // Check for unexpected event sequences in the actual data. 104 for (String actualNamedSequence : actualEvents.keySet()) { 105 if (!mExpectedEvents.containsKey(actualNamedSequence)) { 106 hasMismatches = hasMismatches 107 || !ignoreMistatch(successfulGesture, actualNamedSequence); 108 formatSequenceWithMismatch( 109 sb, 110 actualNamedSequence, 111 new ArrayList<>(), 112 actualEvents.get(actualNamedSequence), 113 0); 114 } 115 } 116 117 return hasMismatches ? "mismatching events: " + sb.toString() : null; 118 } 119 120 // Workaround for b/154157191 ignoreMistatch(boolean successfulGesture, String sequence)121 private static boolean ignoreMistatch(boolean successfulGesture, String sequence) { 122 // b/156287114 123 return false; 124 // return TestProtocol.SEQUENCE_TIS.equals(sequence) && successfulGesture; 125 } 126 127 // If the list of actual events matches the list of expected events, returns -1, otherwise 128 // the position of the mismatch. getMismatchPosition(List<Pattern> expected, List<String> actual)129 private static int getMismatchPosition(List<Pattern> expected, List<String> actual) { 130 for (int i = 0; i < expected.size(); ++i) { 131 if (i >= actual.size() 132 || !expected.get(i).matcher(actual.get(i)).find()) { 133 return i; 134 } 135 } 136 137 if (actual.size() > expected.size()) return expected.size(); 138 139 return -1; 140 } 141 formatSequenceWithMismatch( StringBuilder sb, String sequenceName, List<Pattern> expected, List<String> actualEvents, int mismatchPosition)142 private static void formatSequenceWithMismatch( 143 StringBuilder sb, 144 String sequenceName, 145 List<Pattern> expected, 146 List<String> actualEvents, 147 int mismatchPosition) { 148 sb.append("\n>> SEQUENCE " + sequenceName + " - " 149 + (mismatchPosition == -1 ? "MATCH" : "MISMATCH")); 150 sb.append("\n EXPECTED:"); 151 formatEventListWithMismatch(sb, expected, mismatchPosition); 152 sb.append("\n ACTUAL:"); 153 formatEventListWithMismatch(sb, actualEvents, mismatchPosition); 154 } 155 formatEventListWithMismatch(StringBuilder sb, List events, int position)156 private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) { 157 for (int i = 0; i < events.size(); ++i) { 158 sb.append("\n | "); 159 sb.append(i == position ? "---> " : " "); 160 sb.append(events.get(i).toString()); 161 } 162 if (position == events.size()) sb.append("\n | ---> (end)"); 163 } 164 165 private static class ListMap<T> extends HashMap<String, List<T>> { 166 add(String key, T value)167 void add(String key, T value) { 168 getNonNull(key).add(value); 169 } 170 getNonNull(String key)171 List<T> getNonNull(String key) { 172 List<T> list = get(key); 173 if (list == null) { 174 list = new ArrayList<>(); 175 put(key, list); 176 } 177 return list; 178 } 179 } 180 } 181