blob: 31b5090e81e5afa7e5129df5f3dd52c16c200f0a [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 Fialaf77f8ae2015-09-18 22:57:04 +000081 "test_filename": inspect.getfile(test.__class__)
Todd Fiala33896a92015-09-18 21:01:13 +000082 })
Todd Fialae83f1402015-09-18 22:45:31 +000083 return event
Todd Fiala68615ce2015-09-15 21:38:04 +000084
85 @staticmethod
86 def _error_tuple_class(error_tuple):
87 """Returns the unittest error tuple's error class as a string.
88
89 @param error_tuple the error tuple provided by the test framework.
90
91 @return the error type (typically an exception) raised by the
92 test framework.
93 """
94 type_var = error_tuple[0]
95 module = inspect.getmodule(type_var)
96 if module:
97 return "{}.{}".format(module.__name__, type_var.__name__)
98 else:
99 return type_var.__name__
100
101 @staticmethod
102 def _error_tuple_message(error_tuple):
103 """Returns the unittest error tuple's error message.
104
105 @param error_tuple the error tuple provided by the test framework.
106
107 @return the error message provided by the test framework.
108 """
109 return str(error_tuple[1])
110
111 @staticmethod
Todd Fialae7e911f2015-09-18 07:08:09 +0000112 def _error_tuple_traceback(error_tuple):
113 """Returns the unittest error tuple's error message.
114
115 @param error_tuple the error tuple provided by the test framework.
116
117 @return the error message provided by the test framework.
118 """
119 return error_tuple[2]
120
121 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000122 def _event_dictionary_test_result(test, status):
123 """Returns an event dictionary with common test result fields set.
124
125 @param test a unittest.TestCase instance.
126
127 @param status the status/result of the test
128 (e.g. "success", "failure", etc.)
129
130 @return the event dictionary
131 """
132 event = EventBuilder._event_dictionary_common(test, "test_result")
133 event["status"] = status
134 return event
135
136 @staticmethod
137 def _event_dictionary_issue(test, status, error_tuple):
138 """Returns an event dictionary with common issue-containing test result
139 fields set.
140
141 @param test a unittest.TestCase instance.
142
143 @param status the status/result of the test
144 (e.g. "success", "failure", etc.)
145
146 @param error_tuple the error tuple as reported by the test runner.
147 This is of the form (type<error>, error).
148
149 @return the event dictionary
150 """
151 event = EventBuilder._event_dictionary_test_result(test, status)
152 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
153 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
Todd Fiala33896a92015-09-18 21:01:13 +0000154 backtrace = EventBuilder._error_tuple_traceback(error_tuple)
155 if backtrace is not None:
156 event["issue_backtrace"] = traceback.format_tb(backtrace)
Todd Fiala68615ce2015-09-15 21:38:04 +0000157 return event
158
159 @staticmethod
160 def event_for_start(test):
161 """Returns an event dictionary for the test start event.
162
163 @param test a unittest.TestCase instance.
164
165 @return the event dictionary
166 """
167 return EventBuilder._event_dictionary_common(test, "test_start")
168
169 @staticmethod
170 def event_for_success(test):
171 """Returns an event dictionary for a successful test.
172
173 @param test a unittest.TestCase instance.
174
175 @return the event dictionary
176 """
177 return EventBuilder._event_dictionary_test_result(test, "success")
178
179 @staticmethod
180 def event_for_unexpected_success(test, bugnumber):
181 """Returns an event dictionary for a test that succeeded but was
182 expected to fail.
183
184 @param test a unittest.TestCase instance.
185
186 @param bugnumber the issue identifier for the bug tracking the
187 fix request for the test expected to fail (but is in fact
188 passing here).
189
190 @return the event dictionary
191
192 """
193 event = EventBuilder._event_dictionary_test_result(
194 test, "unexpected_success")
195 if bugnumber:
196 event["bugnumber"] = str(bugnumber)
197 return event
198
199 @staticmethod
200 def event_for_failure(test, error_tuple):
201 """Returns an event dictionary for a test that failed.
202
203 @param test a unittest.TestCase instance.
204
205 @param error_tuple the error tuple as reported by the test runner.
206 This is of the form (type<error>, error).
207
208 @return the event dictionary
209 """
210 return EventBuilder._event_dictionary_issue(
211 test, "failure", error_tuple)
212
213 @staticmethod
214 def event_for_expected_failure(test, error_tuple, bugnumber):
215 """Returns an event dictionary for a test that failed as expected.
216
217 @param test a unittest.TestCase instance.
218
219 @param error_tuple the error tuple as reported by the test runner.
220 This is of the form (type<error>, error).
221
222 @param bugnumber the issue identifier for the bug tracking the
223 fix request for the test expected to fail.
224
225 @return the event dictionary
226
227 """
228 event = EventBuilder._event_dictionary_issue(
229 test, "expected_failure", error_tuple)
230 if bugnumber:
231 event["bugnumber"] = str(bugnumber)
232 return event
233
234 @staticmethod
235 def event_for_skip(test, reason):
236 """Returns an event dictionary for a test that was skipped.
237
238 @param test a unittest.TestCase instance.
239
240 @param reason the reason why the test is being skipped.
241
242 @return the event dictionary
243 """
244 event = EventBuilder._event_dictionary_test_result(test, "skip")
245 event["skip_reason"] = reason
246 return event
247
248 @staticmethod
249 def event_for_error(test, error_tuple):
250 """Returns an event dictionary for a test that hit a test execution error.
251
252 @param test a unittest.TestCase instance.
253
254 @param error_tuple the error tuple as reported by the test runner.
255 This is of the form (type<error>, error).
256
257 @return the event dictionary
258 """
259 return EventBuilder._event_dictionary_issue(test, "error", error_tuple)
260
261 @staticmethod
262 def event_for_cleanup_error(test, error_tuple):
263 """Returns an event dictionary for a test that hit a test execution error
264 during the test cleanup phase.
265
266 @param test a unittest.TestCase instance.
267
268 @param error_tuple the error tuple as reported by the test runner.
269 This is of the form (type<error>, error).
270
271 @return the event dictionary
272 """
273 event = EventBuilder._event_dictionary_issue(
274 test, "error", error_tuple)
275 event["issue_phase"] = "cleanup"
276 return event
277
Todd Fiala33896a92015-09-18 21:01:13 +0000278 @staticmethod
279 def add_entries_to_all_events(entries_dict):
280 """Specifies a dictionary of entries to add to all test events.
281
282 This provides a mechanism for, say, a parallel test runner to
283 indicate to each inferior dotest.py that it should add a
284 worker index to each.
285
286 Calling this method replaces all previous entries added
287 by a prior call to this.
288
289 Event build methods will overwrite any entries that collide.
290 Thus, the passed in dictionary is the base, which gets merged
291 over by event building when keys collide.
292
293 @param entries_dict a dictionary containing key and value
294 pairs that should be merged into all events created by the
295 event generator. May be None to clear out any extra entries.
296 """
297 EventBuilder.BASE_DICTIONARY = dict(entries_dict)
298
Todd Fiala68615ce2015-09-15 21:38:04 +0000299
300class ResultsFormatter(object):
Todd Fiala33896a92015-09-18 21:01:13 +0000301
Todd Fiala68615ce2015-09-15 21:38:04 +0000302 """Provides interface to formatting test results out to a file-like object.
303
304 This class allows the LLDB test framework's raw test-realted
305 events to be processed and formatted in any manner desired.
306 Test events are represented by python dictionaries, formatted
307 as in the EventBuilder class above.
308
309 ResultFormatter instances are given a file-like object in which
310 to write their results.
311
312 ResultFormatter lifetime looks like the following:
313
314 # The result formatter is created.
315 # The argparse options dictionary is generated from calling
316 # the SomeResultFormatter.arg_parser() with the options data
317 # passed to dotest.py via the "--results-formatter-options"
318 # argument. See the help on that for syntactic requirements
319 # on getting that parsed correctly.
320 formatter = SomeResultFormatter(file_like_object, argpared_options_dict)
321
322 # Single call to session start, before parsing any events.
323 formatter.begin_session()
324
Todd Fialae83f1402015-09-18 22:45:31 +0000325 formatter.handle_event({"event":"initialize",...})
326
Todd Fiala68615ce2015-09-15 21:38:04 +0000327 # Zero or more calls specified for events recorded during the test session.
328 # The parallel test runner manages getting results from all the inferior
329 # dotest processes, so from a new format perspective, don't worry about
330 # that. The formatter will be presented with a single stream of events
331 # sandwiched between a single begin_session()/end_session() pair in the
332 # parallel test runner process/thread.
333 for event in zero_or_more_test_events():
Todd Fialae83f1402015-09-18 22:45:31 +0000334 formatter.handle_event(event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000335
Todd Fialae83f1402015-09-18 22:45:31 +0000336 # Single call to terminate/wrap-up. Formatters that need all the
337 # data before they can print a correct result (e.g. xUnit/JUnit),
338 # this is where the final report can be generated.
339 formatter.handle_event({"event":"terminate",...})
Todd Fiala68615ce2015-09-15 21:38:04 +0000340
341 It is not the formatter's responsibility to close the file_like_object.
342 (i.e. do not close it).
343
344 The lldb test framework passes these test events in real time, so they
345 arrive as they come in.
346
347 In the case of the parallel test runner, the dotest inferiors
348 add a 'pid' field to the dictionary that indicates which inferior
349 pid generated the event.
350
351 Note more events may be added in the future to support richer test
352 reporting functionality. One example: creating a true flaky test
353 result category so that unexpected successes really mean the test
354 is marked incorrectly (either should be marked flaky, or is indeed
355 passing consistently now and should have the xfail marker
356 removed). In this case, a flaky_success and flaky_fail event
357 likely will be added to capture these and support reporting things
358 like percentages of flaky test passing so we can see if we're
359 making some things worse/better with regards to failure rates.
360
361 Another example: announcing all the test methods that are planned
362 to be run, so we can better support redo operations of various kinds
363 (redo all non-run tests, redo non-run tests except the one that
364 was running [perhaps crashed], etc.)
365
366 Implementers are expected to override all the public methods
367 provided in this class. See each method's docstring to see
368 expectations about when the call should be chained.
369
370 """
371
372 @classmethod
373 def arg_parser(cls):
374 """@return arg parser used to parse formatter-specific options."""
375 parser = argparse.ArgumentParser(
376 description='{} options'.format(cls.__name__),
377 usage=('dotest.py --results-formatter-options='
378 '"--option1 value1 [--option2 value2 [...]]"'))
379 return parser
380
381 def __init__(self, out_file, options):
382 super(ResultsFormatter, self).__init__()
383 self.out_file = out_file
384 self.options = options
Greg Clayton1827fc22015-09-19 00:39:09 +0000385 self.using_terminal = False
Todd Fiala68615ce2015-09-15 21:38:04 +0000386 if not self.out_file:
387 raise Exception("ResultsFormatter created with no file object")
388 self.start_time_by_test = {}
Todd Fialade9a44e2015-09-22 00:15:50 +0000389 self.terminate_called = False
Todd Fiala68615ce2015-09-15 21:38:04 +0000390
391 # Lock that we use while mutating inner state, like the
392 # total test count and the elements. We minimize how
393 # long we hold the lock just to keep inner state safe, not
394 # entirely consistent from the outside.
395 self.lock = threading.Lock()
396
Todd Fialae83f1402015-09-18 22:45:31 +0000397 def handle_event(self, test_event):
398 """Handles the test event for collection into the formatter output.
Todd Fiala68615ce2015-09-15 21:38:04 +0000399
400 Derived classes may override this but should call down to this
401 implementation first.
402
403 @param test_event the test event as formatted by one of the
404 event_for_* calls.
405 """
Todd Fialade9a44e2015-09-22 00:15:50 +0000406 # Keep track of whether terminate was received. We do this so
407 # that a process can call the 'terminate' event on its own, to
408 # close down a formatter at the appropriate time. Then the
409 # atexit() cleanup can call the "terminate if it hasn't been
410 # called yet".
411 if test_event is not None:
412 if test_event.get("event", "") == "terminate":
413 self.terminate_called = True
Todd Fiala68615ce2015-09-15 21:38:04 +0000414
415 def track_start_time(self, test_class, test_name, start_time):
416 """Tracks the start time of a test so elapsed time can be computed.
417
418 This alleviates the need for test results to be processed serially
419 by test. It will save the start time for the test so that
420 elapsed_time_for_test() can compute the elapsed time properly.
421 """
422 if test_class is None or test_name is None:
423 return
424
425 test_key = "{}.{}".format(test_class, test_name)
426 with self.lock:
427 self.start_time_by_test[test_key] = start_time
428
429 def elapsed_time_for_test(self, test_class, test_name, end_time):
430 """Returns the elapsed time for a test.
431
432 This function can only be called once per test and requires that
433 the track_start_time() method be called sometime prior to calling
434 this method.
435 """
436 if test_class is None or test_name is None:
437 return -2.0
438
439 test_key = "{}.{}".format(test_class, test_name)
440 with self.lock:
441 if test_key not in self.start_time_by_test:
442 return -1.0
443 else:
444 start_time = self.start_time_by_test[test_key]
445 del self.start_time_by_test[test_key]
446 return end_time - start_time
447
Greg Clayton1827fc22015-09-19 00:39:09 +0000448 def is_using_terminal(self):
Todd Fialade9a44e2015-09-22 00:15:50 +0000449 """Returns True if this results formatter is using the terminal and
450 output should be avoided."""
Greg Clayton1827fc22015-09-19 00:39:09 +0000451 return self.using_terminal
Todd Fiala68615ce2015-09-15 21:38:04 +0000452
Todd Fialade9a44e2015-09-22 00:15:50 +0000453 def send_terminate_as_needed(self):
454 """Sends the terminate event if it hasn't been received yet."""
455 if not self.terminate_called:
456 terminate_event = EventBuilder.bare_event("terminate")
457 self.handle_event(terminate_event)
458
Todd Fiala68615ce2015-09-15 21:38:04 +0000459class XunitFormatter(ResultsFormatter):
460 """Provides xUnit-style formatted output.
461 """
462
463 # Result mapping arguments
464 RM_IGNORE = 'ignore'
465 RM_SUCCESS = 'success'
466 RM_FAILURE = 'failure'
467 RM_PASSTHRU = 'passthru'
468
469 @staticmethod
Todd Fiala8effde42015-09-18 16:00:52 +0000470 def _build_illegal_xml_regex():
Todd Fiala33896a92015-09-18 21:01:13 +0000471 """Contructs a regex to match all illegal xml characters.
472
473 Expects to be used against a unicode string."""
Todd Fiala8effde42015-09-18 16:00:52 +0000474 # Construct the range pairs of invalid unicode chareacters.
475 illegal_chars_u = [
476 (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
477 (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
478
479 # For wide builds, we have more.
480 if sys.maxunicode >= 0x10000:
481 illegal_chars_u.extend(
482 [(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
483 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
484 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
485 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
486 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
487 (0x10FFFE, 0x10FFFF)])
488
489 # Build up an array of range expressions.
490 illegal_ranges = [
491 "%s-%s" % (unichr(low), unichr(high))
492 for (low, high) in illegal_chars_u]
493
494 # Compile the regex
495 return re.compile(u'[%s]' % u''.join(illegal_ranges))
496
497 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000498 def _quote_attribute(text):
499 """Returns the given text in a manner safe for usage in an XML attribute.
500
501 @param text the text that should appear within an XML attribute.
502 @return the attribute-escaped version of the input text.
503 """
504 return xml.sax.saxutils.quoteattr(text)
505
Todd Fiala8effde42015-09-18 16:00:52 +0000506 def _replace_invalid_xml(self, str_or_unicode):
Todd Fiala33896a92015-09-18 21:01:13 +0000507 """Replaces invalid XML characters with a '?'.
508
509 @param str_or_unicode a string to replace invalid XML
510 characters within. Can be unicode or not. If not unicode,
511 assumes it is a byte string in utf-8 encoding.
512
513 @returns a utf-8-encoded byte string with invalid
514 XML replaced with '?'.
515 """
Todd Fiala8effde42015-09-18 16:00:52 +0000516 # Get the content into unicode
517 if isinstance(str_or_unicode, str):
518 unicode_content = str_or_unicode.decode('utf-8')
519 else:
520 unicode_content = str_or_unicode
521 return self.invalid_xml_re.sub(u'?', unicode_content).encode('utf-8')
522
Todd Fiala68615ce2015-09-15 21:38:04 +0000523 @classmethod
524 def arg_parser(cls):
525 """@return arg parser used to parse formatter-specific options."""
526 parser = super(XunitFormatter, cls).arg_parser()
527
528 # These are valid choices for results mapping.
529 results_mapping_choices = [
530 XunitFormatter.RM_IGNORE,
531 XunitFormatter.RM_SUCCESS,
532 XunitFormatter.RM_FAILURE,
533 XunitFormatter.RM_PASSTHRU]
534 parser.add_argument(
Todd Fiala33896a92015-09-18 21:01:13 +0000535 "--assert-on-unknown-events",
536 action="store_true",
537 help=('cause unknown test events to generate '
538 'a python assert. Default is to ignore.'))
539 parser.add_argument(
Todd Fiala68615ce2015-09-15 21:38:04 +0000540 "--xpass", action="store", choices=results_mapping_choices,
541 default=XunitFormatter.RM_FAILURE,
542 help=('specify mapping from unexpected success to jUnit/xUnit '
543 'result type'))
544 parser.add_argument(
545 "--xfail", action="store", choices=results_mapping_choices,
546 default=XunitFormatter.RM_IGNORE,
547 help=('specify mapping from expected failure to jUnit/xUnit '
548 'result type'))
549 return parser
550
551 def __init__(self, out_file, options):
552 """Initializes the XunitFormatter instance.
553 @param out_file file-like object where formatted output is written.
554 @param options_dict specifies a dictionary of options for the
555 formatter.
556 """
557 # Initialize the parent
558 super(XunitFormatter, self).__init__(out_file, options)
559 self.text_encoding = "UTF-8"
Todd Fiala8effde42015-09-18 16:00:52 +0000560 self.invalid_xml_re = XunitFormatter._build_illegal_xml_regex()
Todd Fiala68615ce2015-09-15 21:38:04 +0000561
562 self.total_test_count = 0
563
564 self.elements = {
565 "successes": [],
566 "errors": [],
567 "failures": [],
568 "skips": [],
569 "unexpected_successes": [],
570 "expected_failures": [],
571 "all": []
572 }
573
574 self.status_handlers = {
575 "success": self._handle_success,
576 "failure": self._handle_failure,
577 "error": self._handle_error,
578 "skip": self._handle_skip,
579 "expected_failure": self._handle_expected_failure,
580 "unexpected_success": self._handle_unexpected_success
581 }
582
Todd Fialae83f1402015-09-18 22:45:31 +0000583 def handle_event(self, test_event):
584 super(XunitFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000585
586 event_type = test_event["event"]
587 if event_type is None:
588 return
589
Todd Fialae83f1402015-09-18 22:45:31 +0000590 if event_type == "terminate":
591 self._finish_output()
592 elif event_type == "test_start":
Todd Fiala68615ce2015-09-15 21:38:04 +0000593 self.track_start_time(
594 test_event["test_class"],
595 test_event["test_name"],
596 test_event["event_time"])
597 elif event_type == "test_result":
598 self._process_test_result(test_event)
599 else:
Todd Fiala33896a92015-09-18 21:01:13 +0000600 # This is an unknown event.
601 if self.options.assert_on_unknown_events:
602 raise Exception("unknown event type {} from {}\n".format(
603 event_type, test_event))
Todd Fiala68615ce2015-09-15 21:38:04 +0000604
605 def _handle_success(self, test_event):
606 """Handles a test success.
607 @param test_event the test event to handle.
608 """
609 result = self._common_add_testcase_entry(test_event)
610 with self.lock:
611 self.elements["successes"].append(result)
612
613 def _handle_failure(self, test_event):
614 """Handles a test failure.
615 @param test_event the test event to handle.
616 """
Todd Fiala8effde42015-09-18 16:00:52 +0000617 message = self._replace_invalid_xml(test_event["issue_message"])
618 backtrace = self._replace_invalid_xml(
619 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000620
Todd Fiala68615ce2015-09-15 21:38:04 +0000621 result = self._common_add_testcase_entry(
622 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000623 inner_content=(
624 '<failure type={} message={}><![CDATA[{}]]></failure>'.format(
625 XunitFormatter._quote_attribute(test_event["issue_class"]),
626 XunitFormatter._quote_attribute(message),
627 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000628 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000629 with self.lock:
630 self.elements["failures"].append(result)
631
632 def _handle_error(self, test_event):
633 """Handles a test error.
634 @param test_event the test event to handle.
635 """
Todd Fiala8effde42015-09-18 16:00:52 +0000636 message = self._replace_invalid_xml(test_event["issue_message"])
637 backtrace = self._replace_invalid_xml(
638 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000639
Todd Fiala68615ce2015-09-15 21:38:04 +0000640 result = self._common_add_testcase_entry(
641 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000642 inner_content=(
643 '<error type={} message={}><![CDATA[{}]]></error>'.format(
644 XunitFormatter._quote_attribute(test_event["issue_class"]),
645 XunitFormatter._quote_attribute(message),
646 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000647 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000648 with self.lock:
649 self.elements["errors"].append(result)
650
651 def _handle_skip(self, test_event):
652 """Handles a skipped test.
653 @param test_event the test event to handle.
654 """
Todd Fiala8effde42015-09-18 16:00:52 +0000655 reason = self._replace_invalid_xml(test_event.get("skip_reason", ""))
Todd Fiala68615ce2015-09-15 21:38:04 +0000656 result = self._common_add_testcase_entry(
657 test_event,
658 inner_content='<skipped message={} />'.format(
Todd Fiala8effde42015-09-18 16:00:52 +0000659 XunitFormatter._quote_attribute(reason)))
Todd Fiala68615ce2015-09-15 21:38:04 +0000660 with self.lock:
661 self.elements["skips"].append(result)
662
663 def _handle_expected_failure(self, test_event):
664 """Handles a test that failed as expected.
665 @param test_event the test event to handle.
666 """
667 if self.options.xfail == XunitFormatter.RM_PASSTHRU:
668 # This is not a natively-supported junit/xunit
669 # testcase mode, so it might fail a validating
670 # test results viewer.
671 if "bugnumber" in test_event:
672 bug_id_attribute = 'bug-id={} '.format(
673 XunitFormatter._quote_attribute(test_event["bugnumber"]))
674 else:
675 bug_id_attribute = ''
676
677 result = self._common_add_testcase_entry(
678 test_event,
679 inner_content=(
680 '<expected-failure {}type={} message={} />'.format(
681 bug_id_attribute,
682 XunitFormatter._quote_attribute(
683 test_event["issue_class"]),
684 XunitFormatter._quote_attribute(
685 test_event["issue_message"]))
686 ))
687 with self.lock:
688 self.elements["expected_failures"].append(result)
689 elif self.options.xfail == XunitFormatter.RM_SUCCESS:
690 result = self._common_add_testcase_entry(test_event)
691 with self.lock:
692 self.elements["successes"].append(result)
693 elif self.options.xfail == XunitFormatter.RM_FAILURE:
694 result = self._common_add_testcase_entry(
695 test_event,
696 inner_content='<failure type={} message={} />'.format(
697 XunitFormatter._quote_attribute(test_event["issue_class"]),
698 XunitFormatter._quote_attribute(
699 test_event["issue_message"])))
700 with self.lock:
701 self.elements["failures"].append(result)
702 elif self.options.xfail == XunitFormatter.RM_IGNORE:
703 pass
704 else:
705 raise Exception(
706 "unknown xfail option: {}".format(self.options.xfail))
707
708 def _handle_unexpected_success(self, test_event):
709 """Handles a test that passed but was expected to fail.
710 @param test_event the test event to handle.
711 """
712 if self.options.xpass == XunitFormatter.RM_PASSTHRU:
713 # This is not a natively-supported junit/xunit
714 # testcase mode, so it might fail a validating
715 # test results viewer.
716 result = self._common_add_testcase_entry(
717 test_event,
718 inner_content=("<unexpected-success />"))
719 with self.lock:
720 self.elements["unexpected_successes"].append(result)
721 elif self.options.xpass == XunitFormatter.RM_SUCCESS:
722 # Treat the xpass as a success.
723 result = self._common_add_testcase_entry(test_event)
724 with self.lock:
725 self.elements["successes"].append(result)
726 elif self.options.xpass == XunitFormatter.RM_FAILURE:
727 # Treat the xpass as a failure.
728 if "bugnumber" in test_event:
729 message = "unexpected success (bug_id:{})".format(
730 test_event["bugnumber"])
731 else:
732 message = "unexpected success (bug_id:none)"
733 result = self._common_add_testcase_entry(
734 test_event,
735 inner_content='<failure type={} message={} />'.format(
736 XunitFormatter._quote_attribute("unexpected_success"),
737 XunitFormatter._quote_attribute(message)))
738 with self.lock:
739 self.elements["failures"].append(result)
740 elif self.options.xpass == XunitFormatter.RM_IGNORE:
741 # Ignore the xpass result as far as xUnit reporting goes.
742 pass
743 else:
744 raise Exception("unknown xpass option: {}".format(
745 self.options.xpass))
746
747 def _process_test_result(self, test_event):
748 """Processes the test_event known to be a test result.
749
750 This categorizes the event appropriately and stores the data needed
751 to generate the final xUnit report. This method skips events that
752 cannot be represented in xUnit output.
753 """
754 if "status" not in test_event:
755 raise Exception("test event dictionary missing 'status' key")
756
757 status = test_event["status"]
758 if status not in self.status_handlers:
759 raise Exception("test event status '{}' unsupported".format(
760 status))
761
762 # Call the status handler for the test result.
763 self.status_handlers[status](test_event)
764
765 def _common_add_testcase_entry(self, test_event, inner_content=None):
766 """Registers a testcase result, and returns the text created.
767
768 The caller is expected to manage failure/skip/success counts
769 in some kind of appropriate way. This call simply constructs
770 the XML and appends the returned result to the self.all_results
771 list.
772
773 @param test_event the test event dictionary.
774
775 @param inner_content if specified, gets included in the <testcase>
776 inner section, at the point before stdout and stderr would be
777 included. This is where a <failure/>, <skipped/>, <error/>, etc.
778 could go.
779
780 @return the text of the xml testcase element.
781 """
782
783 # Get elapsed time.
784 test_class = test_event["test_class"]
785 test_name = test_event["test_name"]
786 event_time = test_event["event_time"]
787 time_taken = self.elapsed_time_for_test(
788 test_class, test_name, event_time)
789
790 # Plumb in stdout/stderr once we shift over to only test results.
791 test_stdout = ''
792 test_stderr = ''
793
794 # Formulate the output xml.
795 if not inner_content:
796 inner_content = ""
797 result = (
798 '<testcase classname="{}" name="{}" time="{:.3f}">'
799 '{}{}{}</testcase>'.format(
800 test_class,
801 test_name,
802 time_taken,
803 inner_content,
804 test_stdout,
805 test_stderr))
806
807 # Save the result, update total test count.
808 with self.lock:
809 self.total_test_count += 1
810 self.elements["all"].append(result)
811
812 return result
813
Todd Fialae83f1402015-09-18 22:45:31 +0000814 def _finish_output_no_lock(self):
Todd Fiala68615ce2015-09-15 21:38:04 +0000815 """Flushes out the report of test executions to form valid xml output.
816
817 xUnit output is in XML. The reporting system cannot complete the
818 formatting of the output without knowing when there is no more input.
819 This call addresses notifcation of the completed test run and thus is
820 when we can finish off the report output.
821 """
822
823 # Figure out the counts line for the testsuite. If we have
824 # been counting either unexpected successes or expected
825 # failures, we'll output those in the counts, at the risk of
826 # being invalidated by a validating test results viewer.
827 # These aren't counted by default so they won't show up unless
828 # the user specified a formatter option to include them.
829 xfail_count = len(self.elements["expected_failures"])
830 xpass_count = len(self.elements["unexpected_successes"])
831 if xfail_count > 0 or xpass_count > 0:
832 extra_testsuite_attributes = (
833 ' expected-failures="{}"'
834 ' unexpected-successes="{}"'.format(xfail_count, xpass_count))
835 else:
836 extra_testsuite_attributes = ""
837
838 # Output the header.
839 self.out_file.write(
840 '<?xml version="1.0" encoding="{}"?>\n'
Todd Fiala038bf832015-09-18 01:43:08 +0000841 '<testsuites>'
Todd Fiala68615ce2015-09-15 21:38:04 +0000842 '<testsuite name="{}" tests="{}" errors="{}" failures="{}" '
843 'skip="{}"{}>\n'.format(
844 self.text_encoding,
845 "LLDB test suite",
846 self.total_test_count,
847 len(self.elements["errors"]),
848 len(self.elements["failures"]),
849 len(self.elements["skips"]),
850 extra_testsuite_attributes))
851
852 # Output each of the test result entries.
853 for result in self.elements["all"]:
854 self.out_file.write(result + '\n')
855
856 # Close off the test suite.
Todd Fiala038bf832015-09-18 01:43:08 +0000857 self.out_file.write('</testsuite></testsuites>\n')
Todd Fiala68615ce2015-09-15 21:38:04 +0000858
Todd Fialae83f1402015-09-18 22:45:31 +0000859 def _finish_output(self):
Todd Fiala68615ce2015-09-15 21:38:04 +0000860 with self.lock:
Todd Fialae83f1402015-09-18 22:45:31 +0000861 self._finish_output_no_lock()
Todd Fiala68615ce2015-09-15 21:38:04 +0000862
863
864class RawPickledFormatter(ResultsFormatter):
865 """Formats events as a pickled stream.
866
867 The parallel test runner has inferiors pickle their results and send them
868 over a socket back to the parallel test. The parallel test runner then
869 aggregates them into the final results formatter (e.g. xUnit).
870 """
871
872 @classmethod
873 def arg_parser(cls):
874 """@return arg parser used to parse formatter-specific options."""
875 parser = super(RawPickledFormatter, cls).arg_parser()
876 return parser
877
878 def __init__(self, out_file, options):
879 super(RawPickledFormatter, self).__init__(out_file, options)
880 self.pid = os.getpid()
881
Todd Fialae83f1402015-09-18 22:45:31 +0000882 def handle_event(self, test_event):
883 super(RawPickledFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000884
Todd Fialae83f1402015-09-18 22:45:31 +0000885 # Convert initialize/terminate events into job_begin/job_end events.
886 event_type = test_event["event"]
887 if event_type is None:
888 return
889
890 if event_type == "initialize":
891 test_event["event"] = "job_begin"
892 elif event_type == "terminate":
893 test_event["event"] = "job_end"
894
895 # Tack on the pid.
896 test_event["pid"] = self.pid
Todd Fiala68615ce2015-09-15 21:38:04 +0000897
Todd Fiala68615ce2015-09-15 21:38:04 +0000898 # Send it as {serialized_length_of_serialized_bytes}#{serialized_bytes}
899 pickled_message = cPickle.dumps(test_event)
900 self.out_file.send(
901 "{}#{}".format(len(pickled_message), pickled_message))
902
Greg Clayton1827fc22015-09-19 00:39:09 +0000903class Curses(ResultsFormatter):
904 """Receives live results from tests that are running and reports them to the terminal in a curses GUI"""
905
906 def clear_line(self, y):
907 self.out_file.write("\033[%u;0H\033[2K" % (y))
908 self.out_file.flush()
909
910 def print_line(self, y, str):
911 self.out_file.write("\033[%u;0H\033[2K%s" % (y, str))
912 self.out_file.flush()
913
914 def __init__(self, out_file, options):
915 # Initialize the parent
916 super(Curses, self).__init__(out_file, options)
917 self.using_terminal = True
918 self.have_curses = True
919 self.initialize_event = None
920 self.jobs = [None] * 64
921 self.job_tests = [None] * 64
922 try:
923 import lldbcurses
924 self.main_window = lldbcurses.intialize_curses()
Greg Clayton1827fc22015-09-19 00:39:09 +0000925 self.main_window.refresh()
926 except:
927 self.have_curses = False
928 lldbcurses.terminate_curses()
929 self.using_terminal = False
930 print "Unexpected error:", sys.exc_info()[0]
931 raise
932
933
934 self.line_dict = dict()
935 self.events_file = open("/tmp/events.txt", "w")
936 # self.formatters = list()
937 # if tee_results_formatter:
938 # self.formatters.append(tee_results_formatter)
939
940 def status_to_short_str(self, status):
941 if status == 'success':
942 return '.'
943 elif status == 'failure':
944 return 'F'
945 elif status == 'unexpected_success':
946 return '?'
947 elif status == 'expected_failure':
948 return 'X'
949 elif status == 'skip':
950 return 'S'
951 elif status == 'error':
952 return 'E'
953 else:
954 return status
955 def handle_event(self, test_event):
Greg Claytonb0d148e2015-09-21 17:25:01 +0000956 with self.lock:
957 super(Curses, self).handle_event(test_event)
958 # for formatter in self.formatters:
959 # formatter.process_event(test_event)
960 if self.have_curses:
961 import lldbcurses
962 worker_index = -1
963 if 'worker_index' in test_event:
964 worker_index = test_event['worker_index']
965 if 'event' in test_event:
966 print >>self.events_file, str(test_event)
967 event = test_event['event']
968 if event == 'test_start':
969 name = test_event['test_class'] + '.' + test_event['test_name']
970 self.job_tests[worker_index] = test_event
971 if 'pid' in test_event:
972 line = 'pid: %5d ' % (test_event['pid']) + name
973 else:
974 line = name
975 self.job_panel.set_line(worker_index, line)
976 self.main_window.refresh()
977 elif event == 'test_result':
978 status = test_event['status']
979 self.status_panel.increment_status(status)
980 if 'pid' in test_event:
981 line = 'pid: %5d ' % (test_event['pid'])
982 else:
983 line = ''
984 self.job_panel.set_line(worker_index, line)
985 # if status != 'success' and status != 'skip' and status != 'expect_failure':
986 name = test_event['test_class'] + '.' + test_event['test_name']
987 time = test_event['event_time'] - self.job_tests[worker_index]['event_time']
988 self.fail_panel.append_line('%s (%6.2f sec) %s' % (self.status_to_short_str(status), time, name))
989 self.main_window.refresh()
990 self.job_tests[worker_index] = ''
991 elif event == 'job_begin':
992 self.jobs[worker_index] = test_event
993 if 'pid' in test_event:
994 line = 'pid: %5d ' % (test_event['pid'])
995 else:
996 line = ''
997 self.job_panel.set_line(worker_index, line)
998 elif event == 'job_end':
999 self.jobs[worker_index] = ''
1000 self.job_panel.set_line(worker_index, '')
1001 elif event == 'initialize':
1002 self.initialize_event = test_event
1003 num_jobs = test_event['worker_count']
1004 job_frame = self.main_window.get_contained_rect(height=num_jobs+2)
1005 fail_frame = self.main_window.get_contained_rect(top_inset=num_jobs+2, bottom_inset=1)
1006 status_frame = self.main_window.get_contained_rect(height=1, top_inset=self.main_window.get_size().h-1)
1007 self.job_panel = lldbcurses.BoxedPanel(job_frame, "Jobs")
1008 self.fail_panel = lldbcurses.BoxedPanel(fail_frame, "Completed Tests")
1009 self.status_panel = lldbcurses.StatusPanel(status_frame)
1010 self.status_panel.add_status_item(name="success", title="Success", format="%u", width=20, value=0, update=False)
1011 self.status_panel.add_status_item(name="failure", title="Failure", format="%u", width=20, value=0, update=False)
1012 self.status_panel.add_status_item(name="error", title="Error", format="%u", width=20, value=0, update=False)
1013 self.status_panel.add_status_item(name="skip", title="Skipped", format="%u", width=20, value=0, update=True)
1014 self.status_panel.add_status_item(name="expected_failure", title="Expected Failure", format="%u", width=30, value=0, update=False)
1015 self.status_panel.add_status_item(name="unexpected_success", title="Unexpected Success", format="%u", width=30, value=0, update=False)
1016 self.main_window.refresh()
1017 elif event == 'terminate':
1018 lldbcurses.terminate_curses()
1019 self.using_terminal = False
1020
Todd Fiala33896a92015-09-18 21:01:13 +00001021
1022class DumpFormatter(ResultsFormatter):
1023 """Formats events to the file as their raw python dictionary format."""
1024
Todd Fialae83f1402015-09-18 22:45:31 +00001025 def handle_event(self, test_event):
1026 super(DumpFormatter, self).handle_event(test_event)
Todd Fiala33896a92015-09-18 21:01:13 +00001027 self.out_file.write("\n" + pprint.pformat(test_event) + "\n")