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