1# Copyright 2017 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import glob 6import json 7import os 8 9import logging 10 11from autotest_lib.site_utils.sponge_lib import autotest_job_info 12 13 14class ACTSSummaryEnums(object): 15 """A class contains the attribute names used in a ACTS summary.""" 16 17 Requested = 'Requested' 18 Failed = 'Failed' 19 Unknown = 'Unknown' 20 21 22class ACTSRecordEnums(object): 23 """A class contains the attribute names used in an ACTS record.""" 24 25 BeginTime = 'Begin Time' 26 Details = 'Details' 27 EndTime = 'End Time' 28 Extras = 'Extras' 29 ExtraErrors = 'Extra Errors' 30 Result = 'Result' 31 TestClass = 'Test Class' 32 TestName = 'Test Name' 33 UID = 'UID' 34 35 36class ACTSTaskInfo(autotest_job_info.AutotestTaskInfo): 37 """Task info for an ACTS test.""" 38 39 tags = autotest_job_info.AutotestTaskInfo.tags + ['acts', 'testtracker'] 40 logs = autotest_job_info.AutotestTaskInfo.logs + ['results'] 41 42 def __init__(self, test, job): 43 """ 44 @param test: The autotest test for this ACTS test. 45 @param job: The job info that is the parent ot this task. 46 """ 47 super(ACTSTaskInfo, self).__init__(test, job) 48 49 summary_location = os.path.join( 50 self.results_dir, 'results/latest/test_run_summary.json') 51 52 build_info_location = os.path.join(self.results_dir, 53 'results/BUILD_INFO-*') 54 build_info_files = glob.iglob(build_info_location) 55 56 try: 57 build_info_file = next(build_info_files) 58 logging.info('Using build info file: %s', build_info_file) 59 with open(build_info_file) as fd: 60 self.build_info = json.load(fd) 61 except Exception as e: 62 logging.exception(e) 63 logging.error('Bad build info file.') 64 self.build_info = {} 65 66 try: 67 build_prop_str = self.build_info['build_prop'] 68 prop_dict = {} 69 self.build_info['build_prop'] = prop_dict 70 lines = build_prop_str.splitlines() 71 for line in lines: 72 parts = line.split('=') 73 74 if len(parts) != 2: 75 continue 76 77 prop_dict[parts[0]] = parts[1] 78 except Exception as e: 79 logging.exception(e) 80 logging.error('Bad build prop data, using default empty dict') 81 self.build_info['build_prop'] = {} 82 83 try: 84 with open(summary_location) as fd: 85 self._acts_summary = json.load(fd) 86 87 self._summary_block = self._acts_summary['Summary'] 88 89 record_block = self._acts_summary['Results'] 90 self._records = list(ACTSRecord(record) for record in record_block) 91 self.is_valid = True 92 except Exception as e: 93 logging.exception(e) 94 logging.error('Bad acts data, reverting to autotest only.') 95 self.is_valid = False 96 self.tags = autotest_job_info.AutotestTaskInfo.tags 97 98 @property 99 def test_case_count(self): 100 """The number of test cases run.""" 101 return self._summary_block[ACTSSummaryEnums.Requested] 102 103 @property 104 def failed_case_count(self): 105 """The number of failed test cases.""" 106 return self._summary_block[ACTSSummaryEnums.Failed] 107 108 @property 109 def error_case_count(self): 110 """The number of errored test cases.""" 111 return self._summary_block[ACTSSummaryEnums.Unknown] 112 113 @property 114 def records(self): 115 """All records of test cases in the ACTS tests.""" 116 return self._records 117 118 @property 119 def effort_name(self): 120 """The test tracker effort name.""" 121 return self.build_info.get('build_prop', {}).get( 122 'ro.build.version.incremental', 'UNKNOWN_BUILD') 123 124 @property 125 def project_id(self): 126 """The test tracker project id.""" 127 return self.keyvals.get('param-testtracker_project_id', None) 128 129 @property 130 def environment(self): 131 """The name of the enviroment for test tracker.""" 132 return self.build_info.get('branch', 'UNKNOWN_BRANCH') 133 134 135class ACTSRecord(object): 136 """A single record of a test case in an ACTS test.""" 137 138 tags = ['acts', 'testtracker'] 139 140 def __init__(self, json_record): 141 """ 142 @param json_record: The json info for this record 143 """ 144 self._json_record = json_record 145 146 @property 147 def test_class(self): 148 """The test class that was run.""" 149 return self._json_record[ACTSRecordEnums.TestClass] 150 151 @property 152 def test_case(self): 153 """The test case that was run. None implies all in the class.""" 154 return self._json_record.get(ACTSRecordEnums.TestName, None) 155 156 @property 157 def uid(self): 158 """The uid of the test case.""" 159 return self._json_record.get(ACTSRecordEnums.UID, None) 160 161 @property 162 def status(self): 163 """The status of the test case.""" 164 return self._json_record[ACTSRecordEnums.Result] 165 166 @property 167 def start_time(self): 168 """The start time of the test case.""" 169 return self._json_record[ACTSRecordEnums.BeginTime] / 1000.0 170 171 @property 172 def end_time(self): 173 """The end time of the test case.""" 174 return self._json_record[ACTSRecordEnums.EndTime] / 1000.0 175 176 @property 177 def details(self): 178 """Details about the test case.""" 179 return self._json_record.get(ACTSRecordEnums.Details, None) 180 181 @property 182 def extras(self): 183 """Extra info about the test case.""" 184 return self._json_record.get(ACTSRecordEnums.Extras, None) 185 186 @property 187 def extra_errors(self): 188 """Extra errors about the test case.""" 189 return self._json_record.get(ACTSRecordEnums.ExtraErrors, None) 190 191 @property 192 def uuid(self): 193 """The test tracker uuid of the test case.""" 194 extras = self.extras 195 if not extras: 196 return None 197 198 test_tracker_info = self.extras.get('test_tracker_info', None) 199 if not test_tracker_info: 200 return None 201 202 return test_tracker_info.get('test_tracker_uuid', None) 203