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 """This module is where all the record definitions and record containers live. 17 """ 18 19 import json 20 import pprint 21 22 from vts.runners.host import signals 23 from vts.runners.host import utils 24 25 26 class TestResultEnums(object): 27 """Enums used for TestResultRecord class. 28 29 Includes the tokens to mark test result with, and the string names for each 30 field in TestResultRecord. 31 """ 32 33 RECORD_NAME = "Test Name" 34 RECORD_CLASS = "Test Class" 35 RECORD_BEGIN_TIME = "Begin Time" 36 RECORD_END_TIME = "End Time" 37 RECORD_RESULT = "Result" 38 RECORD_UID = "UID" 39 RECORD_EXTRAS = "Extras" 40 RECORD_EXTRA_ERRORS = "Extra Errors" 41 RECORD_DETAILS = "Details" 42 TEST_RESULT_PASS = "PASS" 43 TEST_RESULT_FAIL = "FAIL" 44 TEST_RESULT_SKIP = "SKIP" 45 TEST_RESULT_ERROR = "ERROR" 46 47 48 class TestResultRecord(object): 49 """A record that holds the information of a test case execution. 50 51 Attributes: 52 test_name: A string representing the name of the test case. 53 begin_time: Epoch timestamp of when the test case started. 54 end_time: Epoch timestamp of when the test case ended. 55 self.uid: Unique identifier of a test case. 56 self.result: Test result, PASS/FAIL/SKIP. 57 self.extras: User defined extra information of the test result. 58 self.details: A string explaining the details of the test case. 59 """ 60 61 def __init__(self, t_name, t_class=None): 62 self.test_name = t_name 63 self.test_class = t_class 64 self.begin_time = None 65 self.end_time = None 66 self.uid = None 67 self.result = None 68 self.extras = None 69 self.details = None 70 self.extra_errors = {} 71 72 def testBegin(self): 73 """Call this when the test case it records begins execution. 74 75 Sets the begin_time of this record. 76 """ 77 self.begin_time = utils.get_current_epoch_time() 78 79 def _testEnd(self, result, e): 80 """Class internal function to signal the end of a test case execution. 81 82 Args: 83 result: One of the TEST_RESULT enums in TestResultEnums. 84 e: A test termination signal (usually an exception object). It can 85 be any exception instance or of any subclass of 86 vts.runners.host.signals.TestSignal. 87 """ 88 self.end_time = utils.get_current_epoch_time() 89 self.result = result 90 if isinstance(e, signals.TestSignal): 91 self.details = e.details 92 self.extras = e.extras 93 elif e: 94 self.details = str(e) 95 96 def testPass(self, e=None): 97 """To mark the test as passed in this record. 98 99 Args: 100 e: An instance of vts.runners.host.signals.TestPass. 101 """ 102 self._testEnd(TestResultEnums.TEST_RESULT_PASS, e) 103 104 def testFail(self, e=None): 105 """To mark the test as failed in this record. 106 107 Only testFail does instance check because we want "assert xxx" to also 108 fail the test same way assert_true does. 109 110 Args: 111 e: An exception object. It can be an instance of AssertionError or 112 vts.runners.host.base_test.TestFailure. 113 """ 114 self._testEnd(TestResultEnums.TEST_RESULT_FAIL, e) 115 116 def testSkip(self, e=None): 117 """To mark the test as skipped in this record. 118 119 Args: 120 e: An instance of vts.runners.host.signals.TestSkip. 121 """ 122 self._testEnd(TestResultEnums.TEST_RESULT_SKIP, e) 123 124 def testError(self, e=None): 125 """To mark the test as error in this record. 126 127 Args: 128 e: An exception object. 129 """ 130 self._testEnd(TestResultEnums.TEST_RESULT_ERROR, e) 131 132 def addError(self, tag, e): 133 """Add extra error happened during a test mark the test result as 134 ERROR. 135 136 If an error is added the test record, the record's result is equivalent 137 to the case where an uncaught exception happened. 138 139 Args: 140 tag: A string describing where this error came from, e.g. 'on_pass'. 141 e: An exception object. 142 """ 143 self.result = TestResultEnums.TEST_RESULT_ERROR 144 self.extra_errors[tag] = str(e) 145 146 def __str__(self): 147 d = self.getDict() 148 l = ["%s = %s" % (k, v) for k, v in d.items()] 149 s = ', '.join(l) 150 return s 151 152 def __repr__(self): 153 """This returns a short string representation of the test record.""" 154 t = utils.epoch_to_human_time(self.begin_time) 155 return "%s %s %s" % (t, self.test_name, self.result) 156 157 def getDict(self): 158 """Gets a dictionary representating the content of this class. 159 160 Returns: 161 A dictionary representating the content of this class. 162 """ 163 d = {} 164 d[TestResultEnums.RECORD_NAME] = self.test_name 165 d[TestResultEnums.RECORD_CLASS] = self.test_class 166 d[TestResultEnums.RECORD_BEGIN_TIME] = self.begin_time 167 d[TestResultEnums.RECORD_END_TIME] = self.end_time 168 d[TestResultEnums.RECORD_RESULT] = self.result 169 d[TestResultEnums.RECORD_UID] = self.uid 170 d[TestResultEnums.RECORD_EXTRAS] = self.extras 171 d[TestResultEnums.RECORD_DETAILS] = self.details 172 d[TestResultEnums.RECORD_EXTRA_ERRORS] = self.extra_errors 173 return d 174 175 def jsonString(self): 176 """Converts this test record to a string in json format. 177 178 Format of the json string is: 179 { 180 'Test Name': <test name>, 181 'Begin Time': <epoch timestamp>, 182 'Details': <details>, 183 ... 184 } 185 186 Returns: 187 A json-format string representing the test record. 188 """ 189 return json.dumps(self.getDict()) 190 191 192 class TestResult(object): 193 """A class that contains metrics of a test run. 194 195 This class is essentially a container of TestResultRecord objects. 196 197 Attributes: 198 self.requested: A list of strings, each is the name of a test requested 199 by user. 200 self.failed: A list of records for tests failed. 201 self.executed: A list of records for tests that were actually executed. 202 self.passed: A list of records for tests passed. 203 self.skipped: A list of records for tests skipped. 204 self.error: A list of records for tests with error result token. 205 """ 206 207 def __init__(self): 208 self.requested = [] 209 self.failed = [] 210 self.executed = [] 211 self.passed = [] 212 self.skipped = [] 213 self.error = [] 214 215 def __add__(self, r): 216 """Overrides '+' operator for TestResult class. 217 218 The add operator merges two TestResult objects by concatenating all of 219 their lists together. 220 221 Args: 222 r: another instance of TestResult to be added 223 224 Returns: 225 A TestResult instance that's the sum of two TestResult instances. 226 """ 227 if not isinstance(r, TestResult): 228 raise TypeError("Operand %s of type %s is not a TestResult." % 229 (r, type(r))) 230 sum_result = TestResult() 231 for name in sum_result.__dict__: 232 l_value = list(getattr(self, name)) 233 r_value = list(getattr(r, name)) 234 setattr(sum_result, name, l_value + r_value) 235 return sum_result 236 237 def addRecord(self, record): 238 """Adds a test record to test result. 239 240 A record is considered executed once it's added to the test result. 241 242 Args: 243 record: A test record object to add. 244 """ 245 self.executed.append(record) 246 if record.result == TestResultEnums.TEST_RESULT_FAIL: 247 self.failed.append(record) 248 elif record.result == TestResultEnums.TEST_RESULT_SKIP: 249 self.skipped.append(record) 250 elif record.result == TestResultEnums.TEST_RESULT_PASS: 251 self.passed.append(record) 252 else: 253 self.error.append(record) 254 255 def failClass(self, class_name, e): 256 """Add a record to indicate a test class setup has failed and no test 257 in the class was executed. 258 259 Args: 260 class_name: A string that is the name of the failed test class. 261 e: An exception object. 262 """ 263 record = TestResultRecord("setup_class", class_name) 264 record.testBegin() 265 record.testFail(e) 266 self.executed.append(record) 267 self.failed.append(record) 268 269 def skipClass(self, class_name, reason): 270 """Add a record to indicate all test cases in the class are skipped. 271 272 Args: 273 class_name: A string that is the name of the skipped test class. 274 reason: A string that is the reason for skipping. 275 """ 276 record = TestResultRecord("unknown", class_name) 277 record.testBegin() 278 record.testSkip(signals.TestSkip(reason)) 279 self.executed.append(record) 280 self.skipped.append(record) 281 282 def jsonString(self): 283 """Converts this test result to a string in json format. 284 285 Format of the json string is: 286 { 287 "Results": [ 288 {<executed test record 1>}, 289 {<executed test record 2>}, 290 ... 291 ], 292 "Summary": <summary dict> 293 } 294 295 Returns: 296 A json-format string representing the test results. 297 """ 298 d = {} 299 executed = [record.getDict() for record in self.executed] 300 d["Results"] = executed 301 d["Summary"] = self.summaryDict() 302 jsonString = json.dumps(d, indent=4, sort_keys=True) 303 return jsonString 304 305 def summary(self): 306 """Gets a string that summarizes the stats of this test result. 307 308 The summary rovides the counts of how many test cases fall into each 309 category, like "Passed", "Failed" etc. 310 311 Format of the string is: 312 Requested <int>, Executed <int>, ... 313 314 Returns: 315 A summary string of this test result. 316 """ 317 l = ["%s %d" % (k, v) for k, v in self.summaryDict().items()] 318 # Sort the list so the order is the same every time. 319 msg = ", ".join(sorted(l)) 320 return msg 321 322 def summaryDict(self): 323 """Gets a dictionary that summarizes the stats of this test result. 324 325 The summary rovides the counts of how many test cases fall into each 326 category, like "Passed", "Failed" etc. 327 328 Returns: 329 A dictionary with the stats of this test result. 330 """ 331 d = {} 332 d["Requested"] = len(self.requested) 333 d["Executed"] = len(self.executed) 334 d["Passed"] = len(self.passed) 335 d["Failed"] = len(self.failed) 336 d["Skipped"] = len(self.skipped) 337 d["Error"] = len(self.error) 338 return d 339