From 32b1b40419179177c58d9f083dde402f5ce82ab0 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 12:36:38 +0200 Subject: [PATCH 1/8] bpo-11063, bpo-20519: avoid ctypes and improve import time for uuid --- Lib/test/test_uuid.py | 68 +++++++++------- Lib/uuid.py | 178 +++++++++++++++++++++++++----------------- Modules/_uuidmodule.c | 40 ++++++++++ setup.py | 14 ++++ 4 files changed, 203 insertions(+), 97 deletions(-) create mode 100644 Modules/_uuidmodule.c diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index cd0f81e7e14e8f..793c9f53033801 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -7,6 +7,10 @@ import subprocess import uuid +py_uuid = support.import_fresh_module('uuid', blocked=['_uuid']) +c_uuid = support.import_fresh_module('uuid', fresh=['_uuid']) + + def importable(name): try: __import__(name) @@ -340,7 +344,7 @@ def test_uuid1(self): equal(((u.clock_seq_hi_variant & 0x3f) << 8) | u.clock_seq_low, 0x3fff) - requires_ugt = unittest.skipUnless(uuid._uuid_generate_time is not None, + requires_ugt = unittest.skipUnless(uuid._has_uuid_generate_time_safe, 'requires uuid_generate_time_safe(3)') @requires_ugt @@ -358,29 +362,33 @@ def test_uuid1_safe(self): def test_uuid1_unknown(self): # Even if the platform has uuid_generate_time_safe(), let's mock it to # be uuid_generate_time() and ensure the safety is unknown. - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', None): + f = uuid._generate_time_safe + with unittest.mock.patch('uuid._generate_time_safe', + lambda: (f()[0], None)): u = uuid.uuid1() self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) @requires_ugt def test_uuid1_is_safe(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: 0): + f = uuid._generate_time_safe + with unittest.mock.patch('uuid._generate_time_safe', + lambda: (f()[0], 0)): u = uuid.uuid1() self.assertEqual(u.is_safe, uuid.SafeUUID.safe) @requires_ugt def test_uuid1_is_unsafe(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: -1): + f = uuid._generate_time_safe + with unittest.mock.patch('uuid._generate_time_safe', + lambda: (f()[0], -1)): u = uuid.uuid1() self.assertEqual(u.is_safe, uuid.SafeUUID.unsafe) @requires_ugt def test_uuid1_bogus_return_value(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: 3): + f = uuid._generate_time_safe + with unittest.mock.patch('uuid._generate_time_safe', + lambda: (f()[0], 3)): u = uuid.uuid1() self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) @@ -457,7 +465,8 @@ def testIssue8621(self): self.assertNotEqual(parent_value, child_value) -class TestInternals(unittest.TestCase): +class BaseTestInternals: + @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_find_mac(self): data = ''' @@ -473,7 +482,7 @@ def test_find_mac(self): return_value='/sbin/ifconfig'): with unittest.mock.patch.object(subprocess, 'Popen', return_value=popen): - mac = uuid._find_mac( + mac = self.uuid._find_mac( command='ifconfig', args='', hw_identifiers=[b'hwaddr'], @@ -497,61 +506,66 @@ def check_node(self, node, requires=None, network=False): @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_ifconfig_getnode(self): - node = uuid._ifconfig_getnode() + node = self.uuid._ifconfig_getnode() self.check_node(node, 'ifconfig', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_ip_getnode(self): - node = uuid._ip_getnode() + node = self.uuid._ip_getnode() self.check_node(node, 'ip', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_arp_getnode(self): - node = uuid._arp_getnode() + node = self.uuid._arp_getnode() self.check_node(node, 'arp', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_lanscan_getnode(self): - node = uuid._lanscan_getnode() + node = self.uuid._lanscan_getnode() self.check_node(node, 'lanscan', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_netstat_getnode(self): - node = uuid._netstat_getnode() + node = self.uuid._netstat_getnode() self.check_node(node, 'netstat', True) @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_ipconfig_getnode(self): - node = uuid._ipconfig_getnode() + node = self.uuid._ipconfig_getnode() self.check_node(node, 'ipconfig', True) @unittest.skipUnless(importable('win32wnet'), 'requires win32wnet') @unittest.skipUnless(importable('netbios'), 'requires netbios') def test_netbios_getnode(self): - node = uuid._netbios_getnode() + node = self.uuid._netbios_getnode() self.check_node(node, network=True) def test_random_getnode(self): - node = uuid._random_getnode() + node = self.uuid._random_getnode() # Least significant bit of first octet must be set. self.assertTrue(node & 0x010000000000, '%012x' % node) self.check_node(node) @unittest.skipUnless(os.name == 'posix', 'requires Posix') - @unittest.skipUnless(importable('ctypes'), 'requires ctypes') - def test_unixdll_getnode(self): - try: # Issues 1481, 3581: _uuid_generate_time() might be None. - node = uuid._unixdll_getnode() - except TypeError: - self.skipTest('requires uuid_generate_time') - self.check_node(node) + def test_unix_getnode(self): + if not importable('_uuid') and not importable('ctypes'): + self.skipTest("neither _uuid extension nor ctypes available") + self.check_node(self.uuid._unix_getnode(), 'unix') @unittest.skipUnless(os.name == 'nt', 'requires Windows') @unittest.skipUnless(importable('ctypes'), 'requires ctypes') def test_windll_getnode(self): - node = uuid._windll_getnode() + node = self.uuid._windll_getnode() self.check_node(node) +class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase): + uuid = py_uuid + +@unittest.skipUnless(c_uuid, 'requires the C _uuid module') +class TestInternalsWithExtModule(BaseTestInternals, unittest.TestCase): + uuid = c_uuid + + if __name__ == '__main__': unittest.main() diff --git a/Lib/uuid.py b/Lib/uuid.py index 15a81f5c18b66b..a87d9c90587f10 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -45,6 +45,7 @@ """ import os +import sys from enum import Enum @@ -475,73 +476,110 @@ def _netbios_getnode(): continue return int.from_bytes(bytes, 'big') -# Thanks to Thomas Heller for ctypes and for his help with its use here. - -# If ctypes is available, use it to find system routines for UUID generation. -# XXX This makes the module non-thread-safe! -_uuid_generate_time = _UuidCreate = None -try: - import ctypes, ctypes.util - import sys - - # The uuid_generate_* routines are provided by libuuid on at least - # Linux and FreeBSD, and provided by libc on Mac OS X. - _libnames = ['uuid'] - if not sys.platform.startswith('win'): - _libnames.append('c') - for libname in _libnames: + +_generate_time_safe = _UuidCreate = None +_has_uuid_generate_time_safe = None + +def _load_system_functions(): + """ + Try to load platform-specific functions for generating uuids. + """ + global _generate_time_safe, _UuidCreate, _has_uuid_generate_time_safe + + if _has_uuid_generate_time_safe is not None: + return + + _has_uuid_generate_time_safe = False + + if sys.platform == "darwin" and int(os.uname().release.split('.')[0]) < 9: + # The uuid_generate_* functions are broken on MacOS X 10.5, as noted + # in issue #8621 the function generates the same sequence of values + # in the parent process and all children created using fork (unless + # those children use exec as well). + # + # Assume that the uuid_generate functions are broken from 10.5 onward, + # the test can be adjusted when a later version is fixed. + _uuid = None + else: try: - lib = ctypes.CDLL(ctypes.util.find_library(libname)) - except Exception: # pragma: nocover - continue - # Try to find the safe variety first. - if hasattr(lib, 'uuid_generate_time_safe'): - _uuid_generate_time = lib.uuid_generate_time_safe - # int uuid_generate_time_safe(uuid_t out); - break - elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover - _uuid_generate_time = lib.uuid_generate_time - # void uuid_generate_time(uuid_t out); - _uuid_generate_time.restype = None - break - del _libnames - - # The uuid_generate_* functions are broken on MacOS X 10.5, as noted - # in issue #8621 the function generates the same sequence of values - # in the parent process and all children created using fork (unless - # those children use exec as well). - # - # Assume that the uuid_generate functions are broken from 10.5 onward, - # the test can be adjusted when a later version is fixed. - if sys.platform == 'darwin': - if int(os.uname().release.split('.')[0]) >= 9: - _uuid_generate_time = None - - # On Windows prior to 2000, UuidCreate gives a UUID containing the - # hardware address. On Windows 2000 and later, UuidCreate makes a - # random UUID and UuidCreateSequential gives a UUID containing the - # hardware address. These routines are provided by the RPC runtime. - # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last - # 6 bytes returned by UuidCreateSequential are fixed, they don't appear - # to bear any relationship to the MAC address of any network device - # on the box. - try: - lib = ctypes.windll.rpcrt4 - except: - lib = None - _UuidCreate = getattr(lib, 'UuidCreateSequential', - getattr(lib, 'UuidCreate', None)) -except: - pass - -def _unixdll_getnode(): - """Get the hardware address on Unix using ctypes.""" - _buffer = ctypes.create_string_buffer(16) - _uuid_generate_time(_buffer) - return UUID(bytes=bytes_(_buffer.raw)).node + import _uuid + except ImportError: + _uuid = None + else: + def _generate_time_safe(): + return _uuid.generate_time_safe() + _has_uuid_generate_time_safe = True + + if _uuid is None: + try: + # If we couldn't find an extension module, try ctypes to find + # system routines for UUID generation. + # Thanks to Thomas Heller for ctypes and for his help with its use here. + import ctypes + import ctypes.util + + # The uuid_generate_* routines are provided by libuuid on at least + # Linux and FreeBSD, and provided by libc on Mac OS X. + _libnames = ['uuid'] + if not sys.platform.startswith('win'): + _libnames.append('c') + for libname in _libnames: + try: + lib = ctypes.CDLL(ctypes.util.find_library(libname)) + except Exception: # pragma: nocover + continue + # Try to find the safe variety first. + if hasattr(lib, 'uuid_generate_time_safe'): + _uuid_generate_time = lib.uuid_generate_time_safe + # int uuid_generate_time_safe(uuid_t out); + def _generate_time_safe(): + _buffer = ctypes.create_string_buffer(16) + res = _uuid_generate_time(_buffer) + return bytes(_buffer.raw), res + _has_uuid_generate_time_safe = True + break + + elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover + _uuid_generate_time = lib.uuid_generate_time + # void uuid_generate_time(uuid_t out); + _uuid_generate_time.restype = None + def _generate_time_safe(): + _buffer = ctypes.create_string_buffer(16) + _uuid_generate_time(_buffer) + return bytes(_buffer.raw), None + break + + # On Windows prior to 2000, UuidCreate gives a UUID containing the + # hardware address. On Windows 2000 and later, UuidCreate makes a + # random UUID and UuidCreateSequential gives a UUID containing the + # hardware address. These routines are provided by the RPC runtime. + # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last + # 6 bytes returned by UuidCreateSequential are fixed, they don't appear + # to bear any relationship to the MAC address of any network device + # on the box. + try: + lib = ctypes.windll.rpcrt4 + except: + lib = None + _UuidCreate = getattr(lib, 'UuidCreateSequential', + getattr(lib, 'UuidCreate', None)) + + except Exception as exc: + import warnings + warnings.warn(f"Could not find fallback ctypes uuid functions: {exc}", + ImportWarning) + + +def _unix_getnode(): + """Get the hardware address on Unix using the _uuid extension module + or ctypes.""" + _load_system_functions() + uuid_time, _ = _generate_time_safe() + return UUID(bytes=uuid_time).node def _windll_getnode(): """Get the hardware address on Windows using ctypes.""" + _load_system_functions() _buffer = ctypes.create_string_buffer(16) if _UuidCreate(_buffer) == 0: return UUID(bytes=bytes_(_buffer.raw)).node @@ -551,6 +589,7 @@ def _random_getnode(): import random return random.getrandbits(48) | 0x010000000000 + _node = None def getnode(): @@ -561,16 +600,14 @@ def getnode(): choose a random 48-bit number with its eighth bit set to 1 as recommended in RFC 4122. """ - global _node if _node is not None: return _node - import sys if sys.platform == 'win32': getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] else: - getters = [_unixdll_getnode, _ifconfig_getnode, _ip_getnode, + getters = [_unix_getnode, _ifconfig_getnode, _ip_getnode, _arp_getnode, _lanscan_getnode, _netstat_getnode] for getter in getters + [_random_getnode]: @@ -581,6 +618,7 @@ def getnode(): if _node is not None: return _node + _last_timestamp = None def uuid1(node=None, clock_seq=None): @@ -591,14 +629,14 @@ def uuid1(node=None, clock_seq=None): # When the system provides a version-1 UUID generator, use it (but don't # use UuidCreate here because its UUIDs don't conform to RFC 4122). - if _uuid_generate_time and node is clock_seq is None: - _buffer = ctypes.create_string_buffer(16) - safely_generated = _uuid_generate_time(_buffer) + _load_system_functions() + if _generate_time_safe is not None and node is clock_seq is None: + uuid_time, safely_generated = _generate_time_safe() try: is_safe = SafeUUID(safely_generated) except ValueError: is_safe = SafeUUID.unknown - return UUID(bytes=bytes_(_buffer.raw), is_safe=is_safe) + return UUID(bytes=uuid_time, is_safe=is_safe) global _last_timestamp import time diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c new file mode 100644 index 00000000000000..07a407edb3209c --- /dev/null +++ b/Modules/_uuidmodule.c @@ -0,0 +1,40 @@ +#define PY_SSIZE_T_CLEAN + +#include "Python.h" +#include + + +static PyObject * +_uuid_generate_time_safe(void) +{ + uuid_t out; + int res; + + res = uuid_generate_time_safe(out); + return Py_BuildValue("y#i", (const char *) out, sizeof(out), res); +} + + +static PyMethodDef uuid_methods[] = { + {"generate_time_safe", (PyCFunction)_uuid_generate_time_safe, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static struct PyModuleDef uuidmodule = { + PyModuleDef_HEAD_INIT, + "_uuid", + NULL, + -1, + uuid_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__uuid(void) +{ + assert(sizeof(uuid_t) == 16); + return PyModule_Create(&uuidmodule); +} diff --git a/setup.py b/setup.py index d5c58e0686981f..197403f6a29d5a 100644 --- a/setup.py +++ b/setup.py @@ -1668,6 +1668,20 @@ class db_found(Exception): pass if '_tkinter' not in [e.name for e in self.extensions]: missing.append('_tkinter') + # Build the _uuid module if possible + build_uuid = False + if find_file("uuid.h", inc_dirs, ["/usr/include/uuid"]): + if self.compiler.find_library_file(lib_dirs, 'uuid'): + uuid_libs = ['uuid'] + else: + uuid_libs = [] + build_uuid = True + if build_uuid: + self.extensions.append(Extension('_uuid', ['_uuidmodule.c'], + libraries=uuid_libs)) + else: + missing.append('_uuid') + ## # Uncomment these lines if you want to play with xxmodule.c ## ext = Extension('xx', ['xxmodule.c']) ## self.extensions.append(ext) From 05dcee290775afd49173d656a9515dfe9bc1ad14 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 13:16:31 +0200 Subject: [PATCH 2/8] Fix _windll_getnode --- Lib/uuid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/uuid.py b/Lib/uuid.py index a87d9c90587f10..e47aad03052adc 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -579,6 +579,7 @@ def _unix_getnode(): def _windll_getnode(): """Get the hardware address on Windows using ctypes.""" + import ctypes _load_system_functions() _buffer = ctypes.create_string_buffer(16) if _UuidCreate(_buffer) == 0: From f4d7cf38db1b654ca0fc455ddeee45089f416c76 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 13:17:36 +0200 Subject: [PATCH 3/8] Add blurb --- .../NEWS.d/next/Library/2017-09-28-13-17-33.bpo-20519.FteeQQ.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2017-09-28-13-17-33.bpo-20519.FteeQQ.rst diff --git a/Misc/NEWS.d/next/Library/2017-09-28-13-17-33.bpo-20519.FteeQQ.rst b/Misc/NEWS.d/next/Library/2017-09-28-13-17-33.bpo-20519.FteeQQ.rst new file mode 100644 index 00000000000000..ef86c47f26a1ad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-09-28-13-17-33.bpo-20519.FteeQQ.rst @@ -0,0 +1 @@ +Avoid ctypes use (if possible) and improve import time for uuid. From 8f4dfb25a8d17ca38808bcd007c6e4bbc40efc4f Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 18:17:39 +0200 Subject: [PATCH 4/8] Nits in uuid.py --- Lib/uuid.py | 121 +++++++++++++++++++++--------------------- Modules/_uuidmodule.c | 15 ++---- 2 files changed, 65 insertions(+), 71 deletions(-) diff --git a/Lib/uuid.py b/Lib/uuid.py index e47aad03052adc..d9c59fcd0ee6ee 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -499,75 +499,74 @@ def _load_system_functions(): # # Assume that the uuid_generate functions are broken from 10.5 onward, # the test can be adjusted when a later version is fixed. - _uuid = None + pass else: try: import _uuid except ImportError: - _uuid = None + pass else: - def _generate_time_safe(): - return _uuid.generate_time_safe() + _generate_time_safe = _uuid.generate_time_safe _has_uuid_generate_time_safe = True + return - if _uuid is None: - try: - # If we couldn't find an extension module, try ctypes to find - # system routines for UUID generation. - # Thanks to Thomas Heller for ctypes and for his help with its use here. - import ctypes - import ctypes.util - - # The uuid_generate_* routines are provided by libuuid on at least - # Linux and FreeBSD, and provided by libc on Mac OS X. - _libnames = ['uuid'] - if not sys.platform.startswith('win'): - _libnames.append('c') - for libname in _libnames: - try: - lib = ctypes.CDLL(ctypes.util.find_library(libname)) - except Exception: # pragma: nocover - continue - # Try to find the safe variety first. - if hasattr(lib, 'uuid_generate_time_safe'): - _uuid_generate_time = lib.uuid_generate_time_safe - # int uuid_generate_time_safe(uuid_t out); - def _generate_time_safe(): - _buffer = ctypes.create_string_buffer(16) - res = _uuid_generate_time(_buffer) - return bytes(_buffer.raw), res - _has_uuid_generate_time_safe = True - break - - elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover - _uuid_generate_time = lib.uuid_generate_time - # void uuid_generate_time(uuid_t out); - _uuid_generate_time.restype = None - def _generate_time_safe(): - _buffer = ctypes.create_string_buffer(16) - _uuid_generate_time(_buffer) - return bytes(_buffer.raw), None - break - - # On Windows prior to 2000, UuidCreate gives a UUID containing the - # hardware address. On Windows 2000 and later, UuidCreate makes a - # random UUID and UuidCreateSequential gives a UUID containing the - # hardware address. These routines are provided by the RPC runtime. - # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last - # 6 bytes returned by UuidCreateSequential are fixed, they don't appear - # to bear any relationship to the MAC address of any network device - # on the box. + try: + # If we couldn't find an extension module, try ctypes to find + # system routines for UUID generation. + # Thanks to Thomas Heller for ctypes and for his help with its use here. + import ctypes + import ctypes.util + + # The uuid_generate_* routines are provided by libuuid on at least + # Linux and FreeBSD, and provided by libc on Mac OS X. + _libnames = ['uuid'] + if not sys.platform.startswith('win'): + _libnames.append('c') + for libname in _libnames: try: - lib = ctypes.windll.rpcrt4 - except: - lib = None - _UuidCreate = getattr(lib, 'UuidCreateSequential', - getattr(lib, 'UuidCreate', None)) - - except Exception as exc: - import warnings - warnings.warn(f"Could not find fallback ctypes uuid functions: {exc}", - ImportWarning) + lib = ctypes.CDLL(ctypes.util.find_library(libname)) + except Exception: # pragma: nocover + continue + # Try to find the safe variety first. + if hasattr(lib, 'uuid_generate_time_safe'): + _uuid_generate_time = lib.uuid_generate_time_safe + # int uuid_generate_time_safe(uuid_t out); + def _generate_time_safe(): + _buffer = ctypes.create_string_buffer(16) + res = _uuid_generate_time(_buffer) + return bytes(_buffer.raw), res + _has_uuid_generate_time_safe = True + break + + elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover + _uuid_generate_time = lib.uuid_generate_time + # void uuid_generate_time(uuid_t out); + _uuid_generate_time.restype = None + def _generate_time_safe(): + _buffer = ctypes.create_string_buffer(16) + _uuid_generate_time(_buffer) + return bytes(_buffer.raw), None + break + + # On Windows prior to 2000, UuidCreate gives a UUID containing the + # hardware address. On Windows 2000 and later, UuidCreate makes a + # random UUID and UuidCreateSequential gives a UUID containing the + # hardware address. These routines are provided by the RPC runtime. + # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last + # 6 bytes returned by UuidCreateSequential are fixed, they don't appear + # to bear any relationship to the MAC address of any network device + # on the box. + try: + lib = ctypes.windll.rpcrt4 + except: + lib = None + _UuidCreate = getattr(lib, 'UuidCreateSequential', + getattr(lib, 'UuidCreate', None)) + + except Exception as exc: + import warnings + warnings.warn(f"Could not find fallback ctypes uuid functions: {exc}", + ImportWarning) def _unix_getnode(): diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c index 07a407edb3209c..e263b40d8ff3e2 100644 --- a/Modules/_uuidmodule.c +++ b/Modules/_uuidmodule.c @@ -5,7 +5,7 @@ static PyObject * -_uuid_generate_time_safe(void) +py_uuid_generate_time_safe(void) { uuid_t out; int res; @@ -16,20 +16,15 @@ _uuid_generate_time_safe(void) static PyMethodDef uuid_methods[] = { - {"generate_time_safe", (PyCFunction)_uuid_generate_time_safe, METH_NOARGS, NULL}, + {"generate_time_safe", (PyCFunction) py_uuid_generate_time_safe, METH_NOARGS, NULL}, {NULL, NULL, 0, NULL} /* sentinel */ }; static struct PyModuleDef uuidmodule = { PyModuleDef_HEAD_INIT, - "_uuid", - NULL, - -1, - uuid_methods, - NULL, - NULL, - NULL, - NULL + .m_name = "_uuid", + .m_size = -1, + .m_methods = uuid_methods, }; PyMODINIT_FUNC From a78dc43e3c19090132eb8d6090f061e27a6f60ca Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 18:28:17 +0200 Subject: [PATCH 5/8] Generalize testing with and without C extension "_uuid" --- Lib/test/test_uuid.py | 253 +++++++++++++++++++++--------------------- 1 file changed, 128 insertions(+), 125 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 793c9f53033801..91334b83b389cb 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -5,7 +5,6 @@ import os import shutil import subprocess -import uuid py_uuid = support.import_fresh_module('uuid', blocked=['_uuid']) c_uuid = support.import_fresh_module('uuid', fresh=['_uuid']) @@ -18,7 +17,8 @@ def importable(name): except: return False -class TestUUID(unittest.TestCase): +class BaseTestUUID: + def test_UUID(self): equal = self.assertEqual ascending = [] @@ -32,7 +32,7 @@ def test_UUID(self): (0, 0, 0, 0, 0, 0), 0, 'urn:uuid:00000000-0000-0000-0000-000000000000', - 0, 0, uuid.RESERVED_NCS, None), + 0, 0, self.uuid.RESERVED_NCS, None), ('00010203-0405-0607-0809-0a0b0c0d0e0f', '{00010203-0405-0607-0809-0a0b0c0d0e0f}', '000102030405060708090a0b0c0d0e0f', @@ -41,7 +41,7 @@ def test_UUID(self): (0x00010203, 0x0405, 0x0607, 8, 9, 0x0a0b0c0d0e0f), 0x000102030405060708090a0b0c0d0e0f, 'urn:uuid:00010203-0405-0607-0809-0a0b0c0d0e0f', - 0x607040500010203, 0x809, uuid.RESERVED_NCS, None), + 0x607040500010203, 0x809, self.uuid.RESERVED_NCS, None), ('02d9e6d5-9467-382e-8f9b-9300a64ac3cd', '{02d9e6d5-9467-382e-8f9b-9300a64ac3cd}', '02d9e6d59467382e8f9b9300a64ac3cd', @@ -50,7 +50,7 @@ def test_UUID(self): (0x02d9e6d5, 0x9467, 0x382e, 0x8f, 0x9b, 0x9300a64ac3cd), 0x02d9e6d59467382e8f9b9300a64ac3cd, 'urn:uuid:02d9e6d5-9467-382e-8f9b-9300a64ac3cd', - 0x82e946702d9e6d5, 0xf9b, uuid.RFC_4122, 3), + 0x82e946702d9e6d5, 0xf9b, self.uuid.RFC_4122, 3), ('12345678-1234-5678-1234-567812345678', '{12345678-1234-5678-1234-567812345678}', '12345678123456781234567812345678', @@ -59,7 +59,7 @@ def test_UUID(self): (0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678), 0x12345678123456781234567812345678, 'urn:uuid:12345678-1234-5678-1234-567812345678', - 0x678123412345678, 0x1234, uuid.RESERVED_NCS, None), + 0x678123412345678, 0x1234, self.uuid.RESERVED_NCS, None), ('6ba7b810-9dad-11d1-80b4-00c04fd430c8', '{6ba7b810-9dad-11d1-80b4-00c04fd430c8}', '6ba7b8109dad11d180b400c04fd430c8', @@ -68,7 +68,7 @@ def test_UUID(self): (0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), 0x6ba7b8109dad11d180b400c04fd430c8, 'urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8', - 0x1d19dad6ba7b810, 0xb4, uuid.RFC_4122, 1), + 0x1d19dad6ba7b810, 0xb4, self.uuid.RFC_4122, 1), ('6ba7b811-9dad-11d1-80b4-00c04fd430c8', '{6ba7b811-9dad-11d1-80b4-00c04fd430c8}', '6ba7b8119dad11d180b400c04fd430c8', @@ -77,7 +77,7 @@ def test_UUID(self): (0x6ba7b811, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), 0x6ba7b8119dad11d180b400c04fd430c8, 'urn:uuid:6ba7b811-9dad-11d1-80b4-00c04fd430c8', - 0x1d19dad6ba7b811, 0xb4, uuid.RFC_4122, 1), + 0x1d19dad6ba7b811, 0xb4, self.uuid.RFC_4122, 1), ('6ba7b812-9dad-11d1-80b4-00c04fd430c8', '{6ba7b812-9dad-11d1-80b4-00c04fd430c8}', '6ba7b8129dad11d180b400c04fd430c8', @@ -86,7 +86,7 @@ def test_UUID(self): (0x6ba7b812, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), 0x6ba7b8129dad11d180b400c04fd430c8, 'urn:uuid:6ba7b812-9dad-11d1-80b4-00c04fd430c8', - 0x1d19dad6ba7b812, 0xb4, uuid.RFC_4122, 1), + 0x1d19dad6ba7b812, 0xb4, self.uuid.RFC_4122, 1), ('6ba7b814-9dad-11d1-80b4-00c04fd430c8', '{6ba7b814-9dad-11d1-80b4-00c04fd430c8}', '6ba7b8149dad11d180b400c04fd430c8', @@ -95,7 +95,7 @@ def test_UUID(self): (0x6ba7b814, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00c04fd430c8), 0x6ba7b8149dad11d180b400c04fd430c8, 'urn:uuid:6ba7b814-9dad-11d1-80b4-00c04fd430c8', - 0x1d19dad6ba7b814, 0xb4, uuid.RFC_4122, 1), + 0x1d19dad6ba7b814, 0xb4, self.uuid.RFC_4122, 1), ('7d444840-9dc0-11d1-b245-5ffdce74fad2', '{7d444840-9dc0-11d1-b245-5ffdce74fad2}', '7d4448409dc011d1b2455ffdce74fad2', @@ -104,7 +104,7 @@ def test_UUID(self): (0x7d444840, 0x9dc0, 0x11d1, 0xb2, 0x45, 0x5ffdce74fad2), 0x7d4448409dc011d1b2455ffdce74fad2, 'urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2', - 0x1d19dc07d444840, 0x3245, uuid.RFC_4122, 1), + 0x1d19dc07d444840, 0x3245, self.uuid.RFC_4122, 1), ('e902893a-9d22-3c7e-a7b8-d6e313b71d9f', '{e902893a-9d22-3c7e-a7b8-d6e313b71d9f}', 'e902893a9d223c7ea7b8d6e313b71d9f', @@ -113,7 +113,7 @@ def test_UUID(self): (0xe902893a, 0x9d22, 0x3c7e, 0xa7, 0xb8, 0xd6e313b71d9f), 0xe902893a9d223c7ea7b8d6e313b71d9f, 'urn:uuid:e902893a-9d22-3c7e-a7b8-d6e313b71d9f', - 0xc7e9d22e902893a, 0x27b8, uuid.RFC_4122, 3), + 0xc7e9d22e902893a, 0x27b8, self.uuid.RFC_4122, 3), ('eb424026-6f54-4ef8-a4d0-bb658a1fc6cf', '{eb424026-6f54-4ef8-a4d0-bb658a1fc6cf}', 'eb4240266f544ef8a4d0bb658a1fc6cf', @@ -122,7 +122,7 @@ def test_UUID(self): (0xeb424026, 0x6f54, 0x4ef8, 0xa4, 0xd0, 0xbb658a1fc6cf), 0xeb4240266f544ef8a4d0bb658a1fc6cf, 'urn:uuid:eb424026-6f54-4ef8-a4d0-bb658a1fc6cf', - 0xef86f54eb424026, 0x24d0, uuid.RFC_4122, 4), + 0xef86f54eb424026, 0x24d0, self.uuid.RFC_4122, 4), ('f81d4fae-7dec-11d0-a765-00a0c91e6bf6', '{f81d4fae-7dec-11d0-a765-00a0c91e6bf6}', 'f81d4fae7dec11d0a76500a0c91e6bf6', @@ -131,7 +131,7 @@ def test_UUID(self): (0xf81d4fae, 0x7dec, 0x11d0, 0xa7, 0x65, 0x00a0c91e6bf6), 0xf81d4fae7dec11d0a76500a0c91e6bf6, 'urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6', - 0x1d07decf81d4fae, 0x2765, uuid.RFC_4122, 1), + 0x1d07decf81d4fae, 0x2765, self.uuid.RFC_4122, 1), ('fffefdfc-fffe-fffe-fffe-fffefdfcfbfa', '{fffefdfc-fffe-fffe-fffe-fffefdfcfbfa}', 'fffefdfcfffefffefffefffefdfcfbfa', @@ -140,7 +140,7 @@ def test_UUID(self): (0xfffefdfc, 0xfffe, 0xfffe, 0xff, 0xfe, 0xfffefdfcfbfa), 0xfffefdfcfffefffefffefffefdfcfbfa, 'urn:uuid:fffefdfc-fffe-fffe-fffe-fffefdfcfbfa', - 0xffefffefffefdfc, 0x3ffe, uuid.RESERVED_FUTURE, None), + 0xffefffefffefdfc, 0x3ffe, self.uuid.RESERVED_FUTURE, None), ('ffffffff-ffff-ffff-ffff-ffffffffffff', '{ffffffff-ffff-ffff-ffff-ffffffffffff}', 'ffffffffffffffffffffffffffffffff', @@ -149,14 +149,14 @@ def test_UUID(self): (0xffffffff, 0xffff, 0xffff, 0xff, 0xff, 0xffffffffffff), 0xffffffffffffffffffffffffffffffff, 'urn:uuid:ffffffff-ffff-ffff-ffff-ffffffffffff', - 0xfffffffffffffff, 0x3fff, uuid.RESERVED_FUTURE, None), + 0xfffffffffffffff, 0x3fff, self.uuid.RESERVED_FUTURE, None), ]: equivalents = [] # Construct each UUID in several different ways. - for u in [uuid.UUID(string), uuid.UUID(curly), uuid.UUID(hex), - uuid.UUID(bytes=bytes), uuid.UUID(bytes_le=bytes_le), - uuid.UUID(fields=fields), uuid.UUID(int=integer), - uuid.UUID(urn)]: + for u in [self.uuid.UUID(string), self.uuid.UUID(curly), self.uuid.UUID(hex), + self.uuid.UUID(bytes=bytes), self.uuid.UUID(bytes_le=bytes_le), + self.uuid.UUID(fields=fields), self.uuid.UUID(int=integer), + self.uuid.UUID(urn)]: # Test all conversions and properties of the UUID object. equal(str(u), string) equal(int(u), integer) @@ -210,64 +210,64 @@ def test_exceptions(self): badtype = lambda f: self.assertRaises(TypeError, f) # Badly formed hex strings. - badvalue(lambda: uuid.UUID('')) - badvalue(lambda: uuid.UUID('abc')) - badvalue(lambda: uuid.UUID('1234567812345678123456781234567')) - badvalue(lambda: uuid.UUID('123456781234567812345678123456789')) - badvalue(lambda: uuid.UUID('123456781234567812345678z2345678')) + badvalue(lambda: self.uuid.UUID('')) + badvalue(lambda: self.uuid.UUID('abc')) + badvalue(lambda: self.uuid.UUID('1234567812345678123456781234567')) + badvalue(lambda: self.uuid.UUID('123456781234567812345678123456789')) + badvalue(lambda: self.uuid.UUID('123456781234567812345678z2345678')) # Badly formed bytes. - badvalue(lambda: uuid.UUID(bytes='abc')) - badvalue(lambda: uuid.UUID(bytes='\0'*15)) - badvalue(lambda: uuid.UUID(bytes='\0'*17)) + badvalue(lambda: self.uuid.UUID(bytes='abc')) + badvalue(lambda: self.uuid.UUID(bytes='\0'*15)) + badvalue(lambda: self.uuid.UUID(bytes='\0'*17)) # Badly formed bytes_le. - badvalue(lambda: uuid.UUID(bytes_le='abc')) - badvalue(lambda: uuid.UUID(bytes_le='\0'*15)) - badvalue(lambda: uuid.UUID(bytes_le='\0'*17)) + badvalue(lambda: self.uuid.UUID(bytes_le='abc')) + badvalue(lambda: self.uuid.UUID(bytes_le='\0'*15)) + badvalue(lambda: self.uuid.UUID(bytes_le='\0'*17)) # Badly formed fields. - badvalue(lambda: uuid.UUID(fields=(1,))) - badvalue(lambda: uuid.UUID(fields=(1, 2, 3, 4, 5))) - badvalue(lambda: uuid.UUID(fields=(1, 2, 3, 4, 5, 6, 7))) + badvalue(lambda: self.uuid.UUID(fields=(1,))) + badvalue(lambda: self.uuid.UUID(fields=(1, 2, 3, 4, 5))) + badvalue(lambda: self.uuid.UUID(fields=(1, 2, 3, 4, 5, 6, 7))) # Field values out of range. - badvalue(lambda: uuid.UUID(fields=(-1, 0, 0, 0, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0x100000000, 0, 0, 0, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, -1, 0, 0, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0x10000, 0, 0, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, -1, 0, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0x10000, 0, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0, -1, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0, 0x100, 0, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0, 0, -1, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0, 0, 0x100, 0))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0, 0, 0, -1))) - badvalue(lambda: uuid.UUID(fields=(0, 0, 0, 0, 0, 0x1000000000000))) + badvalue(lambda: self.uuid.UUID(fields=(-1, 0, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0x100000000, 0, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, -1, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0x10000, 0, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, -1, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0x10000, 0, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, -1, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0x100, 0, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, -1, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, 0x100, 0))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, 0, -1))) + badvalue(lambda: self.uuid.UUID(fields=(0, 0, 0, 0, 0, 0x1000000000000))) # Version number out of range. - badvalue(lambda: uuid.UUID('00'*16, version=0)) - badvalue(lambda: uuid.UUID('00'*16, version=6)) + badvalue(lambda: self.uuid.UUID('00'*16, version=0)) + badvalue(lambda: self.uuid.UUID('00'*16, version=6)) # Integer value out of range. - badvalue(lambda: uuid.UUID(int=-1)) - badvalue(lambda: uuid.UUID(int=1<<128)) + badvalue(lambda: self.uuid.UUID(int=-1)) + badvalue(lambda: self.uuid.UUID(int=1<<128)) # Must supply exactly one of hex, bytes, fields, int. h, b, f, i = '00'*16, b'\0'*16, (0, 0, 0, 0, 0, 0), 0 - uuid.UUID(h) - uuid.UUID(hex=h) - uuid.UUID(bytes=b) - uuid.UUID(bytes_le=b) - uuid.UUID(fields=f) - uuid.UUID(int=i) + self.uuid.UUID(h) + self.uuid.UUID(hex=h) + self.uuid.UUID(bytes=b) + self.uuid.UUID(bytes_le=b) + self.uuid.UUID(fields=f) + self.uuid.UUID(int=i) # Wrong number of arguments (positional). - badtype(lambda: uuid.UUID()) - badtype(lambda: uuid.UUID(h, b)) - badtype(lambda: uuid.UUID(h, b, b)) - badtype(lambda: uuid.UUID(h, b, b, f)) - badtype(lambda: uuid.UUID(h, b, b, f, i)) + badtype(lambda: self.uuid.UUID()) + badtype(lambda: self.uuid.UUID(h, b)) + badtype(lambda: self.uuid.UUID(h, b, b)) + badtype(lambda: self.uuid.UUID(h, b, b, f)) + badtype(lambda: self.uuid.UUID(h, b, b, f, i)) # Duplicate arguments. for hh in [[], [('hex', h)]]: @@ -277,12 +277,12 @@ def test_exceptions(self): for ff in [[], [('fields', f)]]: args = dict(hh + bb + bble + ii + ff) if len(args) != 0: - badtype(lambda: uuid.UUID(h, **args)) + badtype(lambda: self.uuid.UUID(h, **args)) if len(args) != 1: - badtype(lambda: uuid.UUID(**args)) + badtype(lambda: self.uuid.UUID(**args)) # Immutability. - u = uuid.UUID(h) + u = self.uuid.UUID(h) badtype(lambda: setattr(u, 'hex', h)) badtype(lambda: setattr(u, 'bytes', b)) badtype(lambda: setattr(u, 'bytes_le', b)) @@ -301,11 +301,11 @@ def test_exceptions(self): badtype(lambda: u > object()) def test_getnode(self): - node1 = uuid.getnode() + node1 = self.uuid.getnode() self.assertTrue(0 < node1 < (1 << 48), '%012x' % node1) # Test it again to ensure consistency. - node2 = uuid.getnode() + node2 = self.uuid.getnode() self.assertEqual(node1, node2, '%012x != %012x' % (node1, node2)) @unittest.skipUnless(importable('ctypes'), 'requires ctypes') @@ -313,114 +313,109 @@ def test_uuid1(self): equal = self.assertEqual # Make sure uuid1() generates UUIDs that are actually version 1. - for u in [uuid.uuid1() for i in range(10)]: - equal(u.variant, uuid.RFC_4122) + for u in [self.uuid.uuid1() for i in range(10)]: + equal(u.variant, self.uuid.RFC_4122) equal(u.version, 1) # Make sure the generated UUIDs are actually unique. uuids = {} - for u in [uuid.uuid1() for i in range(1000)]: + for u in [self.uuid.uuid1() for i in range(1000)]: uuids[u] = 1 equal(len(uuids.keys()), 1000) # Make sure the supplied node ID appears in the UUID. - u = uuid.uuid1(0) + u = self.uuid.uuid1(0) equal(u.node, 0) - u = uuid.uuid1(0x123456789abc) + u = self.uuid.uuid1(0x123456789abc) equal(u.node, 0x123456789abc) - u = uuid.uuid1(0xffffffffffff) + u = self.uuid.uuid1(0xffffffffffff) equal(u.node, 0xffffffffffff) # Make sure the supplied clock sequence appears in the UUID. - u = uuid.uuid1(0x123456789abc, 0) + u = self.uuid.uuid1(0x123456789abc, 0) equal(u.node, 0x123456789abc) equal(((u.clock_seq_hi_variant & 0x3f) << 8) | u.clock_seq_low, 0) - u = uuid.uuid1(0x123456789abc, 0x1234) + u = self.uuid.uuid1(0x123456789abc, 0x1234) equal(u.node, 0x123456789abc) equal(((u.clock_seq_hi_variant & 0x3f) << 8) | u.clock_seq_low, 0x1234) - u = uuid.uuid1(0x123456789abc, 0x3fff) + u = self.uuid.uuid1(0x123456789abc, 0x3fff) equal(u.node, 0x123456789abc) equal(((u.clock_seq_hi_variant & 0x3f) << 8) | u.clock_seq_low, 0x3fff) - requires_ugt = unittest.skipUnless(uuid._has_uuid_generate_time_safe, - 'requires uuid_generate_time_safe(3)') - - @requires_ugt - # bpo-29925: On Mac OS X Tiger, uuid.uuid1().is_safe returns - # uuid.SafeUUID.unknown + # bpo-29925: On Mac OS X Tiger, self.uuid.uuid1().is_safe returns + # self.uuid.SafeUUID.unknown @support.requires_mac_ver(10, 5) def test_uuid1_safe(self): - u = uuid.uuid1() + if not self.uuid._has_uuid_generate_time_safe: + self.skipTest('requires uuid_generate_time_safe(3)') + + u = self.uuid.uuid1() # uuid_generate_time_safe() may return 0 or -1 but what it returns is # dependent on the underlying platform support. At least it cannot be # unknown (unless I suppose the platform is buggy). - self.assertNotEqual(u.is_safe, uuid.SafeUUID.unknown) + self.assertNotEqual(u.is_safe, self.uuid.SafeUUID.unknown) - @requires_ugt def test_uuid1_unknown(self): # Even if the platform has uuid_generate_time_safe(), let's mock it to # be uuid_generate_time() and ensure the safety is unknown. - f = uuid._generate_time_safe - with unittest.mock.patch('uuid._generate_time_safe', - lambda: (f()[0], None)): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) + f = self.uuid._generate_time_safe + with unittest.mock.patch.object(self.uuid, '_generate_time_safe', + lambda: (f()[0], None)): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) - @requires_ugt def test_uuid1_is_safe(self): - f = uuid._generate_time_safe - with unittest.mock.patch('uuid._generate_time_safe', - lambda: (f()[0], 0)): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.safe) + f = self.uuid._generate_time_safe + with unittest.mock.patch.object(self.uuid, '_generate_time_safe', + lambda: (f()[0], 0)): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.safe) - @requires_ugt def test_uuid1_is_unsafe(self): - f = uuid._generate_time_safe - with unittest.mock.patch('uuid._generate_time_safe', - lambda: (f()[0], -1)): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unsafe) + f = self.uuid._generate_time_safe + with unittest.mock.patch.object(self.uuid, '_generate_time_safe', + lambda: (f()[0], -1)): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unsafe) - @requires_ugt def test_uuid1_bogus_return_value(self): - f = uuid._generate_time_safe - with unittest.mock.patch('uuid._generate_time_safe', - lambda: (f()[0], 3)): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) + f = self.uuid._generate_time_safe + with unittest.mock.patch.object(self.uuid, '_generate_time_safe', + lambda: (f()[0], 3)): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) def test_uuid3(self): equal = self.assertEqual # Test some known version-3 UUIDs. - for u, v in [(uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org'), + for u, v in [(self.uuid.uuid3(self.uuid.NAMESPACE_DNS, 'python.org'), '6fa459ea-ee8a-3ca4-894e-db77e160355e'), - (uuid.uuid3(uuid.NAMESPACE_URL, 'http://python.org/'), + (self.uuid.uuid3(self.uuid.NAMESPACE_URL, 'http://python.org/'), '9fe8e8c4-aaa8-32a9-a55c-4535a88b748d'), - (uuid.uuid3(uuid.NAMESPACE_OID, '1.3.6.1'), + (self.uuid.uuid3(self.uuid.NAMESPACE_OID, '1.3.6.1'), 'dd1a1cef-13d5-368a-ad82-eca71acd4cd1'), - (uuid.uuid3(uuid.NAMESPACE_X500, 'c=ca'), + (self.uuid.uuid3(self.uuid.NAMESPACE_X500, 'c=ca'), '658d3002-db6b-3040-a1d1-8ddd7d189a4d'), ]: - equal(u.variant, uuid.RFC_4122) + equal(u.variant, self.uuid.RFC_4122) equal(u.version, 3) - equal(u, uuid.UUID(v)) + equal(u, self.uuid.UUID(v)) equal(str(u), v) def test_uuid4(self): equal = self.assertEqual # Make sure uuid4() generates UUIDs that are actually version 4. - for u in [uuid.uuid4() for i in range(10)]: - equal(u.variant, uuid.RFC_4122) + for u in [self.uuid.uuid4() for i in range(10)]: + equal(u.variant, self.uuid.RFC_4122) equal(u.version, 4) # Make sure the generated UUIDs are actually unique. uuids = {} - for u in [uuid.uuid4() for i in range(1000)]: + for u in [self.uuid.uuid4() for i in range(1000)]: uuids[u] = 1 equal(len(uuids.keys()), 1000) @@ -428,43 +423,51 @@ def test_uuid5(self): equal = self.assertEqual # Test some known version-5 UUIDs. - for u, v in [(uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org'), + for u, v in [(self.uuid.uuid5(self.uuid.NAMESPACE_DNS, 'python.org'), '886313e1-3b8a-5372-9b90-0c9aee199e5d'), - (uuid.uuid5(uuid.NAMESPACE_URL, 'http://python.org/'), + (self.uuid.uuid5(self.uuid.NAMESPACE_URL, 'http://python.org/'), '4c565f0d-3f5a-5890-b41b-20cf47701c5e'), - (uuid.uuid5(uuid.NAMESPACE_OID, '1.3.6.1'), + (self.uuid.uuid5(self.uuid.NAMESPACE_OID, '1.3.6.1'), '1447fa61-5277-5fef-a9b3-fbc6e44f4af3'), - (uuid.uuid5(uuid.NAMESPACE_X500, 'c=ca'), + (self.uuid.uuid5(self.uuid.NAMESPACE_X500, 'c=ca'), 'cc957dd1-a972-5349-98cd-874190002798'), ]: - equal(u.variant, uuid.RFC_4122) + equal(u.variant, self.uuid.RFC_4122) equal(u.version, 5) - equal(u, uuid.UUID(v)) + equal(u, self.uuid.UUID(v)) equal(str(u), v) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def testIssue8621(self): - # On at least some versions of OSX uuid.uuid4 generates + # On at least some versions of OSX self.uuid.uuid4 generates # the same sequence of UUIDs in the parent and any # children started using fork. fds = os.pipe() pid = os.fork() if pid == 0: os.close(fds[0]) - value = uuid.uuid4() + value = self.uuid.uuid4() os.write(fds[1], value.hex.encode('latin-1')) os._exit(0) else: os.close(fds[1]) self.addCleanup(os.close, fds[0]) - parent_value = uuid.uuid4().hex + parent_value = self.uuid.uuid4().hex os.waitpid(pid, 0) child_value = os.read(fds[0], 100).decode('latin-1') self.assertNotEqual(parent_value, child_value) +class TestUUIDWithoutExtModule(BaseTestUUID, unittest.TestCase): + uuid = py_uuid + +@unittest.skipUnless(c_uuid, 'requires the C _uuid module') +class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase): + uuid = c_uuid + + class BaseTestInternals: @unittest.skipUnless(os.name == 'posix', 'requires Posix') From 34df7394b1540b91bd97bd49993921c28a3d656a Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 18:41:40 +0200 Subject: [PATCH 6/8] Fix tests on Windows --- Lib/test/test_uuid.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 91334b83b389cb..0d01b3915c431c 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -308,7 +308,6 @@ def test_getnode(self): node2 = self.uuid.getnode() self.assertEqual(node1, node2, '%012x != %012x' % (node1, node2)) - @unittest.skipUnless(importable('ctypes'), 'requires ctypes') def test_uuid1(self): equal = self.assertEqual @@ -316,6 +315,9 @@ def test_uuid1(self): for u in [self.uuid.uuid1() for i in range(10)]: equal(u.variant, self.uuid.RFC_4122) equal(u.version, 1) + self.assertIn(u.is_safe, {self.uuid.SafeUUID.safe, + self.uuid.SafeUUID.unsafe, + self.uuid.SafeUUID.unknown}) # Make sure the generated UUIDs are actually unique. uuids = {} @@ -347,6 +349,7 @@ def test_uuid1(self): # bpo-29925: On Mac OS X Tiger, self.uuid.uuid1().is_safe returns # self.uuid.SafeUUID.unknown @support.requires_mac_ver(10, 5) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_safe(self): if not self.uuid._has_uuid_generate_time_safe: self.skipTest('requires uuid_generate_time_safe(3)') @@ -357,6 +360,7 @@ def test_uuid1_safe(self): # unknown (unless I suppose the platform is buggy). self.assertNotEqual(u.is_safe, self.uuid.SafeUUID.unknown) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_unknown(self): # Even if the platform has uuid_generate_time_safe(), let's mock it to # be uuid_generate_time() and ensure the safety is unknown. @@ -366,6 +370,7 @@ def test_uuid1_unknown(self): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_is_safe(self): f = self.uuid._generate_time_safe with unittest.mock.patch.object(self.uuid, '_generate_time_safe', @@ -373,6 +378,7 @@ def test_uuid1_is_safe(self): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.safe) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_is_unsafe(self): f = self.uuid._generate_time_safe with unittest.mock.patch.object(self.uuid, '_generate_time_safe', @@ -380,6 +386,7 @@ def test_uuid1_is_unsafe(self): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.unsafe) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_bogus_return_value(self): f = self.uuid._generate_time_safe with unittest.mock.patch.object(self.uuid, '_generate_time_safe', From 92b8670d13e6d2241540aacee1b17664a4646a11 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 22:21:56 +0200 Subject: [PATCH 7/8] Address review comments --- Lib/test/test_uuid.py | 35 +++++++++++++++++++++++------------ Lib/uuid.py | 24 +++++++++++++----------- setup.py | 10 +++++----- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 0d01b3915c431c..f2778c855f829e 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,6 +1,7 @@ import unittest.mock from test import support import builtins +import contextlib import io import os import shutil @@ -17,7 +18,9 @@ def importable(name): except: return False + class BaseTestUUID: + uuid = None def test_UUID(self): equal = self.assertEqual @@ -360,37 +363,44 @@ def test_uuid1_safe(self): # unknown (unless I suppose the platform is buggy). self.assertNotEqual(u.is_safe, self.uuid.SafeUUID.unknown) + @contextlib.contextmanager + def mock_generate_time_safe(self, safe_value): + """ + Mock uuid._generate_time_safe() to return a given *safe_value*. + """ + if os.name != 'posix': + self.skipTest('POSIX-only test') + self.uuid._load_system_functions() + f = self.uuid._generate_time_safe + if f is None: + self.skipTest('need uuid._generate_time_safe') + with unittest.mock.patch.object(self.uuid, '_generate_time_safe', + lambda: (f()[0], safe_value)): + yield + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_unknown(self): # Even if the platform has uuid_generate_time_safe(), let's mock it to # be uuid_generate_time() and ensure the safety is unknown. - f = self.uuid._generate_time_safe - with unittest.mock.patch.object(self.uuid, '_generate_time_safe', - lambda: (f()[0], None)): + with self.mock_generate_time_safe(None): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_is_safe(self): - f = self.uuid._generate_time_safe - with unittest.mock.patch.object(self.uuid, '_generate_time_safe', - lambda: (f()[0], 0)): + with self.mock_generate_time_safe(0): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.safe) @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_is_unsafe(self): - f = self.uuid._generate_time_safe - with unittest.mock.patch.object(self.uuid, '_generate_time_safe', - lambda: (f()[0], -1)): + with self.mock_generate_time_safe(-1): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.unsafe) @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_bogus_return_value(self): - f = self.uuid._generate_time_safe - with unittest.mock.patch.object(self.uuid, '_generate_time_safe', - lambda: (f()[0], 3)): + with self.mock_generate_time_safe(3): u = self.uuid.uuid1() self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) @@ -476,6 +486,7 @@ class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase): class BaseTestInternals: + uuid = None @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_find_mac(self): diff --git a/Lib/uuid.py b/Lib/uuid.py index d9c59fcd0ee6ee..b2fbd38c353efc 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -480,6 +480,13 @@ def _netbios_getnode(): _generate_time_safe = _UuidCreate = None _has_uuid_generate_time_safe = None +# Import optional C extension at toplevel, to help disabling it when testing +try: + import _uuid +except ImportError: + _uuid = None + + def _load_system_functions(): """ Try to load platform-specific functions for generating uuids. @@ -500,15 +507,10 @@ def _load_system_functions(): # Assume that the uuid_generate functions are broken from 10.5 onward, # the test can be adjusted when a later version is fixed. pass - else: - try: - import _uuid - except ImportError: - pass - else: - _generate_time_safe = _uuid.generate_time_safe - _has_uuid_generate_time_safe = True - return + elif _uuid is not None: + _generate_time_safe = _uuid.generate_time_safe + _has_uuid_generate_time_safe = True + return try: # If we couldn't find an extension module, try ctypes to find @@ -529,11 +531,11 @@ def _load_system_functions(): continue # Try to find the safe variety first. if hasattr(lib, 'uuid_generate_time_safe'): - _uuid_generate_time = lib.uuid_generate_time_safe + _uuid_generate_time_safe = lib.uuid_generate_time_safe # int uuid_generate_time_safe(uuid_t out); def _generate_time_safe(): _buffer = ctypes.create_string_buffer(16) - res = _uuid_generate_time(_buffer) + res = _uuid_generate_time_safe(_buffer) return bytes(_buffer.raw), res _has_uuid_generate_time_safe = True break diff --git a/setup.py b/setup.py index 197403f6a29d5a..0b536c8ca32b99 100644 --- a/setup.py +++ b/setup.py @@ -1669,16 +1669,16 @@ class db_found(Exception): pass missing.append('_tkinter') # Build the _uuid module if possible - build_uuid = False - if find_file("uuid.h", inc_dirs, ["/usr/include/uuid"]): + uuid_incs = find_file("uuid.h", inc_dirs, ["/usr/include/uuid"]) + if uuid_incs: if self.compiler.find_library_file(lib_dirs, 'uuid'): uuid_libs = ['uuid'] else: uuid_libs = [] - build_uuid = True - if build_uuid: + if uuid_incs: self.extensions.append(Extension('_uuid', ['_uuidmodule.c'], - libraries=uuid_libs)) + libraries=uuid_libs, + include_dirs=uuid_incs)) else: missing.append('_uuid') From 88bb338ab187f6062f96b87883d1d159364864e6 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Sep 2017 22:35:17 +0200 Subject: [PATCH 8/8] Fix test regression --- Lib/test/test_uuid.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index f2778c855f829e..083c2aa8aab54d 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -571,7 +571,11 @@ def test_random_getnode(self): def test_unix_getnode(self): if not importable('_uuid') and not importable('ctypes'): self.skipTest("neither _uuid extension nor ctypes available") - self.check_node(self.uuid._unix_getnode(), 'unix') + try: # Issues 1481, 3581: _uuid_generate_time() might be None. + node = self.uuid._unix_getnode() + except TypeError: + self.skipTest('requires uuid_generate_time') + self.check_node(node, 'unix') @unittest.skipUnless(os.name == 'nt', 'requires Windows') @unittest.skipUnless(importable('ctypes'), 'requires ctypes')