Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Lib/test/support/testcase.py
Original file line number Diff line number Diff line change
@@ -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):
"""
11 changes: 2 additions & 9 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
@@ -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...
8 changes: 2 additions & 6 deletions Lib/test/test_gdb/util.py
Original file line number Diff line number Diff line change
@@ -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:
9 changes: 2 additions & 7 deletions Lib/test/test_importlib/resources/test_functional.py
Original file line number Diff line number Diff line change
@@ -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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change in this file is going to be overwritten unless ported over to importlib_resources (and backported to support Python 3.9).

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'),
13 changes: 3 additions & 10 deletions Lib/test/test_pyclbr.py
Original file line number Diff line number Diff line change
@@ -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))
21 changes: 2 additions & 19 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
@@ -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')
7 changes: 2 additions & 5 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
@@ -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."""