blob: 24990513158056b7f05961cf8dac56c38d945c4b [file] [log] [blame]
Julien Desprez093c3f62018-05-21 16:23:37 -07001/*
2 * Copyright (C) 2018 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 */
16package com.android.tradefed.result.proto;
17
18import com.android.tradefed.config.OptionClass;
19import com.android.tradefed.invoker.IInvocationContext;
20import com.android.tradefed.log.LogUtil.CLog;
21import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
22import com.android.tradefed.result.ILogSaver;
23import com.android.tradefed.result.ILogSaverListener;
24import com.android.tradefed.result.ITestInvocationListener;
25import com.android.tradefed.result.InputStreamSource;
26import com.android.tradefed.result.LogDataType;
27import com.android.tradefed.result.LogFile;
28import com.android.tradefed.result.TestDescription;
29import com.android.tradefed.testtype.suite.ModuleDefinition;
30
31import com.google.protobuf.Any;
32import com.google.protobuf.Timestamp;
33import com.tradefed.result.proto.LogFileProto.LogFileInfo;
34import com.tradefed.result.proto.TestRecordProto.ChildReference;
35import com.tradefed.result.proto.TestRecordProto.DebugInfo;
36import com.tradefed.result.proto.TestRecordProto.TestRecord;
37import com.tradefed.result.proto.TestRecordProto.TestStatus;
38
39import java.util.HashMap;
40import java.util.Map;
41import java.util.Stack;
42import java.util.UUID;
43
44/**
45 * Result reporter build a {@link TestRecord} protobuf with all the results inside. Should be
46 * extended to handle what to do with the final proto in {@link #processFinalProto(TestRecord)}.
47 */
48@OptionClass(alias = "proto-reporter")
49abstract class ProtoResultReporter implements ITestInvocationListener, ILogSaverListener {
50
51 private Stack<TestRecord.Builder> mLatestChild;
52 private TestRecord.Builder mInvocationRecordBuilder;
53 private long mInvocationStartTime;
54
55 /**
56 * Handling of the partial invocation test record proto after {@link
57 * #invocationStarted(IInvocationContext)} occurred.
58 *
59 * @param invocationStartRecord The partial proto populated after the invocationStart.
60 */
61 public void processStartInvocation(TestRecord invocationStartRecord) {}
62
63 /**
64 * Handling of the final proto with all results.
65 *
66 * @param finalRecord The finalized proto with all the invocation results.
67 */
68 public void processFinalProto(TestRecord finalRecord) {}
69
70 /**
71 * Handling of the partial module record proto after {@link
72 * #testModuleStarted(IInvocationContext)} occurred.
73 *
74 * @param moduleStartRecord The partial proto representing the module.
75 */
76 public void processTestModuleStarted(TestRecord moduleStartRecord) {}
77
78 /**
79 * Handling of the finalized module record proto after {@link #testModuleEnded()} occurred.
80 *
81 * @param moduleRecord The finalized proto representing the module.
82 */
83 public void processTestModuleEnd(TestRecord moduleRecord) {}
84
85 /**
86 * Handling of the partial test run record proto after {@link #testRunStarted(String, int)}
87 * occurred.
88 *
89 * @param runStartedRecord The partial proto representing the run.
90 */
91 public void processTestRunStarted(TestRecord runStartedRecord) {}
92
93 /**
94 * Handling of the finalized run record proto after {@link #testRunEnded(long, HashMap)}
95 * occurred.
96 *
97 * @param runRecord The finalized proto representing the run.
98 */
99 public void processTestRunEnded(TestRecord runRecord) {}
100
101 /**
102 * Handling of the partial test case record proto after {@link #testStarted(TestDescription,
103 * long)} occurred.
104 *
105 * @param testCaseStartedRecord The partial proto representing the test case.
106 */
107 public void processTestCaseStarted(TestRecord testCaseStartedRecord) {}
108
109 /**
110 * Handling of the finalized test case record proto after {@link #testEnded(TestDescription,
111 * long, HashMap)} occurred.
112 *
113 * @param testCaseRecord The finalized proto representing a test case.
114 */
115 public void processTestCaseEnded(TestRecord testCaseRecord) {}
116
117 // Invocation events
118
119 @Override
120 public final void invocationStarted(IInvocationContext context) {
121 mLatestChild = new Stack<>();
122 mInvocationRecordBuilder = TestRecord.newBuilder();
123 // Set invocation unique id
124 mInvocationRecordBuilder.setTestRecordId(UUID.randomUUID().toString());
125
126 // Populate start time of invocation
127 mInvocationStartTime = System.currentTimeMillis();
128 Timestamp startTime = createTimeStamp(mInvocationStartTime);
129 mInvocationRecordBuilder.setStartTime(startTime);
130
131 // Put the invocation record at the bottom of the stack
132 mLatestChild.add(mInvocationRecordBuilder);
133
134 // Send the invocation proto with the currently set information to indicate the beginning
135 // of the invocation.
136 TestRecord startInvocationProto = mInvocationRecordBuilder.build();
137 try {
138 processStartInvocation(startInvocationProto);
139 } catch (RuntimeException e) {
140 CLog.e("Failed to process invocation started:");
141 CLog.e(e);
142 }
143 }
144
145 @Override
146 public final void invocationEnded(long elapsedTime) {
147 // Populate end time of invocation
148 Timestamp endTime = createTimeStamp(mInvocationStartTime + elapsedTime);
149 mInvocationRecordBuilder.setEndTime(endTime);
150
151 // Finalize the protobuf handling: where to put the results.
152 TestRecord record = mInvocationRecordBuilder.build();
153 try {
154 processFinalProto(record);
155 } catch (RuntimeException e) {
156 CLog.e("Failed to process invocation ended:");
157 CLog.e(e);
158 }
159 }
160
161 // Module events (optional when there is no suite)
162
163 @Override
164 public final void testModuleStarted(IInvocationContext moduleContext) {
165 TestRecord.Builder moduleBuilder = TestRecord.newBuilder();
166 moduleBuilder.setParentTestRecordId(mInvocationRecordBuilder.getTestRecordId());
167 moduleBuilder.setTestRecordId(
168 moduleContext.getAttributes().get(ModuleDefinition.MODULE_ID).get(0));
169 moduleBuilder.setStartTime(createTimeStamp(System.currentTimeMillis()));
170 mLatestChild.add(moduleBuilder);
171 try {
172 processTestModuleStarted(moduleBuilder.build());
173 } catch (RuntimeException e) {
174 CLog.e("Failed to process invocation ended:");
175 CLog.e(e);
176 }
177 }
178
179 @Override
180 public final void testModuleEnded() {
181 TestRecord.Builder moduleBuilder = mLatestChild.pop();
182 moduleBuilder.setEndTime(createTimeStamp(System.currentTimeMillis()));
183 TestRecord.Builder parentBuilder = mLatestChild.peek();
184
185 // Finalize the module and track it in the child
186 TestRecord moduleRecord = moduleBuilder.build();
187 parentBuilder.addChildren(createChildReference(moduleRecord));
188 try {
189 processTestModuleEnd(moduleRecord);
190 } catch (RuntimeException e) {
191 CLog.e("Failed to process test module end:");
192 CLog.e(e);
193 }
194 }
195
196 // Run events
197
198 @Override
199 public final void testRunStarted(String runName, int testCount) {
200 TestRecord.Builder runBuilder = TestRecord.newBuilder();
201 TestRecord.Builder parent = mLatestChild.peek();
202 runBuilder.setParentTestRecordId(parent.getTestRecordId());
203 runBuilder.setTestRecordId(runName);
204 runBuilder.setNumExpectedChildren(testCount);
205 runBuilder.setStartTime(createTimeStamp(System.currentTimeMillis()));
206
207 mLatestChild.add(runBuilder);
208 try {
209 processTestRunStarted(runBuilder.build());
210 } catch (RuntimeException e) {
211 CLog.e("Failed to process invocation ended:");
212 CLog.e(e);
213 }
214 }
215
216 @Override
217 public final void testRunFailed(String errorMessage) {
218 TestRecord.Builder current = mLatestChild.peek();
219 DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
220 debugBuilder.setErrorMessage(errorMessage);
221 if (TestStatus.UNKNOWN.equals(current.getStatus())) {
222 current.setDebugInfo(debugBuilder.build());
223 } else {
224 // We are in a test case and we need the run parent.
225 TestRecord.Builder test = mLatestChild.pop();
226 TestRecord.Builder run = mLatestChild.peek();
227 run.setDebugInfo(debugBuilder.build());
228 // Re-add the test
229 mLatestChild.add(test);
230 }
231 }
232
233 @Override
234 public final void testRunEnded(long elapsedTimeMillis, HashMap<String, Metric> runMetrics) {
235 TestRecord.Builder runBuilder = mLatestChild.pop();
236 runBuilder.setEndTime(createTimeStamp(System.currentTimeMillis()));
237 runBuilder.putAllMetrics(runMetrics);
238 TestRecord.Builder parentBuilder = mLatestChild.peek();
239
240 // Finalize the run and track it in the child
241 TestRecord runRecord = runBuilder.build();
242 parentBuilder.addChildren(createChildReference(runRecord));
243 try {
244 processTestRunEnded(runRecord);
245 } catch (RuntimeException e) {
246 CLog.e("Failed to process test run end:");
247 CLog.e(e);
248 }
249 }
250
251 // test case events
252
253 @Override
254 public final void testStarted(TestDescription test, long startTime) {
255 TestRecord.Builder testBuilder = TestRecord.newBuilder();
256 TestRecord.Builder parent = mLatestChild.peek();
257 testBuilder.setParentTestRecordId(parent.getTestRecordId());
258 testBuilder.setTestRecordId(test.toString());
259 testBuilder.setStartTime(createTimeStamp(startTime));
260 testBuilder.setStatus(TestStatus.PASS);
261
262 mLatestChild.add(testBuilder);
263 try {
264 processTestCaseStarted(testBuilder.build());
265 } catch (RuntimeException e) {
266 CLog.e("Failed to process invocation ended:");
267 CLog.e(e);
268 }
269 }
270
271 @Override
272 public final void testEnded(
273 TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
274 TestRecord.Builder testBuilder = mLatestChild.pop();
275 testBuilder.setEndTime(createTimeStamp(endTime));
276 testBuilder.putAllMetrics(testMetrics);
277 TestRecord.Builder parentBuilder = mLatestChild.peek();
278
279 // Finalize the run and track it in the child
280 TestRecord testCaseRecord = testBuilder.build();
281 parentBuilder.addChildren(createChildReference(testCaseRecord));
282 try {
283 processTestCaseEnded(testCaseRecord);
284 } catch (RuntimeException e) {
285 CLog.e("Failed to process test case end:");
286 CLog.e(e);
287 }
288 }
289
290 @Override
291 public final void testFailed(TestDescription test, String trace) {
292 TestRecord.Builder testBuilder = mLatestChild.peek();
293
294 testBuilder.setStatus(TestStatus.FAIL);
295 DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
296 // FIXME: extract the error message from the trace
297 debugBuilder.setErrorMessage(trace);
298 debugBuilder.setTrace(trace);
299 testBuilder.setDebugInfo(debugBuilder.build());
300 }
301
302 @Override
303 public final void testIgnored(TestDescription test) {
304 TestRecord.Builder testBuilder = mLatestChild.peek();
305 testBuilder.setStatus(TestStatus.IGNORED);
306 }
307
308 @Override
309 public final void testAssumptionFailure(TestDescription test, String trace) {
310 TestRecord.Builder testBuilder = mLatestChild.peek();
311
312 testBuilder.setStatus(TestStatus.ASSUMPTION_FAILURE);
313 DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
314 // FIXME: extract the error message from the trace
315 debugBuilder.setErrorMessage(trace);
316 debugBuilder.setTrace(trace);
317 testBuilder.setDebugInfo(debugBuilder.build());
318 }
319
320 // log events
321
322 @Override
323 public final void testLogSaved(
324 String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) {
325 // Ignored
326 }
327
328 @Override
329 public final void setLogSaver(ILogSaver logSaver) {
330 // Ignored
331 }
332
333 @Override
334 public final void logAssociation(String dataName, LogFile logFile) {
335 TestRecord.Builder current = mLatestChild.peek();
336 Map<String, Any> fullmap = new HashMap<>();
337 fullmap.putAll(current.getArtifacts());
338 Any any = Any.pack(createFileProto(logFile));
339 fullmap.put(dataName, any);
340 current.putAllArtifacts(fullmap);
341 }
342
343 private ChildReference createChildReference(TestRecord record) {
344 return ChildReference.newBuilder().setInlineTestRecord(record).build();
345 }
346
347 /** Create and populate Timestamp as recommended in the javadoc of the Timestamp proto. */
348 private Timestamp createTimeStamp(long currentTimeMs) {
349 return Timestamp.newBuilder()
350 .setSeconds(currentTimeMs / 1000)
351 .setNanos((int) ((currentTimeMs % 1000) * 1000000))
352 .build();
353 }
354
355 private LogFileInfo createFileProto(LogFile logFile) {
356 LogFileInfo.Builder logFileBuilder = LogFileInfo.newBuilder();
357 logFileBuilder
358 .setPath(logFile.getPath())
359 .setUrl(logFile.getUrl())
360 .setIsText(logFile.isText())
361 .setIsCompressed(logFile.isCompressed())
362 .setSize(logFile.getSize());
363 return logFileBuilder.build();
364 }
365}