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 | 113 | ||||
-rw-r--r-- | Lib/unittest/test/test_discovery.py | 320 | ||||
-rw-r--r-- | Lib/unittest/test/test_loader.py | 423 | ||||
-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 | 10 | ||||
-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 | 45 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testpatch.py | 24 |
13 files changed, 893 insertions, 151 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..ada733b1ff 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -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,9 @@ 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 testAssertIsNone(self): self.assertIsNone(None) @@ -1147,6 +1145,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 +1172,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 +1206,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 +1218,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 +1270,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 +1298,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 +1339,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 +1362,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 +1385,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 +1424,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 +1454,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..55921febf5 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,9 +84,9 @@ 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) @@ -117,34 +124,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, - ['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 + # (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, + [['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 + self.assertEqual(Module.load_tests_args, + [(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_directory' + ' module tests', 'test*')]) + [(loader, [], 'test*.py'), + (loader, [], 'test*.py')]) def test_discover(self): loader = unittest.TestLoader() @@ -192,6 +369,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 +426,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 +445,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 +507,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..68f1036111 100644 --- a/Lib/unittest/test/test_loader.py +++ b/Lib/unittest/test/test_loader.py @@ -1,12 +1,36 @@ 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 = object() + 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 + 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 +174,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 +193,144 @@ 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('never') + 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 = [] - 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() + with self.assertRaises(TypeError) as cm, \ + warnings.catch_warning(record=True) as w: + 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('never') + 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 = [] + 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 +343,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 +385,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 +402,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 +454,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 +476,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 +497,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 +611,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 +735,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 +804,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 +823,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 +840,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 +861,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 +881,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 +903,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 +923,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 +1049,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..9cbc26041f 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): 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..2a6069f66a 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): @@ -1194,6 +1203,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 diff --git a/Lib/unittest/test/testmock/testpatch.py b/Lib/unittest/test/testmock/testpatch.py index b516f42af0..28fe86b0de 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): |