]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-45162: Revert "Remove many old deprecated unittest features" (GH-30935)
authorGregory P. Smith <greg@krypto.org>
Thu, 27 Jan 2022 04:39:15 +0000 (20:39 -0800)
committerGitHub <noreply@github.com>
Thu, 27 Jan 2022 04:39:15 +0000 (20:39 -0800)
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.

14 files changed:
Doc/library/unittest.rst
Doc/whatsnew/3.11.rst
Doc/whatsnew/3.2.rst
Lib/unittest/__init__.py
Lib/unittest/case.py
Lib/unittest/loader.py
Lib/unittest/runner.py
Lib/unittest/test/_test_warnings.py
Lib/unittest/test/test_assertions.py
Lib/unittest/test/test_case.py
Lib/unittest/test/test_loader.py
Lib/unittest/test/test_program.py
Lib/unittest/test/test_runner.py
Misc/NEWS.d/next/Library/2022-01-26-23-58-48.bpo-45162.4Jmg_j.rst [new file with mode: 0644]

index b5a533194b583e5e3e2a4095e7e563e6bf239903..06df8ce3ad69b8de2835004f2d0c3f8971917645 100644 (file)
@@ -1255,6 +1255,9 @@ Test cases
          :meth:`.assertRegex`.
       .. versionadded:: 3.2
          :meth:`.assertNotRegex`.
+      .. versionadded:: 3.5
+         The name ``assertNotRegexpMatches`` is a deprecated alias
+         for :meth:`.assertNotRegex`.
 
 
    .. method:: assertCountEqual(first, second, msg=None)
@@ -1620,6 +1623,40 @@ Test cases
    :mod:`unittest`-based test framework.
 
 
+.. _deprecated-aliases:
+
+Deprecated aliases
+##################
+
+For historical reasons, some of the :class:`TestCase` methods had one or more
+aliases that are now deprecated.  The following table lists the correct names
+along with their deprecated aliases:
+
+   ==============================  ====================== =======================
+    Method Name                     Deprecated alias       Deprecated alias
+   ==============================  ====================== =======================
+    :meth:`.assertEqual`            failUnlessEqual        assertEquals
+    :meth:`.assertNotEqual`         failIfEqual            assertNotEquals
+    :meth:`.assertTrue`             failUnless             assert\_
+    :meth:`.assertFalse`            failIf
+    :meth:`.assertRaises`           failUnlessRaises
+    :meth:`.assertAlmostEqual`      failUnlessAlmostEqual  assertAlmostEquals
+    :meth:`.assertNotAlmostEqual`   failIfAlmostEqual      assertNotAlmostEquals
+    :meth:`.assertRegex`                                   assertRegexpMatches
+    :meth:`.assertNotRegex`                                assertNotRegexpMatches
+    :meth:`.assertRaisesRegex`                             assertRaisesRegexp
+   ==============================  ====================== =======================
+
+   .. deprecated:: 3.1
+         The fail* aliases listed in the second column have been deprecated.
+   .. deprecated:: 3.2
+         The assert* aliases listed in the third column have been deprecated.
+   .. deprecated:: 3.2
+         ``assertRegexpMatches`` and ``assertRaisesRegexp`` have been renamed to
+         :meth:`.assertRegex` and :meth:`.assertRaisesRegex`.
+   .. deprecated:: 3.5
+         The ``assertNotRegexpMatches`` name is deprecated in favor of :meth:`.assertNotRegex`.
+
 .. _testsuite-objects:
 
 Grouping tests
@@ -1745,7 +1782,7 @@ Loading and running tests
       case is created for that method instead.
 
 
-   .. method:: loadTestsFromModule(module, *, pattern=None)
+   .. method:: loadTestsFromModule(module, pattern=None)
 
       Return a suite of all test cases contained in the given module. This
       method searches *module* for classes derived from :class:`TestCase` and
@@ -1769,11 +1806,10 @@ Loading and running tests
          Support for ``load_tests`` added.
 
       .. versionchanged:: 3.5
-         Support for a keyword-only argument *pattern* has been added.
-
-      .. versionchanged:: 3.11
-         The undocumented and unofficial *use_load_tests* parameter has been
-         removed.
+         The undocumented and unofficial *use_load_tests* default argument is
+         deprecated and ignored, although it is still accepted for backward
+         compatibility.  The method also now accepts a keyword-only argument
+         *pattern* which is passed to ``load_tests`` as the third argument.
 
 
    .. method:: loadTestsFromName(name, module=None)
@@ -2130,6 +2166,8 @@ Loading and running tests
    :class:`TextTestRunner`.
 
    .. versionadded:: 3.2
+      This class was previously named ``_TextTestResult``. The old name still
+      exists as an alias but is deprecated.
 
 
 .. data:: defaultTestLoader
