diff options
author | Bénédikt Tran <10796600+picnixz@users.noreply.github.com> | 2025-03-17 10:10:03 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-17 10:10:03 (GMT) |
commit | de8890f5ab1c1e767029d46c20f513beefc47b18 (patch) | |
tree | e1e33cc3247e57c099a2c61f463e7e65efb9ec1e /Lib/test/support/hashlib_helper.py | |
parent | 85c04f80fd3a3098674f60038f248c076a476acf (diff) | |
download | cpython-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.py | 92 |
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 |