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