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.util;
17 
18 import com.android.tradefed.invoker.IInvocationContext;
19 import com.android.tradefed.log.LogUtil.CLog;
20 import com.android.tradefed.result.ActionInProgress;
21 import com.android.tradefed.result.FailureDescription;
22 import com.android.tradefed.result.LogDataType;
23 import com.android.tradefed.result.LogFile;
24 import com.android.tradefed.result.error.ErrorIdentifier;
25 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
26 import com.android.tradefed.result.skipped.SkipReason;
27 import com.android.tradefed.testtype.suite.ModuleDefinition;
28 
29 import com.google.common.base.Strings;
30 
31 import org.json.JSONException;
32 import org.json.JSONObject;
33 
34 import java.io.File;
35 import java.io.IOException;
36 import java.io.PrintWriter;
37 import java.io.StringWriter;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.Map;
41 
42 import javax.annotation.Nonnull;
43 
44 /**
45  * Helper to serialize/deserialize the events to be passed to the log.
46  */
47 public class SubprocessEventHelper {
48     private static final String CLASSNAME_KEY = "className";
49     private static final String TESTNAME_KEY = "testName";
50     private static final String TRACE_KEY = "trace";
51     private static final String CAUSE_KEY = "cause";
52     private static final String RUNNAME_KEY = "runName";
53     private static final String TESTCOUNT_KEY = "testCount";
54     private static final String ATTEMPT_KEY = "runAttempt";
55     private static final String TIME_KEY = "time";
56     private static final String REASON_KEY = "reason";
57     private static final String START_TIME = "start_time";
58     private static final String END_TIME = "end_time";
59 
60     private static final String DATA_NAME_KEY = "dataName";
61     private static final String DATA_TYPE_KEY = "dataType";
62     private static final String DATA_FILE_KEY = "dataFile";
63     private static final String LOGGED_FILE_KEY = "loggedFile";
64 
65     private static final String TEST_TAG_KEY = "testTag";
66 
67     private static final String MODULE_CONTEXT_KEY = "moduleContextFileName";
68     private static final String MODULE_NAME = "moduleName";
69 
70     // Keys for skip reason
71     private static final String SKIP_REASON_MESSAGE = "skipMessage";
72     private static final String SKIP_REASON_TRIGGER = "trigger";
73 
74     // keys for Error Classification
75     private static final String FAILURE_STATUS_KEY = "failure_status";
76     private static final String ACTION_IN_PROGRESS_KEY = "action_in_progress";
77     private static final String ERROR_NAME_KEY = "error_name";
78     private static final String ERROR_CODE_KEY = "error_code";
79     private static final String ERROR_ORIGIN_KEY = "origin";
80 
81     /**
82      * Helper for testRunStarted information
83      */
84     public static class TestRunStartedEventInfo {
85         public String mRunName = null;
86         public Integer mTestCount = null;
87         public Integer mAttempt = null;
88         public Long mStartTime = null;
89 
90         /** Keep this constructor for legacy compatibility. */
TestRunStartedEventInfo(String runName, int testCount)91         public TestRunStartedEventInfo(String runName, int testCount) {
92             mRunName = runName;
93             mTestCount = testCount;
94             mAttempt = 0;
95             mStartTime = System.currentTimeMillis();
96         }
97 
TestRunStartedEventInfo(String runName, int testCount, int attempt, long startTime)98         public TestRunStartedEventInfo(String runName, int testCount, int attempt, long startTime) {
99             mRunName = runName;
100             mTestCount = testCount;
101             mAttempt = attempt;
102             mStartTime = startTime;
103         }
104 
TestRunStartedEventInfo(JSONObject jsonObject)105         public TestRunStartedEventInfo(JSONObject jsonObject) throws JSONException {
106             mRunName = jsonObject.getString(RUNNAME_KEY);
107             mTestCount = jsonObject.getInt(TESTCOUNT_KEY);
108             mAttempt = jsonObject.optInt(ATTEMPT_KEY, 0);
109             mStartTime = jsonObject.optLong(START_TIME, System.currentTimeMillis());
110         }
111 
112         @Override
toString()113         public String toString() {
114             JSONObject tags = new JSONObject();
115             try {
116                 if (mRunName != null) {
117                     tags.put(RUNNAME_KEY, mRunName);
118                 }
119                 if (mTestCount != null) {
120                     tags.put(TESTCOUNT_KEY, mTestCount.intValue());
121                 }
122                 if (mAttempt != null) {
123                     tags.put(ATTEMPT_KEY, mAttempt.intValue());
124                 }
125                 if (mStartTime != null) {
126                     tags.put(START_TIME, mStartTime.longValue());
127                 }
128             } catch (JSONException e) {
129                 CLog.e(e);
130             }
131             return tags.toString();
132         }
133     }
134 
135     /**
136      * Helper for testRunFailed information
137      */
138     public static class TestRunFailedEventInfo {
139         public String mReason = null;
140         public FailureDescription mFailure = null;
141 
TestRunFailedEventInfo(String reason)142         public TestRunFailedEventInfo(String reason) {
143             mReason = reason;
144         }
145 
TestRunFailedEventInfo(FailureDescription failure)146         public TestRunFailedEventInfo(FailureDescription failure) {
147             mFailure = failure;
148         }
149 
TestRunFailedEventInfo(JSONObject jsonObject)150         public TestRunFailedEventInfo(JSONObject jsonObject) throws JSONException {
151             mReason = jsonObject.getString(REASON_KEY);
152             mFailure = FailureDescription.create(mReason);
153             updateFailureFromJsonObject(mFailure, jsonObject);
154         }
155 
156         @Override
toString()157         public String toString() {
158             JSONObject tags = new JSONObject();
159             try {
160                 if (mFailure != null) {
161                     tags.put(REASON_KEY, mFailure.getErrorMessage());
162                     tags.putOpt(FAILURE_STATUS_KEY, mFailure.getFailureStatus());
163                     tags.putOpt(ACTION_IN_PROGRESS_KEY, mFailure.getActionInProgress());
164                     tags.putOpt(ERROR_ORIGIN_KEY, mFailure.getOrigin());
165                     if (mFailure.getErrorIdentifier() != null) {
166                         tags.putOpt(ERROR_NAME_KEY, mFailure.getErrorIdentifier().name());
167                         tags.putOpt(ERROR_CODE_KEY, mFailure.getErrorIdentifier().code());
168                         tags.putOpt(FAILURE_STATUS_KEY, mFailure.getErrorIdentifier().status());
169                     }
170                 }
171                 if (mReason != null) {
172                     tags.put(REASON_KEY, mReason);
173                 }
174             } catch (JSONException e) {
175                 CLog.e(e);
176             }
177             return tags.toString();
178         }
179     }
180 
181     /**
182      * Helper for testRunEnded Information.
183      */
184     public static class TestRunEndedEventInfo {
185         public Long mTime = null;
186         public Map<String, String> mRunMetrics = null;
187 
TestRunEndedEventInfo(Long time, Map<String, String> runMetrics)188         public TestRunEndedEventInfo(Long time, Map<String, String> runMetrics) {
189             mTime = time;
190             mRunMetrics = runMetrics;
191         }
192 
TestRunEndedEventInfo(JSONObject jsonObject)193         public TestRunEndedEventInfo(JSONObject jsonObject) throws JSONException {
194             mTime = jsonObject.getLong(TIME_KEY);
195             jsonObject.remove(TIME_KEY);
196             Iterator<?> i = jsonObject.keys();
197             mRunMetrics = new HashMap<String, String>();
198             while(i.hasNext()) {
199                 String key = (String) i.next();
200                 mRunMetrics.put(key, jsonObject.get(key).toString());
201             }
202         }
203 
204         @Override
toString()205         public String toString() {
206             JSONObject tags = null;
207             try {
208                 if (mRunMetrics != null) {
209                     tags = new JSONObject(mRunMetrics);
210                 } else {
211                     tags = new JSONObject();
212                 }
213                 if (mTime != null) {
214                     tags.put(TIME_KEY, mTime.longValue());
215                 }
216             } catch (JSONException e) {
217                 CLog.e(e);
218             }
219             return tags.toString();
220         }
221     }
222 
223     /**
224      * Helper for InvocationFailed information.
225      */
226     public static class InvocationFailedEventInfo {
227         public Throwable mCause = null;
228         public FailureDescription mFailure = null;
229 
InvocationFailedEventInfo(Throwable cause)230         public InvocationFailedEventInfo(Throwable cause) {
231             mCause = cause;
232         }
233 
InvocationFailedEventInfo(FailureDescription failure)234         public InvocationFailedEventInfo(FailureDescription failure) {
235             if (failure.getCause() != null) {
236                 mCause = failure.getCause();
237             } else {
238                 mCause = new Throwable(failure.getErrorMessage());
239             }
240             mFailure = failure;
241         }
242 
InvocationFailedEventInfo(JSONObject jsonObject)243         public InvocationFailedEventInfo(JSONObject jsonObject) throws JSONException {
244             String stack = jsonObject.getString(CAUSE_KEY);
245             mCause = new Throwable(stack);
246 
247             if (!Strings.isNullOrEmpty(jsonObject.optString(REASON_KEY))) {
248                 mFailure =
249                         FailureDescription.create(jsonObject.optString(REASON_KEY))
250                                 .setOrigin(jsonObject.optString(ERROR_ORIGIN_KEY))
251                                 .setCause(mCause);
252                 // FailureStatus
253                 FailureStatus status = FailureStatus.UNSET;
254                 if (!Strings.isNullOrEmpty(jsonObject.optString(FAILURE_STATUS_KEY))) {
255                     try {
256                         status = FailureStatus.valueOf(jsonObject.optString(FAILURE_STATUS_KEY));
257                     } catch (NullPointerException | IllegalArgumentException e) {
258                         CLog.e(e);
259                     }
260                 }
261                 mFailure.setFailureStatus(status);
262                 // ActionInProgress
263                 ActionInProgress action = ActionInProgress.UNSET;
264                 if (!Strings.isNullOrEmpty(jsonObject.optString(ACTION_IN_PROGRESS_KEY))) {
265                     try {
266                         action =
267                                 ActionInProgress.valueOf(
268                                         jsonObject.optString(ACTION_IN_PROGRESS_KEY));
269                     } catch (NullPointerException | IllegalArgumentException e) {
270                         CLog.e(e);
271                     }
272                 }
273                 mFailure.setActionInProgress(action);
274                 // ErrorIdentifier
275                 String errorName = jsonObject.optString(ERROR_NAME_KEY);
276                 long errorCode = jsonObject.optLong(ERROR_CODE_KEY);
277                 if (errorName != null) {
278                     ErrorIdentifier errorId =
279                             new ErrorIdentifier() {
280                                 @Override
281                                 public String name() {
282                                     return errorName;
283                                 }
284 
285                                 @Override
286                                 public long code() {
287                                     return errorCode;
288                                 }
289 
290                                 @Override
291                                 public @Nonnull FailureStatus status() {
292                                     FailureStatus status = mFailure.getFailureStatus();
293                                     return (status == null ? FailureStatus.UNSET : status);
294                                 }
295                             };
296                     mFailure.setErrorIdentifier(errorId);
297                 }
298             }
299         }
300 
301         @Override
toString()302         public String toString() {
303             JSONObject tags = new JSONObject();
304             try {
305                 if (mFailure != null) {
306                     tags.put(REASON_KEY, mFailure.getErrorMessage());
307                     tags.putOpt(ACTION_IN_PROGRESS_KEY, mFailure.getActionInProgress());
308                     tags.putOpt(ERROR_ORIGIN_KEY, mFailure.getOrigin());
309                     if (mFailure.getErrorIdentifier() != null) {
310                         tags.putOpt(ERROR_NAME_KEY, mFailure.getErrorIdentifier().name());
311                         tags.putOpt(ERROR_CODE_KEY, mFailure.getErrorIdentifier().code());
312                         tags.putOpt(FAILURE_STATUS_KEY, mFailure.getErrorIdentifier().status());
313                     } else {
314                         tags.putOpt(FAILURE_STATUS_KEY, mFailure.getFailureStatus());
315                     }
316                 }
317                 if (mCause != null) {
318                     StringWriter sw = new StringWriter();
319                     PrintWriter pw = new PrintWriter(sw);
320                     mCause.printStackTrace(pw);
321                     tags.put(CAUSE_KEY, sw.toString());
322                 }
323             } catch (JSONException e) {
324                 CLog.e(e);
325             }
326             return tags.toString();
327         }
328     }
329 
330     /** Base Helper for TestIgnored information. */
331     public static class BaseTestEventInfo {
332         public String mClassName = null;
333         public String mTestName = null;
334 
BaseTestEventInfo(String className, String testName)335         public BaseTestEventInfo(String className, String testName) {
336             mClassName = className;
337             mTestName = testName;
338         }
339 
BaseTestEventInfo(JSONObject jsonObject)340         public BaseTestEventInfo(JSONObject jsonObject) throws JSONException {
341             mClassName = jsonObject.getString(CLASSNAME_KEY);
342             jsonObject.remove(CLASSNAME_KEY);
343             mTestName = jsonObject.getString(TESTNAME_KEY);
344             jsonObject.remove(TESTNAME_KEY);
345         }
346 
getNewJson()347         protected JSONObject getNewJson() {
348             return new JSONObject();
349         }
350 
351         @Override
toString()352         public String toString() {
353             JSONObject tags = null;
354             try {
355                 tags = getNewJson();
356                 if (mClassName != null) {
357                     tags.put(CLASSNAME_KEY, mClassName);
358                 }
359                 if (mTestName != null) {
360                     tags.put(TESTNAME_KEY, mTestName);
361                 }
362             } catch (JSONException e) {
363                 CLog.e(e);
364             }
365             return tags.toString();
366         }
367     }
368 
369     /** Helper for testStarted information */
370     public static class TestStartedEventInfo extends BaseTestEventInfo {
371         public Long mStartTime = null;
372 
TestStartedEventInfo(String className, String testName, Long startTime)373         public TestStartedEventInfo(String className, String testName, Long startTime) {
374             super(className, testName);
375             mStartTime = startTime;
376         }
377 
TestStartedEventInfo(JSONObject jsonObject)378         public TestStartedEventInfo(JSONObject jsonObject) throws JSONException {
379             super(jsonObject);
380             if (jsonObject.has(START_TIME)) {
381                 mStartTime = jsonObject.getLong(START_TIME);
382             }
383             jsonObject.remove(START_TIME);
384         }
385 
386         @Override
getNewJson()387         protected JSONObject getNewJson() {
388             JSONObject json = new JSONObject();
389             try {
390                 json.put(START_TIME, mStartTime);
391             } catch (JSONException e) {
392                 CLog.e(e);
393             }
394             return json;
395         }
396     }
397 
398     public static class SkippedTestEventInfo extends BaseTestEventInfo {
399         public SkipReason skipReason = null;
400 
SkippedTestEventInfo(String className, String testName, SkipReason reason)401         public SkippedTestEventInfo(String className, String testName, SkipReason reason) {
402             super(className, testName);
403             skipReason = reason;
404         }
405 
SkippedTestEventInfo(JSONObject jsonObject)406         public SkippedTestEventInfo(JSONObject jsonObject) throws JSONException {
407             super(jsonObject);
408             skipReason =
409                     new SkipReason(
410                             jsonObject.getString(SKIP_REASON_MESSAGE),
411                             jsonObject.getString(SKIP_REASON_TRIGGER));
412         }
413 
414         @Override
toString()415         public String toString() {
416             JSONObject tags = null;
417             try {
418                 tags = new JSONObject(super.toString());
419                 tags.put(SKIP_REASON_MESSAGE, skipReason.getReason());
420                 tags.put(SKIP_REASON_TRIGGER, skipReason.getTrigger());
421             } catch (JSONException e) {
422                 CLog.e(e);
423             }
424             return tags.toString();
425         }
426     }
427 
428     /** Helper for testFailed information. */
429     public static class FailedTestEventInfo extends BaseTestEventInfo {
430         public String mTrace = null;
431         public FailureDescription mFailure = null;
432 
FailedTestEventInfo(String className, String testName, String trace)433         public FailedTestEventInfo(String className, String testName, String trace) {
434             super(className, testName);
435             mTrace = trace;
436         }
437 
FailedTestEventInfo(String className, String testName, FailureDescription failure)438         public FailedTestEventInfo(String className, String testName, FailureDescription failure) {
439             super(className, testName);
440             mFailure = failure;
441         }
442 
FailedTestEventInfo(JSONObject jsonObject)443         public FailedTestEventInfo(JSONObject jsonObject) throws JSONException {
444             super(jsonObject);
445             mTrace = jsonObject.getString(TRACE_KEY);
446             mFailure = FailureDescription.create(mTrace);
447             updateFailureFromJsonObject(mFailure, jsonObject);
448         }
449 
450         @Override
toString()451         public String toString() {
452             JSONObject tags = null;
453             try {
454                 tags = new JSONObject(super.toString());
455                 if (mFailure != null) {
456                     tags.put(TRACE_KEY, mFailure.getErrorMessage());
457                     tags.putOpt(FAILURE_STATUS_KEY, mFailure.getFailureStatus());
458                     tags.putOpt(ACTION_IN_PROGRESS_KEY, mFailure.getActionInProgress());
459                     tags.putOpt(ERROR_ORIGIN_KEY, mFailure.getOrigin());
460                     if (mFailure.getErrorIdentifier() != null) {
461                         tags.putOpt(ERROR_NAME_KEY, mFailure.getErrorIdentifier().name());
462                         tags.putOpt(ERROR_CODE_KEY, mFailure.getErrorIdentifier().code());
463                         tags.putOpt(FAILURE_STATUS_KEY, mFailure.getErrorIdentifier().status());
464                     }
465                 }
466                 if (mTrace != null) {
467                     tags.put(TRACE_KEY, mTrace);
468                 }
469             } catch (JSONException e) {
470                 CLog.e(e);
471             }
472             return tags.toString();
473         }
474     }
475 
476     /**
477      * Helper for testEnded information.
478      */
479     public static class TestEndedEventInfo extends BaseTestEventInfo {
480         public Map<String, String> mRunMetrics = null;
481         public Long mEndTime = null;
482 
TestEndedEventInfo(String className, String testName, Map<String, String> runMetrics)483         public TestEndedEventInfo(String className, String testName,
484                 Map<String, String> runMetrics) {
485             super(className, testName);
486             mRunMetrics = runMetrics;
487             mEndTime = System.currentTimeMillis();
488         }
489 
490         /**
491          * Create an event object to represent the testEnded callback.
492          *
493          * @param className the classname of the tests
494          * @param testName the name of the tests
495          * @param endTime the timestamp at which the test ended (from {@link
496          *     System#currentTimeMillis()})
497          * @param runMetrics the metrics reported by the test.
498          */
TestEndedEventInfo( String className, String testName, Long endTime, Map<String, String> runMetrics)499         public TestEndedEventInfo(
500                 String className, String testName, Long endTime, Map<String, String> runMetrics) {
501             super(className, testName);
502             mEndTime = endTime;
503             mRunMetrics = runMetrics;
504         }
505 
506         /** Create and populate and event object for testEnded from a JSON. */
TestEndedEventInfo(JSONObject jsonObject)507         public TestEndedEventInfo(JSONObject jsonObject) throws JSONException {
508             super(jsonObject);
509             if (jsonObject.has(END_TIME)) {
510                 mEndTime = jsonObject.getLong(END_TIME);
511             }
512             jsonObject.remove(END_TIME);
513             Iterator<?> i = jsonObject.keys();
514             mRunMetrics = new HashMap<String, String>();
515             while(i.hasNext()) {
516                 String key = (String) i.next();
517                 mRunMetrics.put(key, jsonObject.get(key).toString());
518             }
519         }
520 
521         @Override
getNewJson()522         protected JSONObject getNewJson() {
523             JSONObject json;
524             if (mRunMetrics != null) {
525                 json = new JSONObject(mRunMetrics);
526             } else {
527                 json = new JSONObject();
528             }
529             try {
530                 json.put(END_TIME, mEndTime);
531             } catch (JSONException e) {
532                 CLog.e(e);
533             }
534             return json;
535         }
536     }
537 
538     /** Helper for testLog information. */
539     public static class TestLogEventInfo {
540         public String mDataName = null;
541         public LogDataType mLogType = null;
542         public File mDataFile = null;
543 
TestLogEventInfo(String dataName, LogDataType dataType, File dataFile)544         public TestLogEventInfo(String dataName, LogDataType dataType, File dataFile) {
545             mDataName = dataName;
546             mLogType = dataType;
547             mDataFile = dataFile;
548         }
549 
TestLogEventInfo(JSONObject jsonObject)550         public TestLogEventInfo(JSONObject jsonObject) throws JSONException {
551             mDataName = jsonObject.getString(DATA_NAME_KEY);
552             jsonObject.remove(DATA_NAME_KEY);
553             try {
554                 mLogType = LogDataType.valueOf(jsonObject.getString(DATA_TYPE_KEY));
555             } catch (IllegalArgumentException e) {
556                 CLog.e("Failed to parse type: %s", jsonObject.getString(DATA_TYPE_KEY));
557                 mLogType = LogDataType.TEXT;
558             }
559             jsonObject.remove(DATA_TYPE_KEY);
560             mDataFile = new File(jsonObject.getString(DATA_FILE_KEY));
561         }
562 
563         @Override
toString()564         public String toString() {
565             JSONObject tags = null;
566             try {
567                 tags = new JSONObject();
568                 if (mDataName != null) {
569                     tags.put(DATA_NAME_KEY, mDataName);
570                 }
571                 if (mLogType != null) {
572                     tags.put(DATA_TYPE_KEY, mLogType.toString());
573                 }
574                 if (mDataFile != null) {
575                     tags.put(DATA_FILE_KEY, mDataFile.getAbsolutePath());
576                 }
577             } catch (JSONException e) {
578                 CLog.e(e);
579             }
580             return tags.toString();
581         }
582     }
583 
584     /** Helper for logAssociation information. */
585     public static class LogAssociationEventInfo {
586         public String mDataName = null;
587         public LogFile mLoggedFile = null;
588 
LogAssociationEventInfo(String dataName, LogFile loggedFile)589         public LogAssociationEventInfo(String dataName, LogFile loggedFile) {
590             mDataName = dataName;
591             mLoggedFile = loggedFile;
592         }
593 
LogAssociationEventInfo(JSONObject jsonObject)594         public LogAssociationEventInfo(JSONObject jsonObject) throws JSONException {
595             mDataName = jsonObject.getString(DATA_NAME_KEY);
596             jsonObject.remove(DATA_NAME_KEY);
597             String file = jsonObject.getString(LOGGED_FILE_KEY);
598             try {
599                 mLoggedFile = (LogFile) SerializationUtil.deserialize(new File(file), true);
600             } catch (IOException e) {
601                 throw new JSONException(e.getMessage());
602             } finally {
603                 FileUtil.deleteFile(new File(file));
604             }
605         }
606 
607         @Override
toString()608         public String toString() {
609             JSONObject tags = null;
610             try {
611                 tags = new JSONObject();
612                 if (mDataName != null) {
613                     tags.put(DATA_NAME_KEY, mDataName);
614                 }
615                 if (mLoggedFile != null) {
616                     File serializedLoggedFile = SerializationUtil.serialize(mLoggedFile);
617                     tags.put(LOGGED_FILE_KEY, serializedLoggedFile.getAbsolutePath());
618                 }
619             } catch (JSONException | IOException e) {
620                 CLog.e(e);
621                 throw new RuntimeException(e);
622             }
623             return tags.toString();
624         }
625     }
626 
627     /** Helper for invocation started information. */
628     public static class InvocationStartedEventInfo {
629         public String mTestTag = null;
630         public Long mStartTime = null;
631 
InvocationStartedEventInfo(String testTag, Long startTime)632         public InvocationStartedEventInfo(String testTag, Long startTime) {
633             mTestTag = testTag;
634             mStartTime = startTime;
635         }
636 
InvocationStartedEventInfo(JSONObject jsonObject)637         public InvocationStartedEventInfo(JSONObject jsonObject) throws JSONException {
638             mTestTag = jsonObject.getString(TEST_TAG_KEY);
639             if (jsonObject.has(START_TIME)) {
640                 mStartTime = jsonObject.getLong(START_TIME);
641             }
642         }
643 
644         @Override
toString()645         public String toString() {
646             JSONObject tags = null;
647             try {
648                 tags = new JSONObject();
649                 if (mTestTag != null) {
650                     tags.put(TEST_TAG_KEY, mTestTag);
651                 }
652                 if (mStartTime != null) {
653                     tags.put(START_TIME, mStartTime);
654                 }
655             } catch (JSONException e) {
656                 CLog.e(e);
657             }
658             return tags.toString();
659         }
660     }
661 
662     /** Helper for invocation ended information. */
663     public static class InvocationEndedEventInfo {
664         public Map<String, String> mBuildAttributes;
665 
InvocationEndedEventInfo(Map<String, String> buildAttributes)666         public InvocationEndedEventInfo(Map<String, String> buildAttributes) {
667             mBuildAttributes = new HashMap<String, String>(buildAttributes);
668         }
669 
InvocationEndedEventInfo(JSONObject jsonObject)670         public InvocationEndedEventInfo(JSONObject jsonObject) throws JSONException {
671             mBuildAttributes = new HashMap<String, String>();
672             Iterator<?> i = jsonObject.keys();
673             while (i.hasNext()) {
674                 String key = (String) i.next();
675                 mBuildAttributes.put(key, jsonObject.get(key).toString());
676             }
677         }
678 
679         @Override
toString()680         public String toString() {
681             JSONObject jsonObject = new JSONObject(mBuildAttributes);
682             return jsonObject.toString();
683         }
684     }
685 
686     /** Helper for test module started information. */
687     public static class TestModuleStartedEventInfo {
688         public IInvocationContext mModuleContext;
689 
TestModuleStartedEventInfo(IInvocationContext moduleContext)690         public TestModuleStartedEventInfo(IInvocationContext moduleContext) {
691             mModuleContext = moduleContext;
692         }
693 
TestModuleStartedEventInfo(JSONObject jsonObject)694         public TestModuleStartedEventInfo(JSONObject jsonObject) throws JSONException {
695             String file = jsonObject.getString(MODULE_CONTEXT_KEY);
696             try {
697                 mModuleContext =
698                         (IInvocationContext) SerializationUtil.deserialize(new File(file), true);
699             } catch (IOException e) {
700                 throw new RuntimeException(e);
701             }
702         }
703 
704         @Override
toString()705         public String toString() {
706             JSONObject tags = null;
707             try {
708                 tags = new JSONObject();
709                 File serializedContext = SerializationUtil.serialize(mModuleContext);
710                 tags.put(MODULE_CONTEXT_KEY, serializedContext.getAbsolutePath());
711                 // For easier debugging on the events for modules, add the module name
712                 String moduleName =
713                         mModuleContext
714                                 .getAttributes()
715                                 .getUniqueMap()
716                                 .get(ModuleDefinition.MODULE_ID);
717                 if (moduleName != null) {
718                     tags.put(MODULE_NAME, moduleName);
719                 }
720             } catch (IOException | JSONException e) {
721                 CLog.e(e);
722                 throw new RuntimeException(e);
723             }
724             return tags.toString();
725         }
726     }
727 
728     /**
729      * Updates failure with origin, failureStatus, actionInProgress, errorIdentifier from
730      * jsonObejct.
731      */
updateFailureFromJsonObject( FailureDescription failure, JSONObject jsonObject)732     private static void updateFailureFromJsonObject(
733             FailureDescription failure, JSONObject jsonObject) {
734         // Origin
735         failure.setOrigin(jsonObject.optString(ERROR_ORIGIN_KEY));
736         // FailureStatus
737         FailureStatus status = FailureStatus.UNSET;
738         if (!Strings.isNullOrEmpty(jsonObject.optString(FAILURE_STATUS_KEY))) {
739             try {
740                 status = FailureStatus.valueOf(jsonObject.optString(FAILURE_STATUS_KEY));
741             } catch (NullPointerException | IllegalArgumentException e) {
742                 CLog.e(e);
743             }
744         }
745         failure.setFailureStatus(status);
746         // ActionInProgress
747         ActionInProgress action = ActionInProgress.UNSET;
748         if (!Strings.isNullOrEmpty(jsonObject.optString(ACTION_IN_PROGRESS_KEY))) {
749             try {
750                 action = ActionInProgress.valueOf(jsonObject.optString(ACTION_IN_PROGRESS_KEY));
751             } catch (NullPointerException | IllegalArgumentException e) {
752                 CLog.e(e);
753             }
754         }
755         failure.setActionInProgress(action);
756         // ErrorIdentifier
757         String errorName = jsonObject.optString(ERROR_NAME_KEY);
758         long errorCode = jsonObject.optLong(ERROR_CODE_KEY);
759         if (errorName != null) {
760             ErrorIdentifier errorId =
761                     new ErrorIdentifier() {
762                         @Override
763                         public String name() {
764                             return errorName;
765                         }
766 
767                         @Override
768                         public long code() {
769                             return errorCode;
770                         }
771 
772                         @Override
773                         public @Nonnull FailureStatus status() {
774                             FailureStatus status = failure.getFailureStatus();
775                             return (status == null ? FailureStatus.UNSET : status);
776                         }
777                     };
778             failure.setErrorIdentifier(errorId);
779         }
780     }
781 }
782