blob: b6ef584c16d78f30cf9fb4b720c607a2e2fe91c8 [file] [log] [blame]
Todd Fiala68615ce2015-09-15 21:38:04 +00001"""
2 The LLVM Compiler Infrastructure
3
4This file is distributed under the University of Illinois Open Source
5License. See LICENSE.TXT for details.
6
7Provides classes used by the test results reporting infrastructure
8within the LLDB test suite.
9"""
10
Zachary Turner35d017f2015-10-23 17:04:29 +000011from __future__ import print_function
12
Zachary Turner814236d2015-10-21 17:48:52 +000013import lldb_shared
14
Todd Fiala68615ce2015-09-15 21:38:04 +000015import argparse
Todd Fiala68615ce2015-09-15 21:38:04 +000016import inspect
17import os
Todd Fiala33896a92015-09-18 21:01:13 +000018import pprint
Todd Fiala8effde42015-09-18 16:00:52 +000019import re
Todd Fiala68615ce2015-09-15 21:38:04 +000020import sys
21import threading
22import time
Todd Fialae7e911f2015-09-18 07:08:09 +000023import traceback
Todd Fiala68615ce2015-09-15 21:38:04 +000024import xml.sax.saxutils
25
Zachary Turner58968ac2015-10-23 19:52:36 +000026import six
Zachary Turner814236d2015-10-21 17:48:52 +000027from six.moves import cPickle
28
Todd Fiala68615ce2015-09-15 21:38:04 +000029
30class EventBuilder(object):
31 """Helper class to build test result event dictionaries."""
Todd Fiala33896a92015-09-18 21:01:13 +000032
33 BASE_DICTIONARY = None
34
Todd Fiala68615ce2015-09-15 21:38:04 +000035 @staticmethod
36 def _get_test_name_info(test):
37 """Returns (test-class-name, test-method-name) from a test case instance.
38
39 @param test a unittest.TestCase instance.
40
41 @return tuple containing (test class name, test method name)
42 """
43 test_class_components = test.id().split(".")
44 test_class_name = ".".join(test_class_components[:-1])
45 test_name = test_class_components[-1]
46 return (test_class_name, test_name)
47
48 @staticmethod
Todd Fialae83f1402015-09-18 22:45:31 +000049 def bare_event(event_type):
50 """Creates an event with default additions, event type and timestamp.
51
52 @param event_type the value set for the "event" key, used
53 to distinguish events.
54
55 @returns an event dictionary with all default additions, the "event"
56 key set to the passed in event_type, and the event_time value set to
57 time.time().
58 """
59 if EventBuilder.BASE_DICTIONARY is not None:
60 # Start with a copy of the "always include" entries.
61 event = dict(EventBuilder.BASE_DICTIONARY)
62 else:
63 event = {}
64
65 event.update({
66 "event": event_type,
67 "event_time": time.time()
68 })
69 return event
70
71 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +000072 def _event_dictionary_common(test, event_type):
73 """Returns an event dictionary setup with values for the given event type.
74
75 @param test the unittest.TestCase instance
76
77 @param event_type the name of the event type (string).
78
79 @return event dictionary with common event fields set.
80 """
81 test_class_name, test_name = EventBuilder._get_test_name_info(test)
Todd Fiala33896a92015-09-18 21:01:13 +000082
Todd Fialae83f1402015-09-18 22:45:31 +000083 event = EventBuilder.bare_event(event_type)
84 event.update({
Todd Fiala68615ce2015-09-15 21:38:04 +000085 "test_class": test_class_name,
86 "test_name": test_name,
Todd Fialaf77f8ae2015-09-18 22:57:04 +000087 "test_filename": inspect.getfile(test.__class__)
Todd Fiala33896a92015-09-18 21:01:13 +000088 })
Todd Fialae83f1402015-09-18 22:45:31 +000089 return event
Todd Fiala68615ce2015-09-15 21:38:04 +000090
91 @staticmethod
92 def _error_tuple_class(error_tuple):
93 """Returns the unittest error tuple's error class as a string.
94
95 @param error_tuple the error tuple provided by the test framework.
96
97 @return the error type (typically an exception) raised by the
98 test framework.
99 """
100 type_var = error_tuple[0]
101 module = inspect.getmodule(type_var)
102 if module:
103 return "{}.{}".format(module.__name__, type_var.__name__)
104 else:
105 return type_var.__name__
106
107 @staticmethod
108 def _error_tuple_message(error_tuple):
109 """Returns the unittest error tuple's error message.
110
111 @param error_tuple the error tuple provided by the test framework.
112
113 @return the error message provided by the test framework.
114 """
115 return str(error_tuple[1])
116
117 @staticmethod
Todd Fialae7e911f2015-09-18 07:08:09 +0000118 def _error_tuple_traceback(error_tuple):
119 """Returns the unittest error tuple's error message.
120
121 @param error_tuple the error tuple provided by the test framework.
122
123 @return the error message provided by the test framework.
124 """
125 return error_tuple[2]
126
127 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000128 def _event_dictionary_test_result(test, status):
129 """Returns an event dictionary with common test result fields set.
130
131 @param test a unittest.TestCase instance.
132
133 @param status the status/result of the test
134 (e.g. "success", "failure", etc.)
135
136 @return the event dictionary
137 """
138 event = EventBuilder._event_dictionary_common(test, "test_result")
139 event["status"] = status
140 return event
141
142 @staticmethod
143 def _event_dictionary_issue(test, status, error_tuple):
144 """Returns an event dictionary with common issue-containing test result
145 fields set.
146
147 @param test a unittest.TestCase instance.
148
149 @param status the status/result of the test
150 (e.g. "success", "failure", etc.)
151
152 @param error_tuple the error tuple as reported by the test runner.
153 This is of the form (type<error>, error).
154
155 @return the event dictionary
156 """
157 event = EventBuilder._event_dictionary_test_result(test, status)
158 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
159 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
Todd Fiala33896a92015-09-18 21:01:13 +0000160 backtrace = EventBuilder._error_tuple_traceback(error_tuple)
161 if backtrace is not None:
162 event["issue_backtrace"] = traceback.format_tb(backtrace)
Todd Fiala68615ce2015-09-15 21:38:04 +0000163 return event
164
165 @staticmethod
166 def event_for_start(test):
167 """Returns an event dictionary for the test start event.
168
169 @param test a unittest.TestCase instance.
170
171 @return the event dictionary
172 """
173 return EventBuilder._event_dictionary_common(test, "test_start")
174
175 @staticmethod
176 def event_for_success(test):
177 """Returns an event dictionary for a successful test.
178
179 @param test a unittest.TestCase instance.
180
181 @return the event dictionary
182 """
183 return EventBuilder._event_dictionary_test_result(test, "success")
184
185 @staticmethod
186 def event_for_unexpected_success(test, bugnumber):
187 """Returns an event dictionary for a test that succeeded but was
188 expected to fail.
189
190 @param test a unittest.TestCase instance.
191
192 @param bugnumber the issue identifier for the bug tracking the
193 fix request for the test expected to fail (but is in fact
194 passing here).
195
196 @return the event dictionary
197
198 """
199 event = EventBuilder._event_dictionary_test_result(
200 test, "unexpected_success")
201 if bugnumber:
202 event["bugnumber"] = str(bugnumber)
203 return event
204
205 @staticmethod
206 def event_for_failure(test, error_tuple):
207 """Returns an event dictionary for a test that failed.
208
209 @param test a unittest.TestCase instance.
210
211 @param error_tuple the error tuple as reported by the test runner.
212 This is of the form (type<error>, error).
213
214 @return the event dictionary
215 """
216 return EventBuilder._event_dictionary_issue(
217 test, "failure", error_tuple)
218
219 @staticmethod
220 def event_for_expected_failure(test, error_tuple, bugnumber):
221 """Returns an event dictionary for a test that failed as expected.
222
223 @param test a unittest.TestCase instance.
224
225 @param error_tuple the error tuple as reported by the test runner.
226 This is of the form (type<error>, error).
227
228 @param bugnumber the issue identifier for the bug tracking the
229 fix request for the test expected to fail.
230
231 @return the event dictionary
232
233 """
234 event = EventBuilder._event_dictionary_issue(
235 test, "expected_failure", error_tuple)
236 if bugnumber:
237 event["bugnumber"] = str(bugnumber)
238 return event
239
240 @staticmethod
241 def event_for_skip(test, reason):
242 """Returns an event dictionary for a test that was skipped.
243
244 @param test a unittest.TestCase instance.
245
246 @param reason the reason why the test is being skipped.
247
248 @return the event dictionary
249 """
250 event = EventBuilder._event_dictionary_test_result(test, "skip")
251 event["skip_reason"] = reason
252 return event
253
254 @staticmethod
255 def event_for_error(test, error_tuple):
256 """Returns an event dictionary for a test that hit a test execution error.
257
258 @param test a unittest.TestCase instance.
259
260 @param error_tuple the error tuple as reported by the test runner.
261 This is of the form (type<error>, error).
262
263 @return the event dictionary
264 """
265 return EventBuilder._event_dictionary_issue(test, "error", error_tuple)
266
267 @staticmethod
268 def event_for_cleanup_error(test, error_tuple):
269 """Returns an event dictionary for a test that hit a test execution error
270 during the test cleanup phase.
271
272 @param test a unittest.TestCase instance.
273
274 @param error_tuple the error tuple as reported by the test runner.
275 This is of the form (type<error>, error).
276
277 @return the event dictionary
278 """
279 event = EventBuilder._event_dictionary_issue(
280 test, "error", error_tuple)
281 event["issue_phase"] = "cleanup"
282 return event
283
Todd Fiala33896a92015-09-18 21:01:13 +0000284 @staticmethod
285 def add_entries_to_all_events(entries_dict):
286 """Specifies a dictionary of entries to add to all test events.
287
288 This provides a mechanism for, say, a parallel test runner to
289 indicate to each inferior dotest.py that it should add a
290 worker index to each.
291
292 Calling this method replaces all previous entries added
293 by a prior call to this.
294
295 Event build methods will overwrite any entries that collide.
296 Thus, the passed in dictionary is the base, which gets merged
297 over by event building when keys collide.
298
299 @param entries_dict a dictionary containing key and value
300 pairs that should be merged into all events created by the
301 event generator. May be None to clear out any extra entries.
302 """
303 EventBuilder.BASE_DICTIONARY = dict(entries_dict)
304
Todd Fiala68615ce2015-09-15 21:38:04 +0000305
306class ResultsFormatter(object):
Todd Fiala33896a92015-09-18 21:01:13 +0000307
Todd Fiala68615ce2015-09-15 21:38:04 +0000308 """Provides interface to formatting test results out to a file-like object.
309
310 This class allows the LLDB test framework's raw test-realted
311 events to be processed and formatted in any manner desired.
312 Test events are represented by python dictionaries, formatted
313 as in the EventBuilder class above.
314
315 ResultFormatter instances are given a file-like object in which
316 to write their results.
317
318 ResultFormatter lifetime looks like the following:
319
320 # The result formatter is created.
321 # The argparse options dictionary is generated from calling
322 # the SomeResultFormatter.arg_parser() with the options data
323 # passed to dotest.py via the "--results-formatter-options"
324 # argument. See the help on that for syntactic requirements
325 # on getting that parsed correctly.
326 formatter = SomeResultFormatter(file_like_object, argpared_options_dict)
327
328 # Single call to session start, before parsing any events.
329 formatter.begin_session()
330
Todd Fialae83f1402015-09-18 22:45:31 +0000331 formatter.handle_event({"event":"initialize",...})
332
Todd Fiala68615ce2015-09-15 21:38:04 +0000333 # Zero or more calls specified for events recorded during the test session.
334 # The parallel test runner manages getting results from all the inferior
335 # dotest processes, so from a new format perspective, don't worry about
336 # that. The formatter will be presented with a single stream of events
337 # sandwiched between a single begin_session()/end_session() pair in the
338 # parallel test runner process/thread.
339 for event in zero_or_more_test_events():
Todd Fialae83f1402015-09-18 22:45:31 +0000340 formatter.handle_event(event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000341
Todd Fialae83f1402015-09-18 22:45:31 +0000342 # Single call to terminate/wrap-up. Formatters that need all the
343 # data before they can print a correct result (e.g. xUnit/JUnit),
344 # this is where the final report can be generated.
345 formatter.handle_event({"event":"terminate",...})
Todd Fiala68615ce2015-09-15 21:38:04 +0000346
347 It is not the formatter's responsibility to close the file_like_object.
348 (i.e. do not close it).
349
350 The lldb test framework passes these test events in real time, so they
351 arrive as they come in.
352
353 In the case of the parallel test runner, the dotest inferiors
354 add a 'pid' field to the dictionary that indicates which inferior
355 pid generated the event.
356
357 Note more events may be added in the future to support richer test
358 reporting functionality. One example: creating a true flaky test
359 result category so that unexpected successes really mean the test
360 is marked incorrectly (either should be marked flaky, or is indeed
361 passing consistently now and should have the xfail marker
362 removed). In this case, a flaky_success and flaky_fail event
363 likely will be added to capture these and support reporting things
364 like percentages of flaky test passing so we can see if we're
365 making some things worse/better with regards to failure rates.
366
367 Another example: announcing all the test methods that are planned
368 to be run, so we can better support redo operations of various kinds
369 (redo all non-run tests, redo non-run tests except the one that
370 was running [perhaps crashed], etc.)
371
372 Implementers are expected to override all the public methods
373 provided in this class. See each method's docstring to see
374 expectations about when the call should be chained.
375
376 """
377
378 @classmethod
379 def arg_parser(cls):
380 """@return arg parser used to parse formatter-specific options."""
381 parser = argparse.ArgumentParser(
382 description='{} options'.format(cls.__name__),
383 usage=('dotest.py --results-formatter-options='
384 '"--option1 value1 [--option2 value2 [...]]"'))
385 return parser
386
387 def __init__(self, out_file, options):
388 super(ResultsFormatter, self).__init__()
389 self.out_file = out_file
390 self.options = options
Greg Clayton1827fc22015-09-19 00:39:09 +0000391 self.using_terminal = False
Todd Fiala68615ce2015-09-15 21:38:04 +0000392 if not self.out_file:
393 raise Exception("ResultsFormatter created with no file object")
394 self.start_time_by_test = {}
Todd Fialade9a44e2015-09-22 00:15:50 +0000395 self.terminate_called = False
Todd Fiala68615ce2015-09-15 21:38:04 +0000396
397 # Lock that we use while mutating inner state, like the
398 # total test count and the elements. We minimize how
399 # long we hold the lock just to keep inner state safe, not
400 # entirely consistent from the outside.
401 self.lock = threading.Lock()
402
Todd Fialae83f1402015-09-18 22:45:31 +0000403 def handle_event(self, test_event):
404 """Handles the test event for collection into the formatter output.
Todd Fiala68615ce2015-09-15 21:38:04 +0000405
406 Derived classes may override this but should call down to this
407 implementation first.
408
409 @param test_event the test event as formatted by one of the
410 event_for_* calls.
411 """
Todd Fialade9a44e2015-09-22 00:15:50 +0000412 # Keep track of whether terminate was received. We do this so
413 # that a process can call the 'terminate' event on its own, to
414 # close down a formatter at the appropriate time. Then the
415 # atexit() cleanup can call the "terminate if it hasn't been
416 # called yet".
417 if test_event is not None:
418 if test_event.get("event", "") == "terminate":
419 self.terminate_called = True
Todd Fiala68615ce2015-09-15 21:38:04 +0000420
421 def track_start_time(self, test_class, test_name, start_time):
422 """Tracks the start time of a test so elapsed time can be computed.
423
424 This alleviates the need for test results to be processed serially
425 by test. It will save the start time for the test so that
426 elapsed_time_for_test() can compute the elapsed time properly.
427 """
428 if test_class is None or test_name is None:
429 return
430
431 test_key = "{}.{}".format(test_class, test_name)
432 with self.lock:
433 self.start_time_by_test[test_key] = start_time
434
435 def elapsed_time_for_test(self, test_class, test_name, end_time):
436 """Returns the elapsed time for a test.
437
438 This function can only be called once per test and requires that
439 the track_start_time() method be called sometime prior to calling
440 this method.
441 """
442 if test_class is None or test_name is None:
443 return -2.0
444
445 test_key = "{}.{}".format(test_class, test_name)
446 with self.lock:
447 if test_key not in self.start_time_by_test:
448 return -1.0
449 else:
450 start_time = self.start_time_by_test[test_key]
451 del self.start_time_by_test[test_key]
452 return end_time - start_time
453
Greg Clayton1827fc22015-09-19 00:39:09 +0000454 def is_using_terminal(self):
Todd Fialade9a44e2015-09-22 00:15:50 +0000455 """Returns True if this results formatter is using the terminal and
456 output should be avoided."""
Greg Clayton1827fc22015-09-19 00:39:09 +0000457 return self.using_terminal
Todd Fiala68615ce2015-09-15 21:38:04 +0000458
Todd Fialade9a44e2015-09-22 00:15:50 +0000459 def send_terminate_as_needed(self):
460 """Sends the terminate event if it hasn't been received yet."""
461 if not self.terminate_called:
462 terminate_event = EventBuilder.bare_event("terminate")
463 self.handle_event(terminate_event)
464
Todd Fiala132c2c42015-09-22 06:32:50 +0000465
Todd Fiala68615ce2015-09-15 21:38:04 +0000466class XunitFormatter(ResultsFormatter):
467 """Provides xUnit-style formatted output.
468 """
469
470 # Result mapping arguments
471 RM_IGNORE = 'ignore'
472 RM_SUCCESS = 'success'
473 RM_FAILURE = 'failure'
474 RM_PASSTHRU = 'passthru'
475
476 @staticmethod
Todd Fiala8effde42015-09-18 16:00:52 +0000477 def _build_illegal_xml_regex():
Todd Fiala33896a92015-09-18 21:01:13 +0000478 """Contructs a regex to match all illegal xml characters.
479
480 Expects to be used against a unicode string."""
Todd Fiala8effde42015-09-18 16:00:52 +0000481 # Construct the range pairs of invalid unicode chareacters.
482 illegal_chars_u = [
483 (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
484 (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
485
486 # For wide builds, we have more.
487 if sys.maxunicode >= 0x10000:
488 illegal_chars_u.extend(
489 [(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
490 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
491 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
492 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
493 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
494 (0x10FFFE, 0x10FFFF)])
495
496 # Build up an array of range expressions.
497 illegal_ranges = [
Zachary Turner58968ac2015-10-23 19:52:36 +0000498 "%s-%s" % (six.unichr(low), six.unichr(high))
Todd Fiala8effde42015-09-18 16:00:52 +0000499 for (low, high) in illegal_chars_u]
500
501 # Compile the regex
Zachary Turner58968ac2015-10-23 19:52:36 +0000502 return re.compile(six.u('[%s]') % six.u('').join(illegal_ranges))
Todd Fiala8effde42015-09-18 16:00:52 +0000503
504 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000505 def _quote_attribute(text):
506 """Returns the given text in a manner safe for usage in an XML attribute.
507
508 @param text the text that should appear within an XML attribute.
509 @return the attribute-escaped version of the input text.
510 """
511 return xml.sax.saxutils.quoteattr(text)
512
Todd Fiala8effde42015-09-18 16:00:52 +0000513 def _replace_invalid_xml(self, str_or_unicode):
Todd Fiala33896a92015-09-18 21:01:13 +0000514 """Replaces invalid XML characters with a '?'.
515
516 @param str_or_unicode a string to replace invalid XML
517 characters within. Can be unicode or not. If not unicode,
518 assumes it is a byte string in utf-8 encoding.
519
520 @returns a utf-8-encoded byte string with invalid
521 XML replaced with '?'.
522 """
Todd Fiala8effde42015-09-18 16:00:52 +0000523 # Get the content into unicode
524 if isinstance(str_or_unicode, str):
525 unicode_content = str_or_unicode.decode('utf-8')
526 else:
527 unicode_content = str_or_unicode
Zachary Turner58968ac2015-10-23 19:52:36 +0000528 return self.invalid_xml_re.sub(six.u('?'), unicode_content).encode('utf-8')
Todd Fiala8effde42015-09-18 16:00:52 +0000529
Todd Fiala68615ce2015-09-15 21:38:04 +0000530 @classmethod
531 def arg_parser(cls):
532 """@return arg parser used to parse formatter-specific options."""
533 parser = super(XunitFormatter, cls).arg_parser()
534
535 # These are valid choices for results mapping.
536 results_mapping_choices = [
537 XunitFormatter.RM_IGNORE,
538 XunitFormatter.RM_SUCCESS,
539 XunitFormatter.RM_FAILURE,
540 XunitFormatter.RM_PASSTHRU]
541 parser.add_argument(
Todd Fiala33896a92015-09-18 21:01:13 +0000542 "--assert-on-unknown-events",
543 action="store_true",
544 help=('cause unknown test events to generate '
545 'a python assert. Default is to ignore.'))
546 parser.add_argument(
Todd Fialaea736242015-09-23 15:21:28 +0000547 "--ignore-skip-name",
548 "-n",
549 metavar='PATTERN',
550 action="append",
551 dest='ignore_skip_name_patterns',
552 help=('a python regex pattern, where '
Todd Fiala132c2c42015-09-22 06:32:50 +0000553 'any skipped test with a test method name where regex '
554 'matches (via search) will be ignored for xUnit test '
Todd Fialaea736242015-09-23 15:21:28 +0000555 'result purposes. Can be specified multiple times.'))
Todd Fiala132c2c42015-09-22 06:32:50 +0000556 parser.add_argument(
Todd Fialaea736242015-09-23 15:21:28 +0000557 "--ignore-skip-reason",
558 "-r",
559 metavar='PATTERN',
560 action="append",
561 dest='ignore_skip_reason_patterns',
562 help=('a python regex pattern, where '
Todd Fiala132c2c42015-09-22 06:32:50 +0000563 'any skipped test with a skip reason where the regex '
564 'matches (via search) will be ignored for xUnit test '
Todd Fialaea736242015-09-23 15:21:28 +0000565 'result purposes. Can be specified multiple times.'))
Todd Fiala132c2c42015-09-22 06:32:50 +0000566 parser.add_argument(
Todd Fiala68615ce2015-09-15 21:38:04 +0000567 "--xpass", action="store", choices=results_mapping_choices,
568 default=XunitFormatter.RM_FAILURE,
569 help=('specify mapping from unexpected success to jUnit/xUnit '
570 'result type'))
571 parser.add_argument(
572 "--xfail", action="store", choices=results_mapping_choices,
573 default=XunitFormatter.RM_IGNORE,
574 help=('specify mapping from expected failure to jUnit/xUnit '
575 'result type'))
576 return parser
577
Todd Fiala132c2c42015-09-22 06:32:50 +0000578 @staticmethod
Todd Fialaea736242015-09-23 15:21:28 +0000579 def _build_regex_list_from_patterns(patterns):
Todd Fiala132c2c42015-09-22 06:32:50 +0000580 """Builds a list of compiled regexes from option value.
581
582 @param option string containing a comma-separated list of regex
583 patterns. Zero-length or None will produce an empty regex list.
584
585 @return list of compiled regular expressions, empty if no
586 patterns provided.
587 """
588 regex_list = []
Todd Fialaea736242015-09-23 15:21:28 +0000589 if patterns is not None:
590 for pattern in patterns:
Todd Fiala132c2c42015-09-22 06:32:50 +0000591 regex_list.append(re.compile(pattern))
592 return regex_list
593
Todd Fiala68615ce2015-09-15 21:38:04 +0000594 def __init__(self, out_file, options):
595 """Initializes the XunitFormatter instance.
596 @param out_file file-like object where formatted output is written.
597 @param options_dict specifies a dictionary of options for the
598 formatter.
599 """
600 # Initialize the parent
601 super(XunitFormatter, self).__init__(out_file, options)
602 self.text_encoding = "UTF-8"
Todd Fiala8effde42015-09-18 16:00:52 +0000603 self.invalid_xml_re = XunitFormatter._build_illegal_xml_regex()
Todd Fiala68615ce2015-09-15 21:38:04 +0000604 self.total_test_count = 0
Todd Fiala132c2c42015-09-22 06:32:50 +0000605 self.ignore_skip_name_regexes = (
Todd Fialaea736242015-09-23 15:21:28 +0000606 XunitFormatter._build_regex_list_from_patterns(
607 options.ignore_skip_name_patterns))
Todd Fiala132c2c42015-09-22 06:32:50 +0000608 self.ignore_skip_reason_regexes = (
Todd Fialaea736242015-09-23 15:21:28 +0000609 XunitFormatter._build_regex_list_from_patterns(
610 options.ignore_skip_reason_patterns))
Todd Fiala68615ce2015-09-15 21:38:04 +0000611
612 self.elements = {
613 "successes": [],
614 "errors": [],
615 "failures": [],
616 "skips": [],
617 "unexpected_successes": [],
618 "expected_failures": [],
619 "all": []
620 }
621
622 self.status_handlers = {
623 "success": self._handle_success,
624 "failure": self._handle_failure,
625 "error": self._handle_error,
626 "skip": self._handle_skip,
627 "expected_failure": self._handle_expected_failure,
628 "unexpected_success": self._handle_unexpected_success
629 }
630
Todd Fialae83f1402015-09-18 22:45:31 +0000631 def handle_event(self, test_event):
632 super(XunitFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000633
634 event_type = test_event["event"]
635 if event_type is None:
636 return
637
Todd Fialae83f1402015-09-18 22:45:31 +0000638 if event_type == "terminate":
639 self._finish_output()
640 elif event_type == "test_start":
Todd Fiala68615ce2015-09-15 21:38:04 +0000641 self.track_start_time(
642 test_event["test_class"],
643 test_event["test_name"],
644 test_event["event_time"])
645 elif event_type == "test_result":
646 self._process_test_result(test_event)
647 else:
Todd Fiala33896a92015-09-18 21:01:13 +0000648 # This is an unknown event.
649 if self.options.assert_on_unknown_events:
650 raise Exception("unknown event type {} from {}\n".format(
651 event_type, test_event))
Todd Fiala68615ce2015-09-15 21:38:04 +0000652
653 def _handle_success(self, test_event):
654 """Handles a test success.
655 @param test_event the test event to handle.
656 """
657 result = self._common_add_testcase_entry(test_event)
658 with self.lock:
659 self.elements["successes"].append(result)
660
661 def _handle_failure(self, test_event):
662 """Handles a test failure.
663 @param test_event the test event to handle.
664 """
Todd Fiala8effde42015-09-18 16:00:52 +0000665 message = self._replace_invalid_xml(test_event["issue_message"])
666 backtrace = self._replace_invalid_xml(
667 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000668
Todd Fiala68615ce2015-09-15 21:38:04 +0000669 result = self._common_add_testcase_entry(
670 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000671 inner_content=(
672 '<failure type={} message={}><![CDATA[{}]]></failure>'.format(
673 XunitFormatter._quote_attribute(test_event["issue_class"]),
674 XunitFormatter._quote_attribute(message),
675 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000676 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000677 with self.lock:
678 self.elements["failures"].append(result)
679
680 def _handle_error(self, test_event):
681 """Handles a test error.
682 @param test_event the test event to handle.
683 """
Todd Fiala8effde42015-09-18 16:00:52 +0000684 message = self._replace_invalid_xml(test_event["issue_message"])
685 backtrace = self._replace_invalid_xml(
686 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000687
Todd Fiala68615ce2015-09-15 21:38:04 +0000688 result = self._common_add_testcase_entry(
689 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000690 inner_content=(
691 '<error type={} message={}><![CDATA[{}]]></error>'.format(
692 XunitFormatter._quote_attribute(test_event["issue_class"]),
693 XunitFormatter._quote_attribute(message),
694 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000695 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000696 with self.lock:
697 self.elements["errors"].append(result)
698
Todd Fiala132c2c42015-09-22 06:32:50 +0000699 @staticmethod
700 def _ignore_based_on_regex_list(test_event, test_key, regex_list):
701 """Returns whether to ignore a test event based on patterns.
702
703 @param test_event the test event dictionary to check.
704 @param test_key the key within the dictionary to check.
705 @param regex_list a list of zero or more regexes. May contain
706 zero or more compiled regexes.
707
708 @return True if any o the regex list match based on the
709 re.search() method; false otherwise.
710 """
711 for regex in regex_list:
712 match = regex.search(test_event.get(test_key, ''))
713 if match:
714 return True
715 return False
716
Todd Fiala68615ce2015-09-15 21:38:04 +0000717 def _handle_skip(self, test_event):
718 """Handles a skipped test.
719 @param test_event the test event to handle.
720 """
Todd Fiala132c2c42015-09-22 06:32:50 +0000721
722 # Are we ignoring this test based on test name?
723 if XunitFormatter._ignore_based_on_regex_list(
724 test_event, 'test_name', self.ignore_skip_name_regexes):
725 return
726
727 # Are we ignoring this test based on skip reason?
728 if XunitFormatter._ignore_based_on_regex_list(
729 test_event, 'skip_reason', self.ignore_skip_reason_regexes):
730 return
731
732 # We're not ignoring this test. Process the skip.
Todd Fiala8effde42015-09-18 16:00:52 +0000733 reason = self._replace_invalid_xml(test_event.get("skip_reason", ""))
Todd Fiala68615ce2015-09-15 21:38:04 +0000734 result = self._common_add_testcase_entry(
735 test_event,
736 inner_content='<skipped message={} />'.format(
Todd Fiala8effde42015-09-18 16:00:52 +0000737 XunitFormatter._quote_attribute(reason)))
Todd Fiala68615ce2015-09-15 21:38:04 +0000738 with self.lock:
739 self.elements["skips"].append(result)
740
741 def _handle_expected_failure(self, test_event):
742 """Handles a test that failed as expected.
743 @param test_event the test event to handle.
744 """
745 if self.options.xfail == XunitFormatter.RM_PASSTHRU:
746 # This is not a natively-supported junit/xunit
747 # testcase mode, so it might fail a validating
748 # test results viewer.
749 if "bugnumber" in test_event:
750 bug_id_attribute = 'bug-id={} '.format(
751 XunitFormatter._quote_attribute(test_event["bugnumber"]))
752 else:
753 bug_id_attribute = ''
754
755 result = self._common_add_testcase_entry(
756 test_event,
757 inner_content=(
758 '<expected-failure {}type={} message={} />'.format(
759 bug_id_attribute,
760 XunitFormatter._quote_attribute(
761 test_event["issue_class"]),
762 XunitFormatter._quote_attribute(
763 test_event["issue_message"]))
764 ))
765 with self.lock:
766 self.elements["expected_failures"].append(result)
767 elif self.options.xfail == XunitFormatter.RM_SUCCESS:
768 result = self._common_add_testcase_entry(test_event)
769 with self.lock:
770 self.elements["successes"].append(result)
771 elif self.options.xfail == XunitFormatter.RM_FAILURE:
772 result = self._common_add_testcase_entry(
773 test_event,
774 inner_content='<failure type={} message={} />'.format(
775 XunitFormatter._quote_attribute(test_event["issue_class"]),
776 XunitFormatter._quote_attribute(
777 test_event["issue_message"])))
778 with self.lock:
779 self.elements["failures"].append(result)
780 elif self.options.xfail == XunitFormatter.RM_IGNORE:
781 pass
782 else:
783 raise Exception(
784 "unknown xfail option: {}".format(self.options.xfail))
785
786 def _handle_unexpected_success(self, test_event):
787 """Handles a test that passed but was expected to fail.
788 @param test_event the test event to handle.
789 """
790 if self.options.xpass == XunitFormatter.RM_PASSTHRU:
791 # This is not a natively-supported junit/xunit
792 # testcase mode, so it might fail a validating
793 # test results viewer.
794 result = self._common_add_testcase_entry(
795 test_event,
796 inner_content=("<unexpected-success />"))
797 with self.lock:
798 self.elements["unexpected_successes"].append(result)
799 elif self.options.xpass == XunitFormatter.RM_SUCCESS:
800 # Treat the xpass as a success.
801 result = self._common_add_testcase_entry(test_event)
802 with self.lock:
803 self.elements["successes"].append(result)
804 elif self.options.xpass == XunitFormatter.RM_FAILURE:
805 # Treat the xpass as a failure.
806 if "bugnumber" in test_event:
807 message = "unexpected success (bug_id:{})".format(
808 test_event["bugnumber"])
809 else:
810 message = "unexpected success (bug_id:none)"
811 result = self._common_add_testcase_entry(
812 test_event,
813 inner_content='<failure type={} message={} />'.format(
814 XunitFormatter._quote_attribute("unexpected_success"),
815 XunitFormatter._quote_attribute(message)))
816 with self.lock:
817 self.elements["failures"].append(result)
818 elif self.options.xpass == XunitFormatter.RM_IGNORE:
819 # Ignore the xpass result as far as xUnit reporting goes.
820 pass
821 else:
822 raise Exception("unknown xpass option: {}".format(
823 self.options.xpass))
824
825 def _process_test_result(self, test_event):
826 """Processes the test_event known to be a test result.
827
828 This categorizes the event appropriately and stores the data needed
829 to generate the final xUnit report. This method skips events that
830 cannot be represented in xUnit output.
831 """
832 if "status" not in test_event:
833 raise Exception("test event dictionary missing 'status' key")
834
835 status = test_event["status"]
836 if status not in self.status_handlers:
837 raise Exception("test event status '{}' unsupported".format(
838 status))
839
840 # Call the status handler for the test result.
841 self.status_handlers[status](test_event)
842
843 def _common_add_testcase_entry(self, test_event, inner_content=None):
844 """Registers a testcase result, and returns the text created.
845
846 The caller is expected to manage failure/skip/success counts
847 in some kind of appropriate way. This call simply constructs
848 the XML and appends the returned result to the self.all_results
849 list.
850
851 @param test_event the test event dictionary.
852
853 @param inner_content if specified, gets included in the <testcase>
854 inner section, at the point before stdout and stderr would be
855 included. This is where a <failure/>, <skipped/>, <error/>, etc.
856 could go.
857
858 @return the text of the xml testcase element.
859 """
860
861 # Get elapsed time.
862 test_class = test_event["test_class"]
863 test_name = test_event["test_name"]
864 event_time = test_event["event_time"]
865 time_taken = self.elapsed_time_for_test(
866 test_class, test_name, event_time)
867
868 # Plumb in stdout/stderr once we shift over to only test results.
869 test_stdout = ''
870 test_stderr = ''
871
872 # Formulate the output xml.
873 if not inner_content:
874 inner_content = ""
875 result = (
876 '<testcase classname="{}" name="{}" time="{:.3f}">'
877 '{}{}{}</testcase>'.format(
878 test_class,
879 test_name,
880 time_taken,
881 inner_content,
882 test_stdout,
883 test_stderr))
884
885 # Save the result, update total test count.
886 with self.lock:
887 self.total_test_count += 1
888 self.elements["all"].append(result)
889
890 return result
891
Todd Fialae83f1402015-09-18 22:45:31 +0000892 def _finish_output_no_lock(self):
Todd Fiala68615ce2015-09-15 21:38:04 +0000893 """Flushes out the report of test executions to form valid xml output.
894
895 xUnit output is in XML. The reporting system cannot complete the
896 formatting of the output without knowing when there is no more input.
897 This call addresses notifcation of the completed test run and thus is
898 when we can finish off the report output.
899 """
900
901 # Figure out the counts line for the testsuite. If we have
902 # been counting either unexpected successes or expected
903 # failures, we'll output those in the counts, at the risk of
904 # being invalidated by a validating test results viewer.
905 # These aren't counted by default so they won't show up unless
906 # the user specified a formatter option to include them.
907 xfail_count = len(self.elements["expected_failures"])
908 xpass_count = len(self.elements["unexpected_successes"])
909 if xfail_count > 0 or xpass_count > 0:
910 extra_testsuite_attributes = (
911 ' expected-failures="{}"'
912 ' unexpected-successes="{}"'.format(xfail_count, xpass_count))
913 else:
914 extra_testsuite_attributes = ""
915
916 # Output the header.
917 self.out_file.write(
918 '<?xml version="1.0" encoding="{}"?>\n'
Todd Fiala038bf832015-09-18 01:43:08 +0000919 '<testsuites>'
Todd Fiala68615ce2015-09-15 21:38:04 +0000920 '<testsuite name="{}" tests="{}" errors="{}" failures="{}" '
921 'skip="{}"{}>\n'.format(
922 self.text_encoding,
923 "LLDB test suite",
924 self.total_test_count,
925 len(self.elements["errors"]),
926 len(self.elements["failures"]),
927 len(self.elements["skips"]),
928 extra_testsuite_attributes))
929
930 # Output each of the test result entries.
931 for result in self.elements["all"]:
932 self.out_file.write(result + '\n')
933
934 # Close off the test suite.
Todd Fiala038bf832015-09-18 01:43:08 +0000935 self.out_file.write('</testsuite></testsuites>\n')
Todd Fiala68615ce2015-09-15 21:38:04 +0000936
Todd Fialae83f1402015-09-18 22:45:31 +0000937 def _finish_output(self):
Todd Fiala132c2c42015-09-22 06:32:50 +0000938 """Finish writing output as all incoming events have arrived."""
Todd Fiala68615ce2015-09-15 21:38:04 +0000939 with self.lock:
Todd Fialae83f1402015-09-18 22:45:31 +0000940 self._finish_output_no_lock()
Todd Fiala68615ce2015-09-15 21:38:04 +0000941
942
943class RawPickledFormatter(ResultsFormatter):
944 """Formats events as a pickled stream.
945
946 The parallel test runner has inferiors pickle their results and send them
947 over a socket back to the parallel test. The parallel test runner then
948 aggregates them into the final results formatter (e.g. xUnit).
949 """
950
951 @classmethod
952 def arg_parser(cls):
953 """@return arg parser used to parse formatter-specific options."""
954 parser = super(RawPickledFormatter, cls).arg_parser()
955 return parser
956
957 def __init__(self, out_file, options):
958 super(RawPickledFormatter, self).__init__(out_file, options)
959 self.pid = os.getpid()
960
Todd Fialae83f1402015-09-18 22:45:31 +0000961 def handle_event(self, test_event):
962 super(RawPickledFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000963
Todd Fialae83f1402015-09-18 22:45:31 +0000964 # Convert initialize/terminate events into job_begin/job_end events.
965 event_type = test_event["event"]
966 if event_type is None:
967 return
968
969 if event_type == "initialize":
970 test_event["event"] = "job_begin"
971 elif event_type == "terminate":
972 test_event["event"] = "job_end"
973
974 # Tack on the pid.
975 test_event["pid"] = self.pid
Todd Fiala68615ce2015-09-15 21:38:04 +0000976
Todd Fiala68615ce2015-09-15 21:38:04 +0000977 # Send it as {serialized_length_of_serialized_bytes}#{serialized_bytes}
978 pickled_message = cPickle.dumps(test_event)
979 self.out_file.send(
980 "{}#{}".format(len(pickled_message), pickled_message))
981
Todd Fiala33896a92015-09-18 21:01:13 +0000982
983class DumpFormatter(ResultsFormatter):
984 """Formats events to the file as their raw python dictionary format."""
985
Todd Fialae83f1402015-09-18 22:45:31 +0000986 def handle_event(self, test_event):
987 super(DumpFormatter, self).handle_event(test_event)
Todd Fiala33896a92015-09-18 21:01:13 +0000988 self.out_file.write("\n" + pprint.pformat(test_event) + "\n")