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