bpo-35967 resolve platform.processor late (GH-12239)

* Replace flag-flip indirection with direct inspection

* Use any for simpler code

* Avoid flag flip and set results directly.

* Resolve processor in a single function.

* Extract processor handling into a namespace (class)

* Remove _syscmd_uname, unused

* Restore platform.processor behavior to match prior expectation (reliant on uname -p in a subprocess).

* Extract '_unknown_as_blank' function.

* Override uname_result to resolve the processor late.

* Add a test intended to capture the expected values from 'uname -p'

* Instead of trying to keep track of all of the possible outputs on different systems (probably a fool's errand), simply assert that except for the known platform variance, uname().processor matches the output of 'uname -p'

* Use a skipIf directive

* Use contextlib.suppress to suppress the error. Inline strip call.

* 📜🤖 Added by blurb_it.

* Remove use of contextlib.suppress (it would fail with NameError if it had any effect). Rely on _unknown_as_blank to replace unknown with blank.

Co-authored-by: blurb-it[bot] <blurb-it[bot]@users.noreply.github.com>
This commit is contained in:
Jason R. Coombs 2020-04-16 08:28:09 -04:00 committed by GitHub
parent 6a5bf15c71
commit 518835f335
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 86 deletions

View file

@ -116,6 +116,9 @@ import collections
import os import os
import re import re
import sys import sys
import subprocess
import functools
import itertools
### Globals & Constants ### Globals & Constants
@ -600,22 +603,6 @@ def _follow_symlinks(filepath):
os.path.join(os.path.dirname(filepath), os.readlink(filepath))) os.path.join(os.path.dirname(filepath), os.readlink(filepath)))
return filepath return filepath
def _syscmd_uname(option, default=''):
""" Interface to the system's uname command.
"""
if sys.platform in ('dos', 'win32', 'win16'):
# XXX Others too ?
return default
import subprocess
try:
output = subprocess.check_output(('uname', option),
stderr=subprocess.DEVNULL,
text=True)
except (OSError, subprocess.CalledProcessError):
return default
return (output.strip() or default)
def _syscmd_file(target, default=''): def _syscmd_file(target, default=''):
@ -736,13 +723,89 @@ def architecture(executable=sys.executable, bits='', linkage=''):
return bits, linkage return bits, linkage
def _get_machine_win32():
# Try to use the PROCESSOR_* environment variables
# available on Win XP and later; see
# http://support.microsoft.com/kb/888731 and
# http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
# WOW64 processes mask the native architecture
return (
os.environ.get('PROCESSOR_ARCHITEW6432', '') or
os.environ.get('PROCESSOR_ARCHITECTURE', '')
)
class _Processor:
@classmethod
def get(cls):
func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess)
return func() or ''
def get_win32():
return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
def get_OpenVMS():
try:
import vms_lib
except ImportError:
pass
else:
csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
return 'Alpha' if cpu_number >= 128 else 'VAX'
def from_subprocess():
"""
Fall back to `uname -p`
"""
try:
return subprocess.check_output(
['uname', '-p'],
stderr=subprocess.DEVNULL,
text=True,
).strip()
except (OSError, subprocess.CalledProcessError):
pass
def _unknown_as_blank(val):
return '' if val == 'unknown' else val
### Portable uname() interface ### Portable uname() interface
uname_result = collections.namedtuple("uname_result", class uname_result(
"system node release version machine processor") collections.namedtuple(
"uname_result_base",
"system node release version machine")
):
"""
A uname_result that's largely compatible with a
simple namedtuple except that 'platform' is
resolved late and cached to avoid calling "uname"
except when needed.
"""
@functools.cached_property
def processor(self):
return _unknown_as_blank(_Processor.get())
def __iter__(self):
return itertools.chain(
super().__iter__(),
(self.processor,)
)
def __getitem__(self, key):
if key == 5:
return self.processor
return super().__getitem__(key)
_uname_cache = None _uname_cache = None
def uname(): def uname():
""" Fairly portable uname interface. Returns a tuple """ Fairly portable uname interface. Returns a tuple
@ -756,52 +819,30 @@ def uname():
""" """
global _uname_cache global _uname_cache
no_os_uname = 0
if _uname_cache is not None: if _uname_cache is not None:
return _uname_cache return _uname_cache
processor = ''
# Get some infos from the builtin os.uname API... # Get some infos from the builtin os.uname API...
try: try:
system, node, release, version, machine = os.uname() system, node, release, version, machine = infos = os.uname()
except AttributeError: except AttributeError:
no_os_uname = 1 system = sys.platform
node = _node()
release = version = machine = ''
infos = ()
if no_os_uname or not list(filter(None, (system, node, release, version, machine))): if not any(infos):
# Hmm, no there is either no uname or uname has returned # uname is not available
#'unknowns'... we'll have to poke around the system then.
if no_os_uname:
system = sys.platform
release = ''
version = ''
node = _node()
machine = ''
use_syscmd_ver = 1
# Try win32_ver() on win32 platforms # Try win32_ver() on win32 platforms
if system == 'win32': if system == 'win32':
release, version, csd, ptype = win32_ver() release, version, csd, ptype = win32_ver()
if release and version: machine = machine or _get_machine_win32()
use_syscmd_ver = 0
# Try to use the PROCESSOR_* environment variables
# available on Win XP and later; see
# http://support.microsoft.com/kb/888731 and
# http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
if not machine:
# WOW64 processes mask the native architecture
if "PROCESSOR_ARCHITEW6432" in os.environ:
machine = os.environ.get("PROCESSOR_ARCHITEW6432", '')
else:
machine = os.environ.get('PROCESSOR_ARCHITECTURE', '')
if not processor:
processor = os.environ.get('PROCESSOR_IDENTIFIER', machine)
# Try the 'ver' system command available on some # Try the 'ver' system command available on some
# platforms # platforms
if use_syscmd_ver: if not (release and version):
system, release, version = _syscmd_ver(system) system, release, version = _syscmd_ver(system)
# Normalize system to what win32_ver() normally returns # Normalize system to what win32_ver() normally returns
# (_syscmd_ver() tends to return the vendor name as well) # (_syscmd_ver() tends to return the vendor name as well)
@ -841,42 +882,15 @@ def uname():
if not release or release == '0': if not release or release == '0':
release = version release = version
version = '' version = ''
# Get processor information
try:
import vms_lib
except ImportError:
pass
else:
csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
if (cpu_number >= 128):
processor = 'Alpha'
else:
processor = 'VAX'
if not processor:
# Get processor information from the uname system command
processor = _syscmd_uname('-p', '')
#If any unknowns still exist, replace them with ''s, which are more portable
if system == 'unknown':
system = ''
if node == 'unknown':
node = ''
if release == 'unknown':
release = ''
if version == 'unknown':
version = ''
if machine == 'unknown':
machine = ''
if processor == 'unknown':
processor = ''
# normalize name # normalize name
if system == 'Microsoft' and release == 'Windows': if system == 'Microsoft' and release == 'Windows':
system = 'Windows' system = 'Windows'
release = 'Vista' release = 'Vista'
_uname_cache = uname_result(system, node, release, version, vals = system, node, release, version, machine
machine, processor) # Replace 'unknown' values with the more portable ''
_uname_cache = uname_result(*map(_unknown_as_blank, vals))
return _uname_cache return _uname_cache
### Direct interfaces to some of the uname() return values ### Direct interfaces to some of the uname() return values

View file

@ -4,7 +4,6 @@ import subprocess
import sys import sys
import unittest import unittest
import collections import collections
import contextlib
from unittest import mock from unittest import mock
from test import support from test import support
@ -168,12 +167,8 @@ class PlatformTest(unittest.TestCase):
On some systems, the processor must match the output On some systems, the processor must match the output
of 'uname -p'. See Issue 35967 for rationale. of 'uname -p'. See Issue 35967 for rationale.
""" """
with contextlib.suppress(subprocess.CalledProcessError): proc_res = subprocess.check_output(['uname', '-p'], text=True).strip()
expect = subprocess.check_output(['uname', '-p'], text=True).strip() expect = platform._unknown_as_blank(proc_res)
if expect == 'unknown':
expect = ''
self.assertEqual(platform.uname().processor, expect) self.assertEqual(platform.uname().processor, expect)
@unittest.skipUnless(sys.platform.startswith('win'), "windows only test") @unittest.skipUnless(sys.platform.startswith('win'), "windows only test")

View file

@ -0,0 +1 @@
In platform, delay the invocation of 'uname -p' until the processor attribute is requested.