blob: 52787e91f4da09933f1673b4f3439a8fd71b5c78 [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
11import argparse
12import cPickle
13import inspect
14import os
Todd Fiala33896a92015-09-18 21:01:13 +000015import pprint
Todd Fiala8effde42015-09-18 16:00:52 +000016import re
Todd Fiala68615ce2015-09-15 21:38:04 +000017import sys
18import threading
19import time
Todd Fialae7e911f2015-09-18 07:08:09 +000020import traceback
Todd Fiala68615ce2015-09-15 21:38:04 +000021import xml.sax.saxutils
22
23
24class EventBuilder(object):
25 """Helper class to build test result event dictionaries."""
Todd Fiala33896a92015-09-18 21:01:13 +000026
27 BASE_DICTIONARY = None
28
Todd Fiala68615ce2015-09-15 21:38:04 +000029 @staticmethod
30 def _get_test_name_info(test):
31 """Returns (test-class-name, test-method-name) from a test case instance.
32
33 @param test a unittest.TestCase instance.
34
35 @return tuple containing (test class name, test method name)
36 """
37 test_class_components = test.id().split(".")
38 test_class_name = ".".join(test_class_components[:-1])
39 test_name = test_class_components[-1]
40 return (test_class_name, test_name)
41
42 @staticmethod
Todd Fialae83f1402015-09-18 22:45:31 +000043 def bare_event(event_type):
44 """Creates an event with default additions, event type and timestamp.
45
46 @param event_type the value set for the "event" key, used
47 to distinguish events.
48
49 @returns an event dictionary with all default additions, the "event"
50 key set to the passed in event_type, and the event_time value set to
51 time.time().
52 """
53 if EventBuilder.BASE_DICTIONARY is not None:
54 # Start with a copy of the "always include" entries.
55 event = dict(EventBuilder.BASE_DICTIONARY)
56 else:
57 event = {}
58
59 event.update({
60 "event": event_type,
61 "event_time": time.time()
62 })
63 return event
64
65 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +000066 def _event_dictionary_common(test, event_type):
67 """Returns an event dictionary setup with values for the given event type.
68
69 @param test the unittest.TestCase instance
70
71 @param event_type the name of the event type (string).
72
73 @return event dictionary with common event fields set.
74 """
75 test_class_name, test_name = EventBuilder._get_test_name_info(test)
Todd Fiala33896a92015-09-18 21:01:13 +000076
Todd Fialae83f1402015-09-18 22:45:31 +000077 event = EventBuilder.bare_event(event_type)
78 event.update({
Todd Fiala68615ce2015-09-15 21:38:04 +000079 "test_class": test_class_name,
80 "test_name": test_name,
Todd Fiala33896a92015-09-18 21:01:13 +000081 })
Todd Fialae83f1402015-09-18 22:45:31 +000082 return event
Todd Fiala68615ce2015-09-15 21:38:04 +000083
84 @staticmethod
85 def _error_tuple_class(error_tuple):
86 """Returns the unittest error tuple's error class as a string.
87
88 @param error_tuple the error tuple provided by the test framework.
89
90 @return the error type (typically an exception) raised by the
91 test framework.
92 """
93 type_var = error_tuple[0]
94 module = inspect.getmodule(type_var)
95 if module:
96 return "{}.{}".format(module.__name__, type_var.__name__)
97 else:
98 return type_var.__name__
99
100 @staticmethod
101 def _error_tuple_message(error_tuple):
102 """Returns the unittest error tuple's error message.
103
104 @param error_tuple the error tuple provided by the test framework.
105
106 @return the error message provided by the test framework.
107 """
108 return str(error_tuple[1])
109
110 @staticmethod
Todd Fialae7e911f2015-09-18 07:08:09 +0000111 def _error_tuple_traceback(error_tuple):
112 """Returns the unittest error tuple's error message.
113
114 @param error_tuple the error tuple provided by the test framework.
115
116 @return the error message provided by the test framework.
117 """
118 return error_tuple[2]
119
120 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000121 def _event_dictionary_test_result(test, status):
122 """Returns an event dictionary with common test result fields set.
123
124 @param test a unittest.TestCase instance.
125
126 @param status the status/result of the test
127 (e.g. "success", "failure", etc.)
128
129 @return the event dictionary
130 """
131 event = EventBuilder._event_dictionary_common(test, "test_result")
132 event["status"] = status
133 return event
134
135 @staticmethod
136 def _event_dictionary_issue(test, status, error_tuple):
137 """Returns an event dictionary with common issue-containing test result
138 fields set.
139
140 @param test a unittest.TestCase instance.
141
142 @param status the status/result of the test
143 (e.g. "success", "failure", etc.)
144
145 @param error_tuple the error tuple as reported by the test runner.
146 This is of the form (type<error>, error).
147
148 @return the event dictionary
149 """
150 event = EventBuilder._event_dictionary_test_result(test, status)
151 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
152 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
Todd Fiala33896a92015-09-18 21:01:13 +0000153 backtrace = EventBuilder._error_tuple_traceback(error_tuple)
154 if backtrace is not None:
155 event["issue_backtrace"] = traceback.format_tb(backtrace)
Todd Fiala68615ce2015-09-15 21:38:04 +0000156 return event
157
158 @staticmethod
159 def event_for_start(test):
160 """Returns an event dictionary for the test start event.
161
162 @param test a unittest.TestCase instance.
163
164 @return the event dictionary
165 """
166 return EventBuilder._event_dictionary_common(test, "test_start")
167
168 @staticmethod
169 def event_for_success(test):
170 """Returns an event dictionary for a successful test.
171
172 @param test a unittest.TestCase instance.
173
174 @return the event dictionary
175 """
176 return EventBuilder._event_dictionary_test_result(test, "success")
177
178 @staticmethod
179 def event_for_unexpected_success(test, bugnumber):
180 """Returns an event dictionary for a test that succeeded but was
181 expected to fail.
182
183 @param test a unittest.TestCase instance.
184
185 @param bugnumber the issue identifier for the bug tracking the
186 fix request for the test expected to fail (but is in fact
187 passing here).
188
189 @return the event dictionary
190
191 """
192 event = EventBuilder._event_dictionary_test_result(
193 test, "unexpected_success")
194 if bugnumber:
195 event["bugnumber"] = str(bugnumber)
196 return event
197
198 @staticmethod
199 def event_for_failure(test, error_tuple):
200 """Returns an event dictionary for a test that failed.
201
202 @param test a unittest.TestCase instance.
203
204 @param error_tuple the error tuple as reported by the test runner.
205 This is of the form (type<error>, error).
206
207 @return the event dictionary
208 """
209 return EventBuilder._event_dictionary_issue(
210 test, "failure", error_tuple)
211
212 @staticmethod
213 def event_for_expected_failure(test, error_tuple, bugnumber):
214 """Returns an event dictionary for a test that failed as expected.
215
216 @param test a unittest.TestCase instance.
217
218 @param error_tuple the error tuple as reported by the test runner.
219 This is of the form (type<error>, error).
220
221 @param bugnumber the issue identifier for the bug tracking the
222 fix request for the test expected to fail.
223
224 @return the event dictionary
225
226 """
227 event = EventBuilder._event_dictionary_issue(
228 test, "expected_failure", error_tuple)
229 if bugnumber:
230 event["bugnumber"] = str(bugnumber)
231 return event
232
233 @staticmethod
234 def event_for_skip(test, reason):
235 """Returns an event dictionary for a test that was skipped.
236
237 @param test a unittest.TestCase instance.
238
239 @param reason the reason why the test is being skipped.
240
241 @return the event dictionary
242 """
243 event = EventBuilder._event_dictionary_test_result(test, "skip")
244 event["skip_reason"] = reason
245 return event
246
247 @staticmethod
248 def event_for_error(test, error_tuple):
249 """Returns an event dictionary for a test that hit a test execution error.
250
251 @param test a unittest.TestCase instance.
252
253 @param error_tuple the error tuple as reported by the test runner.
254 This is of the form (type<error>, error).
255
256 @return the event dictionary
257 """
258 return EventBuilder._event_dictionary_issue(test, "error", error_tuple)
259
260 @staticmethod
261 def event_for_cleanup_error(test, error_tuple):
262 """Returns an event dictionary for a test that hit a test execution error
263 during the test cleanup phase.
264
265 @param test a unittest.TestCase instance.
266
267 @param error_tuple the error tuple as reported by the test runner.
268 This is of the form (type<error>, error).
269
270 @return the event dictionary
271 """
272 event = EventBuilder._event_dictionary_issue(
273 test, "error", error_tuple)
274 event["issue_phase"] = "cleanup"
275 return event
276
Todd Fiala33896a92015-09-18 21:01:13 +0000277 @staticmethod
278 def add_entries_to_all_events(entries_dict):
279 """Specifies a dictionary of entries to add to all test events.
280
281 This provides a mechanism for, say, a parallel test runner to
282 indicate to each inferior dotest.py that it should add a
283 worker index to each.
284
285 Calling this method replaces all previous entries added
286 by a prior call to this.
287
288 Event build methods will overwrite any entries that collide.
289 Thus, the passed in dictionary is the base, which gets merged
290 over by event building when keys collide.
291
292 @param entries_dict a dictionary containing key and value
293 pairs that should be merged into all events created by the
294 event generator. May be None to clear out any extra entries.
295 """
296 EventBuilder.BASE_DICTIONARY = dict(entries_dict)
297
Todd Fiala68615ce2015-09-15 21:38:04 +0000298
299class ResultsFormatter(object):
Todd Fiala33896a92015-09-18 21:01:13 +0000300
Todd Fiala68615ce2015-09-15 21:38:04 +0000301 """Provides interface to formatting test results out to a file-like object.
302
303 This class allows the LLDB test framework's raw test-realted
304 events to be processed and formatted in any manner desired.
305 Test events are represented by python dictionaries, formatted
306 as in the EventBuilder class above.
307
308 ResultFormatter instances are given a file-like object in which
309 to write their results.
310
311 ResultFormatter lifetime looks like the following:
312
313 # The result formatter is created.
314 # The argparse options dictionary is generated from calling
315 # the SomeResultFormatter.arg_parser() with the options data
316 # passed to dotest.py via the "--results-formatter-options"
317 # argument. See the help on that for syntactic requirements
318 # on getting that parsed correctly.
319 formatter = SomeResultFormatter(file_like_object, argpared_options_dict)
320
321 # Single call to session start, before parsing any events.
322 formatter.begin_session()
323
Todd Fialae83f1402015-09-18 22:45:31 +0000324 formatter.handle_event({"event":"initialize",...})
325
Todd Fiala68615ce2015-09-15 21:38:04 +0000326 # Zero or more calls specified for events recorded during the test session.
327 # The parallel test runner manages getting results from all the inferior
328 # dotest processes, so from a new format perspective, don't worry about
329 # that. The formatter will be presented with a single stream of events
330 # sandwiched between a single begin_session()/end_session() pair in the
331 # parallel test runner process/thread.
332 for event in zero_or_more_test_events():
Todd Fialae83f1402015-09-18 22:45:31 +0000333 formatter.handle_event(event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000334
Todd Fialae83f1402015-09-18 22:45:31 +0000335 # Single call to terminate/wrap-up. Formatters that need all the
336 # data before they can print a correct result (e.g. xUnit/JUnit),
337 # this is where the final report can be generated.
338 formatter.handle_event({"event":"terminate",...})
Todd Fiala68615ce2015-09-15 21:38:04 +0000339
340 It is not the formatter's responsibility to close the file_like_object.
341 (i.e. do not close it).
342
343 The lldb test framework passes these test events in real time, so they
344 arrive as they come in.
345
346 In the case of the parallel test runner, the dotest inferiors
347 add a 'pid' field to the dictionary that indicates which inferior
348 pid generated the event.
349
350 Note more events may be added in the future to support richer test
351 reporting functionality. One example: creating a true flaky test
352 result category so that unexpected successes really mean the test
353 is marked incorrectly (either should be marked flaky, or is indeed
354 passing consistently now and should have the xfail marker
355 removed). In this case, a flaky_success and flaky_fail event
356 likely will be added to capture these and support reporting things
357 like percentages of flaky test passing so we can see if we're
358 making some things worse/better with regards to failure rates.
359
360 Another example: announcing all the test methods that are planned
361 to be run, so we can better support redo operations of various kinds
362 (redo all non-run tests, redo non-run tests except the one that
363 was running [perhaps crashed], etc.)
364
365 Implementers are expected to override all the public methods
366 provided in this class. See each method's docstring to see
367 expectations about when the call should be chained.
368
369 """
370
371 @classmethod
372 def arg_parser(cls):
373 """@return arg parser used to parse formatter-specific options."""
374 parser = argparse.ArgumentParser(
375 description='{} options'.format(cls.__name__),
376 usage=('dotest.py --results-formatter-options='
377 '"--option1 value1 [--option2 value2 [...]]"'))
378 return parser
379
380 def __init__(self, out_file, options):
381 super(ResultsFormatter, self).__init__()
382 self.out_file = out_file
383 self.options = options
384 if not self.out_file:
385 raise Exception("ResultsFormatter created with no file object")
386 self.start_time_by_test = {}
387
388 # Lock that we use while mutating inner state, like the
389 # total test count and the elements. We minimize how
390 # long we hold the lock just to keep inner state safe, not
391 # entirely consistent from the outside.
392 self.lock = threading.Lock()
393
Todd Fialae83f1402015-09-18 22:45:31 +0000394 def handle_event(self, test_event):
395 """Handles the test event for collection into the formatter output.
Todd Fiala68615ce2015-09-15 21:38:04 +0000396
397 Derived classes may override this but should call down to this
398 implementation first.
399
400 @param test_event the test event as formatted by one of the
401 event_for_* calls.
402 """
403 pass
404
405 def track_start_time(self, test_class, test_name, start_time):
406 """Tracks the start time of a test so elapsed time can be computed.
407
408 This alleviates the need for test results to be processed serially
409 by test. It will save the start time for the test so that
410 elapsed_time_for_test() can compute the elapsed time properly.
411 """
412 if test_class is None or test_name is None:
413 return
414
415 test_key = "{}.{}".format(test_class, test_name)
416 with self.lock:
417 self.start_time_by_test[test_key] = start_time
418
419 def elapsed_time_for_test(self, test_class, test_name, end_time):
420 """Returns the elapsed time for a test.
421
422 This function can only be called once per test and requires that
423 the track_start_time() method be called sometime prior to calling
424 this method.
425 """
426 if test_class is None or test_name is None:
427 return -2.0
428
429 test_key = "{}.{}".format(test_class, test_name)
430 with self.lock:
431 if test_key not in self.start_time_by_test:
432 return -1.0
433 else:
434 start_time = self.start_time_by_test[test_key]
435 del self.start_time_by_test[test_key]
436 return end_time - start_time
437
438
439class XunitFormatter(ResultsFormatter):
440 """Provides xUnit-style formatted output.
441 """
442
443 # Result mapping arguments
444 RM_IGNORE = 'ignore'
445 RM_SUCCESS = 'success'
446 RM_FAILURE = 'failure'
447 RM_PASSTHRU = 'passthru'
448
449 @staticmethod
Todd Fiala8effde42015-09-18 16:00:52 +0000450 def _build_illegal_xml_regex():
Todd Fiala33896a92015-09-18 21:01:13 +0000451 """Contructs a regex to match all illegal xml characters.
452
453 Expects to be used against a unicode string."""
Todd Fiala8effde42015-09-18 16:00:52 +0000454 # Construct the range pairs of invalid unicode chareacters.
455 illegal_chars_u = [
456 (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
457 (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
458
459 # For wide builds, we have more.
460 if sys.maxunicode >= 0x10000:
461 illegal_chars_u.extend(
462 [(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
463 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
464 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
465 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
466 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
467 (0x10FFFE, 0x10FFFF)])
468
469 # Build up an array of range expressions.
470 illegal_ranges = [
471 "%s-%s" % (unichr(low), unichr(high))
472 for (low, high) in illegal_chars_u]
473
474 # Compile the regex
475 return re.compile(u'[%s]' % u''.join(illegal_ranges))
476
477 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000478 def _quote_attribute(text):
479 """Returns the given text in a manner safe for usage in an XML attribute.
480
481 @param text the text that should appear within an XML attribute.
482 @return the attribute-escaped version of the input text.
483 """
484 return xml.sax.saxutils.quoteattr(text)
485
Todd Fiala8effde42015-09-18 16:00:52 +0000486 def _replace_invalid_xml(self, str_or_unicode):
Todd Fiala33896a92015-09-18 21:01:13 +0000487 """Replaces invalid XML characters with a '?'.
488
489 @param str_or_unicode a string to replace invalid XML
490 characters within. Can be unicode or not. If not unicode,
491 assumes it is a byte string in utf-8 encoding.
492
493 @returns a utf-8-encoded byte string with invalid
494 XML replaced with '?'.
495 """
Todd Fiala8effde42015-09-18 16:00:52 +0000496 # Get the content into unicode
497 if isinstance(str_or_unicode, str):
498 unicode_content = str_or_unicode.decode('utf-8')
499 else:
500 unicode_content = str_or_unicode
501 return self.invalid_xml_re.sub(u'?', unicode_content).encode('utf-8')
502
Todd Fiala68615ce2015-09-15 21:38:04 +0000503 @classmethod
504 def arg_parser(cls):
505 """@return arg parser used to parse formatter-specific options."""
506 parser = super(XunitFormatter, cls).arg_parser()
507
508 # These are valid choices for results mapping.
509 results_mapping_choices = [
510 XunitFormatter.RM_IGNORE,
511 XunitFormatter.RM_SUCCESS,
512 XunitFormatter.RM_FAILURE,
513 XunitFormatter.RM_PASSTHRU]
514 parser.add_argument(
Todd Fiala33896a92015-09-18 21:01:13 +0000515 "--assert-on-unknown-events",
516 action="store_true",
517 help=('cause unknown test events to generate '
518 'a python assert. Default is to ignore.'))
519 parser.add_argument(
Todd Fiala68615ce2015-09-15 21:38:04 +0000520 "--xpass", action="store", choices=results_mapping_choices,
521 default=XunitFormatter.RM_FAILURE,
522 help=('specify mapping from unexpected success to jUnit/xUnit '
523 'result type'))
524 parser.add_argument(
525 "--xfail", action="store", choices=results_mapping_choices,
526 default=XunitFormatter.RM_IGNORE,
527 help=('specify mapping from expected failure to jUnit/xUnit '
528 'result type'))
529 return parser
530
531 def __init__(self, out_file, options):
532 """Initializes the XunitFormatter instance.
533 @param out_file file-like object where formatted output is written.
534 @param options_dict specifies a dictionary of options for the
535 formatter.
536 """
537 # Initialize the parent
538 super(XunitFormatter, self).__init__(out_file, options)
539 self.text_encoding = "UTF-8"
Todd Fiala8effde42015-09-18 16:00:52 +0000540 self.invalid_xml_re = XunitFormatter._build_illegal_xml_regex()
Todd Fiala68615ce2015-09-15 21:38:04 +0000541
542 self.total_test_count = 0
543
544 self.elements = {
545 "successes": [],
546 "errors": [],
547 "failures": [],
548 "skips": [],
549 "unexpected_successes": [],
550 "expected_failures": [],
551 "all": []
552 }
553
554 self.status_handlers = {
555 "success": self._handle_success,
556 "failure": self._handle_failure,
557 "error": self._handle_error,
558 "skip": self._handle_skip,
559 "expected_failure": self._handle_expected_failure,
560 "unexpected_success": self._handle_unexpected_success
561 }
562
Todd Fialae83f1402015-09-18 22:45:31 +0000563 def handle_event(self, test_event):
564 super(XunitFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000565
566 event_type = test_event["event"]
567 if event_type is None:
568 return
569
Todd Fialae83f1402015-09-18 22:45:31 +0000570 if event_type == "terminate":
571 self._finish_output()
572 elif event_type == "test_start":
Todd Fiala68615ce2015-09-15 21:38:04 +0000573 self.track_start_time(
574 test_event["test_class"],
575 test_event["test_name"],
576 test_event["event_time"])
577 elif event_type == "test_result":
578 self._process_test_result(test_event)
579 else:
Todd Fiala33896a92015-09-18 21:01:13 +0000580 # This is an unknown event.
581 if self.options.assert_on_unknown_events:
582 raise Exception("unknown event type {} from {}\n".format(
583 event_type, test_event))
Todd Fiala68615ce2015-09-15 21:38:04 +0000584
585 def _handle_success(self, test_event):
586 """Handles a test success.
587 @param test_event the test event to handle.
588 """
589 result = self._common_add_testcase_entry(test_event)
590 with self.lock:
591 self.elements["successes"].append(result)
592
593 def _handle_failure(self, test_event):
594 """Handles a test failure.
595 @param test_event the test event to handle.
596 """
Todd Fiala8effde42015-09-18 16:00:52 +0000597 message = self._replace_invalid_xml(test_event["issue_message"])
598 backtrace = self._replace_invalid_xml(
599 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000600
Todd Fiala68615ce2015-09-15 21:38:04 +0000601 result = self._common_add_testcase_entry(
602 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000603 inner_content=(
604 '<failure type={} message={}><![CDATA[{}]]></failure>'.format(
605 XunitFormatter._quote_attribute(test_event["issue_class"]),
606 XunitFormatter._quote_attribute(message),
607 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000608 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000609 with self.lock:
610 self.elements["failures"].append(result)
611
612 def _handle_error(self, test_event):
613 """Handles a test error.
614 @param test_event the test event to handle.
615 """
Todd Fiala8effde42015-09-18 16:00:52 +0000616 message = self._replace_invalid_xml(test_event["issue_message"])
617 backtrace = self._replace_invalid_xml(
618 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000619
Todd Fiala68615ce2015-09-15 21:38:04 +0000620 result = self._common_add_testcase_entry(
621 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000622 inner_content=(
623 '<error type={} message={}><![CDATA[{}]]></error>'.format(
624 XunitFormatter._quote_attribute(test_event["issue_class"]),
625 XunitFormatter._quote_attribute(message),
626 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000627 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000628 with self.lock:
629 self.elements["errors"].append(result)
630
631 def _handle_skip(self, test_event):
632 """Handles a skipped test.
633 @param test_event the test event to handle.
634 """
Todd Fiala8effde42015-09-18 16:00:52 +0000635 reason = self._replace_invalid_xml(test_event.get("skip_reason", ""))
Todd Fiala68615ce2015-09-15 21:38:04 +0000636 result = self._common_add_testcase_entry(
637 test_event,
638 inner_content='<skipped message={} />'.format(
Todd Fiala8effde42015-09-18 16:00:52 +0000639 XunitFormatter._quote_attribute(reason)))
Todd Fiala68615ce2015-09-15 21:38:04 +0000640 with self.lock:
641 self.elements["skips"].append(result)
642
643 def _handle_expected_failure(self, test_event):
644 """Handles a test that failed as expected.
645 @param test_event the test event to handle.
646 """
647 if self.options.xfail == XunitFormatter.RM_PASSTHRU:
648 # This is not a natively-supported junit/xunit
649 # testcase mode, so it might fail a validating
650 # test results viewer.
651 if "bugnumber" in test_event:
652 bug_id_attribute = 'bug-id={} '.format(
653 XunitFormatter._quote_attribute(test_event["bugnumber"]))
654 else:
655 bug_id_attribute = ''
656
657 result = self._common_add_testcase_entry(
658 test_event,
659 inner_content=(
660 '<expected-failure {}type={} message={} />'.format(
661 bug_id_attribute,
662 XunitFormatter._quote_attribute(
663 test_event["issue_class"]),
664 XunitFormatter._quote_attribute(
665 test_event["issue_message"]))
666 ))
667 with self.lock:
668 self.elements["expected_failures"].append(result)
669 elif self.options.xfail == XunitFormatter.RM_SUCCESS:
670 result = self._common_add_testcase_entry(test_event)
671 with self.lock:
672 self.elements["successes"].append(result)
673 elif self.options.xfail == XunitFormatter.RM_FAILURE:
674 result = self._common_add_testcase_entry(
675 test_event,
676 inner_content='<failure type={} message={} />'.format(
677 XunitFormatter._quote_attribute(test_event["issue_class"]),
678 XunitFormatter._quote_attribute(
679 test_event["issue_message"])))
680 with self.lock:
681 self.elements["failures"].append(result)
682 elif self.options.xfail == XunitFormatter.RM_IGNORE:
683 pass
684 else:
685 raise Exception(
686 "unknown xfail option: {}".format(self.options.xfail))
687
688 def _handle_unexpected_success(self, test_event):
689 """Handles a test that passed but was expected to fail.
690 @param test_event the test event to handle.
691 """
692 if self.options.xpass == XunitFormatter.RM_PASSTHRU:
693 # This is not a natively-supported junit/xunit
694 # testcase mode, so it might fail a validating
695 # test results viewer.
696 result = self._common_add_testcase_entry(
697 test_event,
698 inner_content=("<unexpected-success />"))
699 with self.lock:
700 self.elements["unexpected_successes"].append(result)
701 elif self.options.xpass == XunitFormatter.RM_SUCCESS:
702 # Treat the xpass as a success.
703 result = self._common_add_testcase_entry(test_event)
704 with self.lock:
705 self.elements["successes"].append(result)
706 elif self.options.xpass == XunitFormatter.RM_FAILURE:
707 # Treat the xpass as a failure.
708 if "bugnumber" in test_event:
709 message = "unexpected success (bug_id:{})".format(
710 test_event["bugnumber"])
711 else:
712 message = "unexpected success (bug_id:none)"
713 result = self._common_add_testcase_entry(
714 test_event,
715 inner_content='<failure type={} message={} />'.format(
716 XunitFormatter._quote_attribute("unexpected_success"),
717 XunitFormatter._quote_attribute(message)))
718 with self.lock:
719 self.elements["failures"].append(result)
720 elif self.options.xpass == XunitFormatter.RM_IGNORE:
721 # Ignore the xpass result as far as xUnit reporting goes.
722 pass
723 else:
724 raise Exception("unknown xpass option: {}".format(
725 self.options.xpass))
726
727 def _process_test_result(self, test_event):
728 """Processes the test_event known to be a test result.
729
730 This categorizes the event appropriately and stores the data needed
731 to generate the final xUnit report. This method skips events that
732 cannot be represented in xUnit output.
733 """
734 if "status" not in test_event:
735 raise Exception("test event dictionary missing 'status' key")
736
737 status = test_event["status"]
738 if status not in self.status_handlers:
739 raise Exception("test event status '{}' unsupported".format(
740 status))
741
742 # Call the status handler for the test result.
743 self.status_handlers[status](test_event)
744
745 def _common_add_testcase_entry(self, test_event, inner_content=None):
746 """Registers a testcase result, and returns the text created.
747
748 The caller is expected to manage failure/skip/success counts
749 in some kind of appropriate way. This call simply constructs
750 the XML and appends the returned result to the self.all_results
751 list.
752
753 @param test_event the test event dictionary.
754
755 @param inner_content if specified, gets included in the <testcase>
756 inner section, at the point before stdout and stderr would be
757 included. This is where a <failure/>, <skipped/>, <error/>, etc.
758 could go.
759
760 @return the text of the xml testcase element.
761 """
762
763 # Get elapsed time.
764 test_class = test_event["test_class"]
765 test_name = test_event["test_name"]
766 event_time = test_event["event_time"]
767 time_taken = self.elapsed_time_for_test(
768 test_class, test_name, event_time)
769
770 # Plumb in stdout/stderr once we shift over to only test results.
771 test_stdout = ''
772 test_stderr = ''
773
774 # Formulate the output xml.
775 if not inner_content:
776 inner_content = ""
777 result = (
778 '<testcase classname="{}" name="{}" time="{:.3f}">'
779 '{}{}{}</testcase>'.format(
780 test_class,
781 test_name,
782 time_taken,
783 inner_content,
784 test_stdout,
785 test_stderr))
786
787 # Save the result, update total test count.
788 with self.lock:
789 self.total_test_count += 1
790 self.elements["all"].append(result)
791
792 return result
793
Todd Fialae83f1402015-09-18 22:45:31 +0000794 def _finish_output_no_lock(self):
Todd Fiala68615ce2015-09-15 21:38:04 +0000795 """Flushes out the report of test executions to form valid xml output.
796
797 xUnit output is in XML. The reporting system cannot complete the
798 formatting of the output without knowing when there is no more input.
799 This call addresses notifcation of the completed test run and thus is
800 when we can finish off the report output.
801 """
802
803 # Figure out the counts line for the testsuite. If we have
804 # been counting either unexpected successes or expected
805 # failures, we'll output those in the counts, at the risk of
806 # being invalidated by a validating test results viewer.
807 # These aren't counted by default so they won't show up unless
808 # the user specified a formatter option to include them.
809 xfail_count = len(self.elements["expected_failures"])
810 xpass_count = len(self.elements["unexpected_successes"])
811 if xfail_count > 0 or xpass_count > 0:
812 extra_testsuite_attributes = (
813 ' expected-failures="{}"'
814 ' unexpected-successes="{}"'.format(xfail_count, xpass_count))
815 else:
816 extra_testsuite_attributes = ""
817
818 # Output the header.
819 self.out_file.write(
820 '<?xml version="1.0" encoding="{}"?>\n'
Todd Fiala038bf832015-09-18 01:43:08 +0000821 '<testsuites>'
Todd Fiala68615ce2015-09-15 21:38:04 +0000822 '<testsuite name="{}" tests="{}" errors="{}" failures="{}" '
823 'skip="{}"{}>\n'.format(
824 self.text_encoding,
825 "LLDB test suite",
826 self.total_test_count,
827 len(self.elements["errors"]),
828 len(self.elements["failures"]),
829 len(self.elements["skips"]),
830 extra_testsuite_attributes))
831
832 # Output each of the test result entries.
833 for result in self.elements["all"]:
834 self.out_file.write(result + '\n')
835
836 # Close off the test suite.
Todd Fiala038bf832015-09-18 01:43:08 +0000837 self.out_file.write('</testsuite></testsuites>\n')
Todd Fiala68615ce2015-09-15 21:38:04 +0000838
Todd Fialae83f1402015-09-18 22:45:31 +0000839 def _finish_output(self):
Todd Fiala68615ce2015-09-15 21:38:04 +0000840 with self.lock:
Todd Fialae83f1402015-09-18 22:45:31 +0000841 self._finish_output_no_lock()
Todd Fiala68615ce2015-09-15 21:38:04 +0000842
843
844class RawPickledFormatter(ResultsFormatter):
845 """Formats events as a pickled stream.
846
847 The parallel test runner has inferiors pickle their results and send them
848 over a socket back to the parallel test. The parallel test runner then
849 aggregates them into the final results formatter (e.g. xUnit).
850 """
851
852 @classmethod
853 def arg_parser(cls):
854 """@return arg parser used to parse formatter-specific options."""
855 parser = super(RawPickledFormatter, cls).arg_parser()
856 return parser
857
858 def __init__(self, out_file, options):
859 super(RawPickledFormatter, self).__init__(out_file, options)
860 self.pid = os.getpid()
861
Todd Fialae83f1402015-09-18 22:45:31 +0000862 def handle_event(self, test_event):
863 super(RawPickledFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000864
Todd Fialae83f1402015-09-18 22:45:31 +0000865 # Convert initialize/terminate events into job_begin/job_end events.
866 event_type = test_event["event"]
867 if event_type is None:
868 return
869
870 if event_type == "initialize":
871 test_event["event"] = "job_begin"
872 elif event_type == "terminate":
873 test_event["event"] = "job_end"
874
875 # Tack on the pid.
876 test_event["pid"] = self.pid
Todd Fiala68615ce2015-09-15 21:38:04 +0000877
Todd Fiala68615ce2015-09-15 21:38:04 +0000878 # Send it as {serialized_length_of_serialized_bytes}#{serialized_bytes}
879 pickled_message = cPickle.dumps(test_event)
880 self.out_file.send(
881 "{}#{}".format(len(pickled_message), pickled_message))
882
Todd Fiala33896a92015-09-18 21:01:13 +0000883
884class DumpFormatter(ResultsFormatter):
885 """Formats events to the file as their raw python dictionary format."""
886
Todd Fialae83f1402015-09-18 22:45:31 +0000887 def handle_event(self, test_event):
888 super(DumpFormatter, self).handle_event(test_event)
Todd Fiala33896a92015-09-18 21:01:13 +0000889 self.out_file.write("\n" + pprint.pformat(test_event) + "\n")