1 /* 2 * Copyright (C) 2016 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.tradefed.build.IBuildInfo; 19 import com.android.tradefed.config.Option; 20 import com.android.tradefed.invoker.IInvocationContext; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 23 import com.android.tradefed.util.FileUtil; 24 import com.android.tradefed.util.StreamUtil; 25 import com.android.tradefed.util.SubprocessEventHelper.BaseTestEventInfo; 26 import com.android.tradefed.util.SubprocessEventHelper.FailedTestEventInfo; 27 import com.android.tradefed.util.SubprocessEventHelper.InvocationEndedEventInfo; 28 import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo; 29 import com.android.tradefed.util.SubprocessEventHelper.InvocationStartedEventInfo; 30 import com.android.tradefed.util.SubprocessEventHelper.LogAssociationEventInfo; 31 import com.android.tradefed.util.SubprocessEventHelper.TestEndedEventInfo; 32 import com.android.tradefed.util.SubprocessEventHelper.TestLogEventInfo; 33 import com.android.tradefed.util.SubprocessEventHelper.TestModuleStartedEventInfo; 34 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo; 35 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo; 36 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo; 37 import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo; 38 import com.android.tradefed.util.SubprocessTestResultsParser; 39 import com.android.tradefed.util.proto.TfMetricProtoUtil; 40 41 import org.json.JSONObject; 42 43 import java.io.File; 44 import java.io.FileWriter; 45 import java.io.IOException; 46 import java.io.PrintWriter; 47 import java.net.Socket; 48 import java.util.HashMap; 49 import java.util.List; 50 51 /** 52 * Implements {@link ITestInvocationListener} to be specified as a result_reporter and forward from 53 * the subprocess the results of tests, test runs, test invocations. 54 */ 55 public class SubprocessResultsReporter 56 implements ITestInvocationListener, ILogSaverListener, AutoCloseable { 57 58 @Option(name = "subprocess-report-file", description = "the file where to log the events.") 59 private File mReportFile = null; 60 61 @Option(name = "subprocess-report-port", description = "the port where to connect to send the" 62 + "events.") 63 private Integer mReportPort = null; 64 65 @Option(name = "output-test-log", description = "Option to report test logs to parent process.") 66 private boolean mOutputTestlog = false; 67 68 private IBuildInfo mPrimaryBuildInfo = null; 69 private Socket mReportSocket = null; 70 private PrintWriter mPrintWriter = null; 71 72 private boolean mPrintWarning = true; 73 74 /** {@inheritDoc} */ 75 @Override testAssumptionFailure(TestDescription testId, String trace)76 public void testAssumptionFailure(TestDescription testId, String trace) { 77 FailedTestEventInfo info = 78 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), trace); 79 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ASSUMPTION_FAILURE, info); 80 } 81 82 /** {@inheritDoc} */ 83 @Override testEnded(TestDescription testId, HashMap<String, Metric> metrics)84 public void testEnded(TestDescription testId, HashMap<String, Metric> metrics) { 85 testEnded(testId, System.currentTimeMillis(), metrics); 86 } 87 88 /** {@inheritDoc} */ 89 @Override testEnded(TestDescription testId, long endTime, HashMap<String, Metric> metrics)90 public void testEnded(TestDescription testId, long endTime, HashMap<String, Metric> metrics) { 91 // TODO: transfer the proto metrics instead of string metrics 92 TestEndedEventInfo info = 93 new TestEndedEventInfo( 94 testId.getClassName(), 95 testId.getTestName(), 96 endTime, 97 TfMetricProtoUtil.compatibleConvert(metrics)); 98 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ENDED, info); 99 } 100 101 /** {@inheritDoc} */ 102 @Override testFailed(TestDescription testId, String reason)103 public void testFailed(TestDescription testId, String reason) { 104 FailedTestEventInfo info = 105 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), reason); 106 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_FAILED, info); 107 } 108 109 /** {@inheritDoc} */ 110 @Override testIgnored(TestDescription testId)111 public void testIgnored(TestDescription testId) { 112 BaseTestEventInfo info = new BaseTestEventInfo(testId.getClassName(), testId.getTestName()); 113 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_IGNORED, info); 114 } 115 116 /** {@inheritDoc} */ 117 @Override testRunEnded(long time, HashMap<String, Metric> runMetrics)118 public void testRunEnded(long time, HashMap<String, Metric> runMetrics) { 119 // TODO: Transfer the full proto instead of just Strings. 120 TestRunEndedEventInfo info = 121 new TestRunEndedEventInfo(time, TfMetricProtoUtil.compatibleConvert(runMetrics)); 122 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_ENDED, info); 123 } 124 125 /** {@inheritDoc} */ 126 @Override testRunFailed(String reason)127 public void testRunFailed(String reason) { 128 TestRunFailedEventInfo info = new TestRunFailedEventInfo(reason); 129 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_FAILED, info); 130 } 131 132 /** {@inheritDoc} */ 133 @Override testRunStarted(String runName, int testCount)134 public void testRunStarted(String runName, int testCount) { 135 testRunStarted(runName, testCount, 0); 136 } 137 138 /** {@inheritDoc} */ 139 @Override testRunStarted(String runName, int testCount, int attemptNumber)140 public void testRunStarted(String runName, int testCount, int attemptNumber) { 141 TestRunStartedEventInfo info = 142 new TestRunStartedEventInfo(runName, testCount, attemptNumber); 143 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_STARTED, info); 144 } 145 146 /** 147 * {@inheritDoc} 148 */ 149 @Override testRunStopped(long arg0)150 public void testRunStopped(long arg0) { 151 // ignore 152 } 153 154 /** {@inheritDoc} */ 155 @Override testStarted(TestDescription testId)156 public void testStarted(TestDescription testId) { 157 testStarted(testId, System.currentTimeMillis()); 158 } 159 160 /** {@inheritDoc} */ 161 @Override testStarted(TestDescription testId, long startTime)162 public void testStarted(TestDescription testId, long startTime) { 163 TestStartedEventInfo info = 164 new TestStartedEventInfo(testId.getClassName(), testId.getTestName(), startTime); 165 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_STARTED, info); 166 } 167 168 /** 169 * {@inheritDoc} 170 */ 171 @Override invocationStarted(IInvocationContext context)172 public void invocationStarted(IInvocationContext context) { 173 InvocationStartedEventInfo info = 174 new InvocationStartedEventInfo(context.getTestTag(), System.currentTimeMillis()); 175 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_STARTED, info); 176 177 // Save off primary build info so that we can parse it later during invocation ended. 178 List<IBuildInfo> infos = context.getBuildInfos(); 179 mPrimaryBuildInfo = infos.isEmpty() ? null : infos.get(0); 180 } 181 182 /** {@inheritDoc} */ 183 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)184 public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 185 if (!mOutputTestlog || (mReportPort == null && mReportFile == null)) { 186 return; 187 } 188 if (dataStream != null && dataStream.size() != 0) { 189 File tmpFile = null; 190 try { 191 // put 'subprocess' in front to identify the files. 192 tmpFile = 193 FileUtil.createTempFile( 194 "subprocess-" + dataName, "." + dataType.getFileExt()); 195 FileUtil.writeToFile(dataStream.createInputStream(), tmpFile); 196 TestLogEventInfo info = new TestLogEventInfo(dataName, dataType, tmpFile); 197 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_LOG, info); 198 } catch (IOException e) { 199 CLog.e(e); 200 FileUtil.deleteFile(tmpFile); 201 } 202 } 203 } 204 205 /** {@inheritDoc} */ 206 @Override logAssociation(String dataName, LogFile logFile)207 public void logAssociation(String dataName, LogFile logFile) { 208 LogAssociationEventInfo info = 209 new LogAssociationEventInfo("subprocess-a-" + dataName, logFile); 210 printEvent(SubprocessTestResultsParser.StatusKeys.LOG_ASSOCIATION, info); 211 } 212 213 /** 214 * {@inheritDoc} 215 */ 216 @Override invocationEnded(long elapsedTime)217 public void invocationEnded(long elapsedTime) { 218 if (mPrimaryBuildInfo == null) { 219 return; 220 } 221 InvocationEndedEventInfo eventEnd = 222 new InvocationEndedEventInfo(mPrimaryBuildInfo.getBuildAttributes()); 223 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_ENDED, eventEnd); 224 } 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override invocationFailed(Throwable cause)230 public void invocationFailed(Throwable cause) { 231 InvocationFailedEventInfo info = new InvocationFailedEventInfo(cause); 232 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_FAILED, info); 233 } 234 235 /** {@inheritDoc} */ 236 @Override testModuleStarted(IInvocationContext moduleContext)237 public void testModuleStarted(IInvocationContext moduleContext) { 238 TestModuleStartedEventInfo info = new TestModuleStartedEventInfo(moduleContext); 239 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_MODULE_STARTED, info); 240 } 241 242 /** {@inheritDoc} */ 243 @Override testModuleEnded()244 public void testModuleEnded() { 245 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_MODULE_ENDED, new JSONObject()); 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 @Override getSummary()252 public TestSummary getSummary() { 253 return null; 254 } 255 256 /** 257 * Helper to print the event key and then the json object. 258 */ printEvent(String key, Object event)259 public void printEvent(String key, Object event) { 260 if (mReportFile != null) { 261 if (mReportFile.canWrite()) { 262 try { 263 try (FileWriter fw = new FileWriter(mReportFile, true)) { 264 String eventLog = String.format("%s %s\n", key, event.toString()); 265 fw.append(eventLog); 266 fw.flush(); 267 } 268 } catch (IOException e) { 269 throw new RuntimeException(e); 270 } 271 } else { 272 throw new RuntimeException( 273 String.format("report file: %s is not writable", 274 mReportFile.getAbsolutePath())); 275 } 276 } 277 if(mReportPort != null) { 278 try { 279 if (mReportSocket == null) { 280 mReportSocket = new Socket("localhost", mReportPort.intValue()); 281 mPrintWriter = new PrintWriter(mReportSocket.getOutputStream(), true); 282 } 283 if (!mReportSocket.isConnected()) { 284 throw new RuntimeException("Reporter Socket is not connected"); 285 } 286 String eventLog = String.format("%s %s\n", key, event.toString()); 287 mPrintWriter.print(eventLog); 288 mPrintWriter.flush(); 289 } catch (IOException e) { 290 throw new RuntimeException(e); 291 } 292 } 293 if (mReportFile == null && mReportPort == null) { 294 if (mPrintWarning) { 295 // Only print the warning the first time. 296 mPrintWarning = false; 297 CLog.w("No report file or socket has been configured, skipping this reporter."); 298 } 299 } 300 } 301 302 /** {@inheritDoc} */ 303 @Override close()304 public void close() { 305 StreamUtil.close(mReportSocket); 306 StreamUtil.close(mPrintWriter); 307 } 308 309 /** Sets whether or not we should output the test logged or not. */ setOutputTestLog(boolean outputTestLog)310 public void setOutputTestLog(boolean outputTestLog) { 311 mOutputTestlog = outputTestLog; 312 } 313 } 314