https://github.com/python/cpython/commit/c6a566e47b9903d48e6e1e78a1af20e6c6c535cf
commit: c6a566e47b9903d48e6e1e78a1af20e6c6c535cf
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-01-20T11:19:00+02:00
summary:

[3.13] gh-71339: Add additional assertion methods in test.support (GH-128707) 
(GH-128815)

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 06cad77a5b345adde88609be9c3c470c5cd9f417)

files:
M Lib/test/support/testcase.py
M Lib/test/test_descr.py
M Lib/test/test_gdb/util.py
M Lib/test/test_importlib/resources/test_functional.py
M Lib/test/test_pyclbr.py
M Lib/test/test_typing.py
M Lib/test/test_venv.py

diff --git a/Lib/test/support/testcase.py b/Lib/test/support/testcase.py
index fad1e4cb3499c0..fd32457d1467ca 100644
--- a/Lib/test/support/testcase.py
+++ b/Lib/test/support/testcase.py
@@ -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):
         """
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 14bd87eb9c8d84..dd1fa321ecf171 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -15,6 +15,7 @@
 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 @@ def test_wrap_lenfunc_bad_cast(self):
         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...
diff --git a/Lib/test/test_gdb/util.py b/Lib/test/test_gdb/util.py
index 8fe9cfc543395e..54c6b2de7cc99d 100644
--- a/Lib/test/test_gdb/util.py
+++ b/Lib/test/test_gdb/util.py
@@ -7,6 +7,7 @@
 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 @@ def get_stack_trace(self, source=None, script=None,
 
         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:
diff --git a/Lib/test/test_importlib/resources/test_functional.py 
b/Lib/test/test_importlib/resources/test_functional.py
index 4317abf3162c52..3fc1ade35bef5a 100644
--- a/Lib/test/test_importlib/resources/test_functional.py
+++ b/Lib/test/test_importlib/resources/test_functional.py
@@ -3,6 +3,7 @@
 import importlib
 
 from test.support import warnings_helper
+from test.support.testcase import ExtraAssertions
 
 from importlib import resources
 
@@ -28,7 +29,7 @@ def anchor02(self):
         return importlib.import_module('data02')
 
 
-class FunctionalAPIBase(util.DiskSetup):
+class FunctionalAPIBase(util.DiskSetup, ExtraAssertions):
     def setUp(self):
         super().setUp()
         self.load_fixture('data02')
@@ -43,12 +44,6 @@ def _gen_resourcetxt_path_parts(self):
             with self.subTest(path_parts=path_parts):
                 yield path_parts
 
-    def assertEndsWith(self, string, suffix):
-        """Assert that `string` ends with `suffix`.
-
-        Used to ignore an architecture-specific UTF-16 byte-order mark."""
-        self.assertEqual(string[-len(suffix) :], suffix)
-
     def test_read_text(self):
         self.assertEqual(
             resources.read_text(self.anchor01, 'utf-8.file'),
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index d409a2d4a312e6..a65705aaf53abc 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -10,6 +10,7 @@
 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 @@
 # 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 @@ def assertListEq(self, l1, l2, ignore):
             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 @@ def ismethod(oclass, obj, name):
         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))
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 140ceb74735570..89a32c7a1a0d14 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -46,6 +46,7 @@
 import types
 
 from test.support import captured_stderr, cpython_only, infinite_recursion, 
requires_docstrings, import_helper
+from test.support.testcase import ExtraAssertions
 from test.typinganndata import ann_module695, mod_generics_cache, 
_typed_dict_helper
 
 
@@ -54,21 +55,7 @@
 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:
@@ -1249,10 +1236,6 @@ class Gen[*Ts]: ...
 
 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')
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 0b09010c69d4ea..c39c83f9d0a5c3 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -26,6 +26,7 @@
                           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
@@ -64,7 +65,7 @@ def check_output(cmd, encoding=None):
         )
     return out, err
 
-class BaseTest(unittest.TestCase):
+class BaseTest(unittest.TestCase, ExtraAssertions):
     """Base class for venv tests."""
     maxDiff = 80 * 50
 
@@ -111,10 +112,6 @@ def get_text_file_contents(self, *args, encoding='utf-8'):
             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."""
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to