1a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake# Copyright 2017 The Chromium Authors. All rights reserved.
2a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake# Use of this source code is governed by a BSD-style license that can be
3a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake# found in the LICENSE file.
40c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake
50c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peakeimport glob
6a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakeimport json
7a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakeimport os
8a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
959325d258052f28b10407f8adb500e0545764388Benny Peakeimport logging
1059325d258052f28b10407f8adb500e0545764388Benny Peake
11a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakefrom autotest_lib.site_utils.sponge_lib import autotest_job_info
12a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
13a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
146ade820e4f4843cef1b6a4e086db769d6ef088edBenny PeakeUNKNOWN_EFFORT_NAME = 'UNKNOWN_BUILD'
156ade820e4f4843cef1b6a4e086db769d6ef088edBenny PeakeUNKNOWN_ENV_NAME = 'UNKNOWN_BOARD'
166ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake
176ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake
18a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakeclass ACTSSummaryEnums(object):
19a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    """A class contains the attribute names used in a ACTS summary."""
20a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
21a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    Requested = 'Requested'
22a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    Failed = 'Failed'
23a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    Unknown = 'Unknown'
24a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
25a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
26a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakeclass ACTSRecordEnums(object):
27a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    """A class contains the attribute names used in an ACTS record."""
28a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
29a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    BeginTime = 'Begin Time'
30a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    Details = 'Details'
31a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    EndTime = 'End Time'
32a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    Extras = 'Extras'
33a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    ExtraErrors = 'Extra Errors'
34a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    Result = 'Result'
35a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    TestClass = 'Test Class'
36a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    TestName = 'Test Name'
37a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    UID = 'UID'
38a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
39a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
40a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakeclass ACTSTaskInfo(autotest_job_info.AutotestTaskInfo):
41a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    """Task info for an ACTS test."""
42a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
43a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    tags = autotest_job_info.AutotestTaskInfo.tags + ['acts', 'testtracker']
44a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    logs = autotest_job_info.AutotestTaskInfo.logs + ['results']
45a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
46a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def __init__(self, test, job):
47a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """
48a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        @param test: The autotest test for this ACTS test.
49a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        @param job: The job info that is the parent ot this task.
50a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """
51a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        super(ACTSTaskInfo, self).__init__(test, job)
52a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
53a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        summary_location = os.path.join(
540c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                self.results_dir, 'results/latest/test_run_summary.json')
550c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake
560c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake        build_info_location = os.path.join(self.results_dir,
570c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                'results/BUILD_INFO-*')
580c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake        build_info_files = glob.iglob(build_info_location)
590c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake
600c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake        try:
610c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            build_info_file = next(build_info_files)
620c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            logging.info('Using build info file: %s', build_info_file)
630c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            with open(build_info_file) as fd:
640c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                self.build_info = json.load(fd)
650c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake        except Exception as e:
660c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            logging.exception(e)
670c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            logging.error('Bad build info file.')
680c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            self.build_info = {}
690c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake
700c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake        try:
710c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            build_prop_str = self.build_info['build_prop']
720c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            prop_dict = {}
730c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            self.build_info['build_prop'] = prop_dict
740c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            lines = build_prop_str.splitlines()
750c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            for line in lines:
760c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                parts = line.split('=')
770c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake
780c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                if len(parts) != 2:
790c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                    continue
800c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake
810c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake                prop_dict[parts[0]] = parts[1]
820c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake        except Exception as e:
830c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            logging.exception(e)
840c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            logging.error('Bad build prop data, using default empty dict')
850c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            self.build_info['build_prop'] = {}
86a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
8759325d258052f28b10407f8adb500e0545764388Benny Peake        try:
8859325d258052f28b10407f8adb500e0545764388Benny Peake            with open(summary_location) as fd:
8959325d258052f28b10407f8adb500e0545764388Benny Peake                self._acts_summary = json.load(fd)
90a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
9159325d258052f28b10407f8adb500e0545764388Benny Peake            self._summary_block = self._acts_summary['Summary']
92a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
9359325d258052f28b10407f8adb500e0545764388Benny Peake            record_block = self._acts_summary['Results']
9459325d258052f28b10407f8adb500e0545764388Benny Peake            self._records = list(ACTSRecord(record) for record in record_block)
9559325d258052f28b10407f8adb500e0545764388Benny Peake            self.is_valid = True
9659325d258052f28b10407f8adb500e0545764388Benny Peake        except Exception as e:
970c4bbc47993f20b8d17a1f39d1d1b621ce6b8b6eBenny Peake            logging.exception(e)
9859325d258052f28b10407f8adb500e0545764388Benny Peake            logging.error('Bad acts data, reverting to autotest only.')
9959325d258052f28b10407f8adb500e0545764388Benny Peake            self.is_valid = False
10059325d258052f28b10407f8adb500e0545764388Benny Peake            self.tags = autotest_job_info.AutotestTaskInfo.tags
101a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
102a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
103a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def test_case_count(self):
104a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The number of test cases run."""
105a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._summary_block[ACTSSummaryEnums.Requested]
106a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
107a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
108a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def failed_case_count(self):
109a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The number of failed test cases."""
110a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._summary_block[ACTSSummaryEnums.Failed]
111a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
112a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
113a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def error_case_count(self):
114a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The number of errored test cases."""
115a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._summary_block[ACTSSummaryEnums.Unknown]
116a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
117a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
118a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def records(self):
119a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """All records of test cases in the ACTS tests."""
120a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._records
121a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
122a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
123405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake    def owner(self):
124405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake        """The owner of the task."""
125405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake        if 'param-testtracker_owner' in self.keyvals:
12610fde0d929c0f2552060c69da01feff454050607Benny Peake            return self.keyvals['param-testtracker_owner'].strip("'").strip('"')
127405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake        elif 'param-test_tracker_owner' in self.keyvals:
12810fde0d929c0f2552060c69da01feff454050607Benny Peake            return self.keyvals['param-testtracker_owner'].strip("'").strip('"')
129405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake        else:
13010fde0d929c0f2552060c69da01feff454050607Benny Peake            return self._job.user.strip("'").strip('"')
131405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake
132405ac5e6f5c66beee3082304a1c0f22f2eb335bdBenny Peake    @property
133a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def effort_name(self):
134a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The test tracker effort name."""
1356ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        build_id = self.build_info.get('build_prop', {}).get('ro.build.id')
1366ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        if build_id and any(c.isdigit() for c in build_id):
1376ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return build_id
1386ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        else:
1396ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            build_version = self.build_info.get('build_prop', {}).get(
1406ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake                    'ro.build.version.incremental', UNKNOWN_EFFORT_NAME)
1416ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return build_version
1426ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake
143a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
144a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
145a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def project_id(self):
146a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The test tracker project id."""
14745fa8a3818e928bd232ddb9bab6f2fc5f6d83681Benny Peake        if 'param-testtracker_project_id' in self.keyvals:
1486ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return self.keyvals.get('param-testtracker_project_id')
14945fa8a3818e928bd232ddb9bab6f2fc5f6d83681Benny Peake        else:
1506ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return self.keyvals.get('param-test_tracker_project_id')
151a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
152a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
153a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def environment(self):
154a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The name of the enviroment for test tracker."""
1556057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake        build_props = self.build_info.get('build_prop', {})
1566057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake
1576057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake        if 'ro.product.board' in build_props:
1586057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake            board = build_props['ro.product.board']
1596057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake        elif 'ro.build.product' in build_props:
1606057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake            board = build_props['ro.build.product']
1616057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake        else:
1626057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake            board = UNKNOWN_ENV_NAME
1636057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake
1646057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake        return board
165a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
16654f38a41e6676c65b741932e61cf139a95efd756Benny Peake    @property
16754f38a41e6676c65b741932e61cf139a95efd756Benny Peake    def extra_environment(self):
16854f38a41e6676c65b741932e61cf139a95efd756Benny Peake        """Extra environment info about the task."""
16954f38a41e6676c65b741932e61cf139a95efd756Benny Peake        if 'param-testtracker_extra_env' in self.keyvals:
17054f38a41e6676c65b741932e61cf139a95efd756Benny Peake            extra = self.keyvals.get('param-testtracker_extra_env', [])
17154f38a41e6676c65b741932e61cf139a95efd756Benny Peake        else:
1726057527518a23a0348d2bfaa4dcef2b537bdda3aBenny Peake            extra = self.keyvals.get('param-test_tracker_extra_env', [])
17354f38a41e6676c65b741932e61cf139a95efd756Benny Peake
17454f38a41e6676c65b741932e61cf139a95efd756Benny Peake        if not isinstance(extra, list):
17554f38a41e6676c65b741932e61cf139a95efd756Benny Peake            extra = [extra]
17654f38a41e6676c65b741932e61cf139a95efd756Benny Peake
17754f38a41e6676c65b741932e61cf139a95efd756Benny Peake        return extra
17854f38a41e6676c65b741932e61cf139a95efd756Benny Peake
179a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
180a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peakeclass ACTSRecord(object):
181a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    """A single record of a test case in an ACTS test."""
182a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
183a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    tags = ['acts', 'testtracker']
184a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
185a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def __init__(self, json_record):
186a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """
187a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        @param json_record: The json info for this record
188a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """
189a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        self._json_record = json_record
190a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
191a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
192a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def test_class(self):
193a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The test class that was run."""
194a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._json_record[ACTSRecordEnums.TestClass]
195a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
196a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
197a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def test_case(self):
198a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The test case that was run. None implies all in the class."""
1996ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return self._json_record.get(ACTSRecordEnums.TestName)
200a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
201a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
202a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def uid(self):
203a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The uid of the test case."""
2046ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return self._json_record.get(ACTSRecordEnums.UID)
205a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
206a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
207a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def status(self):
208a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The status of the test case."""
209a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._json_record[ACTSRecordEnums.Result]
210a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
211a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
212a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def start_time(self):
213a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The start time of the test case."""
214a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._json_record[ACTSRecordEnums.BeginTime] / 1000.0
215a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
216a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
217a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def end_time(self):
218a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The end time of the test case."""
219a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        return self._json_record[ACTSRecordEnums.EndTime] / 1000.0
220a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
221a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
222a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def details(self):
223a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """Details about the test case."""
2246ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return self._json_record.get(ACTSRecordEnums.Details)
225a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
226a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
227a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def extras(self):
228a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """Extra info about the test case."""
2296ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return self._json_record.get(ACTSRecordEnums.Extras)
230a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
231a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
232a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def extra_errors(self):
233a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """Extra errors about the test case."""
2346ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return self._json_record.get(ACTSRecordEnums.ExtraErrors)
2356ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake
2366ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake    @property
2376ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake    def extra_environment(self):
2386ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        """Extra details about the environment for this test."""
2396ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        extras = self.extras
2406ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        if not extras:
2416ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return None
2426ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake
2436ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        test_tracker_info = self.extras.get('test_tracker_info')
2446ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        if not test_tracker_info:
2456ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return self.extras.get('test_tracker_environment_info')
2466ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake
2476ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return test_tracker_info.get('extra_environment')
248a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
249a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    @property
250a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake    def uuid(self):
251a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        """The test tracker uuid of the test case."""
252a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        extras = self.extras
253a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        if not extras:
254a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake            return None
255a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
2566ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        test_tracker_info = self.extras.get('test_tracker_info')
257a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake        if not test_tracker_info:
2586ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake            return self.extras.get('test_tracker_uuid')
259a6bcf7f18aded465d734820b73eef14a5ec10d46Benny Peake
2606ade820e4f4843cef1b6a4e086db769d6ef088edBenny Peake        return test_tracker_info.get('test_tracker_uuid')
261