blob: 7b3e796111bd62e4e63214f0f0ffb2de42903e8f [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
43 def _event_dictionary_common(test, event_type):
44 """Returns an event dictionary setup with values for the given event type.
45
46 @param test the unittest.TestCase instance
47
48 @param event_type the name of the event type (string).
49
50 @return event dictionary with common event fields set.
51 """
52 test_class_name, test_name = EventBuilder._get_test_name_info(test)
Todd Fiala33896a92015-09-18 21:01:13 +000053
54 if EventBuilder.BASE_DICTIONARY is not None:
55 # Start with a copy of the "always include" entries.
56 result = dict(EventBuilder.BASE_DICTIONARY)
57 else:
58 result = {}
59 result.update({
Todd Fiala68615ce2015-09-15 21:38:04 +000060 "event": event_type,
61 "test_class": test_class_name,
62 "test_name": test_name,
63 "event_time": time.time()
Todd Fiala33896a92015-09-18 21:01:13 +000064 })
65 return result
Todd Fiala68615ce2015-09-15 21:38:04 +000066
67 @staticmethod
68 def _error_tuple_class(error_tuple):
69 """Returns the unittest error tuple's error class as a string.
70
71 @param error_tuple the error tuple provided by the test framework.
72
73 @return the error type (typically an exception) raised by the
74 test framework.
75 """
76 type_var = error_tuple[0]
77 module = inspect.getmodule(type_var)
78 if module:
79 return "{}.{}".format(module.__name__, type_var.__name__)
80 else:
81 return type_var.__name__
82
83 @staticmethod
84 def _error_tuple_message(error_tuple):
85 """Returns the unittest error tuple's error message.
86
87 @param error_tuple the error tuple provided by the test framework.
88
89 @return the error message provided by the test framework.
90 """
91 return str(error_tuple[1])
92
93 @staticmethod
Todd Fialae7e911f2015-09-18 07:08:09 +000094 def _error_tuple_traceback(error_tuple):
95 """Returns the unittest error tuple's error message.
96
97 @param error_tuple the error tuple provided by the test framework.
98
99 @return the error message provided by the test framework.
100 """
101 return error_tuple[2]
102
103 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000104 def _event_dictionary_test_result(test, status):
105 """Returns an event dictionary with common test result fields set.
106
107 @param test a unittest.TestCase instance.
108
109 @param status the status/result of the test
110 (e.g. "success", "failure", etc.)
111
112 @return the event dictionary
113 """
114 event = EventBuilder._event_dictionary_common(test, "test_result")
115 event["status"] = status
116 return event
117
118 @staticmethod
119 def _event_dictionary_issue(test, status, error_tuple):
120 """Returns an event dictionary with common issue-containing test result
121 fields set.
122
123 @param test a unittest.TestCase instance.
124
125 @param status the status/result of the test
126 (e.g. "success", "failure", etc.)
127
128 @param error_tuple the error tuple as reported by the test runner.
129 This is of the form (type<error>, error).
130
131 @return the event dictionary
132 """
133 event = EventBuilder._event_dictionary_test_result(test, status)
134 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
135 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
Todd Fiala33896a92015-09-18 21:01:13 +0000136 backtrace = EventBuilder._error_tuple_traceback(error_tuple)
137 if backtrace is not None:
138 event["issue_backtrace"] = traceback.format_tb(backtrace)
Todd Fiala68615ce2015-09-15 21:38:04 +0000139 return event
140
141 @staticmethod
142 def event_for_start(test):
143 """Returns an event dictionary for the test start event.
144
145 @param test a unittest.TestCase instance.
146
147 @return the event dictionary
148 """
149 return EventBuilder._event_dictionary_common(test, "test_start")
150
151 @staticmethod
152 def event_for_success(test):
153 """Returns an event dictionary for a successful test.
154
155 @param test a unittest.TestCase instance.
156
157 @return the event dictionary
158 """
159 return EventBuilder._event_dictionary_test_result(test, "success")
160
161 @staticmethod
162 def event_for_unexpected_success(test, bugnumber):
163 """Returns an event dictionary for a test that succeeded but was
164 expected to fail.
165
166 @param test a unittest.TestCase instance.
167
168 @param bugnumber the issue identifier for the bug tracking the
169 fix request for the test expected to fail (but is in fact
170 passing here).
171
172 @return the event dictionary
173
174 """
175 event = EventBuilder._event_dictionary_test_result(
176 test, "unexpected_success")
177 if bugnumber:
178 event["bugnumber"] = str(bugnumber)
179 return event
180
181 @staticmethod
182 def event_for_failure(test, error_tuple):
183 """Returns an event dictionary for a test that failed.
184
185 @param test a unittest.TestCase instance.
186
187 @param error_tuple the error tuple as reported by the test runner.
188 This is of the form (type<error>, error).
189
190 @return the event dictionary
191 """
192 return EventBuilder._event_dictionary_issue(
193 test, "failure", error_tuple)
194
195 @staticmethod
196 def event_for_expected_failure(test, error_tuple, bugnumber):
197 """Returns an event dictionary for a test that failed as expected.
198
199 @param test a unittest.TestCase instance.
200
201 @param error_tuple the error tuple as reported by the test runner.
202 This is of the form (type<error>, error).
203
204 @param bugnumber the issue identifier for the bug tracking the
205 fix request for the test expected to fail.
206
207 @return the event dictionary
208
209 """
210 event = EventBuilder._event_dictionary_issue(
211 test, "expected_failure", error_tuple)
212 if bugnumber:
213 event["bugnumber"] = str(bugnumber)
214 return event
215
216 @staticmethod
217 def event_for_skip(test, reason):
218 """Returns an event dictionary for a test that was skipped.
219
220 @param test a unittest.TestCase instance.
221
222 @param reason the reason why the test is being skipped.
223
224 @return the event dictionary
225 """
226 event = EventBuilder._event_dictionary_test_result(test, "skip")
227 event["skip_reason"] = reason
228 return event
229
230 @staticmethod
231 def event_for_error(test, error_tuple):
232 """Returns an event dictionary for a test that hit a test execution error.
233
234 @param test a unittest.TestCase instance.
235
236 @param error_tuple the error tuple as reported by the test runner.
237 This is of the form (type<error>, error).
238
239 @return the event dictionary
240 """
241 return EventBuilder._event_dictionary_issue(test, "error", error_tuple)
242
243 @staticmethod
244 def event_for_cleanup_error(test, error_tuple):
245 """Returns an event dictionary for a test that hit a test execution error
246 during the test cleanup phase.
247
248 @param test a unittest.TestCase instance.
249
250 @param error_tuple the error tuple as reported by the test runner.
251 This is of the form (type<error>, error).
252
253 @return the event dictionary
254 """
255 event = EventBuilder._event_dictionary_issue(
256 test, "error", error_tuple)
257 event["issue_phase"] = "cleanup"
258 return event
259
Todd Fiala33896a92015-09-18 21:01:13 +0000260 @staticmethod
261 def add_entries_to_all_events(entries_dict):
262 """Specifies a dictionary of entries to add to all test events.
263
264 This provides a mechanism for, say, a parallel test runner to
265 indicate to each inferior dotest.py that it should add a
266 worker index to each.
267
268 Calling this method replaces all previous entries added
269 by a prior call to this.
270
271 Event build methods will overwrite any entries that collide.
272 Thus, the passed in dictionary is the base, which gets merged
273 over by event building when keys collide.
274
275 @param entries_dict a dictionary containing key and value
276 pairs that should be merged into all events created by the
277 event generator. May be None to clear out any extra entries.
278 """
279 EventBuilder.BASE_DICTIONARY = dict(entries_dict)
280
281 @staticmethod
282 def base_event():
283 """@return the base event dictionary that all events should contain."""
284 if EventBuilder.BASE_DICTIONARY is not None:
285 return dict(EventBuilder.BASE_DICTIONARY)
286 else:
287 return None
288
Todd Fiala68615ce2015-09-15 21:38:04 +0000289
290class ResultsFormatter(object):
Todd Fiala33896a92015-09-18 21:01:13 +0000291
Todd Fiala68615ce2015-09-15 21:38:04 +0000292 """Provides interface to formatting test results out to a file-like object.
293
294 This class allows the LLDB test framework's raw test-realted
295 events to be processed and formatted in any manner desired.
296 Test events are represented by python dictionaries, formatted
297 as in the EventBuilder class above.
298
299 ResultFormatter instances are given a file-like object in which
300 to write their results.
301
302 ResultFormatter lifetime looks like the following:
303
304 # The result formatter is created.
305 # The argparse options dictionary is generated from calling
306 # the SomeResultFormatter.arg_parser() with the options data
307 # passed to dotest.py via the "--results-formatter-options"
308 # argument. See the help on that for syntactic requirements
309 # on getting that parsed correctly.
310 formatter = SomeResultFormatter(file_like_object, argpared_options_dict)
311
312 # Single call to session start, before parsing any events.
313 formatter.begin_session()
314
315 # Zero or more calls specified for events recorded during the test session.
316 # The parallel test runner manages getting results from all the inferior
317 # dotest processes, so from a new format perspective, don't worry about
318 # that. The formatter will be presented with a single stream of events
319 # sandwiched between a single begin_session()/end_session() pair in the
320 # parallel test runner process/thread.
321 for event in zero_or_more_test_events():
322 formatter.process_event(event)
323
324 # Single call to session end. Formatters that need all the data before
325 # they can print a correct result (e.g. xUnit/JUnit), this is where
326 # the final report can be generated.
327 formatter.end_session()
328
329 It is not the formatter's responsibility to close the file_like_object.
330 (i.e. do not close it).
331
332 The lldb test framework passes these test events in real time, so they
333 arrive as they come in.
334
335 In the case of the parallel test runner, the dotest inferiors
336 add a 'pid' field to the dictionary that indicates which inferior
337 pid generated the event.
338
339 Note more events may be added in the future to support richer test
340 reporting functionality. One example: creating a true flaky test
341 result category so that unexpected successes really mean the test
342 is marked incorrectly (either should be marked flaky, or is indeed
343 passing consistently now and should have the xfail marker
344 removed). In this case, a flaky_success and flaky_fail event
345 likely will be added to capture these and support reporting things
346 like percentages of flaky test passing so we can see if we're
347 making some things worse/better with regards to failure rates.
348
349 Another example: announcing all the test methods that are planned
350 to be run, so we can better support redo operations of various kinds
351 (redo all non-run tests, redo non-run tests except the one that
352 was running [perhaps crashed], etc.)
353
354 Implementers are expected to override all the public methods
355 provided in this class. See each method's docstring to see
356 expectations about when the call should be chained.
357
358 """
359
360 @classmethod
361 def arg_parser(cls):
362 """@return arg parser used to parse formatter-specific options."""
363 parser = argparse.ArgumentParser(
364 description='{} options'.format(cls.__name__),
365 usage=('dotest.py --results-formatter-options='
366 '"--option1 value1 [--option2 value2 [...]]"'))
367 return parser
368
369 def __init__(self, out_file, options):
370 super(ResultsFormatter, self).__init__()
371 self.out_file = out_file
372 self.options = options
373 if not self.out_file:
374 raise Exception("ResultsFormatter created with no file object")
375 self.start_time_by_test = {}
376
377 # Lock that we use while mutating inner state, like the
378 # total test count and the elements. We minimize how
379 # long we hold the lock just to keep inner state safe, not
380 # entirely consistent from the outside.
381 self.lock = threading.Lock()
382
383 def begin_session(self):
384 """Begins a test session.
385
386 All process_event() calls must be sandwiched between
387 begin_session() and end_session() calls.
388
389 Derived classes may override this but should call this first.
390 """
391 pass
392
393 def end_session(self):
394 """Ends a test session.
395
396 All process_event() calls must be sandwiched between
397 begin_session() and end_session() calls.
398
399 All results formatting should be sent to the output
400 file object by the end of this call.
401
402 Derived classes may override this but should call this after
403 the dervied class's behavior is complete.
404 """
405 pass
406
407 def process_event(self, test_event):
408 """Processes the test event for collection into the formatter output.
409
410 Derived classes may override this but should call down to this
411 implementation first.
412
413 @param test_event the test event as formatted by one of the
414 event_for_* calls.
415 """
416 pass
417
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
451
452class XunitFormatter(ResultsFormatter):
453 """Provides xUnit-style formatted output.
454 """
455
456 # Result mapping arguments
457 RM_IGNORE = 'ignore'
458 RM_SUCCESS = 'success'
459 RM_FAILURE = 'failure'
460 RM_PASSTHRU = 'passthru'
461
462 @staticmethod
Todd Fiala8effde42015-09-18 16:00:52 +0000463 def _build_illegal_xml_regex():
Todd Fiala33896a92015-09-18 21:01:13 +0000464 """Contructs a regex to match all illegal xml characters.
465
466 Expects to be used against a unicode string."""
Todd Fiala8effde42015-09-18 16:00:52 +0000467 # Construct the range pairs of invalid unicode chareacters.
468 illegal_chars_u = [
469 (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
470 (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
471
472 # For wide builds, we have more.
473 if sys.maxunicode >= 0x10000:
474 illegal_chars_u.extend(
475 [(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
476 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
477 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
478 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
479 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
480 (0x10FFFE, 0x10FFFF)])
481
482 # Build up an array of range expressions.
483 illegal_ranges = [
484 "%s-%s" % (unichr(low), unichr(high))
485 for (low, high) in illegal_chars_u]
486
487 # Compile the regex
488 return re.compile(u'[%s]' % u''.join(illegal_ranges))
489
490 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000491 def _quote_attribute(text):
492 """Returns the given text in a manner safe for usage in an XML attribute.
493
494 @param text the text that should appear within an XML attribute.
495 @return the attribute-escaped version of the input text.
496 """
497 return xml.sax.saxutils.quoteattr(text)
498
Todd Fiala8effde42015-09-18 16:00:52 +0000499 def _replace_invalid_xml(self, str_or_unicode):
Todd Fiala33896a92015-09-18 21:01:13 +0000500 """Replaces invalid XML characters with a '?'.
501
502 @param str_or_unicode a string to replace invalid XML
503 characters within. Can be unicode or not. If not unicode,
504 assumes it is a byte string in utf-8 encoding.
505
506 @returns a utf-8-encoded byte string with invalid
507 XML replaced with '?'.
508 """
Todd Fiala8effde42015-09-18 16:00:52 +0000509 # Get the content into unicode
510 if isinstance(str_or_unicode, str):
511 unicode_content = str_or_unicode.decode('utf-8')
512 else:
513 unicode_content = str_or_unicode
514 return self.invalid_xml_re.sub(u'?', unicode_content).encode('utf-8')
515
Todd Fiala68615ce2015-09-15 21:38:04 +0000516 @classmethod
517 def arg_parser(cls):
518 """@return arg parser used to parse formatter-specific options."""
519 parser = super(XunitFormatter, cls).arg_parser()
520
521 # These are valid choices for results mapping.
522 results_mapping_choices = [
523 XunitFormatter.RM_IGNORE,
524 XunitFormatter.RM_SUCCESS,
525 XunitFormatter.RM_FAILURE,
526 XunitFormatter.RM_PASSTHRU]
527 parser.add_argument(
Todd Fiala33896a92015-09-18 21:01:13 +0000528 "--assert-on-unknown-events",
529 action="store_true",
530 help=('cause unknown test events to generate '
531 'a python assert. Default is to ignore.'))
532 parser.add_argument(
Todd Fiala68615ce2015-09-15 21:38:04 +0000533 "--xpass", action="store", choices=results_mapping_choices,
534 default=XunitFormatter.RM_FAILURE,
535 help=('specify mapping from unexpected success to jUnit/xUnit '
536 'result type'))
537 parser.add_argument(
538 "--xfail", action="store", choices=results_mapping_choices,
539 default=XunitFormatter.RM_IGNORE,
540 help=('specify mapping from expected failure to jUnit/xUnit '
541 'result type'))
542 return parser
543
544 def __init__(self, out_file, options):
545 """Initializes the XunitFormatter instance.
546 @param out_file file-like object where formatted output is written.
547 @param options_dict specifies a dictionary of options for the
548 formatter.
549 """
550 # Initialize the parent
551 super(XunitFormatter, self).__init__(out_file, options)
552 self.text_encoding = "UTF-8"
Todd Fiala8effde42015-09-18 16:00:52 +0000553 self.invalid_xml_re = XunitFormatter._build_illegal_xml_regex()
Todd Fiala68615ce2015-09-15 21:38:04 +0000554
555 self.total_test_count = 0
556
557 self.elements = {
558 "successes": [],
559 "errors": [],
560 "failures": [],
561 "skips": [],
562 "unexpected_successes": [],
563 "expected_failures": [],
564 "all": []
565 }
566
567 self.status_handlers = {
568 "success": self._handle_success,
569 "failure": self._handle_failure,
570 "error": self._handle_error,
571 "skip": self._handle_skip,
572 "expected_failure": self._handle_expected_failure,
573 "unexpected_success": self._handle_unexpected_success
574 }
575
576 def begin_session(self):
577 super(XunitFormatter, self).begin_session()
578
579 def process_event(self, test_event):
580 super(XunitFormatter, self).process_event(test_event)
581
582 event_type = test_event["event"]
583 if event_type is None:
584 return
585
586 if event_type == "test_start":
587 self.track_start_time(
588 test_event["test_class"],
589 test_event["test_name"],
590 test_event["event_time"])
591 elif event_type == "test_result":
592 self._process_test_result(test_event)
593 else:
Todd Fiala33896a92015-09-18 21:01:13 +0000594 # This is an unknown event.
595 if self.options.assert_on_unknown_events:
596 raise Exception("unknown event type {} from {}\n".format(
597 event_type, test_event))
Todd Fiala68615ce2015-09-15 21:38:04 +0000598
599 def _handle_success(self, test_event):
600 """Handles a test success.
601 @param test_event the test event to handle.
602 """
603 result = self._common_add_testcase_entry(test_event)
604 with self.lock:
605 self.elements["successes"].append(result)
606
607 def _handle_failure(self, test_event):
608 """Handles a test failure.
609 @param test_event the test event to handle.
610 """
Todd Fiala8effde42015-09-18 16:00:52 +0000611 message = self._replace_invalid_xml(test_event["issue_message"])
612 backtrace = self._replace_invalid_xml(
613 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000614
Todd Fiala68615ce2015-09-15 21:38:04 +0000615 result = self._common_add_testcase_entry(
616 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000617 inner_content=(
618 '<failure type={} message={}><![CDATA[{}]]></failure>'.format(
619 XunitFormatter._quote_attribute(test_event["issue_class"]),
620 XunitFormatter._quote_attribute(message),
621 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000622 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000623 with self.lock:
624 self.elements["failures"].append(result)
625
626 def _handle_error(self, test_event):
627 """Handles a test error.
628 @param test_event the test event to handle.
629 """
Todd Fiala8effde42015-09-18 16:00:52 +0000630 message = self._replace_invalid_xml(test_event["issue_message"])
631 backtrace = self._replace_invalid_xml(
632 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000633
Todd Fiala68615ce2015-09-15 21:38:04 +0000634 result = self._common_add_testcase_entry(
635 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000636 inner_content=(
637 '<error type={} message={}><![CDATA[{}]]></error>'.format(
638 XunitFormatter._quote_attribute(test_event["issue_class"]),
639 XunitFormatter._quote_attribute(message),
640 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000641 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000642 with self.lock:
643 self.elements["errors"].append(result)
644
645 def _handle_skip(self, test_event):
646 """Handles a skipped test.
647 @param test_event the test event to handle.
648 """
Todd Fiala8effde42015-09-18 16:00:52 +0000649 reason = self._replace_invalid_xml(test_event.get("skip_reason", ""))
Todd Fiala68615ce2015-09-15 21:38:04 +0000650 result = self._common_add_testcase_entry(
651 test_event,
652 inner_content='<skipped message={} />'.format(
Todd Fiala8effde42015-09-18 16:00:52 +0000653 XunitFormatter._quote_attribute(reason)))
Todd Fiala68615ce2015-09-15 21:38:04 +0000654 with self.lock:
655 self.elements["skips"].append(result)
656
657 def _handle_expected_failure(self, test_event):
658 """Handles a test that failed as expected.
659 @param test_event the test event to handle.
660 """
661 if self.options.xfail == XunitFormatter.RM_PASSTHRU:
662 # This is not a natively-supported junit/xunit
663 # testcase mode, so it might fail a validating
664 # test results viewer.
665 if "bugnumber" in test_event:
666 bug_id_attribute = 'bug-id={} '.format(
667 XunitFormatter._quote_attribute(test_event["bugnumber"]))
668 else:
669 bug_id_attribute = ''
670
671 result = self._common_add_testcase_entry(
672 test_event,
673 inner_content=(
674 '<expected-failure {}type={} message={} />'.format(
675 bug_id_attribute,
676 XunitFormatter._quote_attribute(
677 test_event["issue_class"]),
678 XunitFormatter._quote_attribute(
679 test_event["issue_message"]))
680 ))
681 with self.lock:
682 self.elements["expected_failures"].append(result)
683 elif self.options.xfail == XunitFormatter.RM_SUCCESS:
684 result = self._common_add_testcase_entry(test_event)
685 with self.lock:
686 self.elements["successes"].append(result)
687 elif self.options.xfail == XunitFormatter.RM_FAILURE:
688 result = self._common_add_testcase_entry(
689 test_event,
690 inner_content='<failure type={} message={} />'.format(
691 XunitFormatter._quote_attribute(test_event["issue_class"]),
692 XunitFormatter._quote_attribute(
693 test_event["issue_message"])))
694 with self.lock:
695 self.elements["failures"].append(result)
696 elif self.options.xfail == XunitFormatter.RM_IGNORE:
697 pass
698 else:
699 raise Exception(
700 "unknown xfail option: {}".format(self.options.xfail))
701
702 def _handle_unexpected_success(self, test_event):
703 """Handles a test that passed but was expected to fail.
704 @param test_event the test event to handle.
705 """
706 if self.options.xpass == XunitFormatter.RM_PASSTHRU:
707 # This is not a natively-supported junit/xunit
708 # testcase mode, so it might fail a validating
709 # test results viewer.
710 result = self._common_add_testcase_entry(
711 test_event,
712 inner_content=("<unexpected-success />"))
713 with self.lock:
714 self.elements["unexpected_successes"].append(result)
715 elif self.options.xpass == XunitFormatter.RM_SUCCESS:
716 # Treat the xpass as a success.
717 result = self._common_add_testcase_entry(test_event)
718 with self.lock:
719 self.elements["successes"].append(result)
720 elif self.options.xpass == XunitFormatter.RM_FAILURE:
721 # Treat the xpass as a failure.
722 if "bugnumber" in test_event:
723 message = "unexpected success (bug_id:{})".format(
724 test_event["bugnumber"])
725 else:
726 message = "unexpected success (bug_id:none)"
727 result = self._common_add_testcase_entry(
728 test_event,
729 inner_content='<failure type={} message={} />'.format(
730 XunitFormatter._quote_attribute("unexpected_success"),
731 XunitFormatter._quote_attribute(message)))
732 with self.lock:
733 self.elements["failures"].append(result)
734 elif self.options.xpass == XunitFormatter.RM_IGNORE:
735 # Ignore the xpass result as far as xUnit reporting goes.
736 pass
737 else:
738 raise Exception("unknown xpass option: {}".format(
739 self.options.xpass))
740
741 def _process_test_result(self, test_event):
742 """Processes the test_event known to be a test result.
743
744 This categorizes the event appropriately and stores the data needed
745 to generate the final xUnit report. This method skips events that
746 cannot be represented in xUnit output.
747 """
748 if "status" not in test_event:
749 raise Exception("test event dictionary missing 'status' key")
750
751 status = test_event["status"]
752 if status not in self.status_handlers:
753 raise Exception("test event status '{}' unsupported".format(
754 status))
755
756 # Call the status handler for the test result.
757 self.status_handlers[status](test_event)
758
759 def _common_add_testcase_entry(self, test_event, inner_content=None):
760 """Registers a testcase result, and returns the text created.
761
762 The caller is expected to manage failure/skip/success counts
763 in some kind of appropriate way. This call simply constructs
764 the XML and appends the returned result to the self.all_results
765 list.
766
767 @param test_event the test event dictionary.
768
769 @param inner_content if specified, gets included in the <testcase>
770 inner section, at the point before stdout and stderr would be
771 included. This is where a <failure/>, <skipped/>, <error/>, etc.
772 could go.
773
774 @return the text of the xml testcase element.
775 """
776
777 # Get elapsed time.
778 test_class = test_event["test_class"]
779 test_name = test_event["test_name"]
780 event_time = test_event["event_time"]
781 time_taken = self.elapsed_time_for_test(
782 test_class, test_name, event_time)
783
784 # Plumb in stdout/stderr once we shift over to only test results.
785 test_stdout = ''
786 test_stderr = ''
787
788 # Formulate the output xml.
789 if not inner_content:
790 inner_content = ""
791 result = (
792 '<testcase classname="{}" name="{}" time="{:.3f}">'
793 '{}{}{}</testcase>'.format(
794 test_class,
795 test_name,
796 time_taken,
797 inner_content,
798 test_stdout,
799 test_stderr))
800
801 # Save the result, update total test count.
802 with self.lock:
803 self.total_test_count += 1
804 self.elements["all"].append(result)
805
806 return result
807
808 def _end_session_internal(self):
809 """Flushes out the report of test executions to form valid xml output.
810
811 xUnit output is in XML. The reporting system cannot complete the
812 formatting of the output without knowing when there is no more input.
813 This call addresses notifcation of the completed test run and thus is
814 when we can finish off the report output.
815 """
816
817 # Figure out the counts line for the testsuite. If we have
818 # been counting either unexpected successes or expected
819 # failures, we'll output those in the counts, at the risk of
820 # being invalidated by a validating test results viewer.
821 # These aren't counted by default so they won't show up unless
822 # the user specified a formatter option to include them.
823 xfail_count = len(self.elements["expected_failures"])
824 xpass_count = len(self.elements["unexpected_successes"])
825 if xfail_count > 0 or xpass_count > 0:
826 extra_testsuite_attributes = (
827 ' expected-failures="{}"'
828 ' unexpected-successes="{}"'.format(xfail_count, xpass_count))
829 else:
830 extra_testsuite_attributes = ""
831
832 # Output the header.
833 self.out_file.write(
834 '<?xml version="1.0" encoding="{}"?>\n'
Todd Fiala038bf832015-09-18 01:43:08 +0000835 '<testsuites>'
Todd Fiala68615ce2015-09-15 21:38:04 +0000836 '<testsuite name="{}" tests="{}" errors="{}" failures="{}" '
837 'skip="{}"{}>\n'.format(
838 self.text_encoding,
839 "LLDB test suite",
840 self.total_test_count,
841 len(self.elements["errors"]),
842 len(self.elements["failures"]),
843 len(self.elements["skips"]),
844 extra_testsuite_attributes))
845
846 # Output each of the test result entries.
847 for result in self.elements["all"]:
848 self.out_file.write(result + '\n')
849
850 # Close off the test suite.
Todd Fiala038bf832015-09-18 01:43:08 +0000851 self.out_file.write('</testsuite></testsuites>\n')
Todd Fiala68615ce2015-09-15 21:38:04 +0000852
853 super(XunitFormatter, self).end_session()
854
855 def end_session(self):
856 with self.lock:
857 self._end_session_internal()
858
859
860class RawPickledFormatter(ResultsFormatter):
861 """Formats events as a pickled stream.
862
863 The parallel test runner has inferiors pickle their results and send them
864 over a socket back to the parallel test. The parallel test runner then
865 aggregates them into the final results formatter (e.g. xUnit).
866 """
867
868 @classmethod
869 def arg_parser(cls):
870 """@return arg parser used to parse formatter-specific options."""
871 parser = super(RawPickledFormatter, cls).arg_parser()
872 return parser
873
874 def __init__(self, out_file, options):
875 super(RawPickledFormatter, self).__init__(out_file, options)
876 self.pid = os.getpid()
877
878 def begin_session(self):
879 super(RawPickledFormatter, self).begin_session()
Todd Fiala33896a92015-09-18 21:01:13 +0000880 event = EventBuilder.base_event()
881 if event is None:
882 event = {}
883 event.update({
Todd Fiala68615ce2015-09-15 21:38:04 +0000884 "event": "session_begin",
885 "event_time": time.time(),
886 "pid": self.pid
887 })
Todd Fiala33896a92015-09-18 21:01:13 +0000888 self.process_event(event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000889
890 def process_event(self, test_event):
891 super(RawPickledFormatter, self).process_event(test_event)
892
Todd Fiala68615ce2015-09-15 21:38:04 +0000893 # Send it as {serialized_length_of_serialized_bytes}#{serialized_bytes}
894 pickled_message = cPickle.dumps(test_event)
895 self.out_file.send(
896 "{}#{}".format(len(pickled_message), pickled_message))
897
898 def end_session(self):
Todd Fiala33896a92015-09-18 21:01:13 +0000899 event = EventBuilder.base_event()
900 if event is None:
901 event = {}
902 event.update({
Todd Fiala68615ce2015-09-15 21:38:04 +0000903 "event": "session_end",
904 "event_time": time.time(),
905 "pid": self.pid
906 })
Todd Fiala33896a92015-09-18 21:01:13 +0000907 self.process_event(event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000908 super(RawPickledFormatter, self).end_session()
Todd Fiala33896a92015-09-18 21:01:13 +0000909
910
911class DumpFormatter(ResultsFormatter):
912 """Formats events to the file as their raw python dictionary format."""
913
914 def process_event(self, test_event):
915 super(DumpFormatter, self).process_event(test_event)
916 self.out_file.write("\n" + pprint.pformat(test_event) + "\n")