Skip to content

Commit 66abe98

Browse files
authored
bpo-40275: Move requires_hashdigest() to test.support.hashlib_helper (GH-19716)
Add a new test.support.hashlib_helper submodule.
1 parent 2208134 commit 66abe98

File tree

9 files changed

+71
-68
lines changed

9 files changed

+71
-68
lines changed

Lib/test/support/__init__.py

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import functools
1212
import gc
1313
import glob
14-
import hashlib
1514
import importlib
1615
import importlib.util
1716
import locale
@@ -59,11 +58,6 @@
5958
except ImportError:
6059
resource = None
6160

62-
try:
63-
import _hashlib
64-
except ImportError:
65-
_hashlib = None
66-
6761
__all__ = [
6862
# globals
6963
"PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast",
@@ -81,7 +75,7 @@
8175
"create_empty_file", "can_symlink", "fs_is_case_insensitive",
8276
# unittest
8377
"is_resource_enabled", "requires", "requires_freebsd_version",
84-
"requires_linux_version", "requires_mac_ver", "requires_hashdigest",
78+
"requires_linux_version", "requires_mac_ver",
8579
"check_syntax_error", "check_syntax_warning",
8680
"TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
8781
"transient_internet", "BasicTestRunner", "run_unittest", "run_doctest",
@@ -685,36 +679,6 @@ def wrapper(*args, **kw):
685679
return decorator
686680

687681

688-
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
689-
"""Decorator raising SkipTest if a hashing algorithm is not available
690-
691-
The hashing algorithm could be missing or blocked by a strict crypto
692-
policy.
693-
694-
If 'openssl' is True, then the decorator checks that OpenSSL provides
695-
the algorithm. Otherwise the check falls back to built-in
696-
implementations. The usedforsecurity flag is passed to the constructor.
697-
698-
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
699-
ValueError: unsupported hash type md4
700-
"""
701-
def decorator(func):
702-
@functools.wraps(func)
703-
def wrapper(*args, **kwargs):
704-
try:
705-
if openssl and _hashlib is not None:
706-
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
707-
else:
708-
hashlib.new(digestname, usedforsecurity=usedforsecurity)
709-
except ValueError:
710-
raise unittest.SkipTest(
711-
f"hash digest '{digestname}' is not available."
712-
)
713-
return func(*args, **kwargs)
714-
return wrapper
715-
return decorator
716-
717-
718682
def system_must_validate_cert(f):
719683
"""Skip the test on TLS certificate validation failures."""
720684
@functools.wraps(f)

Lib/test/support/hashlib_helper.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import functools
2+
import hashlib
3+
import unittest
4+
5+
try:
6+
import _hashlib
7+
except ImportError:
8+
_hashlib = None
9+
10+
11+
def requires_hashdigest(digestname, openssl=None, usedforsecurity=True):
12+
"""Decorator raising SkipTest if a hashing algorithm is not available
13+
14+
The hashing algorithm could be missing or blocked by a strict crypto
15+
policy.
16+
17+
If 'openssl' is True, then the decorator checks that OpenSSL provides
18+
the algorithm. Otherwise the check falls back to built-in
19+
implementations. The usedforsecurity flag is passed to the constructor.
20+
21+
ValueError: [digital envelope routines: EVP_DigestInit_ex] disabled for FIPS
22+
ValueError: unsupported hash type md4
23+
"""
24+
def decorator(func):
25+
@functools.wraps(func)
26+
def wrapper(*args, **kwargs):
27+
try:
28+
if openssl and _hashlib is not None:
29+
_hashlib.new(digestname, usedforsecurity=usedforsecurity)
30+
else:
31+
hashlib.new(digestname, usedforsecurity=usedforsecurity)
32+
except ValueError:
33+
raise unittest.SkipTest(
34+
f"hash digest '{digestname}' is not available."
35+
)
36+
return func(*args, **kwargs)
37+
return wrapper
38+
return decorator

Lib/test/test_hashlib.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import warnings
2020
from test import support
2121
from test.support import _4G, bigmemtest, import_fresh_module
22-
from test.support import requires_hashdigest
2322
from http.client import HTTPException
2423

2524
# Were we compiled --with-pydebug or with #define Py_DEBUG?

Lib/test/test_hmac.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import unittest.mock
77
import warnings
88

9-
from test.support import requires_hashdigest
9+
from test.support import hashlib_helper
1010

1111

1212
def ignore_warning(func):
@@ -21,7 +21,7 @@ def wrapper(*args, **kwargs):
2121

2222
class TestVectorsTestCase(unittest.TestCase):
2323

24-
@requires_hashdigest('md5', openssl=True)
24+
@hashlib_helper.requires_hashdigest('md5', openssl=True)
2525
def test_md5_vectors(self):
2626
# Test the HMAC module against test vectors from the RFC.
2727

@@ -79,7 +79,7 @@ def md5test(key, data, digest):
7979
b"and Larger Than One Block-Size Data"),
8080
"6f630fad67cda0ee1fb1f562db3aa53e")
8181

