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