]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-71339: Add additional assertion methods in test.support (GH-128707) (GH...
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 20 Jan 2025 12:06:02 +0000 (14:06 +0200)
committerGitHub <noreply@github.com>
Mon, 20 Jan 2025 12:06:02 +0000 (12:06 +0000)
Add a mix-in class ExtraAssertions containing the following methods:

* assertHasAttr() and assertNotHasAttr()
* assertIsSubclass() and assertNotIsSubclass()
* assertStartsWith() and assertNotStartsWith()
* assertEndsWith() and assertNotEndsWith()

(cherry picked from commit c6a566e47b9903d48e6e1e78a1af20e6c6c535cf)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
(cherry picked from commit 06cad77a5b345adde88609be9c3c470c5cd9f417)

Lib/test/support/testcase.py
Lib/test/test_descr.py
Lib/test/test_gdb/util.py
Lib/test/test_pyclbr.py
Lib/test/test_typing.py
Lib/test/test_venv.py

index fad1e4cb3499c07ba7ca7fc8bdc36be5cfcaaa58..fd32457d1467ca8e5ef1211605ee4f1bec3a27d8 100644 (file)
@@ -1,6 +1,63 @@
 from math import copysign, isnan
 
 
+class ExtraAssertions:
+
+    def assertIsSubclass(self, cls, superclass, msg=None):
+        if issubclass(cls, superclass):
+            return
+        standardMsg = f'{cls!r} is not a subclass of {superclass!r}'
+        self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertNotIsSubclass(self, cls, superclass, msg=None):
+        if not issubclass(cls, superclass):
+            return
+        standardMsg = f'{cls!r} is a subclass of {superclass!r}'
+        self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertHasAttr(self, obj, name, msg=None):
+        if not hasattr(obj, name):
+            if isinstance(obj, types.ModuleType):
+                standardMsg = f'module {obj.__name__!r} has no attribute {name!r}'
+            elif isinstance(obj, type):
+                standardMsg = f'type object {obj.__name__!r} has no attribute {name!r}'
+            else:
+                standardMsg = f'{type(obj).__name__!r} object has no attribute {name!r}'
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertNotHasAttr(self, obj, name, msg=None):
+        if hasattr(obj, name):
+            if isinstance(obj, types.ModuleType):
+                standardMsg = f'module {obj.__name__!r} has unexpected attribute {name!r}'
+            elif isinstance(obj, type):
+                standardMsg = f'type object {obj.__name__!r} has unexpected attribute {name!r}'
+            else:
+                standardMsg = f'{type(obj).__name__!r} object has unexpected attribute {name!r}'
+            self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertStartsWith(self, s, prefix, msg=None):
+        if s.startswith(prefix):
+            return
+        standardMsg = f"{s!r} doesn't start with {prefix!r}"
+        self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertNotStartsWith(self, s, prefix, msg=None):
+        if not s.startswith(prefix):
+            return
+        self.fail(self._formatMessage(msg, f"{s!r} starts with {prefix!r}"))
+
+    def assertEndsWith(self, s, suffix, msg=None):
+        if s.endswith(suffix):
+            return
+        standardMsg = f"{s!r} doesn't end with {suffix!r}"
+        self.fail(self._formatMessage(msg, standardMsg))
+
+    def assertNotEndsWith(self, s, suffix, msg=None):
+        if not s.endswith(suffix):
+            return
+        self.fail(self._formatMessage(msg, f"{s!r} ends with {suffix!r}"))
+
+
 class ExceptionIsLikeMixin:
     def assertExceptionIsLike(self, exc, template):
         """
index 3a11435e3e254305080620f46927aa6799564d2d..99388b53878f364dfa147c2e6bfacc9bef2ce644 100644 (file)
@@ -15,6 +15,7 @@ import weakref
 from copy import deepcopy
 from contextlib import redirect_stdout
 from test import support
+from test.support.testcase import ExtraAssertions
 
 try:
     import _testcapi
@@ -403,15 +404,7 @@ class OperatorsTest(unittest.TestCase):
         self.assertEqual(range(sys.maxsize).__len__(), sys.maxsize)
 
 
-class ClassPropertiesAndMethods(unittest.TestCase):
-
-    def assertHasAttr(self, obj, name):
-        self.assertTrue(hasattr(obj, name),
-                        '%r has no attribute %r' % (obj, name))
-
-    def assertNotHasAttr(self, obj, name):
-        self.assertFalse(hasattr(obj, name),
-                         '%r has unexpected attribute %r' % (obj, name))
+class ClassPropertiesAndMethods(unittest.TestCase, ExtraAssertions):
 
     def test_python_dicts(self):
         # Testing Python subclass of dict...
index 8fe9cfc543395e16e2aa8c5cffb048f82a4ff697..54c6b2de7cc99d8a0858e5679a7e0425cd4c40d6 100644 (file)
@@ -7,6 +7,7 @@ import sys
 import sysconfig
 import unittest
 from test import support
+from test.support.testcase import ExtraAssertions
 
 
 GDB_PROGRAM = shutil.which('gdb') or 'gdb'
@@ -152,7 +153,7 @@ def setup_module():
         print()
 
 
-class DebuggerTests(unittest.TestCase):
+class DebuggerTests(unittest.TestCase, ExtraAssertions):
 
     """Test that the debugger can debug Python."""
 
@@ -280,11 +281,6 @@ class DebuggerTests(unittest.TestCase):
 
         return out
 
-    def assertEndsWith(self, actual, exp_end):
-        '''Ensure that the given "actual" string ends with "exp_end"'''
-        self.assertTrue(actual.endswith(exp_end),
-                        msg='%r did not end with %r' % (actual, exp_end))
-
     def assertMultilineMatches(self, actual, pattern):
         m = re.match(pattern, actual, re.DOTALL)
         if not m:
index 5415fa08330325bce99619278f84bac76eda71f5..a7580a4124defd530cebaaaff613b413d1a87eb4 100644 (file)
@@ -10,6 +10,7 @@ import pyclbr
 from unittest import TestCase, main as unittest_main
 from test.test_importlib import util as test_importlib_util
 import warnings
+from test.support.testcase import ExtraAssertions
 
 
 StaticMethodType = type(staticmethod(lambda: None))
@@ -22,7 +23,7 @@ ClassMethodType = type(classmethod(lambda c: None))
 # is imperfect (as designed), testModule is called with a set of
 # members to ignore.
 
-class PyclbrTest(TestCase):
+class PyclbrTest(TestCase, ExtraAssertions):
 
     def assertListEq(self, l1, l2, ignore):
         ''' succeed iff {l1} - {ignore} == {l2} - {ignore} '''
@@ -31,14 +32,6 @@ class PyclbrTest(TestCase):
             print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr)
             self.fail("%r missing" % missing.pop())
 
-    def assertHasattr(self, obj, attr, ignore):
-        ''' succeed iff hasattr(obj,attr) or attr in ignore. '''
-        if attr in ignore: return
-        if not hasattr(obj, attr): print("???", attr)
-        self.assertTrue(hasattr(obj, attr),
-                        'expected hasattr(%r, %r)' % (obj, attr))
-
-
     def assertHaskey(self, obj, key, ignore):
         ''' succeed iff key in obj or key in ignore. '''
         if key in ignore: return
@@ -86,7 +79,7 @@ class PyclbrTest(TestCase):
         for name, value in dict.items():
             if name in ignore:
                 continue
-            self.assertHasattr(module, name, ignore)
+            self.assertHasAttr(module, name, ignore)
             py_item = getattr(module, name)
             if isinstance(value, pyclbr.Function):
                 self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))
index c5f4d775f22fb645442da025b89442be57a7c11f..5c862d7928c950cde1e107a0ac0c190321ecde09 100644 (file)
@@ -47,6 +47,7 @@ import weakref
 import types
 
 from test.support import captured_stderr, cpython_only
+from test.support.testcase import ExtraAssertions
 from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper
 
 
@@ -55,21 +56,7 @@ NOT_A_BASE_TYPE = "type 'typing.%s' is not an acceptable base type"
 CANNOT_SUBCLASS_INSTANCE = 'Cannot subclass an instance of %s'
 
 
-class BaseTestCase(TestCase):
-
-    def assertIsSubclass(self, cls, class_or_tuple, msg=None):
-        if not issubclass(cls, class_or_tuple):
-            message = '%r is not a subclass of %r' % (cls, class_or_tuple)
-            if msg is not None:
-                message += ' : %s' % msg
-            raise self.failureException(message)
-
-    def assertNotIsSubclass(self, cls, class_or_tuple, msg=None):
-        if issubclass(cls, class_or_tuple):
-            message = '%r is a subclass of %r' % (cls, class_or_tuple)
-            if msg is not None:
-                message += ' : %s' % msg
-            raise self.failureException(message)
+class BaseTestCase(TestCase, ExtraAssertions):
 
     def clear_caches(self):
         for f in typing._cleanups:
@@ -1051,10 +1038,6 @@ class UnpackTests(BaseTestCase):
 
 class TypeVarTupleTests(BaseTestCase):
 
-    def assertEndsWith(self, string, tail):
-        if not string.endswith(tail):
-            self.fail(f"String {string!r} does not end with {tail!r}")
-
     def test_name(self):
         Ts = TypeVarTuple('Ts')
         self.assertEqual(Ts.__name__, 'Ts')
index 8254a701e09a10780721915962393c6a8c62642d..43c67ac751d585231c177696adc0abdbd307106d 100644 (file)
@@ -25,6 +25,7 @@ from test.support import (captured_stdout, captured_stderr,
                           requires_resource, copy_python_src_ignore)
 from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree,
                                     TESTFN, FakePath)
+from test.support.testcase import ExtraAssertions
 import unittest
 import venv
 from unittest.mock import patch, Mock
@@ -58,7 +59,7 @@ def check_output(cmd, encoding=None):
             p.returncode, cmd, out, err)
     return out, err
 
-class BaseTest(unittest.TestCase):
+class BaseTest(unittest.TestCase, ExtraAssertions):
     """Base class for venv tests."""
     maxDiff = 80 * 50
 
@@ -98,10 +99,6 @@ class BaseTest(unittest.TestCase):
             result = f.read()
         return result
 
-    def assertEndsWith(self, string, tail):
-        if not string.endswith(tail):
-            self.fail(f"String {string!r} does not end with {tail!r}")
-
 class BasicTest(BaseTest):
     """Test venv module functionality."""