82-
@requires_hashdigest('sha1', openssl=True)
82+
@hashlib_helper.requires_hashdigest('sha1', openssl=True)
8383
def test_sha_vectors(self):
8484
def shatest(key, data, digest):
8585
h = hmac.HMAC(key, data, digestmod=hashlib.sha1)
@@ -272,23 +272,23 @@ def hmactest(key, data, hexdigests):
272272
'134676fb6de0446065c97440fa8c6a58',
273273
})
274274

275-
@requires_hashdigest('sha224', openssl=True)
275+
@hashlib_helper.requires_hashdigest('sha224', openssl=True)
276276
def test_sha224_rfc4231(self):
277277
self._rfc4231_test_cases(hashlib.sha224, 'sha224', 28, 64)
278278

279-
@requires_hashdigest('sha256', openssl=True)
279+
@hashlib_helper.requires_hashdigest('sha256', openssl=True)
280280
def test_sha256_rfc4231(self):
281281
self._rfc4231_test_cases(hashlib.sha256, 'sha256', 32, 64)
282282

283-
@requires_hashdigest('sha384', openssl=True)
283+
@hashlib_helper.requires_hashdigest('sha384', openssl=True)
284284
def test_sha384_rfc4231(self):
285285
self._rfc4231_test_cases(hashlib.sha384, 'sha384', 48, 128)
286286

287-
@requires_hashdigest('sha512', openssl=True)
287+
@hashlib_helper.requires_hashdigest('sha512', openssl=True)
288288
def test_sha512_rfc4231(self):
289289
self._rfc4231_test_cases(hashlib.sha512, 'sha512', 64, 128)
290290

291-
@requires_hashdigest('sha256')
291+
@hashlib_helper.requires_hashdigest('sha256')
292292
def test_legacy_block_size_warnings(self):
293293
class MockCrazyHash(object):
294294
"""Ain't no block_size attribute here."""
@@ -329,29 +329,29 @@ class ConstructorTestCase(unittest.TestCase):
329329
"6c845b47f52b3b47f6590c502db7825aad757bf4fadc8fa972f7cd2e76a5bdeb"
330330
)
331331

332-
@requires_hashdigest('sha256')
332+
@hashlib_helper.requires_hashdigest('sha256')
333333
def test_normal(self):
334334
# Standard constructor call.
335335
try:
336336
hmac.HMAC(b"key", digestmod='sha256')
337337
except Exception:
338338
self.fail("Standard constructor call raised exception.")
339339

340-
@requires_hashdigest('sha256')
340+
@hashlib_helper.requires_hashdigest('sha256')
341341
def test_with_str_key(self):
342342
# Pass a key of type str, which is an error, because it expects a key
343343
# of type bytes
344344
with self.assertRaises(TypeError):
345345
h = hmac.HMAC("key", digestmod='sha256')
346346

