summaryrefslogtreecommitdiffstats
path: root/Lib/unittest
diff options
context:
space:
mode:
authorGregory P. Smith <greg@krypto.org>2022-01-27 04:39:15 (GMT)
committerGitHub <noreply@github.com>2022-01-27 04:39:15 (GMT)
commitb50322d20337ca468f2070eedb051a16ee1eba94 (patch)
treeba478b8fe2b91b583cec640d518f9643fb5b6f7d /Lib/unittest
parent9f0881476e0113d3a35e0ffa96649b9276dd75c5 (diff)
downloadcpython-b50322d20337ca468f2070eedb051a16ee1eba94.zip
cpython-b50322d20337ca468f2070eedb051a16ee1eba94.tar.gz
cpython-b50322d20337ca468f2070eedb051a16ee1eba94.tar.bz2
bpo-45162: Revert "Remove many old deprecated unittest features" (GH-30935)
Revert "bpo-45162: Remove many old deprecated unittest features (GH-28268)" This reverts commit b0a6ede3d0bd6fa4ffe413ab4dfc1059201df25b. We're deferring this change until 3.12 while upstream projects that use the legacy assertion method names are fixed. See the issue for links to the discussion. Many upstream projects now have issues and PRs filed.
Diffstat (limited to 'Lib/unittest')
-rw-r--r--Lib/unittest/__init__.py3
-rw-r--r--Lib/unittest/case.py50
-rw-r--r--Lib/unittest/loader.py24
-rw-r--r--Lib/unittest/runner.py9
-rw-r--r--Lib/unittest/test/_test_warnings.py11
-rw-r--r--Lib/unittest/test/test_assertions.py9
-rw-r--r--Lib/unittest/test/test_case.py69
-rw-r--r--Lib/unittest/test/test_loader.py147
-rw-r--r--Lib/unittest/test/test_program.py16
-rw-r--r--Lib/unittest/test/test_runner.py10
10 files changed, 323 insertions, 25 deletions
diff --git a/Lib/unittest/__init__.py b/Lib/unittest/__init__.py
index 540433d..4b18488 100644
--- a/Lib/unittest/__init__.py
+++ b/Lib/unittest/__init__.py
@@ -68,6 +68,9 @@ from .signals import installHandler, registerResult, removeResult, removeHandler
# IsolatedAsyncioTestCase will be imported lazily.
from .loader import makeSuite, getTestCaseNames, findTestCases
+# deprecated
+_TextTestResult = TextTestResult
+
# There are no tests here, so don't try to run anything discovered from
# introspecting the symbols (e.g. FunctionTestCase). Instead, all our
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index f106765..0d55020 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -1139,6 +1139,35 @@ class TestCase(object):
standardMsg = self._truncateMessage(standardMsg, diff)
self.fail(self._formatMessage(msg, standardMsg))
+ def assertDictContainsSubset(self, subset, dictionary, msg=None):
+ """Checks whether dictionary is a superset of subset."""
+ warnings.warn('assertDictContainsSubset is deprecated',
+ DeprecationWarning)
+ missing = []
+ mismatched = []
+ for key, value in subset.items():
+ if key not in dictionary:
+ missing.append(key)
+ elif value != dictionary[key]:
+ mismatched.append('%s, expected: %s, actual: %s' %
+ (safe_repr(key), safe_repr(value),
+ safe_repr(dictionary[key])))
+
+ if not (missing or mismatched):
+ return
+
+ standardMsg = ''
+ if missing:
+ standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in
+ missing)
+ if mismatched:
+ if standardMsg:
+ standardMsg += '; '
+ standardMsg += 'Mismatched values: %s' % ','.join(mismatched)
+
+ self.fail(self._formatMessage(msg, standardMsg))
+
+
def assertCountEqual(self, first, second, msg=None):
"""Asserts that two iterables have the same elements, the same number of
times, without regard to order.
@@ -1302,6 +1331,27 @@ class TestCase(object):
raise self.failureException(msg)
+ def _deprecate(original_func):
+ def deprecated_func(*args, **kwargs):
+ warnings.warn(
+ 'Please use {0} instead.'.format(original_func.__name__),
+ DeprecationWarning, 2)
+ return original_func(*args, **kwargs)
+ return deprecated_func
+
+ # see #9424
+ failUnlessEqual = assertEquals = _deprecate(assertEqual)
+ failIfEqual = assertNotEquals = _deprecate(assertNotEqual)
+ failUnlessAlmostEqual = assertAlmostEquals = _deprecate(assertAlmostEqual)
+ failIfAlmostEqual = assertNotAlmostEquals = _deprecate(assertNotAlmostEqual)
+ failUnless = assert_ = _deprecate(assertTrue)
+ failUnlessRaises = _deprecate(assertRaises)
+ failIf = _deprecate(assertFalse)
+ assertRaisesRegexp = _deprecate(assertRaisesRegex)
+ assertRegexpMatches = _deprecate(assertRegex)
+ assertNotRegexpMatches = _deprecate(assertNotRegex)
+
+
class FunctionTestCase(TestCase):
"""A test case that wraps a test function.
diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py
index eb18cd0..7e6ce2f 100644
--- a/Lib/unittest/loader.py
+++ b/Lib/unittest/loader.py
@@ -93,8 +93,30 @@ class TestLoader(object):
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
return loaded_suite
- def loadTestsFromModule(self, module, *, pattern=None):
+ # XXX After Python 3.5, remove backward compatibility hacks for
+ # use_load_tests deprecation via *args and **kws. See issue 16662.
+ def loadTestsFromModule(self, module, *args, pattern=None, **kws):
"""Return a suite of all test cases contained in the given module"""
+ # This method used to take an undocumented and unofficial
+ # use_load_tests argument. For backward compatibility, we still
+ # accept the argument (which can also be the first position) but we
+ # ignore it and issue a deprecation warning if it's present.
+ if len(args) > 0 or 'use_load_tests' in kws:
+ warnings.warn('use_load_tests is deprecated and ignored',
+ DeprecationWarning)
+ kws.pop('use_load_tests', None)
+ if len(args) > 1:
+ # Complain about the number of arguments, but don't forget the
+ # required `module` argument.
+ complaint = len(args) + 1
+ raise TypeError('loadTestsFromModule() takes 1 positional argument but {} were given'.format(complaint))
+ if len(kws) != 0:
+ # Since the keyword arguments are unsorted (see PEP 468), just
+ # pick the alphabetically sorted first argument to complain about,
+ # if multiple were given. At least the error message will be
+ # predictable.
+ complaint = sorted(kws)[0]
+ raise TypeError("loadTestsFromModule() got an unexpected keyword argument '{}'".format(complaint))
tests = []
for name in dir(module):
obj = getattr(module, name)
diff --git a/Lib/unittest/runner.py b/Lib/unittest/runner.py
index 6678adb..cb452c7 100644
--- a/Lib/unittest/runner.py
+++ b/Lib/unittest/runner.py
@@ -200,6 +200,15 @@ class TextTestRunner(object):
if self.warnings:
# if self.warnings is set, use it to filter all the warnings
warnings.simplefilter(self.warnings)
+ # if the filter is 'default' or 'always', special-case the
+ # warnings from the deprecated unittest methods to show them
+ # no more than once per module, because they can be fairly
+ # noisy. The -Wd and -Wa flags can be used to bypass this
+ # only when self.warnings is None.
+ if self.warnings in ['default', 'always']:
+ warnings.filterwarnings('module',
+ category=DeprecationWarning,
+ message=r'Please use assert\w+ instead.')
startTime = time.perf_counter()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
diff --git a/Lib/unittest/test/_test_warnings.py b/Lib/unittest/test/_test_warnings.py
index 08b846e..5cbfb53 100644
--- a/Lib/unittest/test/_test_warnings.py
+++ b/Lib/unittest/test/_test_warnings.py
@@ -18,6 +18,17 @@ def warnfun():
warnings.warn('rw', RuntimeWarning)
class TestWarnings(unittest.TestCase):
+ # unittest warnings will be printed at most once per type (max one message
+ # for the fail* methods, and one for the assert* methods)
+ def test_assert(self):
+ self.assertEquals(2+2, 4)
+ self.assertEquals(2*2, 4)
+ self.assertEquals(2**2, 4)
+
+ def test_fail(self):
+ self.failUnless(1)
+ self.failUnless(True)
+
def test_other_unittest(self):
self.assertAlmostEqual(2+2, 4)
self.assertNotAlmostEqual(4+4, 2)
diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py
index 6557104..a0db3423 100644
--- a/Lib/unittest/test/test_assertions.py
+++ b/Lib/unittest/test/test_assertions.py
@@ -271,6 +271,15 @@ class TestLongMessage(unittest.TestCase):
r"\+ \{'key': 'value'\}$",
r"\+ \{'key': 'value'\} : oops$"])
+ def testAssertDictContainsSubset(self):
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+
+ self.assertMessages('assertDictContainsSubset', ({'key': 'value'}, {}),
+ ["^Missing: 'key'$", "^oops$",
+ "^Missing: 'key'$",
+ "^Missing: 'key' : oops$"])
+
def testAssertMultiLineEqual(self):
self.assertMessages('assertMultiLineEqual', ("", "foo"),
[r"\+ foo$", "^oops$",
diff --git a/Lib/unittest/test/test_case.py b/Lib/unittest/test/test_case.py
index 067ec8d..f6cb997 100644
--- a/Lib/unittest/test/test_case.py
+++ b/Lib/unittest/test/test_case.py
@@ -698,6 +698,36 @@ class Test_TestCase(unittest.TestCase, TestEquality, TestHashing):
self.assertRaises(self.failureException, self.assertNotIn, 'cow',
animals)
+ def testAssertDictContainsSubset(self):
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+
+ self.assertDictContainsSubset({}, {})
+ self.assertDictContainsSubset({}, {'a': 1})
+ self.assertDictContainsSubset({'a': 1}, {'a': 1})
+ self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2})
+ self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2})
+
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({1: "one"}, {})
+
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'a': 2}, {'a': 1})
+
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'c': 1}, {'a': 1})
+
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1})
+
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'a': 1, 'c': 1}, {'a': 1})
+
+ one = ''.join(chr(i) for i in range(255))
+ # this used to cause a UnicodeDecodeError constructing the failure msg
+ with self.assertRaises(self.failureException):
+ self.assertDictContainsSubset({'foo': one}, {'foo': '\uFFFD'})
+
def testAssertEqual(self):
equal_pairs = [
((), ()),
@@ -1760,18 +1790,45 @@ test case
pass
self.assertIsNone(value)
- def testDeprecatedFailMethods(self):
- """Test that the deprecated fail* methods get removed in 3.11"""
+ def testDeprecatedMethodNames(self):
+ """
+ Test that the deprecated methods raise a DeprecationWarning. See #9424.
+ """
+ old = (
+ (self.failIfEqual, (3, 5)),
+ (self.assertNotEquals, (3, 5)),
+ (self.failUnlessEqual, (3, 3)),
+ (self.assertEquals, (3, 3)),
+ (self.failUnlessAlmostEqual, (2.0, 2.0)),
+ (self.assertAlmostEquals, (2.0, 2.0)),
+ (self.failIfAlmostEqual, (3.0, 5.0)),
+ (self.assertNotAlmostEquals, (3.0, 5.0)),
+ (self.failUnless, (True,)),
+ (self.assert_, (True,)),
+ (self.failUnlessRaises, (TypeError, lambda _: 3.14 + 'spam')),
+ (self.failIf, (False,)),
+ (self.assertDictContainsSubset, (dict(a=1, b=2), dict(a=1, b=2, c=3))),
+ (self.assertRaisesRegexp, (KeyError, 'foo', lambda: {}['foo'])),
+ (self.assertRegexpMatches, ('bar', 'bar')),
+ )
+ for meth, args in old:
+ with self.assertWarns(DeprecationWarning):
+ meth(*args)
+
+ # disable this test for now. When the version where the fail* methods will
+ # be removed is decided, re-enable it and update the version
+ def _testDeprecatedFailMethods(self):
+ """Test that the deprecated fail* methods get removed in 3.x"""
+ if sys.version_info[:2] < (3, 3):
+ return
deprecated_names = [
'failIfEqual', 'failUnlessEqual', 'failUnlessAlmostEqual',
'failIfAlmostEqual', 'failUnless', 'failUnlessRaises', 'failIf',
- 'assertNotEquals', 'assertEquals', 'assertAlmostEquals',
- 'assertNotAlmostEquals', 'assert_', 'assertDictContainsSubset',
- 'assertRaisesRegexp', 'assertRegexpMatches'
+ 'assertDictContainsSubset',
]
for deprecated_name in deprecated_names:
with self.assertRaises(AttributeError):
- getattr(self, deprecated_name)
+ getattr(self, deprecated_name) # remove these in 3.x
def testDeepcopy(self):
# Issue: 5660
diff --git a/Lib/unittest/test/test_loader.py b/Lib/unittest/test/test_loader.py
index fc0d46e..90e2081 100644
--- a/Lib/unittest/test/test_loader.py
+++ b/Lib/unittest/test/test_loader.py
@@ -5,6 +5,25 @@ import warnings
import unittest
+# Decorator used in the deprecation tests to reset the warning registry for
+# test isolation and reproducibility.
+def warningregistry(func):
+ def wrapper(*args, **kws):
+ missing = []
+ saved = getattr(warnings, '__warningregistry__', missing).copy()
+ try:
+ return func(*args, **kws)
+ finally:
+ if saved is missing:
+ try:
+ del warnings.__warningregistry__
+ except AttributeError:
+ pass
+ else:
+ warnings.__warningregistry__ = saved
+ return wrapper
+
+
class Test_TestLoader(unittest.TestCase):
### Basic object tests
@@ -155,8 +174,9 @@ class Test_TestLoader(unittest.TestCase):
self.assertEqual(list(suite), reference)
- # Check that loadTestsFromModule honors a module
+ # 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):
@@ -175,13 +195,126 @@ class Test_TestLoader(unittest.TestCase):
suite = loader.loadTestsFromModule(m)
self.assertIsInstance(suite, unittest.TestSuite)
self.assertEqual(load_tests_args, [loader, suite, None])
+ # With Python 3.5, the undocumented and unofficial use_load_tests is
+ # ignored (and deprecated).
+ load_tests_args = []
+ with warnings.catch_warnings(record=False):
+ warnings.simplefilter('ignore')
+ suite = loader.loadTestsFromModule(m, use_load_tests=False)
+ self.assertEqual(load_tests_args, [loader, suite, None])
+
+ @warningregistry
+ def test_loadTestsFromModule__use_load_tests_deprecated_positional(self):
+ m = types.ModuleType('m')
+ class MyTestCase(unittest.TestCase):
+ def test(self):
+ pass
+ m.testcase_1 = MyTestCase
+
+ load_tests_args = []
+ def load_tests(loader, tests, pattern):
+ self.assertIsInstance(tests, unittest.TestSuite)
+ load_tests_args.extend((loader, tests, pattern))
+ return tests
+ m.load_tests = load_tests
+ # The method still works.
+ loader = unittest.TestLoader()
+ # use_load_tests=True as a positional argument.
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always')
+ suite = loader.loadTestsFromModule(m, False)
+ self.assertIsInstance(suite, unittest.TestSuite)
+ # load_tests was still called because use_load_tests is deprecated
+ # and ignored.
+ self.assertEqual(load_tests_args, [loader, suite, None])
+ # We got a warning.
+ self.assertIs(w[-1].category, DeprecationWarning)
+ self.assertEqual(str(w[-1].message),
+ 'use_load_tests is deprecated and ignored')
+
+ @warningregistry
+ def test_loadTestsFromModule__use_load_tests_deprecated_keyword(self):
+ m = types.ModuleType('m')
+ class MyTestCase(unittest.TestCase):
+ def test(self):
+ pass
+ m.testcase_1 = MyTestCase
+
+ load_tests_args = []
+ def load_tests(loader, tests, pattern):
+ self.assertIsInstance(tests, unittest.TestSuite)
+ load_tests_args.extend((loader, tests, pattern))
+ return tests
+ m.load_tests = load_tests
+ # The method still works.
+ loader = unittest.TestLoader()
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always')
+ suite = loader.loadTestsFromModule(m, use_load_tests=False)
+ self.assertIsInstance(suite, unittest.TestSuite)
+ # load_tests was still called because use_load_tests is deprecated
+ # and ignored.
+ self.assertEqual(load_tests_args, [loader, suite, None])
+ # We got a warning.
+ self.assertIs(w[-1].category, DeprecationWarning)
+ self.assertEqual(str(w[-1].message),
+ 'use_load_tests is deprecated and ignored')
+
+ @warningregistry
+ def test_loadTestsFromModule__too_many_positional_args(self):
+ m = types.ModuleType('m')
+ class MyTestCase(unittest.TestCase):
+ def test(self):
+ pass
+ m.testcase_1 = MyTestCase
+
+ load_tests_args = []
+ def load_tests(loader, tests, pattern):
+ self.assertIsInstance(tests, unittest.TestSuite)
+ load_tests_args.extend((loader, tests, pattern))
+ return tests
+ m.load_tests = load_tests
+ loader = unittest.TestLoader()
+ with self.assertRaises(TypeError) as cm, \
+ warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always')
+ loader.loadTestsFromModule(m, False, 'testme.*')
+ # We still got the deprecation warning.
+ self.assertIs(w[-1].category, DeprecationWarning)
+ self.assertEqual(str(w[-1].message),
+ 'use_load_tests is deprecated and ignored')
+ # We also got a TypeError for too many positional arguments.
+ self.assertEqual(type(cm.exception), TypeError)
+ self.assertEqual(
+ str(cm.exception),
+ 'loadTestsFromModule() takes 1 positional argument but 3 were given')
+
+ @warningregistry
+ def test_loadTestsFromModule__use_load_tests_other_bad_keyword(self):
+ m = types.ModuleType('m')
+ class MyTestCase(unittest.TestCase):
+ def test(self):
+ pass
+ m.testcase_1 = MyTestCase
- # In Python 3.11, the undocumented and unofficial use_load_tests has
- # been removed.
- with self.assertRaises(TypeError):
- loader.loadTestsFromModule(m, False)
- with self.assertRaises(TypeError):
- loader.loadTestsFromModule(m, use_load_tests=False)
+ load_tests_args = []
+ def load_tests(loader, tests, pattern):
+ self.assertIsInstance(tests, unittest.TestSuite)
+ load_tests_args.extend((loader, tests, pattern))
+ return tests
+ m.load_tests = load_tests
+ loader = unittest.TestLoader()
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore')
+ with self.assertRaises(TypeError) as cm:
+ loader.loadTestsFromModule(
+ m, use_load_tests=False, very_bad=True, worse=False)
+ self.assertEqual(type(cm.exception), TypeError)
+ # The error message names the first bad argument alphabetically,
+ # however use_load_tests (which sorts first) is ignored.
+ self.assertEqual(
+ str(cm.exception),
+ "loadTestsFromModule() got an unexpected keyword argument 'very_bad'")
def test_loadTestsFromModule__pattern(self):
m = types.ModuleType('m')
diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py
index 687f629..fa9e629 100644
--- a/Lib/unittest/test/test_program.py
+++ b/Lib/unittest/test/test_program.py
@@ -460,14 +460,14 @@ class TestCommandLineArgs(unittest.TestCase):
return stderr.decode()
t = '_test_warnings'
- self.assertIn('Ran 5 tests', run_unittest([t]))
- self.assertIn('Ran 5 tests', run_unittest(['-k', 'TestWarnings', t]))
- self.assertIn('Ran 5 tests', run_unittest(['discover', '-p', '*_test*', '-k', 'TestWarnings']))
- self.assertIn('Ran 1 test ', run_unittest(['-k', 'f', t]))
- self.assertIn('Ran 5 tests', run_unittest(['-k', 't', t]))
- self.assertIn('Ran 2 tests', run_unittest(['-k', '*t', t]))
- self.assertIn('Ran 5 tests', run_unittest(['-k', '*test_warnings.*Warning*', t]))
- self.assertIn('Ran 1 test ', run_unittest(['-k', '*test_warnings.*warning*', t]))
+ self.assertIn('Ran 7 tests', run_unittest([t]))
+ self.assertIn('Ran 7 tests', run_unittest(['-k', 'TestWarnings', t]))
+ self.assertIn('Ran 7 tests', run_unittest(['discover', '-p', '*_test*', '-k', 'TestWarnings']))
+ self.assertIn('Ran 2 tests', run_unittest(['-k', 'f', t]))
+ self.assertIn('Ran 7 tests', run_unittest(['-k', 't', t]))
+ self.assertIn('Ran 3 tests', run_unittest(['-k', '*t', t]))
+ self.assertIn('Ran 7 tests', run_unittest(['-k', '*test_warnings.*Warning*', t]))
+ self.assertIn('Ran 1 test', run_unittest(['-k', '*test_warnings.*warning*', t]))
if __name__ == '__main__':
diff --git a/Lib/unittest/test/test_runner.py b/Lib/unittest/test/test_runner.py
index bcf7538..c487e9e 100644
--- a/Lib/unittest/test/test_runner.py
+++ b/Lib/unittest/test/test_runner.py
@@ -1150,6 +1150,8 @@ class Test_TextTestRunner(unittest.TestCase):
return [b.splitlines() for b in p.communicate()]
opts = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=os.path.dirname(__file__))
+ ae_msg = b'Please use assertEqual instead.'
+ at_msg = b'Please use assertTrue instead.'
# no args -> all the warnings are printed, unittest warnings only once
p = subprocess.Popen([sys.executable, '-E', '_test_warnings.py'], **opts)
@@ -1157,11 +1159,11 @@ class Test_TextTestRunner(unittest.TestCase):
out, err = get_parse_out_err(p)
self.assertIn(b'OK', err)
# check that the total number of warnings in the output is correct
- self.assertEqual(len(out), 10)
+ self.assertEqual(len(out), 12)
# check that the numbers of the different kind of warnings is correct
for msg in [b'dw', b'iw', b'uw']:
self.assertEqual(out.count(msg), 3)
- for msg in [b'rw']:
+ for msg in [ae_msg, at_msg, b'rw']:
self.assertEqual(out.count(msg), 1)
args_list = (
@@ -1188,9 +1190,11 @@ class Test_TextTestRunner(unittest.TestCase):
with p:
out, err = get_parse_out_err(p)
self.assertIn(b'OK', err)
- self.assertEqual(len(out), 12)
+ self.assertEqual(len(out), 14)
for msg in [b'dw', b'iw', b'uw', b'rw']:
self.assertEqual(out.count(msg), 3)
+ for msg in [ae_msg, at_msg]:
+ self.assertEqual(out.count(msg), 1)
def testStdErrLookedUpAtInstantiationTime(self):
# see issue 10786