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.shared.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 if (rawEvents == null) return null; 61 62 final int expectedCount = mExpectedEvents.entrySet() 63 .stream().mapToInt(e -> e.getValue().size()).sum(); 64 if (rawEvents.size() >= expectedCount 65 || SystemClock.uptimeMillis() > startTime + waitForExpectedCountMs) { 66 break; 67 } 68 SystemClock.sleep(100); 69 } 70 71 finishNoWait(); 72 73 // Parse raw events into a map. 74 final ListMap<String> eventSequences = new ListMap<>(); 75 for (String rawEvent : rawEvents) { 76 final String[] split = rawEvent.split("/"); 77 eventSequences.add(split[0], split[1]); 78 } 79 return eventSequences; 80 } 81 finishNoWait()82 void finishNoWait() { 83 mLauncher.getTestInfo(TestProtocol.REQUEST_STOP_EVENT_LOGGING); 84 } 85 verify(long waitForExpectedCountMs)86 String verify(long waitForExpectedCountMs) { 87 final ListMap<String> actualEvents = finishSync(waitForExpectedCountMs); 88 if (actualEvents == null) return "null event sequences because launcher likely died"; 89 90 return lowLevelMismatchDiagnostics(actualEvents); 91 } 92 lowLevelMismatchDiagnostics(ListMap<String> actualEvents)93 private String lowLevelMismatchDiagnostics(ListMap<String> actualEvents) { 94 final StringBuilder sb = new StringBuilder(); 95 boolean hasMismatches = false; 96 for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) { 97 String sequence = expectedEvents.getKey(); 98 99 List<String> actual = new ArrayList<>(actualEvents.getNonNull(sequence)); 100 final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual); 101 hasMismatches = hasMismatches || mismatchPosition != -1; 102 formatSequenceWithMismatch( 103 sb, 104 sequence, 105 expectedEvents.getValue(), 106 actual, 107 mismatchPosition); 108 } 109 // Check for unexpected event sequences in the actual data. 110 for (String actualNamedSequence : actualEvents.keySet()) { 111 if (!mExpectedEvents.containsKey(actualNamedSequence)) { 112 hasMismatches = true; 113 formatSequenceWithMismatch( 114 sb, 115 actualNamedSequence, 116 new ArrayList<>(), 117 actualEvents.get(actualNamedSequence), 118 0); 119 } 120 } 121 122 return hasMismatches ? "Mismatching events: " + sb.toString() : null; 123 } 124 125 // If the list of actual events matches the list of expected events, returns -1, otherwise 126 // the position of the mismatch. getMismatchPosition(List<Pattern> expected, List<String> actual)127 private static int getMismatchPosition(List<Pattern> expected, List<String> actual) { 128 for (int i = 0; i < expected.size(); ++i) { 129 if (i >= actual.size() 130 || !expected.get(i).matcher(actual.get(i)).find()) { 131 return i; 132 } 133 } 134 135 if (actual.size() > expected.size()) return expected.size(); 136 137 return -1; 138 } 139 formatSequenceWithMismatch( StringBuilder sb, String sequenceName, List<Pattern> expected, List<String> actualEvents, int mismatchPosition)140 private static void formatSequenceWithMismatch( 141 StringBuilder sb, 142 String sequenceName, 143 List<Pattern> expected, 144 List<String> actualEvents, 145 int mismatchPosition) { 146 sb.append("\n>> SEQUENCE " + sequenceName + " - " 147 + (mismatchPosition == -1 ? "MATCH" : "MISMATCH")); 148 sb.append("\n EXPECTED:"); 149 formatEventListWithMismatch(sb, expected, mismatchPosition); 150 sb.append("\n ACTUAL:"); 151 formatEventListWithMismatch(sb, actualEvents, mismatchPosition); 152 } 153 formatEventListWithMismatch(StringBuilder sb, List events, int position)154 private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) { 155 for (int i = 0; i < events.size(); ++i) { 156 sb.append("\n | "); 157 sb.append(i == position ? "---> " : " "); 158 sb.append(events.get(i).toString()); 159 } 160 if (position == events.size()) sb.append("\n | ---> (end)"); 161 } 162 163 private static class ListMap<T> extends HashMap<String, List<T>> { 164 add(String key, T value)165 void add(String key, T value) { 166 getNonNull(key).add(value); 167 } 168 getNonNull(String key)169 List<T> getNonNull(String key) { 170 List<T> list = get(key); 171 if (list == null) { 172 list = new ArrayList<>(); 173 put(key, list); 174 } 175 return list; 176 } 177 } 178 } 179