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("\n");
119                                         errorDetails.append(scanner.nextLine());
120                                     }
121                                     break;
122                                 }
123                             }
124                         } catch (InputMismatchException e) {
125                             Log.e(LOG_TAG, "Unable to parse system_app_anr using Scanner");
126                         }
127                     }
128                     errorDetails.append(truncate(dropboxSnippet, MAX_CRASH_SNIPPET_LINES));
129                     errorDetails.append("    ...\n");
130               }
131             }
132             timestamp = entry.getTimeMillis();
133         }
134         Assert.assertEquals(errorDetails.toString(), 0, crashCount);
135     }
136 
137     /**
138      * Truncate the text to at most the specified number of lines, and append a marker at the end
139      * when truncated
140      * @param text
141      * @param maxLines
142      * @return
143      */
truncate(String text, int maxLines)144     private static String truncate(String text, int maxLines) {
145         String[] lines = text.split("\\r?\\n");
146         StringBuilder ret = new StringBuilder();
147         for (int i = 0; i < maxLines && i < lines.length; i++) {
148             ret.append(lines[i]);
149             ret.append('\n');
150         }
151         if (lines.length > maxLines) {
152             ret.append("... ");
153             ret.append(lines.length - maxLines);
154             ret.append(" more lines truncated ...\n");
155         }
156         return ret.toString();
157     }
158 
159     /** Parse known failure file and add to the list of known failures */
populateKnownFailures()160     private void populateKnownFailures() {
161 
162         try {
163             BufferedReader reader =
164                     new BufferedReader(
165                             new InputStreamReader(
166                                     mContext.getAssets().open("bug_map"), StandardCharsets.UTF_8));
167             while (reader.ready()) {
168                 failures = Arrays.asList(reader.readLine());
169 
170                 for (String bug : failures) {
171                     Log.i(LOG_TAG, String.format("ParsedFile: %s", bug));
172 
173                     List<String> split_bug = Arrays.asList(bug.split(" "));
174 
175                     if (split_bug.size() != 3) {
176                         Log.e(
177                                 LOG_TAG,
178                                 String.format(
179                                         "bug_map file splits lines using space, please correct: %s",
180                                         bug));
181                     } else {
182                         String dropbox_label = split_bug.get(0);
183                         Pattern pattern = Pattern.compile(split_bug.get(1), Pattern.MULTILINE);
184                         String bug_id = split_bug.get(2);
185                         Log.i(
186                                 LOG_TAG,
187                                 String.format(
188                                         "Adding failure b/%s to test: %s", bug_id, dropbox_label));
189 
190                         mKnownFailures.addKnownFailure(dropbox_label, pattern, bug_id);
191                     }
192                 }
193             }
194         } catch (IOException | NullPointerException e) {
195             e.printStackTrace();
196         }
197     }
198 }
199