blob: 12fb2e51d77c1d93e12ec2b061208a691e7facf6 [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
Zachary Turner35d017f2015-10-23 17:04:29 +000011from __future__ import print_function
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000012from __future__ import absolute_import
Zachary Turner35d017f2015-10-23 17:04:29 +000013
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000014# System modules
Todd Fiala68615ce2015-09-15 21:38:04 +000015import argparse
Todd Fiala68615ce2015-09-15 21:38:04 +000016import inspect
17import os
Todd Fiala33896a92015-09-18 21:01:13 +000018import pprint
Todd Fiala8effde42015-09-18 16:00:52 +000019import re
Todd Fiala68615ce2015-09-15 21:38:04 +000020import sys
21import threading
22import time
Todd Fialae7e911f2015-09-18 07:08:09 +000023import traceback
Todd Fiala68615ce2015-09-15 21:38:04 +000024import xml.sax.saxutils
25
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000026# Third-party modules
Zachary Turner58968ac2015-10-23 19:52:36 +000027import six
Zachary Turner814236d2015-10-21 17:48:52 +000028from six.moves import cPickle
29
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000030# LLDB modules
Todd Fiala68615ce2015-09-15 21:38:04 +000031
Todd Fiala46a4e342015-12-02 18:48:38 +000032
Todd Fiala68615ce2015-09-15 21:38:04 +000033class EventBuilder(object):
34 """Helper class to build test result event dictionaries."""
Todd Fiala33896a92015-09-18 21:01:13 +000035
36 BASE_DICTIONARY = None
37
Todd Fiala46a4e342015-12-02 18:48:38 +000038 # Test Status Tags
39 STATUS_SUCCESS = "success"
40 STATUS_FAILURE = "failure"
41 STATUS_EXPECTED_FAILURE = "expected_failure"
42 STATUS_UNEXPECTED_SUCCESS = "unexpected_success"
43 STATUS_SKIP = "skip"
44 STATUS_ERROR = "error"
45
Todd Fiala68615ce2015-09-15 21:38:04 +000046 @staticmethod
47 def _get_test_name_info(test):
48 """Returns (test-class-name, test-method-name) from a test case instance.
49
50 @param test a unittest.TestCase instance.
51
52 @return tuple containing (test class name, test method name)
53 """
54 test_class_components = test.id().split(".")
55 test_class_name = ".".join(test_class_components[:-1])
56 test_name = test_class_components[-1]
57 return (test_class_name, test_name)
58
59 @staticmethod
Todd Fialae83f1402015-09-18 22:45:31 +000060 def bare_event(event_type):
61 """Creates an event with default additions, event type and timestamp.
62
63 @param event_type the value set for the "event" key, used
64 to distinguish events.
65
66 @returns an event dictionary with all default additions, the "event"
67 key set to the passed in event_type, and the event_time value set to
68 time.time().
69 """
70 if EventBuilder.BASE_DICTIONARY is not None:
71 # Start with a copy of the "always include" entries.
72 event = dict(EventBuilder.BASE_DICTIONARY)
73 else:
74 event = {}
75
76 event.update({
77 "event": event_type,
78 "event_time": time.time()
79 })
80 return event
81
82 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +000083 def _event_dictionary_common(test, event_type):
84 """Returns an event dictionary setup with values for the given event type.
85
86 @param test the unittest.TestCase instance
87
88 @param event_type the name of the event type (string).
89
90 @return event dictionary with common event fields set.
91 """
92 test_class_name, test_name = EventBuilder._get_test_name_info(test)
Todd Fiala33896a92015-09-18 21:01:13 +000093
Todd Fialae83f1402015-09-18 22:45:31 +000094 event = EventBuilder.bare_event(event_type)
95 event.update({
Todd Fiala68615ce2015-09-15 21:38:04 +000096 "test_class": test_class_name,
97 "test_name": test_name,
Todd Fialaf77f8ae2015-09-18 22:57:04 +000098 "test_filename": inspect.getfile(test.__class__)
Todd Fiala33896a92015-09-18 21:01:13 +000099 })
Todd Fialae83f1402015-09-18 22:45:31 +0000100 return event
Todd Fiala68615ce2015-09-15 21:38:04 +0000101
102 @staticmethod
103 def _error_tuple_class(error_tuple):
104 """Returns the unittest error tuple's error class as a string.
105
106 @param error_tuple the error tuple provided by the test framework.
107
108 @return the error type (typically an exception) raised by the
109 test framework.
110 """
111 type_var = error_tuple[0]
112 module = inspect.getmodule(type_var)
113 if module:
114 return "{}.{}".format(module.__name__, type_var.__name__)
115 else:
116 return type_var.__name__
117
118 @staticmethod
119 def _error_tuple_message(error_tuple):
120 """Returns the unittest error tuple's error message.
121
122 @param error_tuple the error tuple provided by the test framework.
123
124 @return the error message provided by the test framework.
125 """
126 return str(error_tuple[1])
127
128 @staticmethod
Todd Fialae7e911f2015-09-18 07:08:09 +0000129 def _error_tuple_traceback(error_tuple):
130 """Returns the unittest error tuple's error message.
131
132 @param error_tuple the error tuple provided by the test framework.
133
134 @return the error message provided by the test framework.
135 """
136 return error_tuple[2]
137
138 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000139 def _event_dictionary_test_result(test, status):
140 """Returns an event dictionary with common test result fields set.
141
142 @param test a unittest.TestCase instance.
143
144 @param status the status/result of the test
145 (e.g. "success", "failure", etc.)
146
147 @return the event dictionary
148 """
149 event = EventBuilder._event_dictionary_common(test, "test_result")
150 event["status"] = status
151 return event
152
153 @staticmethod
154 def _event_dictionary_issue(test, status, error_tuple):
155 """Returns an event dictionary with common issue-containing test result
156 fields set.
157
158 @param test a unittest.TestCase instance.
159
160 @param status the status/result of the test
161 (e.g. "success", "failure", etc.)
162
163 @param error_tuple the error tuple as reported by the test runner.
164 This is of the form (type<error>, error).
165
166 @return the event dictionary
167 """
168 event = EventBuilder._event_dictionary_test_result(test, status)
169 event["issue_class"] = EventBuilder._error_tuple_class(error_tuple)
170 event["issue_message"] = EventBuilder._error_tuple_message(error_tuple)
Todd Fiala33896a92015-09-18 21:01:13 +0000171 backtrace = EventBuilder._error_tuple_traceback(error_tuple)
172 if backtrace is not None:
173 event["issue_backtrace"] = traceback.format_tb(backtrace)
Todd Fiala68615ce2015-09-15 21:38:04 +0000174 return event
175
176 @staticmethod
177 def event_for_start(test):
178 """Returns an event dictionary for the test start event.
179
180 @param test a unittest.TestCase instance.
181
182 @return the event dictionary
183 """
184 return EventBuilder._event_dictionary_common(test, "test_start")
185
186 @staticmethod
187 def event_for_success(test):
188 """Returns an event dictionary for a successful test.
189
190 @param test a unittest.TestCase instance.
191
192 @return the event dictionary
193 """
Todd Fiala46a4e342015-12-02 18:48:38 +0000194 return EventBuilder._event_dictionary_test_result(
195 test, EventBuilder.STATUS_SUCCESS)
Todd Fiala68615ce2015-09-15 21:38:04 +0000196
197 @staticmethod
198 def event_for_unexpected_success(test, bugnumber):
199 """Returns an event dictionary for a test that succeeded but was
200 expected to fail.
201
202 @param test a unittest.TestCase instance.
203
204 @param bugnumber the issue identifier for the bug tracking the
205 fix request for the test expected to fail (but is in fact
206 passing here).
207
208 @return the event dictionary
209
210 """
211 event = EventBuilder._event_dictionary_test_result(
Todd Fiala46a4e342015-12-02 18:48:38 +0000212 test, EventBuilder.STATUS_UNEXPECTED_SUCCESS)
Todd Fiala68615ce2015-09-15 21:38:04 +0000213 if bugnumber:
214 event["bugnumber"] = str(bugnumber)
215 return event
216
217 @staticmethod
218 def event_for_failure(test, error_tuple):
219 """Returns an event dictionary for a test that failed.
220
221 @param test a unittest.TestCase instance.
222
223 @param error_tuple the error tuple as reported by the test runner.
224 This is of the form (type<error>, error).
225
226 @return the event dictionary
227 """
228 return EventBuilder._event_dictionary_issue(
Todd Fiala46a4e342015-12-02 18:48:38 +0000229 test, EventBuilder.STATUS_FAILURE, error_tuple)
Todd Fiala68615ce2015-09-15 21:38:04 +0000230
231 @staticmethod
232 def event_for_expected_failure(test, error_tuple, bugnumber):
233 """Returns an event dictionary for a test that failed as expected.
234
235 @param test a unittest.TestCase instance.
236
237 @param error_tuple the error tuple as reported by the test runner.
238 This is of the form (type<error>, error).
239
240 @param bugnumber the issue identifier for the bug tracking the
241 fix request for the test expected to fail.
242
243 @return the event dictionary
244
245 """
246 event = EventBuilder._event_dictionary_issue(
Todd Fiala46a4e342015-12-02 18:48:38 +0000247 test, EventBuilder.STATUS_EXPECTED_FAILURE, error_tuple)
Todd Fiala68615ce2015-09-15 21:38:04 +0000248 if bugnumber:
249 event["bugnumber"] = str(bugnumber)
250 return event
251
252 @staticmethod
253 def event_for_skip(test, reason):
254 """Returns an event dictionary for a test that was skipped.
255
256 @param test a unittest.TestCase instance.
257
258 @param reason the reason why the test is being skipped.
259
260 @return the event dictionary
261 """
Todd Fiala46a4e342015-12-02 18:48:38 +0000262 event = EventBuilder._event_dictionary_test_result(
263 test, EventBuilder.STATUS_SKIP)
Todd Fiala68615ce2015-09-15 21:38:04 +0000264 event["skip_reason"] = reason
265 return event
266
267 @staticmethod
268 def event_for_error(test, error_tuple):
269 """Returns an event dictionary for a test that hit a test execution error.
270
271 @param test a unittest.TestCase instance.
272
273 @param error_tuple the error tuple as reported by the test runner.
274 This is of the form (type<error>, error).
275
276 @return the event dictionary
277 """
Todd Fiala46a4e342015-12-02 18:48:38 +0000278 return EventBuilder._event_dictionary_issue(
279 test, EventBuilder.STATUS_ERROR, error_tuple)
Todd Fiala68615ce2015-09-15 21:38:04 +0000280
281 @staticmethod
282 def event_for_cleanup_error(test, error_tuple):
283 """Returns an event dictionary for a test that hit a test execution error
284 during the test cleanup phase.
285
286 @param test a unittest.TestCase instance.
287
288 @param error_tuple the error tuple as reported by the test runner.
289 This is of the form (type<error>, error).
290
291 @return the event dictionary
292 """
293 event = EventBuilder._event_dictionary_issue(
Todd Fiala46a4e342015-12-02 18:48:38 +0000294 test, EventBuilder.STATUS_ERROR, error_tuple)
Todd Fiala68615ce2015-09-15 21:38:04 +0000295 event["issue_phase"] = "cleanup"
296 return event
297
Todd Fiala33896a92015-09-18 21:01:13 +0000298 @staticmethod
299 def add_entries_to_all_events(entries_dict):
300 """Specifies a dictionary of entries to add to all test events.
301
302 This provides a mechanism for, say, a parallel test runner to
303 indicate to each inferior dotest.py that it should add a
304 worker index to each.
305
306 Calling this method replaces all previous entries added
307 by a prior call to this.
308
309 Event build methods will overwrite any entries that collide.
310 Thus, the passed in dictionary is the base, which gets merged
311 over by event building when keys collide.
312
313 @param entries_dict a dictionary containing key and value
314 pairs that should be merged into all events created by the
315 event generator. May be None to clear out any extra entries.
316 """
317 EventBuilder.BASE_DICTIONARY = dict(entries_dict)
318
Todd Fiala68615ce2015-09-15 21:38:04 +0000319
320class ResultsFormatter(object):
Todd Fiala33896a92015-09-18 21:01:13 +0000321
Todd Fiala68615ce2015-09-15 21:38:04 +0000322 """Provides interface to formatting test results out to a file-like object.
323
324 This class allows the LLDB test framework's raw test-realted
325 events to be processed and formatted in any manner desired.
326 Test events are represented by python dictionaries, formatted
327 as in the EventBuilder class above.
328
329 ResultFormatter instances are given a file-like object in which
330 to write their results.
331
332 ResultFormatter lifetime looks like the following:
333
334 # The result formatter is created.
335 # The argparse options dictionary is generated from calling
336 # the SomeResultFormatter.arg_parser() with the options data
337 # passed to dotest.py via the "--results-formatter-options"
338 # argument. See the help on that for syntactic requirements
339 # on getting that parsed correctly.
340 formatter = SomeResultFormatter(file_like_object, argpared_options_dict)
341
342 # Single call to session start, before parsing any events.
343 formatter.begin_session()
344
Todd Fialae83f1402015-09-18 22:45:31 +0000345 formatter.handle_event({"event":"initialize",...})
346
Todd Fiala68615ce2015-09-15 21:38:04 +0000347 # Zero or more calls specified for events recorded during the test session.
348 # The parallel test runner manages getting results from all the inferior
349 # dotest processes, so from a new format perspective, don't worry about
350 # that. The formatter will be presented with a single stream of events
351 # sandwiched between a single begin_session()/end_session() pair in the
352 # parallel test runner process/thread.
353 for event in zero_or_more_test_events():
Todd Fialae83f1402015-09-18 22:45:31 +0000354 formatter.handle_event(event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000355
Todd Fialae83f1402015-09-18 22:45:31 +0000356 # Single call to terminate/wrap-up. Formatters that need all the
357 # data before they can print a correct result (e.g. xUnit/JUnit),
358 # this is where the final report can be generated.
359 formatter.handle_event({"event":"terminate",...})
Todd Fiala68615ce2015-09-15 21:38:04 +0000360
361 It is not the formatter's responsibility to close the file_like_object.
362 (i.e. do not close it).
363
364 The lldb test framework passes these test events in real time, so they
365 arrive as they come in.
366
367 In the case of the parallel test runner, the dotest inferiors
368 add a 'pid' field to the dictionary that indicates which inferior
369 pid generated the event.
370
371 Note more events may be added in the future to support richer test
372 reporting functionality. One example: creating a true flaky test
373 result category so that unexpected successes really mean the test
374 is marked incorrectly (either should be marked flaky, or is indeed
375 passing consistently now and should have the xfail marker
376 removed). In this case, a flaky_success and flaky_fail event
377 likely will be added to capture these and support reporting things
378 like percentages of flaky test passing so we can see if we're
379 making some things worse/better with regards to failure rates.
380
381 Another example: announcing all the test methods that are planned
382 to be run, so we can better support redo operations of various kinds
383 (redo all non-run tests, redo non-run tests except the one that
384 was running [perhaps crashed], etc.)
385
386 Implementers are expected to override all the public methods
387 provided in this class. See each method's docstring to see
388 expectations about when the call should be chained.
389
390 """
Todd Fiala68615ce2015-09-15 21:38:04 +0000391 @classmethod
392 def arg_parser(cls):
393 """@return arg parser used to parse formatter-specific options."""
394 parser = argparse.ArgumentParser(
395 description='{} options'.format(cls.__name__),
396 usage=('dotest.py --results-formatter-options='
397 '"--option1 value1 [--option2 value2 [...]]"'))
398 return parser
399
400 def __init__(self, out_file, options):
401 super(ResultsFormatter, self).__init__()
402 self.out_file = out_file
403 self.options = options
Greg Clayton1827fc22015-09-19 00:39:09 +0000404 self.using_terminal = False
Todd Fiala68615ce2015-09-15 21:38:04 +0000405 if not self.out_file:
406 raise Exception("ResultsFormatter created with no file object")
407 self.start_time_by_test = {}
Todd Fialade9a44e2015-09-22 00:15:50 +0000408 self.terminate_called = False
Todd Fiala68615ce2015-09-15 21:38:04 +0000409
Todd Fiala46a4e342015-12-02 18:48:38 +0000410 # Store counts of test_result events by status.
411 self.result_status_counts = {
412 EventBuilder.STATUS_SUCCESS: 0,
413 EventBuilder.STATUS_EXPECTED_FAILURE: 0,
414 EventBuilder.STATUS_SKIP: 0,
415 EventBuilder.STATUS_UNEXPECTED_SUCCESS: 0,
416 EventBuilder.STATUS_FAILURE: 0,
417 EventBuilder.STATUS_ERROR: 0
418 }
419
Todd Fiala68615ce2015-09-15 21:38:04 +0000420 # Lock that we use while mutating inner state, like the
421 # total test count and the elements. We minimize how
422 # long we hold the lock just to keep inner state safe, not
423 # entirely consistent from the outside.
424 self.lock = threading.Lock()
425
Todd Fialae83f1402015-09-18 22:45:31 +0000426 def handle_event(self, test_event):
427 """Handles the test event for collection into the formatter output.
Todd Fiala68615ce2015-09-15 21:38:04 +0000428
429 Derived classes may override this but should call down to this
430 implementation first.
431
432 @param test_event the test event as formatted by one of the
433 event_for_* calls.
434 """
Todd Fialade9a44e2015-09-22 00:15:50 +0000435 # Keep track of whether terminate was received. We do this so
436 # that a process can call the 'terminate' event on its own, to
437 # close down a formatter at the appropriate time. Then the
438 # atexit() cleanup can call the "terminate if it hasn't been
439 # called yet".
440 if test_event is not None:
Todd Fiala46a4e342015-12-02 18:48:38 +0000441 event_type = test_event.get("event", "")
442 if event_type == "terminate":
Todd Fialade9a44e2015-09-22 00:15:50 +0000443 self.terminate_called = True
Todd Fiala46a4e342015-12-02 18:48:38 +0000444 elif event_type == "test_result":
445 # Keep track of event counts per test result status type
446 status = test_event["status"]
447 self.result_status_counts[status] += 1
Todd Fiala68615ce2015-09-15 21:38:04 +0000448
449 def track_start_time(self, test_class, test_name, start_time):
Todd Fiala46a4e342015-12-02 18:48:38 +0000450 """tracks the start time of a test so elapsed time can be computed.
Todd Fiala68615ce2015-09-15 21:38:04 +0000451
Todd Fiala46a4e342015-12-02 18:48:38 +0000452 this alleviates the need for test results to be processed serially
453 by test. it will save the start time for the test so that
Todd Fiala68615ce2015-09-15 21:38:04 +0000454 elapsed_time_for_test() can compute the elapsed time properly.
455 """
456 if test_class is None or test_name is None:
457 return
458
459 test_key = "{}.{}".format(test_class, test_name)
460 with self.lock:
461 self.start_time_by_test[test_key] = start_time
462
463 def elapsed_time_for_test(self, test_class, test_name, end_time):
Todd Fiala46a4e342015-12-02 18:48:38 +0000464 """returns the elapsed time for a test.
Todd Fiala68615ce2015-09-15 21:38:04 +0000465
Todd Fiala46a4e342015-12-02 18:48:38 +0000466 this function can only be called once per test and requires that
Todd Fiala68615ce2015-09-15 21:38:04 +0000467 the track_start_time() method be called sometime prior to calling
468 this method.
469 """
470 if test_class is None or test_name is None:
471 return -2.0
472
473 test_key = "{}.{}".format(test_class, test_name)
474 with self.lock:
475 if test_key not in self.start_time_by_test:
476 return -1.0
477 else:
478 start_time = self.start_time_by_test[test_key]
479 del self.start_time_by_test[test_key]
480 return end_time - start_time
481
Greg Clayton1827fc22015-09-19 00:39:09 +0000482 def is_using_terminal(self):
Todd Fiala46a4e342015-12-02 18:48:38 +0000483 """returns true if this results formatter is using the terminal and
Todd Fialade9a44e2015-09-22 00:15:50 +0000484 output should be avoided."""
Greg Clayton1827fc22015-09-19 00:39:09 +0000485 return self.using_terminal
Todd Fiala68615ce2015-09-15 21:38:04 +0000486
Todd Fialade9a44e2015-09-22 00:15:50 +0000487 def send_terminate_as_needed(self):
Todd Fiala46a4e342015-12-02 18:48:38 +0000488 """sends the terminate event if it hasn't been received yet."""
Todd Fialade9a44e2015-09-22 00:15:50 +0000489 if not self.terminate_called:
490 terminate_event = EventBuilder.bare_event("terminate")
491 self.handle_event(terminate_event)
492
Todd Fiala46a4e342015-12-02 18:48:38 +0000493 # Derived classes may require self access
494 # pylint: disable=no-self-use
495 def replaces_summary(self):
496 """Returns whether the results formatter includes a summary
497 suitable to replace the old lldb test run results.
498
499 @return True if the lldb test runner can skip its summary
500 generation when using this results formatter; False otherwise.
501 """
502 return False
503
504 def counts_by_test_result_status(self, status):
505 """Returns number of test method results for the given status.
506
507 @status_result a test result status (e.g. success, fail, skip)
508 as defined by the EventBuilder.STATUS_* class members.
509
510 @return an integer returning the number of test methods matching
511 the given test result status.
512 """
513 return self.result_status_counts[status]
514
Todd Fiala132c2c42015-09-22 06:32:50 +0000515
Todd Fiala68615ce2015-09-15 21:38:04 +0000516class XunitFormatter(ResultsFormatter):
517 """Provides xUnit-style formatted output.
518 """
519
520 # Result mapping arguments
521 RM_IGNORE = 'ignore'
522 RM_SUCCESS = 'success'
523 RM_FAILURE = 'failure'
524 RM_PASSTHRU = 'passthru'
525
526 @staticmethod
Todd Fiala8effde42015-09-18 16:00:52 +0000527 def _build_illegal_xml_regex():
Todd Fiala33896a92015-09-18 21:01:13 +0000528 """Contructs a regex to match all illegal xml characters.
529
530 Expects to be used against a unicode string."""
Todd Fiala8effde42015-09-18 16:00:52 +0000531 # Construct the range pairs of invalid unicode chareacters.
532 illegal_chars_u = [
533 (0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F), (0x7F, 0x84),
534 (0x86, 0x9F), (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
535
536 # For wide builds, we have more.
537 if sys.maxunicode >= 0x10000:
538 illegal_chars_u.extend(
539 [(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF), (0x3FFFE, 0x3FFFF),
540 (0x4FFFE, 0x4FFFF), (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
541 (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF), (0x9FFFE, 0x9FFFF),
542 (0xAFFFE, 0xAFFFF), (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
543 (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF), (0xFFFFE, 0xFFFFF),
544 (0x10FFFE, 0x10FFFF)])
545
546 # Build up an array of range expressions.
547 illegal_ranges = [
Zachary Turner58968ac2015-10-23 19:52:36 +0000548 "%s-%s" % (six.unichr(low), six.unichr(high))
Todd Fiala8effde42015-09-18 16:00:52 +0000549 for (low, high) in illegal_chars_u]
550
551 # Compile the regex
Zachary Turner58968ac2015-10-23 19:52:36 +0000552 return re.compile(six.u('[%s]') % six.u('').join(illegal_ranges))
Todd Fiala8effde42015-09-18 16:00:52 +0000553
554 @staticmethod
Todd Fiala68615ce2015-09-15 21:38:04 +0000555 def _quote_attribute(text):
556 """Returns the given text in a manner safe for usage in an XML attribute.
557
558 @param text the text that should appear within an XML attribute.
559 @return the attribute-escaped version of the input text.
560 """
561 return xml.sax.saxutils.quoteattr(text)
562
Todd Fiala8effde42015-09-18 16:00:52 +0000563 def _replace_invalid_xml(self, str_or_unicode):
Todd Fiala33896a92015-09-18 21:01:13 +0000564 """Replaces invalid XML characters with a '?'.
565
566 @param str_or_unicode a string to replace invalid XML
567 characters within. Can be unicode or not. If not unicode,
568 assumes it is a byte string in utf-8 encoding.
569
570 @returns a utf-8-encoded byte string with invalid
571 XML replaced with '?'.
572 """
Todd Fiala8effde42015-09-18 16:00:52 +0000573 # Get the content into unicode
574 if isinstance(str_or_unicode, str):
575 unicode_content = str_or_unicode.decode('utf-8')
576 else:
577 unicode_content = str_or_unicode
Todd Fiala46a4e342015-12-02 18:48:38 +0000578 return self.invalid_xml_re.sub(
579 six.u('?'), unicode_content).encode('utf-8')
Todd Fiala8effde42015-09-18 16:00:52 +0000580
Todd Fiala68615ce2015-09-15 21:38:04 +0000581 @classmethod
582 def arg_parser(cls):
583 """@return arg parser used to parse formatter-specific options."""
584 parser = super(XunitFormatter, cls).arg_parser()
585
586 # These are valid choices for results mapping.
587 results_mapping_choices = [
588 XunitFormatter.RM_IGNORE,
589 XunitFormatter.RM_SUCCESS,
590 XunitFormatter.RM_FAILURE,
591 XunitFormatter.RM_PASSTHRU]
592 parser.add_argument(
Todd Fiala33896a92015-09-18 21:01:13 +0000593 "--assert-on-unknown-events",
594 action="store_true",
595 help=('cause unknown test events to generate '
596 'a python assert. Default is to ignore.'))
597 parser.add_argument(
Todd Fialaea736242015-09-23 15:21:28 +0000598 "--ignore-skip-name",
599 "-n",
600 metavar='PATTERN',
601 action="append",
602 dest='ignore_skip_name_patterns',
603 help=('a python regex pattern, where '
Todd Fiala132c2c42015-09-22 06:32:50 +0000604 'any skipped test with a test method name where regex '
605 'matches (via search) will be ignored for xUnit test '
Todd Fialaea736242015-09-23 15:21:28 +0000606 'result purposes. Can be specified multiple times.'))
Todd Fiala132c2c42015-09-22 06:32:50 +0000607 parser.add_argument(
Todd Fialaea736242015-09-23 15:21:28 +0000608 "--ignore-skip-reason",
609 "-r",
610 metavar='PATTERN',
611 action="append",
612 dest='ignore_skip_reason_patterns',
613 help=('a python regex pattern, where '
Todd Fiala132c2c42015-09-22 06:32:50 +0000614 'any skipped test with a skip reason where the regex '
615 'matches (via search) will be ignored for xUnit test '
Todd Fialaea736242015-09-23 15:21:28 +0000616 'result purposes. Can be specified multiple times.'))
Todd Fiala132c2c42015-09-22 06:32:50 +0000617 parser.add_argument(
Todd Fiala68615ce2015-09-15 21:38:04 +0000618 "--xpass", action="store", choices=results_mapping_choices,
619 default=XunitFormatter.RM_FAILURE,
620 help=('specify mapping from unexpected success to jUnit/xUnit '
621 'result type'))
622 parser.add_argument(
623 "--xfail", action="store", choices=results_mapping_choices,
624 default=XunitFormatter.RM_IGNORE,
625 help=('specify mapping from expected failure to jUnit/xUnit '
626 'result type'))
627 return parser
628
Todd Fiala132c2c42015-09-22 06:32:50 +0000629 @staticmethod
Todd Fialaea736242015-09-23 15:21:28 +0000630 def _build_regex_list_from_patterns(patterns):
Todd Fiala132c2c42015-09-22 06:32:50 +0000631 """Builds a list of compiled regexes from option value.
632
633 @param option string containing a comma-separated list of regex
634 patterns. Zero-length or None will produce an empty regex list.
635
636 @return list of compiled regular expressions, empty if no
637 patterns provided.
638 """
639 regex_list = []
Todd Fialaea736242015-09-23 15:21:28 +0000640 if patterns is not None:
641 for pattern in patterns:
Todd Fiala132c2c42015-09-22 06:32:50 +0000642 regex_list.append(re.compile(pattern))
643 return regex_list
644
Todd Fiala68615ce2015-09-15 21:38:04 +0000645 def __init__(self, out_file, options):
646 """Initializes the XunitFormatter instance.
647 @param out_file file-like object where formatted output is written.
648 @param options_dict specifies a dictionary of options for the
649 formatter.
650 """
651 # Initialize the parent
652 super(XunitFormatter, self).__init__(out_file, options)
653 self.text_encoding = "UTF-8"
Todd Fiala8effde42015-09-18 16:00:52 +0000654 self.invalid_xml_re = XunitFormatter._build_illegal_xml_regex()
Todd Fiala68615ce2015-09-15 21:38:04 +0000655 self.total_test_count = 0
Todd Fiala132c2c42015-09-22 06:32:50 +0000656 self.ignore_skip_name_regexes = (
Todd Fialaea736242015-09-23 15:21:28 +0000657 XunitFormatter._build_regex_list_from_patterns(
658 options.ignore_skip_name_patterns))
Todd Fiala132c2c42015-09-22 06:32:50 +0000659 self.ignore_skip_reason_regexes = (
Todd Fialaea736242015-09-23 15:21:28 +0000660 XunitFormatter._build_regex_list_from_patterns(
661 options.ignore_skip_reason_patterns))
Todd Fiala68615ce2015-09-15 21:38:04 +0000662
663 self.elements = {
664 "successes": [],
665 "errors": [],
666 "failures": [],
667 "skips": [],
668 "unexpected_successes": [],
669 "expected_failures": [],
670 "all": []
671 }
672
673 self.status_handlers = {
Todd Fiala46a4e342015-12-02 18:48:38 +0000674 EventBuilder.STATUS_SUCCESS: self._handle_success,
675 EventBuilder.STATUS_FAILURE: self._handle_failure,
676 EventBuilder.STATUS_ERROR: self._handle_error,
677 EventBuilder.STATUS_SKIP: self._handle_skip,
678 EventBuilder.STATUS_EXPECTED_FAILURE:
679 self._handle_expected_failure,
680 EventBuilder.STATUS_UNEXPECTED_SUCCESS:
681 self._handle_unexpected_success
Todd Fiala68615ce2015-09-15 21:38:04 +0000682 }
683
Todd Fialae83f1402015-09-18 22:45:31 +0000684 def handle_event(self, test_event):
685 super(XunitFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +0000686
687 event_type = test_event["event"]
688 if event_type is None:
689 return
690
Todd Fialae83f1402015-09-18 22:45:31 +0000691 if event_type == "terminate":
692 self._finish_output()
693 elif event_type == "test_start":
Todd Fiala68615ce2015-09-15 21:38:04 +0000694 self.track_start_time(
695 test_event["test_class"],
696 test_event["test_name"],
697 test_event["event_time"])
698 elif event_type == "test_result":
699 self._process_test_result(test_event)
700 else:
Todd Fiala33896a92015-09-18 21:01:13 +0000701 # This is an unknown event.
702 if self.options.assert_on_unknown_events:
703 raise Exception("unknown event type {} from {}\n".format(
704 event_type, test_event))
Todd Fiala68615ce2015-09-15 21:38:04 +0000705
706 def _handle_success(self, test_event):
707 """Handles a test success.
708 @param test_event the test event to handle.
709 """
710 result = self._common_add_testcase_entry(test_event)
711 with self.lock:
712 self.elements["successes"].append(result)
713
714 def _handle_failure(self, test_event):
715 """Handles a test failure.
716 @param test_event the test event to handle.
717 """
Todd Fiala8effde42015-09-18 16:00:52 +0000718 message = self._replace_invalid_xml(test_event["issue_message"])
719 backtrace = self._replace_invalid_xml(
720 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000721
Todd Fiala68615ce2015-09-15 21:38:04 +0000722 result = self._common_add_testcase_entry(
723 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000724 inner_content=(
725 '<failure type={} message={}><![CDATA[{}]]></failure>'.format(
726 XunitFormatter._quote_attribute(test_event["issue_class"]),
727 XunitFormatter._quote_attribute(message),
728 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000729 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000730 with self.lock:
731 self.elements["failures"].append(result)
732
733 def _handle_error(self, test_event):
734 """Handles a test error.
735 @param test_event the test event to handle.
736 """
Todd Fiala8effde42015-09-18 16:00:52 +0000737 message = self._replace_invalid_xml(test_event["issue_message"])
738 backtrace = self._replace_invalid_xml(
739 "".join(test_event.get("issue_backtrace", [])))
Todd Fialae7e911f2015-09-18 07:08:09 +0000740
Todd Fiala68615ce2015-09-15 21:38:04 +0000741 result = self._common_add_testcase_entry(
742 test_event,
Todd Fiala8effde42015-09-18 16:00:52 +0000743 inner_content=(
744 '<error type={} message={}><![CDATA[{}]]></error>'.format(
745 XunitFormatter._quote_attribute(test_event["issue_class"]),
746 XunitFormatter._quote_attribute(message),
747 backtrace)
Todd Fialae7e911f2015-09-18 07:08:09 +0000748 ))
Todd Fiala68615ce2015-09-15 21:38:04 +0000749 with self.lock:
750 self.elements["errors"].append(result)
751
Todd Fiala132c2c42015-09-22 06:32:50 +0000752 @staticmethod
753 def _ignore_based_on_regex_list(test_event, test_key, regex_list):
754 """Returns whether to ignore a test event based on patterns.
755
756 @param test_event the test event dictionary to check.
757 @param test_key the key within the dictionary to check.
758 @param regex_list a list of zero or more regexes. May contain
759 zero or more compiled regexes.
760
761 @return True if any o the regex list match based on the
762 re.search() method; false otherwise.
763 """
764 for regex in regex_list:
765 match = regex.search(test_event.get(test_key, ''))
766 if match:
767 return True
768 return False
769
Todd Fiala68615ce2015-09-15 21:38:04 +0000770 def _handle_skip(self, test_event):
771 """Handles a skipped test.
772 @param test_event the test event to handle.
773 """
Todd Fiala132c2c42015-09-22 06:32:50 +0000774
775 # Are we ignoring this test based on test name?
776 if XunitFormatter._ignore_based_on_regex_list(
777 test_event, 'test_name', self.ignore_skip_name_regexes):
778 return
779
780 # Are we ignoring this test based on skip reason?
781 if XunitFormatter._ignore_based_on_regex_list(
782 test_event, 'skip_reason', self.ignore_skip_reason_regexes):
783 return
784
785 # We're not ignoring this test. Process the skip.
Todd Fiala8effde42015-09-18 16:00:52 +0000786 reason = self._replace_invalid_xml(test_event.get("skip_reason", ""))
Todd Fiala68615ce2015-09-15 21:38:04 +0000787 result = self._common_add_testcase_entry(
788 test_event,
789 inner_content='<skipped message={} />'.format(
Todd Fiala8effde42015-09-18 16:00:52 +0000790 XunitFormatter._quote_attribute(reason)))
Todd Fiala68615ce2015-09-15 21:38:04 +0000791 with self.lock:
792 self.elements["skips"].append(result)
793
794 def _handle_expected_failure(self, test_event):
795 """Handles a test that failed as expected.
796 @param test_event the test event to handle.
797 """
798 if self.options.xfail == XunitFormatter.RM_PASSTHRU:
799 # This is not a natively-supported junit/xunit
800 # testcase mode, so it might fail a validating
801 # test results viewer.
802 if "bugnumber" in test_event:
803 bug_id_attribute = 'bug-id={} '.format(
804 XunitFormatter._quote_attribute(test_event["bugnumber"]))
805 else:
806 bug_id_attribute = ''
807
808 result = self._common_add_testcase_entry(
809 test_event,
810 inner_content=(
811 '<expected-failure {}type={} message={} />'.format(
812 bug_id_attribute,
813 XunitFormatter._quote_attribute(
814 test_event["issue_class"]),
815 XunitFormatter._quote_attribute(
816 test_event["issue_message"]))
817 ))
818 with self.lock:
819 self.elements["expected_failures"].append(result)
820 elif self.options.xfail == XunitFormatter.RM_SUCCESS:
821 result = self._common_add_testcase_entry(test_event)
822 with self.lock:
823 self.elements["successes"].append(result)
824 elif self.options.xfail == XunitFormatter.RM_FAILURE:
825 result = self._common_add_testcase_entry(
826 test_event,
827 inner_content='<failure type={} message={} />'.format(
828 XunitFormatter._quote_attribute(test_event["issue_class"]),
829 XunitFormatter._quote_attribute(
830 test_event["issue_message"])))
831 with self.lock:
832 self.elements["failures"].append(result)
833 elif self.options.xfail == XunitFormatter.RM_IGNORE:
834 pass
835 else:
836 raise Exception(
837 "unknown xfail option: {}".format(self.options.xfail))
838
839 def _handle_unexpected_success(self, test_event):
840 """Handles a test that passed but was expected to fail.
841 @param test_event the test event to handle.
842 """
843 if self.options.xpass == XunitFormatter.RM_PASSTHRU:
844 # This is not a natively-supported junit/xunit
845 # testcase mode, so it might fail a validating
846 # test results viewer.
847 result = self._common_add_testcase_entry(
848 test_event,
849 inner_content=("<unexpected-success />"))
850 with self.lock:
851 self.elements["unexpected_successes"].append(result)
852 elif self.options.xpass == XunitFormatter.RM_SUCCESS:
853 # Treat the xpass as a success.
854 result = self._common_add_testcase_entry(test_event)
855 with self.lock:
856 self.elements["successes"].append(result)
857 elif self.options.xpass == XunitFormatter.RM_FAILURE:
858 # Treat the xpass as a failure.
859 if "bugnumber" in test_event:
860 message = "unexpected success (bug_id:{})".format(
861 test_event["bugnumber"])
862 else:
863 message = "unexpected success (bug_id:none)"
864 result = self._common_add_testcase_entry(
865 test_event,
866 inner_content='<failure type={} message={} />'.format(
867 XunitFormatter._quote_attribute("unexpected_success"),
868 XunitFormatter._quote_attribute(message)))
869 with self.lock:
870 self.elements["failures"].append(result)
871 elif self.options.xpass == XunitFormatter.RM_IGNORE:
872 # Ignore the xpass result as far as xUnit reporting goes.
873 pass
874 else:
875 raise Exception("unknown xpass option: {}".format(
876 self.options.xpass))
877
878 def _process_test_result(self, test_event):
879 """Processes the test_event known to be a test result.
880
881 This categorizes the event appropriately and stores the data needed
882 to generate the final xUnit report. This method skips events that
883 cannot be represented in xUnit output.
884 """
885 if "status" not in test_event:
886 raise Exception("test event dictionary missing 'status' key")
887
888 status = test_event["status"]
889 if status not in self.status_handlers:
890 raise Exception("test event status '{}' unsupported".format(
891 status))
892
893 # Call the status handler for the test result.
894 self.status_handlers[status](test_event)
895
896 def _common_add_testcase_entry(self, test_event, inner_content=None):
897 """Registers a testcase result, and returns the text created.
898
899 The caller is expected to manage failure/skip/success counts
900 in some kind of appropriate way. This call simply constructs
901 the XML and appends the returned result to the self.all_results
902 list.
903
904 @param test_event the test event dictionary.
905
906 @param inner_content if specified, gets included in the <testcase>
907 inner section, at the point before stdout and stderr would be
908 included. This is where a <failure/>, <skipped/>, <error/>, etc.
909 could go.
910
911 @return the text of the xml testcase element.
912 """
913
914 # Get elapsed time.
915 test_class = test_event["test_class"]
916 test_name = test_event["test_name"]
917 event_time = test_event["event_time"]
918 time_taken = self.elapsed_time_for_test(
919 test_class, test_name, event_time)
920
921 # Plumb in stdout/stderr once we shift over to only test results.
922 test_stdout = ''
923 test_stderr = ''
924
925 # Formulate the output xml.
926 if not inner_content:
927 inner_content = ""
928 result = (
929 '<testcase classname="{}" name="{}" time="{:.3f}">'
930 '{}{}{}</testcase>'.format(
931 test_class,
932 test_name,
933 time_taken,
934 inner_content,
935 test_stdout,
936 test_stderr))
937
938 # Save the result, update total test count.
939 with self.lock:
940 self.total_test_count += 1
941 self.elements["all"].append(result)
942
943 return result
944
Todd Fialae83f1402015-09-18 22:45:31 +0000945 def _finish_output_no_lock(self):
Todd Fiala68615ce2015-09-15 21:38:04 +0000946 """Flushes out the report of test executions to form valid xml output.
947
948 xUnit output is in XML. The reporting system cannot complete the
949 formatting of the output without knowing when there is no more input.
950 This call addresses notifcation of the completed test run and thus is
951 when we can finish off the report output.
952 """
953
954 # Figure out the counts line for the testsuite. If we have
955 # been counting either unexpected successes or expected
956 # failures, we'll output those in the counts, at the risk of
957 # being invalidated by a validating test results viewer.
958 # These aren't counted by default so they won't show up unless
959 # the user specified a formatter option to include them.
960 xfail_count = len(self.elements["expected_failures"])
961 xpass_count = len(self.elements["unexpected_successes"])
962 if xfail_count > 0 or xpass_count > 0:
963 extra_testsuite_attributes = (
964 ' expected-failures="{}"'
965 ' unexpected-successes="{}"'.format(xfail_count, xpass_count))
966 else:
967 extra_testsuite_attributes = ""
968
969 # Output the header.
970 self.out_file.write(
971 '<?xml version="1.0" encoding="{}"?>\n'
Todd Fiala038bf832015-09-18 01:43:08 +0000972 '<testsuites>'
Todd Fiala68615ce2015-09-15 21:38:04 +0000973 '<testsuite name="{}" tests="{}" errors="{}" failures="{}" '
974 'skip="{}"{}>\n'.format(
975 self.text_encoding,
976 "LLDB test suite",
977 self.total_test_count,
978 len(self.elements["errors"]),
979 len(self.elements["failures"]),
980 len(self.elements["skips"]),
981 extra_testsuite_attributes))
982
983 # Output each of the test result entries.
984 for result in self.elements["all"]:
985 self.out_file.write(result + '\n')
986
987 # Close off the test suite.
Todd Fiala038bf832015-09-18 01:43:08 +0000988 self.out_file.write('</testsuite></testsuites>\n')
Todd Fiala68615ce2015-09-15 21:38:04 +0000989
Todd Fialae83f1402015-09-18 22:45:31 +0000990 def _finish_output(self):
Todd Fiala132c2c42015-09-22 06:32:50 +0000991 """Finish writing output as all incoming events have arrived."""
Todd Fiala68615ce2015-09-15 21:38:04 +0000992 with self.lock:
Todd Fialae83f1402015-09-18 22:45:31 +0000993 self._finish_output_no_lock()
Todd Fiala68615ce2015-09-15 21:38:04 +0000994
995
996class RawPickledFormatter(ResultsFormatter):
997 """Formats events as a pickled stream.
998
999 The parallel test runner has inferiors pickle their results and send them
1000 over a socket back to the parallel test. The parallel test runner then
1001 aggregates them into the final results formatter (e.g. xUnit).
1002 """
1003
1004 @classmethod
1005 def arg_parser(cls):
1006 """@return arg parser used to parse formatter-specific options."""
1007 parser = super(RawPickledFormatter, cls).arg_parser()
1008 return parser
1009
1010 def __init__(self, out_file, options):
1011 super(RawPickledFormatter, self).__init__(out_file, options)
1012 self.pid = os.getpid()
1013
Todd Fialae83f1402015-09-18 22:45:31 +00001014 def handle_event(self, test_event):
1015 super(RawPickledFormatter, self).handle_event(test_event)
Todd Fiala68615ce2015-09-15 21:38:04 +00001016
Todd Fialae83f1402015-09-18 22:45:31 +00001017 # Convert initialize/terminate events into job_begin/job_end events.
1018 event_type = test_event["event"]
1019 if event_type is None:
1020 return
1021
1022 if event_type == "initialize":
1023 test_event["event"] = "job_begin"
1024 elif event_type == "terminate":
1025 test_event["event"] = "job_end"
1026
1027 # Tack on the pid.
1028 test_event["pid"] = self.pid
Todd Fiala68615ce2015-09-15 21:38:04 +00001029
Todd Fiala68615ce2015-09-15 21:38:04 +00001030 # Send it as {serialized_length_of_serialized_bytes}#{serialized_bytes}
1031 pickled_message = cPickle.dumps(test_event)
1032 self.out_file.send(
1033 "{}#{}".format(len(pickled_message), pickled_message))
1034
Todd Fiala33896a92015-09-18 21:01:13 +00001035
1036class DumpFormatter(ResultsFormatter):
1037 """Formats events to the file as their raw python dictionary format."""
1038
Todd Fialae83f1402015-09-18 22:45:31 +00001039 def handle_event(self, test_event):
1040 super(DumpFormatter, self).handle_event(test_event)
Todd Fiala33896a92015-09-18 21:01:13 +00001041 self.out_file.write("\n" + pprint.pformat(test_event) + "\n")