1 # Copyright (c) 2008-2011 testtools developers. See LICENSE for details.
3 """Test TestResults and related things."""
17 from testtools import (
18 ExtendedToOriginalDecorator,
23 ThreadsafeForwardingResult,
26 from testtools.compat import (
28 _get_exception_encoding,
34 from testtools.content import (
39 from testtools.content_type import ContentType, UTF8_TEXT
40 from testtools.matchers import (
46 from testtools.tests.helpers import (
50 run_with_stack_hidden,
52 from testtools.testresult.doubles import (
57 from testtools.testresult.real import (
63 def make_erroring_test():
70 def make_failing_test():
77 def make_unexpectedly_successful_test():
80 self.expectFailure("yo!", lambda: None)
81 return Test("succeeded")
91 def make_exception_info(exceptionFactory, *args, **kwargs):
93 raise exceptionFactory(*args, **kwargs)
98 class Python26Contract(object):
100 def test_fresh_result_is_successful(self):
101 # A result is considered successful before any tests are run.
102 result = self.makeResult()
103 self.assertTrue(result.wasSuccessful())
105 def test_addError_is_failure(self):
106 # addError fails the test run.
107 result = self.makeResult()
108 result.startTest(self)
109 result.addError(self, an_exc_info)
110 result.stopTest(self)
111 self.assertFalse(result.wasSuccessful())
113 def test_addFailure_is_failure(self):
114 # addFailure fails the test run.
115 result = self.makeResult()
116 result.startTest(self)
117 result.addFailure(self, an_exc_info)
118 result.stopTest(self)
119 self.assertFalse(result.wasSuccessful())
121 def test_addSuccess_is_success(self):
122 # addSuccess does not fail the test run.
123 result = self.makeResult()
124 result.startTest(self)
125 result.addSuccess(self)
126 result.stopTest(self)
127 self.assertTrue(result.wasSuccessful())
130 class Python27Contract(Python26Contract):
132 def test_addExpectedFailure(self):
133 # Calling addExpectedFailure(test, exc_info) completes ok.
134 result = self.makeResult()
135 result.startTest(self)
136 result.addExpectedFailure(self, an_exc_info)
138 def test_addExpectedFailure_is_success(self):
139 # addExpectedFailure does not fail the test run.
140 result = self.makeResult()
141 result.startTest(self)
142 result.addExpectedFailure(self, an_exc_info)
143 result.stopTest(self)
144 self.assertTrue(result.wasSuccessful())
146 def test_addSkipped(self):
147 # Calling addSkip(test, reason) completes ok.
148 result = self.makeResult()
149 result.startTest(self)
150 result.addSkip(self, _u("Skipped for some reason"))
152 def test_addSkip_is_success(self):
153 # addSkip does not fail the test run.
154 result = self.makeResult()
155 result.startTest(self)
156 result.addSkip(self, _u("Skipped for some reason"))
157 result.stopTest(self)
158 self.assertTrue(result.wasSuccessful())
160 def test_addUnexpectedSuccess(self):
161 # Calling addUnexpectedSuccess(test) completes ok.
162 result = self.makeResult()
163 result.startTest(self)
164 result.addUnexpectedSuccess(self)
166 def test_addUnexpectedSuccess_was_successful(self):
167 # addUnexpectedSuccess does not fail the test run in Python 2.7.
168 result = self.makeResult()
169 result.startTest(self)
170 result.addUnexpectedSuccess(self)
171 result.stopTest(self)
172 self.assertTrue(result.wasSuccessful())
174 def test_startStopTestRun(self):
175 # Calling startTestRun completes ok.
176 result = self.makeResult()
177 result.startTestRun()
181 class DetailsContract(Python27Contract):
182 """Tests for the contract of TestResults."""
184 def test_addExpectedFailure_details(self):
185 # Calling addExpectedFailure(test, details=xxx) completes ok.
186 result = self.makeResult()
187 result.startTest(self)
188 result.addExpectedFailure(self, details={})
190 def test_addError_details(self):
191 # Calling addError(test, details=xxx) completes ok.
192 result = self.makeResult()
193 result.startTest(self)
194 result.addError(self, details={})
196 def test_addFailure_details(self):
197 # Calling addFailure(test, details=xxx) completes ok.
198 result = self.makeResult()
199 result.startTest(self)
200 result.addFailure(self, details={})
202 def test_addSkipped_details(self):
203 # Calling addSkip(test, reason) completes ok.
204 result = self.makeResult()
205 result.startTest(self)
206 result.addSkip(self, details={})
208 def test_addUnexpectedSuccess_details(self):
209 # Calling addUnexpectedSuccess(test) completes ok.
210 result = self.makeResult()
211 result.startTest(self)
212 result.addUnexpectedSuccess(self, details={})
214 def test_addSuccess_details(self):
215 # Calling addSuccess(test) completes ok.
216 result = self.makeResult()
217 result.startTest(self)
218 result.addSuccess(self, details={})
221 class FallbackContract(DetailsContract):
222 """When we fallback we take our policy choice to map calls.
224 For instance, we map unexpectedSuccess to an error code, not to success.
227 def test_addUnexpectedSuccess_was_successful(self):
228 # addUnexpectedSuccess fails test run in testtools.
229 result = self.makeResult()
230 result.startTest(self)
231 result.addUnexpectedSuccess(self)
232 result.stopTest(self)
233 self.assertFalse(result.wasSuccessful())
236 class StartTestRunContract(FallbackContract):
237 """Defines the contract for testtools policy choices.
239 That is things which are not simply extensions to unittest but choices we
240 have made differently.
243 def test_startTestRun_resets_unexpected_success(self):
244 result = self.makeResult()
245 result.startTest(self)
246 result.addUnexpectedSuccess(self)
247 result.stopTest(self)
248 result.startTestRun()
249 self.assertTrue(result.wasSuccessful())
251 def test_startTestRun_resets_failure(self):
252 result = self.makeResult()
253 result.startTest(self)
254 result.addFailure(self, an_exc_info)
255 result.stopTest(self)
256 result.startTestRun()
257 self.assertTrue(result.wasSuccessful())
259 def test_startTestRun_resets_errors(self):
260 result = self.makeResult()
261 result.startTest(self)
262 result.addError(self, an_exc_info)
263 result.stopTest(self)
264 result.startTestRun()
265 self.assertTrue(result.wasSuccessful())
268 class TestTestResultContract(TestCase, StartTestRunContract):
270 run_test_with = FullStackRunTest
272 def makeResult(self):
276 class TestMultiTestResultContract(TestCase, StartTestRunContract):
278 run_test_with = FullStackRunTest
280 def makeResult(self):
281 return MultiTestResult(TestResult(), TestResult())
284 class TestTextTestResultContract(TestCase, StartTestRunContract):
286 run_test_with = FullStackRunTest
288 def makeResult(self):
289 return TextTestResult(StringIO())
292 class TestThreadSafeForwardingResultContract(TestCase, StartTestRunContract):
294 run_test_with = FullStackRunTest
296 def makeResult(self):
297 result_semaphore = threading.Semaphore(1)
298 target = TestResult()
299 return ThreadsafeForwardingResult(target, result_semaphore)
302 class TestExtendedTestResultContract(TestCase, StartTestRunContract):
304 def makeResult(self):
305 return ExtendedTestResult()
308 class TestPython26TestResultContract(TestCase, Python26Contract):
310 def makeResult(self):
311 return Python26TestResult()
314 class TestAdaptedPython26TestResultContract(TestCase, FallbackContract):
316 def makeResult(self):
317 return ExtendedToOriginalDecorator(Python26TestResult())
320 class TestPython27TestResultContract(TestCase, Python27Contract):
322 def makeResult(self):
323 return Python27TestResult()
326 class TestAdaptedPython27TestResultContract(TestCase, DetailsContract):
328 def makeResult(self):
329 return ExtendedToOriginalDecorator(Python27TestResult())
332 class TestTestResult(TestCase):
333 """Tests for 'TestResult'."""
335 run_tests_with = FullStackRunTest
337 def makeResult(self):
338 """Make an arbitrary result for testing."""
341 def test_addSkipped(self):
342 # Calling addSkip on a TestResult records the test that was skipped in
343 # its skip_reasons dict.
344 result = self.makeResult()
345 result.addSkip(self, _u("Skipped for some reason"))
346 self.assertEqual({_u("Skipped for some reason"):[self]},
348 result.addSkip(self, _u("Skipped for some reason"))
349 self.assertEqual({_u("Skipped for some reason"):[self, self]},
351 result.addSkip(self, _u("Skipped for another reason"))
352 self.assertEqual({_u("Skipped for some reason"):[self, self],
353 _u("Skipped for another reason"):[self]},
356 def test_now_datetime_now(self):
357 result = self.makeResult()
358 olddatetime = testresult.real.datetime
360 testresult.real.datetime = olddatetime
361 self.addCleanup(restore)
364 now = datetime.datetime.now(utc)
365 stubdatetime = Module()
366 stubdatetime.datetime = Module()
367 stubdatetime.datetime.now = lambda tz: now
368 testresult.real.datetime = stubdatetime
369 # Calling _now() looks up the time.
370 self.assertEqual(now, result._now())
371 then = now + datetime.timedelta(0, 1)
372 # Set an explicit datetime, which gets returned from then on.
374 self.assertNotEqual(now, result._now())
375 self.assertEqual(then, result._now())
376 # go back to looking it up.
378 self.assertEqual(now, result._now())
380 def test_now_datetime_time(self):
381 result = self.makeResult()
382 now = datetime.datetime.now(utc)
384 self.assertEqual(now, result._now())
386 def test_traceback_formatting_without_stack_hidden(self):
387 # During the testtools test run, we show our levels of the stack,
388 # because we want to be able to use our test suite to debug our own
390 result = self.makeResult()
391 test = make_erroring_test()
396 'Traceback (most recent call last):\n'
397 ' File "...testtools...runtest.py", line ..., in _run_user\n'
398 ' return fn(*args, **kwargs)\n'
399 ' File "...testtools...testcase.py", line ..., in _run_test_method\n'
400 ' return self._get_test_method()()\n'
401 ' File "...testtools...tests...test_testresult.py", line ..., in error\n'
403 'ZeroDivisionError: ...\n',
404 doctest.ELLIPSIS | doctest.REPORT_UDIFF))
406 def test_traceback_formatting_with_stack_hidden(self):
407 result = self.makeResult()
408 test = make_erroring_test()
409 run_with_stack_hidden(True, test.run, result)
413 'Traceback (most recent call last):\n'
414 ' File "...testtools...tests...test_testresult.py", line ..., in error\n'
416 'ZeroDivisionError: ...\n',
420 class TestMultiTestResult(TestCase):
421 """Tests for 'MultiTestResult'."""
424 super(TestMultiTestResult, self).setUp()
425 self.result1 = LoggingResult([])
426 self.result2 = LoggingResult([])
427 self.multiResult = MultiTestResult(self.result1, self.result2)
429 def assertResultLogsEqual(self, expectedEvents):
430 """Assert that our test results have received the expected events."""
431 self.assertEqual(expectedEvents, self.result1._events)
432 self.assertEqual(expectedEvents, self.result2._events)
436 '<MultiTestResult (%r, %r)>' % (
437 ExtendedToOriginalDecorator(self.result1),
438 ExtendedToOriginalDecorator(self.result2)),
439 repr(self.multiResult))
441 def test_empty(self):
442 # Initializing a `MultiTestResult` doesn't do anything to its
444 self.assertResultLogsEqual([])
446 def test_startTest(self):
447 # Calling `startTest` on a `MultiTestResult` calls `startTest` on all
449 self.multiResult.startTest(self)
450 self.assertResultLogsEqual([('startTest', self)])
452 def test_stopTest(self):
453 # Calling `stopTest` on a `MultiTestResult` calls `stopTest` on all
455 self.multiResult.stopTest(self)
456 self.assertResultLogsEqual([('stopTest', self)])
458 def test_addSkipped(self):
459 # Calling `addSkip` on a `MultiTestResult` calls addSkip on its
461 reason = _u("Skipped for some reason")
462 self.multiResult.addSkip(self, reason)
463 self.assertResultLogsEqual([('addSkip', self, reason)])
465 def test_addSuccess(self):
466 # Calling `addSuccess` on a `MultiTestResult` calls `addSuccess` on
467 # all its `TestResult`s.
468 self.multiResult.addSuccess(self)
469 self.assertResultLogsEqual([('addSuccess', self)])
472 # Calling `done` on a `MultiTestResult` calls `done` on all its
474 self.multiResult.done()
475 self.assertResultLogsEqual([('done')])
477 def test_addFailure(self):
478 # Calling `addFailure` on a `MultiTestResult` calls `addFailure` on
479 # all its `TestResult`s.
480 exc_info = make_exception_info(AssertionError, 'failure')
481 self.multiResult.addFailure(self, exc_info)
482 self.assertResultLogsEqual([('addFailure', self, exc_info)])
484 def test_addError(self):
485 # Calling `addError` on a `MultiTestResult` calls `addError` on all
487 exc_info = make_exception_info(RuntimeError, 'error')
488 self.multiResult.addError(self, exc_info)
489 self.assertResultLogsEqual([('addError', self, exc_info)])
491 def test_startTestRun(self):
492 # Calling `startTestRun` on a `MultiTestResult` forwards to all its
494 self.multiResult.startTestRun()
495 self.assertResultLogsEqual([('startTestRun')])
497 def test_stopTestRun(self):
498 # Calling `stopTestRun` on a `MultiTestResult` forwards to all its
500 self.multiResult.stopTestRun()
501 self.assertResultLogsEqual([('stopTestRun')])
503 def test_stopTestRun_returns_results(self):
504 # `MultiTestResult.stopTestRun` returns a tuple of all of the return
505 # values the `stopTestRun`s that it forwards to.
506 class Result(LoggingResult):
507 def stopTestRun(self):
508 super(Result, self).stopTestRun()
510 multi_result = MultiTestResult(Result([]), Result([]))
511 result = multi_result.stopTestRun()
512 self.assertEqual(('foo', 'foo'), result)
515 # the time call is dispatched, not eaten by the base class
516 self.multiResult.time('foo')
517 self.assertResultLogsEqual([('time', 'foo')])
520 class TestTextTestResult(TestCase):
521 """Tests for 'TextTestResult'."""
524 super(TestTextTestResult, self).setUp()
525 self.result = TextTestResult(StringIO())
528 return self.result.stream.getvalue()
530 def test__init_sets_stream(self):
531 result = TextTestResult("fp")
532 self.assertEqual("fp", result.stream)
534 def reset_output(self):
535 self.result.stream = StringIO()
537 def test_startTestRun(self):
538 self.result.startTestRun()
539 self.assertEqual("Tests running...\n", self.getvalue())
541 def test_stopTestRun_count_many(self):
543 self.result.startTestRun()
544 self.result.startTest(test)
545 self.result.stopTest(test)
546 self.result.startTest(test)
547 self.result.stopTest(test)
548 self.result.stream = StringIO()
549 self.result.stopTestRun()
550 self.assertThat(self.getvalue(),
551 DocTestMatches("\nRan 2 tests in ...s\n...", doctest.ELLIPSIS))
553 def test_stopTestRun_count_single(self):
555 self.result.startTestRun()
556 self.result.startTest(test)
557 self.result.stopTest(test)
559 self.result.stopTestRun()
560 self.assertThat(self.getvalue(),
561 DocTestMatches("\nRan 1 test in ...s\nOK\n", doctest.ELLIPSIS))
563 def test_stopTestRun_count_zero(self):
564 self.result.startTestRun()
566 self.result.stopTestRun()
567 self.assertThat(self.getvalue(),
568 DocTestMatches("\nRan 0 tests in ...s\nOK\n", doctest.ELLIPSIS))
570 def test_stopTestRun_current_time(self):
572 now = datetime.datetime.now(utc)
573 self.result.time(now)
574 self.result.startTestRun()
575 self.result.startTest(test)
576 now = now + datetime.timedelta(0, 0, 0, 1)
577 self.result.time(now)
578 self.result.stopTest(test)
580 self.result.stopTestRun()
581 self.assertThat(self.getvalue(),
582 DocTestMatches("... in 0.001s\n...", doctest.ELLIPSIS))
584 def test_stopTestRun_successful(self):
585 self.result.startTestRun()
586 self.result.stopTestRun()
587 self.assertThat(self.getvalue(),
588 DocTestMatches("...\nOK\n", doctest.ELLIPSIS))
590 def test_stopTestRun_not_successful_failure(self):
591 test = make_failing_test()
592 self.result.startTestRun()
593 test.run(self.result)
594 self.result.stopTestRun()
595 self.assertThat(self.getvalue(),
596 DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS))
598 def test_stopTestRun_not_successful_error(self):
599 test = make_erroring_test()
600 self.result.startTestRun()
601 test.run(self.result)
602 self.result.stopTestRun()
603 self.assertThat(self.getvalue(),
604 DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS))
606 def test_stopTestRun_not_successful_unexpected_success(self):
607 test = make_unexpectedly_successful_test()
608 self.result.startTestRun()
609 test.run(self.result)
610 self.result.stopTestRun()
611 self.assertThat(self.getvalue(),
612 DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS))
614 def test_stopTestRun_shows_details(self):
616 self.result.startTestRun()
617 make_erroring_test().run(self.result)
618 make_unexpectedly_successful_test().run(self.result)
619 make_failing_test().run(self.result)
621 self.result.stopTestRun()
622 run_with_stack_hidden(True, run_tests)
623 self.assertThat(self.getvalue(),
624 DocTestMatches("""...======================================================================
625 ERROR: testtools.tests.test_testresult.Test.error
626 ----------------------------------------------------------------------
627 Traceback (most recent call last):
628 File "...testtools...tests...test_testresult.py", line ..., in error
630 ZeroDivisionError:... divi... by zero...
631 ======================================================================
632 FAIL: testtools.tests.test_testresult.Test.failed
633 ----------------------------------------------------------------------
634 Traceback (most recent call last):
635 File "...testtools...tests...test_testresult.py", line ..., in failed
638 ======================================================================
639 UNEXPECTED SUCCESS: testtools.tests.test_testresult.Test.succeeded
640 ----------------------------------------------------------------------
641 ...""", doctest.ELLIPSIS | doctest.REPORT_NDIFF))
644 class TestThreadSafeForwardingResult(TestCase):
645 """Tests for `TestThreadSafeForwardingResult`."""
648 super(TestThreadSafeForwardingResult, self).setUp()
649 self.result_semaphore = threading.Semaphore(1)
650 self.target = LoggingResult([])
651 self.result1 = ThreadsafeForwardingResult(self.target,
652 self.result_semaphore)
654 def test_nonforwarding_methods(self):
655 # startTest and stopTest are not forwarded because they need to be
657 self.result1.startTest(self)
658 self.result1.stopTest(self)
659 self.assertEqual([], self.target._events)
661 def test_startTestRun(self):
662 self.result1.startTestRun()
663 self.result2 = ThreadsafeForwardingResult(self.target,
664 self.result_semaphore)
665 self.result2.startTestRun()
666 self.assertEqual(["startTestRun", "startTestRun"], self.target._events)
668 def test_stopTestRun(self):
669 self.result1.stopTestRun()
670 self.result2 = ThreadsafeForwardingResult(self.target,
671 self.result_semaphore)
672 self.result2.stopTestRun()
673 self.assertEqual(["stopTestRun", "stopTestRun"], self.target._events)
675 def test_forwarding_methods(self):
676 # error, failure, skip and success are forwarded in batches.
677 exc_info1 = make_exception_info(RuntimeError, 'error')
678 starttime1 = datetime.datetime.utcfromtimestamp(1.489)
679 endtime1 = datetime.datetime.utcfromtimestamp(51.476)
680 self.result1.time(starttime1)
681 self.result1.startTest(self)
682 self.result1.time(endtime1)
683 self.result1.addError(self, exc_info1)
684 exc_info2 = make_exception_info(AssertionError, 'failure')
685 starttime2 = datetime.datetime.utcfromtimestamp(2.489)
686 endtime2 = datetime.datetime.utcfromtimestamp(3.476)
687 self.result1.time(starttime2)
688 self.result1.startTest(self)
689 self.result1.time(endtime2)
690 self.result1.addFailure(self, exc_info2)
691 reason = _u("Skipped for some reason")
692 starttime3 = datetime.datetime.utcfromtimestamp(4.489)
693 endtime3 = datetime.datetime.utcfromtimestamp(5.476)
694 self.result1.time(starttime3)
695 self.result1.startTest(self)
696 self.result1.time(endtime3)
697 self.result1.addSkip(self, reason)
698 starttime4 = datetime.datetime.utcfromtimestamp(6.489)
699 endtime4 = datetime.datetime.utcfromtimestamp(7.476)
700 self.result1.time(starttime4)
701 self.result1.startTest(self)
702 self.result1.time(endtime4)
703 self.result1.addSuccess(self)
705 ('time', starttime1),
708 ('addError', self, exc_info1),
710 ('time', starttime2),
713 ('addFailure', self, exc_info2),
715 ('time', starttime3),
718 ('addSkip', self, reason),
720 ('time', starttime4),
723 ('addSuccess', self),
725 ], self.target._events)
728 class TestExtendedToOriginalResultDecoratorBase(TestCase):
730 def make_26_result(self):
731 self.result = Python26TestResult()
732 self.make_converter()
734 def make_27_result(self):
735 self.result = Python27TestResult()
736 self.make_converter()
738 def make_converter(self):
739 self.converter = ExtendedToOriginalDecorator(self.result)
741 def make_extended_result(self):
742 self.result = ExtendedTestResult()
743 self.make_converter()
745 def check_outcome_details(self, outcome):
746 """Call an outcome with a details dict to be passed through."""
747 # This dict is /not/ convertible - thats deliberate, as it should
748 # not hit the conversion code path.
749 details = {'foo': 'bar'}
750 getattr(self.converter, outcome)(self, details=details)
751 self.assertEqual([(outcome, self, details)], self.result._events)
753 def get_details_and_string(self):
754 """Get a details dict and expected string."""
755 text1 = lambda: [_b("1\n2\n")]
756 text2 = lambda: [_b("3\n4\n")]
757 bin1 = lambda: [_b("5\n")]
758 details = {'text 1': Content(ContentType('text', 'plain'), text1),
759 'text 2': Content(ContentType('text', 'strange'), text2),
760 'bin 1': Content(ContentType('application', 'binary'), bin1)}
763 " bin 1 (application/binary)\n"
775 def check_outcome_details_to_exec_info(self, outcome, expected=None):
776 """Call an outcome with a details dict to be made into exc_info."""
777 # The conversion is a done using RemoteError and the string contents
778 # of the text types in the details dict.
781 details, err_str = self.get_details_and_string()
782 getattr(self.converter, outcome)(self, details=details)
783 err = self.converter._details_to_exc_info(details)
784 self.assertEqual([(expected, self, err)], self.result._events)
786 def check_outcome_details_to_nothing(self, outcome, expected=None):
787 """Call an outcome with a details dict to be swallowed."""
790 details = {'foo': 'bar'}
791 getattr(self.converter, outcome)(self, details=details)
792 self.assertEqual([(expected, self)], self.result._events)
794 def check_outcome_details_to_string(self, outcome):
795 """Call an outcome with a details dict to be stringified."""
796 details, err_str = self.get_details_and_string()
797 getattr(self.converter, outcome)(self, details=details)
798 self.assertEqual([(outcome, self, err_str)], self.result._events)
800 def check_outcome_details_to_arg(self, outcome, arg, extra_detail=None):
801 """Call an outcome with a details dict to have an arg extracted."""
802 details, _ = self.get_details_and_string()
804 details.update(extra_detail)
805 getattr(self.converter, outcome)(self, details=details)
806 self.assertEqual([(outcome, self, arg)], self.result._events)
808 def check_outcome_exc_info(self, outcome, expected=None):
809 """Check that calling a legacy outcome still works."""
810 # calling some outcome with the legacy exc_info style api (no keyword
811 # parameters) gets passed through.
815 getattr(self.converter, outcome)(self, err)
816 self.assertEqual([(expected, self, err)], self.result._events)
818 def check_outcome_exc_info_to_nothing(self, outcome, expected=None):
819 """Check that calling a legacy outcome on a fallback works."""
820 # calling some outcome with the legacy exc_info style api (no keyword
821 # parameters) gets passed through.
825 getattr(self.converter, outcome)(self, err)
826 self.assertEqual([(expected, self)], self.result._events)
828 def check_outcome_nothing(self, outcome, expected=None):
829 """Check that calling a legacy outcome still works."""
832 getattr(self.converter, outcome)(self)
833 self.assertEqual([(expected, self)], self.result._events)
835 def check_outcome_string_nothing(self, outcome, expected):
836 """Check that calling outcome with a string calls expected."""
837 getattr(self.converter, outcome)(self, "foo")
838 self.assertEqual([(expected, self)], self.result._events)
840 def check_outcome_string(self, outcome):
841 """Check that calling outcome with a string works."""
842 getattr(self.converter, outcome)(self, "foo")
843 self.assertEqual([(outcome, self, "foo")], self.result._events)
846 class TestExtendedToOriginalResultDecorator(
847 TestExtendedToOriginalResultDecoratorBase):
849 def test_progress_py26(self):
850 self.make_26_result()
851 self.converter.progress(1, 2)
853 def test_progress_py27(self):
854 self.make_27_result()
855 self.converter.progress(1, 2)
857 def test_progress_pyextended(self):
858 self.make_extended_result()
859 self.converter.progress(1, 2)
860 self.assertEqual([('progress', 1, 2)], self.result._events)
862 def test_shouldStop(self):
863 self.make_26_result()
864 self.assertEqual(False, self.converter.shouldStop)
865 self.converter.decorated.stop()
866 self.assertEqual(True, self.converter.shouldStop)
868 def test_startTest_py26(self):
869 self.make_26_result()
870 self.converter.startTest(self)
871 self.assertEqual([('startTest', self)], self.result._events)
873 def test_startTest_py27(self):
874 self.make_27_result()
875 self.converter.startTest(self)
876 self.assertEqual([('startTest', self)], self.result._events)
878 def test_startTest_pyextended(self):
879 self.make_extended_result()
880 self.converter.startTest(self)
881 self.assertEqual([('startTest', self)], self.result._events)
883 def test_startTestRun_py26(self):
884 self.make_26_result()
885 self.converter.startTestRun()
886 self.assertEqual([], self.result._events)
888 def test_startTestRun_py27(self):
889 self.make_27_result()
890 self.converter.startTestRun()
891 self.assertEqual([('startTestRun',)], self.result._events)
893 def test_startTestRun_pyextended(self):
894 self.make_extended_result()
895 self.converter.startTestRun()
896 self.assertEqual([('startTestRun',)], self.result._events)
898 def test_stopTest_py26(self):
899 self.make_26_result()
900 self.converter.stopTest(self)
901 self.assertEqual([('stopTest', self)], self.result._events)
903 def test_stopTest_py27(self):
904 self.make_27_result()
905 self.converter.stopTest(self)
906 self.assertEqual([('stopTest', self)], self.result._events)
908 def test_stopTest_pyextended(self):
909 self.make_extended_result()
910 self.converter.stopTest(self)
911 self.assertEqual([('stopTest', self)], self.result._events)
913 def test_stopTestRun_py26(self):
914 self.make_26_result()
915 self.converter.stopTestRun()
916 self.assertEqual([], self.result._events)
918 def test_stopTestRun_py27(self):
919 self.make_27_result()
920 self.converter.stopTestRun()
921 self.assertEqual([('stopTestRun',)], self.result._events)
923 def test_stopTestRun_pyextended(self):
924 self.make_extended_result()
925 self.converter.stopTestRun()
926 self.assertEqual([('stopTestRun',)], self.result._events)
928 def test_tags_py26(self):
929 self.make_26_result()
930 self.converter.tags(1, 2)
932 def test_tags_py27(self):
933 self.make_27_result()
934 self.converter.tags(1, 2)
936 def test_tags_pyextended(self):
937 self.make_extended_result()
938 self.converter.tags(1, 2)
939 self.assertEqual([('tags', 1, 2)], self.result._events)
941 def test_time_py26(self):
942 self.make_26_result()
943 self.converter.time(1)
945 def test_time_py27(self):
946 self.make_27_result()
947 self.converter.time(1)
949 def test_time_pyextended(self):
950 self.make_extended_result()
951 self.converter.time(1)
952 self.assertEqual([('time', 1)], self.result._events)
955 class TestExtendedToOriginalAddError(TestExtendedToOriginalResultDecoratorBase):
959 def test_outcome_Original_py26(self):
960 self.make_26_result()
961 self.check_outcome_exc_info(self.outcome)
963 def test_outcome_Original_py27(self):
964 self.make_27_result()
965 self.check_outcome_exc_info(self.outcome)
967 def test_outcome_Original_pyextended(self):
968 self.make_extended_result()
969 self.check_outcome_exc_info(self.outcome)
971 def test_outcome_Extended_py26(self):
972 self.make_26_result()
973 self.check_outcome_details_to_exec_info(self.outcome)
975 def test_outcome_Extended_py27(self):
976 self.make_27_result()
977 self.check_outcome_details_to_exec_info(self.outcome)
979 def test_outcome_Extended_pyextended(self):
980 self.make_extended_result()
981 self.check_outcome_details(self.outcome)
983 def test_outcome__no_details(self):
984 self.make_extended_result()
986 lambda: getattr(self.converter, self.outcome)(self),
987 Raises(MatchesException(ValueError)))
990 class TestExtendedToOriginalAddFailure(
991 TestExtendedToOriginalAddError):
993 outcome = 'addFailure'
996 class TestExtendedToOriginalAddExpectedFailure(
997 TestExtendedToOriginalAddError):
999 outcome = 'addExpectedFailure'
1001 def test_outcome_Original_py26(self):
1002 self.make_26_result()
1003 self.check_outcome_exc_info_to_nothing(self.outcome, 'addSuccess')
1005 def test_outcome_Extended_py26(self):
1006 self.make_26_result()
1007 self.check_outcome_details_to_nothing(self.outcome, 'addSuccess')
1011 class TestExtendedToOriginalAddSkip(
1012 TestExtendedToOriginalResultDecoratorBase):
1016 def test_outcome_Original_py26(self):
1017 self.make_26_result()
1018 self.check_outcome_string_nothing(self.outcome, 'addSuccess')
1020 def test_outcome_Original_py27(self):
1021 self.make_27_result()
1022 self.check_outcome_string(self.outcome)
1024 def test_outcome_Original_pyextended(self):
1025 self.make_extended_result()
1026 self.check_outcome_string(self.outcome)
1028 def test_outcome_Extended_py26(self):
1029 self.make_26_result()
1030 self.check_outcome_string_nothing(self.outcome, 'addSuccess')
1032 def test_outcome_Extended_py27_no_reason(self):
1033 self.make_27_result()
1034 self.check_outcome_details_to_string(self.outcome)
1036 def test_outcome_Extended_py27_reason(self):
1037 self.make_27_result()
1038 self.check_outcome_details_to_arg(self.outcome, 'foo',
1039 {'reason': Content(UTF8_TEXT, lambda:[_b('foo')])})
1041 def test_outcome_Extended_pyextended(self):
1042 self.make_extended_result()
1043 self.check_outcome_details(self.outcome)
1045 def test_outcome__no_details(self):
1046 self.make_extended_result()
1048 lambda: getattr(self.converter, self.outcome)(self),
1049 Raises(MatchesException(ValueError)))
1052 class TestExtendedToOriginalAddSuccess(
1053 TestExtendedToOriginalResultDecoratorBase):
1055 outcome = 'addSuccess'
1056 expected = 'addSuccess'
1058 def test_outcome_Original_py26(self):
1059 self.make_26_result()
1060 self.check_outcome_nothing(self.outcome, self.expected)
1062 def test_outcome_Original_py27(self):
1063 self.make_27_result()
1064 self.check_outcome_nothing(self.outcome)
1066 def test_outcome_Original_pyextended(self):
1067 self.make_extended_result()
1068 self.check_outcome_nothing(self.outcome)
1070 def test_outcome_Extended_py26(self):
1071 self.make_26_result()
1072 self.check_outcome_details_to_nothing(self.outcome, self.expected)
1074 def test_outcome_Extended_py27(self):
1075 self.make_27_result()
1076 self.check_outcome_details_to_nothing(self.outcome)
1078 def test_outcome_Extended_pyextended(self):
1079 self.make_extended_result()
1080 self.check_outcome_details(self.outcome)
1083 class TestExtendedToOriginalAddUnexpectedSuccess(
1084 TestExtendedToOriginalResultDecoratorBase):
1086 outcome = 'addUnexpectedSuccess'
1087 expected = 'addFailure'
1089 def test_outcome_Original_py26(self):
1090 self.make_26_result()
1091 getattr(self.converter, self.outcome)(self)
1092 [event] = self.result._events
1093 self.assertEqual((self.expected, self), event[:2])
1095 def test_outcome_Original_py27(self):
1096 self.make_27_result()
1097 self.check_outcome_nothing(self.outcome)
1099 def test_outcome_Original_pyextended(self):
1100 self.make_extended_result()
1101 self.check_outcome_nothing(self.outcome)
1103 def test_outcome_Extended_py26(self):
1104 self.make_26_result()
1105 getattr(self.converter, self.outcome)(self)
1106 [event] = self.result._events
1107 self.assertEqual((self.expected, self), event[:2])
1109 def test_outcome_Extended_py27(self):
1110 self.make_27_result()
1111 self.check_outcome_details_to_nothing(self.outcome)
1113 def test_outcome_Extended_pyextended(self):
1114 self.make_extended_result()
1115 self.check_outcome_details(self.outcome)
1118 class TestExtendedToOriginalResultOtherAttributes(
1119 TestExtendedToOriginalResultDecoratorBase):
1121 def test_other_attribute(self):
1122 class OtherExtendedResult:
1126 self.result = OtherExtendedResult()
1127 self.make_converter()
1128 self.assertEqual(1, self.converter.bar)
1129 self.assertEqual(2, self.converter.foo())
1132 class TestNonAsciiResults(TestCase):
1133 """Test all kinds of tracebacks are cleanly interpreted as unicode
1135 Currently only uses weak "contains" assertions, would be good to be much
1136 stricter about the expected output. This would add a few failures for the
1137 current release of IronPython for instance, which gets some traceback
1142 _u("pa\u026a\u03b8\u0259n"), # Unicode encodings only
1143 _u("\u5357\u7121"), # In ISO 2022 encodings
1144 _u("\xa7\xa7\xa7"), # In ISO 8859 encodings
1146 # Everything but Jython shows syntax errors on the current character
1147 _error_on_character = os.name != "java"
1149 def _run(self, stream, test):
1150 """Run the test, the same as in testtools.run but not to stdout"""
1151 result = TextTestResult(stream)
1152 result.startTestRun()
1154 return test.run(result)
1156 result.stopTestRun()
1158 def _write_module(self, name, encoding, contents):
1159 """Create Python module on disk with contents in given encoding"""
1161 # Need to pre-check that the coding is valid or codecs.open drops
1162 # the file without closing it which breaks non-refcounted pythons
1163 codecs.lookup(encoding)
1165 self.skip("Encoding unsupported by implementation: %r" % encoding)
1166 f = codecs.open(os.path.join(self.dir, name + ".py"), "w", encoding)
1172 def _test_external_case(self, testline, coding="ascii", modulelevel="",
1174 """Create and run a test case in a seperate module"""
1175 self._setup_external_case(testline, coding, modulelevel, suffix)
1176 return self._run_external_case()
1178 def _setup_external_case(self, testline, coding="ascii", modulelevel="",
1180 """Create a test case in a seperate module"""
1181 _, prefix, self.modname = self.id().rsplit(".", 2)
1182 self.dir = tempfile.mkdtemp(prefix=prefix, suffix=suffix)
1183 self.addCleanup(shutil.rmtree, self.dir)
1184 self._write_module(self.modname, coding,
1185 # Older Python 2 versions don't see a coding declaration in a
1186 # docstring so it has to be in a comment, but then we can't
1187 # workaround bug: <http://ironpython.codeplex.com/workitem/26940>
1189 "import testtools\n"
1191 "class Test(testtools.TestCase):\n"
1192 " def runTest(self):\n"
1193 " %s\n" % (coding, modulelevel, testline))
1195 def _run_external_case(self):
1196 """Run the prepared test case in a seperate module"""
1197 sys.path.insert(0, self.dir)
1198 self.addCleanup(sys.path.remove, self.dir)
1199 module = __import__(self.modname)
1200 self.addCleanup(sys.modules.pop, self.modname)
1202 self._run(stream, module.Test())
1203 return stream.getvalue()
1205 def _silence_deprecation_warnings(self):
1206 """Shut up DeprecationWarning for this test only"""
1207 warnings.simplefilter("ignore", DeprecationWarning)
1208 self.addCleanup(warnings.filters.remove, warnings.filters[0])
1210 def _get_sample_text(self, encoding="unicode_internal"):
1211 if encoding is None and str_is_unicode:
1212 encoding = "unicode_internal"
1213 for u in self._sample_texts:
1215 b = u.encode(encoding)
1216 if u == b.decode(encoding):
1220 except (LookupError, UnicodeError):
1222 self.skip("Could not find a sample text for encoding: %r" % encoding)
1224 def _as_output(self, text):
1227 def test_non_ascii_failure_string(self):
1228 """Assertion contents can be non-ascii and should get decoded"""
1229 text, raw = self._get_sample_text(_get_exception_encoding())
1230 textoutput = self._test_external_case("self.fail(%s)" % _r(raw))
1231 self.assertIn(self._as_output(text), textoutput)
1233 def test_non_ascii_failure_string_via_exec(self):
1234 """Assertion via exec can be non-ascii and still gets decoded"""
1235 text, raw = self._get_sample_text(_get_exception_encoding())
1236 textoutput = self._test_external_case(
1237 testline='exec ("self.fail(%s)")' % _r(raw))
1238 self.assertIn(self._as_output(text), textoutput)
1240 def test_control_characters_in_failure_string(self):
1241 """Control characters in assertions should be escaped"""
1242 textoutput = self._test_external_case("self.fail('\\a\\a\\a')")
1243 self.expectFailure("Defense against the beeping horror unimplemented",
1244 self.assertNotIn, self._as_output("\a\a\a"), textoutput)
1245 self.assertIn(self._as_output(_u("\uFFFD\uFFFD\uFFFD")), textoutput)
1247 def test_os_error(self):
1248 """Locale error messages from the OS shouldn't break anything"""
1249 textoutput = self._test_external_case(
1250 modulelevel="import os",
1251 testline="os.mkdir('/')")
1252 if os.name != "nt" or sys.version_info < (2, 5):
1253 self.assertIn(self._as_output("OSError: "), textoutput)
1255 self.assertIn(self._as_output("WindowsError: "), textoutput)
1257 def test_assertion_text_shift_jis(self):
1258 """A terminal raw backslash in an encoded string is weird but fine"""
1259 example_text = _u("\u5341")
1260 textoutput = self._test_external_case(
1262 testline="self.fail('%s')" % example_text)
1264 output_text = example_text
1266 output_text = example_text.encode("shift_jis").decode(
1267 _get_exception_encoding(), "replace")
1268 self.assertIn(self._as_output("AssertionError: %s" % output_text),
1271 def test_file_comment_iso2022_jp(self):
1272 """Control character escapes must be preserved if valid encoding"""
1273 example_text, _ = self._get_sample_text("iso2022_jp")
1274 textoutput = self._test_external_case(
1275 coding="iso2022_jp",
1276 testline="self.fail('Simple') # %s" % example_text)
1277 self.assertIn(self._as_output(example_text), textoutput)
1279 def test_unicode_exception(self):
1280 """Exceptions that can be formated losslessly as unicode should be"""
1281 example_text, _ = self._get_sample_text()
1283 "class FancyError(Exception):\n"
1284 # A __unicode__ method does nothing on py3k but the default works
1285 " def __unicode__(self):\n"
1286 " return self.args[0]\n")
1287 textoutput = self._test_external_case(
1288 modulelevel=exception_class,
1289 testline="raise FancyError(%s)" % _r(example_text))
1290 self.assertIn(self._as_output(example_text), textoutput)
1292 def test_unprintable_exception(self):
1293 """A totally useless exception instance still prints something"""
1295 "class UnprintableError(Exception):\n"
1296 " def __str__(self):\n"
1297 " raise RuntimeError\n"
1298 " def __unicode__(self):\n"
1299 " raise RuntimeError\n"
1300 " def __repr__(self):\n"
1301 " raise RuntimeError\n")
1302 textoutput = self._test_external_case(
1303 modulelevel=exception_class,
1304 testline="raise UnprintableError")
1305 self.assertIn(self._as_output(
1306 "UnprintableError: <unprintable UnprintableError object>\n"),
1309 def test_string_exception(self):
1310 """Raise a string rather than an exception instance if supported"""
1311 if sys.version_info > (2, 6):
1312 self.skip("No string exceptions in Python 2.6 or later")
1313 elif sys.version_info > (2, 5):
1314 self._silence_deprecation_warnings()
1315 textoutput = self._test_external_case(testline="raise 'plain str'")
1316 self.assertIn(self._as_output("\nplain str\n"), textoutput)
1318 def test_non_ascii_dirname(self):
1319 """Script paths in the traceback can be non-ascii"""
1320 text, raw = self._get_sample_text(sys.getfilesystemencoding())
1321 textoutput = self._test_external_case(
1322 # Avoid bug in Python 3 by giving a unicode source encoding rather
1323 # than just ascii which raises a SyntaxError with no other details
1325 testline="self.fail('Simple')",
1327 self.assertIn(self._as_output(text), textoutput)
1329 def test_syntax_error(self):
1330 """Syntax errors should still have fancy special-case formatting"""
1331 textoutput = self._test_external_case("exec ('f(a, b c)')")
1332 self.assertIn(self._as_output(
1333 ' File "<string>", line 1\n'
1335 + ' ' * self._error_on_character +
1340 def test_syntax_error_malformed(self):
1341 """Syntax errors with bogus parameters should break anything"""
1342 textoutput = self._test_external_case("raise SyntaxError(3, 2, 1)")
1343 self.assertIn(self._as_output("\nSyntaxError: "), textoutput)
1345 def test_syntax_error_import_binary(self):
1346 """Importing a binary file shouldn't break SyntaxError formatting"""
1347 if sys.version_info < (2, 5):
1348 # Python 2.4 assumes the file is latin-1 and tells you off
1349 self._silence_deprecation_warnings()
1350 self._setup_external_case("import bad")
1351 f = open(os.path.join(self.dir, "bad.py"), "wb")
1353 f.write(_b("x\x9c\xcb*\xcd\xcb\x06\x00\x04R\x01\xb9"))
1356 textoutput = self._run_external_case()
1357 self.assertIn(self._as_output("\nSyntaxError: "), textoutput)
1359 def test_syntax_error_line_iso_8859_1(self):
1360 """Syntax error on a latin-1 line shows the line decoded"""
1361 text, raw = self._get_sample_text("iso-8859-1")
1362 textoutput = self._setup_external_case("import bad")
1363 self._write_module("bad", "iso-8859-1",
1364 "# coding: iso-8859-1\n! = 0 # %s\n" % text)
1365 textoutput = self._run_external_case()
1366 self.assertIn(self._as_output(_u(
1367 #'bad.py", line 2\n'
1371 (text,)), textoutput)
1373 def test_syntax_error_line_iso_8859_5(self):
1374 """Syntax error on a iso-8859-5 line shows the line decoded"""
1375 text, raw = self._get_sample_text("iso-8859-5")
1376 textoutput = self._setup_external_case("import bad")
1377 self._write_module("bad", "iso-8859-5",
1378 "# coding: iso-8859-5\n%% = 0 # %s\n" % text)
1379 textoutput = self._run_external_case()
1380 self.assertIn(self._as_output(_u(
1381 #'bad.py", line 2\n'
1383 + ' ' * self._error_on_character +
1386 (text,)), textoutput)
1388 def test_syntax_error_line_euc_jp(self):
1389 """Syntax error on a euc_jp line shows the line decoded"""
1390 text, raw = self._get_sample_text("euc_jp")
1391 textoutput = self._setup_external_case("import bad")
1392 self._write_module("bad", "euc_jp",
1393 "# coding: euc_jp\n$ = 0 # %s\n" % text)
1394 textoutput = self._run_external_case()
1395 self.assertIn(self._as_output(_u(
1396 #'bad.py", line 2\n'
1398 + ' ' * self._error_on_character +
1401 (text,)), textoutput)
1403 def test_syntax_error_line_utf_8(self):
1404 """Syntax error on a utf-8 line shows the line decoded"""
1405 text, raw = self._get_sample_text("utf-8")
1406 textoutput = self._setup_external_case("import bad")
1407 self._write_module("bad", "utf-8", _u("\ufeff^ = 0 # %s\n") % text)
1408 textoutput = self._run_external_case()
1409 self.assertIn(self._as_output(_u(
1412 + ' ' * self._error_on_character +
1418 class TestNonAsciiResultsWithUnittest(TestNonAsciiResults):
1419 """Test that running under unittest produces clean ascii strings"""
1421 def _run(self, stream, test):
1422 from unittest import TextTestRunner as _Runner
1423 return _Runner(stream).run(test)
1425 def _as_output(self, text):
1428 return text.encode("utf-8")
1431 class TestDetailsToStr(TestCase):
1433 def test_no_details(self):
1434 string = _details_to_str({})
1435 self.assertThat(string, Equals(''))
1437 def test_binary_content(self):
1438 content = content_from_stream(
1439 StringIO('foo'), content_type=ContentType('image', 'jpeg'))
1440 string = _details_to_str({'attachment': content})
1444 attachment (image/jpeg)
1447 def test_single_line_content(self):
1448 content = text_content('foo')
1449 string = _details_to_str({'attachment': content})
1450 self.assertThat(string, Equals('attachment: {{{foo}}}\n'))
1452 def test_multi_line_text_content(self):
1453 content = text_content('foo\nbar\nbaz')
1454 string = _details_to_str({'attachment': content})
1455 self.assertThat(string, Equals('attachment: {{{\nfoo\nbar\nbaz\n}}}\n'))
1457 def test_special_text_content(self):
1458 content = text_content('foo')
1459 string = _details_to_str({'attachment': content}, special='attachment')
1460 self.assertThat(string, Equals('foo\n'))
1462 def test_multiple_text_content(self):
1463 string = _details_to_str(
1464 {'attachment': text_content('foo\nfoo'),
1465 'attachment-1': text_content('bar\nbar')})
1467 string, Equals('attachment: {{{\n'
1472 'attachment-1: {{{\n'
1477 def test_empty_attachment(self):
1478 string = _details_to_str({'attachment': text_content('')})
1485 def test_lots_of_different_attachments(self):
1486 jpg = lambda x: content_from_stream(
1487 StringIO(x), ContentType('image', 'jpeg'))
1489 'attachment': text_content('foo'),
1490 'attachment-1': text_content('traceback'),
1491 'attachment-2': jpg('pic1'),
1492 'attachment-3': text_content('bar'),
1493 'attachment-4': text_content(''),
1494 'attachment-5': jpg('pic2'),
1496 string = _details_to_str(attachments, special='attachment-1')
1500 attachment-2 (image/jpeg)
1501 attachment-5 (image/jpeg)
1505 attachment: {{{foo}}}
1506 attachment-3: {{{bar}}}
1513 from unittest import TestLoader
1514 return TestLoader().loadTestsFromName(__name__)