-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
bpo-28009: Fix uuid.uuid1() and uuid.get_node() on AIX #8672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 24 commits
4ed41e9
ba47933
c6029a4
7e1874d
6a5bccb
a98309b
53ef750
6fd5f8e
3bb1c08
1af8e3d
d3eaab9
3469a01
399e8ec
6e2a9bf
bb3a460
db6767f
25d3ef1
fa9b43b
4a0c8f0
6463707
a0ef760
095e221
ec4c0e8
70a45f0
b1b4952
8f0687a
4756670
c55714a
10f272e
27b6c32
33969b9
7a57734
4628cea
d2830a2
0688727
ff1ee20
083e9c6
30cd017
27a972b
543e66d
588fda7
28f7a01
6fc2129
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,11 +9,10 @@ | |
import shutil | ||
import subprocess | ||
import sys | ||
from unittest import mock | ||
|
||
py_uuid = support.import_fresh_module('uuid', blocked=['_uuid']) | ||
c_uuid = support.import_fresh_module('uuid', fresh=['_uuid']) | ||
|
||
_AIX = sys.platform.startswith("aix") | ||
|
||
def importable(name): | ||
try: | ||
|
@@ -461,8 +460,7 @@ def test_uuid1_eui64(self): | |
with unittest.mock.patch.multiple( | ||
self.uuid, | ||
_node=None, # Ignore any cached node value. | ||
_NODE_GETTERS_WIN32=[too_large_getter], | ||
_NODE_GETTERS_UNIX=[too_large_getter], | ||
_NODE_GETTERS=[too_large_getter], | ||
): | ||
node = self.uuid.getnode() | ||
self.assertTrue(0 < node < (1 << 48), '%012x' % node) | ||
|
@@ -569,19 +567,19 @@ def test_uuid1_bogus_return_value(self): | |
self.assertEqual(u.is_safe, self.uuid.SafeUUID.unknown) | ||
|
||
def test_uuid1_time(self): | ||
with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ | ||
mock.patch.object(self.uuid, '_generate_time_safe', None), \ | ||
mock.patch.object(self.uuid, '_last_timestamp', None), \ | ||
mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \ | ||
mock.patch('time.time_ns', return_value=1545052026752910643), \ | ||
mock.patch('random.getrandbits', return_value=5317): # guaranteed to be random | ||
with unittest.mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ | ||
unittest.mock.patch.object(self.uuid, '_generate_time_safe', None), \ | ||
unittest.mock.patch.object(self.uuid, '_last_timestamp', None), \ | ||
unittest.mock.patch.object(self.uuid, 'getnode', return_value=93328246233727), \ | ||
unittest.mock.patch('time.time_ns', return_value=1545052026752910643), \ | ||
unittest.mock.patch('random.getrandbits', return_value=5317): # guaranteed to be random | ||
u = self.uuid.uuid1() | ||
self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) | ||
|
||
with mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ | ||
mock.patch.object(self.uuid, '_generate_time_safe', None), \ | ||
mock.patch.object(self.uuid, '_last_timestamp', None), \ | ||
mock.patch('time.time_ns', return_value=1545052026752910643): | ||
with unittest.mock.patch.object(self.uuid, '_has_uuid_generate_time_safe', False), \ | ||
unittest.mock.patch.object(self.uuid, '_generate_time_safe', None), \ | ||
unittest.mock.patch.object(self.uuid, '_last_timestamp', None), \ | ||
unittest.mock.patch('time.time_ns', return_value=1545052026752910643): | ||
u = self.uuid.uuid1(node=93328246233727, clock_seq=5317) | ||
self.assertEqual(u, self.uuid.UUID('a7a55b92-01fc-11e9-94c5-54e1acf6da7f')) | ||
|
||
|
@@ -669,14 +667,41 @@ class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase): | |
class BaseTestInternals: | ||
uuid = None | ||
|
||
@unittest.skipUnless(_AIX, 'requires AIX') | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the skip logic changes that you extracted out to other PRs, a better check here will be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That also suggests abstracting out a marker attribute for
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You (or I) resolved this. Both marker attributes are defined, and rather than skip, are assigned to the mock tests. If I have missed something - I'll get on it, if not - please consider approving the changes, Thx. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue is that this is a valid test anywhere that those two conditions are true, so rather than testing for "AIX_" here, you can just test for the MAC notation conventions that the test cares about. |
||
def test_find_mac_netstat(self): | ||
data = '''\ | ||
Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll | ||
en0 1500 link#2 fe.ad.c.1.23.4 1714807956 0 711348489 0 0 | ||
01:00:5e:00:00:01 | ||
en0 1500 192.168.129 x071 1714807956 0 711348489 0 0 | ||
224.0.0.1 | ||
en0 1500 192.168.90 x071 1714807956 0 711348489 0 0 | ||
224.0.0.1 | ||
''' | ||
popen = unittest.mock.MagicMock() | ||
popen.stdout = io.BytesIO(data.encode()) | ||
|
||
with unittest.mock.patch.object(shutil, 'which', | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return_value='/usr/bin/netstat'): | ||
with unittest.mock.patch.object(subprocess, 'Popen', | ||
return_value=popen): | ||
mac = self.uuid._find_mac_netstat( | ||
command='netstat', | ||
args='-ia', | ||
hw_identifiers=b'Address', | ||
get_index=lambda x: x, | ||
) | ||
|
||
self.assertEqual(mac, 0xfead0c012304) | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@unittest.skipUnless(os.name == 'posix', 'requires Posix') | ||
@unittest.skipIf(_AIX, 'AIX "ifconfig" does not provide macaddr') | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def test_find_mac(self): | ||
data = ''' | ||
fake hwaddr | ||
fake Link encap:UNSPEC hwaddr 00-00 | ||
cscotun0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 | ||
eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab | ||
''' | ||
|
||
popen = unittest.mock.MagicMock() | ||
popen.stdout = io.BytesIO(data.encode()) | ||
|
||
|
@@ -703,21 +728,25 @@ def check_node(self, node, requires=None): | |
"%s is not an RFC 4122 node ID" % hex) | ||
|
||
@unittest.skipUnless(os.name == 'posix', 'requires Posix') | ||
@unittest.skipIf(_AIX, 'AIX "ifconfig" does not provide macaddr') | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def test_ifconfig_getnode(self): | ||
node = self.uuid._ifconfig_getnode() | ||
self.check_node(node, 'ifconfig') | ||
|
||
@unittest.skipUnless(os.name == 'posix', 'requires Posix') | ||
@unittest.skipIf(_AIX, 'requires command "ip"') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
def test_ip_getnode(self): | ||
node = self.uuid._ip_getnode() | ||
self.check_node(node, 'ip') | ||
|
||
@unittest.skipUnless(os.name == 'posix', 'requires Posix') | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@unittest.skipIf(_AIX, 'AIX "arp" does not provide macaddr') | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def test_arp_getnode(self): | ||
node = self.uuid._arp_getnode() | ||
self.check_node(node, 'arp') | ||
|
||
@unittest.skipUnless(os.name == 'posix', 'requires Posix') | ||
@unittest.skipIf(_AIX, 'requires command "lanscan"') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
def test_lanscan_getnode(self): | ||
node = self.uuid._lanscan_getnode() | ||
self.check_node(node, 'lanscan') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,10 @@ | |
|
||
__author__ = 'Ka-Ping Yee <[email protected]>' | ||
|
||
_WIN32 = sys.platform == 'win32' | ||
_AIX = sys.platform.startswith("aix") | ||
_MAC_DELIM = b':' if not _AIX else b'.' | ||
taleinat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ | ||
'reserved for NCS compatibility', 'specified in RFC 4122', | ||
'reserved for Microsoft compatibility', 'reserved for future definition'] | ||
|
@@ -390,7 +394,7 @@ def _find_mac(command, args, hw_identifiers, get_index): | |
if words[i] in hw_identifiers: | ||
try: | ||
word = words[get_index(i)] | ||
mac = int(word.replace(b':', b''), 16) | ||
mac = int(word.replace(_MAC_DELIM, b''), 16) | ||
if _is_universal(mac): | ||
return mac | ||
first_local_mac = first_local_mac or mac | ||
|
@@ -405,6 +409,48 @@ def _find_mac(command, args, hw_identifiers, get_index): | |
pass | ||
return first_local_mac or None | ||
|
||
def _find_mac_netstat(command, args, hw_identifiers, get_index): | ||
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
aixtools marked this conversation as resolved.
Show resolved
Hide resolved
|
||
first_local_mac = None | ||
mac = None | ||
try: | ||
proc = _popen(command, *args.split()) | ||
if not proc: | ||
return None | ||
with proc: | ||
words = proc.stdout.readline().rstrip().split() | ||
try: | ||
i = words.index(hw_identifiers) | ||
except ValueError: | ||
return None | ||
for line in proc.stdout: | ||
try: | ||
words = line.rstrip().split() | ||
word = words[get_index(i)] | ||
if len(word) == 17 and word.count(_MAC_DELIM) == 5: | ||
mac = int(word.replace(_MAC_DELIM, b''), 16) | ||
elif _AIX and word.count(_MAC_DELIM) == 5: | ||
# the extracted hex string is not a 12 hex digit | ||
# string, so add the fields piece by piece | ||
if len(word) < 17 and len(word) >= 11: | ||
mac = 0 | ||
fields = word.split(_MAC_DELIM) | ||
for hex in fields: | ||
mac <<= 8 | ||
mac += int(hex, 16) | ||
if mac and _is_universal(mac): | ||
return mac | ||
first_local_mac = first_local_mac or mac | ||
except (ValueError, IndexError): | ||
# Virtual interfaces, such as those provided by | ||
# VPNs, do not have a colon-delimited MAC address | ||
# as expected, but a 16-byte HWAddr separated by | ||
# dashes. These should be ignored in favor of a | ||
# real MAC address | ||
pass | ||
except OSError: | ||
pass | ||
return first_local_mac or None | ||
|
||
def _ifconfig_getnode(): | ||
"""Get the hardware address on Unix by running ifconfig.""" | ||
# This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. | ||
|
@@ -456,32 +502,9 @@ def _lanscan_getnode(): | |
|
||
def _netstat_getnode(): | ||
"""Get the hardware address on Unix by running netstat.""" | ||
# This might work on AIX, Tru64 UNIX. | ||
first_local_mac = None | ||
try: | ||
proc = _popen('netstat', '-ia') | ||
if not proc: | ||
return None | ||
with proc: | ||
words = proc.stdout.readline().rstrip().split() | ||
try: | ||
i = words.index(b'Address') | ||
except ValueError: | ||
return None | ||
for line in proc.stdout: | ||
try: | ||
words = line.rstrip().split() | ||
word = words[i] | ||
if len(word) == 17 and word.count(b':') == 5: | ||
mac = int(word.replace(b':', b''), 16) | ||
if _is_universal(mac): | ||
return mac | ||
first_local_mac = first_local_mac or mac | ||
except (ValueError, IndexError): | ||
pass | ||
except OSError: | ||
pass | ||
return first_local_mac or None | ||
# This works on AIX and might work on Tru64 UNIX. | ||
mac = _find_mac_netstat('netstat', '-ia', b'Address', lambda i: i) | ||
return mac or None | ||
|
||
def _ipconfig_getnode(): | ||
"""Get the hardware address on Windows by running ipconfig.exe.""" | ||
|
@@ -673,13 +696,15 @@ def _random_getnode(): | |
return random.getrandbits(48) | (1 << 40) | ||
|
||
|
||
if _AIX: | ||
_NODE_GETTERS = [_unix_getnode, _netstat_getnode] | ||
elif _WIN32: | ||
_NODE_GETTERS = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] | ||
else: | ||
_NODE_GETTERS = [_unix_getnode, _ifconfig_getnode, _ip_getnode, | ||
_arp_getnode, _lanscan_getnode, _netstat_getnode] | ||
_node = None | ||
|
||
_NODE_GETTERS_WIN32 = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] | ||
|
||
_NODE_GETTERS_UNIX = [_unix_getnode, _ifconfig_getnode, _ip_getnode, | ||
_arp_getnode, _lanscan_getnode, _netstat_getnode] | ||
|
||
def getnode(*, getters=None): | ||
"""Get the hardware address as a 48-bit positive integer. | ||
|
||
|
@@ -692,12 +717,7 @@ def getnode(*, getters=None): | |
if _node is not None: | ||
return _node | ||
|
||
if sys.platform == 'win32': | ||
getters = _NODE_GETTERS_WIN32 | ||
else: | ||
getters = _NODE_GETTERS_UNIX | ||
|
||
for getter in getters + [_random_getnode]: | ||
for getter in _NODE_GETTERS + [_random_getnode]: | ||
try: | ||
_node = getter() | ||
except: | ||
|
@@ -706,7 +726,6 @@ def getnode(*, getters=None): | |
return _node | ||
assert False, '_random_getnode() returned invalid value: {}'.format(_node) | ||
|
||
|
||
_last_timestamp = None | ||
|
||
def uuid1(node=None, clock_seq=None): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fix uuid.uuid1() and uuid.get_node() on AIX |
Uh oh!
There was an error while loading. Please reload this page.