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