1 /*
2  * Copyright (C) 2011 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 
17 package com.android.cts.tradefed.result;
18 
19 import com.android.ddmlib.testrunner.TestIdentifier;
20 import com.android.tradefed.build.IBuildInfo;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.result.ITestInvocationListener;
24 import com.android.tradefed.result.InputStreamSource;
25 import com.android.tradefed.result.LogDataType;
26 import com.android.tradefed.result.TestSummary;
27 
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.Map;
32 import java.util.concurrent.Callable;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 import java.util.concurrent.TimeUnit;
36 import java.util.zip.GZIPOutputStream;
37 
38 /**
39  * Class that sends a HTTP POST multipart/form-data request containing details
40  * about a test failure.
41  */
42 public class IssueReporter implements ITestInvocationListener {
43 
44     private static final int BUGREPORT_SIZE = 500 * 1024;
45 
46     private static final String PRODUCT_NAME_KEY = "buildName";
47     private static final String BUILD_TYPE_KEY = "build_type";
48     private static final String BUILD_ID_KEY = "buildID";
49 
50     @Option(name = "issue-server", description = "Server url to post test failures to.")
51     private String mServerUrl;
52 
53     private final ExecutorService mReporterService = Executors.newCachedThreadPool();
54 
55     private Issue mCurrentIssue;
56     private String mBuildId;
57     private String mBuildType;
58     private String mProductName;
59 
60     @Override
testFailed(TestIdentifier test, String trace)61     public void testFailed(TestIdentifier test, String trace) {
62         mCurrentIssue = new Issue();
63         mCurrentIssue.mTestName = test.toString();
64         mCurrentIssue.mStackTrace = trace;
65     }
66 
67     @Override
testAssumptionFailure(TestIdentifier test, String trace)68     public void testAssumptionFailure(TestIdentifier test, String trace) {
69         mCurrentIssue = new Issue();
70         mCurrentIssue.mTestName = test.toString();
71         mCurrentIssue.mStackTrace = trace;
72     }
73 
74     @Override
testIgnored(TestIdentifier test)75     public void testIgnored(TestIdentifier test) {
76         // TODO: ??
77     }
78 
79     @Override
testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)80     public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
81         if (dataName.startsWith("bug-")) {
82             try {
83                 setBugReport(dataStream);
84             } catch (IOException e) {
85                 CLog.e(e);
86             }
87         }
88     }
89 
90     /**
91      * Set the bug report for the current test failure. GZip it to save space.
92      * This is only called when the --bugreport option is enabled.
93      */
setBugReport(InputStreamSource dataStream)94     private void setBugReport(InputStreamSource dataStream) throws IOException {
95         if (mCurrentIssue != null) {
96             // Only one bug report can be stored at a time and they are gzipped to
97             // about 0.5 MB so there shoudn't be any memory leak bringing down CTS.
98             InputStream input = null;
99             try {
100                 input = dataStream.createInputStream();
101                 mCurrentIssue.mBugReport = getBytes(input, BUGREPORT_SIZE);
102             } finally {
103                 if (input != null) {
104                     input.close();
105                 }
106             }
107         } else {
108             CLog.e("setBugReport is getting called on an empty issue...");
109         }
110     }
111 
112     /**
113      * @param input that will be gzipped and returne as a byte array
114      * @param size of the output expected
115      * @return the byte array with the input's data
116      * @throws IOException
117      */
getBytes(InputStream input, int size)118     static byte[] getBytes(InputStream input, int size) throws IOException {
119         ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(size);
120         GZIPOutputStream gzipOutput = new GZIPOutputStream(byteOutput);
121         for (byte[] buffer = new byte[1024]; ; ) {
122             int numRead = input.read(buffer);
123             if (numRead < 0) {
124                 break;
125             }
126             gzipOutput.write(buffer, 0, numRead);
127         }
128         gzipOutput.close();
129         return byteOutput.toByteArray();
130     }
131 
132     @Override
testEnded(TestIdentifier test, Map<String, String> testMetrics)133     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
134         if (mCurrentIssue != null) {
135             mReporterService.submit(mCurrentIssue);
136             mCurrentIssue = null;
137         }
138     }
139 
140     @Override
testRunEnded(long elapsedTime, Map<String, String> runMetrics)141     public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
142         setDeviceMetrics(runMetrics);
143     }
144 
145     /** Set device information. Populated once when the device info app runs. */
setDeviceMetrics(Map<String, String> metrics)146     private void setDeviceMetrics(Map<String, String> metrics) {
147         if (metrics.containsKey(BUILD_ID_KEY)) {
148             mBuildId = metrics.get(BUILD_ID_KEY);
149         }
150         if (metrics.containsKey(BUILD_TYPE_KEY)) {
151             mBuildType = metrics.get(BUILD_TYPE_KEY);
152         }
153         if (metrics.containsKey(PRODUCT_NAME_KEY)) {
154             mProductName = metrics.get(PRODUCT_NAME_KEY);
155         }
156     }
157 
158     @Override
invocationEnded(long elapsedTime)159     public void invocationEnded(long elapsedTime) {
160         try {
161             mReporterService.shutdown();
162             if (!mReporterService.awaitTermination(1, TimeUnit.MINUTES)) {
163                 CLog.i("Some issues could not be reported...");
164             }
165         } catch (InterruptedException e) {
166             CLog.e(e);
167         }
168     }
169 
170     class Issue implements Callable<Void> {
171 
172         private String mTestName;
173         private String mStackTrace;
174         private byte[] mBugReport;
175 
176         @Override
call()177         public Void call() throws Exception {
178             if (isEmpty(mServerUrl)
179                     || isEmpty(mBuildId)
180                     || isEmpty(mBuildType)
181                     || isEmpty(mProductName)
182                     || isEmpty(mTestName)
183                     || isEmpty(mStackTrace)) {
184                 return null;
185             }
186 
187             new MultipartForm(mServerUrl)
188                     .addFormValue("productName", mProductName)
189                     .addFormValue("buildType", mBuildType)
190                     .addFormValue("buildId", mBuildId)
191                     .addFormValue("testName", mTestName)
192                     .addFormValue("stackTrace", mStackTrace)
193                     .addFormFile("bugReport", "bugreport.txt.gz", mBugReport)
194                     .submit();
195 
196             return null;
197         }
198 
isEmpty(String value)199         private boolean isEmpty(String value) {
200             return value == null || value.trim().isEmpty();
201         }
202     }
203 
204     @Override
invocationStarted(IBuildInfo buildInfo)205     public void invocationStarted(IBuildInfo buildInfo) {
206     }
207 
208     @Override
testRunStarted(String id, int numTests)209     public void testRunStarted(String id, int numTests) {
210     }
211 
212     @Override
testStarted(TestIdentifier test)213     public void testStarted(TestIdentifier test) {
214     }
215 
216     @Override
testRunFailed(String arg0)217     public void testRunFailed(String arg0) {
218     }
219 
220     @Override
testRunStopped(long elapsedTime)221     public void testRunStopped(long elapsedTime) {
222     }
223 
224     @Override
invocationFailed(Throwable cause)225     public void invocationFailed(Throwable cause) {
226     }
227 
228     @Override
getSummary()229     public TestSummary getSummary() {
230         return null;
231     }
232 }
233