@@ -2152,7 +2190,10 @@ Loading and running tests
    By default this runner shows :exc:`DeprecationWarning`,
    :exc:`PendingDeprecationWarning`, :exc:`ResourceWarning` and
    :exc:`ImportWarning` even if they are :ref:`ignored by default
-   <warning-ignored>`.  This behavior can
+   <warning-ignored>`. Deprecation warnings caused by :ref:`deprecated unittest
+   methods <deprecated-aliases>` are also special-cased and, when the warning
+   filters are ``'default'`` or ``'always'``, they will appear only once
+   per-module, in order to avoid too many warning messages.  This behavior can
    be overridden using Python's :option:`!-Wd` or :option:`!-Wa` options
    (see :ref:`Warning control <using-on-warnings>`) and leaving
    *warnings* to ``None``.
index edb2f6b89bbae6e42fe43bd6131e3cae6a877724..adf38b6b0145c25ad6706a067f8e65194f7d7c5c 100644 (file)
@@ -489,28 +489,6 @@ Removed
   and :class:`fileinput.FileInput`, deprecated since Python 3.9.
   (Contributed by Hugo van Kemenade in :issue:`45132`.)
 
-* Removed many old deprecated :mod:`unittest` features:
-
-  - :class:`~unittest.TestCase` method aliases ``failUnlessEqual``,
-    ``failIfEqual``, ``failUnless``, ``failIf``, ``failUnlessRaises``,
-    ``failUnlessAlmostEqual``, ``failIfAlmostEqual`` (deprecated in Python 3.1),
-    ``assertEquals``, ``assertNotEquals``, ``assert_``, ``assertAlmostEquals``,
-    ``assertNotAlmostEquals``, ``assertRegexpMatches``, ``assertRaisesRegexp``
-    (deprecated in Python 3.2), and ``assertNotRegexpMatches`` (deprecated in
-    Python 3.5).
-
-  - Undocumented and broken :class:`~unittest.TestCase` method
-    ``assertDictContainsSubset`` (deprecated in Python 3.2).
-
-  - Undocumented :meth:`<unittest.TestLoader.loadTestsFromModule>
-    TestLoader.loadTestsFromModule` parameter *use_load_tests* (deprecated
-    and ignored since Python 3.2).
-
-  - An alias of the :class:`~unittest.TextTestResult` class:
-    ``_TextTestResult`` (deprecated in Python 3.2).
-
-  (Contributed by Serhiy Storchaka in :issue:`45162`.)
-
 * The following deprecated functions and methods are removed in the :mod:`gettext`
   module: :func:`~gettext.lgettext`, :func:`~gettext.ldgettext`,
   :func:`~gettext.lngettext` and :func:`~gettext.ldngettext`.
index 09ec58eca5b110e31ae4f0c40506521440ab0cd5..840cb061129b7fe592af1a0ae6ab8aa9e881d31f 100644 (file)
@@ -1815,7 +1815,8 @@ names.
    ===============================   ==============================
 
   Likewise, the ``TestCase.fail*`` methods deprecated in Python 3.1 are expected
-  to be removed in Python 3.3.
+  to be removed in Python 3.3.  Also see the :ref:`deprecated-aliases` section in
+  the :mod:`unittest` documentation.
 
   (Contributed by Ezio Melotti; :issue:`9424`.)
 
index 540433d3b3bb9ea94f358ca3a2ae78213609bcdd..4b184889573940199333cf981d78a4949fe86568 100644 (file)
@@ -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
index f1067652cc87d23e1286626703065d1a12c8c8ff..0d550204a7687faff78b2d8d86d70cb54535541b 100644 (file)
@@ -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.
index eb18cd0b49cd2641ce6f53d9a612af5cc5c18501..7e6ce2f224bafc0dc08ba70019c37918ee094be5 100644 (file)
@@ -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)
index 6678adb6a7d81319570bfe4b0ce3cfb251d7841e..cb452c7adefb73078086a5071775b7129962bd27 100644 (file)
@@ -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:
index 08b846ee47e9263afe9274b0579cb8c46f1bcc23..5cbfb532ad05021f5ab3f71dead07d99f16c968c 100644 (file)
@@ -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)
index 6557104b81fc0f8451f1058d3bbdf910f5de9bbc..a0db3423b868aa8463ffadcb284d586442435c32 100644 (file)
@@ -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$",
index 067ec8d458679f265bebe2f1230c308b441b296d..f6cb9977d06d438fde4b606ae85f09261fccfabd 100644 (file)
@@ -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
index fc0d46ee8d04366424a17028555b00cf4945dd21..90e208182719b2ed3c21a3f7783ab4a4633bc17f 100644 (file)
@@ -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')
index 687f62996740ebf39fd1300aec3d96a8ce8052cf..fa9e6291caaf56e029a44705e401d7ab1c758579 100644 (file)
@@ -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__':
index bcf7538c26fcfc246c921cfd215133c47ca5d7df..c487e9ec4efcbff02fe0c14dc5ea722281ae59e4 100644 (file)
@@ -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
diff --git a/Misc/NEWS.d/next/Library/2022-01-26-23-58-48.bpo-45162.4Jmg_j.rst b/Misc/NEWS.d/next/Library/2022-01-26-23-58-48.bpo-45162.4Jmg_j.rst
new file mode 100644 (file)
index 0000000..29fee1e
--- /dev/null
@@ -0,0 +1,3 @@
+The deprecated :mod:`unittest` APIs removed in 3.11a1 have been
+temporarily restored to be removed in 3.12 while cleanups in external
+projects go in.