diff options
Diffstat (limited to 'Lib/unittest/test')
-rw-r--r-- | Lib/unittest/test/__main__.py | 18 | ||||
-rw-r--r-- | Lib/unittest/test/support.py | 38 | ||||
-rw-r--r-- | Lib/unittest/test/test_assertions.py | 4 | ||||
-rw-r--r-- | Lib/unittest/test/test_break.py | 4 | ||||
-rw-r--r-- | Lib/unittest/test/test_case.py | 255 | ||||
-rw-r--r-- | Lib/unittest/test/test_discovery.py | 169 | ||||
-rw-r--r-- | Lib/unittest/test/test_functiontestcase.py | 4 | ||||
-rw-r--r-- | Lib/unittest/test/test_loader.py | 4 | ||||
-rw-r--r-- | Lib/unittest/test/test_program.py | 72 | ||||
-rw-r--r-- | Lib/unittest/test/test_result.py | 92 | ||||
-rw-r--r-- | Lib/unittest/test/test_runner.py | 16 | ||||
-rw-r--r-- | Lib/unittest/test/test_setups.py | 7 | ||||
-rw-r--r-- | Lib/unittest/test/test_skipping.py | 80 | ||||
-rw-r--r-- | Lib/unittest/test/test_suite.py | 81 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/__main__.py | 18 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testcallable.py | 4 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testhelpers.py | 59 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testmock.py | 143 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testpatch.py | 1 | ||||
-rw-r--r-- | Lib/unittest/test/testmock/testwith.py | 83 |
20 files changed, 1045 insertions, 107 deletions
diff --git a/Lib/unittest/test/__main__.py b/Lib/unittest/test/__main__.py new file mode 100644 index 00000000000..44d0591e847 --- /dev/null +++ b/Lib/unittest/test/__main__.py @@ -0,0 +1,18 @@ +import os +import unittest + + +def load_tests(loader, standard_tests, pattern): + # top level directory cached on loader instance + this_dir = os.path.dirname(__file__) + pattern = pattern or "test_*.py" + # We are inside unittest.test, so the top-level is two notches up + top_level_dir = os.path.dirname(os.path.dirname(this_dir)) + package_tests = loader.discover(start_dir=this_dir, pattern=pattern, + top_level_dir=top_level_dir) + standard_tests.addTests(package_tests) + return standard_tests + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/unittest/test/support.py b/Lib/unittest/test/support.py index dbe4ddcd0e7..02e8f3a00b6 100644 --- a/Lib/unittest/test/support.py +++ b/Lib/unittest/test/support.py @@ -41,7 +41,7 @@ class TestHashing(object): self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) -class LoggingResult(unittest.TestResult): +class _BaseLoggingResult(unittest.TestResult): def __init__(self, log): self._events = log super().__init__() @@ -52,7 +52,7 @@ class LoggingResult(unittest.TestResult): def startTestRun(self): self._events.append('startTestRun') - super(LoggingResult, self).startTestRun() + super().startTestRun() def stopTest(self, test): self._events.append('stopTest') @@ -60,7 +60,7 @@ class LoggingResult(unittest.TestResult): def stopTestRun(self): self._events.append('stopTestRun') - super(LoggingResult, self).stopTestRun() + super().stopTestRun() def addFailure(self, *args): self._events.append('addFailure') @@ -68,7 +68,7 @@ class LoggingResult(unittest.TestResult): def addSuccess(self, *args): self._events.append('addSuccess') - super(LoggingResult, self).addSuccess(*args) + super().addSuccess(*args) def addError(self, *args): self._events.append('addError') @@ -76,15 +76,39 @@ class LoggingResult(unittest.TestResult): def addSkip(self, *args): self._events.append('addSkip') - super(LoggingResult, self).addSkip(*args) + super().addSkip(*args) def addExpectedFailure(self, *args): self._events.append('addExpectedFailure') - super(LoggingResult, self).addExpectedFailure(*args) + super().addExpectedFailure(*args) def addUnexpectedSuccess(self, *args): self._events.append('addUnexpectedSuccess') - super(LoggingResult, self).addUnexpectedSuccess(*args) + super().addUnexpectedSuccess(*args) + + +class LegacyLoggingResult(_BaseLoggingResult): + """ + A legacy TestResult implementation, without an addSubTest method, + which records its method calls. + """ + + @property + def addSubTest(self): + raise AttributeError + + +class LoggingResult(_BaseLoggingResult): + """ + A TestResult implementation which records its method calls. + """ + + def addSubTest(self, test, subtest, err): + if err is None: + self._events.append('addSubTestSuccess') + else: + self._events.append('addSubTestFailure') + super().addSubTest(test, subtest, err) class ResultWithNoStartTestRunStopTestRun(object): diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py index 7931cadaf3d..af08d5ad65a 100644 --- a/Lib/unittest/test/test_assertions.py +++ b/Lib/unittest/test/test_assertions.py @@ -361,3 +361,7 @@ class TestLongMessage(unittest.TestCase): ['^"regex" does not match "foo"$', '^oops$', '^"regex" does not match "foo"$', '^"regex" does not match "foo" : oops$']) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_break.py b/Lib/unittest/test/test_break.py index 75532f42d3d..0bf1a229b84 100644 --- a/Lib/unittest/test/test_break.py +++ b/Lib/unittest/test/test_break.py @@ -282,3 +282,7 @@ class TestBreakSignalIgnored(TestBreak): "if threads have been used") class TestBreakSignalDefault(TestBreak): int_handler = signal.SIG_DFL + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py index b8cb0c76b7c..363390af09c 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -1,8 +1,10 @@ +import contextlib import difflib import pprint import pickle import re import sys +import logging import warnings import weakref import inspect @@ -13,9 +15,15 @@ from test import support import unittest from .support import ( - TestEquality, TestHashing, LoggingResult, + TestEquality, TestHashing, LoggingResult, LegacyLoggingResult, ResultWithNoStartTestRunStopTestRun ) +from test.support import captured_stderr + + +log_foo = logging.getLogger('foo') +log_foobar = logging.getLogger('foo.bar') +log_quux = logging.getLogger('quux') class Test(object): @@ -297,6 +305,98 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): Foo('test').run() + def _check_call_order__subtests(self, result, events, expected_events): + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + for i in [1, 2, 3]: + with self.subTest(i=i): + if i == 1: + self.fail('failure') + for j in [2, 3]: + with self.subTest(j=j): + if i * j == 6: + raise RuntimeError('raised by Foo.test') + 1 / 0 + + # Order is the following: + # i=1 => subtest failure + # i=2, j=2 => subtest success + # i=2, j=3 => subtest error + # i=3, j=2 => subtest error + # i=3, j=3 => subtest success + # toplevel => error + Foo(events).run(result) + self.assertEqual(events, expected_events) + + def test_run_call_order__subtests(self): + events = [] + result = LoggingResult(events) + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addSubTestFailure', 'addSubTestSuccess', + 'addSubTestFailure', 'addSubTestFailure', + 'addSubTestSuccess', 'addError', 'stopTest'] + 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), + # text execution stops after the first subtest failure. + events = [] + result = LegacyLoggingResult(events) + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addFailure', 'stopTest'] + self._check_call_order__subtests(result, events, expected) + + def _check_call_order__subtests_success(self, result, events, expected_events): + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + for i in [1, 2]: + with self.subTest(i=i): + for j in [2, 3]: + with self.subTest(j=j): + pass + + Foo(events).run(result) + self.assertEqual(events, expected_events) + + def test_run_call_order__subtests_success(self): + events = [] + result = LoggingResult(events) + # The 6 subtest successes are individually recorded, in addition + # to the whole test success. + expected = (['startTest', 'setUp', 'test', 'tearDown'] + + 6 * ['addSubTestSuccess'] + + ['addSuccess', 'stopTest']) + self._check_call_order__subtests_success(result, events, expected) + + def test_run_call_order__subtests_success_legacy(self): + # With a legacy result, only the whole test success is recorded. + events = [] + result = LegacyLoggingResult(events) + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addSuccess', 'stopTest'] + self._check_call_order__subtests_success(result, events, expected) + + def test_run_call_order__subtests_failfast(self): + events = [] + result = LoggingResult(events) + result.failfast = True + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + with self.subTest(i=1): + self.fail('failure') + with self.subTest(i=2): + self.fail('failure') + self.fail('failure') + + expected = ['startTest', 'setUp', 'test', 'tearDown', + 'addSubTestFailure', 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + # "This class attribute gives the exception raised by the test() method. # If a test framework needs to use a specialized exception, possibly to # carry additional information, it must subclass this exception in @@ -729,18 +829,18 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): # set a lower threshold value and add a cleanup to restore it old_threshold = self._diffThreshold - self._diffThreshold = 2**8 + self._diffThreshold = 2**5 self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) # under the threshold: diff marker (^) in error message - s = 'x' * (2**7) + s = 'x' * (2**4) with self.assertRaises(self.failureException) as cm: self.assertEqual(s + 'a', s + 'b') self.assertIn('^', str(cm.exception)) self.assertEqual(s + 'a', s + 'a') # over the threshold: diff not used and marker (^) not in error message - s = 'x' * (2**9) + s = 'x' * (2**6) # if the path that uses difflib is taken, _truncateMessage will be # called -- replace it with explodingTruncation to verify that this # doesn't happen @@ -757,6 +857,35 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing): self.assertEqual(str(cm.exception), '%r != %r' % (s1, s2)) self.assertEqual(s + 'a', s + 'a') + def testAssertEqual_shorten(self): + # set a lower threshold value and add a cleanup to restore it + old_threshold = self._diffThreshold + self._diffThreshold = 0 + self.addCleanup(lambda: setattr(self, '_diffThreshold', old_threshold)) + + s = 'x' * 100 + s1, s2 = s + 'a', s + 'b' + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + c = 'xxxx[35 chars]' + 'x' * 61 + self.assertEqual(str(cm.exception), "'%sa' != '%sb'" % (c, c)) + self.assertEqual(s + 'a', s + 'a') + + p = 'y' * 50 + s1, s2 = s + 'a' + p, s + 'b' + p + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + c = 'xxxx[85 chars]xxxxxxxxxxx' + self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, p, c, p)) + + p = 'y' * 100 + s1, s2 = s + 'a' + p, s + 'b' + p + with self.assertRaises(self.failureException) as cm: + self.assertEqual(s1, s2) + c = 'xxxx[91 chars]xxxxx' + d = 'y' * 40 + '[56 chars]yyyy' + self.assertEqual(str(cm.exception), "'%sa%s' != '%sb%s'" % (c, d, c, d)) + def testAssertCountEqual(self): a = object() self.assertCountEqual([1, 2, 3], [3, 2, 1]) @@ -1159,6 +1288,94 @@ test case with self.assertWarnsRegex(RuntimeWarning, "o+"): _runtime_warn("barz") + @contextlib.contextmanager + def assertNoStderr(self): + with captured_stderr() as buf: + yield + self.assertEqual(buf.getvalue(), "") + + def assertLogRecords(self, records, matches): + self.assertEqual(len(records), len(matches)) + for rec, match in zip(records, matches): + self.assertIsInstance(rec, logging.LogRecord) + for k, v in match.items(): + self.assertEqual(getattr(rec, k), v) + + def testAssertLogsDefaults(self): + # defaults: root logger, level INFO + with self.assertNoStderr(): + with self.assertLogs() as cm: + log_foo.info("1") + log_foobar.debug("2") + self.assertEqual(cm.output, ["INFO:foo:1"]) + self.assertLogRecords(cm.records, [{'name': 'foo'}]) + + def testAssertLogsTwoMatchingMessages(self): + # Same, but with two matching log messages + with self.assertNoStderr(): + with self.assertLogs() as cm: + log_foo.info("1") + log_foobar.debug("2") + log_quux.warning("3") + self.assertEqual(cm.output, ["INFO:foo:1", "WARNING:quux:3"]) + self.assertLogRecords(cm.records, + [{'name': 'foo'}, {'name': 'quux'}]) + + def checkAssertLogsPerLevel(self, level): + # Check level filtering + with self.assertNoStderr(): + with self.assertLogs(level=level) as cm: + log_foo.warning("1") + log_foobar.error("2") + log_quux.critical("3") + self.assertEqual(cm.output, ["ERROR:foo.bar:2", "CRITICAL:quux:3"]) + self.assertLogRecords(cm.records, + [{'name': 'foo.bar'}, {'name': 'quux'}]) + + def testAssertLogsPerLevel(self): + self.checkAssertLogsPerLevel(logging.ERROR) + self.checkAssertLogsPerLevel('ERROR') + + def checkAssertLogsPerLogger(self, logger): + # Check per-logger fitering + with self.assertNoStderr(): + with self.assertLogs(level='DEBUG') as outer_cm: + with self.assertLogs(logger, level='DEBUG') as cm: + log_foo.info("1") + log_foobar.debug("2") + log_quux.warning("3") + self.assertEqual(cm.output, ["INFO:foo:1", "DEBUG:foo.bar:2"]) + self.assertLogRecords(cm.records, + [{'name': 'foo'}, {'name': 'foo.bar'}]) + # The outer catchall caught the quux log + self.assertEqual(outer_cm.output, ["WARNING:quux:3"]) + + def testAssertLogsPerLogger(self): + self.checkAssertLogsPerLogger(logging.getLogger('foo')) + self.checkAssertLogsPerLogger('foo') + + def testAssertLogsFailureNoLogs(self): + # Failure due to no logs + with self.assertNoStderr(): + with self.assertRaises(self.failureException): + with self.assertLogs(): + pass + + def testAssertLogsFailureLevelTooHigh(self): + # Failure due to level too high + with self.assertNoStderr(): + with self.assertRaises(self.failureException): + with self.assertLogs(level='WARNING'): + log_foo.info("1") + + def testAssertLogsFailureMismatchingLogger(self): + # Failure due to mismatching logger (and the logged message is + # passed through) + with self.assertLogs('quux', level='ERROR'): + with self.assertRaises(self.failureException): + with self.assertLogs('foo'): + log_quux.error("1") + def testDeprecatedMethodNames(self): """ Test that the deprecated methods raise a DeprecationWarning. See #9424. @@ -1313,3 +1530,33 @@ test case with support.disable_gc(): del case self.assertFalse(wr()) + + def test_no_exception_leak(self): + # Issue #19880: TestCase.run() should not keep a reference + # to the exception + class MyException(Exception): + ninstance = 0 + + def __init__(self): + MyException.ninstance += 1 + Exception.__init__(self) + + def __del__(self): + MyException.ninstance -= 1 + + class TestCase(unittest.TestCase): + def test1(self): + raise MyException() + + @unittest.expectedFailure + def test2(self): + raise MyException() + + for method_name in ('test1', 'test2'): + testcase = TestCase(method_name) + testcase.run() + self.assertEqual(MyException.ninstance, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_discovery.py b/Lib/unittest/test/test_discovery.py index ccc7db249e4..6b7b1280f8b 100644 --- a/Lib/unittest/test/test_discovery.py +++ b/Lib/unittest/test/test_discovery.py @@ -1,12 +1,15 @@ import os import re import sys +import types +import builtins +from test import support import unittest class TestableTestProgram(unittest.TestProgram): - module = '__main__' + module = None exit = True defaultTest = failfast = catchbreak = buffer = None verbosity = 1 @@ -46,9 +49,9 @@ class TestDiscovery(unittest.TestCase): def restore_isdir(): os.path.isdir = original_isdir - path_lists = [['test1.py', 'test2.py', 'not_a_test.py', 'test_dir', + path_lists = [['test2.py', 'test1.py', 'not_a_test.py', 'test_dir', 'test.foo', 'test-not-a-module.py', 'another_dir'], - ['test3.py', 'test4.py', ]] + ['test4.py', 'test3.py', ]] os.listdir = lambda path: path_lists.pop(0) self.addCleanup(restore_listdir) @@ -70,6 +73,8 @@ class TestDiscovery(unittest.TestCase): loader._top_level_dir = top_level suite = list(loader._find_tests(top_level, 'test*.py')) + # 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 @@ -132,6 +137,7 @@ class TestDiscovery(unittest.TestCase): # and directly from the test_directory2 package self.assertEqual(suite, ['load_tests', 'test_directory2' + ' module tests']) + # The test module paths should be sorted for reliable execution order self.assertEqual(Module.paths, ['test_directory', 'test_directory2']) # load_tests should have been called once with loader, tests and pattern @@ -169,7 +175,7 @@ class TestDiscovery(unittest.TestCase): self.addCleanup(restore_isdir) _find_tests_args = [] - def _find_tests(start_dir, pattern): + def _find_tests(start_dir, pattern, namespace=None): _find_tests_args.append((start_dir, pattern)) return ['tests'] loader._find_tests = _find_tests @@ -184,11 +190,9 @@ class TestDiscovery(unittest.TestCase): self.assertEqual(_find_tests_args, [(start_dir, 'pattern')]) self.assertIn(top_level_dir, sys.path) - def test_discover_with_modules_that_fail_to_import(self): - loader = unittest.TestLoader() - + def setup_import_issue_tests(self, fakefile): listdir = os.listdir - os.listdir = lambda _: ['test_this_does_not_exist.py'] + os.listdir = lambda _: [fakefile] isfile = os.path.isfile os.path.isfile = lambda _: True orig_sys_path = sys.path[:] @@ -198,6 +202,11 @@ class TestDiscovery(unittest.TestCase): sys.path[:] = orig_sys_path self.addCleanup(restore) + def test_discover_with_modules_that_fail_to_import(self): + loader = unittest.TestLoader() + + self.setup_import_issue_tests('test_this_does_not_exist.py') + suite = loader.discover('.') self.assertIn(os.getcwd(), sys.path) self.assertEqual(suite.countTestCases(), 1) @@ -206,62 +215,74 @@ class TestDiscovery(unittest.TestCase): with self.assertRaises(ImportError): test.test_this_does_not_exist() + def test_discover_with_module_that_raises_SkipTest_on_import(self): + loader = unittest.TestLoader() + + def _get_module_from_name(name): + raise unittest.SkipTest('skipperoo') + loader._get_module_from_name = _get_module_from_name + + self.setup_import_issue_tests('test_skip_dummy.py') + + suite = loader.discover('.') + self.assertEqual(suite.countTestCases(), 1) + + result = unittest.TestResult() + suite.run(result) + self.assertEqual(len(result.skipped), 1) + def test_command_line_handling_parseArgs(self): program = TestableTestProgram() args = [] - def do_discovery(argv): - args.extend(argv) - program._do_discovery = do_discovery + program._do_discovery = args.append program.parseArgs(['something', 'discover']) - self.assertEqual(args, []) + self.assertEqual(args, [[]]) + args[:] = [] program.parseArgs(['something', 'discover', 'foo', 'bar']) - self.assertEqual(args, ['foo', 'bar']) + self.assertEqual(args, [['foo', 'bar']]) def test_command_line_handling_discover_by_default(self): program = TestableTestProgram() - program.module = None - self.called = False - def do_discovery(argv): - self.called = True - self.assertEqual(argv, []) - program._do_discovery = do_discovery + args = [] + program._do_discovery = args.append program.parseArgs(['something']) - self.assertTrue(self.called) + self.assertEqual(args, [[]]) + self.assertEqual(program.verbosity, 1) + self.assertIs(program.buffer, False) + self.assertIs(program.catchbreak, False) + self.assertIs(program.failfast, False) def test_command_line_handling_discover_by_default_with_options(self): program = TestableTestProgram() - program.module = None - args = ['something', '-v', '-b', '-v', '-c', '-f'] - self.called = False - def do_discovery(argv): - self.called = True - self.assertEqual(argv, args[1:]) - program._do_discovery = do_discovery - program.parseArgs(args) - self.assertTrue(self.called) + args = [] + program._do_discovery = args.append + program.parseArgs(['something', '-v', '-b', '-v', '-c', '-f']) + self.assertEqual(args, [[]]) + self.assertEqual(program.verbosity, 2) + self.assertIs(program.buffer, True) + self.assertIs(program.catchbreak, True) + self.assertIs(program.failfast, True) def test_command_line_handling_do_discovery_too_many_arguments(self): - class Stop(Exception): - pass - def usageExit(): - raise Stop - program = TestableTestProgram() - program.usageExit = usageExit program.testLoader = None - with self.assertRaises(Stop): + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: # too many args program._do_discovery(['one', 'two', 'three', 'four']) + self.assertEqual(cm.exception.args, (2,)) + self.assertIn('usage:', stderr.getvalue()) def test_command_line_handling_do_discovery_uses_default_loader(self): program = object.__new__(unittest.TestProgram) + program._initArgParsers() class Loader(object): args = [] @@ -417,7 +438,7 @@ class TestDiscovery(unittest.TestCase): expectedPath = os.path.abspath(os.path.dirname(unittest.test.__file__)) self.wasRun = False - def _find_tests(start_dir, pattern): + def _find_tests(start_dir, pattern, namespace=None): self.wasRun = True self.assertEqual(start_dir, expectedPath) return tests @@ -427,5 +448,79 @@ class TestDiscovery(unittest.TestCase): self.assertEqual(suite._tests, tests) + def test_discovery_from_dotted_path_builtin_modules(self): + + loader = unittest.TestLoader() + + listdir = os.listdir + os.listdir = lambda _: ['test_this_does_not_exist.py'] + isfile = os.path.isfile + isdir = os.path.isdir + os.path.isdir = lambda _: False + orig_sys_path = sys.path[:] + def restore(): + os.path.isfile = isfile + os.path.isdir = isdir + os.listdir = listdir + sys.path[:] = orig_sys_path + self.addCleanup(restore) + + with self.assertRaises(TypeError) as cm: + loader.discover('sys') + self.assertEqual(str(cm.exception), + 'Can not use builtin modules ' + 'as dotted module names') + + def test_discovery_from_dotted_namespace_packages(self): + loader = unittest.TestLoader() + + orig_import = __import__ + package = types.ModuleType('package') + package.__path__ = ['/a', '/b'] + package.__spec__ = types.SimpleNamespace( + loader=None, + submodule_search_locations=['/a', '/b'] + ) + + def _import(packagename, *args, **kwargs): + sys.modules[packagename] = package + return package + + def cleanup(): + builtins.__import__ = orig_import + self.addCleanup(cleanup) + builtins.__import__ = _import + + _find_tests_args = [] + def _find_tests(start_dir, pattern, namespace=None): + _find_tests_args.append((start_dir, pattern)) + return ['%s/tests' % start_dir] + + loader._find_tests = _find_tests + loader.suiteClass = list + suite = loader.discover('package') + self.assertEqual(suite, ['/a/tests', '/b/tests']) + + def test_discovery_failed_discovery(self): + loader = unittest.TestLoader() + package = types.ModuleType('package') + orig_import = __import__ + + def _import(packagename, *args, **kwargs): + sys.modules[packagename] = package + return package + + def cleanup(): + builtins.__import__ = orig_import + self.addCleanup(cleanup) + builtins.__import__ = _import + + with self.assertRaises(TypeError) as cm: + loader.discover('package') + self.assertEqual(str(cm.exception), + 'don\'t know how to discover from {!r}' + .format(package)) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/unittest/test/test_functiontestcase.py b/Lib/unittest/test/test_functiontestcase.py index 9ce5ee3556e..d7fe07a53b9 100644 --- a/Lib/unittest/test/test_functiontestcase.py +++ b/Lib/unittest/test/test_functiontestcase.py @@ -142,3 +142,7 @@ class Test_FunctionTestCase(unittest.TestCase): test = unittest.FunctionTestCase(lambda: None, description=desc) self.assertEqual(test.shortDescription(), "this tests foo") + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_loader.py b/Lib/unittest/test/test_loader.py index fcd2e076245..b62a1b5c541 100644 --- a/Lib/unittest/test/test_loader.py +++ b/Lib/unittest/test/test_loader.py @@ -1306,3 +1306,7 @@ class Test_TestLoader(unittest.TestCase): def test_suiteClass__default_value(self): loader = unittest.TestLoader() self.assertIs(loader.suiteClass, unittest.TestSuite) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py index 8a4b3fad581..32942980306 100644 --- a/Lib/unittest/test/test_program.py +++ b/Lib/unittest/test/test_program.py @@ -2,6 +2,7 @@ import io import os import sys +from test import support import unittest @@ -64,6 +65,41 @@ class Test_TestProgram(unittest.TestCase): return self.suiteClass( [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + def loadTestsFromNames(self, names, module): + return self.suiteClass( + [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + + def test_defaultTest_with_string(self): + class FakeRunner(object): + def run(self, test): + self.test = test + return True + + old_argv = sys.argv + sys.argv = ['faketest'] + runner = FakeRunner() + program = unittest.TestProgram(testRunner=runner, exit=False, + defaultTest='unittest.test', + testLoader=self.FooBarLoader()) + sys.argv = old_argv + self.assertEqual(('unittest.test',), program.testNames) + + def test_defaultTest_with_iterable(self): + class FakeRunner(object): + def run(self, test): + self.test = test + return True + + old_argv = sys.argv + sys.argv = ['faketest'] + runner = FakeRunner() + program = unittest.TestProgram( + testRunner=runner, exit=False, + defaultTest=['unittest.test', 'unittest.test2'], + testLoader=self.FooBarLoader()) + sys.argv = old_argv + self.assertEqual(['unittest.test', 'unittest.test2'], + program.testNames) def test_NonExit(self): program = unittest.main(exit=False, @@ -151,20 +187,38 @@ class TestCommandLineArgs(unittest.TestCase): if attr == 'catch' and not hasInstallHandler: continue + setattr(program, attr, None) + program.parseArgs([None]) + self.assertIs(getattr(program, attr), False) + + false = [] + setattr(program, attr, false) + program.parseArgs([None]) + self.assertIs(getattr(program, attr), false) + + true = [42] + setattr(program, attr, true) + program.parseArgs([None]) + self.assertIs(getattr(program, attr), true) + short_opt = '-%s' % arg[0] long_opt = '--%s' % arg for opt in short_opt, long_opt: setattr(program, attr, None) - - program.parseArgs([None, opt]) - self.assertTrue(getattr(program, attr)) - - for opt in short_opt, long_opt: - not_none = object() - setattr(program, attr, not_none) - program.parseArgs([None, opt]) - self.assertEqual(getattr(program, attr), not_none) + self.assertIs(getattr(program, attr), True) + + setattr(program, attr, False) + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: + program.parseArgs([None, opt]) + self.assertEqual(cm.exception.args, (2,)) + + setattr(program, attr, True) + with support.captured_stderr() as stderr, \ + self.assertRaises(SystemExit) as cm: + program.parseArgs([None, opt]) + self.assertEqual(cm.exception.args, (2,)) def testWarning(self): """Test the warnings argument""" diff --git a/Lib/unittest/test/test_result.py b/Lib/unittest/test/test_result.py index 7d40725cf2c..489fe177546 100644 --- a/Lib/unittest/test/test_result.py +++ b/Lib/unittest/test/test_result.py @@ -227,6 +227,40 @@ class Test_TestResult(unittest.TestCase): self.assertIs(test_case, test) self.assertIsInstance(formatted_exc, str) + def test_addSubTest(self): + class Foo(unittest.TestCase): + def test_1(self): + nonlocal subtest + with self.subTest(foo=1): + subtest = self._subtest + try: + 1/0 + except ZeroDivisionError: + exc_info_tuple = sys.exc_info() + # Register an error by hand (to check the API) + result.addSubTest(test, subtest, exc_info_tuple) + # Now trigger a failure + self.fail("some recognizable failure") + + subtest = None + test = Foo('test_1') + result = unittest.TestResult() + + test.run(result) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.errors[0] + self.assertIs(test_case, subtest) + self.assertIn("ZeroDivisionError", formatted_exc) + test_case, formatted_exc = result.failures[0] + self.assertIs(test_case, subtest) + self.assertIn("some recognizable failure", formatted_exc) + def testGetDescriptionWithoutDocstring(self): result = unittest.TextTestResult(None, True, 1) self.assertEqual( @@ -234,6 +268,37 @@ class Test_TestResult(unittest.TestCase): 'testGetDescriptionWithoutDocstring (' + __name__ + '.Test_TestResult)') + def testGetSubTestDescriptionWithoutDocstring(self): + with self.subTest(foo=1, bar=2): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult) (bar=2, foo=1)') + with self.subTest('some message'): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetSubTestDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult) [some message]') + + def testGetSubTestDescriptionWithoutDocstringAndParams(self): + with self.subTest(): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetSubTestDescriptionWithoutDocstringAndParams ' + '(' + __name__ + '.Test_TestResult) (<subtest>)') + + def testGetNestedSubTestDescriptionWithoutDocstring(self): + with self.subTest(foo=1): + with self.subTest(bar=2): + result = unittest.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self._subtest), + 'testGetNestedSubTestDescriptionWithoutDocstring ' + '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)') + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def testGetDescriptionWithOneLineDocstring(self): @@ -247,6 +312,18 @@ class Test_TestResult(unittest.TestCase): @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") + def testGetSubTestDescriptionWithOneLineDocstring(self): + """Tests getDescription() for a method with a docstring.""" + result = unittest.TextTestResult(None, True, 1) + with self.subTest(foo=1, bar=2): + self.assertEqual( + result.getDescription(self._subtest), + ('testGetSubTestDescriptionWithOneLineDocstring ' + '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n' + 'Tests getDescription() for a method with a docstring.')) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") def testGetDescriptionWithMultiLineDocstring(self): """Tests getDescription() for a method with a longer docstring. The second line of the docstring. @@ -259,6 +336,21 @@ class Test_TestResult(unittest.TestCase): 'Tests getDescription() for a method with a longer ' 'docstring.')) + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def testGetSubTestDescriptionWithMultiLineDocstring(self): + """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. + """ + result = unittest.TextTestResult(None, True, 1) + with self.subTest(foo=1, bar=2): + self.assertEqual( + result.getDescription(self._subtest), + ('testGetSubTestDescriptionWithMultiLineDocstring ' + '(' + __name__ + '.Test_TestResult) (bar=2, foo=1)\n' + 'Tests getDescription() for a method with a longer ' + 'docstring.')) + def testStackFrameTrimming(self): class Frame(object): class tb_frame(object): diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py index e22e6bc279e..ef1c1af9f10 100644 --- a/Lib/unittest/test/test_runner.py +++ b/Lib/unittest/test/test_runner.py @@ -5,6 +5,7 @@ import pickle import subprocess import unittest +from unittest.case import _Outcome from .support import LoggingResult, ResultWithNoStartTestRunStopTestRun @@ -42,12 +43,8 @@ class TestCleanUp(unittest.TestCase): def testNothing(self): pass - class MockOutcome(object): - success = True - errors = [] - test = TestableTest('testNothing') - test._outcomeForDoCleanups = MockOutcome + outcome = test._outcome = _Outcome() exc1 = Exception('foo') exc2 = Exception('bar') @@ -61,9 +58,10 @@ class TestCleanUp(unittest.TestCase): test.addCleanup(cleanup2) self.assertFalse(test.doCleanups()) - self.assertFalse(MockOutcome.success) + self.assertFalse(outcome.success) - (Type1, instance1, _), (Type2, instance2, _) = reversed(MockOutcome.errors) + ((_, (Type1, instance1, _)), + (_, (Type2, instance2, _))) = reversed(outcome.errors) self.assertEqual((Type1, instance1), (Exception, exc1)) self.assertEqual((Type2, instance2), (Exception, exc2)) @@ -341,3 +339,7 @@ class Test_TextTestRunner(unittest.TestCase): f = io.StringIO() runner = unittest.TextTestRunner(f) self.assertTrue(runner.stream.stream is f) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_setups.py b/Lib/unittest/test/test_setups.py index b8d5aa41e94..392f95efc07 100644 --- a/Lib/unittest/test/test_setups.py +++ b/Lib/unittest/test/test_setups.py @@ -494,14 +494,13 @@ class TestSetups(unittest.TestCase): Test.__module__ = 'Module' sys.modules['Module'] = Module - _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) - suite = unittest.TestSuite() - suite.addTest(_suite) - messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') for phase, msg in enumerate(messages): + _suite = unittest.defaultTestLoader.loadTestsFromTestCase(Test) + suite = unittest.TestSuite([_suite]) with self.assertRaisesRegex(Exception, msg): suite.debug() + if __name__ == '__main__': unittest.main() diff --git a/Lib/unittest/test/test_skipping.py b/Lib/unittest/test/test_skipping.py index 952240eeede..314a7a4d0f8 100644 --- a/Lib/unittest/test/test_skipping.py +++ b/Lib/unittest/test/test_skipping.py @@ -29,6 +29,31 @@ class Test_TestSkipping(unittest.TestCase): self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(result.testsRun, 1) + def test_skipping_subtests(self): + class Foo(unittest.TestCase): + def test_skip_me(self): + with self.subTest(a=1): + with self.subTest(b=2): + self.skipTest("skip 1") + self.skipTest("skip 2") + self.skipTest("skip 3") + events = [] + result = LoggingResult(events) + test = Foo("test_skip_me") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'addSkip', + 'addSkip', 'stopTest']) + self.assertEqual(len(result.skipped), 3) + subtest, msg = result.skipped[0] + self.assertEqual(msg, "skip 1") + self.assertIsInstance(subtest, unittest.TestCase) + self.assertIsNot(subtest, test) + subtest, msg = result.skipped[1] + self.assertEqual(msg, "skip 2") + self.assertIsInstance(subtest, unittest.TestCase) + self.assertIsNot(subtest, test) + self.assertEqual(result.skipped[2], (test, "skip 3")) + def test_skipping_decorators(self): op_table = ((unittest.skipUnless, False, True), (unittest.skipIf, True, False)) @@ -95,6 +120,31 @@ class Test_TestSkipping(unittest.TestCase): self.assertEqual(result.expectedFailures[0][0], test) self.assertTrue(result.wasSuccessful()) + def test_expected_failure_subtests(self): + # A failure in any subtest counts as the expected failure of the + # whole test. + class Foo(unittest.TestCase): + @unittest.expectedFailure + def test_die(self): + with self.subTest(): + # This one succeeds + pass + with self.subTest(): + self.fail("help me!") + with self.subTest(): + # This one doesn't get executed + self.fail("shouldn't come here") + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addSubTestSuccess', + 'addExpectedFailure', 'stopTest']) + self.assertEqual(len(result.expectedFailures), 1) + self.assertIs(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + def test_unexpected_success(self): class Foo(unittest.TestCase): @unittest.expectedFailure @@ -108,7 +158,31 @@ class Test_TestSkipping(unittest.TestCase): ['startTest', 'addUnexpectedSuccess', 'stopTest']) self.assertFalse(result.failures) self.assertEqual(result.unexpectedSuccesses, [test]) - self.assertTrue(result.wasSuccessful()) + self.assertFalse(result.wasSuccessful()) + + def test_unexpected_success_subtests(self): + # Success in all subtests counts as the unexpected success of + # the whole test. + class Foo(unittest.TestCase): + @unittest.expectedFailure + def test_die(self): + with self.subTest(): + # This one succeeds + pass + with self.subTest(): + # So does this one + pass + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', + 'addSubTestSuccess', 'addSubTestSuccess', + 'addUnexpectedSuccess', 'stopTest']) + self.assertFalse(result.failures) + self.assertEqual(result.unexpectedSuccesses, [test]) + self.assertFalse(result.wasSuccessful()) def test_skip_doesnt_run_setup(self): class Foo(unittest.TestCase): @@ -147,3 +221,7 @@ class Test_TestSkipping(unittest.TestCase): suite = unittest.TestSuite([test]) suite.run(result) self.assertEqual(result.skipped, [(test, "testing")]) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/test_suite.py b/Lib/unittest/test/test_suite.py index 2db978ddb8a..38311ca4880 100644 --- a/Lib/unittest/test/test_suite.py +++ b/Lib/unittest/test/test_suite.py @@ -1,6 +1,8 @@ import unittest +import gc import sys +import weakref from .support import LoggingResult, TestEquality @@ -49,6 +51,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality): suite = unittest.TestSuite() self.assertEqual(suite.countTestCases(), 0) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 0) # "class TestSuite([tests])" # ... @@ -61,6 +66,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality): suite = unittest.TestSuite([]) self.assertEqual(suite.countTestCases(), 0) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 0) # "class TestSuite([tests])" # ... @@ -82,6 +90,14 @@ class Test_TestSuite(unittest.TestCase, TestEquality): suite_3 = unittest.TestSuite(set(suite_1)) self.assertEqual(suite_3.countTestCases(), 2) + # countTestCases() still works after tests are run + suite_1.run(unittest.TestResult()) + self.assertEqual(suite_1.countTestCases(), 2) + suite_2.run(unittest.TestResult()) + self.assertEqual(suite_2.countTestCases(), 2) + suite_3.run(unittest.TestResult()) + self.assertEqual(suite_3.countTestCases(), 2) + # "class TestSuite([tests])" # ... # "If tests is given, it must be an iterable of individual test cases @@ -97,6 +113,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality): suite = unittest.TestSuite(tests()) self.assertEqual(suite.countTestCases(), 2) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 2) ################################################################ ### /Tests for TestSuite.__init__ @@ -143,6 +162,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality): suite = unittest.TestSuite((test1, test2)) self.assertEqual(suite.countTestCases(), 2) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 2) # "Return the number of tests represented by the this test object. # ...this method is also implemented by the TestSuite class, which can @@ -160,6 +182,10 @@ class Test_TestSuite(unittest.TestCase, TestEquality): parent = unittest.TestSuite((test3, child, Test1('test1'))) self.assertEqual(parent.countTestCases(), 4) + # countTestCases() still works after tests are run + parent.run(unittest.TestResult()) + self.assertEqual(parent.countTestCases(), 4) + self.assertEqual(child.countTestCases(), 2) # "Run the tests associated with this suite, collecting the result into # the test result object passed as result." @@ -218,6 +244,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality): self.assertEqual(suite.countTestCases(), 1) self.assertEqual(list(suite), [test]) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 1) # "Add a ... TestSuite to the suite" def test_addTest__TestSuite(self): @@ -231,6 +260,9 @@ class Test_TestSuite(unittest.TestCase, TestEquality): self.assertEqual(suite.countTestCases(), 1) self.assertEqual(list(suite), [suite_2]) + # countTestCases() still works after tests are run + suite.run(unittest.TestResult()) + self.assertEqual(suite.countTestCases(), 1) # "Add all the tests from an iterable of TestCase and TestSuite # instances to this test suite." @@ -300,7 +332,54 @@ class Test_TestSuite(unittest.TestCase, TestEquality): # when the bug is fixed this line will not crash suite.run(unittest.TestResult()) + def test_remove_test_at_index(self): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + suite = unittest.TestSuite() + + suite._tests = [1, 2, 3] + suite._removeTestAtIndex(1) + + self.assertEqual([1, None, 3], suite._tests) + + def test_remove_test_at_index_not_indexable(self): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + suite = unittest.TestSuite() + suite._tests = None + + # if _removeAtIndex raises for noniterables this next line will break + suite._removeTestAtIndex(2) + + def assert_garbage_collect_test_after_run(self, TestSuiteClass): + if not unittest.BaseTestSuite._cleanup: + raise unittest.SkipTest("Suite cleanup is disabled") + + class Foo(unittest.TestCase): + def test_nothing(self): + pass + + test = Foo('test_nothing') + wref = weakref.ref(test) + + suite = TestSuiteClass([wref()]) + suite.run(unittest.TestResult()) + + del test + + # for the benefit of non-reference counting implementations + gc.collect() + self.assertEqual(suite._tests, [None]) + self.assertIsNone(wref()) + + def test_garbage_collect_test_after_run_BaseTestSuite(self): + self.assert_garbage_collect_test_after_run(unittest.BaseTestSuite) + + def test_garbage_collect_test_after_run_TestSuite(self): + self.assert_garbage_collect_test_after_run(unittest.TestSuite) def test_basetestsuite(self): class Test(unittest.TestCase): @@ -343,6 +422,7 @@ class Test_TestSuite(unittest.TestCase, TestEquality): self.assertEqual(len(result.errors), 1) self.assertEqual(len(result.failures), 0) self.assertEqual(result.testsRun, 2) + self.assertEqual(suite.countTestCases(), 2) def test_overriding_call(self): @@ -363,6 +443,5 @@ class Test_TestSuite(unittest.TestCase, TestEquality): self.assertFalse(result._testRunEntered) - if __name__ == '__main__': unittest.main() diff --git a/Lib/unittest/test/testmock/__main__.py b/Lib/unittest/test/testmock/__main__.py new file mode 100644 index 00000000000..45c633a4ee4 --- /dev/null +++ b/Lib/unittest/test/testmock/__main__.py @@ -0,0 +1,18 @@ +import os +import unittest + + +def load_tests(loader, standard_tests, pattern): + # top level directory cached on loader instance + this_dir = os.path.dirname(__file__) + pattern = pattern or "test*.py" + # We are inside unittest.test.testmock, so the top-level is three notches up + top_level_dir = os.path.dirname(os.path.dirname(os.path.dirname(this_dir))) + package_tests = loader.discover(start_dir=this_dir, pattern=pattern, + top_level_dir=top_level_dir) + standard_tests.addTests(package_tests) + return standard_tests + + +if __name__ == '__main__': + unittest.main() diff --git a/Lib/unittest/test/testmock/testcallable.py b/Lib/unittest/test/testmock/testcallable.py index 7b2dd003ea2..5390a4e10f3 100644 --- a/Lib/unittest/test/testmock/testcallable.py +++ b/Lib/unittest/test/testmock/testcallable.py @@ -145,3 +145,7 @@ class TestCallable(unittest.TestCase): mock.wibble.assert_called_once_with() self.assertRaises(TypeError, mock.wibble, 'some', 'args') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/unittest/test/testmock/testhelpers.py b/Lib/unittest/test/testmock/testhelpers.py index a362a2f7848..1dbc0b64ba5 100644 --- a/Lib/unittest/test/testmock/testhelpers.py +++ b/Lib/unittest/test/testmock/testhelpers.py @@ -337,9 +337,10 @@ class SpecSignatureTest(unittest.TestCase): def test_basic(self): - for spec in (SomeClass, SomeClass()): - mock = create_autospec(spec) - self._check_someclass_mock(mock) + mock = create_autospec(SomeClass) + self._check_someclass_mock(mock) + mock = create_autospec(SomeClass()) + self._check_someclass_mock(mock) def test_create_autospec_return_value(self): @@ -576,10 +577,10 @@ class SpecSignatureTest(unittest.TestCase): def test_spec_inheritance_for_classes(self): class Foo(object): - def a(self): + def a(self, x): pass class Bar(object): - def f(self): + def f(self, y): pass class_mock = create_autospec(Foo) @@ -587,26 +588,30 @@ class SpecSignatureTest(unittest.TestCase): self.assertIsNot(class_mock, class_mock()) for this_mock in class_mock, class_mock(): - this_mock.a() - this_mock.a.assert_called_with() - self.assertRaises(TypeError, this_mock.a, 'foo') + this_mock.a(x=5) + this_mock.a.assert_called_with(x=5) + this_mock.a.assert_called_with(5) + self.assertRaises(TypeError, this_mock.a, 'foo', 'bar') self.assertRaises(AttributeError, getattr, this_mock, 'b') instance_mock = create_autospec(Foo()) - instance_mock.a() - instance_mock.a.assert_called_with() - self.assertRaises(TypeError, instance_mock.a, 'foo') + instance_mock.a(5) + instance_mock.a.assert_called_with(5) + instance_mock.a.assert_called_with(x=5) + self.assertRaises(TypeError, instance_mock.a, 'foo', 'bar') self.assertRaises(AttributeError, getattr, instance_mock, 'b') # The return value isn't isn't callable self.assertRaises(TypeError, instance_mock) - instance_mock.Bar.f() - instance_mock.Bar.f.assert_called_with() + instance_mock.Bar.f(6) + instance_mock.Bar.f.assert_called_with(6) + instance_mock.Bar.f.assert_called_with(y=6) self.assertRaises(AttributeError, getattr, instance_mock.Bar, 'g') - instance_mock.Bar().f() - instance_mock.Bar().f.assert_called_with() + instance_mock.Bar().f(6) + instance_mock.Bar().f.assert_called_with(6) + instance_mock.Bar().f.assert_called_with(y=6) self.assertRaises(AttributeError, getattr, instance_mock.Bar(), 'g') @@ -663,12 +668,15 @@ class SpecSignatureTest(unittest.TestCase): self.assertRaises(TypeError, mock) mock(1, 2) mock.assert_called_with(1, 2) + mock.assert_called_with(1, b=2) + mock.assert_called_with(a=1, b=2) f.f = f mock = create_autospec(f) self.assertRaises(TypeError, mock.f) mock.f(3, 4) mock.f.assert_called_with(3, 4) + mock.f.assert_called_with(a=3, b=4) def test_skip_attributeerrors(self): @@ -704,9 +712,13 @@ class SpecSignatureTest(unittest.TestCase): self.assertRaises(TypeError, mock) mock(1) mock.assert_called_once_with(1) + mock.assert_called_once_with(a=1) + self.assertRaises(AssertionError, mock.assert_called_once_with, 2) mock(4, 5) mock.assert_called_with(4, 5) + mock.assert_called_with(a=4, b=5) + self.assertRaises(AssertionError, mock.assert_called_with, a=5, b=4) def test_class_with_no_init(self): @@ -719,24 +731,27 @@ class SpecSignatureTest(unittest.TestCase): def test_signature_callable(self): class Callable(object): - def __init__(self): + def __init__(self, x, y): pass def __call__(self, a): pass mock = create_autospec(Callable) - mock() - mock.assert_called_once_with() + mock(1, 2) + mock.assert_called_once_with(1, 2) + mock.assert_called_once_with(x=1, y=2) self.assertRaises(TypeError, mock, 'a') - instance = mock() + instance = mock(1, 2) self.assertRaises(TypeError, instance) instance(a='a') + instance.assert_called_once_with('a') instance.assert_called_once_with(a='a') instance('a') instance.assert_called_with('a') + instance.assert_called_with(a='a') - mock = create_autospec(Callable()) + mock = create_autospec(Callable(1, 2)) mock(a='a') mock.assert_called_once_with(a='a') self.assertRaises(TypeError, mock) @@ -779,7 +794,11 @@ class SpecSignatureTest(unittest.TestCase): pass a = create_autospec(Foo) + a.f(10) + a.f.assert_called_with(10) + a.f.assert_called_with(self=10) a.f(self=10) + a.f.assert_called_with(10) a.f.assert_called_with(self=10) diff --git a/Lib/unittest/test/testmock/testmock.py b/Lib/unittest/test/testmock/testmock.py index cef5405fe95..20cc6541e66 100644 --- a/Lib/unittest/test/testmock/testmock.py +++ b/Lib/unittest/test/testmock/testmock.py @@ -25,6 +25,18 @@ class Iter(object): __next__ = next +class Something(object): + def meth(self, a, b, c, d=None): + pass + + @classmethod + def cmeth(cls, a, b, c, d=None): + pass + + @staticmethod + def smeth(a, b, c, d=None): + pass + class MockTest(unittest.TestCase): @@ -273,6 +285,43 @@ class MockTest(unittest.TestCase): mock.assert_called_with(1, 2, 3, a='fish', b='nothing') + def test_assert_called_with_function_spec(self): + def f(a, b, c, d=None): + pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock.assert_called_with(1, 2, 3) + mock.assert_called_with(a=1, b=2, c=3) + self.assertRaises(AssertionError, mock.assert_called_with, + 1, b=3, c=2) + # Expected call doesn't match the spec's signature + with self.assertRaises(AssertionError) as cm: + mock.assert_called_with(e=8) + self.assertIsInstance(cm.exception.__cause__, TypeError) + + + def test_assert_called_with_method_spec(self): + def _check(mock): + mock(1, b=2, c=3) + mock.assert_called_with(1, 2, 3) + mock.assert_called_with(a=1, b=2, c=3) + self.assertRaises(AssertionError, mock.assert_called_with, + 1, b=3, c=2) + + mock = Mock(spec=Something().meth) + _check(mock) + mock = Mock(spec=Something.cmeth) + _check(mock) + mock = Mock(spec=Something().cmeth) + _check(mock) + mock = Mock(spec=Something.smeth) + _check(mock) + mock = Mock(spec=Something().smeth) + _check(mock) + + def test_assert_called_once_with(self): mock = Mock() mock() @@ -297,6 +346,29 @@ class MockTest(unittest.TestCase): ) + def test_assert_called_once_with_function_spec(self): + def f(a, b, c, d=None): + pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock.assert_called_once_with(1, 2, 3) + mock.assert_called_once_with(a=1, b=2, c=3) + self.assertRaises(AssertionError, mock.assert_called_once_with, + 1, b=3, c=2) + # Expected call doesn't match the spec's signature + with self.assertRaises(AssertionError) as cm: + mock.assert_called_once_with(e=8) + self.assertIsInstance(cm.exception.__cause__, TypeError) + # Mock called more than once => always fails + mock(4, 5, 6) + self.assertRaises(AssertionError, mock.assert_called_once_with, + 1, 2, 3) + self.assertRaises(AssertionError, mock.assert_called_once_with, + 4, 5, 6) + + def test_attribute_access_returns_mocks(self): mock = Mock() something = mock.something @@ -995,6 +1067,39 @@ class MockTest(unittest.TestCase): ) + def test_assert_has_calls_with_function_spec(self): + def f(a, b, c, d=None): + pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock(4, 5, c=6, d=7) + mock(10, 11, c=12) + calls = [ + ('', (1, 2, 3), {}), + ('', (4, 5, 6), {'d': 7}), + ((10, 11, 12), {}), + ] + mock.assert_has_calls(calls) + mock.assert_has_calls(calls, any_order=True) + mock.assert_has_calls(calls[1:]) + mock.assert_has_calls(calls[1:], any_order=True) + mock.assert_has_calls(calls[:-1]) + mock.assert_has_calls(calls[:-1], any_order=True) + # Reversed order + calls = list(reversed(calls)) + with self.assertRaises(AssertionError): + mock.assert_has_calls(calls) + mock.assert_has_calls(calls, any_order=True) + with self.assertRaises(AssertionError): + mock.assert_has_calls(calls[1:]) + mock.assert_has_calls(calls[1:], any_order=True) + with self.assertRaises(AssertionError): + mock.assert_has_calls(calls[:-1]) + mock.assert_has_calls(calls[:-1], any_order=True) + + def test_assert_any_call(self): mock = Mock() mock(1, 2) @@ -1021,6 +1126,26 @@ class MockTest(unittest.TestCase): ) + def test_assert_any_call_with_function_spec(self): + def f(a, b, c, d=None): + pass + + mock = Mock(spec=f) + + mock(1, b=2, c=3) + mock(4, 5, c=6, d=7) + mock.assert_any_call(1, 2, 3) + mock.assert_any_call(a=1, b=2, c=3) + mock.assert_any_call(4, 5, 6, 7) + mock.assert_any_call(a=4, b=5, c=6, d=7) + self.assertRaises(AssertionError, mock.assert_any_call, + 1, b=3, c=2) + # Expected call doesn't match the spec's signature + with self.assertRaises(AssertionError) as cm: + mock.assert_any_call(e=8) + self.assertIsInstance(cm.exception.__cause__, TypeError) + + def test_mock_calls_create_autospec(self): def f(a, b): pass @@ -1177,20 +1302,6 @@ class MockTest(unittest.TestCase): self.assertEqual(m.method_calls, []) - def test_attribute_deletion(self): - # this behaviour isn't *useful*, but at least it's now tested... - for Klass in Mock, MagicMock, NonCallableMagicMock, NonCallableMock: - m = Klass() - original = m.foo - m.foo = 3 - del m.foo - self.assertEqual(m.foo, original) - - new = m.foo = Mock() - del m.foo - self.assertEqual(m.foo, new) - - def test_mock_parents(self): for Klass in Mock, MagicMock: m = Klass() @@ -1254,7 +1365,8 @@ class MockTest(unittest.TestCase): def test_attribute_deletion(self): - for mock in Mock(), MagicMock(): + for mock in (Mock(), MagicMock(), NonCallableMagicMock(), + NonCallableMock()): self.assertTrue(hasattr(mock, 'm')) del mock.m @@ -1274,6 +1386,5 @@ class MockTest(unittest.TestCase): mock.foo - if __name__ == '__main__': unittest.main() diff --git a/Lib/unittest/test/testmock/testpatch.py b/Lib/unittest/test/testmock/testpatch.py index c1091b4e9b3..c1bc34fa8c9 100644 --- a/Lib/unittest/test/testmock/testpatch.py +++ b/Lib/unittest/test/testmock/testpatch.py @@ -1780,6 +1780,5 @@ class PatchTest(unittest.TestCase): self.assertIs(os.path, path) - if __name__ == '__main__': unittest.main() diff --git a/Lib/unittest/test/testmock/testwith.py b/Lib/unittest/test/testmock/testwith.py index 0a0cfad120a..f54e051e945 100644 --- a/Lib/unittest/test/testmock/testwith.py +++ b/Lib/unittest/test/testmock/testwith.py @@ -172,5 +172,88 @@ class TestMockOpen(unittest.TestCase): self.assertEqual(result, 'foo') + def test_readline_data(self): + # Check that readline will return all the lines from the fake file + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = h.readline() + line2 = h.readline() + line3 = h.readline() + self.assertEqual(line1, 'foo\n') + self.assertEqual(line2, 'bar\n') + self.assertEqual(line3, 'baz\n') + + # Check that we properly emulate a file that doesn't end in a newline + mock = mock_open(read_data='foo') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readline() + self.assertEqual(result, 'foo') + + + def test_readlines_data(self): + # Test that emulating a file that ends in a newline character works + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readlines() + self.assertEqual(result, ['foo\n', 'bar\n', 'baz\n']) + + # Test that files without a final newline will also be correctly + # emulated + mock = mock_open(read_data='foo\nbar\nbaz') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.readlines() + + self.assertEqual(result, ['foo\n', 'bar\n', 'baz']) + + + def test_mock_open_read_with_argument(self): + # At one point calling read with an argument was broken + # for mocks returned by mock_open + some_data = 'foo\nbar\nbaz' + mock = mock_open(read_data=some_data) + self.assertEqual(mock().read(10), some_data) + + + def test_interleaved_reads(self): + # Test that calling read, readline, and readlines pulls data + # sequentially from the data we preload with + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = h.readline() + rest = h.readlines() + self.assertEqual(line1, 'foo\n') + self.assertEqual(rest, ['bar\n', 'baz\n']) + + mock = mock_open(read_data='foo\nbar\nbaz\n') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + line1 = h.readline() + rest = h.read() + self.assertEqual(line1, 'foo\n') + self.assertEqual(rest, 'bar\nbaz\n') + + + def test_overriding_return_values(self): + mock = mock_open(read_data='foo') + handle = mock() + + handle.read.return_value = 'bar' + handle.readline.return_value = 'bar' + handle.readlines.return_value = ['bar'] + + self.assertEqual(handle.read(), 'bar') + self.assertEqual(handle.readline(), 'bar') + self.assertEqual(handle.readlines(), ['bar']) + + # call repeatedly to check that a StopIteration is not propagated + self.assertEqual(handle.readline(), 'bar') + self.assertEqual(handle.readline(), 'bar') + + if __name__ == '__main__': unittest.main() |