summaryrefslogtreecommitdiffstats
path: root/Lib/test/support/hashlib_helper.py
diff options
context:
space:
mode:
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>2025-03-17 10:10:03 (GMT)
committerGitHub <noreply@github.com>2025-03-17 10:10:03 (GMT)
commitde8890f5ab1c1e767029d46c20f513beefc47b18 (patch)
treee1e33cc3247e57c099a2c61f463e7e65efb9ec1e /Lib/test/support/hashlib_helper.py
parent85c04f80fd3a3098674f60038f248c076a476acf (diff)
downloadcpython-de8890f5ab1c1e767029d46c20f513beefc47b18.zip
cpython-de8890f5ab1c1e767029d46c20f513beefc47b18.tar.gz
cpython-de8890f5ab1c1e767029d46c20f513beefc47b18.tar.bz2
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
Diffstat (limited to 'Lib/test/support/hashlib_helper.py')
-rw-r--r--Lib/test/support/hashlib_helper.py92
1 files changed, 65 insertions, 27 deletions
diff --git a/Lib/test/support/hashlib_helper.py b/Lib/test/support/hashlib_helper.py
index 477e0f1..bed3d69 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