• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tradefed.result;
17 
18 import com.android.loganalysis.item.JavaCrashItem;
19 import com.android.loganalysis.item.LogcatItem;
20 import com.android.loganalysis.parser.LogcatParser;
21 import com.android.tradefed.device.ITestDevice;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
24 import com.android.tradefed.util.StreamUtil;
25 
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 
33 /**
34  * Special listener: on failures (instrumentation process crashing) it will attempt to extract from
35  * the logcat the crash and adds it to the failure message associated with the test.
36  */
37 public class LogcatCrashResultForwarder extends ResultForwarder {
38 
39     /** Special error message from the instrumentation when something goes wrong on device side. */
40     public static final String ERROR_MESSAGE = "Process crashed.";
41 
42     public static final int MAX_NUMBER_CRASH = 3;
43 
44     private Long mStartTime = null;
45     private Long mLastStartTime = null;
46     private ITestDevice mDevice;
47     private LogcatItem mLogcatItem = null;
48 
LogcatCrashResultForwarder(ITestDevice device, ITestInvocationListener... listeners)49     public LogcatCrashResultForwarder(ITestDevice device, ITestInvocationListener... listeners) {
50         super(listeners);
51         mDevice = device;
52     }
53 
54     @Override
testStarted(TestDescription test, long startTime)55     public void testStarted(TestDescription test, long startTime) {
56         mStartTime = startTime;
57         super.testStarted(test, startTime);
58     }
59 
60     @Override
testFailed(TestDescription test, String trace)61     public void testFailed(TestDescription test, String trace) {
62         // If the test case was detected as crashing the instrumentation, we had the crash to it.
63         trace = extractCrashAndAddToMessage(trace, mStartTime);
64         super.testFailed(test, trace);
65     }
66 
67     @Override
testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics)68     public void testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
69         super.testEnded(test, endTime, testMetrics);
70         mLastStartTime = mStartTime;
71         mStartTime = null;
72     }
73 
74     @Override
testRunFailed(String errorMessage)75     public void testRunFailed(String errorMessage) {
76         // Also add the failure to the run failure if the testFailed generated it.
77         // A Process crash would end the instrumentation, so a testRunFailed is probably going to
78         // be raised for the same reason.
79         if (mLogcatItem != null) {
80             super.testRunFailed(addJavaCrashToString(mLogcatItem, errorMessage));
81             mLogcatItem = null;
82             return;
83         }
84         errorMessage = extractCrashAndAddToMessage(errorMessage, mLastStartTime);
85         super.testRunFailed(errorMessage);
86         mLogcatItem = null;
87     }
88 
89     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)90     public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
91         super.testRunEnded(elapsedTime, runMetrics);
92         mLastStartTime = null;
93     }
94 
95     /** Attempt to extract the crash from the logcat if the test was seen as started. */
extractCrashAndAddToMessage(String errorMessage, Long startTime)96     private String extractCrashAndAddToMessage(String errorMessage, Long startTime) {
97         if (errorMessage.contains(ERROR_MESSAGE) && startTime != null) {
98             mLogcatItem = extractLogcat(mDevice, startTime);
99             errorMessage = addJavaCrashToString(mLogcatItem, errorMessage);
100         }
101         return errorMessage;
102     }
103 
104     /**
105      * Extract a formatted object from the logcat snippet.
106      *
107      * @param device The device from which to pull the logcat.
108      * @param startTime The beginning time of the last tests.
109      * @return A {@link LogcatItem} that contains the information inside the logcat.
110      */
extractLogcat(ITestDevice device, long startTime)111     private LogcatItem extractLogcat(ITestDevice device, long startTime) {
112         try (InputStreamSource logSource = device.getLogcatSince(startTime)) {
113             if (logSource.size() == 0L) {
114                 return null;
115             }
116             String message = StreamUtil.getStringFromStream(logSource.createInputStream());
117             LogcatParser parser = new LogcatParser();
118             List<String> lines = Arrays.asList(message.split("\n"));
119             return parser.parse(lines);
120         } catch (IOException e) {
121             CLog.e(e);
122         }
123         return null;
124     }
125 
126     /** Append the Java crash information to the failure message. */
addJavaCrashToString(LogcatItem item, String errorMsg)127     private String addJavaCrashToString(LogcatItem item, String errorMsg) {
128         if (item == null) {
129             return errorMsg;
130         }
131         List<String> crashes = dedupCrash(item.getJavaCrashes());
132         int displayed = Math.min(crashes.size(), MAX_NUMBER_CRASH);
133         for (int i = 0; i < displayed; i++) {
134             errorMsg = String.format("%s\nCrash Message:%s\n", errorMsg, crashes.get(i));
135         }
136         return errorMsg;
137     }
138 
139     /** Remove identical crash from the list of errors. */
dedupCrash(List<JavaCrashItem> origList)140     private List<String> dedupCrash(List<JavaCrashItem> origList) {
141         LinkedHashSet<String> dedupList = new LinkedHashSet<>();
142         for (JavaCrashItem item : origList) {
143             dedupList.add(String.format("%s\n%s", item.getMessage(), item.getStack()));
144         }
145         return new ArrayList<>(dedupList);
146     }
147 }
148