From 66abe98a816de84f89e2de4aa78cf09056227c25 Mon Sep 17 00:00:00 2001 From: Hai Shi Date: Wed, 29 Apr 2020 09:11:29 +0800 Subject: [PATCH] bpo-40275: Move requires_hashdigest() to test.support.hashlib_helper (GH-19716) Add a new test.support.hashlib_helper submodule. --- Lib/test/support/__init__.py | 38 +----------------------------- Lib/test/support/hashlib_helper.py | 38 ++++++++++++++++++++++++++++++ Lib/test/test_hashlib.py | 1 - Lib/test/test_hmac.py | 38 +++++++++++++++--------------- Lib/test/test_imaplib.py | 10 ++++---- Lib/test/test_poplib.py | 5 ++-- Lib/test/test_smtplib.py | 4 ++-- Lib/test/test_tarfile.py | 2 +- Lib/test/test_urllib2_localnet.py | 3 ++- 9 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 Lib/test/support/hashlib_helper.py diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index f48decc704c..ee5882f237c 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -11,7 +11,6 @@ import fnmatch import functools import gc import glob -import hashlib import importlib import importlib.util import locale @@ -59,11 +58,6 @@ try: except ImportError: resource = None -try: - import _hashlib -except ImportError: - _hashlib = None - __all__ = [ # globals "PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast", @@ -81,7 +75,7 @@ __all__ = [ "create_empty_file", "can_symlink", "fs_is_case_insensitive", # unittest "is_resource_enabled", "requires", "requires_freebsd_version", - "requires_linux_version", "requires_mac_ver", "requires_hashdigest", + "requires_linux_version", "requires_mac_ver", "check_syntax_error", "check_syntax_warning", "TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset", "transient_internet", "BasicTestRunner", "run_unittest", "run_doctest", @@ -685,36 +679,6 @@ def requires_mac_ver(*min_version): return decorator -def requires_hashdigest(digestname, openssl=None, usedforsecurity=True): - """Decorator raising SkipTest if a hashing algorithm is not available - - The hashing algorithm could be missing or blocked by a strict crypto - policy. - - 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. - - ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS - ValueError: unsupported hash type md4 - """ - def decorator(func): - @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) - except ValueError: - raise unittest.SkipTest( - f"hash digest '{digestname}' is not available." - ) - return func(*args, **kwargs) - return wrapper - return decorator - - def system_must_validate_cert(f): """Skip the test on TLS certificate validation failures.""" @functools.wraps(f) diff --git a/Lib/test/support/hashlib_helper.py b/Lib/test/support/hashlib_helper.py new file mode 100644 index 00000000000..a28132a565a --- /dev/null +++ b/Lib/test/support/hashlib_helper.py @@ -0,0 +1,38 @@ +import functools +import hashlib +import unittest + +try: + import _hashlib +except ImportError: + _hashlib = None + + +def requires_hashdigest(digestname, openssl=None, usedforsecurity=True): + """Decorator raising SkipTest if a hashing algorithm is not available + + The hashing algorithm could be missing or blocked by a strict crypto + policy. + + 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. + + ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS + ValueError: unsupported hash type md4 + """ + def decorator(func): + @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) + except ValueError: + raise unittest.SkipTest( + f"hash digest '{digestname}' is not available." + ) + return func(*args, **kwargs) + return wrapper + return decorator diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py index 0e30b2fb11f..33b687e0b40 100644 --- a/Lib/test/test_hashlib.py +++ b/Lib/test/test_hashlib.py @@ -19,7 +19,6 @@ import unittest import warnings from test import support from test.support import _4G, bigmemtest, import_fresh_module -from test.support import requires_hashdigest from http.client import HTTPException # Were we compiled --with-pydebug or with #define Py_DEBUG? diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py index 23c108f6e3c..08086f0e78c 100644 --- a/Lib/test/test_hmac.py +++ b/Lib/test/test_hmac.py @@ -6,7 +6,7 @@ import unittest import unittest.mock import warnings -from test.support import requires_hashdigest +from test.support import hashlib_helper def ignore_warning(func): @@ -21,7 +21,7 @@ def ignore_warning(func): class TestVectorsTestCase(unittest.TestCase): - @requires_hashdigest('md5', openssl=True) + @hashlib_helper.requires_hashdigest('md5', openssl=True) def test_md5_vectors(self): # Test the HMAC module against test vectors from the RFC. @@ -79,7 +79,7 @@ class TestVectorsTestCase(unittest.TestCase): b"and Larger Than One Block-Size Data"), "6f630fad67cda0ee1fb1f562db3aa53e") - @requires_hashdigest('sha1', openssl=True) + @hashlib_helper.requires_hashdigest('sha1', openssl=True) def test_sha_vectors(self): def shatest(key, data, digest): h = hmac.HMAC(key, data, digestmod=hashlib.sha1) @@ -272,23 +272,23 @@ class TestVectorsTestCase(unittest.TestCase): '134676fb6de0446065c97440fa8c6a58', }) - @requires_hashdigest('sha224', openssl=True) + @hashlib_helper.requires_hashdigest('sha224', openssl=True) def test_sha224_rfc4231(self): self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64) - @requires_hashdigest('sha256', openssl=True) + @hashlib_helper.requires_hashdigest('sha256', openssl=True) def test_sha256_rfc4231(self): self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64) - @requires_hashdigest('sha384', openssl=True) + @hashlib_helper.requires_hashdigest('sha384', openssl=True) def test_sha384_rfc4231(self): self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128) - @requires_hashdigest('sha512', openssl=True) + @hashlib_helper.requires_hashdigest('sha512', openssl=True) def test_sha512_rfc4231(self): self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128) - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_legacy_block_size_warnings(self): class MockCrazyHash(object): """Ain't no block_size attribute here.""" @@ -329,7 +329,7 @@ class ConstructorTestCase(unittest.TestCase): "6c845b47f52b3b47f6590c502db7825aad757bf4fadc8fa972f7cd2e76a5bdeb" ) - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_normal(self): # Standard constructor call. try: @@ -337,21 +337,21 @@ class ConstructorTestCase(unittest.TestCase): except Exception: self.fail("Standard constructor call raised exception.") - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_with_str_key(self): # Pass a key of type str, which is an error, because it expects a key # of type bytes with self.assertRaises(TypeError): h = hmac.HMAC("key", digestmod='sha256') - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_dot_new_with_str_key(self): # Pass a key of type str, which is an error, because it expects a key # of type bytes with self.assertRaises(TypeError): h = hmac.new("key", digestmod='sha256') - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_withtext(self): # Constructor call with text. try: @@ -360,7 +360,7 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with text argument raised exception.") self.assertEqual(h.hexdigest(), self.expected) - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_with_bytearray(self): try: h = hmac.HMAC(bytearray(b"key"), bytearray(b"hash this!"), @@ -369,7 +369,7 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with bytearray arguments raised exception.") self.assertEqual(h.hexdigest(), self.expected) - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_with_memoryview_msg(self): try: h = hmac.HMAC(b"key", memoryview(b"hash this!"), digestmod="sha256") @@ -377,7 +377,7 @@ class ConstructorTestCase(unittest.TestCase): self.fail("Constructor call with memoryview msg raised exception.") self.assertEqual(h.hexdigest(), self.expected) - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_withmodule(self): # Constructor call with text and digest module. try: @@ -388,7 +388,7 @@ class ConstructorTestCase(unittest.TestCase): class SanityTestCase(unittest.TestCase): - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_exercise_all_methods(self): # Exercising all methods once. # This must not raise any exceptions @@ -404,7 +404,7 @@ class SanityTestCase(unittest.TestCase): class CopyTestCase(unittest.TestCase): - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_attributes(self): # Testing if attributes are of same type. h1 = hmac.HMAC(b"key", digestmod="sha256") @@ -416,7 +416,7 @@ class CopyTestCase(unittest.TestCase): self.assertEqual(type(h1.outer), type(h2.outer), "Types of outer don't match.") - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_realcopy(self): # Testing if the copy method created a real copy. h1 = hmac.HMAC(b"key", digestmod="sha256") @@ -428,7 +428,7 @@ class CopyTestCase(unittest.TestCase): self.assertTrue(id(h1.outer) != id(h2.outer), "No real copy of the attribute 'outer'.") - @requires_hashdigest('sha256') + @hashlib_helper.requires_hashdigest('sha256') def test_equality(self): # Testing if the copy has the same digests. h1 = hmac.HMAC(b"key", digestmod="sha256") diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 69ee63b18c3..ce601565cf1 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -11,8 +11,8 @@ import threading import socket from test.support import (reap_threads, verbose, transient_internet, - run_with_tz, run_with_locale, cpython_only, - requires_hashdigest) + run_with_tz, run_with_locale, cpython_only) +from test.support import hashlib_helper import unittest from unittest import mock from datetime import datetime, timezone, timedelta @@ -385,7 +385,7 @@ class NewIMAPTestsMixin(): self.assertEqual(code, 'OK') self.assertEqual(server.response, b'ZmFrZQ==\r\n') # b64 encoded 'fake' - @requires_hashdigest('md5') + @hashlib_helper.requires_hashdigest('md5') def test_login_cram_md5_bytes(self): class AuthHandler(SimpleIMAPHandler): capabilities = 'LOGINDISABLED AUTH=CRAM-MD5' @@ -403,7 +403,7 @@ class NewIMAPTestsMixin(): ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf") self.assertEqual(ret, "OK") - @requires_hashdigest('md5') + @hashlib_helper.requires_hashdigest('md5') def test_login_cram_md5_plain_text(self): class AuthHandler(SimpleIMAPHandler): capabilities = 'LOGINDISABLED AUTH=CRAM-MD5' @@ -849,7 +849,7 @@ class ThreadedNetworkedTests(unittest.TestCase): b'ZmFrZQ==\r\n') # b64 encoded 'fake' @reap_threads - @requires_hashdigest('md5') + @hashlib_helper.requires_hashdigest('md5') def test_login_cram_md5(self): class AuthHandler(SimpleIMAPHandler): diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py index d4877b1fbbc..b670afcf4e6 100644 --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -13,6 +13,7 @@ import threading from unittest import TestCase, skipUnless from test import support as test_support +from test.support import hashlib_helper from test.support import socket_helper HOST = socket_helper.HOST @@ -311,11 +312,11 @@ class TestPOP3Class(TestCase): def test_rpop(self): self.assertOK(self.client.rpop('foo')) - @test_support.requires_hashdigest('md5') + @hashlib_helper.requires_hashdigest('md5') def test_apop_normal(self): self.assertOK(self.client.apop('foo', 'dummypassword')) - @test_support.requires_hashdigest('md5') + @hashlib_helper.requires_hashdigest('md5') def test_apop_REDOS(self): # Replace welcome with very long evil welcome. # NB The upper bound on welcome length is currently 2048. diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index d1ffb368a4f..c1bd2e29125 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -20,9 +20,9 @@ import threading import unittest from test import support, mock_socket +from test.support import hashlib_helper from test.support import socket_helper from test.support import threading_setup, threading_cleanup, join_thread -from test.support import requires_hashdigest from unittest.mock import Mock HOST = socket_helper.HOST @@ -1058,7 +1058,7 @@ class SMTPSimTests(unittest.TestCase): self.assertEqual(resp, (235, b'Authentication Succeeded')) smtp.close() - @requires_hashdigest('md5') + @hashlib_helper.requires_hashdigest('md5') def testAUTH_CRAM_MD5(self): self.serv.add_feature("AUTH CRAM-MD5") smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 99196f60431..25e9e936044 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -11,7 +11,7 @@ import unittest.mock import tarfile from test import support -from test.support import script_helper, requires_hashdigest +from test.support import script_helper # Check for our compression modules. try: diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py index 8cfb214c9af..421b9f7de2e 100644 --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -9,6 +9,7 @@ import unittest import hashlib from test import support +from test.support import hashlib_helper try: import ssl @@ -322,7 +323,7 @@ class ProxyAuthTests(unittest.TestCase): PASSWD = "test123" REALM = "TestRealm" - @support.requires_hashdigest("md5") + @hashlib_helper.requires_hashdigest("md5") def setUp(self): super(ProxyAuthTests, self).setUp() # Ignore proxy bypass settings in the environment.