diff options
author | Antoine Pitrou <pitrou@free.fr> | 2017-09-28 21:03:06 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-28 21:03:06 (GMT) |
commit | a106aec2ed6ba171838ca7e6ba43c4e722bbecd1 (patch) | |
tree | 2a242b9061198d40f40d2921818e6da8089a9b50 | |
parent | 8d59aca4a953b097a9b02b0ecafef840e4ac5855 (diff) | |
download | cpython-a106aec2ed6ba171838ca7e6ba43c4e722bbecd1.zip cpython-a106aec2ed6ba171838ca7e6ba43c4e722bbecd1.tar.gz cpython-a106aec2ed6ba171838ca7e6ba43c4e722bbecd1.tar.bz2 |
bpo-11063, bpo-20519: avoid ctypes and improve import time for uuid (#3796)
bpo-11063, bpo-20519: avoid ctypes and improve import time for uuid.
-rw-r--r-- | Lib/test/test_uuid.py | 315 | ||||
-rw-r--r-- | Lib/uuid.py | 176 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2017-09-28-13-17-33.bpo-20519.FteeQQ.rst | 1 | ||||
-rw-r--r-- | Modules/_uuidmodule.c | 35 | ||||
-rw-r--r-- | setup.py | 14 |
5 files changed, 335 insertions, 206 deletions
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index cd0f81e..083c2aa 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -1,11 +1,15 @@ import unittest.mock from test import support import builtins +import contextlib import io 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']) + def importable(name): try: @@ -14,7 +18,10 @@ def importable(name): except: return False -class TestUUID(unittest.TestCase): + +class BaseTestUUID: + uuid = None + def test_UUID(self): equal = self.assertEqual ascending = [] @@ -28,7 +35,7 @@ class TestUUID(unittest.TestCase): (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', @@ -37,7 +44,7 @@ class TestUUID(unittest.TestCase): (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', @@ -46,7 +53,7 @@ class TestUUID(unittest.TestCase): (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', @@ -55,7 +62,7 @@ class TestUUID(unittest.TestCase): (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', @@ -64,7 +71,7 @@ class TestUUID(unittest.TestCase): (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', @@ -73,7 +80,7 @@ class TestUUID(unittest.TestCase): (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', @@ -82,7 +89,7 @@ class TestUUID(unittest.TestCase): (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', @@ -91,7 +98,7 @@ class TestUUID(unittest.TestCase): (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', @@ -100,7 +107,7 @@ class TestUUID(unittest.TestCase): (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', @@ -109,7 +116,7 @@ class TestUUID(unittest.TestCase): (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', @@ -118,7 +125,7 @@ class TestUUID(unittest.TestCase): (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', @@ -127,7 +134,7 @@ class TestUUID(unittest.TestCase): (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', @@ -136,7 +143,7 @@ class TestUUID(unittest.TestCase): (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', @@ -145,14 +152,14 @@ class TestUUID(unittest.TestCase): (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) @@ -206,64 +213,64 @@ class TestUUID(unittest.TestCase): 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)]]: @@ -273,12 +280,12 @@ class TestUUID(unittest.TestCase): 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)) @@ -297,122 +304,135 @@ class TestUUID(unittest.TestCase): 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') 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) + 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 = {} - 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._uuid_generate_time is not None, - '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) + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') 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) - - @requires_ugt + 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. - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', None): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) + with self.mock_generate_time_safe(None): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) - @requires_ugt + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_is_safe(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: 0): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.safe) + with self.mock_generate_time_safe(0): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.safe) - @requires_ugt + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_is_unsafe(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: -1): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unsafe) + with self.mock_generate_time_safe(-1): + u = self.uuid.uuid1() + self.assertEqual(u.is_safe, self.uuid.SafeUUID.unsafe) - @requires_ugt + @unittest.skipUnless(os.name == 'posix', 'POSIX-only test') def test_uuid1_bogus_return_value(self): - with unittest.mock.patch.object(uuid._uuid_generate_time, - 'restype', lambda x: 3): - u = uuid.uuid1() - self.assertEqual(u.is_safe, uuid.SafeUUID.unknown) + with self.mock_generate_time_safe(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) @@ -420,44 +440,54 @@ class TestUUID(unittest.TestCase): 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 TestInternals(unittest.TestCase): +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: + uuid = None + @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_find_mac(self): data = ''' @@ -473,7 +503,7 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab 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 +527,70 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab @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): + def test_unix_getnode(self): + if not importable('_uuid') and not importable('ctypes'): + self.skipTest("neither _uuid extension nor ctypes available") try: # Issues 1481, 3581: _uuid_generate_time() might be None. - node = uuid._unixdll_getnode() + node = self.uuid._unix_getnode() except TypeError: self.skipTest('requires uuid_generate_time') - self.check_node(node) + self.check_node(node, '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 15a81f5..b2fbd38 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -45,6 +45,7 @@ Typical usage: """ import os +import sys from enum import Enum @@ -475,73 +476,112 @@ 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 +_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 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: - 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. + import _uuid +except ImportError: + _uuid = 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. + pass + elif _uuid is not None: + _generate_time_safe = _uuid.generate_time_safe + _has_uuid_generate_time_safe = True + return + 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 + # 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_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_safe(_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.""" + import ctypes + _load_system_functions() _buffer = ctypes.create_string_buffer(16) if _UuidCreate(_buffer) == 0: return UUID(bytes=bytes_(_buffer.raw)).node @@ -551,6 +591,7 @@ def _random_getnode(): import random return random.getrandbits(48) | 0x010000000000 + _node = None def getnode(): @@ -561,16 +602,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 +620,7 @@ def getnode(): if _node is not None: return _node + _last_timestamp = None def uuid1(node=None, clock_seq=None): @@ -591,14 +631,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/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 0000000..ef86c47 --- /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. diff --git a/Modules/_uuidmodule.c b/Modules/_uuidmodule.c new file mode 100644 index 0000000..e263b40 --- /dev/null +++ b/Modules/_uuidmodule.c @@ -0,0 +1,35 @@ +#define PY_SSIZE_T_CLEAN + +#include "Python.h" +#include <uuid/uuid.h> + + +static PyObject * +py_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) py_uuid_generate_time_safe, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} /* sentinel */ +}; + +static struct PyModuleDef uuidmodule = { + PyModuleDef_HEAD_INIT, + .m_name = "_uuid", + .m_size = -1, + .m_methods = uuid_methods, +}; + +PyMODINIT_FUNC +PyInit__uuid(void) +{ + assert(sizeof(uuid_t) == 16); + return PyModule_Create(&uuidmodule); +} @@ -1668,6 +1668,20 @@ class PyBuildExt(build_ext): if '_tkinter' not in [e.name for e in self.extensions]: missing.append('_tkinter') + # Build the _uuid module if possible + 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 = [] + if uuid_incs: + self.extensions.append(Extension('_uuid', ['_uuidmodule.c'], + libraries=uuid_libs, + include_dirs=uuid_incs)) + else: + missing.append('_uuid') + ## # Uncomment these lines if you want to play with xxmodule.c ## ext = Extension('xx', ['xxmodule.c']) ## self.extensions.append(ext) |