2 The LLVM Compiler Infrastructure
4 This file is distributed under the University of Illinois Open Source
5 License. See LICENSE.TXT for details.
7 Provides a class to build Python test event data structures.
10 from __future__ import print_function
11 from __future__ import absolute_import
21 from . import build_exception
23 class EventBuilder(object):
24 """Helper class to build test result event dictionaries."""
26 BASE_DICTIONARY = None
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"
36 RESULT_TYPES = {TYPE_JOB_RESULT, TYPE_TEST_RESULT}
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"
46 STATUS_ERROR = "error"
47 STATUS_TIMEOUT = "timeout"
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 = {
54 STATUS_EXCEPTIONAL_EXIT,
59 def _get_test_name_info(test):
60 """Returns (test-class-name, test-method-name) from a test case instance.
62 @param test a unittest.TestCase instance.
64 @return tuple containing (test class name, test method name)
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
72 def bare_event(event_type):
73 """Creates an event with default additions, event type and timestamp.
75 @param event_type the value set for the "event" key, used
76 to distinguish events.
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
82 if EventBuilder.BASE_DICTIONARY is not None:
83 # Start with a copy of the "always include" entries.
84 event = dict(EventBuilder.BASE_DICTIONARY)
90 "event_time": time.time()
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("source python filename has unexpected extension: {}".format(test_filename))
102 def _event_dictionary_common(test, event_type):
103 """Returns an event dictionary setup with values for the given event type.
105 @param test the unittest.TestCase instance
107 @param event_type the name of the event type (string).
109 @return event dictionary with common event fields set.
111 test_class_name, test_name = EventBuilder._get_test_name_info(test)
113 # Determine the filename for the test case. If there is an attribute
114 # for it, use it. Otherwise, determine from the TestCase class path.
115 if hasattr(test, "test_filename"):
116 test_filename = EventBuilder._assert_is_python_sourcefile(test.test_filename)
118 test_filename = EventBuilder._assert_is_python_sourcefile(inspect.getsourcefile(test.__class__))
120 event = EventBuilder.bare_event(event_type)
122 "test_class": test_class_name,
123 "test_name": test_name,
124 "test_filename": test_filename
130 def _error_tuple_class(error_tuple):
131 """Returns the unittest error tuple's error class as a string.
133 @param error_tuple the error tuple provided by the test framework.
135 @return the error type (typically an exception) raised by the
138 type_var = error_tuple[0]
139 module = inspect.getmodule(type_var)
141 return "{}.{}".format(module.__name__, type_var.__name__)
143 return type_var.__name__
146 def _error_tuple_message(error_tuple):
147 """Returns the unittest error tuple's error message.
149 @param error_tuple the error tuple provided by the test framework.
151 @return the error message provided by the test framework.
153 return str(error_tuple[1])
156 def _error_tuple_traceback(error_tuple):
157 """Returns the unittest error tuple's error message.
159 @param error_tuple the error tuple provided by the test framework.
161 @return the error message provided by the test framework.
163 return error_tuple[2]
166 def _event_dictionary_test_result(test, status):
167 """Returns an event dictionary with common test result fields set.
169 @param test a unittest.TestCase instance.
171 @param status the status/result of the test
172 (e.g. "success", "failure", etc.)
174 @return the event dictionary
176 event = EventBuilder._event_dictionary_common(
177 test, EventBuilder.TYPE_TEST_RESULT)
178 event["status"] = status
182 def _event_dictionary_issue(test, status, error_tuple):
183 """Returns an event dictionary with common issue-containing test result
186 @param test a unittest.TestCase instance.
188 @param status the status/result of the test
189 (e.g. "success", "failure", etc.)
191 @param error_tuple the error tuple as reported by the test runner.
192 This is of the form (type<error>, error).
194 @return the event dictionary
196 event = EventBuilder._event_dictionary_test_result(test, status)
197 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
198 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
199 backtrace = EventBuilder._error_tuple_traceback(error_tuple)
200 if backtrace is not None:
201 event["issue_backtrace"] = traceback.format_tb(backtrace)
205 def event_for_start(test):
206 """Returns an event dictionary for the test start event.
208 @param test a unittest.TestCase instance.
210 @return the event dictionary
212 return EventBuilder._event_dictionary_common(
213 test, EventBuilder.TYPE_TEST_START)
216 def event_for_success(test):
217 """Returns an event dictionary for a successful test.
219 @param test a unittest.TestCase instance.
221 @return the event dictionary
223 return EventBuilder._event_dictionary_test_result(
224 test, EventBuilder.STATUS_SUCCESS)
227 def event_for_unexpected_success(test, bugnumber):
228 """Returns an event dictionary for a test that succeeded but was
231 @param test a unittest.TestCase instance.
233 @param bugnumber the issue identifier for the bug tracking the
234 fix request for the test expected to fail (but is in fact
237 @return the event dictionary
240 event = EventBuilder._event_dictionary_test_result(
241 test, EventBuilder.STATUS_UNEXPECTED_SUCCESS)
243 event["bugnumber"] = str(bugnumber)
247 def event_for_failure(test, error_tuple):
248 """Returns an event dictionary for a test that failed.
250 @param test a unittest.TestCase instance.
252 @param error_tuple the error tuple as reported by the test runner.
253 This is of the form (type<error>, error).
255 @return the event dictionary
257 return EventBuilder._event_dictionary_issue(
258 test, EventBuilder.STATUS_FAILURE, error_tuple)
261 def event_for_expected_failure(test, error_tuple, bugnumber):
262 """Returns an event dictionary for a test that failed as expected.
264 @param test a unittest.TestCase instance.
266 @param error_tuple the error tuple as reported by the test runner.
267 This is of the form (type<error>, error).
269 @param bugnumber the issue identifier for the bug tracking the
270 fix request for the test expected to fail.
272 @return the event dictionary
275 event = EventBuilder._event_dictionary_issue(
276 test, EventBuilder.STATUS_EXPECTED_FAILURE, error_tuple)
278 event["bugnumber"] = str(bugnumber)
282 def event_for_skip(test, reason):
283 """Returns an event dictionary for a test that was skipped.
285 @param test a unittest.TestCase instance.
287 @param reason the reason why the test is being skipped.
289 @return the event dictionary
291 event = EventBuilder._event_dictionary_test_result(
292 test, EventBuilder.STATUS_SKIP)
293 event["skip_reason"] = reason
297 def event_for_error(test, error_tuple):
298 """Returns an event dictionary for a test that hit a test execution error.
300 @param test a unittest.TestCase instance.
302 @param error_tuple the error tuple as reported by the test runner.
303 This is of the form (type<error>, error).
305 @return the event dictionary
307 event = EventBuilder._event_dictionary_issue(
308 test, EventBuilder.STATUS_ERROR, error_tuple)
309 event["issue_phase"] = "test"
313 def event_for_build_error(test, error_tuple):
314 """Returns an event dictionary for a test that hit a test execution error
315 during the test cleanup phase.
317 @param test a unittest.TestCase instance.
319 @param error_tuple the error tuple as reported by the test runner.
320 This is of the form (type<error>, error).
322 @return the event dictionary
324 event = EventBuilder._event_dictionary_issue(
325 test, EventBuilder.STATUS_ERROR, error_tuple)
326 event["issue_phase"] = "build"
328 build_error = error_tuple[1]
329 event["build_command"] = build_error.command
330 event["build_error"] = build_error.build_error
334 def event_for_cleanup_error(test, error_tuple):
335 """Returns an event dictionary for a test that hit a test execution error
336 during the test cleanup phase.
338 @param test a unittest.TestCase instance.
340 @param error_tuple the error tuple as reported by the test runner.
341 This is of the form (type<error>, error).
343 @return the event dictionary
345 event = EventBuilder._event_dictionary_issue(
346 test, EventBuilder.STATUS_ERROR, error_tuple)
347 event["issue_phase"] = "cleanup"
351 def event_for_job_test_add_error(test_filename, exception, backtrace):
352 event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
353 event["status"] = EventBuilder.STATUS_ERROR
354 if test_filename is not None:
355 event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
356 if exception is not None and "__class__" in dir(exception):
357 event["issue_class"] = exception.__class__
358 event["issue_message"] = exception
359 if backtrace is not None:
360 event["issue_backtrace"] = backtrace
364 def event_for_job_exceptional_exit(
365 pid, worker_index, exception_code, exception_description,
366 test_filename, command_line):
367 """Creates an event for a job (i.e. process) exit due to signal.
369 @param pid the process id for the job that failed
370 @param worker_index optional id for the job queue running the process
371 @param exception_code optional code
372 (e.g. SIGTERM integer signal number)
373 @param exception_description optional string containing symbolic
374 representation of the issue (e.g. "SIGTERM")
375 @param test_filename the path to the test filename that exited
376 in some exceptional way.
377 @param command_line the Popen()-style list provided as the command line
378 for the process that timed out.
380 @return an event dictionary coding the job completion description.
382 event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
383 event["status"] = EventBuilder.STATUS_EXCEPTIONAL_EXIT
386 if worker_index is not None:
387 event["worker_index"] = int(worker_index)
388 if exception_code is not None:
389 event["exception_code"] = exception_code
390 if exception_description is not None:
391 event["exception_description"] = exception_description
392 if test_filename is not None:
393 event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
394 if command_line is not None:
395 event["command_line"] = command_line
399 def event_for_job_timeout(pid, worker_index, test_filename, command_line):
400 """Creates an event for a job (i.e. process) timeout.
402 @param pid the process id for the job that timed out
403 @param worker_index optional id for the job queue running the process
404 @param test_filename the path to the test filename that timed out.
405 @param command_line the Popen-style list provided as the command line
406 for the process that timed out.
408 @return an event dictionary coding the job completion description.
410 event = EventBuilder.bare_event(EventBuilder.TYPE_JOB_RESULT)
411 event["status"] = "timeout"
414 if worker_index is not None:
415 event["worker_index"] = int(worker_index)
416 if test_filename is not None:
417 event["test_filename"] = EventBuilder._assert_is_python_sourcefile(test_filename)
418 if command_line is not None:
419 event["command_line"] = command_line
423 def event_for_mark_test_rerun_eligible(test):
424 """Creates an event that indicates the specified test is explicitly
427 Note there is a mode that will enable test rerun eligibility at the
428 global level. These markings for explicit rerun eligibility are
429 intended for the mode of running where only explicitly re-runnable
430 tests are rerun upon hitting an issue.
432 @param test the TestCase instance to which this pertains.
434 @return an event that specifies the given test as being eligible to
437 event = EventBuilder._event_dictionary_common(
439 EventBuilder.TYPE_MARK_TEST_RERUN_ELIGIBLE)
443 def event_for_mark_test_expected_failure(test):
444 """Creates an event that indicates the specified test is expected
447 @param test the TestCase instance to which this pertains.
449 @return an event that specifies the given test is expected to fail.
451 event = EventBuilder._event_dictionary_common(
453 EventBuilder.TYPE_MARK_TEST_EXPECTED_FAILURE)
457 def add_entries_to_all_events(entries_dict):
458 """Specifies a dictionary of entries to add to all test events.
460 This provides a mechanism for, say, a parallel test runner to
461 indicate to each inferior dotest.py that it should add a
462 worker index to each.
464 Calling this method replaces all previous entries added
465 by a prior call to this.
467 Event build methods will overwrite any entries that collide.
468 Thus, the passed in dictionary is the base, which gets merged
469 over by event building when keys collide.
471 @param entries_dict a dictionary containing key and value
472 pairs that should be merged into all events created by the
473 event generator. May be None to clear out any extra entries.
475 EventBuilder.BASE_DICTIONARY = dict(entries_dict)