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.ddmlib.testrunner.TestIdentifier; 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.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.TestEndedEventInfo; 29 import com.android.tradefed.util.SubprocessEventHelper.TestLogEventInfo; 30 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo; 31 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo; 32 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo; 33 import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo; 34 import com.android.tradefed.util.SubprocessTestResultsParser; 35 36 import java.io.File; 37 import java.io.FileWriter; 38 import java.io.IOException; 39 import java.io.PrintWriter; 40 import java.net.Socket; 41 import java.util.Map; 42 43 /** 44 * Implements {@link ITestInvocationListener} to be specified as a result_reporter and forward 45 * from the subprocess the results of tests, test runs, test invocations. 46 */ 47 public class SubprocessResultsReporter implements ITestInvocationListener, AutoCloseable { 48 49 @Option(name = "subprocess-report-file", description = "the file where to log the events.") 50 private File mReportFile = null; 51 52 @Option(name = "subprocess-report-port", description = "the port where to connect to send the" 53 + "events.") 54 private Integer mReportPort = null; 55 56 @Option(name = "output-test-log", description = "Option to report test logs to parent process.") 57 private boolean mOutputTestlog = false; 58 59 private Socket mReportSocket = null; 60 private PrintWriter mPrintWriter = null; 61 62 private boolean mPrintWarning = true; 63 64 /** 65 * {@inheritDoc} 66 */ 67 @Override 68 public void testAssumptionFailure(TestIdentifier testId, String trace) { 69 FailedTestEventInfo info = 70 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), trace); 71 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ASSUMPTION_FAILURE, info); 72 } 73 74 /** 75 * {@inheritDoc} 76 */ 77 @Override 78 public void testEnded(TestIdentifier testId, Map<String, String> metrics) { 79 testEnded(testId, System.currentTimeMillis(), metrics); 80 } 81 82 /** {@inheritDoc} */ 83 @Override 84 public void testEnded(TestIdentifier testId, long endTime, Map<String, String> metrics) { 85 TestEndedEventInfo info = 86 new TestEndedEventInfo( 87 testId.getClassName(), testId.getTestName(), endTime, metrics); 88 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ENDED, info); 89 } 90 91 /** {@inheritDoc} */ 92 @Override 93 public void testFailed(TestIdentifier testId, String reason) { 94 FailedTestEventInfo info = 95 new FailedTestEventInfo(testId.getClassName(), testId.getTestName(), reason); 96 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_FAILED, info); 97 } 98 99 /** 100 * {@inheritDoc} 101 */ 102 @Override 103 public void testIgnored(TestIdentifier testId) { 104 BaseTestEventInfo info = new BaseTestEventInfo(testId.getClassName(), testId.getTestName()); 105 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_IGNORED, info); 106 } 107 108 /** 109 * {@inheritDoc} 110 */ 111 @Override 112 public void testRunEnded(long time, Map<String, String> runMetrics) { 113 TestRunEndedEventInfo info = new TestRunEndedEventInfo(time, runMetrics); 114 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_ENDED, info); 115 } 116 117 /** 118 * {@inheritDoc} 119 */ 120 @Override 121 public void testRunFailed(String reason) { 122 TestRunFailedEventInfo info = new TestRunFailedEventInfo(reason); 123 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_FAILED, info); 124 } 125 126 @Override 127 public void testRunStarted(String runName, int testCount) { 128 TestRunStartedEventInfo info = new TestRunStartedEventInfo(runName, testCount); 129 printEvent(SubprocessTestResultsParser.StatusKeys.TEST_RUN_STARTED, info); 130 } 131 132 /** 133 * {@inheritDoc} 134 */ 135 @Override 136 public void testRunStopped(long arg0) { 137 // ignore 138 } 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override 144 public void testStarted(TestIdentifier testId) { 145 testStarted(testId, System.currentTimeMillis()); 146 } 147 148 /** {@inheritDoc} */ 149 @Override 150 public void testStarted(TestIdentifier 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 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 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 /** 190 * {@inheritDoc} 191 */ 192 @Override 193 public void invocationEnded(long elapsedTime) { 194 // ignore 195 } 196 197 /** 198 * {@inheritDoc} 199 */ 200 @Override 201 public void invocationFailed(Throwable cause) { 202 InvocationFailedEventInfo info = new InvocationFailedEventInfo(cause); 203 printEvent(SubprocessTestResultsParser.StatusKeys.INVOCATION_FAILED, info); 204 } 205 206 /** 207 * {@inheritDoc} 208 */ 209 @Override 210 public TestSummary getSummary() { 211 return null; 212 } 213 214 /** 215 * Helper to print the event key and then the json object. 216 */ 217 public void printEvent(String key, Object event) { 218 if (mReportFile != null) { 219 if (mReportFile.canWrite()) { 220 try { 221 try (FileWriter fw = new FileWriter(mReportFile, true)) { 222 String eventLog = String.format("%s %s\n", key, event.toString()); 223 fw.append(eventLog); 224 fw.flush(); 225 } 226 } catch (IOException e) { 227 throw new RuntimeException(e); 228 } 229 } else { 230 throw new RuntimeException( 231 String.format("report file: %s is not writable", 232 mReportFile.getAbsolutePath())); 233 } 234 } 235 if(mReportPort != null) { 236 try { 237 if (mReportSocket == null) { 238 mReportSocket = new Socket("localhost", mReportPort.intValue()); 239 mPrintWriter = new PrintWriter(mReportSocket.getOutputStream(), true); 240 } 241 if (!mReportSocket.isConnected()) { 242 throw new RuntimeException("Reporter Socket is not connected"); 243 } 244 String eventLog = String.format("%s %s\n", key, event.toString()); 245 mPrintWriter.print(eventLog); 246 mPrintWriter.flush(); 247 } catch (IOException e) { 248 throw new RuntimeException(e); 249 } 250 } 251 if (mReportFile == null && mReportPort == null) { 252 if (mPrintWarning) { 253 // Only print the warning the first time. 254 mPrintWarning = false; 255 CLog.w("No report file or socket has been configured, skipping this reporter."); 256 } 257 } 258 } 259 260 /** {@inheritDoc} */ 261 @Override 262 public void close() { 263 StreamUtil.close(mReportSocket); 264 StreamUtil.close(mPrintWriter); 265 } 266 } 267