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.config.Option; 19 import com.android.tradefed.invoker.IInvocationContext; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 22 import com.android.tradefed.result.retry.ISupportGranularResults; 23 import com.android.tradefed.result.skipped.SkipReason; 24 import com.android.tradefed.util.FileUtil; 25 import com.android.tradefed.util.StreamUtil; 26 import com.android.tradefed.util.SubprocessEventHelper.BaseTestEventInfo; 27 import com.android.tradefed.util.SubprocessEventHelper.FailedTestEventInfo; 28 import com.android.tradefed.util.SubprocessEventHelper.InvocationEndedEventInfo; 29 import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo; 30 import com.android.tradefed.util.SubprocessEventHelper.InvocationStartedEventInfo; 31 import com.android.tradefed.util.SubprocessEventHelper.LogAssociationEventInfo; 32 import com.android.tradefed.util.SubprocessEventHelper.SkippedTestEventInfo; 33 import com.android.tradefed.util.SubprocessEventHelper.TestEndedEventInfo; 34 import com.android.tradefed.util.SubprocessEventHelper.TestLogEventInfo; 35 import com.android.tradefed.util.SubprocessEventHelper.TestModuleStartedEventInfo; 36 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo; 37 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo; 38 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo; 39 import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo; 40 import com.android.tradefed.util.SubprocessTestResultsParser; 41 import com.android.tradefed.util.proto.TfMetricProtoUtil; 42 43 import org.json.JSONObject; 44 45 import java.io.File; 46 import java.io.FileWriter; 47 import java.io.IOException; 48 import java.io.PrintWriter; 49 import java.net.Socket; 50 import java.util.HashMap; 51 import java.util.Map; 52 53 /** 54 * Implements {@link ITestInvocationListener} to be specified as a result_reporter and forward from 55 * the subprocess the results of tests, test runs, test invocations. 56 */ 57 public class SubprocessResultsReporter 58 implements ITestInvocationListener, 59 ILogSaverListener, 60 AutoCloseable, 61 ISupportGranularResults { 62 63 @Option( 64 name = "enable-granular-attempts", 65 description = "Whether to allow SubprocessResultsReporter receiving granular attempts.") 66 private boolean mGranularAttempts = true; 67 68 @Option(name = "subprocess-report-file", description = "the file where to log the events.") 69 private File mReportFile = null; 70 71 @Option(name = "subprocess-report-port", description = "the port where to connect to send the" 72 + "events.") 73 private Integer mReportPort = null; 74 75 @Option(name = "output-test-log", description = "Option to report test logs to parent process.") 76 private boolean mOutputTestlog = false; 77 78 private IInvocationContext mContext = null; 79 private Socket mReportSocket = null; 80 private Object mLock = new Object(); 81 private PrintWriter mPrintWriter = null; 82 83 private boolean mPrintWarning = true; 84 private boolean mCancelled = false; 85 86 @Override supportGranularResults()87 public boolean supportGranularResults() { 88 return mGranularAttempts; 89 } 90 91 /** {@inheritDoc} */ 92 @Override testAssumptionFailure(TestDescription testId, String trace)93 public void testAssumptionFailure(TestDescription testId, String trace) { 94 FailedTestEventInfo info = 95 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), trace); 96 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ASSUMPTION_FAILURE, info); 97 } 98 99 /** {@inheritDoc} */ 100 @Override testAssumptionFailure(TestDescription testId, FailureDescription failure)101 public void testAssumptionFailure(TestDescription testId, FailureDescription failure) { 102 FailedTestEventInfo info = 103 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), failure); 104 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ASSUMPTION_FAILURE, info); 105 } 106 107 /** {@inheritDoc} */ 108 @Override testSkipped(TestDescription testId, SkipReason reason)109 public void testSkipped(TestDescription testId, SkipReason reason) { 110 SkippedTestEventInfo info = 111 new SkippedTestEventInfo(testId.getClassName(), testId.getTestName(), reason); 112 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_SKIPPED, info); 113 } 114 115 /** {@inheritDoc} */ 116 @Override testEnded(TestDescription testId, HashMap<String, Metric> metrics)117 public void testEnded(TestDescription testId, HashMap<String, Metric> metrics) { 118 testEnded(testId, System.currentTimeMillis(), metrics); 119 } 120 121 /** {@inheritDoc} */ 122 @Override testEnded(TestDescription testId, long endTime, HashMap<String, Metric> metrics)123 public void testEnded(TestDescription testId, long endTime, HashMap<String, Metric> metrics) { 124 // TODO: transfer the proto metrics instead of string metrics 125 TestEndedEventInfo info = 126 new TestEndedEventInfo( 127 testId.getClassName(), 128 testId.getTestName(), 129 endTime, 130 TfMetricProtoUtil.compatibleConvert(metrics)); 131 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ENDED, info); 132 } 133 134 /** {@inheritDoc} */ 135 @Override testFailed(TestDescription testId, String reason)136 public void testFailed(TestDescription testId, String reason) { 137 FailedTestEventInfo info = 138 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), reason); 139 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_FAILED, info); 140 } 141 142 /** {@inheritDoc} */ 143 @Override testFailed(TestDescription testId, FailureDescription failure)144 public void testFailed(TestDescription testId, FailureDescription failure) { 145 FailedTestEventInfo info = 146 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), failure); 147 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_FAILED, info); 148 } 149 150 /** {@inheritDoc} */ 151 @Override testIgnored(TestDescription testId)152 public void testIgnored(TestDescription testId) { 153 BaseTestEventInfo info = new BaseTestEventInfo(testId.getClassName(), testId.getTestName()); 154 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_IGNORED, info); 155 } 156 157 /** {@inheritDoc} */ 158 @Override testRunEnded(long time, HashMap<String, Metric> runMetrics)159 public void testRunEnded(long time, HashMap<String, Metric> runMetrics) { 160 // TODO: Transfer the full proto instead of just Strings. 161 TestRunEndedEventInfo info = 162 new TestRunEndedEventInfo(time, TfMetricProtoUtil.compatibleConvert(runMetrics)); 163 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_ENDED, info); 164 } 165 166 /** {@inheritDoc} */ 167 @Override testRunFailed(String reason)168 public void testRunFailed(String reason) { 169 TestRunFailedEventInfo info = new TestRunFailedEventInfo(reason); 170 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_FAILED, info); 171 } 172 173 /** {@inheritDoc} */ 174 @Override testRunFailed(FailureDescription failure)175 public void testRunFailed(FailureDescription failure) { 176 TestRunFailedEventInfo info = new TestRunFailedEventInfo(failure); 177 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_FAILED, info); 178 } 179 180 /** {@inheritDoc} */ 181 @Override testRunStarted(String runName, int testCount)182 public void testRunStarted(String runName, int testCount) { 183 testRunStarted(runName, testCount, 0); 184 } 185 186 /** {@inheritDoc} */ 187 @Override testRunStarted(String runName, int testCount, int attemptNumber)188 public void testRunStarted(String runName, int testCount, int attemptNumber) { 189 testRunStarted(runName, testCount, attemptNumber, System.currentTimeMillis()); 190 } 191 192 /** {@inheritDoc} */ 193 @Override testRunStarted(String runName, int testCount, int attemptNumber, long startTime)194 public void testRunStarted(String runName, int testCount, int attemptNumber, long startTime) { 195 TestRunStartedEventInfo info = 196 new TestRunStartedEventInfo(runName, testCount, attemptNumber, startTime); 197 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_STARTED, info); 198 } 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override testRunStopped(long arg0)204 public void testRunStopped(long arg0) { 205 // ignore 206 } 207 208 /** {@inheritDoc} */ 209 @Override testStarted(TestDescription testId)210 public void testStarted(TestDescription testId) { 211 testStarted(testId, System.currentTimeMillis()); 212 } 213 214 /** {@inheritDoc} */ 215 @Override testStarted(TestDescription testId, long startTime)216 public void testStarted(TestDescription testId, long startTime) { 217 TestStartedEventInfo info = 218 new TestStartedEventInfo(testId.getClassName(), testId.getTestName(), startTime); 219 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_STARTED, info); 220 } 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override invocationStarted(IInvocationContext context)226 public void invocationStarted(IInvocationContext context) { 227 InvocationStartedEventInfo info = 228 new InvocationStartedEventInfo(context.getTestTag(), System.currentTimeMillis()); 229 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_STARTED, info); 230 // Save off the context so that we can parse it later during invocation ended. 231 mContext = context; 232 } 233 234 /** {@inheritDoc} */ 235 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)236 public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 237 if (!mOutputTestlog || (mReportPort == null && mReportFile == null)) { 238 return; 239 } 240 if (dataStream != null && dataStream.size() != 0) { 241 File tmpFile = null; 242 try { 243 // put 'subprocess' in front to identify the files. 244 tmpFile = 245 FileUtil.createTempFile( 246 "subprocess-" + dataName, "." + dataType.getFileExt()); 247 FileUtil.writeToFile(dataStream.createInputStream(), tmpFile); 248 TestLogEventInfo info = new TestLogEventInfo(dataName, dataType, tmpFile); 249 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_LOG, info); 250 } catch (IOException e) { 251 CLog.e(e); 252 FileUtil.deleteFile(tmpFile); 253 } 254 } 255 } 256 257 /** {@inheritDoc} */ 258 @Override logAssociation(String dataName, LogFile logFile)259 public void logAssociation(String dataName, LogFile logFile) { 260 LogAssociationEventInfo info = new LogAssociationEventInfo(dataName, logFile); 261 printEvent(SubprocessTestResultsParser.StatusKeys.LOG_ASSOCIATION, info); 262 } 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override invocationEnded(long elapsedTime)268 public void invocationEnded(long elapsedTime) { 269 if (mContext == null) { 270 return; 271 } 272 273 Map<String, String> metrics = mContext.getAttributes().getUniqueMap(); 274 InvocationEndedEventInfo eventEnd = new InvocationEndedEventInfo(metrics); 275 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_ENDED, eventEnd); 276 // Upon invocation ended, trigger the end of the socket when the process finishes 277 SocketFinisher thread = new SocketFinisher(); 278 Runtime.getRuntime().addShutdownHook(thread); 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override invocationFailed(Throwable cause)285 public void invocationFailed(Throwable cause) { 286 InvocationFailedEventInfo info = new InvocationFailedEventInfo(cause); 287 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_FAILED, info); 288 } 289 290 /** {@inheritDoc} */ 291 @Override invocationFailed(FailureDescription failure)292 public void invocationFailed(FailureDescription failure) { 293 InvocationFailedEventInfo info = new InvocationFailedEventInfo(failure); 294 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_FAILED, info); 295 } 296 297 /** {@inheritDoc} */ 298 @Override testModuleStarted(IInvocationContext moduleContext)299 public void testModuleStarted(IInvocationContext moduleContext) { 300 TestModuleStartedEventInfo info = new TestModuleStartedEventInfo(moduleContext); 301 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_MODULE_STARTED, info); 302 } 303 304 /** {@inheritDoc} */ 305 @Override testModuleEnded()306 public void testModuleEnded() { 307 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_MODULE_ENDED, new JSONObject()); 308 } 309 310 /** 311 * {@inheritDoc} 312 */ 313 @Override getSummary()314 public TestSummary getSummary() { 315 return null; 316 } 317 318 /** 319 * Helper to print the event key and then the json object. 320 */ printEvent(String key, Object event)321 public void printEvent(String key, Object event) { 322 if (mReportFile != null) { 323 if (mReportFile.canWrite()) { 324 try { 325 try (FileWriter fw = new FileWriter(mReportFile, true)) { 326 String eventLog = String.format("%s %s\n", key, event.toString()); 327 fw.append(eventLog); 328 fw.flush(); 329 } 330 } catch (IOException e) { 331 throw new RuntimeException(e); 332 } 333 } else { 334 throw new RuntimeException( 335 String.format("report file: %s is not writable", 336 mReportFile.getAbsolutePath())); 337 } 338 } 339 if(mReportPort != null) { 340 try { 341 if (mCancelled) { 342 return; 343 } 344 synchronized (mLock) { 345 if (mReportSocket == null) { 346 mReportSocket = new Socket("localhost", mReportPort.intValue()); 347 mPrintWriter = new PrintWriter(mReportSocket.getOutputStream(), true); 348 } 349 if (!mReportSocket.isConnected()) { 350 throw new RuntimeException("Reporter Socket is not connected"); 351 } 352 String eventLog = String.format("%s %s\n", key, event.toString()); 353 mPrintWriter.print(eventLog); 354 mPrintWriter.flush(); 355 } 356 } catch (IOException e) { 357 throw new RuntimeException(e); 358 } 359 } 360 if (mReportFile == null && mReportPort == null) { 361 if (mPrintWarning) { 362 // Only print the warning the first time. 363 mPrintWarning = false; 364 CLog.w("No report file or socket has been configured, skipping this reporter."); 365 } 366 } 367 } 368 369 /** {@inheritDoc} */ 370 @Override close()371 public void close() { 372 mCancelled = true; 373 synchronized (mLock) { 374 if (mPrintWriter != null) { 375 mPrintWriter.flush(); 376 } 377 StreamUtil.close(mPrintWriter); 378 mPrintWriter = null; 379 StreamUtil.close(mReportSocket); 380 mReportSocket = null; 381 } 382 } 383 384 /** Sets whether or not we should output the test logged or not. */ setOutputTestLog(boolean outputTestLog)385 public void setOutputTestLog(boolean outputTestLog) { 386 mOutputTestlog = outputTestLog; 387 } 388 389 /** Threads that help terminating the socket. */ 390 private class SocketFinisher extends Thread { 391 SocketFinisher()392 public SocketFinisher() { 393 super(); 394 setName("SubprocessResultsReporter-socket-finisher"); 395 } 396 397 @Override run()398 public void run() { 399 close(); 400 } 401 } 402 } 403