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 package com.android.devicehealthchecks; 17 18 import android.content.Context; 19 import android.os.DropBoxManager; 20 import android.os.Bundle; 21 import android.text.TextUtils; 22 import android.util.Log; 23 import androidx.test.platform.app.InstrumentationRegistry; 24 25 import org.junit.Assert; 26 import org.junit.Before; 27 28 import java.io.BufferedReader; 29 import java.io.InputStreamReader; 30 import java.io.IOException; 31 import java.nio.charset.StandardCharsets; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.InputMismatchException; 35 import java.util.List; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 import java.util.Scanner; 39 40 abstract class CrashCheckBase { 41 42 private static final int MAX_DROPBOX_READ = 4096; // read up to 4K from a dropbox entry 43 private static final int MAX_DROPBOX_READ_ANR = 40960; // read up to 40K for ANR 44 private static final int MAX_CRASH_SNIPPET_LINES = 40; 45 private static final String INCLUDE_KNOWN_FAILURES = "include_known_failures"; 46 private static final Pattern ANR_SUBJECT = Pattern.compile("Subject:"); 47 private static final String LOG_TAG = CrashCheckBase.class.getSimpleName(); 48 private Context mContext; 49 private KnownFailures mKnownFailures = new KnownFailures(); 50 /** whether known failures should be reported anyways, useful for bug investigation */ 51 private boolean mIncludeKnownFailures = false; 52 53 private List<String> failures = new ArrayList<>(); 54 55 @Before setUp()56 public void setUp() throws Exception { 57 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 58 Bundle bundle = InstrumentationRegistry.getArguments(); 59 mIncludeKnownFailures = TextUtils.equals("true", bundle.getString(INCLUDE_KNOWN_FAILURES)); 60 if (!mIncludeKnownFailures) { 61 Log.i(LOG_TAG, "Will ignore known failures, populating known failure list"); 62 populateKnownFailures(); 63 } 64 } 65 66 /** 67 * Check dropbox service for a particular label and assert if found 68 */ checkCrash(String label)69 protected void checkCrash(String label) { 70 DropBoxManager dropbox = (DropBoxManager) mContext 71 .getSystemService(Context.DROPBOX_SERVICE); 72 Assert.assertNotNull("Unable access the DropBoxManager service", dropbox); 73 74 long timestamp = 0; 75 DropBoxManager.Entry entry; 76 int crashCount = 0; 77 StringBuilder errorDetails = new StringBuilder("\nPlease triage this boot crash:\n"); 78 errorDetails.append("go/how-to-triage-devicehealthchecks\n"); 79 errorDetails.append("Error Details:\n"); 80 while (null != (entry = dropbox.getNextEntry(label, timestamp))) { 81 String dropboxSnippet; 82 try { 83 if (label.equals("system_app_anr")) { 84 dropboxSnippet = entry.getText(MAX_DROPBOX_READ_ANR); 85 } else { 86 dropboxSnippet = entry.getText(MAX_DROPBOX_READ); 87 } 88 } finally { 89 entry.close(); 90 } 91 if (dropboxSnippet == null) { 92 crashCount++; 93 94 errorDetails.append(label); 95 errorDetails.append(": (missing details)\n"); 96 } 97 else { 98 KnownFailureItem k = mKnownFailures.findMatchedKnownFailure(label, dropboxSnippet); 99 if (k != null && !mIncludeKnownFailures) { 100 Log.i( 101 LOG_TAG, 102 String.format( 103 "Ignored a known failure, type: %s, pattern: %s, bug: b/%s", 104 label, k.failurePattern, k.bugNumber)); 105 } else { 106 crashCount++; 107 errorDetails.append(label); 108 errorDetails.append(": "); 109 if (label.equals("system_app_anr")) { 110 // Read Snippet line by line until Subject is found 111 try (Scanner scanner = new Scanner(dropboxSnippet)) { 112 while (scanner.hasNextLine()) { 113 String line = scanner.nextLine(); 114 Matcher matcher = ANR_SUBJECT.matcher(line); 115 if (matcher.find()) { 116 errorDetails.append(line); 117 if (scanner.hasNextLine()) { 118 errorDetails.append(scanner.nextLine()); 119 } 120 break; 121 } 122 } 123 } catch (InputMismatchException e) { 124 Log.e(LOG_TAG, "Unable to parse system_app_anr using Scanner"); 125 } 126 } 127 errorDetails.append(truncate(dropboxSnippet, MAX_CRASH_SNIPPET_LINES)); 128 errorDetails.append(" ...\n"); 129 } 130 } 131 timestamp = entry.getTimeMillis(); 132 } 133 Assert.assertEquals(errorDetails.toString(), 0, crashCount); 134 } 135 136 /** 137 * Truncate the text to at most the specified number of lines, and append a marker at the end 138 * when truncated 139 * @param text 140 * @param maxLines 141 * @return 142 */ truncate(String text, int maxLines)143 private static String truncate(String text, int maxLines) { 144 String[] lines = text.split("\\r?\\n"); 145 StringBuilder ret = new StringBuilder(); 146 for (int i = 0; i < maxLines && i < lines.length; i++) { 147 ret.append(lines[i]); 148 ret.append('\n'); 149 } 150 if (lines.length > maxLines) { 151 ret.append("... "); 152 ret.append(lines.length - maxLines); 153 ret.append(" more lines truncated ...\n"); 154 } 155 return ret.toString(); 156 } 157 158 /** Parse known failure file and add to the list of known failures */ populateKnownFailures()159 private void populateKnownFailures() { 160 161 try { 162 BufferedReader reader = 163 new BufferedReader( 164 new InputStreamReader( 165 mContext.getAssets().open("bug_map"), StandardCharsets.UTF_8)); 166 while (reader.ready()) { 167 failures = Arrays.asList(reader.readLine()); 168 169 for (String bug : failures) { 170 Log.i(LOG_TAG, String.format("ParsedFile: %s", bug)); 171 172 List<String> split_bug = Arrays.asList(bug.split(" ")); 173 174 if (split_bug.size() != 3) { 175 Log.e( 176 LOG_TAG, 177 String.format( 178 "bug_map file splits lines using space, please correct: %s", 179 bug)); 180 } else { 181 String dropbox_label = split_bug.get(0); 182 Pattern pattern = Pattern.compile(split_bug.get(1), Pattern.MULTILINE); 183 String bug_id = split_bug.get(2); 184 Log.i( 185 LOG_TAG, 186 String.format( 187 "Adding failure b/%s to test: %s", bug_id, dropbox_label)); 188 189 mKnownFailures.addKnownFailure(dropbox_label, pattern, bug_id); 190 } 191 } 192 } 193 } catch (IOException | NullPointerException e) { 194 e.printStackTrace(); 195 } 196 } 197 } 198