1""" 2Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 3See https://llvm.org/LICENSE.txt for license information. 4SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 5 6Provides a class to build Python test event data structures. 7""" 8 9from __future__ import print_function 10from __future__ import absolute_import 11 12# System modules 13import inspect 14import time 15import traceback 16 17# Third-party modules 18 19# LLDB modules 20from . import build_exception 21 22 23class EventBuilder(object): 24 """Helper class to build test result event dictionaries.""" 25 26 BASE_DICTIONARY = None 27 28 # Test Event Types 29 TYPE_JOB_RESULT = "job_result" 30 TYPE_TEST_RESULT = "test_result" 31 TYPE_TEST_START = "test_start" 32 TYPE_MARK_TEST_RERUN_ELIGIBLE = "test_eligible_for_rerun" 33 TYPE_MARK_TEST_EXPECTED_FAILURE = "test_expected_failure" 34 TYPE_SESSION_TERMINATE = "terminate" 35 36 RESULT_TYPES = {TYPE_JOB_RESULT, TYPE_TEST_RESULT} 37 38 # Test/Job Status Tags 39 STATUS_EXCEPTIONAL_EXIT = "exceptional_exit" 40 STATUS_SUCCESS = "success" 41 STATUS_FAILURE = "failure" 42 STATUS_EXPECTED_FAILURE = "expected_failure" 43 STATUS_EXPECTED_TIMEOUT = "expected_timeout" 44 STATUS_UNEXPECTED_SUCCESS = "unexpected_success" 45 STATUS_SKIP = "skip" 46 STATUS_ERROR = "error" 47 STATUS_TIMEOUT = "timeout" 48 49 """Test methods or jobs with a status matching any of these 50 status values will cause a testrun failure, unless 51 the test methods rerun and do not trigger an issue when rerun.""" 52 TESTRUN_ERROR_STATUS_VALUES = { 53 STATUS_ERROR, 54 STATUS_EXCEPTIONAL_EXIT, 55 STATUS_FAILURE, 56 STATUS_TIMEOUT} 57 58 @staticmethod 59 def _get_test_name_info(test): 60 """Returns (test-class-name, test-method-name) from a test case instance. 61 62 @param test a unittest.TestCase instance. 63 64 @return tuple containing (test class name, test method name) 65 """ 66 test_class_components = test.id().split(".") 67 test_class_name = ".".join(test_class_components[:-1]) 68 test_name = test_class_components[-1] 69 return test_class_name, test_name 70 71 @staticmethod 72 def bare_event(event_type): 73 """Creates an event with default additions, event type and timestamp. 74 75 @param event_type the value set for the "event" key, used 76 to distinguish events. 77 78 @returns an event dictionary with all default additions, the "event" 79 key set to the passed in event_type, and the event_time value set to 80 time.time(). 81 """ 82 if EventBuilder.BASE_DICTIONARY is not None: 83 # Start with a copy of the "always include" entries. 84 event = dict(EventBuilder.BASE_DICTIONARY) 85 else: 86 event = {} 87 88 event.update({ 89 "event": event_type, 90 "event_time": time.time() 91 }) 92 return event 93 94 @staticmethod 95 def _assert_is_python_sourcefile(test_filename): 96 if test_filename is not None: 97 if not test_filename.endswith(".py"): 98 raise Exception( 99 "source python filename has unexpected extension: {}".format(test_filename)) 100 return test_filename 101 102 @staticmethod 103 def _event_dictionary_common(test, event_type): 104 """Returns an event dictionary setup with values for the given event type. 105 106 @param test the unittest.TestCase instance 107 108 @param event_type the name of the event type (string). 109 110 @return event dictionary with common event fields set. 111 """ 112 test_class_name, test_name = EventBuilder._get_test_name_info(test) 113 114 # Determine the filename for the test case. If there is an attribute 115 # for it, use it. Otherwise, determine from the TestCase class path. 116 if hasattr(test, "test_filename"): 117 test_filename = EventBuilder._assert_is_python_sourcefile( 118 test.test_filename) 119 else: 120 test_filename = EventBuilder._assert_is_python_sourcefile( 121 inspect.getsourcefile(test.__class__)) 122 123 event = EventBuilder.bare_event(event_type) 124 event.update({ 125 "test_class": test_class_name, 126 "test_name": test_name, 127 "test_filename": test_filename 128 }) 129 130 return event 131 132 @staticmethod 133 def _error_tuple_class(error_tuple): 134 """Returns the unittest error tuple's error class as a string. 135 136 @param error_tuple the error tuple provided by the test framework. 137 138 @return the error type (typically an exception) raised by the 139 test framework. 140 """ 141 type_var = error_tuple[0] 142 module = inspect.getmodule(type_var) 143 if module: 144 return "{}.{}".format(module.__name__, type_var.__name__) 145 else: 146 return type_var.__name__ 147 148 @staticmethod 149 def _error_tuple_message(error_tuple): 150 """Returns the unittest error tuple's error message. 151 152 @param error_tuple the error tuple provided by the test framework. 153 154 @return the error message provided by the test framework. 155 """ 156 return str(error_tuple[1]) 157 158 @staticmethod 159 def _error_tuple_traceback(error_tuple): 160 """Returns the unittest error tuple's error message. 161 162 @param error_tuple the error tuple provided by the test framework. 163 164 @return the error message provided by the test framework. 165 """ 166 return error_tuple[2] 167 168 @staticmethod 169 def _event_dictionary_test_result(test, status): 170 """Returns an event dictionary with common test result fields set. 171 172 @param test a unittest.TestCase instance. 173 174 @param status the status/result of the test 175 (e.g. "success", "failure", etc.) 176 177 @return the event dictionary 178 """ 179 event = EventBuilder._event_dictionary_common( 180 test, EventBuilder.TYPE_TEST_RESULT) 181 event["status"] = status 182 return event 183 184 @staticmethod 185 def _event_dictionary_issue(test, status, error_tuple): 186 """Returns an event dictionary with common issue-containing test result 187 fields set. 188 189 @param test a unittest.TestCase instance. 190 191 @param status the status/result of the test 192 (e.g. "success", "failure", etc.) 193 194 @param error_tuple the error tuple as reported by the test runner. 195 This is of the form (type<error>, error). 196 197 @return the event dictionary 198 """ 199 event = EventBuilder._event_dictionary_test_result(test, status) 200 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple) 201 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple) 202 backtrace = EventBuilder._error_tuple_traceback(error_tuple) 203 if backtrace is not None: 204 event["issue_backtrace"] = traceback.format_tb(backtrace) 205 return event 206 207 @staticmethod 208 def event_for_start(test): 209 """Returns an event dictionary for the test start event. 210 211 @param test a unittest.TestCase instance. 212 213 @return the event dictionary 214 """ 215 return EventBuilder._event_dictionary_common( 216 test, EventBuilder.TYPE_TEST_START) 217 218 @staticmethod 219 def event_for_success(test): 220 """Returns an event dictionary for a successful test. 221 222 @param test a unittest.TestCase instance. 223 224 @return the event dictionary 225 """ 226 return EventBuilder._event_dictionary_test_result( 227 test, EventBuilder.STATUS_SUCCESS) 228 229 @staticmethod 230 def event_for_unexpected_success(test, bugnumber): 231 """Returns an event dictionary for a test that succeeded but was 232 expected to fail. 233 234 @param test a unittest.TestCase instance. 235 236 @param bugnumber the issue identifier for the bug tracking the 237 fix request for the test expected to fail (but is in fact 238 passing here). 239 240 @return the event dictionary 241 242 """ 243 event = EventBuilder._event_dictionary_test_result( 244 test, EventBuilder.STATUS_UNEXPECTED_SUCCESS) 245 if bugnumber: 246 event["bugnumber"] = str(bugnumber) 247 return event 248 249 @staticmethod 250 def event_for_failure(test, error_tuple): 251 """Returns an event dictionary for a test that failed. 252 253 @param test a unittest.TestCase instance. 254 255 @param error_tuple the error tuple as reported by the test runner. 256 This is of the form (type<error>, error). 257 258 @return the event dictionary 259 """ 260 return EventBuilder._event_dictionary_issue( 261 test, EventBuilder.STATUS_FAILURE, error_tuple) 262 263 @staticmethod 264 def event_for_expected_failure(test, error_tuple, bugnumber): 265 """Returns an event dictionary for a test that failed as expected. 266 267 @param test a unittest.TestCase instance. 268 269 @param error_tuple the error tuple as reported by the test runner. 270 This is of the form (type<error>, error). 271 272 @param bugnumber the issue identifier for the bug tracking the 273 fix request for the test expected to fail. 274 275 @return the event dictionary 276 277 """ 278 event = EventBuilder._event_dictionary_issue( 279 test, EventBuilder.STATUS_EXPECTED_FAILURE, error_tuple) 280 if bugnumber: 281 event["bugnumber"] = str(bugnumber) 282 return event 283 284 @staticmethod 285 def event_for_skip(test, reason): 286 """Returns an event dictionary for a test that was skipped. 287 288 @param test a unittest.TestCase instance. 289 290 @param reason the reason why the test is being skipped. 291 292 @return the event dictionary 293 """ 294 event = EventBuilder._event_dictionary_test_result( 295 test, EventBuilder.STATUS_SKIP) 296 event["skip_reason"] = reason 297 return event 298 299 @staticmethod 300 def event_for_error(test, error_tuple): 301 """Returns an event dictionary for a test that hit a test execution error. 302 303 @param test a unittest.TestCase instance. 304 305 @param error_tuple the error tuple as reported by the test runner. 306 This is of the form (type<error>, error). 307 308 @return the event dictionary 309 """ 310 event = EventBuilder._event_dictionary_issue( 311 test, EventBuilder.STATUS_ERROR, error_tuple) 312 event["issue_phase"] = "test" 313 return event 314 315 @staticmethod 316 def event_for_build_error(test, error_tuple): 317 """Returns an event dictionary for a test that hit a test execution error 318 during the test cleanup phase. 319 320 @param test a unittest.TestCase instance. 321 322 @param error_tuple the error tuple as reported by the test runner. 323 This is of the form (type<error>, error). 324 325 @return the event dictionary 326 """ 327 event = EventBuilder._event_dictionary_issue( 328 test, EventBuilder.STATUS_ERROR, error_tuple) 329 event["issue_phase"] = "build" 330 331 build_error = error_tuple[1] 332 event["build_command"] = build_error.command 333 event["build_error"] = build_error.build_error 334 return event 335 336 @staticmethod 337 def event_for_cleanup_error(test, error_tuple): 338 """Returns an event dictionary for a test that hit a test execution error 339 during the test cleanup phase. 340 341 @param test a unittest.TestCase instance. 342 343 @param error_tuple the error tuple as reported by the test runner. 344 This is of the form (type<error>, error). 345 346 @return the event dictionary 347 """ 348 event = EventBuilder._event_dictionary_issue( 349 test, EventBuilder.STATUS_ERROR, error_tuple) 350 event["issue_phase"] = "cleanup" 351 return event 352 353 @staticmethod 354 def event_for_job_test_add_error(test_filename, exception, backtrace): 355 event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT) 356 event["status"] = EventBuilder.STATUS_ERROR 357 if test_filename is not None: 358 event["test_filename"] = EventBuilder._assert_is_python_sourcefile( 359 test_filename) 360 if exception is not None and "__class__" in dir(exception): 361 event["issue_class"] = exception.__class__ 362 event["issue_message"] = exception 363 if backtrace is not None: 364 event["issue_backtrace"] = backtrace 365 return event 366 367 @staticmethod 368 def event_for_job_exceptional_exit( 369 pid, worker_index, exception_code, exception_description, 370 test_filename, command_line): 371 """Creates an event for a job (i.e. process) exit due to signal. 372 373 @param pid the process id for the job that failed 374 @param worker_index optional id for the job queue running the process 375 @param exception_code optional code 376 (e.g. SIGTERM integer signal number) 377 @param exception_description optional string containing symbolic 378 representation of the issue (e.g. "SIGTERM") 379 @param test_filename the path to the test filename that exited 380 in some exceptional way. 381 @param command_line the Popen()-style list provided as the command line 382 for the process that timed out. 383 384 @return an event dictionary coding the job completion description. 385 """ 386 event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT) 387 event["status"] = EventBuilder.STATUS_EXCEPTIONAL_EXIT 388 if pid is not None: 389 event["pid"] = pid 390 if worker_index is not None: 391 event["worker_index"] = int(worker_index) 392 if exception_code is not None: 393 event["exception_code"] = exception_code 394 if exception_description is not None: 395 event["exception_description"] = exception_description 396 if test_filename is not None: 397 event["test_filename"] = EventBuilder._assert_is_python_sourcefile( 398 test_filename) 399 if command_line is not None: 400 event["command_line"] = command_line 401 return event 402 403 @staticmethod 404 def event_for_job_timeout(pid, worker_index, test_filename, command_line): 405 """Creates an event for a job (i.e. process) timeout. 406 407 @param pid the process id for the job that timed out 408 @param worker_index optional id for the job queue running the process 409 @param test_filename the path to the test filename that timed out. 410 @param command_line the Popen-style list provided as the command line 411 for the process that timed out. 412 413 @return an event dictionary coding the job completion description. 414 """ 415 event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT) 416 event["status"] = "timeout" 417 if pid is not None: 418 event["pid"] = pid 419 if worker_index is not None: 420 event["worker_index"] = int(worker_index) 421 if test_filename is not None: 422 event["test_filename"] = EventBuilder._assert_is_python_sourcefile( 423 test_filename) 424 if command_line is not None: 425 event["command_line"] = command_line 426 return event 427 428 @staticmethod 429 def event_for_mark_test_rerun_eligible(test): 430 """Creates an event that indicates the specified test is explicitly 431 eligible for rerun. 432 433 Note there is a mode that will enable test rerun eligibility at the 434 global level. These markings for explicit rerun eligibility are 435 intended for the mode of running where only explicitly re-runnable 436 tests are rerun upon hitting an issue. 437 438 @param test the TestCase instance to which this pertains. 439 440 @return an event that specifies the given test as being eligible to 441 be rerun. 442 """ 443 event = EventBuilder._event_dictionary_common( 444 test, 445 EventBuilder.TYPE_MARK_TEST_RERUN_ELIGIBLE) 446 return event 447 448 @staticmethod 449 def event_for_mark_test_expected_failure(test): 450 """Creates an event that indicates the specified test is expected 451 to fail. 452 453 @param test the TestCase instance to which this pertains. 454 455 @return an event that specifies the given test is expected to fail. 456 """ 457 event = EventBuilder._event_dictionary_common( 458 test, 459 EventBuilder.TYPE_MARK_TEST_EXPECTED_FAILURE) 460 return event 461 462 @staticmethod 463 def add_entries_to_all_events(entries_dict): 464 """Specifies a dictionary of entries to add to all test events. 465 466 This provides a mechanism for, say, a parallel test runner to 467 indicate to each inferior dotest.py that it should add a 468 worker index to each. 469 470 Calling this method replaces all previous entries added 471 by a prior call to this. 472 473 Event build methods will overwrite any entries that collide. 474 Thus, the passed in dictionary is the base, which gets merged 475 over by event building when keys collide. 476 477 @param entries_dict a dictionary containing key and value 478 pairs that should be merged into all events created by the 479 event generator. May be None to clear out any extra entries. 480 """ 481 EventBuilder.BASE_DICTIONARY = dict(entries_dict) 482