1 /*
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  */
16 package com.android.tradefed.result.proto;
17 
18 import com.android.ddmlib.Log.LogLevel;
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.config.OptionClass;
21 import com.android.tradefed.error.HarnessException;
22 import com.android.tradefed.invoker.IInvocationContext;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
25 import com.android.tradefed.result.FailureDescription;
26 import com.android.tradefed.result.ILogSaverListener;
27 import com.android.tradefed.result.ITestInvocationListener;
28 import com.android.tradefed.result.LogFile;
29 import com.android.tradefed.result.TestDescription;
30 import com.android.tradefed.result.proto.LogFileProto.LogFileInfo;
31 import com.android.tradefed.result.proto.TestRecordProto.ChildReference;
32 import com.android.tradefed.result.proto.TestRecordProto.DebugInfo;
33 import com.android.tradefed.result.proto.TestRecordProto.DebugInfoContext;
34 import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
35 import com.android.tradefed.result.proto.TestRecordProto.TestStatus;
36 import com.android.tradefed.result.retry.ISupportGranularResults;
37 import com.android.tradefed.result.skipped.SkipReason;
38 import com.android.tradefed.testtype.suite.ModuleDefinition;
39 import com.android.tradefed.util.SerializationUtil;
40 import com.android.tradefed.util.StreamUtil;
41 
42 import com.google.common.base.Strings;
43 import com.google.protobuf.Any;
44 import com.google.protobuf.Timestamp;
45 
46 import java.io.IOException;
47 import java.util.HashMap;
48 import java.util.Map;
49 import java.util.Stack;
50 import java.util.UUID;
51 
52 /**
53  * Result reporter build a {@link TestRecord} protobuf with all the results inside. Should be
54  * extended to handle what to do with the final proto in {@link #processFinalProto(TestRecord)}.
55  */
56 @OptionClass(alias = "proto-reporter")
57 public abstract class ProtoResultReporter
58         implements ITestInvocationListener, ILogSaverListener, ISupportGranularResults {
59 
60     @Option(
61         name = "enable-granular-attempts",
62         description =
63                 "Whether or not to allow this reporter receiving granular attempts. Feature flag."
64     )
65     private boolean mReportGranularResults = true;
66 
67     private Stack<TestRecord.Builder> mLatestChild;
68     private TestRecord.Builder mInvocationRecordBuilder;
69     private long mInvocationStartTime;
70     private IInvocationContext mContext;
71 
72     private FailureDescription mInvocationFailureDescription = null;
73     private SkipReason mInvocationSkipReason = null;
74     /** Whether or not a testModuleStart had currently been called. */
75     private boolean mModuleInProgress = false;
76 
77     private IInvocationContext mModuleContext;
78     /** Track whether or not invocation ended has been reported. */
79     private boolean mInvocationEnded = false;
80     /** Whether or not to inline test record of child events */
81     private boolean mInlineRecordOfChildren = true;
82 
83     @Override
supportGranularResults()84     public boolean supportGranularResults() {
85         return mReportGranularResults;
86     }
87 
setGranularResults(boolean granularResults)88     public void setGranularResults(boolean granularResults) {
89         mReportGranularResults = granularResults;
90     }
91 
setInlineRecordOfChildren(boolean inline)92     public void setInlineRecordOfChildren(boolean inline) {
93         mInlineRecordOfChildren = inline;
94     }
95 
96     /**
97      * Handling of the partial invocation test record proto after {@link
98      * #invocationStarted(IInvocationContext)} occurred.
99      *
100      * @param invocationStartRecord The partial proto populated after the invocationStart.
101      * @param invocationContext The invocation {@link IInvocationContext}.
102      */
processStartInvocation( TestRecord invocationStartRecord, IInvocationContext invocationContext)103     public void processStartInvocation(
104             TestRecord invocationStartRecord, IInvocationContext invocationContext) {}
105 
106     /**
107      * Handling of the final proto with all results.
108      *
109      * @param finalRecord The finalized proto with all the invocation results.
110      */
processFinalProto(TestRecord finalRecord)111     public void processFinalProto(TestRecord finalRecord) {}
112 
113     /**
114      * Handling of the partial module record proto after {@link
115      * #testModuleStarted(IInvocationContext)} occurred.
116      *
117      * @param moduleStartRecord The partial proto representing the module.
118      */
processTestModuleStarted(TestRecord moduleStartRecord)119     public void processTestModuleStarted(TestRecord moduleStartRecord) {}
120 
121     /**
122      * Handling of the finalized module record proto after {@link #testModuleEnded()} occurred.
123      *
124      * @param moduleRecord The finalized proto representing the module.
125      */
processTestModuleEnd(TestRecord moduleRecord)126     public void processTestModuleEnd(TestRecord moduleRecord) {}
127 
128     /**
129      * Handling of the partial test run record proto after {@link #testRunStarted(String, int)}
130      * occurred.
131      *
132      * @param runStartedRecord The partial proto representing the run.
133      */
processTestRunStarted(TestRecord runStartedRecord)134     public void processTestRunStarted(TestRecord runStartedRecord) {}
135 
136     /**
137      * Handling of the finalized run record proto after {@link #testRunEnded(long, HashMap)}
138      * occurred.
139      *
140      * @param runRecord The finalized proto representing the run.
141      * @param moduleInProgress whether or not a module is in progress.
142      */
processTestRunEnded(TestRecord runRecord, boolean moduleInProgress)143     public void processTestRunEnded(TestRecord runRecord, boolean moduleInProgress) {}
144 
145     /**
146      * Handling of the partial test case record proto after {@link #testStarted(TestDescription,
147      * long)} occurred.
148      *
149      * @param testCaseStartedRecord The partial proto representing the test case.
150      */
processTestCaseStarted(TestRecord testCaseStartedRecord)151     public void processTestCaseStarted(TestRecord testCaseStartedRecord) {}
152 
153     /**
154      * Handling of the finalized test case record proto after {@link #testEnded(TestDescription,
155      * long, HashMap)} occurred.
156      *
157      * @param testCaseRecord The finalized proto representing a test case.
158      */
processTestCaseEnded(TestRecord testCaseRecord)159     public void processTestCaseEnded(TestRecord testCaseRecord) {}
160 
161     /**
162      * Use the invocation record to send one by one all the final logs of the invocation.
163      *
164      * @param invocationLogs The finalized proto representing the invocation.
165      */
processFinalInvocationLogs(TestRecord invocationLogs)166     public void processFinalInvocationLogs(TestRecord invocationLogs) {}
167 
168     // Invocation events
169 
170     @Override
invocationStarted(IInvocationContext context)171     public final void invocationStarted(IInvocationContext context) {
172         mLatestChild = new Stack<>();
173         mInvocationRecordBuilder = TestRecord.newBuilder();
174         // Set invocation unique id
175         mInvocationRecordBuilder.setTestRecordId(UUID.randomUUID().toString());
176 
177         // Populate start time of invocation
178         mInvocationStartTime = System.currentTimeMillis();
179         Timestamp startTime = createTimeStamp(mInvocationStartTime);
180         mInvocationRecordBuilder.setStartTime(startTime);
181         mInvocationRecordBuilder.setDescription(Any.pack(context.toProto()));
182 
183         mContext = context;
184 
185         // Put the invocation record at the bottom of the stack
186         mLatestChild.add(mInvocationRecordBuilder);
187 
188         // Send the invocation proto with the currently set information to indicate the beginning
189         // of the invocation.
190         TestRecord startInvocationProto = mInvocationRecordBuilder.build();
191         try {
192             processStartInvocation(startInvocationProto, context);
193         } catch (RuntimeException e) {
194             CLog.e("Failed to process invocation started:");
195             CLog.e(e);
196         }
197     }
198 
199     @Override
invocationFailed(Throwable cause)200     public void invocationFailed(Throwable cause) {
201         // Translate the exception into a FailureDescription
202         mInvocationFailureDescription =
203                 FailureDescription.create(cause.getMessage()).setCause(cause);
204         if (cause instanceof HarnessException) {
205             mInvocationFailureDescription.setErrorIdentifier(
206                     ((HarnessException) cause).getErrorId());
207             mInvocationFailureDescription.setOrigin(((HarnessException) cause).getOrigin());
208         }
209     }
210 
211     @Override
invocationFailed(FailureDescription failure)212     public void invocationFailed(FailureDescription failure) {
213         mInvocationFailureDescription = failure;
214     }
215 
216     @Override
invocationSkipped(SkipReason reason)217     public void invocationSkipped(SkipReason reason) {
218         mInvocationSkipReason = reason;
219     }
220 
221     @Override
invocationEnded(long elapsedTime)222     public final void invocationEnded(long elapsedTime) {
223         if (mModuleInProgress) {
224             // If we had a module in progress, and a new module start occurs, complete the call
225             testModuleEnded();
226         }
227         // Populate end time of invocation
228         Timestamp endTime = createTimeStamp(mInvocationStartTime + elapsedTime);
229         mInvocationRecordBuilder.setEndTime(endTime);
230         // Update the context in case it changed
231         mInvocationRecordBuilder.setDescription(Any.pack(mContext.toProto()));
232 
233         DebugInfo invocationFailure = handleInvocationFailure();
234         if (invocationFailure != null) {
235             mInvocationRecordBuilder.setDebugInfo(invocationFailure);
236             mInvocationRecordBuilder.setStatus(TestStatus.FAIL);
237         } else {
238             mInvocationRecordBuilder.setStatus(TestStatus.PASS);
239         }
240         if (mInvocationSkipReason != null) {
241             mInvocationRecordBuilder.setSkipReason(convertSkipReason(mInvocationSkipReason));
242         }
243 
244         // Finalize the protobuf handling: where to put the results.
245         TestRecord record = mInvocationRecordBuilder.build();
246         try {
247             processFinalProto(record);
248         } catch (RuntimeException e) {
249             CLog.e("Failed to process invocation ended:");
250             CLog.e(e);
251         }
252         mInvocationEnded = true;
253     }
254 
255     // Module events (optional when there is no suite)
256 
257     @Override
testModuleStarted(IInvocationContext moduleContext)258     public final void testModuleStarted(IInvocationContext moduleContext) {
259         if (mModuleInProgress) {
260             // If we had a module in progress, and a new module start occurs, complete the call
261             testModuleEnded();
262         }
263         TestRecord.Builder moduleBuilder = TestRecord.newBuilder();
264         moduleBuilder.setParentTestRecordId(mInvocationRecordBuilder.getTestRecordId());
265         moduleBuilder.setTestRecordId(
266                 moduleContext.getAttributes().get(ModuleDefinition.MODULE_ID).get(0));
267         moduleBuilder.setStartTime(createTimeStamp(System.currentTimeMillis()));
268         moduleBuilder.setDescription(Any.pack(moduleContext.toProto()));
269         mLatestChild.add(moduleBuilder);
270         mModuleInProgress = true;
271         mModuleContext = moduleContext;
272         try {
273             processTestModuleStarted(moduleBuilder.build());
274         } catch (RuntimeException e) {
275             CLog.e("Failed to process invocation ended:");
276             CLog.e(e);
277         }
278     }
279 
280     @Override
testModuleEnded()281     public final void testModuleEnded() {
282         TestRecord.Builder moduleBuilder = mLatestChild.pop();
283         mModuleInProgress = false;
284 
285         moduleBuilder.setEndTime(createTimeStamp(System.currentTimeMillis()));
286         // Module do not have a fail status
287         moduleBuilder.setStatus(TestStatus.PASS);
288         // Repack module for updated properties
289         moduleBuilder.setDescription(Any.pack(mModuleContext.toProto()));
290         TestRecord.Builder parentBuilder = mLatestChild.peek();
291         mModuleContext = null;
292         // Finalize the module and track it in the child
293         TestRecord moduleRecord = moduleBuilder.build();
294         ChildReference moduleReference = createModuleChildReference(moduleRecord);
295         if (moduleReference != null) {
296             parentBuilder.addChildren(moduleReference);
297         }
298         try {
299             processTestModuleEnd(moduleRecord);
300         } catch (RuntimeException e) {
301             CLog.e("Failed to process test module end:");
302             CLog.e(e);
303         }
304     }
305 
306     // Run events
307 
308     @Override
testRunStarted(String runName, int testCount)309     public final void testRunStarted(String runName, int testCount) {
310         testRunStarted(runName, testCount, 0);
311     }
312 
313     @Override
testRunStarted(String runName, int testCount, int attemptNumber)314     public void testRunStarted(String runName, int testCount, int attemptNumber) {
315         testRunStarted(runName, testCount, attemptNumber, System.currentTimeMillis());
316     }
317 
318     @Override
testRunStarted(String runName, int testCount, int attemptNumber, long startTime)319     public void testRunStarted(String runName, int testCount, int attemptNumber, long startTime) {
320         TestRecord.Builder runBuilder = TestRecord.newBuilder();
321         TestRecord.Builder parent = mLatestChild.peek();
322         runBuilder.setParentTestRecordId(parent.getTestRecordId());
323         runBuilder.setTestRecordId(runName);
324         runBuilder.setNumExpectedChildren(testCount);
325         runBuilder.setStartTime(createTimeStamp(startTime));
326         runBuilder.setAttemptId(attemptNumber);
327 
328         mLatestChild.add(runBuilder);
329         try {
330             processTestRunStarted(runBuilder.build());
331         } catch (RuntimeException e) {
332             CLog.e("Failed to process invocation ended:");
333             CLog.e(e);
334         }
335     }
336 
337     @Override
testRunFailed(String errorMessage)338     public final void testRunFailed(String errorMessage) {
339         TestRecord.Builder current = mLatestChild.peek();
340         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
341         debugBuilder.setErrorMessage(errorMessage);
342         if (TestStatus.UNKNOWN.equals(current.getStatus())) {
343             current.setDebugInfo(debugBuilder.build());
344             current.setStatus(TestStatus.FAIL);
345         } else {
346             // We are in a test case and we need the run parent.
347             TestRecord.Builder test = mLatestChild.pop();
348             TestRecord.Builder run = mLatestChild.peek();
349             run.setDebugInfo(debugBuilder.build());
350             run.setStatus(TestStatus.FAIL);
351             // Re-add the test
352             mLatestChild.add(test);
353         }
354     }
355 
356     @Override
testRunFailed(FailureDescription failure)357     public final void testRunFailed(FailureDescription failure) {
358         TestRecord.Builder current = mLatestChild.peek();
359         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
360         debugBuilder.setErrorMessage(failure.toString());
361         if (failure.getFailureStatus() != null) {
362             debugBuilder.setFailureStatus(failure.getFailureStatus());
363         }
364         DebugInfoContext.Builder debugContext = DebugInfoContext.newBuilder();
365         if (failure.getActionInProgress() != null) {
366             debugContext.setActionInProgress(failure.getActionInProgress().toString());
367         }
368         if (!Strings.isNullOrEmpty(failure.getDebugHelpMessage())) {
369             debugContext.setDebugHelpMessage(failure.getDebugHelpMessage());
370         }
371         if (!Strings.isNullOrEmpty(failure.getOrigin())) {
372             debugContext.setOrigin(failure.getOrigin());
373         }
374         if (failure.getErrorIdentifier() != null) {
375             debugContext.setErrorName(failure.getErrorIdentifier().name());
376             debugContext.setErrorCode(failure.getErrorIdentifier().code());
377             // Error Identifier dictates the status if it exists
378             debugBuilder.setFailureStatus(failure.getErrorIdentifier().status());
379         }
380         debugBuilder.setDebugInfoContext(debugContext.build());
381 
382         if (TestStatus.UNKNOWN.equals(current.getStatus())) {
383             current.setDebugInfo(debugBuilder.build());
384             current.setStatus(TestStatus.FAIL);
385         } else {
386             // We are in a test case and we need the run parent.
387             TestRecord.Builder test = mLatestChild.pop();
388             TestRecord.Builder run = mLatestChild.peek();
389             run.setDebugInfo(debugBuilder.build());
390             run.setStatus(TestStatus.FAIL);
391             // Re-add the test
392             mLatestChild.add(test);
393         }
394     }
395 
396     @Override
testRunEnded(long elapsedTimeMillis, HashMap<String, Metric> runMetrics)397     public final void testRunEnded(long elapsedTimeMillis, HashMap<String, Metric> runMetrics) {
398         TestRecord.Builder runBuilder = mLatestChild.pop();
399         long startTime = timeStampToMillis(runBuilder.getStartTime());
400         runBuilder.setEndTime(createTimeStamp(startTime + elapsedTimeMillis));
401         runBuilder.putAllMetrics(runMetrics);
402         TestRecord.Builder parentBuilder = mLatestChild.peek();
403 
404         if (!runBuilder.hasDebugInfo()) {
405             runBuilder.setStatus(TestStatus.PASS);
406         }
407 
408         // Finalize the run and track it in the child
409         TestRecord runRecord = runBuilder.build();
410         parentBuilder.addChildren(createChildReference(runRecord));
411         try {
412             processTestRunEnded(runRecord, mModuleInProgress);
413         } catch (RuntimeException e) {
414             CLog.e("Failed to process test run end:");
415             CLog.e(e);
416         }
417     }
418 
419     // test case events
420 
421     @Override
testStarted(TestDescription test)422     public final void testStarted(TestDescription test) {
423         testStarted(test, System.currentTimeMillis());
424     }
425 
426     @Override
testStarted(TestDescription test, long startTime)427     public final void testStarted(TestDescription test, long startTime) {
428         TestRecord.Builder testBuilder = TestRecord.newBuilder();
429         TestRecord.Builder parent = mLatestChild.peek();
430         testBuilder.setParentTestRecordId(parent.getTestRecordId());
431         testBuilder.setTestRecordId(test.toString());
432         testBuilder.setStartTime(createTimeStamp(startTime));
433         testBuilder.setStatus(TestStatus.PASS);
434 
435         mLatestChild.add(testBuilder);
436         try {
437             processTestCaseStarted(testBuilder.build());
438         } catch (RuntimeException e) {
439             CLog.e("Failed to process invocation ended:");
440             CLog.e(e);
441         }
442     }
443 
444     @Override
testEnded(TestDescription test, HashMap<String, Metric> testMetrics)445     public void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) {
446         testEnded(test, System.currentTimeMillis(), testMetrics);
447     }
448 
449     @Override
testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)450     public final void testEnded(
451             TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
452         TestRecord.Builder testBuilder = mLatestChild.pop();
453         testBuilder.setEndTime(createTimeStamp(endTime));
454         testBuilder.putAllMetrics(testMetrics);
455         TestRecord.Builder parentBuilder = mLatestChild.peek();
456 
457         // Finalize the run and track it in the child
458         TestRecord testCaseRecord = testBuilder.build();
459         parentBuilder.addChildren(createChildReference(testCaseRecord));
460         try {
461             processTestCaseEnded(testCaseRecord);
462         } catch (RuntimeException e) {
463             CLog.e("Failed to process test case end:");
464             CLog.e(e);
465         }
466     }
467 
468     @Override
testSkipped(TestDescription test, SkipReason reason)469     public final void testSkipped(TestDescription test, SkipReason reason) {
470         TestRecord.Builder testBuilder = mLatestChild.peek();
471 
472         testBuilder.setSkipReason(convertSkipReason(reason));
473     }
474 
475     @Override
testFailed(TestDescription test, String trace)476     public final void testFailed(TestDescription test, String trace) {
477         TestRecord.Builder testBuilder = mLatestChild.peek();
478 
479         testBuilder.setStatus(TestStatus.FAIL);
480         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
481         // FIXME: extract the error message from the trace
482         debugBuilder.setErrorMessage(trace);
483         debugBuilder.setTrace(trace);
484         testBuilder.setDebugInfo(debugBuilder.build());
485     }
486 
487     @Override
testFailed(TestDescription test, FailureDescription failure)488     public final void testFailed(TestDescription test, FailureDescription failure) {
489         TestRecord.Builder testBuilder = mLatestChild.peek();
490 
491         testBuilder.setStatus(TestStatus.FAIL);
492         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
493         // FIXME: extract the error message from the trace
494         debugBuilder.setErrorMessage(failure.toString());
495         debugBuilder.setTrace(failure.toString());
496         if (failure.getFailureStatus() != null) {
497             debugBuilder.setFailureStatus(failure.getFailureStatus());
498         }
499         DebugInfoContext.Builder debugContext = DebugInfoContext.newBuilder();
500         if (failure.getActionInProgress() != null) {
501             debugContext.setActionInProgress(failure.getActionInProgress().toString());
502         }
503         if (!Strings.isNullOrEmpty(failure.getDebugHelpMessage())) {
504             debugContext.setDebugHelpMessage(failure.getDebugHelpMessage());
505         }
506         debugBuilder.setDebugInfoContext(debugContext.build());
507 
508         testBuilder.setDebugInfo(debugBuilder.build());
509     }
510 
511     @Override
testIgnored(TestDescription test)512     public final void testIgnored(TestDescription test) {
513         TestRecord.Builder testBuilder = mLatestChild.peek();
514         testBuilder.setStatus(TestStatus.IGNORED);
515     }
516 
517     @Override
testAssumptionFailure(TestDescription test, String trace)518     public final void testAssumptionFailure(TestDescription test, String trace) {
519         TestRecord.Builder testBuilder = mLatestChild.peek();
520 
521         testBuilder.setStatus(TestStatus.ASSUMPTION_FAILURE);
522         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
523         // FIXME: extract the error message from the trace
524         debugBuilder.setErrorMessage(trace);
525         debugBuilder.setTrace(trace);
526         testBuilder.setDebugInfo(debugBuilder.build());
527     }
528 
529     @Override
testAssumptionFailure(TestDescription test, FailureDescription failure)530     public final void testAssumptionFailure(TestDescription test, FailureDescription failure) {
531         TestRecord.Builder testBuilder = mLatestChild.peek();
532 
533         testBuilder.setStatus(TestStatus.ASSUMPTION_FAILURE);
534         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
535         // FIXME: extract the error message from the trace
536         debugBuilder.setErrorMessage(failure.toString());
537         debugBuilder.setTrace(failure.toString());
538         if (failure.getFailureStatus() != null) {
539             debugBuilder.setFailureStatus(failure.getFailureStatus());
540         }
541         testBuilder.setDebugInfo(debugBuilder.build());
542     }
543 
544     // log events
545 
546     @Override
logAssociation(String dataName, LogFile logFile)547     public final void logAssociation(String dataName, LogFile logFile) {
548         if (mLatestChild == null || mLatestChild.isEmpty()) {
549             CLog.w("Skip logging '%s' logAssociation called out of sequence.", dataName);
550             return;
551         }
552         TestRecord.Builder current = mLatestChild.peek();
553         if (mInvocationEnded) {
554             // For after invocation ended events, report artifacts one by one.
555             current.clearArtifacts();
556             current.clearChildren();
557         }
558         Map<String, Any> fullmap = new HashMap<>();
559         fullmap.putAll(current.getArtifactsMap());
560         Any any = Any.pack(createFileProto(logFile));
561         // Ensure keys are made unique to avoid colliding in the proto representation.
562         int count = 0;
563         String key;
564         do {
565             key = String.format("%s%s", dataName, count == 0 ? "" : count);
566             count++;
567         } while (fullmap.containsKey(key));
568         fullmap.put(key, any);
569         current.putAllArtifacts(fullmap);
570         if (mInvocationEnded) {
571             CLog.logAndDisplay(LogLevel.DEBUG, "process final logs: %s", logFile.getPath());
572             processFinalInvocationLogs(current.build());
573         }
574     }
575 
576     /**
577      * Creates a child reference for a module.
578      */
createModuleChildReference(TestRecord record)579     protected ChildReference createModuleChildReference(TestRecord record) {
580         return createChildReference(record);
581     }
582 
createChildReference(TestRecord record)583     private ChildReference createChildReference(TestRecord record) {
584         ChildReference.Builder child = ChildReference.newBuilder();
585         child.setTestRecordId(record.getTestRecordId());
586         if (mInlineRecordOfChildren) {
587             child.setInlineTestRecord(record);
588         }
589         return child.build();
590     }
591 
592     /** Create and populate Timestamp as recommended in the javadoc of the Timestamp proto. */
createTimeStamp(long currentTimeMs)593     private Timestamp createTimeStamp(long currentTimeMs) {
594         return Timestamp.newBuilder()
595                 .setSeconds(currentTimeMs / 1000)
596                 .setNanos((int) ((currentTimeMs % 1000) * 1000000))
597                 .build();
598     }
599 
timeStampToMillis(Timestamp stamp)600     private long timeStampToMillis(Timestamp stamp) {
601         return stamp.getSeconds() * 1000L + (stamp.getNanos() / 1000000L);
602     }
603 
createFileProto(LogFile logFile)604     private LogFileInfo createFileProto(LogFile logFile) {
605         LogFileInfo.Builder logFileBuilder = LogFileInfo.newBuilder();
606         logFileBuilder
607                 .setPath(logFile.getPath())
608                 .setIsText(logFile.isText())
609                 .setLogType(logFile.getType().toString())
610                 .setIsCompressed(logFile.isCompressed())
611                 .setSize(logFile.getSize());
612         // Url can be null so avoid NPE by checking it before setting the proto
613         if (logFile.getUrl() != null) {
614             logFileBuilder.setUrl(logFile.getUrl());
615         }
616         return logFileBuilder.build();
617     }
618 
handleInvocationFailure()619     private DebugInfo handleInvocationFailure() {
620         DebugInfo.Builder debugBuilder = DebugInfo.newBuilder();
621         if (mInvocationFailureDescription == null) {
622             return null;
623         }
624 
625         Throwable baseException = mInvocationFailureDescription.getCause();
626         if (mInvocationFailureDescription.getErrorMessage() != null) {
627             debugBuilder.setErrorMessage(mInvocationFailureDescription.getErrorMessage());
628         }
629         debugBuilder.setTrace(StreamUtil.getStackTrace(baseException));
630         if (mInvocationFailureDescription != null
631                 && mInvocationFailureDescription.getFailureStatus() != null) {
632             debugBuilder.setFailureStatus(mInvocationFailureDescription.getFailureStatus());
633         }
634         DebugInfoContext.Builder debugContext = DebugInfoContext.newBuilder();
635         if (mInvocationFailureDescription != null) {
636             if (mInvocationFailureDescription.getActionInProgress() != null) {
637                 debugContext.setActionInProgress(
638                         mInvocationFailureDescription.getActionInProgress().toString());
639             }
640             if (!Strings.isNullOrEmpty(mInvocationFailureDescription.getDebugHelpMessage())) {
641                 debugContext.setDebugHelpMessage(
642                         mInvocationFailureDescription.getDebugHelpMessage());
643             }
644             if (!Strings.isNullOrEmpty(mInvocationFailureDescription.getOrigin())) {
645                 debugContext.setOrigin(mInvocationFailureDescription.getOrigin());
646             }
647             if (mInvocationFailureDescription.getErrorIdentifier() != null) {
648                 debugContext.setErrorName(
649                         mInvocationFailureDescription.getErrorIdentifier().name());
650                 debugContext.setErrorCode(
651                         mInvocationFailureDescription.getErrorIdentifier().code());
652             }
653         }
654         try {
655             debugContext.setErrorType(SerializationUtil.serializeToString(baseException));
656         } catch (IOException e) {
657             CLog.e("Failed to serialize the invocation failure:");
658             CLog.e(e);
659         }
660         debugBuilder.setDebugInfoContext(debugContext);
661 
662         return debugBuilder.build();
663     }
664 
convertSkipReason( SkipReason skip)665     private com.android.tradefed.result.proto.TestRecordProto.SkipReason convertSkipReason(
666             SkipReason skip) {
667         com.android.tradefed.result.proto.TestRecordProto.SkipReason.Builder reason =
668                 com.android.tradefed.result.proto.TestRecordProto.SkipReason.newBuilder();
669         reason.setReason(skip.getReason()).setTrigger(skip.getTrigger());
670         return reason.build();
671     }
672 }
673