4
4
_testcapi = import_helper .import_module ('_testcapi' )
5
5
6
6
7
- SIZEOF_PY_HASH_T = _testcapi .SIZEOF_VOID_P
7
+ SIZEOF_VOID_P = _testcapi .SIZEOF_VOID_P
8
+ SIZEOF_PY_HASH_T = SIZEOF_VOID_P
8
9
9
10
10
11
class CAPITest (unittest .TestCase ):
@@ -31,3 +32,48 @@ def test_hash_getfuncdef(self):
31
32
self .assertEqual (func_def .name , hash_info .algorithm )
32
33
self .assertEqual (func_def .hash_bits , hash_info .hash_bits )
33
34
self .assertEqual (func_def .seed_bits , hash_info .seed_bits )
35
+
36
+ def test_hash_pointer (self ):
37
+ # Test Py_HashPointer()
38
+ hash_pointer = _testcapi .hash_pointer
39
+
40
+ UHASH_T_MASK = ((2 ** (8 * SIZEOF_PY_HASH_T )) - 1 )
41
+ HASH_T_MAX = (2 ** (8 * SIZEOF_PY_HASH_T - 1 ) - 1 )
42
+
43
+ def python_hash_pointer (x ):
44
+ # Py_HashPointer() rotates the pointer bits by 4 bits to the right
45
+ x = (x >> 4 ) | ((x & 15 ) << (8 * SIZEOF_VOID_P - 4 ))
46
+
47
+ # Convert unsigned uintptr_t (Py_uhash_t) to signed Py_hash_t
48
+ if HASH_T_MAX < x :
49
+ x = (~ x ) + 1
50
+ x &= UHASH_T_MASK
51
+ x = (~ x ) + 1
52
+ return x
53
+
54
+ if SIZEOF_VOID_P == 8 :
55
+ values = (
56
+ 0xABCDEF1234567890 ,
57
+ 0x1234567890ABCDEF ,
58
+ 0xFEE4ABEDD1CECA5E ,
59
+ )
60
+ else :
61
+ values = (
62
+ 0x12345678 ,
63
+ 0x1234ABCD ,
64
+ 0xDEADCAFE ,
65
+ )
66
+
67
+ for value in values :
68
+ expected = python_hash_pointer (value )
69
+ with self .subTest (value = value ):
70
+ self .assertEqual (hash_pointer (value ), expected ,
71
+ f"hash_pointer({ value :x} ) = "
72
+ f"{ hash_pointer (value ):x} != { expected :x} " )
73
+
74
+ # Py_HashPointer(NULL) returns 0
75
+ self .assertEqual (hash_pointer (0 ), 0 )
76
+
77
+ # Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2
78
+ VOID_P_MAX = - 1 & (2 ** (8 * SIZEOF_VOID_P ) - 1 )
79
+ self .assertEqual (hash_pointer (VOID_P_MAX ), - 2 )
0 commit comments