diff options
Diffstat (limited to 'Lib/unittest/test')
| -rw-r--r-- | Lib/unittest/test/support.py | 4 | ||||
| -rw-r--r-- | Lib/unittest/test/test_assertions.py | 15 | ||||
| -rw-r--r-- | Lib/unittest/test/test_break.py | 3 | ||||
| -rw-r--r-- | Lib/unittest/test/test_case.py | 191 | ||||
| -rw-r--r-- | Lib/unittest/test/test_discovery.py | 360 | ||||
| -rw-r--r-- | Lib/unittest/test/test_loader.py | 425 | ||||
| -rw-r--r-- | Lib/unittest/test/test_program.py | 26 | ||||
| -rw-r--r-- | Lib/unittest/test/test_result.py | 43 | ||||
| -rw-r--r-- | Lib/unittest/test/test_runner.py | 19 | ||||
| -rw-r--r-- | Lib/unittest/test/test_setups.py | 7 | ||||
| -rw-r--r-- | Lib/unittest/test/testmock/testmagicmethods.py | 11 | ||||
| -rw-r--r-- | Lib/unittest/test/testmock/testmock.py | 74 | ||||
| -rw-r--r-- | Lib/unittest/test/testmock/testpatch.py | 50 | 
13 files changed, 1073 insertions, 155 deletions
| diff --git a/Lib/unittest/test/support.py b/Lib/unittest/test/support.py index 02e8f3a00b..529265304f 100644 --- a/Lib/unittest/test/support.py +++ b/Lib/unittest/test/support.py @@ -25,8 +25,6 @@ class TestHashing(object):              try:                  if not hash(obj_1) == hash(obj_2):                      self.fail("%r and %r do not hash equal" % (obj_1, obj_2)) -            except KeyboardInterrupt: -                raise              except Exception as e:                  self.fail("Problem hashing %r and %r: %s" % (obj_1, obj_2, e)) @@ -35,8 +33,6 @@ class TestHashing(object):                  if hash(obj_1) == hash(obj_2):                      self.fail("%s and %s hash equal, but shouldn't" %                                (obj_1, obj_2)) -            except KeyboardInterrupt: -                raise              except Exception as e:                  self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py index c349a95794..e6e2bc2ca7 100644 --- a/Lib/unittest/test/test_assertions.py +++ b/Lib/unittest/test/test_assertions.py @@ -133,7 +133,6 @@ class Test_Assertions(unittest.TestCase):          try:              self.assertNotRegex('Ala ma kota', r'k.t', 'Message')          except self.failureException as e: -            self.assertIn("'kot'", e.args[0])              self.assertIn('Message', e.args[0])          else:              self.fail('assertNotRegex should have failed.') @@ -329,6 +328,20 @@ class TestLongMessage(unittest.TestCase):                               "^unexpectedly identical: None$",                               "^unexpectedly identical: None : oops$"]) +    def testAssertRegex(self): +        self.assertMessages('assertRegex', ('foo', 'bar'), +                            ["^Regex didn't match:", +                             "^oops$", +                             "^Regex didn't match:", +                             "^Regex didn't match: (.*) : oops$"]) + +    def testAssertNotRegex(self): +        self.assertMessages('assertNotRegex', ('foo', 'foo'), +                            ["^Regex matched:", +                             "^oops$", +                             "^Regex matched:", +                             "^Regex matched: (.*) : oops$"]) +      def assertMessagesCM(self, methodName, args, func, errors):          """ diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py index 0bf1a229b8..2c7501952c 100644 --- a/Lib/unittest/test/test_break.py +++ b/Lib/unittest/test/test_break.py @@ -211,6 +211,7 @@ class TestBreak(unittest.TestCase):                  self.verbosity = verbosity                  self.failfast = failfast                  self.catchbreak = catchbreak +                self.tb_locals = False                  self.testRunner = FakeRunner                  self.test = test                  self.result = None @@ -221,6 +222,7 @@ class TestBreak(unittest.TestCase):          self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,                                                       'verbosity': verbosity,                                                       'failfast': failfast, +                                                     'tb_locals': False,                                                       'warnings': None})])          self.assertEqual(FakeRunner.runArgs, [test])          self.assertEqual(p.result, result) @@ -235,6 +237,7 @@ class TestBreak(unittest.TestCase):          self.assertEqual(FakeRunner.initArgs, [((), {'buffer': None,                                                       'verbosity': verbosity,                                                       'failfast': failfast, +                                                     'tb_locals': False,                                                       'warnings': None})])          self.assertEqual(FakeRunner.runArgs, [test])          self.assertEqual(p.result, result) diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index 321d67a82f..8f752b8ae0 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -339,7 +339,7 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):          self._check_call_order__subtests(result, events, expected)      def test_run_call_order__subtests_legacy(self): -        # With a legacy result object (without a addSubTest method), +        # With a legacy result object (without an addSubTest method),          # text execution stops after the first subtest failure.          events = []          result = LegacyLoggingResult(events) @@ -1103,12 +1103,9 @@ test case          except self.failureException as e:              # need to remove the first line of the error message              error = str(e).split('\n', 1)[1] +            self.assertEqual(sample_text_error, error) -            # no fair testing ourself with ourself, and assertEqual is used for strings -            # so can't use assertEqual either. Just use assertTrue. -            self.assertTrue(sample_text_error == error) - -    def testAsertEqualSingleLine(self): +    def testAssertEqualSingleLine(self):          sample_text = "laden swallows fly slowly"          revised_sample_text = "unladen swallows fly quickly"          sample_text_error = """\ @@ -1120,8 +1117,85 @@ test case          try:              self.assertEqual(sample_text, revised_sample_text)          except self.failureException as e: +            # need to remove the first line of the error message              error = str(e).split('\n', 1)[1] -            self.assertTrue(sample_text_error == error) +            self.assertEqual(sample_text_error, error) + +    def testEqualityBytesWarning(self): +        if sys.flags.bytes_warning: +            def bytes_warning(): +                return self.assertWarnsRegex(BytesWarning, +                            'Comparison between bytes and string') +        else: +            def bytes_warning(): +                return contextlib.ExitStack() + +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertEqual('a', b'a') +        with bytes_warning(): +            self.assertNotEqual('a', b'a') + +        a = [0, 'a'] +        b = [0, b'a'] +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertListEqual(a, b) +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertTupleEqual(tuple(a), tuple(b)) +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertSequenceEqual(a, tuple(b)) +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertSequenceEqual(tuple(a), b) +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertSequenceEqual('a', b'a') +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertSetEqual(set(a), set(b)) + +        with self.assertRaises(self.failureException): +            self.assertListEqual(a, tuple(b)) +        with self.assertRaises(self.failureException): +            self.assertTupleEqual(tuple(a), b) + +        a = [0, b'a'] +        b = [0] +        with self.assertRaises(self.failureException): +            self.assertListEqual(a, b) +        with self.assertRaises(self.failureException): +            self.assertTupleEqual(tuple(a), tuple(b)) +        with self.assertRaises(self.failureException): +            self.assertSequenceEqual(a, tuple(b)) +        with self.assertRaises(self.failureException): +            self.assertSequenceEqual(tuple(a), b) +        with self.assertRaises(self.failureException): +            self.assertSetEqual(set(a), set(b)) + +        a = [0] +        b = [0, b'a'] +        with self.assertRaises(self.failureException): +            self.assertListEqual(a, b) +        with self.assertRaises(self.failureException): +            self.assertTupleEqual(tuple(a), tuple(b)) +        with self.assertRaises(self.failureException): +            self.assertSequenceEqual(a, tuple(b)) +        with self.assertRaises(self.failureException): +            self.assertSequenceEqual(tuple(a), b) +        with self.assertRaises(self.failureException): +            self.assertSetEqual(set(a), set(b)) + +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertDictEqual({'a': 0}, {b'a': 0}) +        with self.assertRaises(self.failureException): +            self.assertDictEqual({}, {b'a': 0}) +        with self.assertRaises(self.failureException): +            self.assertDictEqual({b'a': 0}, {}) + +        with self.assertRaises(self.failureException): +            self.assertCountEqual([b'a', b'a'], [b'a', b'a', b'a']) +        with bytes_warning(): +            self.assertCountEqual(['a', b'a'], ['a', b'a']) +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertCountEqual(['a', 'a'], [b'a', b'a']) +        with bytes_warning(), self.assertRaises(self.failureException): +            self.assertCountEqual(['a', 'a', []], [b'a', b'a', []])      def testAssertIsNone(self):          self.assertIsNone(None) @@ -1147,6 +1221,9 @@ test case          # Failure when no exception is raised          with self.assertRaises(self.failureException):              self.assertRaises(ExceptionMock, lambda: 0) +        # Failure when the function is None +        with self.assertWarns(DeprecationWarning): +            self.assertRaises(ExceptionMock, None)          # Failure when another exception is raised          with self.assertRaises(ExceptionMock):              self.assertRaises(ValueError, Stub) @@ -1171,10 +1248,31 @@ test case          with self.assertRaises(self.failureException):              with self.assertRaises(ExceptionMock):                  pass +        # Custom message +        with self.assertRaisesRegex(self.failureException, 'foobar'): +            with self.assertRaises(ExceptionMock, msg='foobar'): +                pass +        # Invalid keyword argument +        with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ +             self.assertRaises(AssertionError): +            with self.assertRaises(ExceptionMock, foobar=42): +                pass          # Failure when another exception is raised          with self.assertRaises(ExceptionMock):              self.assertRaises(ValueError, Stub) +    def testAssertRaisesNoExceptionType(self): +        with self.assertRaises(TypeError): +            self.assertRaises() +        with self.assertRaises(TypeError): +            self.assertRaises(1) +        with self.assertRaises(TypeError): +            self.assertRaises(object) +        with self.assertRaises(TypeError): +            self.assertRaises((ValueError, 1)) +        with self.assertRaises(TypeError): +            self.assertRaises((ValueError, object)) +      def testAssertRaisesRegex(self):          class ExceptionMock(Exception):              pass @@ -1184,6 +1282,8 @@ test case          self.assertRaisesRegex(ExceptionMock, re.compile('expect$'), Stub)          self.assertRaisesRegex(ExceptionMock, 'expect$', Stub) +        with self.assertWarns(DeprecationWarning): +            self.assertRaisesRegex(ExceptionMock, 'expect$', None)      def testAssertNotRaisesRegex(self):          self.assertRaisesRegex( @@ -1194,6 +1294,15 @@ test case                  self.failureException, '^Exception not raised by <lambda>$',                  self.assertRaisesRegex, Exception, 'x',                  lambda: None) +        # Custom message +        with self.assertRaisesRegex(self.failureException, 'foobar'): +            with self.assertRaisesRegex(Exception, 'expect', msg='foobar'): +                pass +        # Invalid keyword argument +        with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ +             self.assertRaises(AssertionError): +            with self.assertRaisesRegex(Exception, 'expect', foobar=42): +                pass      def testAssertRaisesRegexInvalidRegex(self):          # Issue 20145. @@ -1237,6 +1346,20 @@ test case          self.assertIsInstance(e, ExceptionMock)          self.assertEqual(e.args[0], v) +    def testAssertRaisesRegexNoExceptionType(self): +        with self.assertRaises(TypeError): +            self.assertRaisesRegex() +        with self.assertRaises(TypeError): +            self.assertRaisesRegex(ValueError) +        with self.assertRaises(TypeError): +            self.assertRaisesRegex(1, 'expect') +        with self.assertRaises(TypeError): +            self.assertRaisesRegex(object, 'expect') +        with self.assertRaises(TypeError): +            self.assertRaisesRegex((ValueError, 1), 'expect') +        with self.assertRaises(TypeError): +            self.assertRaisesRegex((ValueError, object), 'expect') +      def testAssertWarnsCallable(self):          def _runtime_warn():              warnings.warn("foo", RuntimeWarning) @@ -1251,6 +1374,9 @@ test case          # Failure when no warning is triggered          with self.assertRaises(self.failureException):              self.assertWarns(RuntimeWarning, lambda: 0) +        # Failure when the function is None +        with self.assertWarns(DeprecationWarning): +            self.assertWarns(RuntimeWarning, None)          # Failure when another warning is triggered          with warnings.catch_warnings():              # Force default filter (in case tests are run with -We) @@ -1289,6 +1415,15 @@ test case          with self.assertRaises(self.failureException):              with self.assertWarns(RuntimeWarning):                  pass +        # Custom message +        with self.assertRaisesRegex(self.failureException, 'foobar'): +            with self.assertWarns(RuntimeWarning, msg='foobar'): +                pass +        # Invalid keyword argument +        with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ +             self.assertRaises(AssertionError): +            with self.assertWarns(RuntimeWarning, foobar=42): +                pass          # Failure when another warning is triggered          with warnings.catch_warnings():              # Force default filter (in case tests are run with -We) @@ -1303,6 +1438,20 @@ test case                  with self.assertWarns(DeprecationWarning):                      _runtime_warn() +    def testAssertWarnsNoExceptionType(self): +        with self.assertRaises(TypeError): +            self.assertWarns() +        with self.assertRaises(TypeError): +            self.assertWarns(1) +        with self.assertRaises(TypeError): +            self.assertWarns(object) +        with self.assertRaises(TypeError): +            self.assertWarns((UserWarning, 1)) +        with self.assertRaises(TypeError): +            self.assertWarns((UserWarning, object)) +        with self.assertRaises(TypeError): +            self.assertWarns((UserWarning, Exception)) +      def testAssertWarnsRegexCallable(self):          def _runtime_warn(msg):              warnings.warn(msg, RuntimeWarning) @@ -1312,6 +1461,9 @@ test case          with self.assertRaises(self.failureException):              self.assertWarnsRegex(RuntimeWarning, "o+",                                    lambda: 0) +        # Failure when the function is None +        with self.assertWarns(DeprecationWarning): +            self.assertWarnsRegex(RuntimeWarning, "o+", None)          # Failure when another warning is triggered          with warnings.catch_warnings():              # Force default filter (in case tests are run with -We) @@ -1348,6 +1500,15 @@ test case          with self.assertRaises(self.failureException):              with self.assertWarnsRegex(RuntimeWarning, "o+"):                  pass +        # Custom message +        with self.assertRaisesRegex(self.failureException, 'foobar'): +            with self.assertWarnsRegex(RuntimeWarning, 'o+', msg='foobar'): +                pass +        # Invalid keyword argument +        with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ +             self.assertRaises(AssertionError): +            with self.assertWarnsRegex(RuntimeWarning, 'o+', foobar=42): +                pass          # Failure when another warning is triggered          with warnings.catch_warnings():              # Force default filter (in case tests are run with -We) @@ -1369,6 +1530,22 @@ test case                  with self.assertWarnsRegex(RuntimeWarning, "o+"):                      _runtime_warn("barz") +    def testAssertWarnsRegexNoExceptionType(self): +        with self.assertRaises(TypeError): +            self.assertWarnsRegex() +        with self.assertRaises(TypeError): +            self.assertWarnsRegex(UserWarning) +        with self.assertRaises(TypeError): +            self.assertWarnsRegex(1, 'expect') +        with self.assertRaises(TypeError): +            self.assertWarnsRegex(object, 'expect') +        with self.assertRaises(TypeError): +            self.assertWarnsRegex((UserWarning, 1), 'expect') +        with self.assertRaises(TypeError): +            self.assertWarnsRegex((UserWarning, object), 'expect') +        with self.assertRaises(TypeError): +            self.assertWarnsRegex((UserWarning, Exception), 'expect') +      @contextlib.contextmanager      def assertNoStderr(self):          with captured_stderr() as buf: diff --git a/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py index f12e8983cd..bb196e6997 100644 --- a/Lib/unittest/test/test_discovery.py +++ b/Lib/unittest/test/test_discovery.py @@ -1,4 +1,5 @@ -import os +import os.path +from os.path import abspath  import re  import sys  import types @@ -69,7 +70,13 @@ class TestDiscovery(unittest.TestCase):          self.addCleanup(restore_isfile)          loader._get_module_from_name = lambda path: path + ' module' -        loader.loadTestsFromModule = lambda module: module + ' tests' +        orig_load_tests = loader.loadTestsFromModule +        def loadTestsFromModule(module, pattern=None): +            # This is where load_tests is called. +            base = orig_load_tests(module, pattern=pattern) +            return base + [module + ' tests'] +        loader.loadTestsFromModule = loadTestsFromModule +        loader.suiteClass = lambda thing: thing          top_level = os.path.abspath('/foo')          loader._top_level_dir = top_level @@ -77,12 +84,52 @@ class TestDiscovery(unittest.TestCase):          # The test suites found should be sorted alphabetically for reliable          # execution order. -        expected = [name + ' module tests' for name in -                    ('test1', 'test2')] -        expected.extend([('test_dir.%s' % name) + ' module tests' for name in +        expected = [[name + ' module tests'] for name in +                    ('test1', 'test2', 'test_dir')] +        expected.extend([[('test_dir.%s' % name) + ' module tests'] for name in                      ('test3', 'test4')])          self.assertEqual(suite, expected) +    def test_find_tests_socket(self): +        # A socket is neither a directory nor a regular file. +        # https://bugs.python.org/issue25320 +        loader = unittest.TestLoader() + +        original_listdir = os.listdir +        def restore_listdir(): +            os.listdir = original_listdir +        original_isfile = os.path.isfile +        def restore_isfile(): +            os.path.isfile = original_isfile +        original_isdir = os.path.isdir +        def restore_isdir(): +            os.path.isdir = original_isdir + +        path_lists = [['socket']] +        os.listdir = lambda path: path_lists.pop(0) +        self.addCleanup(restore_listdir) + +        os.path.isdir = lambda path: False +        self.addCleanup(restore_isdir) + +        os.path.isfile = lambda path: False +        self.addCleanup(restore_isfile) + +        loader._get_module_from_name = lambda path: path + ' module' +        orig_load_tests = loader.loadTestsFromModule +        def loadTestsFromModule(module, pattern=None): +            # This is where load_tests is called. +            base = orig_load_tests(module, pattern=pattern) +            return base + [module + ' tests'] +        loader.loadTestsFromModule = loadTestsFromModule +        loader.suiteClass = lambda thing: thing + +        top_level = os.path.abspath('/foo') +        loader._top_level_dir = top_level +        suite = list(loader._find_tests(top_level, 'test*.py')) + +        self.assertEqual(suite, []) +      def test_find_tests_with_package(self):          loader = unittest.TestLoader() @@ -117,34 +164,204 @@ class TestDiscovery(unittest.TestCase):                  if os.path.basename(path) == 'test_directory':                      def load_tests(loader, tests, pattern):                          self.load_tests_args.append((loader, tests, pattern)) -                        return 'load_tests' +                        return [self.path + ' load_tests']                      self.load_tests = load_tests              def __eq__(self, other):                  return self.path == other.path          loader._get_module_from_name = lambda name: Module(name) -        def loadTestsFromModule(module, use_load_tests): -            if use_load_tests: -                raise self.failureException('use_load_tests should be False for packages') -            return module.path + ' module tests' +        orig_load_tests = loader.loadTestsFromModule +        def loadTestsFromModule(module, pattern=None): +            # This is where load_tests is called. +            base = orig_load_tests(module, pattern=pattern) +            return base + [module.path + ' module tests']          loader.loadTestsFromModule = loadTestsFromModule +        loader.suiteClass = lambda thing: thing          loader._top_level_dir = '/foo'          # this time no '.py' on the pattern so that it can match          # a test package          suite = list(loader._find_tests('/foo', 'test*')) -        # We should have loaded tests from the test_directory package by calling load_tests -        # and directly from the test_directory2 package +        # We should have loaded tests from the a_directory and test_directory2 +        # directly and via load_tests for the test_directory package, which +        # still calls the baseline module loader. +        self.assertEqual(suite, +                         [['a_directory module tests'], +                          ['test_directory load_tests', +                           'test_directory module tests'], +                          ['test_directory2 module tests']]) + + +        # The test module paths should be sorted for reliable execution order +        self.assertEqual(Module.paths, +                         ['a_directory', 'test_directory', 'test_directory2']) + +        # load_tests should have been called once with loader, tests and pattern +        # (but there are no tests in our stub module itself, so thats [] at the +        # time of call. +        self.assertEqual(Module.load_tests_args, +                         [(loader, [], 'test*')]) + +    def test_find_tests_default_calls_package_load_tests(self): +        loader = unittest.TestLoader() + +        original_listdir = os.listdir +        def restore_listdir(): +            os.listdir = original_listdir +        original_isfile = os.path.isfile +        def restore_isfile(): +            os.path.isfile = original_isfile +        original_isdir = os.path.isdir +        def restore_isdir(): +            os.path.isdir = original_isdir + +        directories = ['a_directory', 'test_directory', 'test_directory2'] +        path_lists = [directories, [], [], []] +        os.listdir = lambda path: path_lists.pop(0) +        self.addCleanup(restore_listdir) + +        os.path.isdir = lambda path: True +        self.addCleanup(restore_isdir) + +        os.path.isfile = lambda path: os.path.basename(path) not in directories +        self.addCleanup(restore_isfile) + +        class Module(object): +            paths = [] +            load_tests_args = [] + +            def __init__(self, path): +                self.path = path +                self.paths.append(path) +                if os.path.basename(path) == 'test_directory': +                    def load_tests(loader, tests, pattern): +                        self.load_tests_args.append((loader, tests, pattern)) +                        return [self.path + ' load_tests'] +                    self.load_tests = load_tests + +            def __eq__(self, other): +                return self.path == other.path + +        loader._get_module_from_name = lambda name: Module(name) +        orig_load_tests = loader.loadTestsFromModule +        def loadTestsFromModule(module, pattern=None): +            # This is where load_tests is called. +            base = orig_load_tests(module, pattern=pattern) +            return base + [module.path + ' module tests'] +        loader.loadTestsFromModule = loadTestsFromModule +        loader.suiteClass = lambda thing: thing + +        loader._top_level_dir = '/foo' +        # this time no '.py' on the pattern so that it can match +        # a test package +        suite = list(loader._find_tests('/foo', 'test*.py')) + +        # We should have loaded tests from the a_directory and test_directory2 +        # directly and via load_tests for the test_directory package, which +        # still calls the baseline module loader.          self.assertEqual(suite, -                         ['load_tests', 'test_directory2' + ' module tests']) +                         [['a_directory module tests'], +                          ['test_directory load_tests', +                           'test_directory module tests'], +                          ['test_directory2 module tests']])          # The test module paths should be sorted for reliable execution order -        self.assertEqual(Module.paths, ['test_directory', 'test_directory2']) +        self.assertEqual(Module.paths, +                         ['a_directory', 'test_directory', 'test_directory2']) +          # load_tests should have been called once with loader, tests and pattern          self.assertEqual(Module.load_tests_args, -                         [(loader, 'test_directory' + ' module tests', 'test*')]) +                         [(loader, [], 'test*.py')]) + +    def test_find_tests_customise_via_package_pattern(self): +        # This test uses the example 'do-nothing' load_tests from +        # https://docs.python.org/3/library/unittest.html#load-tests-protocol +        # to make sure that that actually works. +        # Housekeeping +        original_listdir = os.listdir +        def restore_listdir(): +            os.listdir = original_listdir +        self.addCleanup(restore_listdir) +        original_isfile = os.path.isfile +        def restore_isfile(): +            os.path.isfile = original_isfile +        self.addCleanup(restore_isfile) +        original_isdir = os.path.isdir +        def restore_isdir(): +            os.path.isdir = original_isdir +        self.addCleanup(restore_isdir) +        self.addCleanup(sys.path.remove, abspath('/foo')) + +        # Test data: we expect the following: +        # a listdir to find our package, and isfile and isdir checks on it. +        # a module-from-name call to turn that into a module +        # followed by load_tests. +        # then our load_tests will call discover() which is messy +        # but that finally chains into find_tests again for the child dir - +        # which is why we don't have an infinite loop. +        # We expect to see: +        # the module load tests for both package and plain module called, +        # and the plain module result nested by the package module load_tests +        # indicating that it was processed and could have been mutated. +        vfs = {abspath('/foo'): ['my_package'], +               abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} +        def list_dir(path): +            return list(vfs[path]) +        os.listdir = list_dir +        os.path.isdir = lambda path: not path.endswith('.py') +        os.path.isfile = lambda path: path.endswith('.py') + +        class Module(object): +            paths = [] +            load_tests_args = [] + +            def __init__(self, path): +                self.path = path +                self.paths.append(path) +                if path.endswith('test_module'): +                    def load_tests(loader, tests, pattern): +                        self.load_tests_args.append((loader, tests, pattern)) +                        return [self.path + ' load_tests'] +                else: +                    def load_tests(loader, tests, pattern): +                        self.load_tests_args.append((loader, tests, pattern)) +                        # top level directory cached on loader instance +                        __file__ = '/foo/my_package/__init__.py' +                        this_dir = os.path.dirname(__file__) +                        pkg_tests = loader.discover( +                            start_dir=this_dir, pattern=pattern) +                        return [self.path + ' load_tests', tests +                            ] + pkg_tests +                self.load_tests = load_tests + +            def __eq__(self, other): +                return self.path == other.path + +        loader = unittest.TestLoader() +        loader._get_module_from_name = lambda name: Module(name) +        loader.suiteClass = lambda thing: thing + +        loader._top_level_dir = abspath('/foo') +        # this time no '.py' on the pattern so that it can match +        # a test package +        suite = list(loader._find_tests(abspath('/foo'), 'test*.py')) + +        # We should have loaded tests from both my_package and +        # my_pacakge.test_module, and also run the load_tests hook in both. +        # (normally this would be nested TestSuites.) +        self.assertEqual(suite, +                         [['my_package load_tests', [], +                          ['my_package.test_module load_tests']]]) +        # Parents before children. +        self.assertEqual(Module.paths, +                         ['my_package', 'my_package.test_module']) + +        # load_tests should have been called twice with loader, tests and pattern +        self.assertEqual(Module.load_tests_args, +                         [(loader, [], 'test*.py'), +                          (loader, [], 'test*.py')])      def test_discover(self):          loader = unittest.TestLoader() @@ -192,6 +409,51 @@ class TestDiscovery(unittest.TestCase):          self.assertEqual(_find_tests_args, [(start_dir, 'pattern')])          self.assertIn(top_level_dir, sys.path) +    def test_discover_start_dir_is_package_calls_package_load_tests(self): +        # This test verifies that the package load_tests in a package is indeed +        # invoked when the start_dir is a package (and not the top level). +        # http://bugs.python.org/issue22457 + +        # Test data: we expect the following: +        # an isfile to verify the package, then importing and scanning +        # as per _find_tests' normal behaviour. +        # We expect to see our load_tests hook called once. +        vfs = {abspath('/toplevel'): ['startdir'], +               abspath('/toplevel/startdir'): ['__init__.py']} +        def list_dir(path): +            return list(vfs[path]) +        self.addCleanup(setattr, os, 'listdir', os.listdir) +        os.listdir = list_dir +        self.addCleanup(setattr, os.path, 'isfile', os.path.isfile) +        os.path.isfile = lambda path: path.endswith('.py') +        self.addCleanup(setattr, os.path, 'isdir', os.path.isdir) +        os.path.isdir = lambda path: not path.endswith('.py') +        self.addCleanup(sys.path.remove, abspath('/toplevel')) + +        class Module(object): +            paths = [] +            load_tests_args = [] + +            def __init__(self, path): +                self.path = path + +            def load_tests(self, loader, tests, pattern): +                return ['load_tests called ' + self.path] + +            def __eq__(self, other): +                return self.path == other.path + +        loader = unittest.TestLoader() +        loader._get_module_from_name = lambda name: Module(name) +        loader.suiteClass = lambda thing: thing + +        suite = loader.discover('/toplevel/startdir', top_level_dir='/toplevel') + +        # We should have loaded tests from the package __init__. +        # (normally this would be nested TestSuites.) +        self.assertEqual(suite, +                         [['load_tests called startdir']]) +      def setup_import_issue_tests(self, fakefile):          listdir = os.listdir          os.listdir = lambda _: [fakefile] @@ -204,6 +466,17 @@ class TestDiscovery(unittest.TestCase):              sys.path[:] = orig_sys_path          self.addCleanup(restore) +    def setup_import_issue_package_tests(self, vfs): +        self.addCleanup(setattr, os, 'listdir', os.listdir) +        self.addCleanup(setattr, os.path, 'isfile', os.path.isfile) +        self.addCleanup(setattr, os.path, 'isdir', os.path.isdir) +        self.addCleanup(sys.path.__setitem__, slice(None), list(sys.path)) +        def list_dir(path): +            return list(vfs[path]) +        os.listdir = list_dir +        os.path.isdir = lambda path: not path.endswith('.py') +        os.path.isfile = lambda path: path.endswith('.py') +      def test_discover_with_modules_that_fail_to_import(self):          loader = unittest.TestLoader() @@ -212,11 +485,44 @@ class TestDiscovery(unittest.TestCase):          suite = loader.discover('.')          self.assertIn(os.getcwd(), sys.path)          self.assertEqual(suite.countTestCases(), 1) +        # Errors loading the suite are also captured for introspection. +        self.assertNotEqual([], loader.errors) +        self.assertEqual(1, len(loader.errors)) +        error = loader.errors[0] +        self.assertTrue( +            'Failed to import test module: test_this_does_not_exist' in error, +            'missing error string in %r' % error)          test = list(list(suite)[0])[0] # extract test from suite          with self.assertRaises(ImportError):              test.test_this_does_not_exist() +    def test_discover_with_init_modules_that_fail_to_import(self): +        vfs = {abspath('/foo'): ['my_package'], +               abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} +        self.setup_import_issue_package_tests(vfs) +        import_calls = [] +        def _get_module_from_name(name): +            import_calls.append(name) +            raise ImportError("Cannot import Name") +        loader = unittest.TestLoader() +        loader._get_module_from_name = _get_module_from_name +        suite = loader.discover(abspath('/foo')) + +        self.assertIn(abspath('/foo'), sys.path) +        self.assertEqual(suite.countTestCases(), 1) +        # Errors loading the suite are also captured for introspection. +        self.assertNotEqual([], loader.errors) +        self.assertEqual(1, len(loader.errors)) +        error = loader.errors[0] +        self.assertTrue( +            'Failed to import test module: my_package' in error, +            'missing error string in %r' % error) +        test = list(list(suite)[0])[0] # extract test from suite +        with self.assertRaises(ImportError): +            test.my_package() +        self.assertEqual(import_calls, ['my_package']) +          # Check picklability          for proto in range(pickle.HIGHEST_PROTOCOL + 1):              pickle.loads(pickle.dumps(test, proto)) @@ -241,6 +547,30 @@ class TestDiscovery(unittest.TestCase):          for proto in range(pickle.HIGHEST_PROTOCOL + 1):              pickle.loads(pickle.dumps(suite, proto)) +    def test_discover_with_init_module_that_raises_SkipTest_on_import(self): +        vfs = {abspath('/foo'): ['my_package'], +               abspath('/foo/my_package'): ['__init__.py', 'test_module.py']} +        self.setup_import_issue_package_tests(vfs) +        import_calls = [] +        def _get_module_from_name(name): +            import_calls.append(name) +            raise unittest.SkipTest('skipperoo') +        loader = unittest.TestLoader() +        loader._get_module_from_name = _get_module_from_name +        suite = loader.discover(abspath('/foo')) + +        self.assertIn(abspath('/foo'), sys.path) +        self.assertEqual(suite.countTestCases(), 1) +        result = unittest.TestResult() +        suite.run(result) +        self.assertEqual(len(result.skipped), 1) +        self.assertEqual(result.testsRun, 1) +        self.assertEqual(import_calls, ['my_package']) + +        # Check picklability +        for proto in range(pickle.HIGHEST_PROTOCOL + 1): +            pickle.loads(pickle.dumps(suite, proto)) +      def test_command_line_handling_parseArgs(self):          program = TestableTestProgram() diff --git a/Lib/unittest/test/test_loader.py b/Lib/unittest/test/test_loader.py index b62a1b5c54..4b97882d65 100644 --- a/Lib/unittest/test/test_loader.py +++ b/Lib/unittest/test/test_loader.py @@ -1,12 +1,37 @@  import sys  import types - +import warnings  import unittest +# Decorator used in the deprecation tests to reset the warning registry for +# test isolation and reproducibility. +def warningregistry(func): +    def wrapper(*args, **kws): +        missing = [] +        saved = getattr(warnings, '__warningregistry__', missing).copy() +        try: +            return func(*args, **kws) +        finally: +            if saved is missing: +                try: +                    del warnings.__warningregistry__ +                except AttributeError: +                    pass +            else: +                warnings.__warningregistry__ = saved +    return wrapper +  class Test_TestLoader(unittest.TestCase): +    ### Basic object tests +    ################################################################ + +    def test___init__(self): +        loader = unittest.TestLoader() +        self.assertEqual([], loader.errors) +      ### Tests for TestLoader.loadTestsFromTestCase      ################################################################ @@ -150,6 +175,7 @@ class Test_TestLoader(unittest.TestCase):      # Check that loadTestsFromModule honors (or not) a module      # with a load_tests function. +    @warningregistry      def test_loadTestsFromModule__load_tests(self):          m = types.ModuleType('m')          class MyTestCase(unittest.TestCase): @@ -168,10 +194,145 @@ class Test_TestLoader(unittest.TestCase):          suite = loader.loadTestsFromModule(m)          self.assertIsInstance(suite, unittest.TestSuite)          self.assertEqual(load_tests_args, [loader, suite, None]) +        # With Python 3.5, the undocumented and unofficial use_load_tests is +        # ignored (and deprecated). +        load_tests_args = [] +        with warnings.catch_warnings(record=False): +            warnings.simplefilter('ignore') +            suite = loader.loadTestsFromModule(m, use_load_tests=False) +        self.assertEqual(load_tests_args, [loader, suite, None]) + +    @warningregistry +    def test_loadTestsFromModule__use_load_tests_deprecated_positional(self): +        m = types.ModuleType('m') +        class MyTestCase(unittest.TestCase): +            def test(self): +                pass +        m.testcase_1 = MyTestCase + +        load_tests_args = [] +        def load_tests(loader, tests, pattern): +            self.assertIsInstance(tests, unittest.TestSuite) +            load_tests_args.extend((loader, tests, pattern)) +            return tests +        m.load_tests = load_tests +        # The method still works. +        loader = unittest.TestLoader() +        # use_load_tests=True as a positional argument. +        with warnings.catch_warnings(record=True) as w: +            warnings.simplefilter('always') +            suite = loader.loadTestsFromModule(m, False) +        self.assertIsInstance(suite, unittest.TestSuite) +        # load_tests was still called because use_load_tests is deprecated +        # and ignored. +        self.assertEqual(load_tests_args, [loader, suite, None]) +        # We got a warning. +        self.assertIs(w[-1].category, DeprecationWarning) +        self.assertEqual(str(w[-1].message), +                             'use_load_tests is deprecated and ignored') + +    @warningregistry +    def test_loadTestsFromModule__use_load_tests_deprecated_keyword(self): +        m = types.ModuleType('m') +        class MyTestCase(unittest.TestCase): +            def test(self): +                pass +        m.testcase_1 = MyTestCase + +        load_tests_args = [] +        def load_tests(loader, tests, pattern): +            self.assertIsInstance(tests, unittest.TestSuite) +            load_tests_args.extend((loader, tests, pattern)) +            return tests +        m.load_tests = load_tests +        # The method still works. +        loader = unittest.TestLoader() +        with warnings.catch_warnings(record=True) as w: +            warnings.simplefilter('always') +            suite = loader.loadTestsFromModule(m, use_load_tests=False) +        self.assertIsInstance(suite, unittest.TestSuite) +        # load_tests was still called because use_load_tests is deprecated +        # and ignored. +        self.assertEqual(load_tests_args, [loader, suite, None]) +        # We got a warning. +        self.assertIs(w[-1].category, DeprecationWarning) +        self.assertEqual(str(w[-1].message), +                             'use_load_tests is deprecated and ignored') + +    @warningregistry +    def test_loadTestsFromModule__too_many_positional_args(self): +        m = types.ModuleType('m') +        class MyTestCase(unittest.TestCase): +            def test(self): +                pass +        m.testcase_1 = MyTestCase + +        load_tests_args = [] +        def load_tests(loader, tests, pattern): +            self.assertIsInstance(tests, unittest.TestSuite) +            load_tests_args.extend((loader, tests, pattern)) +            return tests +        m.load_tests = load_tests +        loader = unittest.TestLoader() +        with self.assertRaises(TypeError) as cm, \ +             warnings.catch_warnings(record=True) as w: +            warnings.simplefilter('always') +            loader.loadTestsFromModule(m, False, 'testme.*') +        # We still got the deprecation warning. +        self.assertIs(w[-1].category, DeprecationWarning) +        self.assertEqual(str(w[-1].message), +                                'use_load_tests is deprecated and ignored') +        # We also got a TypeError for too many positional arguments. +        self.assertEqual(type(cm.exception), TypeError) +        self.assertEqual( +            str(cm.exception), +            'loadTestsFromModule() takes 1 positional argument but 3 were given') + +    @warningregistry +    def test_loadTestsFromModule__use_load_tests_other_bad_keyword(self): +        m = types.ModuleType('m') +        class MyTestCase(unittest.TestCase): +            def test(self): +                pass +        m.testcase_1 = MyTestCase + +        load_tests_args = [] +        def load_tests(loader, tests, pattern): +            self.assertIsInstance(tests, unittest.TestSuite) +            load_tests_args.extend((loader, tests, pattern)) +            return tests +        m.load_tests = load_tests +        loader = unittest.TestLoader() +        with warnings.catch_warnings(): +            warnings.simplefilter('ignore') +            with self.assertRaises(TypeError) as cm: +                loader.loadTestsFromModule( +                    m, use_load_tests=False, very_bad=True, worse=False) +        self.assertEqual(type(cm.exception), TypeError) +        # The error message names the first bad argument alphabetically, +        # however use_load_tests (which sorts first) is ignored. +        self.assertEqual( +            str(cm.exception), +            "loadTestsFromModule() got an unexpected keyword argument 'very_bad'") + +    def test_loadTestsFromModule__pattern(self): +        m = types.ModuleType('m') +        class MyTestCase(unittest.TestCase): +            def test(self): +                pass +        m.testcase_1 = MyTestCase          load_tests_args = [] -        suite = loader.loadTestsFromModule(m, use_load_tests=False) -        self.assertEqual(load_tests_args, []) +        def load_tests(loader, tests, pattern): +            self.assertIsInstance(tests, unittest.TestSuite) +            load_tests_args.extend((loader, tests, pattern)) +            return tests +        m.load_tests = load_tests + +        loader = unittest.TestLoader() +        suite = loader.loadTestsFromModule(m, pattern='testme.*') +        self.assertIsInstance(suite, unittest.TestSuite) +        self.assertEqual(load_tests_args, [loader, suite, 'testme.*'])      def test_loadTestsFromModule__faulty_load_tests(self):          m = types.ModuleType('m') @@ -184,6 +345,13 @@ class Test_TestLoader(unittest.TestCase):          suite = loader.loadTestsFromModule(m)          self.assertIsInstance(suite, unittest.TestSuite)          self.assertEqual(suite.countTestCases(), 1) +        # Errors loading the suite are also captured for introspection. +        self.assertNotEqual([], loader.errors) +        self.assertEqual(1, len(loader.errors)) +        error = loader.errors[0] +        self.assertTrue( +            'Failed to call load_tests:' in error, +            'missing error string in %r' % error)          test = list(suite)[0]          self.assertRaisesRegex(TypeError, "some failure", test.m) @@ -219,15 +387,15 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromName__malformed_name(self):          loader = unittest.TestLoader() -        # XXX Should this raise ValueError or ImportError? -        try: -            loader.loadTestsFromName('abc () //') -        except ValueError: -            pass -        except ImportError: -            pass -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise ValueError") +        suite = loader.loadTestsFromName('abc () //') +        error, test = self.check_deferred_error(loader, suite) +        expected = "Failed to import test module: abc () //" +        expected_regex = "Failed to import test module: abc \(\) //" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex( +            ImportError, expected_regex, getattr(test, 'abc () //'))      # "The specifier name is a ``dotted name'' that may resolve ... to a      # module" @@ -236,28 +404,47 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromName__unknown_module_name(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromName('sdasfasfasdf') -        except ImportError as e: -            self.assertEqual(str(e), "No module named 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise ImportError") +        suite = loader.loadTestsFromName('sdasfasfasdf') +        expected = "No module named 'sdasfasfasdf'" +        error, test = self.check_deferred_error(loader, suite) +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method      # within a test case class, or a callable object which returns a      # TestCase or TestSuite instance."      # -    # What happens when the module is found, but the attribute can't? -    def test_loadTestsFromName__unknown_attr_name(self): +    # What happens when the module is found, but the attribute isn't? +    def test_loadTestsFromName__unknown_attr_name_on_module(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromName('unittest.sdasfasfasdf') -        except AttributeError as e: -            self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") +        suite = loader.loadTestsFromName('unittest.loader.sdasfasfasdf') +        expected = "module 'unittest.loader' has no attribute 'sdasfasfasdf'" +        error, test = self.check_deferred_error(loader, suite) +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf) + +    # "The specifier name is a ``dotted name'' that may resolve either to +    # a module, a test case class, a TestSuite instance, a test method +    # within a test case class, or a callable object which returns a +    # TestCase or TestSuite instance." +    # +    # What happens when the module is found, but the attribute isn't? +    def test_loadTestsFromName__unknown_attr_name_on_package(self): +        loader = unittest.TestLoader() + +        suite = loader.loadTestsFromName('unittest.sdasfasfasdf') +        expected = "No module named 'unittest.sdasfasfasdf'" +        error, test = self.check_deferred_error(loader, suite) +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -269,12 +456,13 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromName__relative_unknown_name(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromName('sdasfasfasdf', unittest) -        except AttributeError as e: -            self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") +        suite = loader.loadTestsFromName('sdasfasfasdf', unittest) +        expected = "module 'unittest' has no attribute 'sdasfasfasdf'" +        error, test = self.check_deferred_error(loader, suite) +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -290,12 +478,13 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromName__relative_empty_name(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromName('', unittest) -        except AttributeError as e: -            pass -        else: -            self.fail("Failed to raise AttributeError") +        suite = loader.loadTestsFromName('', unittest) +        error, test = self.check_deferred_error(loader, suite) +        expected = "has no attribute ''" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, getattr(test, ''))      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -310,14 +499,15 @@ class Test_TestLoader(unittest.TestCase):          loader = unittest.TestLoader()          # XXX Should this raise AttributeError or ValueError? -        try: -            loader.loadTestsFromName('abc () //', unittest) -        except ValueError: -            pass -        except AttributeError: -            pass -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise ValueError") +        suite = loader.loadTestsFromName('abc () //', unittest) +        error, test = self.check_deferred_error(loader, suite) +        expected = "module 'unittest' has no attribute 'abc () //'" +        expected_regex = "module 'unittest' has no attribute 'abc \(\) //'" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex( +            AttributeError, expected_regex, getattr(test, 'abc () //'))      # "The method optionally resolves name relative to the given module"      # @@ -423,12 +613,13 @@ class Test_TestLoader(unittest.TestCase):          m.testcase_1 = MyTestCase          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromName('testcase_1.testfoo', m) -        except AttributeError as e: -            self.assertEqual(str(e), "type object 'MyTestCase' has no attribute 'testfoo'") -        else: -            self.fail("Failed to raise AttributeError") +        suite = loader.loadTestsFromName('testcase_1.testfoo', m) +        expected = "type object 'MyTestCase' has no attribute 'testfoo'" +        error, test = self.check_deferred_error(loader, suite) +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.testfoo)      # "The specifier name is a ``dotted name'' that may resolve ... to      # ... a callable object which returns a ... TestSuite instance" @@ -546,6 +737,23 @@ class Test_TestLoader(unittest.TestCase):      ### Tests for TestLoader.loadTestsFromNames()      ################################################################ +    def check_deferred_error(self, loader, suite): +        """Helper function for checking that errors in loading are reported. + +        :param loader: A loader with some errors. +        :param suite: A suite that should have a late bound error. +        :return: The first error message from the loader and the test object +            from the suite. +        """ +        self.assertIsInstance(suite, unittest.TestSuite) +        self.assertEqual(suite.countTestCases(), 1) +        # Errors loading the suite are also captured for introspection. +        self.assertNotEqual([], loader.errors) +        self.assertEqual(1, len(loader.errors)) +        error = loader.errors[0] +        test = list(suite)[0] +        return error, test +      # "Similar to loadTestsFromName(), but takes a sequence of names rather      # than a single name."      # @@ -598,14 +806,15 @@ class Test_TestLoader(unittest.TestCase):          loader = unittest.TestLoader()          # XXX Should this raise ValueError or ImportError? -        try: -            loader.loadTestsFromNames(['abc () //']) -        except ValueError: -            pass -        except ImportError: -            pass -        else: -            self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") +        suite = loader.loadTestsFromNames(['abc () //']) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "Failed to import test module: abc () //" +        expected_regex = "Failed to import test module: abc \(\) //" +        self.assertIn( +            expected,  error, +            'missing error string in %r' % error) +        self.assertRaisesRegex( +            ImportError, expected_regex, getattr(test, 'abc () //'))      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -616,12 +825,13 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromNames__unknown_module_name(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromNames(['sdasfasfasdf']) -        except ImportError as e: -            self.assertEqual(str(e), "No module named 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromNames failed to raise ImportError") +        suite = loader.loadTestsFromNames(['sdasfasfasdf']) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "Failed to import test module: sdasfasfasdf" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(ImportError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -632,12 +842,14 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromNames__unknown_attr_name(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromNames(['unittest.sdasfasfasdf', 'unittest']) -        except AttributeError as e: -            self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromNames failed to raise AttributeError") +        suite = loader.loadTestsFromNames( +            ['unittest.loader.sdasfasfasdf', 'unittest.test.dummy']) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "module 'unittest.loader' has no attribute 'sdasfasfasdf'" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -651,12 +863,13 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromNames__unknown_name_relative_1(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromNames(['sdasfasfasdf'], unittest) -        except AttributeError as e: -            self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") +        suite = loader.loadTestsFromNames(['sdasfasfasdf'], unittest) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "module 'unittest' has no attribute 'sdasfasfasdf'" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -670,12 +883,13 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromNames__unknown_name_relative_2(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromNames(['TestCase', 'sdasfasfasdf'], unittest) -        except AttributeError as e: -            self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") -        else: -            self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") +        suite = loader.loadTestsFromNames(['TestCase', 'sdasfasfasdf'], unittest) +        error, test = self.check_deferred_error(loader, list(suite)[1]) +        expected = "module 'unittest' has no attribute 'sdasfasfasdf'" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.sdasfasfasdf)      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -691,12 +905,13 @@ class Test_TestLoader(unittest.TestCase):      def test_loadTestsFromNames__relative_empty_name(self):          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromNames([''], unittest) -        except AttributeError: -            pass -        else: -            self.fail("Failed to raise ValueError") +        suite = loader.loadTestsFromNames([''], unittest) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "has no attribute ''" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, getattr(test, ''))      # "The specifier name is a ``dotted name'' that may resolve either to      # a module, a test case class, a TestSuite instance, a test method @@ -710,14 +925,15 @@ class Test_TestLoader(unittest.TestCase):          loader = unittest.TestLoader()          # XXX Should this raise AttributeError or ValueError? -        try: -            loader.loadTestsFromNames(['abc () //'], unittest) -        except AttributeError: -            pass -        except ValueError: -            pass -        else: -            self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") +        suite = loader.loadTestsFromNames(['abc () //'], unittest) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "module 'unittest' has no attribute 'abc () //'" +        expected_regex = "module 'unittest' has no attribute 'abc \(\) //'" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex( +            AttributeError, expected_regex, getattr(test, 'abc () //'))      # "The method optionally resolves name relative to the given module"      # @@ -835,12 +1051,13 @@ class Test_TestLoader(unittest.TestCase):          m.testcase_1 = MyTestCase          loader = unittest.TestLoader() -        try: -            loader.loadTestsFromNames(['testcase_1.testfoo'], m) -        except AttributeError as e: -            self.assertEqual(str(e), "type object 'MyTestCase' has no attribute 'testfoo'") -        else: -            self.fail("Failed to raise AttributeError") +        suite = loader.loadTestsFromNames(['testcase_1.testfoo'], m) +        error, test = self.check_deferred_error(loader, list(suite)[0]) +        expected = "type object 'MyTestCase' has no attribute 'testfoo'" +        self.assertIn( +            expected, error, +            'missing error string in %r' % error) +        self.assertRaisesRegex(AttributeError, expected, test.testfoo)      # "The specifier name is a ``dotted name'' that may resolve ... to      # ... a callable object which returns a ... TestSuite instance" diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py index 725d67fdaf..1cfc17959e 100644 --- a/Lib/unittest/test/test_program.py +++ b/Lib/unittest/test/test_program.py @@ -134,6 +134,7 @@ class InitialisableProgram(unittest.TestProgram):      result = None      verbosity = 1      defaultTest = None +    tb_locals = False      testRunner = None      testLoader = unittest.defaultTestLoader      module = '__main__' @@ -147,18 +148,19 @@ RESULT = object()  class FakeRunner(object):      initArgs = None      test = None -    raiseError = False +    raiseError = 0      def __init__(self, **kwargs):          FakeRunner.initArgs = kwargs          if FakeRunner.raiseError: -            FakeRunner.raiseError = False +            FakeRunner.raiseError -= 1              raise TypeError      def run(self, test):          FakeRunner.test = test          return RESULT +  class TestCommandLineArgs(unittest.TestCase):      def setUp(self): @@ -166,7 +168,7 @@ class TestCommandLineArgs(unittest.TestCase):          self.program.createTests = lambda: None          FakeRunner.initArgs = None          FakeRunner.test = None -        FakeRunner.raiseError = False +        FakeRunner.raiseError = 0      def testVerbosity(self):          program = self.program @@ -256,6 +258,7 @@ class TestCommandLineArgs(unittest.TestCase):          self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity',                                                  'failfast': 'failfast',                                                  'buffer': 'buffer', +                                                'tb_locals': False,                                                  'warnings': 'warnings'})          self.assertEqual(FakeRunner.test, 'test')          self.assertIs(program.result, RESULT) @@ -274,10 +277,25 @@ class TestCommandLineArgs(unittest.TestCase):          self.assertEqual(FakeRunner.test, 'test')          self.assertIs(program.result, RESULT) +    def test_locals(self): +        program = self.program + +        program.testRunner = FakeRunner +        program.parseArgs([None, '--locals']) +        self.assertEqual(True, program.tb_locals) +        program.runTests() +        self.assertEqual(FakeRunner.initArgs, {'buffer': False, +                                               'failfast': False, +                                               'tb_locals': True, +                                               'verbosity': 1, +                                               'warnings': None}) +      def testRunTestsOldRunnerClass(self):          program = self.program -        FakeRunner.raiseError = True +        # Two TypeErrors are needed to fall all the way back to old-style +        # runners - one to fail tb_locals, one to fail buffer etc. +        FakeRunner.raiseError = 2          program.testRunner = FakeRunner          program.verbosity = 'verbosity'          program.failfast = 'failfast' diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py index 489fe17754..e39e2eaeca 100644 --- a/Lib/unittest/test/test_result.py +++ b/Lib/unittest/test/test_result.py @@ -8,6 +8,20 @@ import traceback  import unittest +class MockTraceback(object): +    class TracebackException: +        def __init__(self, *args, **kwargs): +            self.capture_locals = kwargs.get('capture_locals', False) +        def format(self): +            result = ['A traceback'] +            if self.capture_locals: +                result.append('locals') +            return result + +def restore_traceback(): +    unittest.result.traceback = traceback + +  class Test_TestResult(unittest.TestCase):      # Note: there are not separate tests for TestResult.wasSuccessful(),      # TestResult.errors, TestResult.failures, TestResult.testsRun or @@ -227,6 +241,25 @@ class Test_TestResult(unittest.TestCase):          self.assertIs(test_case, test)          self.assertIsInstance(formatted_exc, str) +    def test_addError_locals(self): +        class Foo(unittest.TestCase): +            def test_1(self): +                1/0 + +        test = Foo('test_1') +        result = unittest.TestResult() +        result.tb_locals = True + +        unittest.result.traceback = MockTraceback +        self.addCleanup(restore_traceback) +        result.startTestRun() +        test.run(result) +        result.stopTestRun() + +        self.assertEqual(len(result.errors), 1) +        test_case, formatted_exc = result.errors[0] +        self.assertEqual('A tracebacklocals', formatted_exc) +      def test_addSubTest(self):          class Foo(unittest.TestCase):              def test_1(self): @@ -398,6 +431,7 @@ def __init__(self, stream=None, descriptions=None, verbosity=None):      self.testsRun = 0      self.shouldStop = False      self.buffer = False +    self.tb_locals = False  classDict['__init__'] = __init__  OldResult = type('OldResult', (object,), classDict) @@ -454,15 +488,6 @@ class Test_OldTestResult(unittest.TestCase):          runner.run(Test('testFoo')) -class MockTraceback(object): -    @staticmethod -    def format_exception(*_): -        return ['A traceback'] - -def restore_traceback(): -    unittest.result.traceback = traceback - -  class TestOutputBuffering(unittest.TestCase):      def setUp(self): diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py index 7c0bd51d79..ddc498c230 100644 --- a/Lib/unittest/test/test_runner.py +++ b/Lib/unittest/test/test_runner.py @@ -158,7 +158,7 @@ class Test_TextTestRunner(unittest.TestCase):          self.assertEqual(runner.warnings, None)          self.assertTrue(runner.descriptions)          self.assertEqual(runner.resultclass, unittest.TextTestResult) - +        self.assertFalse(runner.tb_locals)      def test_multiple_inheritance(self):          class AResult(unittest.TestResult): @@ -172,14 +172,13 @@ class Test_TextTestRunner(unittest.TestCase):          # on arguments in its __init__ super call          ATextResult(None, None, 1) -      def testBufferAndFailfast(self):          class Test(unittest.TestCase):              def testFoo(self):                  pass          result = unittest.TestResult()          runner = unittest.TextTestRunner(stream=io.StringIO(), failfast=True, -                                           buffer=True) +                                         buffer=True)          # Use our result object          runner._makeResult = lambda: result          runner.run(Test('testFoo')) @@ -187,6 +186,11 @@ class Test_TextTestRunner(unittest.TestCase):          self.assertTrue(result.failfast)          self.assertTrue(result.buffer) +    def test_locals(self): +        runner = unittest.TextTestRunner(stream=io.StringIO(), tb_locals=True) +        result = runner.run(unittest.TestSuite()) +        self.assertEqual(True, result.tb_locals) +      def testRunnerRegistersResult(self):          class Test(unittest.TestCase):              def testFoo(self): @@ -286,7 +290,8 @@ class Test_TextTestRunner(unittest.TestCase):          # no args -> all the warnings are printed, unittest warnings only once          p = subprocess.Popen([sys.executable, '_test_warnings.py'], **opts) -        out, err = get_parse_out_err(p) +        with p: +            out, err = get_parse_out_err(p)          self.assertIn(b'OK', err)          # check that the total number of warnings in the output is correct          self.assertEqual(len(out), 12) @@ -307,7 +312,8 @@ class Test_TextTestRunner(unittest.TestCase):          # in all these cases no warnings are printed          for args in args_list:              p = subprocess.Popen(args, **opts) -            out, err = get_parse_out_err(p) +            with p: +                out, err = get_parse_out_err(p)              self.assertIn(b'OK', err)              self.assertEqual(len(out), 0) @@ -316,7 +322,8 @@ class Test_TextTestRunner(unittest.TestCase):          #                                     unittest warnings only once          p = subprocess.Popen([sys.executable, '_test_warnings.py', 'always'],                               **opts) -        out, err = get_parse_out_err(p) +        with p: +            out, err = get_parse_out_err(p)          self.assertIn(b'OK', err)          self.assertEqual(len(out), 14)          for msg in [b'dw', b'iw', b'uw', b'rw']: diff --git a/Lib/unittest/test/test_setups.py b/Lib/unittest/test/test_setups.py index 392f95efc0..2df703ed93 100644 --- a/Lib/unittest/test/test_setups.py +++ b/Lib/unittest/test/test_setups.py @@ -111,7 +111,7 @@ class TestSetups(unittest.TestCase):          self.assertEqual(len(result.errors), 1)          error, _ = result.errors[0]          self.assertEqual(str(error), -                    'setUpClass (%s.BrokenTest)' % __name__) +                    'setUpClass (%s.%s)' % (__name__, BrokenTest.__qualname__))      def test_error_in_teardown_class(self):          class Test(unittest.TestCase): @@ -144,7 +144,7 @@ class TestSetups(unittest.TestCase):          error, _ = result.errors[0]          self.assertEqual(str(error), -                    'tearDownClass (%s.Test)' % __name__) +                    'tearDownClass (%s.%s)' % (__name__, Test.__qualname__))      def test_class_not_torndown_when_setup_fails(self):          class Test(unittest.TestCase): @@ -414,7 +414,8 @@ class TestSetups(unittest.TestCase):          self.assertEqual(len(result.errors), 0)          self.assertEqual(len(result.skipped), 1)          skipped = result.skipped[0][0] -        self.assertEqual(str(skipped), 'setUpClass (%s.Test)' % __name__) +        self.assertEqual(str(skipped), +                    'setUpClass (%s.%s)' % (__name__, Test.__qualname__))      def test_skiptest_in_setupmodule(self):          class Test(unittest.TestCase): diff --git a/Lib/unittest/test/testmock/testmagicmethods.py b/Lib/unittest/test/testmock/testmagicmethods.py index e05c6e014d..bb9b956bb2 100644 --- a/Lib/unittest/test/testmock/testmagicmethods.py +++ b/Lib/unittest/test/testmock/testmagicmethods.py @@ -424,6 +424,17 @@ class TestMockingMagicMethods(unittest.TestCase):          self.assertEqual(list(m), []) +    def test_matmul(self): +        m = MagicMock() +        self.assertIsInstance(m @ 1, MagicMock) +        m.__matmul__.return_value = 42 +        m.__rmatmul__.return_value = 666 +        m.__imatmul__.return_value = 24 +        self.assertEqual(m @ 1, 42) +        self.assertEqual(1 @ m, 666) +        m @= 24 +        self.assertEqual(m, 24) +      def test_divmod_and_rdivmod(self):          m = MagicMock()          self.assertIsInstance(divmod(5, m), MagicMock) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index cf1673c6ca..5f82b82966 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -174,6 +174,15 @@ class MockTest(unittest.TestCase):          self.assertEqual([mock(), mock(), mock()], [3, 2, 1],                            "callable side effect not used correctly") +    def test_autospec_side_effect_exception(self): +        # Test for issue 23661 +        def f(): +            pass + +        mock = create_autospec(f) +        mock.side_effect = ValueError('Bazinga!') +        self.assertRaisesRegex(ValueError, 'Bazinga!', mock) +      @unittest.skipUnless('java' in sys.platform,                            'This test only applies to Jython')      def test_java_exception_side_effect(self): @@ -295,6 +304,17 @@ class MockTest(unittest.TestCase):          # an exception. See issue 24857.          self.assertFalse(mock.call_args == "a long sequence") + +    def test_calls_equal_with_any(self): +        call1 = mock.call(mock.MagicMock()) +        call2 = mock.call(mock.ANY) + +        # Check that equality and non-equality is consistent even when +        # comparing with mock.ANY +        self.assertTrue(call1 == call2) +        self.assertFalse(call1 != call2) + +      def test_assert_called_with(self):          mock = Mock()          mock() @@ -310,6 +330,12 @@ class MockTest(unittest.TestCase):          mock.assert_called_with(1, 2, 3, a='fish', b='nothing') +    def test_assert_called_with_any(self): +        m = MagicMock() +        m(MagicMock()) +        m.assert_called_with(mock.ANY) + +      def test_assert_called_with_function_spec(self):          def f(a, b, c, d=None):              pass @@ -1194,6 +1220,42 @@ class MockTest(unittest.TestCase):          m = mock.create_autospec(object(), name='sweet_func')          self.assertIn('sweet_func', repr(m)) +    #Issue21238 +    def test_mock_unsafe(self): +        m = Mock() +        with self.assertRaises(AttributeError): +            m.assert_foo_call() +        with self.assertRaises(AttributeError): +            m.assret_foo_call() +        m = Mock(unsafe=True) +        m.assert_foo_call() +        m.assret_foo_call() + +    #Issue21262 +    def test_assert_not_called(self): +        m = Mock() +        m.hello.assert_not_called() +        m.hello() +        with self.assertRaises(AssertionError): +            m.hello.assert_not_called() + +    #Issue21256 printout of keyword args should be in deterministic order +    def test_sorted_call_signature(self): +        m = Mock() +        m.hello(name='hello', daddy='hero') +        text = "call(daddy='hero', name='hello')" +        self.assertEqual(repr(m.hello.call_args), text) + +    #Issue21270 overrides tuple methods for mock.call objects +    def test_override_tuple_methods(self): +        c = call.count() +        i = call.index(132,'hello') +        m = Mock() +        m.count() +        m.index(132,"hello") +        self.assertEqual(m.method_calls[0], c) +        self.assertEqual(m.method_calls[1], i) +      def test_mock_add_spec(self):          class _One(object):              one = 1 @@ -1357,6 +1419,18 @@ class MockTest(unittest.TestCase):          self.assertEqual('abc', first)          self.assertEqual('abc', second) +    def test_mock_open_after_eof(self): +        # read, readline and readlines should work after end of file. +        _open = mock.mock_open(read_data='foo') +        h = _open('bar') +        h.read() +        self.assertEqual('', h.read()) +        self.assertEqual('', h.read()) +        self.assertEqual('', h.readline()) +        self.assertEqual('', h.readline()) +        self.assertEqual([], h.readlines()) +        self.assertEqual([], h.readlines()) +      def test_mock_parents(self):          for Klass in Mock, MagicMock:              m = Klass() diff --git a/Lib/unittest/test/testmock/testpatch.py b/Lib/unittest/test/testmock/testpatch.py index b516f42af0..dfce3696d6 100644 --- a/Lib/unittest/test/testmock/testpatch.py +++ b/Lib/unittest/test/testmock/testpatch.py @@ -377,7 +377,7 @@ class PatchTest(unittest.TestCase):      def test_patchobject_wont_create_by_default(self):          try: -            @patch.object(SomeClass, 'frooble', sentinel.Frooble) +            @patch.object(SomeClass, 'ord', sentinel.Frooble)              def test():                  self.fail('Patching non existent attributes should fail') @@ -386,7 +386,27 @@ class PatchTest(unittest.TestCase):              pass          else:              self.fail('Patching non existent attributes should fail') -        self.assertFalse(hasattr(SomeClass, 'frooble')) +        self.assertFalse(hasattr(SomeClass, 'ord')) + + +    def test_patch_builtins_without_create(self): +        @patch(__name__+'.ord') +        def test_ord(mock_ord): +            mock_ord.return_value = 101 +            return ord('c') + +        @patch(__name__+'.open') +        def test_open(mock_open): +            m = mock_open.return_value +            m.read.return_value = 'abcd' + +            fobj = open('doesnotexists.txt') +            data = fobj.read() +            fobj.close() +            return data + +        self.assertEqual(test_ord(), 101) +        self.assertEqual(test_open(), 'abcd')      def test_patch_with_static_methods(self): @@ -1797,5 +1817,31 @@ class PatchTest(unittest.TestCase):          self.assertEqual(stopped, ["three", "two", "one"]) +    def test_special_attrs(self): +        def foo(x=0): +            """TEST""" +            return x +        with patch.object(foo, '__defaults__', (1, )): +            self.assertEqual(foo(), 1) +        self.assertEqual(foo(), 0) + +        with patch.object(foo, '__doc__', "FUN"): +            self.assertEqual(foo.__doc__, "FUN") +        self.assertEqual(foo.__doc__, "TEST") + +        with patch.object(foo, '__module__', "testpatch2"): +            self.assertEqual(foo.__module__, "testpatch2") +        self.assertEqual(foo.__module__, 'unittest.test.testmock.testpatch') + +        with patch.object(foo, '__annotations__', dict([('s', 1, )])): +            self.assertEqual(foo.__annotations__, dict([('s', 1, )])) +        self.assertEqual(foo.__annotations__, dict()) + +        def foo(*a, x=0): +            return x +        with patch.object(foo, '__kwdefaults__', dict([('x', 1, )])): +            self.assertEqual(foo(), 1) +        self.assertEqual(foo(), 0) +  if __name__ == '__main__':      unittest.main() | 