347-
@requires_hashdigest('sha256')
347+
@hashlib_helper.requires_hashdigest('sha256')
348348
def test_dot_new_with_str_key(self):
349349
# Pass a key of type str, which is an error, because it expects a key
350350
# of type bytes
351351
with self.assertRaises(TypeError):
352352
h = hmac.new("key", digestmod='sha256')
353353

354-
@requires_hashdigest('sha256')
354+
@hashlib_helper.requires_hashdigest('sha256')
355355
def test_withtext(self):
356356
# Constructor call with text.
357357
try:
@@ -360,7 +360,7 @@ def test_withtext(self):
360360
self.fail("Constructor call with text argument raised exception.")
361361
self.assertEqual(h.hexdigest(), self.expected)
362362

363-
@requires_hashdigest('sha256')
363+
@hashlib_helper.requires_hashdigest('sha256')
364364
def test_with_bytearray(self):
365365
try:
366366
h = hmac.HMAC(bytearray(b"key"), bytearray(b"hash this!"),
@@ -369,15 +369,15 @@ def test_with_bytearray(self):
369369
self.fail("Constructor call with bytearray arguments raised exception.")
370370
self.assertEqual(h.hexdigest(), self.expected)
371371

372-
@requires_hashdigest('sha256')
372+
@hashlib_helper.requires_hashdigest('sha256')
373373
def test_with_memoryview_msg(self):
374374
try:
375375
h = hmac.HMAC(b"key", memoryview(b"hash this!"), digestmod="sha256")
376376
except Exception:
377377
self.fail("Constructor call with memoryview msg raised exception.")
378378
self.assertEqual(h.hexdigest(), self.expected)
379379

380-
@requires_hashdigest('sha256')
380+
@hashlib_helper.requires_hashdigest('sha256')
381381
def test_withmodule(self):
382382
# Constructor call with text and digest module.
383383
try:
@@ -388,7 +388,7 @@ def test_withmodule(self):
388388

389389
class SanityTestCase(unittest.TestCase):
390390

391-
@requires_hashdigest('sha256')
391+
@hashlib_helper.requires_hashdigest('sha256')
392392
def test_exercise_all_methods(self):
393393
# Exercising all methods once.
394394
# This must not raise any exceptions
@@ -404,7 +404,7 @@ def test_exercise_all_methods(self):
404404

405405
class CopyTestCase(unittest.TestCase):
406406

407-
@requires_hashdigest('sha256')
407+
@hashlib_helper.requires_hashdigest('sha256')
408408
def test_attributes(self):
409409
# Testing if attributes are of same type.
410410
h1 = hmac.HMAC(b"key", digestmod="sha256")
@@ -416,7 +416,7 @@ def test_attributes(self):
416416
self.assertEqual(type(h1.outer), type(h2.outer),
417417
"Types of outer don't match.")
418418

419-
@requires_hashdigest('sha256')
419+
@hashlib_helper.requires_hashdigest('sha256')
420420
def test_realcopy(self):
421421
# Testing if the copy method created a real copy.
422422
h1 = hmac.HMAC(b"key", digestmod="sha256")
@@ -428,7 +428,7 @@ def test_realcopy(self):
428428
self.assertTrue(id(h1.outer) != id(h2.outer),
429429
"No real copy of the attribute 'outer'.")
430430

431-
@requires_hashdigest('sha256')
431+
@hashlib_helper.requires_hashdigest('sha256')
432432
def test_equality(self):
433433
# Testing if the copy has the same digests.
434434
h1 = hmac.HMAC(b"key", digestmod="sha256")

Lib/test/test_imaplib.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import socket
1212

1313
from test.support import (reap_threads, verbose, transient_internet,
14-
run_with_tz, run_with_locale, cpython_only,
15-
requires_hashdigest)
14+
run_with_tz, run_with_locale, cpython_only)
15+
from test.support import hashlib_helper
1616
import unittest
1717
from unittest import mock
1818
from datetime import datetime, timezone, timedelta
@@ -385,7 +385,7 @@ def cmd_AUTHENTICATE(self, tag, args):
385385
self.assertEqual(code, 'OK')
386386
self.assertEqual(server.response, b'ZmFrZQ==\r\n') # b64 encoded 'fake'
387387

