diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index f07da75..1c8f6b3 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -1808,19 +1808,19 @@ def test_DocTestSuite(): >>> import test.sample_doctest >>> suite = doctest.DocTestSuite(test.sample_doctest) >>> suite.run(unittest.TestResult()) - + We can also supply the module by name: >>> suite = doctest.DocTestSuite('test.sample_doctest') >>> suite.run(unittest.TestResult()) - + We can use the current module: >>> suite = test.sample_doctest.test_suite() >>> suite.run(unittest.TestResult()) - + We can supply global variables. If we pass globs, they will be used instead of the module globals. Here we'll pass an empty @@ -1828,7 +1828,7 @@ def test_DocTestSuite(): >>> suite = doctest.DocTestSuite('test.sample_doctest', globs={}) >>> suite.run(unittest.TestResult()) - + Alternatively, we can provide extra globals. Here we'll make an error go away by providing an extra global variable: @@ -1836,7 +1836,7 @@ def test_DocTestSuite(): >>> suite = doctest.DocTestSuite('test.sample_doctest', ... extraglobs={'y': 1}) >>> suite.run(unittest.TestResult()) - + You can pass option flags. Here we'll cause an extra error by disabling the blank-line feature: @@ -1844,7 +1844,7 @@ def test_DocTestSuite(): >>> suite = doctest.DocTestSuite('test.sample_doctest', ... optionflags=doctest.DONT_ACCEPT_BLANKLINE) >>> suite.run(unittest.TestResult()) - + You can supply setUp and tearDown functions: @@ -1861,7 +1861,7 @@ def test_DocTestSuite(): >>> suite = doctest.DocTestSuite('test.sample_doctest', ... setUp=setUp, tearDown=tearDown) >>> suite.run(unittest.TestResult()) - + But the tearDown restores sanity: @@ -1879,7 +1879,7 @@ def test_DocTestSuite(): >>> suite = doctest.DocTestSuite('test.sample_doctest', setUp=setUp) >>> suite.run(unittest.TestResult()) - + Here, we didn't need to use a tearDown function because we modified the test globals, which are a copy of the @@ -1898,7 +1898,7 @@ def test_DocFileSuite(): ... 'test_doctest2.txt', ... 'test_doctest4.txt') >>> suite.run(unittest.TestResult()) - + The test files are looked for in the directory containing the calling module. A package keyword argument can be provided to @@ -1910,7 +1910,7 @@ def test_DocFileSuite(): ... 'test_doctest4.txt', ... package='test') >>> suite.run(unittest.TestResult()) - + Support for using a package's __loader__.get_data() is also provided. @@ -1929,14 +1929,14 @@ def test_DocFileSuite(): ... finally: ... if added_loader: ... del test.__loader__ - + '/' should be used as a path separator. It will be converted to a native separator at run time: >>> suite = doctest.DocFileSuite('../test/test_doctest.txt') >>> suite.run(unittest.TestResult()) - + If DocFileSuite is used from an interactive session, then files are resolved relative to the directory of sys.argv[0]: @@ -1961,7 +1961,7 @@ def test_DocFileSuite(): >>> suite = doctest.DocFileSuite(test_file, module_relative=False) >>> suite.run(unittest.TestResult()) - + It is an error to specify `package` when `module_relative=False`: @@ -1977,7 +1977,7 @@ def test_DocFileSuite(): ... 'test_doctest4.txt', ... globs={'favorite_color': 'blue'}) >>> suite.run(unittest.TestResult()) - + In this case, we supplied a missing favorite color. You can provide doctest options: @@ -1988,7 +1988,7 @@ def test_DocFileSuite(): ... optionflags=doctest.DONT_ACCEPT_BLANKLINE, ... globs={'favorite_color': 'blue'}) >>> suite.run(unittest.TestResult()) - + And, you can provide setUp and tearDown functions: @@ -2007,7 +2007,7 @@ def test_DocFileSuite(): ... 'test_doctest4.txt', ... setUp=setUp, tearDown=tearDown) >>> suite.run(unittest.TestResult()) - + But the tearDown restores sanity: @@ -2026,7 +2026,7 @@ def test_DocFileSuite(): >>> suite = doctest.DocFileSuite('test_doctest.txt', setUp=setUp) >>> suite.run(unittest.TestResult()) - + Here, we didn't need to use a tearDown function because we modified the test globals. The test globals are @@ -2038,7 +2038,7 @@ def test_DocFileSuite(): >>> suite = doctest.DocFileSuite('test_doctest3.txt') >>> suite.run(unittest.TestResult()) - + If the tests contain non-ASCII characters, we have to specify which encoding the file is encoded with. We do so by using the `encoding` @@ -2049,7 +2049,7 @@ def test_DocFileSuite(): ... 'test_doctest4.txt', ... encoding='utf-8') >>> suite.run(unittest.TestResult()) - + """ diff --git a/Lib/test/test_unittest.py b/Lib/test/test_unittest.py index c9c17d3..c03962e 100644 --- a/Lib/test/test_unittest.py +++ b/Lib/test/test_unittest.py @@ -10,6 +10,8 @@ from test import support import unittest from unittest import TestCase import types +import sys +from io import StringIO ### Support code ################################################################ @@ -71,6 +73,33 @@ class TestHashing(object): except Exception as e: self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) +class SkipInTest(unittest.TestCase): + """Tests skip in test methods""" + def test_doesnt_skip(self): + """Doesn't skip""" + self.assert_(True) + + def test_fails(self): + """Fails""" + self.fail() + + def test_skips(self): + """Test skips""" + self.skip() + + def test_skips_message(self): + """Test skips with a message""" + self.skip("A message") + +class SkipInSetUp(unittest.TestCase): + """Tests skip in setUp()""" + def setUp(self): + super(SkipInSetUp, self).setUp() + self.skip() + + def test_dummy(self): + """Never executed""" + ################################################################ ### /Support code @@ -1924,6 +1953,25 @@ class Test_TestResult(TestCase): self.failUnless(test_case is test) self.failUnless(isinstance(formatted_exc, str)) + def test_skipped_attribute(self): + """List of skipped tests""" + result = unittest.TestResult() + self.assertEqual([], result.skipped) + self.assertEqual("", repr(result)) + + def test_add_skipped(self): + """TestResult.addSkipped()""" + result = unittest.TestResult() + test = SkipInTest("test_doesnt_skip") + try: + raise unittest.SkipException("Skipped") + except: + result.addSkipped(test, sys.exc_info()) + + self.assert_(test is result.skipped[0][0]) + self.assertEqual("Skipped", result.skipped[0][1]) + self.assertEqual("", repr(result)) + ### Support code for Test_TestCase ################################################################ @@ -2264,6 +2312,41 @@ class Test_TestCase(TestCase, TestEquality, TestHashing): expected = ['startTest', 'test', 'stopTest'] self.assertEqual(events, expected) + + def test_no_skip(self): + """Test case that doesn't skip""" + test = SkipInTest("test_doesnt_skip") + result = unittest.TestResult() + + test(result) + + self.assertEqual(1, result.testsRun) + self.assertEqual([], result.skipped) + + def test_skip_in_test(self): + """Test case that skips in test method""" + test = SkipInTest("test_skips") + result = unittest.TestResult() + + test(result) + + self.assertEqual(1, result.testsRun) + self.assertEqual([], result.failures) + self.assertEqual([], result.errors) + self.assert_(test is result.skipped[0][0]) + + def test_skip_in_set_up(self): + """Test case that skips in setUp()""" + test = SkipInSetUp("test_dummy") + result = unittest.TestResult() + + test(result) + + self.assertEqual(1, result.testsRun) + self.assertEqual([], result.failures) + self.assertEqual([], result.errors) + self.assert_(test is result.skipped[0][0]) + class Test_Assertions(TestCase): def test_AlmostEqual(self): self.failUnlessAlmostEqual(1.00000001, 1.0) @@ -2284,6 +2367,120 @@ class Test_Assertions(TestCase): self.assertRaises(AssertionError, self.failIfAlmostEqual, 0, .1+.1j, places=0) +class Test_SkipMethods(unittest.TestCase): + """Tests for skip() methods""" + def setUp(self): + super().setUp() + self.test = SkipInTest("test_doesnt_skip") + + def catch_skip_exception(self, callable, *args, **kwargs): + try: + callable(*args, **kwargs) + except unittest.SkipException as e: + return e + return None + + def test_skip(self): + e = self.catch_skip_exception(self.test.skip) + self.assertNotEqual(None, e) + + def test_skip_message(self): + e = self.catch_skip_exception(self.test.skip, "A message") + self.assertNotEqual(None, e) + self.assertEqual("A message", str(e)) + + def test_skip_if_false(self): + e = self.catch_skip_exception(self.test.skipIf, False) + self.assertEqual(None, e) + + def test_skip_if_true(self): + e = self.catch_skip_exception(self.test.skipIf, True) + self.assertNotEqual(None, e) + + def test_skip_if_true_message(self): + e = self.catch_skip_exception(self.test.skipIf, True, "Another message") + self.assertNotEqual(None, e) + self.assertEqual("Another message", str(e)) + +class Test_TextTestResult(TestCase): + """Tests for _TextTestResult""" + + def setUp(self): + super().setUp() + self.out = unittest._WritelnDecorator(StringIO()) + + def testNoSkippingQuiet(self): + result = unittest._TextTestResult(self.out, True, 1) + test = SkipInTest("test_doesnt_skip") + test(result) + + self.assertEqual(".", self.out.getvalue()) + + def testNoSkippingVerbose(self): + result = unittest._TextTestResult(self.out, True, 2) + test = SkipInTest("test_doesnt_skip") + test(result) + + self.assertEqual("Doesn't skip ... ok\n", self.out.getvalue()) + + def testSkippingQuiet(self): + result = unittest._TextTestResult(self.out, True, 1) + test = SkipInTest("test_skips") + test(result) + + self.assertEqual("S", self.out.getvalue()) + + def testSkippingVerbose(self): + result = unittest._TextTestResult(self.out, True, 2) + test = SkipInTest("test_skips") + test(result) + + self.assertEqual("Test skips ... SKIPPED\n", self.out.getvalue()) + + def testSkippingMessageVerbose(self): + result = unittest._TextTestResult(self.out, True, 2) + test = SkipInTest("test_skips_message") + test(result) + + self.assertEqual("Test skips with a message ... SKIPPED (A message)\n", self.out.getvalue()) + + +class Test_TextTestRunner(TestCase): + """Tests for TextTestRunner""" + + def setUp(self): + super().setUp() + self.out = unittest._WritelnDecorator(StringIO()) + + def testOkNoSkips(self): + runner = unittest.TextTestRunner(self.out, True) + test = SkipInTest("test_doesnt_skip") + runner.run(test) + + self.assert_(self.out.getvalue().endswith("OK\n")) + + def testOkSkip(self): + runner = unittest.TextTestRunner(self.out, True) + test = SkipInTest("test_skips") + runner.run(test) + + self.assert_(self.out.getvalue().endswith("OK (skipped=1)\n")) + + def testFailedNoSkip(self): + runner = unittest.TextTestRunner(self.out, True) + test = SkipInTest("test_fails") + runner.run(test) + + self.assert_(self.out.getvalue().endswith("FAILED (failures=1)\n")) + + def testFailedSkip(self): + runner = unittest.TextTestRunner(self.out, True) + test = unittest.TestSuite((SkipInTest("test_fails"), SkipInTest("test_skips"))) + runner.run(test) + + self.assert_(self.out.getvalue().endswith("FAILED (failures=1, skipped=1)\n")) + + ###################################################################### ## Main ###################################################################### @@ -2291,7 +2488,8 @@ class Test_Assertions(TestCase): def test_main(): support.run_unittest(Test_TestCase, Test_TestLoader, Test_TestSuite, Test_TestResult, Test_FunctionTestCase, - Test_Assertions) + Test_Assertions, Test_SkipMethods, Test_TextTestResult, + Test_TextTestRunner) if __name__ == "__main__": test_main() diff --git a/Lib/unittest.py b/Lib/unittest.py index 5beeb05..20dc4b2 100644 --- a/Lib/unittest.py +++ b/Lib/unittest.py @@ -73,6 +73,8 @@ def _strclass(cls): __unittest = 1 +class SkipException(Exception): pass + class TestResult: """Holder for test result information. @@ -87,6 +89,7 @@ class TestResult: def __init__(self): self.failures = [] self.errors = [] + self.skipped = [] self.testsRun = 0 self.shouldStop = False @@ -109,6 +112,11 @@ class TestResult: returned by sys.exc_info().""" self.failures.append((test, self._exc_info_to_string(err, test))) + def addSkipped(self, test, err): + """Called when a test is skipped. 'err' is a tuble of values as + returned by sys.exc_info().""" + self.skipped.append((test, str(err[1]))) + def addSuccess(self, test): "Called when a test has completed successfully" pass @@ -145,9 +153,9 @@ class TestResult: return length def __repr__(self): - return "<%s run=%i errors=%i failures=%i>" % \ + return "<%s run=%i errors=%i failures=%i skipped=%i>" % \ (_strclass(self.__class__), self.testsRun, len(self.errors), - len(self.failures)) + len(self.failures), len(self.skipped)) class TestCase: """A class whose instances are single test cases. @@ -243,6 +251,9 @@ class TestCase: try: try: self.setUp() + except SkipException: + result.addSkipped(self, self._exc_info()) + return except KeyboardInterrupt: raise except: @@ -255,6 +266,8 @@ class TestCase: ok = True except self.failureException: result.addFailure(self, self._exc_info()) + except SkipException: + result.addSkipped(self, self._exc_info()) except KeyboardInterrupt: raise except: @@ -371,6 +384,16 @@ class TestCase: assertFalse = failIf + def skip(self, msg=None): + """Skip the test, with the given message.""" + if msg is not None: + raise SkipException(msg) + else: + raise SkipException() + + def skipIf(self, expr, msg=None): + """Skip the test if the expression is true.""" + if expr: self.skip(msg) class TestSuite: @@ -705,6 +728,16 @@ class _TextTestResult(TestResult): self.stream.write('F') self.stream.flush() + def addSkipped(self, test, err): + TestResult.addSkipped(self, test, err) + if self.showAll: + msg = str(err[1]) + if msg: + msg = " (" + msg + ")" + self.stream.writeln("SKIPPED" + msg) + elif self.dots: + self.stream.write('S') + def printErrors(self): if self.dots or self.showAll: self.stream.writeln() @@ -748,15 +781,21 @@ class TextTestRunner: self.stream.writeln() if not result.wasSuccessful(): self.stream.write("FAILED (") - failed, errored = len(result.failures), len(result.errors) + failed, errored, skipped = map( + len, (result.failures, result.errors, result.skipped)) if failed: self.stream.write("failures=%d" % failed) if errored: if failed: self.stream.write(", ") self.stream.write("errors=%d" % errored) + if skipped: + self.stream.write(", skipped=%d" % skipped) self.stream.writeln(")") else: - self.stream.writeln("OK") + if result.skipped: + self.stream.writeln("OK (skipped=%d)" % len(result.skipped)) + else: + self.stream.writeln("OK") return result