Skip to content

Commit f81f0f9

Browse files
committed
gh-139122: Add node hooks for UUIDv6 tests
1 parent f4cb529 commit f81f0f9

8 files changed

Lines changed: 111 additions & 79 deletions

File tree

‎Include/internal/pycore_global_objects_fini_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_global_strings.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ struct _Py_global_strings {
690690
STRUCT_FOR_ID(nlocals)
691691
STRUCT_FOR_ID(node)
692692
STRUCT_FOR_ID(node_depth)
693+
STRUCT_FOR_ID(node_func)
693694
STRUCT_FOR_ID(node_offset)
694695
STRUCT_FOR_ID(nothing)
695696
STRUCT_FOR_ID(ns)

‎Include/internal/pycore_runtime_init_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Include/internal/pycore_unicodeobject_generated.h‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Lib/test/test_uuid.py‎

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -898,20 +898,19 @@ def test_uuid5(self):
898898
equal(str(u), v)
899899

900900
@contextlib.contextmanager
901-
def _uuid6_hooks(self, nanoseconds, *, random_func=None):
901+
def _uuid6_hooks(self, nanoseconds, *, random_func=None, node_func=None):
902902
time_func = nanoseconds if callable(nanoseconds) else lambda: nanoseconds
903-
with support.swap_item(sys.modules, 'uuid', self.uuid):
904-
self.uuid._install_py_hooks(random_func=random_func,
905-
time_func=time_func)
903+
self.uuid._install_py_hooks(random_func=random_func,
904+
time_func=time_func, node_func=node_func)
905+
if self.is_c_uuid:
906+
self.uuid._install_c_hooks(random_func=random_func,
907+
time_func=time_func, node_func=node_func)
908+
try:
909+
yield
910+
finally:
911+
self.uuid._install_py_hooks(random_func=None, time_func=None)
906912
if self.is_c_uuid:
907-
self.uuid._install_c_hooks(random_func=random_func,
908-
time_func=time_func)
909-
try:
910-
yield
911-
finally:
912-
self.uuid._install_py_hooks(random_func=None, time_func=None)
913-
if self.is_c_uuid:
914-
self.uuid._install_c_hooks(random_func=None, time_func=None)
913+
self.uuid._install_c_hooks(random_func=None, time_func=None)
915914

916915
def test_uuid6(self):
917916
equal = self.assertEqual
@@ -928,10 +927,8 @@ def random_func(size):
928927
data = (fake_clock_seq | 0xc000).to_bytes(2, 'big')
929928
return (data * ((size + 1) // 2))[:size]
930929

931-
with (
932-
self._uuid6_hooks(fake_nanoseconds, random_func=random_func),
933-
mock.patch.object(self.uuid, 'getnode', return_value=fake_node_value),
934-
):
930+
with self._uuid6_hooks(fake_nanoseconds, random_func=random_func,
931+
node_func=lambda: fake_node_value):
935932
u = self.uuid.uuid6()
936933
equal(u.variant, self.uuid.RFC_4122)
937934
equal(u.version, 6)
@@ -1069,11 +1066,13 @@ def random_func(size):
10691066
with self.assertRaises(RuntimeError) as cm:
10701067
self.uuid.uuid6(node=0)
10711068
self.assertIs(cm.exception, error)
1072-
with self._uuid6_hooks(0):
1073-
with mock.patch.object(self.uuid, 'getnode', side_effect=error):
1074-
with self.assertRaises(RuntimeError) as cm:
1075-
self.uuid.uuid6(clock_seq=0)
1076-
self.assertIs(cm.exception, error)
1069+
def node_func():
1070+
raise error
1071+
1072+
with self._uuid6_hooks(0, node_func=node_func):
1073+
with self.assertRaises(RuntimeError) as cm:
1074+
self.uuid.uuid6(clock_seq=0)
1075+
self.assertIs(cm.exception, error)
10771076

10781077
def test_uuid6_monotonicity(self):
10791078
# Repeated and backward clock readings must still advance the timestamp.
@@ -1918,17 +1917,20 @@ def full_test(p, u):
19181917
self.assertEqual(len(all_ps), len(all_us))
19191918
self.assertEqual(len(all_ps), len(uuids))
19201919

1921-
def _install_hooks(self, uuid_mod, *, start_at=0, inc_by=0, seed=0):
1920+
def _install_hooks(self, uuid_mod, *, start_at=0, inc_by=0, seed=0,
1921+
node_func=None):
19221922
py_hooks = UuidHooks(start_at=start_at, inc_by=inc_by, seed=seed)
19231923
uuid_mod._install_py_hooks(
19241924
random_func=py_hooks.random_func,
1925-
time_func=py_hooks.time_func
1925+
time_func=py_hooks.time_func,
1926+
node_func=node_func,
19261927
)
19271928

19281929
c_hooks = UuidHooks(start_at=start_at, inc_by=inc_by, seed=seed)
19291930
uuid_mod._install_c_hooks(
19301931
random_func=c_hooks.random_func,
1931-
time_func=c_hooks.time_func
1932+
time_func=c_hooks.time_func,
1933+
node_func=node_func,
19321934
)
19331935

19341936
def _reset_hooks(self, uuid_mod):
@@ -1943,19 +1945,19 @@ def test_exact_same_algo_uuid6(self):
19431945

19441946
values = (None, 0, -1, (1 << 100) | 0x123456789abc)
19451947
try:
1946-
with mock.patch.object(uuid, 'getnode', return_value=0x123456789abc):
1947-
for start_at in (-1001, 0, 1672533296123456789):
1948-
for inc_by in (-101, 0, 101, 1000000):
1949-
for node, clock_seq in product(values, repeat=2):
1950-
self._install_hooks(uuid, start_at=start_at,
1951-
inc_by=inc_by)
1952-
with self.subTest(start=start_at, inc=inc_by,
1953-
node=node, clock_seq=clock_seq):
1954-
for _ in range(200):
1955-
self.assertEqual(
1956-
uuid._py_uuid6(node, clock_seq).bytes,
1957-
uuid._c_uuid6(node, clock_seq).bytes,
1958-
)
1948+
for start_at in (-1001, 0, 1672533296123456789):
1949+
for inc_by in (-101, 0, 101, 1000000):
1950+
for node, clock_seq in product(values, repeat=2):
1951+
self._install_hooks(uuid, start_at=start_at,
1952+
inc_by=inc_by,
1953+
node_func=lambda: 0x123456789abc)
1954+
with self.subTest(start=start_at, inc=inc_by,
1955+
node=node, clock_seq=clock_seq):
1956+
for _ in range(200):
1957+
self.assertEqual(
1958+
uuid._py_uuid6(node, clock_seq).bytes,
1959+
uuid._c_uuid6(node, clock_seq).bytes,
1960+
)
19591961
finally:
19601962
self._reset_hooks(uuid)
19611963

@@ -1988,36 +1990,37 @@ def test_uuid6_node_cache(self):
19881990
self.addCleanup(uuid._install_c_hooks, random_func=None, time_func=None)
19891991
for node in (0, -1):
19901992
with self.subTest(node=node):
1991-
uuid._install_c_hooks(random_func=None, time_func=None)
1992-
with mock.patch.object(uuid, 'getnode', return_value=node) as getnode:
1993-
self.assertEqual(uuid.uuid6(clock_seq=0).node,
1994-
node & 0xffffffffffff)
1995-
self.assertEqual(uuid.uuid6(node=42, clock_seq=0).node, 42)
1996-
self.assertEqual(uuid.uuid6(clock_seq=0).node,
1997-
node & 0xffffffffffff)
1998-
getnode.assert_called_once_with()
1993+
getnode = mock.Mock(return_value=node)
1994+
uuid._install_c_hooks(random_func=None, time_func=None,
1995+
node_func=getnode)
1996+
self.assertEqual(uuid.uuid6(clock_seq=0).node,
1997+
node & 0xffffffffffff)
1998+
self.assertEqual(uuid.uuid6(node=42, clock_seq=0).node, 42)
1999+
self.assertEqual(uuid.uuid6(clock_seq=0).node,
2000+
node & 0xffffffffffff)
2001+
getnode.assert_called_once_with()
19992002

20002003
def test_uuid6_node_cache_error(self):
20012004
# A failed lookup must leave the cache empty so the next call can retry.
20022005
import uuid
20032006

2004-
uuid._install_c_hooks(random_func=None, time_func=None)
20052007
self.addCleanup(uuid._install_c_hooks, random_func=None, time_func=None)
20062008
error = RuntimeError('node lookup failed')
2007-
with mock.patch.object(uuid, 'getnode', side_effect=[error, 42]) as getnode:
2008-
with self.assertRaises(RuntimeError) as cm:
2009-
uuid.uuid6(clock_seq=0)
2010-
self.assertIs(cm.exception, error)
2011-
self.assertEqual(uuid.uuid6(clock_seq=0).node, 42)
2012-
self.assertEqual(uuid.uuid6(clock_seq=0).node, 42)
2013-
self.assertEqual(getnode.call_count, 2)
2009+
getnode = mock.Mock(side_effect=[error, 42])
2010+
uuid._install_c_hooks(random_func=None, time_func=None,
2011+
node_func=getnode)
2012+
with self.assertRaises(RuntimeError) as cm:
2013+
uuid.uuid6(clock_seq=0)
2014+
self.assertIs(cm.exception, error)
2015+
self.assertEqual(uuid.uuid6(clock_seq=0).node, 42)
2016+
self.assertEqual(uuid.uuid6(clock_seq=0).node, 42)
2017+
self.assertEqual(getnode.call_count, 2)
20142018

20152019
def test_uuid6_node_cache_reentrant(self):
20162020
# getnode() can reenter uuid6(). The recursive call initializes the cache;
20172021
# the outer call must reuse that value without deadlocking or replacing it.
20182022
import uuid
20192023

2020-
uuid._install_c_hooks(random_func=None, time_func=None)
20212024
self.addCleanup(uuid._install_c_hooks, random_func=None, time_func=None)
20222025
recursive = False
20232026

@@ -2029,8 +2032,9 @@ def getnode():
20292032
return 22
20302033
return 11
20312034

2032-
with mock.patch.object(uuid, 'getnode', side_effect=getnode):
2033-
self.assertEqual(uuid.uuid6(clock_seq=0).node, 11)
2035+
uuid._install_c_hooks(random_func=None, time_func=None,
2036+
node_func=getnode)
2037+
self.assertEqual(uuid.uuid6(clock_seq=0).node, 11)
20342038

20352039
@threading_helper.requires_working_threading()
20362040
@threading_helper.reap_threads
@@ -2057,17 +2061,17 @@ def generate():
20572061
local.id = 1
20582062
return uuid.uuid6(clock_seq=0)
20592063

2060-
uuid._install_c_hooks(random_func=None, time_func=None)
2064+
uuid._install_c_hooks(random_func=None, time_func=None,
2065+
node_func=getnode)
20612066
self.addCleanup(uuid._install_c_hooks, random_func=None, time_func=None)
2062-
with mock.patch.object(uuid, 'getnode', side_effect=getnode):
2063-
with ThreadPoolExecutor(1) as pool:
2064-
pending = pool.submit(generate)
2065-
try:
2066-
self.assertTrue(entered.wait(support.SHORT_TIMEOUT))
2067-
other = uuid.uuid6(clock_seq=0)
2068-
finally:
2069-
resume.set()
2070-
first = pending.result(timeout=support.SHORT_TIMEOUT)
2067+
with ThreadPoolExecutor(1) as pool:
2068+
pending = pool.submit(generate)
2069+
try:
2070+
self.assertTrue(entered.wait(support.SHORT_TIMEOUT))
2071+
other = uuid.uuid6(clock_seq=0)
2072+
finally:
2073+
resume.set()
2074+
first = pending.result(timeout=support.SHORT_TIMEOUT)
20712075
self.assertEqual(first.node, 2)
20722076
self.assertEqual(other.node, 2)
20732077

‎Lib/uuid.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class SafeUUID:
113113

114114
_random_hook = None
115115
_time_hook = None
116+
_node_hook = None
116117

117118

118119
def _gen_random(size):
@@ -129,13 +130,21 @@ def _gen_time():
129130
return _time_hook()
130131

131132

132-
def _install_py_hooks(*, random_func, time_func):
133+
def _gen_node():
134+
if _node_hook is None:
135+
return getnode()
136+
return _node_hook()
137+
138+
139+
def _install_py_hooks(*, random_func, time_func, node_func=None):
133140
global _random_hook
134141
global _time_hook
142+
global _node_hook
135143
global _last_timestamp_v7
136144
global _last_timestamp_v6
137145
_random_hook = random_func
138146
_time_hook = time_func
147+
_node_hook = node_func
139148
# Reset timestamps for repeatability of tests.
140149
_last_timestamp_v7 = None
141150
_last_timestamp_v6 = None
@@ -848,7 +857,7 @@ def uuid6(node=None, clock_seq=None):
848857
time_lo = timestamp & 0x0fff # keep 12 bits and clear version bits
849858
clock_s = clock_seq & 0x3fff # keep 14 bits and clear variant bits
850859
if node is None:
851-
node = getnode()
860+
node = _gen_node()
852861
# --- 32 + 16 --- -- 4 -- -- 12 -- -- 2 -- -- 14 --- 48
853862
# time_hi_and_mid | version | time_lo | variant | clock_seq | node
854863
int_uuid_6 = time_hi_and_mid << 80

‎Modules/_uuidmodule.c‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ typedef struct {
180180
PyObject *random_func;
181181
PyObject *random_size_int;
182182
PyObject *time_func;
183+
PyObject *node_func;
183184

184185
// A freelist for uuid objects -- 15-20% performance boost.
185186
uuidobject *freelist;
@@ -423,7 +424,8 @@ uuid6_get_node(uuid_state *state, uint64_t *result)
423424
Py_BEGIN_CRITICAL_SECTION_MUTEX(&state->node_mutex);
424425
node_id = _Py_atomic_load_uint64_relaxed(&state->node_id);
425426
if (node_id == NODE_NOT_INITIALIZED) {
426-
PyObject *getnode = get_uuidmod_attr("getnode");
427+
PyObject *getnode = state->node_func != NULL
428+
? Py_NewRef(state->node_func) : get_uuidmod_attr("getnode");
427429
PyObject *value = NULL;
428430
if (getnode != NULL) {
429431
value = PyObject_CallNoArgs(getnode);
@@ -655,13 +657,14 @@ _uuid._install_c_hooks
655657
*
656658
random_func: object
657659
time_func: object
660+
node_func: object = None
658661
659662
[clinic start generated code]*/
660663

661664
static PyObject *
662665
_uuid__install_c_hooks_impl(PyObject *module, PyObject *random_func,
663-
PyObject *time_func)
664-
/*[clinic end generated code: output=884aa6e91b2ea832 input=6c5017297067e2ea]*/
666+
PyObject *time_func, PyObject *node_func)
667+
/*[clinic end generated code: output=e742978b99d5a2ea input=beb6f73390b31d93]*/
665668
{
666669
uuid_state *state = get_uuid_state(module);
667670

@@ -671,6 +674,8 @@ _uuid__install_c_hooks_impl(PyObject *module, PyObject *random_func,
671674
state->last_timestamp_v6_init = 0;
672675
state->last_timestamp_v7_init = 0;
673676
Py_BEGIN_CRITICAL_SECTION_MUTEX(&state->node_mutex);
677+
Py_XSETREF(state->node_func,
678+
node_func == Py_None ? NULL : Py_NewRef(node_func));
674679
_Py_atomic_store_uint64_relaxed(&state->node_id, NODE_NOT_INITIALIZED);
675680
Py_END_CRITICAL_SECTION();
676681

@@ -1923,6 +1928,7 @@ module_traverse(PyObject *mod, visitproc visit, void *arg)
19231928
Py_VISIT(state->uint128_max);
19241929
Py_VISIT(state->random_func);
19251930
Py_VISIT(state->time_func);
1931+
Py_VISIT(state->node_func);
19261932
Py_VISIT(state->random_size_int);
19271933
return 0;
19281934
}
@@ -1937,6 +1943,7 @@ module_clear(PyObject *mod)
19371943
Py_CLEAR(state->uint128_max);
19381944
Py_CLEAR(state->random_func);
19391945
Py_CLEAR(state->time_func);
1946+
Py_CLEAR(state->node_func);
19401947
Py_CLEAR(state->random_size_int);
19411948

19421949
if (state->freelist != NULL) {
@@ -2019,6 +2026,7 @@ uuid_exec(PyObject *module)
20192026
state->random_last_pid = uuid_getpid();
20202027

20212028
state->time_func = NULL;
2029+
state->node_func = NULL;
20222030
state->random_func = NULL;
20232031
state->random_size_int = PyLong_FromSize_t((Py_ssize_t)RANDOM_BUF_SIZE);
20242032
if (state->random_size_int == NULL) {

0 commit comments

Comments
 (0)