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