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 re
import sys
import subprocess
import functools
import itertools
### Globals & Constants
@ -600,22 +603,6 @@ def _follow_symlinks(filepath):
os.path.join(os.path.dirname(filepath), os.readlink(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=''):
@ -736,13 +723,89 @@ def architecture(executable=sys.executable, 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
uname_result = collections.namedtuple("uname_result",
"system node release version machine processor")
class uname_result(
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
def uname():
""" Fairly portable uname interface. Returns a tuple
@ -756,52 +819,30 @@ def uname():
"""
global _uname_cache
no_os_uname = 0
if _uname_cache is not None:
return _uname_cache
processor = ''
# Get some infos from the builtin os.uname API...
try:
system, node, release, version, machine = os.uname()
system, node, release, version, machine = infos = os.uname()
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))):
# Hmm, no there is either no uname or uname has returned
#'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
if not any(infos):
# uname is not available
# Try win32_ver() on win32 platforms
if system == 'win32':
release, version, csd, ptype = win32_ver()
if release and version:
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)
machine = machine or _get_machine_win32()
# Try the 'ver' system command available on some
# platforms
if use_syscmd_ver:
if not (release and version):
system, release, version = _syscmd_ver(system)
# Normalize system to what win32_ver() normally returns
# (_syscmd_ver() tends to return the vendor name as well)
@ -841,42 +882,15 @@ def uname():
if not release or release == '0':
release = 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
if system == 'Microsoft' and release == 'Windows':
system = 'Windows'
release = 'Vista'
_uname_cache = uname_result(system, node, release, version,
machine, processor)
vals = system, node, release, version, machine
# Replace 'unknown' values with the more portable ''
_uname_cache = uname_result(*map(_unknown_as_blank, vals))
return _uname_cache
### Direct interfaces to some of the uname() return values

View File

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