388-
@requires_hashdigest('md5')
388+
@hashlib_helper.requires_hashdigest('md5')
389389
def test_login_cram_md5_bytes(self):
390390
class AuthHandler(SimpleIMAPHandler):
391391
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
@@ -403,7 +403,7 @@ def cmd_AUTHENTICATE(self, tag, args):
403403
ret, _ = client.login_cram_md5("tim", b"tanstaaftanstaaf")
404404
self.assertEqual(ret, "OK")
405405

406-
@requires_hashdigest('md5')
406+
@hashlib_helper.requires_hashdigest('md5')
407407
def test_login_cram_md5_plain_text(self):
408408
class AuthHandler(SimpleIMAPHandler):
409409
capabilities = 'LOGINDISABLED AUTH=CRAM-MD5'
@@ -849,7 +849,7 @@ def cmd_AUTHENTICATE(self, tag, args):
849849
b'ZmFrZQ==\r\n') # b64 encoded 'fake'
850850

851851
@reap_threads
852-
@requires_hashdigest('md5')
852+
@hashlib_helper.requires_hashdigest('md5')
853853
def test_login_cram_md5(self):
854854

855855
class AuthHandler(SimpleIMAPHandler):

Lib/test/test_poplib.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from unittest import TestCase, skipUnless
1515
from test import support as test_support
16+
from test.support import hashlib_helper
1617
from test.support import socket_helper
1718

1819
HOST = socket_helper.HOST
@@ -311,11 +312,11 @@ def test_noop(self):
311312
def test_rpop(self):
312313
self.assertOK(self.client.rpop('foo'))
313314

314-
@test_support.requires_hashdigest('md5')
315+
@hashlib_helper.requires_hashdigest('md5')
315316
def test_apop_normal(self):
316317
self.assertOK(self.client.apop('foo', 'dummypassword'))
317318

318-
@test_support.requires_hashdigest('md5')
319+
@hashlib_helper.requires_hashdigest('md5')
319320
def test_apop_REDOS(self):
320321
# Replace welcome with very long evil welcome.
321322
# NB The upper bound on welcome length is currently 2048.

Lib/test/test_smtplib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020

2121
import unittest
2222
from test import support, mock_socket
23+
from test.support import hashlib_helper
2324
from test.support import socket_helper
2425
from test.support import threading_setup, threading_cleanup, join_thread
25-
from test.support import requires_hashdigest
2626
from unittest.mock import Mock
2727

2828
HOST = socket_helper.HOST
@@ -1058,7 +1058,7 @@ def testAUTH_LOGIN(self):
10581058
self.assertEqual(resp, (235, b'Authentication Succeeded'))
10591059
smtp.close()
10601060

1061-
@requires_hashdigest('md5')
1061+
@hashlib_helper.requires_hashdigest('md5')
10621062
def testAUTH_CRAM_MD5(self):
10631063
self.serv.add_feature("AUTH CRAM-MD5")
10641064
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',

Lib/test/test_tarfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import tarfile
1212

1313
from test import support
14-
from test.support import script_helper, requires_hashdigest
14+
from test.support import script_helper
1515

1616
# Check for our compression modules.
1717
try:

Lib/test/test_urllib2_localnet.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import hashlib
1010

1111
from test import support
12+
from test.support import hashlib_helper
1213

1314
try:
1415
import ssl
@@ -322,7 +323,7 @@ class ProxyAuthTests(unittest.TestCase):
322323
PASSWD = "test123"
323324
REALM = "TestRealm"
324325

325-
@support.requires_hashdigest("md5")
326+
@hashlib_helper.requires_hashdigest("md5")
326327
def setUp(self):
327328
super(ProxyAuthTests, self).setUp()
328329
# Ignore proxy bypass settings in the environment.

0 commit comments

Comments
 (0)