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]