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