]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-71339: Add additional assertion methods in test.support (GH-128707) (GH...
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 20 Jan 2025 09:19:00 +0000 (11:19 +0200)
committerGitHub <noreply@github.com>
Mon, 20 Jan 2025 09:19:00 +0000 (11:19 +0200)
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)

Lib/test/support/testcase.py
Lib/test/test_descr.py
Lib/test/test_gdb/util.py
Lib/test/test_importlib/resources/test_functional.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 14bd87eb9c8d8435c88162121f1ab39411e6c035..dd1fa321ecf171209f786a6df993614ecc4d613b 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 4317abf3162c5290e4de969d0f4b1ea3356a3e72..3fc1ade35bef5ae6f95bfe8d8b29727ab933156e 100644 (file)
@@ -3,6 +3,7 @@ import os
 import importlib
 
 from test.support import warnings_helper
+from test.support.testcase import ExtraAssertions
 
 from importlib import resources
 
@@ -28,7 +29,7 @@ class ModuleAnchorMixin:
         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 @@ class FunctionalAPIBase(util.DiskSetup):
             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'),
index d409a2d4a312e638742a20cb26b1a1e343050c2f..a65705aaf53abc74d53b11717c45f21513d2dfa9 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 140ceb747355705306d3edcbb51bab94dcc5365a..89a32c7a1a0d14fb01ea577f4510061adb5e0750 100644 (file)
@@ -46,6 +46,7 @@ import weakref
 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 @@ 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:
@@ -1249,10 +1236,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 0b09010c69d4eac2565c06694811d9b4a076ef99..c39c83f9d0a5c3ab7fe7ca6b265a0a5fa4491f0a 100644 (file)
@@ -26,6 +26,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
@@ -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 @@ 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."""