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