• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 The Abseil Authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import datetime
16import io
17import os
18import re
19import subprocess
20import sys
21import tempfile
22import threading
23import time
24import unittest
25from unittest import mock
26from xml.etree import ElementTree
27from xml.parsers import expat
28
29from absl import logging
30from absl.testing import _bazelize_command
31from absl.testing import absltest
32from absl.testing import parameterized
33from absl.testing import xml_reporter
34
35
36class StringIOWriteLn(io.StringIO):
37
38  def writeln(self, line):
39    self.write(line + '\n')
40
41
42class MockTest(absltest.TestCase):
43  failureException = AssertionError
44
45  def __init__(self, name):
46    super(MockTest, self).__init__()
47    self.name = name
48
49  def id(self):
50    return self.name
51
52  def runTest(self):
53    return
54
55  def shortDescription(self):
56    return "This is this test's description."
57
58
59# str(exception_type) is different between Python 2 and 3.
60def xml_escaped_exception_type(exception_type):
61  return xml_reporter._escape_xml_attr(str(exception_type))
62
63
64OUTPUT_STRING = '\n'.join([
65    r'<\?xml version="1.0"\?>',
66    ('<testsuites name="" tests="%(tests)d" failures="%(failures)d"'
67     ' errors="%(errors)d" time="%(run_time).3f" timestamp="%(start_time)s">'),
68    ('<testsuite name="%(suite_name)s" tests="%(tests)d"'
69     ' failures="%(failures)d" errors="%(errors)d" time="%(run_time).3f"'
70     ' timestamp="%(start_time)s">'),
71    ('  <testcase name="%(test_name)s" status="%(status)s" result="%(result)s"'
72     ' time="%(run_time).3f" classname="%(classname)s"'
73     ' timestamp="%(start_time)s">%(message)s'),
74    '  </testcase>', '</testsuite>',
75    '</testsuites>',
76])
77
78FAILURE_MESSAGE = r"""
79  <failure message="e" type="{}"><!\[CDATA\[Traceback \(most recent call last\):
80  File ".*xml_reporter_test\.py", line \d+, in get_sample_failure
81    self.fail\(\'e\'\)
82AssertionError: e
83\]\]></failure>""".format(xml_escaped_exception_type(AssertionError))
84
85ERROR_MESSAGE = r"""
86  <error message="invalid&#x20;literal&#x20;for&#x20;int\(\)&#x20;with&#x20;base&#x20;10:&#x20;(&apos;)?a(&apos;)?" type="{}"><!\[CDATA\[Traceback \(most recent call last\):
87  File ".*xml_reporter_test\.py", line \d+, in get_sample_error
88    int\('a'\)
89ValueError: invalid literal for int\(\) with base 10: '?a'?
90\]\]></error>""".format(xml_escaped_exception_type(ValueError))
91
92UNICODE_MESSAGE = r"""
93  <%s message="{0}" type="{1}"><!\[CDATA\[Traceback \(most recent call last\):
94  File ".*xml_reporter_test\.py", line \d+, in get_unicode_sample_failure
95    raise AssertionError\(u'\\xe9'\)
96AssertionError: {0}
97\]\]></%s>""".format(
98        r'\xe9',
99        xml_escaped_exception_type(AssertionError))
100
101NEWLINE_MESSAGE = r"""
102  <%s message="{0}" type="{1}"><!\[CDATA\[Traceback \(most recent call last\):
103  File ".*xml_reporter_test\.py", line \d+, in get_newline_message_sample_failure
104    raise AssertionError\(\'{2}'\)
105AssertionError: {3}
106\]\]></%s>""".format(
107    'new&#xA;line',
108    xml_escaped_exception_type(AssertionError),
109    r'new\\nline',
110    'new\nline')
111
112UNEXPECTED_SUCCESS_MESSAGE = '\n'.join([
113    '',
114    (r'  <error message="" type=""><!\[CDATA\[Test case '
115     r'__main__.MockTest.unexpectedly_passing_test should have failed, '
116     r'but passed.\]\]></error>'),
117])
118
119UNICODE_ERROR_MESSAGE = UNICODE_MESSAGE % ('error', 'error')
120NEWLINE_ERROR_MESSAGE = NEWLINE_MESSAGE % ('error', 'error')
121
122
123class TextAndXMLTestResultTest(absltest.TestCase):
124
125  def setUp(self):
126    super().setUp()
127    self.stream = StringIOWriteLn()
128    self.xml_stream = io.StringIO()
129
130  def _make_result(self, times):
131    timer = mock.Mock()
132    timer.side_effect = times
133    return xml_reporter._TextAndXMLTestResult(self.xml_stream, self.stream,
134                                              'foo', 0, timer)
135
136  def _assert_match(self, regex, output):
137    fail_msg = 'Expected regex:\n{}\nTo match:\n{}'.format(regex, output)
138    self.assertRegex(output, regex, fail_msg)
139
140  def _assert_valid_xml(self, xml_output):
141    try:
142      expat.ParserCreate().Parse(xml_output)
143    except expat.ExpatError as e:
144      raise AssertionError('Bad XML output: {}\n{}'.format(e, xml_output))
145
146  def _simulate_error_test(self, test, result):
147    result.startTest(test)
148    result.addError(test, self.get_sample_error())
149    result.stopTest(test)
150
151  def _simulate_failing_test(self, test, result):
152    result.startTest(test)
153    result.addFailure(test, self.get_sample_failure())
154    result.stopTest(test)
155
156  def _simulate_passing_test(self, test, result):
157    result.startTest(test)
158    result.addSuccess(test)
159    result.stopTest(test)
160
161  def _iso_timestamp(self, timestamp):
162    return datetime.datetime.utcfromtimestamp(timestamp).isoformat() + '+00:00'
163
164  def test_with_passing_test(self):
165    start_time = 0
166    end_time = 2
167    result = self._make_result((start_time, start_time, end_time, end_time))
168
169    test = MockTest('__main__.MockTest.passing_test')
170    result.startTestRun()
171    result.startTest(test)
172    result.addSuccess(test)
173    result.stopTest(test)
174    result.stopTestRun()
175    result.printErrors()
176
177    run_time = end_time - start_time
178    expected_re = OUTPUT_STRING % {
179        'suite_name': 'MockTest',
180        'tests': 1,
181        'failures': 0,
182        'errors': 0,
183        'run_time': run_time,
184        'start_time': re.escape(self._iso_timestamp(start_time),),
185        'test_name': 'passing_test',
186        'classname': '__main__.MockTest',
187        'status': 'run',
188        'result': 'completed',
189        'attributes': '',
190        'message': ''
191    }
192    self._assert_match(expected_re, self.xml_stream.getvalue())
193
194  def test_with_passing_subtest(self):
195    start_time = 0
196    end_time = 2
197    result = self._make_result((start_time, start_time, end_time, end_time))
198
199    test = MockTest('__main__.MockTest.passing_test')
200    subtest = unittest.case._SubTest(test, 'msg', None)
201    result.startTestRun()
202    result.startTest(test)
203    result.addSubTest(test, subtest, None)
204    result.stopTestRun()
205    result.printErrors()
206
207    run_time = end_time - start_time
208    expected_re = OUTPUT_STRING % {
209        'suite_name': 'MockTest',
210        'tests': 1,
211        'failures': 0,
212        'errors': 0,
213        'run_time': run_time,
214        'start_time': re.escape(self._iso_timestamp(start_time),),
215        'test_name': r'passing_test&#x20;\[msg\]',
216        'classname': '__main__.MockTest',
217        'status': 'run',
218        'result': 'completed',
219        'attributes': '',
220        'message': ''
221    }
222    self._assert_match(expected_re, self.xml_stream.getvalue())
223
224  def test_with_passing_subtest_with_dots_in_parameter_name(self):
225    start_time = 0
226    end_time = 2
227    result = self._make_result((start_time, start_time, end_time, end_time))
228
229    test = MockTest('__main__.MockTest.passing_test')
230    subtest = unittest.case._SubTest(test, 'msg', {'case': 'a.b.c'})
231    result.startTestRun()
232    result.startTest(test)
233    result.addSubTest(test, subtest, None)
234    result.stopTestRun()
235    result.printErrors()
236
237    run_time = end_time - start_time
238    expected_re = OUTPUT_STRING % {
239        'suite_name':
240            'MockTest',
241        'tests':
242            1,
243        'failures':
244            0,
245        'errors':
246            0,
247        'run_time':
248            run_time,
249        'start_time':
250            re.escape(self._iso_timestamp(start_time),),
251        'test_name':
252            r'passing_test&#x20;\[msg\]&#x20;\(case=&apos;a.b.c&apos;\)',
253        'classname':
254            '__main__.MockTest',
255        'status':
256            'run',
257        'result':
258            'completed',
259        'attributes':
260            '',
261        'message':
262            ''
263    }
264    self._assert_match(expected_re, self.xml_stream.getvalue())
265
266  def get_sample_error(self):
267    try:
268      int('a')
269    except ValueError:
270      error_values = sys.exc_info()
271      return error_values
272
273  def get_sample_failure(self):
274    try:
275      self.fail('e')
276    except AssertionError:
277      error_values = sys.exc_info()
278      return error_values
279
280  def get_newline_message_sample_failure(self):
281    try:
282      raise AssertionError('new\nline')
283    except AssertionError:
284      error_values = sys.exc_info()
285      return error_values
286
287  def get_unicode_sample_failure(self):
288    try:
289      raise AssertionError(u'\xe9')
290    except AssertionError:
291      error_values = sys.exc_info()
292      return error_values
293
294  def get_terminal_escape_sample_failure(self):
295    try:
296      raise AssertionError('\x1b')
297    except AssertionError:
298      error_values = sys.exc_info()
299      return error_values
300
301  def test_with_failing_test(self):
302    start_time = 10
303    end_time = 20
304    result = self._make_result((start_time, start_time, end_time, end_time))
305
306    test = MockTest('__main__.MockTest.failing_test')
307    result.startTestRun()
308    result.startTest(test)
309    result.addFailure(test, self.get_sample_failure())
310    result.stopTest(test)
311    result.stopTestRun()
312    result.printErrors()
313
314    run_time = end_time - start_time
315    expected_re = OUTPUT_STRING % {
316        'suite_name': 'MockTest',
317        'tests': 1,
318        'failures': 1,
319        'errors': 0,
320        'run_time': run_time,
321        'start_time': re.escape(self._iso_timestamp(start_time),),
322        'test_name': 'failing_test',
323        'classname': '__main__.MockTest',
324        'status': 'run',
325        'result': 'completed',
326        'attributes': '',
327        'message': FAILURE_MESSAGE
328    }
329    self._assert_match(expected_re, self.xml_stream.getvalue())
330
331  def test_with_failing_subtest(self):
332    start_time = 10
333    end_time = 20
334    result = self._make_result((start_time, start_time, end_time, end_time))
335
336    test = MockTest('__main__.MockTest.failing_test')
337    subtest = unittest.case._SubTest(test, 'msg', None)
338    result.startTestRun()
339    result.startTest(test)
340    result.addSubTest(test, subtest, self.get_sample_failure())
341    result.stopTestRun()
342    result.printErrors()
343
344    run_time = end_time - start_time
345    expected_re = OUTPUT_STRING % {
346        'suite_name': 'MockTest',
347        'tests': 1,
348        'failures': 1,
349        'errors': 0,
350        'run_time': run_time,
351        'start_time': re.escape(self._iso_timestamp(start_time),),
352        'test_name': r'failing_test&#x20;\[msg\]',
353        'classname': '__main__.MockTest',
354        'status': 'run',
355        'result': 'completed',
356        'attributes': '',
357        'message': FAILURE_MESSAGE
358    }
359    self._assert_match(expected_re, self.xml_stream.getvalue())
360
361  def test_with_error_test(self):
362    start_time = 100
363    end_time = 200
364    result = self._make_result((start_time, start_time, end_time, end_time))
365
366    test = MockTest('__main__.MockTest.failing_test')
367    result.startTestRun()
368    result.startTest(test)
369    result.addError(test, self.get_sample_error())
370    result.stopTest(test)
371    result.stopTestRun()
372    result.printErrors()
373    xml = self.xml_stream.getvalue()
374
375    self._assert_valid_xml(xml)
376
377    run_time = end_time - start_time
378    expected_re = OUTPUT_STRING % {
379        'suite_name': 'MockTest',
380        'tests': 1,
381        'failures': 0,
382        'errors': 1,
383        'run_time': run_time,
384        'start_time': re.escape(self._iso_timestamp(start_time),),
385        'test_name': 'failing_test',
386        'classname': '__main__.MockTest',
387        'status': 'run',
388        'result': 'completed',
389        'attributes': '',
390        'message': ERROR_MESSAGE
391    }
392    self._assert_match(expected_re, xml)
393
394  def test_with_error_subtest(self):
395    start_time = 10
396    end_time = 20
397    result = self._make_result((start_time, start_time, end_time, end_time))
398
399    test = MockTest('__main__.MockTest.error_test')
400    subtest = unittest.case._SubTest(test, 'msg', None)
401    result.startTestRun()
402    result.startTest(test)
403    result.addSubTest(test, subtest, self.get_sample_error())
404    result.stopTestRun()
405    result.printErrors()
406
407    run_time = end_time - start_time
408    expected_re = OUTPUT_STRING % {
409        'suite_name': 'MockTest',
410        'tests': 1,
411        'failures': 0,
412        'errors': 1,
413        'run_time': run_time,
414        'start_time': re.escape(self._iso_timestamp(start_time),),
415        'test_name': r'error_test&#x20;\[msg\]',
416        'classname': '__main__.MockTest',
417        'status': 'run',
418        'result': 'completed',
419        'attributes': '',
420        'message': ERROR_MESSAGE
421    }
422    self._assert_match(expected_re, self.xml_stream.getvalue())
423
424  def test_with_fail_and_error_test(self):
425    """Tests a failure and subsequent error within a single result."""
426    start_time = 123
427    end_time = 456
428    result = self._make_result((start_time, start_time, end_time, end_time))
429
430    test = MockTest('__main__.MockTest.failing_test')
431    result.startTestRun()
432    result.startTest(test)
433    result.addFailure(test, self.get_sample_failure())
434    # This could happen in tearDown
435    result.addError(test, self.get_sample_error())
436    result.stopTest(test)
437    result.stopTestRun()
438    result.printErrors()
439    xml = self.xml_stream.getvalue()
440
441    self._assert_valid_xml(xml)
442
443    run_time = end_time - start_time
444    expected_re = OUTPUT_STRING % {
445        'suite_name': 'MockTest',
446        'tests': 1,
447        'failures': 1,  # Only the failure is tallied (because it was first).
448        'errors': 0,
449        'run_time': run_time,
450        'start_time': re.escape(self._iso_timestamp(start_time),),
451        'test_name': 'failing_test',
452        'classname': '__main__.MockTest',
453        'status': 'run',
454        'result': 'completed',
455        'attributes': '',
456        # Messages from failure and error should be concatenated in order.
457        'message': FAILURE_MESSAGE + ERROR_MESSAGE
458    }
459    self._assert_match(expected_re, xml)
460
461  def test_with_error_and_fail_test(self):
462    """Tests an error and subsequent failure within a single result."""
463    start_time = 123
464    end_time = 456
465    result = self._make_result((start_time, start_time, end_time, end_time))
466
467    test = MockTest('__main__.MockTest.failing_test')
468    result.startTestRun()
469    result.startTest(test)
470    result.addError(test, self.get_sample_error())
471    result.addFailure(test, self.get_sample_failure())
472    result.stopTest(test)
473    result.stopTestRun()
474    result.printErrors()
475    xml = self.xml_stream.getvalue()
476
477    self._assert_valid_xml(xml)
478
479    run_time = end_time - start_time
480    expected_re = OUTPUT_STRING % {
481        'suite_name': 'MockTest',
482        'tests': 1,
483        'failures': 0,
484        'errors': 1,  # Only the error is tallied (because it was first).
485        'run_time': run_time,
486        'start_time': re.escape(self._iso_timestamp(start_time),),
487        'test_name': 'failing_test',
488        'classname': '__main__.MockTest',
489        'status': 'run',
490        'result': 'completed',
491        'attributes': '',
492        # Messages from error and failure should be concatenated in order.
493        'message': ERROR_MESSAGE + FAILURE_MESSAGE
494    }
495    self._assert_match(expected_re, xml)
496
497  def test_with_newline_error_test(self):
498    start_time = 100
499    end_time = 200
500    result = self._make_result((start_time, start_time, end_time, end_time))
501
502    test = MockTest('__main__.MockTest.failing_test')
503    result.startTestRun()
504    result.startTest(test)
505    result.addError(test, self.get_newline_message_sample_failure())
506    result.stopTest(test)
507    result.stopTestRun()
508    result.printErrors()
509    xml = self.xml_stream.getvalue()
510
511    self._assert_valid_xml(xml)
512
513    run_time = end_time - start_time
514    expected_re = OUTPUT_STRING % {
515        'suite_name': 'MockTest',
516        'tests': 1,
517        'failures': 0,
518        'errors': 1,
519        'run_time': run_time,
520        'start_time': re.escape(self._iso_timestamp(start_time),),
521        'test_name': 'failing_test',
522        'classname': '__main__.MockTest',
523        'status': 'run',
524        'result': 'completed',
525        'attributes': '',
526        'message': NEWLINE_ERROR_MESSAGE
527    } + '\n'
528    self._assert_match(expected_re, xml)
529
530  def test_with_unicode_error_test(self):
531    start_time = 100
532    end_time = 200
533    result = self._make_result((start_time, start_time, end_time, end_time))
534
535    test = MockTest('__main__.MockTest.failing_test')
536    result.startTestRun()
537    result.startTest(test)
538    result.addError(test, self.get_unicode_sample_failure())
539    result.stopTest(test)
540    result.stopTestRun()
541    result.printErrors()
542    xml = self.xml_stream.getvalue()
543
544    self._assert_valid_xml(xml)
545
546    run_time = end_time - start_time
547    expected_re = OUTPUT_STRING % {
548        'suite_name': 'MockTest',
549        'tests': 1,
550        'failures': 0,
551        'errors': 1,
552        'run_time': run_time,
553        'start_time': re.escape(self._iso_timestamp(start_time),),
554        'test_name': 'failing_test',
555        'classname': '__main__.MockTest',
556        'status': 'run',
557        'result': 'completed',
558        'attributes': '',
559        'message': UNICODE_ERROR_MESSAGE
560    }
561    self._assert_match(expected_re, xml)
562
563  def test_with_terminal_escape_error(self):
564    start_time = 100
565    end_time = 200
566    result = self._make_result((start_time, start_time, end_time, end_time))
567
568    test = MockTest('__main__.MockTest.failing_test')
569    result.startTestRun()
570    result.startTest(test)
571    result.addError(test, self.get_terminal_escape_sample_failure())
572    result.stopTest(test)
573    result.stopTestRun()
574    result.printErrors()
575
576    self._assert_valid_xml(self.xml_stream.getvalue())
577
578  def test_with_expected_failure_test(self):
579    start_time = 100
580    end_time = 200
581    result = self._make_result((start_time, start_time, end_time, end_time))
582    error_values = ''
583
584    try:
585      raise RuntimeError('Test expectedFailure')
586    except RuntimeError:
587      error_values = sys.exc_info()
588
589    test = MockTest('__main__.MockTest.expected_failing_test')
590    result.startTestRun()
591    result.startTest(test)
592    result.addExpectedFailure(test, error_values)
593    result.stopTest(test)
594    result.stopTestRun()
595    result.printErrors()
596
597    run_time = end_time - start_time
598    expected_re = OUTPUT_STRING % {
599        'suite_name': 'MockTest',
600        'tests': 1,
601        'failures': 0,
602        'errors': 0,
603        'run_time': run_time,
604        'start_time': re.escape(self._iso_timestamp(start_time),),
605        'test_name': 'expected_failing_test',
606        'classname': '__main__.MockTest',
607        'status': 'run',
608        'result': 'completed',
609        'attributes': '',
610        'message': ''
611    }
612    self._assert_match(re.compile(expected_re, re.DOTALL),
613                       self.xml_stream.getvalue())
614
615  def test_with_unexpected_success_error_test(self):
616    start_time = 100
617    end_time = 200
618    result = self._make_result((start_time, start_time, end_time, end_time))
619
620    test = MockTest('__main__.MockTest.unexpectedly_passing_test')
621    result.startTestRun()
622    result.startTest(test)
623    result.addUnexpectedSuccess(test)
624    result.stopTest(test)
625    result.stopTestRun()
626    result.printErrors()
627
628    run_time = end_time - start_time
629    expected_re = OUTPUT_STRING % {
630        'suite_name': 'MockTest',
631        'tests': 1,
632        'failures': 0,
633        'errors': 1,
634        'run_time': run_time,
635        'start_time': re.escape(self._iso_timestamp(start_time),),
636        'test_name': 'unexpectedly_passing_test',
637        'classname': '__main__.MockTest',
638        'status': 'run',
639        'result': 'completed',
640        'attributes': '',
641        'message': UNEXPECTED_SUCCESS_MESSAGE
642    }
643    self._assert_match(expected_re, self.xml_stream.getvalue())
644
645  def test_with_skipped_test(self):
646    start_time = 100
647    end_time = 100
648    result = self._make_result((start_time, start_time, end_time, end_time))
649
650    test = MockTest('__main__.MockTest.skipped_test_with_reason')
651    result.startTestRun()
652    result.startTest(test)
653    result.addSkip(test, 'b"r')
654    result.stopTest(test)
655    result.stopTestRun()
656    result.printErrors()
657
658    run_time = end_time - start_time
659    expected_re = OUTPUT_STRING % {
660        'suite_name': 'MockTest',
661        'tests': 1,
662        'failures': 0,
663        'errors': 0,
664        'run_time': run_time,
665        'start_time': re.escape(self._iso_timestamp(start_time),),
666        'test_name': 'skipped_test_with_reason',
667        'classname': '__main__.MockTest',
668        'status': 'notrun',
669        'result': 'suppressed',
670        'message': ''
671    }
672    self._assert_match(expected_re, self.xml_stream.getvalue())
673
674  def test_suite_time(self):
675    start_time1 = 100
676    end_time1 = 200
677    start_time2 = 400
678    end_time2 = 700
679    name = '__main__.MockTest.failing_test'
680    result = self._make_result((start_time1, start_time1, end_time1,
681                                start_time2, end_time2, end_time2))
682
683    test = MockTest('%s1' % name)
684    result.startTestRun()
685    result.startTest(test)
686    result.addSuccess(test)
687    result.stopTest(test)
688
689    test = MockTest('%s2' % name)
690    result.startTest(test)
691    result.addSuccess(test)
692    result.stopTest(test)
693    result.stopTestRun()
694    result.printErrors()
695
696    run_time = max(end_time1, end_time2) - min(start_time1, start_time2)
697    timestamp = self._iso_timestamp(start_time1)
698    expected_prefix = """<?xml version="1.0"?>
699<testsuites name="" tests="2" failures="0" errors="0" time="%.3f" timestamp="%s">
700<testsuite name="MockTest" tests="2" failures="0" errors="0" time="%.3f" timestamp="%s">
701""" % (run_time, timestamp, run_time, timestamp)
702    xml_output = self.xml_stream.getvalue()
703    self.assertTrue(
704        xml_output.startswith(expected_prefix),
705        '%s not found in %s' % (expected_prefix, xml_output))
706
707  def test_with_no_suite_name(self):
708    start_time = 1000
709    end_time = 1200
710    result = self._make_result((start_time, start_time, end_time, end_time))
711
712    test = MockTest('__main__.MockTest.bad_name')
713    result.startTestRun()
714    result.startTest(test)
715    result.addSuccess(test)
716    result.stopTest(test)
717    result.stopTestRun()
718    result.printErrors()
719
720    run_time = end_time - start_time
721    expected_re = OUTPUT_STRING % {
722        'suite_name': 'MockTest',
723        'tests': 1,
724        'failures': 0,
725        'errors': 0,
726        'run_time': run_time,
727        'start_time': re.escape(self._iso_timestamp(start_time),),
728        'test_name': 'bad_name',
729        'classname': '__main__.MockTest',
730        'status': 'run',
731        'result': 'completed',
732        'attributes': '',
733        'message': ''
734    }
735    self._assert_match(expected_re, self.xml_stream.getvalue())
736
737  def test_unnamed_parameterized_testcase(self):
738    """Test unnamed parameterized test cases.
739
740    Unnamed parameterized test cases might have non-alphanumeric characters in
741    their test method names. This test ensures xml_reporter handles them
742    correctly.
743    """
744
745    class ParameterizedTest(parameterized.TestCase):
746
747      @parameterized.parameters(('a (b.c)',))
748      def test_prefix(self, case):
749        self.assertTrue(case.startswith('a'))
750
751    start_time = 1000
752    end_time = 1200
753    result = self._make_result((start_time, start_time, end_time, end_time))
754    test = ParameterizedTest(methodName='test_prefix0')
755    result.startTestRun()
756    result.startTest(test)
757    result.addSuccess(test)
758    result.stopTest(test)
759    result.stopTestRun()
760    result.printErrors()
761
762    run_time = end_time - start_time
763    classname = xml_reporter._escape_xml_attr(
764        unittest.util.strclass(test.__class__))
765    expected_re = OUTPUT_STRING % {
766        'suite_name': 'ParameterizedTest',
767        'tests': 1,
768        'failures': 0,
769        'errors': 0,
770        'run_time': run_time,
771        'start_time': re.escape(self._iso_timestamp(start_time),),
772        'test_name': re.escape('test_prefix0&#x20;(&apos;a&#x20;(b.c)&apos;)'),
773        'classname': classname,
774        'status': 'run',
775        'result': 'completed',
776        'attributes': '',
777        'message': ''
778    }
779    self._assert_match(expected_re, self.xml_stream.getvalue())
780
781  def teststop_test_without_pending_test(self):
782    end_time = 1200
783    result = self._make_result((end_time,))
784
785    test = MockTest('__main__.MockTest.bad_name')
786    result.stopTest(test)
787    result.stopTestRun()
788    # Just verify that this doesn't crash
789
790  def test_text_and_xmltest_runner(self):
791    runner = xml_reporter.TextAndXMLTestRunner(self.xml_stream, self.stream,
792                                               'foo', 1)
793    result1 = runner._makeResult()
794    result2 = xml_reporter._TextAndXMLTestResult(None, None, None, 0, None)
795    self.failUnless(type(result1) is type(result2))
796
797  def test_timing_with_time_stub(self):
798    """Make sure that timing is correct even if time.time is stubbed out."""
799    try:
800      saved_time = time.time
801      time.time = lambda: -1
802      reporter = xml_reporter._TextAndXMLTestResult(self.xml_stream,
803                                                    self.stream,
804                                                    'foo', 0)
805      test = MockTest('bar')
806      reporter.startTest(test)
807      self.failIf(reporter.start_time == -1)
808    finally:
809      time.time = saved_time
810
811  def test_concurrent_add_and_delete_pending_test_case_result(self):
812    """Make sure adding/deleting pending test case results are thread safe."""
813    result = xml_reporter._TextAndXMLTestResult(None, self.stream, None, 0,
814                                                None)
815    def add_and_delete_pending_test_case_result(test_name):
816      test = MockTest(test_name)
817      result.addSuccess(test)
818      result.delete_pending_test_case_result(test)
819
820    for i in range(50):
821      add_and_delete_pending_test_case_result('add_and_delete_test%s' % i)
822    self.assertEqual(result.pending_test_case_results, {})
823
824  def test_concurrent_test_runs(self):
825    """Make sure concurrent test runs do not race each other."""
826    num_passing_tests = 20
827    num_failing_tests = 20
828    num_error_tests = 20
829    total_num_tests = num_passing_tests + num_failing_tests + num_error_tests
830
831    times = [0] + [i for i in range(2 * total_num_tests)
832                  ] + [2 * total_num_tests - 1]
833    result = self._make_result(times)
834    threads = []
835    names = []
836    result.startTestRun()
837    for i in range(num_passing_tests):
838      name = 'passing_concurrent_test_%s' % i
839      names.append(name)
840      test_name = '__main__.MockTest.%s' % name
841      # xml_reporter uses id(test) as the test identifier.
842      # In a real testing scenario, all the test instances are created before
843      # running them. So all ids will be unique.
844      # We must do the same here: create test instance beforehand.
845      test = MockTest(test_name)
846      threads.append(threading.Thread(
847          target=self._simulate_passing_test, args=(test, result)))
848    for i in range(num_failing_tests):
849      name = 'failing_concurrent_test_%s' % i
850      names.append(name)
851      test_name = '__main__.MockTest.%s' % name
852      test = MockTest(test_name)
853      threads.append(threading.Thread(
854          target=self._simulate_failing_test, args=(test, result)))
855    for i in range(num_error_tests):
856      name = 'error_concurrent_test_%s' % i
857      names.append(name)
858      test_name = '__main__.MockTest.%s' % name
859      test = MockTest(test_name)
860      threads.append(threading.Thread(
861          target=self._simulate_error_test, args=(test, result)))
862    for t in threads:
863      t.start()
864    for t in threads:
865      t.join()
866
867    result.stopTestRun()
868    result.printErrors()
869    tests_not_in_xml = []
870    for tn in names:
871      if tn not in self.xml_stream.getvalue():
872        tests_not_in_xml.append(tn)
873    msg = ('Expected xml_stream to contain all test %s results, but %s tests '
874           'are missing. List of missing tests: %s' % (
875               total_num_tests, len(tests_not_in_xml), tests_not_in_xml))
876    self.assertEqual([], tests_not_in_xml, msg)
877
878  def test_add_failure_during_stop_test(self):
879    """Tests an addFailure() call from within a stopTest() call stack."""
880    result = self._make_result((0, 2))
881    test = MockTest('__main__.MockTest.failing_test')
882    result.startTestRun()
883    result.startTest(test)
884
885    # Replace parent stopTest method from unittest.TextTestResult with
886    # a version that calls self.addFailure().
887    with mock.patch.object(
888        unittest.TextTestResult,
889        'stopTest',
890        side_effect=lambda t: result.addFailure(t, self.get_sample_failure())):
891      # Run stopTest in a separate thread since we are looking to verify that
892      # it does not deadlock, and would otherwise prevent the test from
893      # completing.
894      stop_test_thread = threading.Thread(target=result.stopTest, args=(test,))
895      stop_test_thread.daemon = True
896      stop_test_thread.start()
897
898    stop_test_thread.join(10.0)
899    self.assertFalse(stop_test_thread.is_alive(),
900                     'result.stopTest(test) call failed to complete')
901
902
903class XMLTest(absltest.TestCase):
904
905  def test_escape_xml(self):
906    self.assertEqual(xml_reporter._escape_xml_attr('"Hi" <\'>\t\r\n'),
907                     '&quot;Hi&quot;&#x20;&lt;&apos;&gt;&#x9;&#xD;&#xA;')
908
909
910class XmlReporterFixtureTest(absltest.TestCase):
911
912  def _get_helper(self):
913    binary_name = 'absl/testing/tests/xml_reporter_helper_test'
914    return _bazelize_command.get_executable_path(binary_name)
915
916  def _run_test_and_get_xml(self, flag):
917    """Runs xml_reporter_helper_test and returns an Element instance.
918
919    Runs xml_reporter_helper_test in a new process so that it can
920    exercise the entire test infrastructure, and easily test issues in
921    the test fixture.
922
923    Args:
924      flag: flag to pass to xml_reporter_helper_test
925
926    Returns:
927      The Element instance of the XML output.
928    """
929
930    xml_fhandle, xml_fname = tempfile.mkstemp()
931    os.close(xml_fhandle)
932
933    try:
934      binary = self._get_helper()
935      args = [binary, flag, '--xml_output_file=%s' % xml_fname]
936      ret = subprocess.call(args)
937      self.assertEqual(ret, 0)
938
939      xml = ElementTree.parse(xml_fname).getroot()
940    finally:
941      os.remove(xml_fname)
942
943    return xml
944
945  def _run_test(self, flag, num_errors, num_failures, suites):
946    xml_fhandle, xml_fname = tempfile.mkstemp()
947    os.close(xml_fhandle)
948
949    try:
950      binary = self._get_helper()
951      args = [binary, flag, '--xml_output_file=%s' % xml_fname]
952      ret = subprocess.call(args)
953      self.assertNotEqual(ret, 0)
954
955      xml = ElementTree.parse(xml_fname).getroot()
956      logging.info('xml output is:\n%s', ElementTree.tostring(xml))
957    finally:
958      os.remove(xml_fname)
959
960    self.assertEqual(int(xml.attrib['errors']), num_errors)
961    self.assertEqual(int(xml.attrib['failures']), num_failures)
962    self.assertLen(xml, len(suites))
963    actual_suites = sorted(
964        xml.findall('testsuite'), key=lambda x: x.attrib['name'])
965    suites = sorted(suites, key=lambda x: x['name'])
966    for actual_suite, expected_suite in zip(actual_suites, suites):
967      self.assertEqual(actual_suite.attrib['name'], expected_suite['name'])
968      self.assertLen(actual_suite, len(expected_suite['cases']))
969      actual_cases = sorted(actual_suite.findall('testcase'),
970                            key=lambda x: x.attrib['name'])
971      expected_cases = sorted(expected_suite['cases'], key=lambda x: x['name'])
972      for actual_case, expected_case in zip(actual_cases, expected_cases):
973        self.assertEqual(actual_case.attrib['name'], expected_case['name'])
974        self.assertEqual(actual_case.attrib['classname'],
975                         expected_case['classname'])
976        if 'error' in expected_case:
977          actual_error = actual_case.find('error')
978          self.assertEqual(actual_error.attrib['message'],
979                           expected_case['error'])
980        if 'failure' in expected_case:
981          actual_failure = actual_case.find('failure')
982          self.assertEqual(actual_failure.attrib['message'],
983                           expected_case['failure'])
984
985    return xml
986
987  def test_set_up_module_error(self):
988    self._run_test(
989        flag='--set_up_module_error',
990        num_errors=1,
991        num_failures=0,
992        suites=[{'name': '__main__',
993                 'cases': [{'name': 'setUpModule',
994                            'classname': '__main__',
995                            'error': 'setUpModule Errored!'}]}])
996
997  def test_tear_down_module_error(self):
998    self._run_test(
999        flag='--tear_down_module_error',
1000        num_errors=1,
1001        num_failures=0,
1002        suites=[{'name': 'FailableTest',
1003                 'cases': [{'name': 'test',
1004                            'classname': '__main__.FailableTest'}]},
1005                {'name': '__main__',
1006                 'cases': [{'name': 'tearDownModule',
1007                            'classname': '__main__',
1008                            'error': 'tearDownModule Errored!'}]}])
1009
1010  def test_set_up_class_error(self):
1011    self._run_test(
1012        flag='--set_up_class_error',
1013        num_errors=1,
1014        num_failures=0,
1015        suites=[{'name': 'FailableTest',
1016                 'cases': [{'name': 'setUpClass',
1017                            'classname': '__main__.FailableTest',
1018                            'error': 'setUpClass Errored!'}]}])
1019
1020  def test_tear_down_class_error(self):
1021    self._run_test(
1022        flag='--tear_down_class_error',
1023        num_errors=1,
1024        num_failures=0,
1025        suites=[{'name': 'FailableTest',
1026                 'cases': [{'name': 'test',
1027                            'classname': '__main__.FailableTest'},
1028                           {'name': 'tearDownClass',
1029                            'classname': '__main__.FailableTest',
1030                            'error': 'tearDownClass Errored!'}]}])
1031
1032  def test_set_up_error(self):
1033    self._run_test(
1034        flag='--set_up_error',
1035        num_errors=1,
1036        num_failures=0,
1037        suites=[{'name': 'FailableTest',
1038                 'cases': [{'name': 'test',
1039                            'classname': '__main__.FailableTest',
1040                            'error': 'setUp Errored!'}]}])
1041
1042  def test_tear_down_error(self):
1043    self._run_test(
1044        flag='--tear_down_error',
1045        num_errors=1,
1046        num_failures=0,
1047        suites=[{'name': 'FailableTest',
1048                 'cases': [{'name': 'test',
1049                            'classname': '__main__.FailableTest',
1050                            'error': 'tearDown Errored!'}]}])
1051
1052  def test_test_error(self):
1053    self._run_test(
1054        flag='--test_error',
1055        num_errors=1,
1056        num_failures=0,
1057        suites=[{'name': 'FailableTest',
1058                 'cases': [{'name': 'test',
1059                            'classname': '__main__.FailableTest',
1060                            'error': 'test Errored!'}]}])
1061
1062  def test_set_up_failure(self):
1063    self._run_test(
1064        flag='--set_up_fail',
1065        num_errors=0,
1066        num_failures=1,
1067        suites=[{'name': 'FailableTest',
1068                 'cases': [{'name': 'test',
1069                            'classname': '__main__.FailableTest',
1070                            'failure': 'setUp Failed!'}]}])
1071
1072  def test_tear_down_failure(self):
1073    self._run_test(
1074        flag='--tear_down_fail',
1075        num_errors=0,
1076        num_failures=1,
1077        suites=[{'name': 'FailableTest',
1078                 'cases': [{'name': 'test',
1079                            'classname': '__main__.FailableTest',
1080                            'failure': 'tearDown Failed!'}]}])
1081
1082  def test_test_fail(self):
1083    self._run_test(
1084        flag='--test_fail',
1085        num_errors=0,
1086        num_failures=1,
1087        suites=[{'name': 'FailableTest',
1088                 'cases': [{'name': 'test',
1089                            'classname': '__main__.FailableTest',
1090                            'failure': 'test Failed!'}]}])
1091
1092  def test_test_randomization_seed_logging(self):
1093    # We expect the resulting XML to start as follows:
1094    # <testsuites ...>
1095    #  <properties>
1096    #   <property name="test_randomize_ordering_seed" value="17" />
1097    # ...
1098    #
1099    # which we validate here.
1100    out = self._run_test_and_get_xml('--test_randomize_ordering_seed=17')
1101    expected_attrib = {'name': 'test_randomize_ordering_seed', 'value': '17'}
1102    property_attributes = [
1103        prop.attrib for prop in out.findall('./properties/property')]
1104    self.assertIn(expected_attrib, property_attributes)
1105
1106
1107if __name__ == '__main__':
1108  absltest.main()
1109