https://github.com/python/cpython/commit/de8890f5ab1c1e767029d46c20f513beefc47b18
commit: de8890f5ab1c1e767029d46c20f513beefc47b18
branch: main
author: Bénédikt Tran <10796600+picn...@users.noreply.github.com>
committer: picnixz <10796600+picn...@users.noreply.github.com>
date: 2025-03-17T11:10:03+01:00
summary:

gh-130149: cleanup refactorization of `test_hmac.py` (#131318)

New features:

* refactor `hashlib_helper.requires_hashdigest` in prevision of a future
  `hashlib_helper.requires_builtin_hashdigest` for built-in hashes only
* add `hashlib_helper.requires_openssl_hashdigest` to request OpenSSL
   hashes, assuming that `_hashlib` exists.

Refactoring:

* split hmac.copy() test by implementation
* update how algorithms are discovered for RFC test cases
* simplify how OpenSSL hash digests are requested
* refactor hexdigest tests for RFC test vectors
* typo fix: `assert_hmac_hexdigest_by_new` -> `assert_hmac_hexdigest_by_name`

Improvements:

* strengthen contract on `hmac_new_by_name` and `hmac_digest_by_name`
* rename mixin classes to better match their responsibility

files:
M Lib/hmac.py
M Lib/test/support/hashlib_helper.py
M Lib/test/test_hmac.py

diff --git a/Lib/hmac.py b/Lib/hmac.py
index 8b4eb2fe741e60..30b6b478734dfc 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -65,6 +65,7 @@ def __init__(self, key, msg=None, digestmod=''):
 
     def _init_hmac(self, key, msg, digestmod):
         self._hmac = _hashopenssl.hmac_new(key, msg, digestmod=digestmod)
+        self._inner = self._outer = None  # because the slots are defined
         self.digest_size = self._hmac.digest_size
         self.block_size = self._hmac.block_size
 
diff --git a/Lib/test/support/hashlib_helper.py 
b/Lib/test/support/hashlib_helper.py
index 477e0f110eabba..bed3d696cb384d 100644
--- a/Lib/test/support/hashlib_helper.py
+++ b/Lib/test/support/hashlib_helper.py
@@ -1,6 +1,7 @@
 import functools
 import hashlib
 import unittest
+from test.support.import_helper import import_module
 
 try:
     import _hashlib
@@ -12,44 +13,81 @@ def requires_hashlib():
     return unittest.skipIf(_hashlib is None, "requires _hashlib")
 
 
+def _decorate_func_or_class(func_or_class, decorator_func):
+    if not isinstance(func_or_class, type):
+        return decorator_func(func_or_class)
+
+    decorated_class = func_or_class
+    setUpClass = decorated_class.__dict__.get('setUpClass')
+    if setUpClass is None:
+        def setUpClass(cls):
+            super(decorated_class, cls).setUpClass()
+        setUpClass.__qualname__ = decorated_class.__qualname__ + '.setUpClass'
+        setUpClass.__module__ = decorated_class.__module__
+    else:
+        setUpClass = setUpClass.__func__
+    setUpClass = classmethod(decorator_func(setUpClass))
+    decorated_class.setUpClass = setUpClass
+    return decorated_class
+
+
 def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
-    """Decorator raising SkipTest if a hashing algorithm is not available
+    """Decorator raising SkipTest if a hashing algorithm is not available.
 
-    The hashing algorithm could be missing or blocked by a strict crypto
-    policy.
+    The hashing algorithm may be missing, blocked by a strict crypto policy,
+    or Python may be configured with `--with-builtin-hashlib-hashes=no`.
 
     If 'openssl' is True, then the decorator checks that OpenSSL provides
-    the algorithm. Otherwise the check falls back to built-in
-    implementations. The usedforsecurity flag is passed to the constructor.
+    the algorithm. Otherwise the check falls back to (optional) built-in
+    HACL* implementations.
+
+    The usedforsecurity flag is passed to the constructor but has no effect
+    on HACL* implementations.
 
+    Examples of exceptions being suppressed:
     ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for 
FIPS
     ValueError: unsupported hash type md4
     """
+    if openssl and _hashlib is not None:
+        def test_availability():
+            _hashlib.new(digestname, usedforsecurity=usedforsecurity)
+    else:
+        def test_availability():
+            hashlib.new(digestname, usedforsecurity=usedforsecurity)
+
+    def decorator_func(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            try:
+                test_availability()
+            except ValueError as exc:
+                msg = f"missing hash algorithm: {digestname!r}"
+                raise unittest.SkipTest(msg) from exc
+            return func(*args, **kwargs)
+        return wrapper
+
     def decorator(func_or_class):
-        if isinstance(func_or_class, type):
-            setUpClass = func_or_class.__dict__.get('setUpClass')
-            if setUpClass is None:
-                def setUpClass(cls):
-                    super(func_or_class, cls).setUpClass()
-                setUpClass.__qualname__ = func_or_class.__qualname__ + 
'.setUpClass'
-                setUpClass.__module__ = func_or_class.__module__
-            else:
-                setUpClass = setUpClass.__func__
-            setUpClass = classmethod(decorator(setUpClass))
-            func_or_class.setUpClass = setUpClass
-            return func_or_class
-
-        @functools.wraps(func_or_class)
+        return _decorate_func_or_class(func_or_class, decorator_func)
+    return decorator
+
+
+def requires_openssl_hashdigest(digestname, *, usedforsecurity=True):
+    """Decorator raising SkipTest if an OpenSSL hashing algorithm is missing.
+
+    The hashing algorithm may be missing or blocked by a strict crypto policy.
+    """
+    def decorator_func(func):
+        @requires_hashlib()
+        @functools.wraps(func)
         def wrapper(*args, **kwargs):
             try:
-                if openssl and _hashlib is not None:
-                    _hashlib.new(digestname, usedforsecurity=usedforsecurity)
-                else:
-                    hashlib.new(digestname, usedforsecurity=usedforsecurity)
+                _hashlib.new(digestname, usedforsecurity=usedforsecurity)
             except ValueError:
-                raise unittest.SkipTest(
-                    f"hash digest {digestname!r} is not available."
-                )
-            return func_or_class(*args, **kwargs)
+                msg = f"missing OpenSSL hash algorithm: {digestname!r}"
+                raise unittest.SkipTest(msg)
+            return func(*args, **kwargs)
         return wrapper
+
+    def decorator(func_or_class):
+        return _decorate_func_or_class(func_or_class, decorator_func)
     return decorator
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index c8c806dc1baf8b..ed96af2b2d874b 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -61,7 +61,11 @@ class CreatorMixin:
     """Mixin exposing a method creating a HMAC object."""
 
     def hmac_new(self, key, msg=None, digestmod=None):
-        """Create a new HMAC object."""
+        """Create a new HMAC object.
+
+        Implementations should accept arbitrary 'digestmod' as this
+        method can be used to test which exceptions are being raised.
+        """
         raise NotImplementedError
 
     def bind_hmac_new(self, digestmod):
@@ -73,7 +77,11 @@ class DigestMixin:
     """Mixin exposing a method computing a HMAC digest."""
 
     def hmac_digest(self, key, msg=None, digestmod=None):
-        """Compute a HMAC digest."""
+        """Compute a HMAC digest.
+
+        Implementations should accept arbitrary 'digestmod' as this
+        method can be used to test which exceptions are being raised.
+        """
         raise NotImplementedError
 
     def bind_hmac_digest(self, digestmod):
@@ -120,7 +128,7 @@ def hmac_digest(self, key, msg=None, digestmod=None):
         return _hashlib.hmac_digest(key, msg, digest=digestmod)
 
 
-class CheckerMixin:
+class ObjectCheckerMixin:
     """Mixin for checking HMAC objects (pure Python, OpenSSL or built-in)."""
 
     def check_object(self, h, hexdigest, hashname, digest_size, block_size):
@@ -141,10 +149,10 @@ def check_hexdigest(self, h, hexdigest, digest_size):
         self.assertEqual(h.hexdigest().upper(), hexdigest.upper())
 
 
-class TestVectorsMixin(CreatorMixin, DigestMixin, CheckerMixin):
-    """Mixin class for all test vectors test cases."""
+class AssertersMixin(CreatorMixin, DigestMixin, ObjectCheckerMixin):
+    """Mixin class for common tests."""
 
-    def hmac_new_by_name(self, key, msg=None, hashname=None):
+    def hmac_new_by_name(self, key, msg=None, *, hashname):
         """Alternative implementation of hmac_new().
 
         This is typically useful when one needs to test against an HMAC
@@ -152,13 +160,22 @@ def hmac_new_by_name(self, key, msg=None, hashname=None):
         by their name (all HMAC implementations must at least recognize
         hash functions by their names but some may use aliases such as
         `hashlib.sha1` instead of "sha1").
+
+        Unlike hmac_new(), this method may assert the type of 'hashname'
+        as it should only be used in tests that are expected to create
+        a HMAC object.
         """
-        self.assertIsInstance(hashname, str | None)
+        self.assertIsInstance(hashname, str)
         return self.hmac_new(key, msg, digestmod=hashname)
 
-    def hmac_digest_by_name(self, key, msg=None, hashname=None):
-        """Alternative implementation of hmac_digest()."""
-        self.assertIsInstance(hashname, str | None)
+    def hmac_digest_by_name(self, key, msg=None, *, hashname):
+        """Alternative implementation of hmac_digest().
+
+        Unlike hmac_digest(), this method may assert the type of 'hashname'
+        as it should only be used in tests that are expected to compute a
+        HMAC digest.
+        """
+        self.assertIsInstance(hashname, str)
         return self.hmac_digest(key, msg, digestmod=hashname)
 
     def assert_hmac(
@@ -196,7 +213,7 @@ def assert_hmac(
         self.assert_hmac_new_by_name(
             key, msg, hexdigest, hashname, digest_size, block_size
         )
-        self.assert_hmac_hexdigest_by_new(
+        self.assert_hmac_hexdigest_by_name(
             key, msg, hexdigest, hashname, digest_size
         )
 
@@ -255,16 +272,28 @@ def assert_hmac_hexdigest(
         self, key, msg, hexdigest, digestmod, digest_size,
     ):
         """Check a HMAC digest computed by hmac_digest()."""
-        d = self.hmac_digest(key, msg, digestmod=digestmod)
-        self.assertEqual(len(d), digest_size)
-        self.assertEqual(d, binascii.unhexlify(hexdigest))
+        self._check_hmac_hexdigest(
+            key, msg, hexdigest, digest_size,
+            hmac_digest_func=self.hmac_digest,
+            hmac_digest_kwds={'digestmod': digestmod},
+        )
 
-    def assert_hmac_hexdigest_by_new(
+    def assert_hmac_hexdigest_by_name(
         self, key, msg, hexdigest, hashname, digest_size
     ):
         """Check a HMAC digest computed by hmac_digest_by_name()."""
-        self.assertIsInstance(hashname, str | None)
-        d = self.hmac_digest_by_name(key, msg, hashname=hashname)
+        self.assertIsInstance(hashname, str)
+        self._check_hmac_hexdigest(
+            key, msg, hexdigest, digest_size,
+            hmac_digest_func=self.hmac_digest_by_name,
+            hmac_digest_kwds={'hashname': hashname},
+        )
+
+    def _check_hmac_hexdigest(
+        self, key, msg, hexdigest, digest_size,
+        hmac_digest_func, hmac_digest_kwds,
+    ):
+        d = hmac_digest_func(key, msg, **hmac_digest_kwds)
         self.assertEqual(len(d), digest_size)
         self.assertEqual(d, binascii.unhexlify(hexdigest))
 
@@ -279,7 +308,7 @@ def assert_hmac_extra_cases(
         self.check_object(h1, hexdigest, hashname, digest_size, block_size)
 
 
-class PyTestVectorsMixin(PyModuleMixin, TestVectorsMixin):
+class PyAssertersMixin(PyModuleMixin, AssertersMixin):
 
     def assert_hmac_extra_cases(
         self, key, msg, hexdigest, digestmod, hashname, digest_size, block_size
@@ -293,46 +322,62 @@ def assert_hmac_extra_cases(
         self.check_object(h, hexdigest, hashname, digest_size, block_size)
 
 
-class OpenSSLTestVectorsMixin(TestVectorsMixin):
-
-    def hmac_new(self, key, msg=None, digestmod=None):
-        return _hashlib.hmac_new(key, msg, digestmod=digestmod)
-
-    def hmac_digest(self, key, msg=None, digestmod=None):
-        return _hashlib.hmac_digest(key, msg, digest=digestmod)
+class OpenSSLAssertersMixin(ThroughOpenSSLAPIMixin, AssertersMixin):
 
-    def hmac_new_by_name(self, key, msg=None, hashname=None):
-        # ignore 'digestmod' and use the exact openssl function
+    def hmac_new_by_name(self, key, msg=None, *, hashname):
+        self.assertIsInstance(hashname, str)
         openssl_func = getattr(_hashlib, f"openssl_{hashname}")
         return self.hmac_new(key, msg, digestmod=openssl_func)
 
-    def hmac_digest_by_name(self, key, msg=None, hashname=None):
+    def hmac_digest_by_name(self, key, msg=None, *, hashname):
+        self.assertIsInstance(hashname, str)
         openssl_func = getattr(_hashlib, f"openssl_{hashname}")
         return self.hmac_digest(key, msg, digestmod=openssl_func)
 
 
-class RFCTestCasesMixin(TestVectorsMixin):
-    """Test HMAC implementations against test vectors from the RFC.
-
-    Subclasses must override the 'md5' and other 'sha*' attributes
-    to test the implementations. Their value can be a string, a callable,
-    or a PEP-257 module.
-    """
+class HashFunctionsTrait:
+    """Trait class for 'hashfunc' in hmac_new() and hmac_digest()."""
 
     ALGORITHMS = [
         'md5', 'sha1',
         'sha224', 'sha256', 'sha384', 'sha512',
     ]
 
-    # Those will be automatically set to non-None on subclasses
-    # as they are set by __init_subclass()__.
-    md5 = sha1 = sha224 = sha256 = sha384 = sha512 = None
+    # By default, a missing algorithm skips the test that uses it.
+    md5 = sha1 = sha224 = sha256 = sha384 = sha512 = property(
+        lambda self: self.skipTest("missing hash function")
+    )
+
+
+class WithOpenSSLHashFunctions(HashFunctionsTrait):
+    """Test a HMAC implementation with an OpenSSL-based callable 'hashfunc'."""
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+        for name in cls.ALGORITHMS:
+            @property
+            @hashlib_helper.requires_openssl_hashdigest(name)
+            def func(self, *, __name=name):  # __name needed to bind 'name'
+                return getattr(_hashlib, f'openssl_{__name}')
+            setattr(cls, name, func)
+
+
+class WithNamedHashFunctions(HashFunctionsTrait):
+    """Test a HMAC implementation with a named 'hashfunc'."""
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
 
-    def __init_subclass__(cls, *args, **kwargs):
-        super().__init_subclass__(*args, **kwargs)
         for name in cls.ALGORITHMS:
             setattr(cls, name, name)
 
+
+class RFCTestCaseMixin(HashFunctionsTrait):
+    """Test HMAC implementations against test vectors from the RFC."""
+
     def test_md5(self):
         def md5test(key, msg, hexdigest):
             self.assert_hmac(key, msg, hexdigest, self.md5, "md5", 16, 64)
@@ -412,7 +457,6 @@ def test_sha2_512_rfc4231(self):
         self._test_sha2_rfc4231(self.sha512, 'sha512', 64, 128)
 
     def _test_sha2_rfc4231(self, hashfunc, hashname, digest_size, block_size):
-
         def hmactest(key, data, hexdigests):
             hexdigest = hexdigests[hashname]
 
@@ -531,57 +575,9 @@ def hmactest(key, data, hexdigests):
                                '134676fb6de0446065c97440fa8c6a58',
                  })
 
-    @hashlib_helper.requires_hashdigest('sha256')
-    def test_legacy_block_size_warnings(self):
-        class MockCrazyHash(object):
-            """Ain't no block_size attribute here."""
-            def __init__(self, *args):
-                self._x = hashlib.sha256(*args)
-                self.digest_size = self._x.digest_size
-            def update(self, v):
-                self._x.update(v)
-            def digest(self):
-                return self._x.digest()
-
-        with warnings.catch_warnings():
-            warnings.simplefilter('error', RuntimeWarning)
-            with self.assertRaises(RuntimeWarning):
-                hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
-                self.fail('Expected warning about missing block_size')
-
-            MockCrazyHash.block_size = 1
-            with self.assertRaises(RuntimeWarning):
-                hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
-                self.fail('Expected warning about small block_size')
-
-    def test_with_fallback(self):
-        cache = getattr(hashlib, '__builtin_constructor_cache')
-        try:
-            cache['foo'] = hashlib.sha256
-            hexdigest = hmac.digest(b'key', b'message', 'foo').hex()
-            expected = ('6e9ef29b75fffc5b7abae527d58fdadb'
-                        '2fe42e7219011976917343065f58ed4a')
-            self.assertEqual(hexdigest, expected)
-        finally:
-            cache.pop('foo')
 
-
-class RFCWithOpenSSLHashFunctionTestCasesMixin(RFCTestCasesMixin):
-
-    def __init_subclass__(cls, *args, **kwargs):
-        super().__init_subclass__(*args, **kwargs)
-
-        for name in cls.ALGORITHMS:
-            @property
-            @hashlib_helper.requires_hashlib()
-            @hashlib_helper.requires_hashdigest(name, openssl=True)
-            def func(self, *, __name=name):  # __name needed to bind 'name'
-                return getattr(_hashlib, f'openssl_{__name}')
-            setattr(cls, name, func)
-
-
-class PyRFCTestCase(PyTestVectorsMixin, ThroughObjectMixin,
-                    RFCWithOpenSSLHashFunctionTestCasesMixin,
+class PyRFCTestCase(ThroughObjectMixin, PyAssertersMixin,
+                    WithOpenSSLHashFunctions, RFCTestCaseMixin,
                     unittest.TestCase):
     """Python implementation of HMAC using hmac.HMAC().
 
@@ -589,8 +585,8 @@ class PyRFCTestCase(PyTestVectorsMixin, ThroughObjectMixin,
     """
 
 
-class PyDotNewRFCTestCase(PyTestVectorsMixin, ThroughModuleAPIMixin,
-                          RFCWithOpenSSLHashFunctionTestCasesMixin,
+class PyDotNewRFCTestCase(ThroughModuleAPIMixin, PyAssertersMixin,
+                          WithOpenSSLHashFunctions, RFCTestCaseMixin,
                           unittest.TestCase):
     """Python implementation of HMAC using hmac.new().
 
@@ -598,12 +594,13 @@ class PyDotNewRFCTestCase(PyTestVectorsMixin, 
ThroughModuleAPIMixin,
     """
 
 
-class OpenSSLRFCTestCase(OpenSSLTestVectorsMixin,
-                         RFCWithOpenSSLHashFunctionTestCasesMixin,
+class OpenSSLRFCTestCase(OpenSSLAssertersMixin,
+                         WithOpenSSLHashFunctions, RFCTestCaseMixin,
                          unittest.TestCase):
     """OpenSSL implementation of HMAC.
 
-    The underlying hash functions are also OpenSSL-based."""
+    The underlying hash functions are also OpenSSL-based.
+    """
 
 
 # TODO(picnixz): once we have a HACL* HMAC, we should also test the Python
@@ -668,7 +665,7 @@ def _invalid_digestmod_cases(self, func, key, msg, choices):
         return cases
 
 
-class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, CheckerMixin):
+class ConstructorTestCaseMixin(CreatorMixin, DigestMixin, ObjectCheckerMixin):
     """HMAC constructor tests based on HMAC-SHA-2/256."""
 
     key = b"key"
@@ -847,7 +844,7 @@ def test_repr(self):
         self.assertStartsWith(repr(h), "<hmac.HMAC object at")
 
 
-@hashlib_helper.requires_hashdigest('sha256', openssl=True)
+@hashlib_helper.requires_openssl_hashdigest('sha256')
 class OpenSSLSanityTestCase(ThroughOpenSSLAPIMixin, SanityTestCaseMixin,
                             unittest.TestCase):
 
@@ -899,45 +896,50 @@ def HMAC(self, key, msg=None):
         return self.hmac.HMAC(key, msg, digestmod='sha256')
 
 
-@hashlib_helper.requires_hashlib()
-@hashlib_helper.requires_hashdigest('sha256', openssl=True)
+@hashlib_helper.requires_openssl_hashdigest('sha256')
 class OpenSSLUpdateTestCase(UpdateTestCaseMixin, unittest.TestCase):
 
     def HMAC(self, key, msg=None):
         return hmac.new(key, msg, digestmod='sha256')
 
 
+class CopyBaseTestCase:
+
+    def test_attributes(self):
+        raise NotImplementedError
+
+    def test_realcopy(self):
+        raise NotImplementedError
+
+
 @hashlib_helper.requires_hashdigest('sha256')
-class CopyTestCase(unittest.TestCase):
+class PythonCopyTestCase(CopyBaseTestCase, unittest.TestCase):
 
-    def test_attributes_old(self):
+    def test_attributes(self):
         # Testing if attributes are of same type.
         h1 = hmac.HMAC.__new__(hmac.HMAC)
         h1._init_old(b"key", b"msg", digestmod="sha256")
+        self.assertIsNone(h1._hmac)
+        self.assertIsNotNone(h1._inner)
+        self.assertIsNotNone(h1._outer)
+
         h2 = h1.copy()
+        self.assertIsNone(h2._hmac)
+        self.assertIsNotNone(h2._inner)
+        self.assertIsNotNone(h2._outer)
         self.assertEqual(type(h1._inner), type(h2._inner))
         self.assertEqual(type(h1._outer), type(h2._outer))
 
-    def test_realcopy_old(self):
+    def test_realcopy(self):
         # Testing if the copy method created a real copy.
         h1 = hmac.HMAC.__new__(hmac.HMAC)
         h1._init_old(b"key", b"msg", digestmod="sha256")
-        self.assertIsNone(h1._hmac)
-
         h2 = h1.copy()
-        self.assertIsNone(h2._hmac)
         # Using id() in case somebody has overridden __eq__/__ne__.
         self.assertNotEqual(id(h1), id(h2))
         self.assertNotEqual(id(h1._inner), id(h2._inner))
         self.assertNotEqual(id(h1._outer), id(h2._outer))
 
-    @hashlib_helper.requires_hashlib()
-    def test_realcopy_hmac(self):
-        h1 = hmac.HMAC.__new__(hmac.HMAC)
-        h1._init_hmac(b"key", b"msg", digestmod="sha256")
-        h2 = h1.copy()
-        self.assertNotEqual(id(h1._hmac), id(h2._hmac))
-
     def test_equality(self):
         # Testing if the copy has the same digests.
         h1 = hmac.HMAC(b"key", digestmod="sha256")
@@ -951,11 +953,47 @@ def test_equality_new(self):
         h1 = hmac.new(b"key", digestmod="sha256")
         h1.update(b"some random text")
         h2 = h1.copy()
+        # Using id() in case somebody has overridden __eq__/__ne__.
         self.assertNotEqual(id(h1), id(h2))
         self.assertEqual(h1.digest(), h2.digest())
         self.assertEqual(h1.hexdigest(), h2.hexdigest())
 
 
+class ExtensionCopyTestCase(CopyBaseTestCase):
+
+    def init(self, h):
+        """Call the dedicate init() method to test."""
+        raise NotImplementedError
+
+    def test_attributes(self):
+        # Testing if attributes are of same type.
+        h1 = hmac.HMAC.__new__(hmac.HMAC)
+
+        self.init(h1)
+        self.assertIsNotNone(h1._hmac)
+        self.assertIsNone(h1._inner)
+        self.assertIsNone(h1._outer)
+
+        h2 = h1.copy()
+        self.assertIsNotNone(h2._hmac)
+        self.assertIsNone(h2._inner)
+        self.assertIsNone(h2._outer)
+
+    def test_realcopy(self):
+        h1 = hmac.HMAC.__new__(hmac.HMAC)
+        self.init(h1)
+        h2 = h1.copy()
+        # Using id() in case somebody has overridden __eq__/__ne__.
+        self.assertNotEqual(id(h1._hmac), id(h2._hmac))
+
+
+@hashlib_helper.requires_openssl_hashdigest('sha256')
+class OpenSSLCopyTestCase(ExtensionCopyTestCase, unittest.TestCase):
+
+    def init(self, h):
+        h._init_hmac(b"key", b"msg", digestmod="sha256")
+
+
 class CompareDigestMixin:
 
     @staticmethod
@@ -1087,5 +1125,44 @@ class OperatorCompareDigestTestCase(CompareDigestMixin, 
unittest.TestCase):
     compare_digest = operator_compare_digest
 
 
+class PyMiscellaneousTests(unittest.TestCase):
+    """Miscellaneous tests for the pure Python HMAC module."""
+
+    @hashlib_helper.requires_hashdigest('sha256')
+    def test_legacy_block_size_warnings(self):
+        class MockCrazyHash(object):
+            """Ain't no block_size attribute here."""
+            def __init__(self, *args):
+                self._x = hashlib.sha256(*args)
+                self.digest_size = self._x.digest_size
+            def update(self, v):
+                self._x.update(v)
+            def digest(self):
+                return self._x.digest()
+
+        with warnings.catch_warnings():
+            warnings.simplefilter('error', RuntimeWarning)
+            with self.assertRaises(RuntimeWarning):
+                hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
+                self.fail('Expected warning about missing block_size')
+
+            MockCrazyHash.block_size = 1
+            with self.assertRaises(RuntimeWarning):
+                hmac.HMAC(b'a', b'b', digestmod=MockCrazyHash)
+                self.fail('Expected warning about small block_size')
+
+    @hashlib_helper.requires_hashdigest('sha256')
+    def test_with_fallback(self):
+        cache = getattr(hashlib, '__builtin_constructor_cache')
+        try:
+            cache['foo'] = hashlib.sha256
+            hexdigest = hmac.digest(b'key', b'message', 'foo').hex()
+            expected = ('6e9ef29b75fffc5b7abae527d58fdadb'
+                        '2fe42e7219011976917343065f58ed4a')
+            self.assertEqual(hexdigest, expected)
+        finally:
+            cache.pop('foo')
+
+
 if __name__ == "__main__":
     unittest.main()

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to