diff --git a/Lib/sysconfig.py b/Lib/sysconfig.py index 9b641f50a9..daf9f00006 100644 --- a/Lib/sysconfig.py +++ b/Lib/sysconfig.py @@ -18,12 +18,17 @@ 'parse_config_h', ] +# Keys for get_config_var() that are never converted to Python integers. +_ALWAYS_STR = { + 'MACOSX_DEPLOYMENT_TARGET', +} + _INSTALL_SCHEMES = { 'posix_prefix': { - 'stdlib': '{installed_base}/lib/python{py_version_short}', - 'platstdlib': '{platbase}/lib/python{py_version_short}', + 'stdlib': '{installed_base}/{platlibdir}/python{py_version_short}', + 'platstdlib': '{platbase}/{platlibdir}/python{py_version_short}', 'purelib': '{base}/lib/python{py_version_short}/site-packages', - 'platlib': '{platbase}/lib/python{py_version_short}/site-packages', + 'platlib': '{platbase}/{platlibdir}/python{py_version_short}/site-packages', 'include': '{installed_base}/include/python{py_version_short}{abiflags}', 'platinclude': @@ -51,49 +56,73 @@ 'scripts': '{base}/Scripts', 'data': '{base}', }, - 'nt_user': { - 'stdlib': '{userbase}/Python{py_version_nodot}', - 'platstdlib': '{userbase}/Python{py_version_nodot}', - 'purelib': '{userbase}/Python{py_version_nodot}/site-packages', - 'platlib': '{userbase}/Python{py_version_nodot}/site-packages', - 'include': '{userbase}/Python{py_version_nodot}/Include', - 'scripts': '{userbase}/Python{py_version_nodot}/Scripts', - 'data': '{userbase}', - }, - 'posix_user': { - 'stdlib': '{userbase}/lib/python{py_version_short}', - 'platstdlib': '{userbase}/lib/python{py_version_short}', - 'purelib': '{userbase}/lib/python{py_version_short}/site-packages', - 'platlib': '{userbase}/lib/python{py_version_short}/site-packages', - 'include': '{userbase}/include/python{py_version_short}', - 'scripts': '{userbase}/bin', - 'data': '{userbase}', - }, - 'osx_framework_user': { - 'stdlib': '{userbase}/lib/python', - 'platstdlib': '{userbase}/lib/python', - 'purelib': '{userbase}/lib/python/site-packages', - 'platlib': '{userbase}/lib/python/site-packages', - 'include': '{userbase}/include', - 'scripts': '{userbase}/bin', - 'data': '{userbase}', - }, } -# XXX RUSTPYTHON: replace python with rustpython in all these paths -for group in _INSTALL_SCHEMES.values(): - for key in group.keys(): - group[key] = group[key].replace("Python", "RustPython").replace("python", "rustpython") +# NOTE: site.py has copy of this function. +# Sync it when modify this function. +def _getuserbase(): + env_base = os.environ.get("PYTHONUSERBASE", None) + if env_base: + return env_base + + # VxWorks has no home directories + if sys.platform == "vxworks": + return None + + def joinuser(*args): + return os.path.expanduser(os.path.join(*args)) + + if os.name == "nt": + base = os.environ.get("APPDATA") or "~" + return joinuser(base, "Python") + + if sys.platform == "darwin" and sys._framework: + return joinuser("~", "Library", sys._framework, + f"{sys.version_info[0]}.{sys.version_info[1]}") + + return joinuser("~", ".local") + +_HAS_USER_BASE = (_getuserbase() is not None) + +if _HAS_USER_BASE: + _INSTALL_SCHEMES |= { + # NOTE: When modifying "purelib" scheme, update site._get_path() too. + 'nt_user': { + 'stdlib': '{userbase}/Python{py_version_nodot_plat}', + 'platstdlib': '{userbase}/Python{py_version_nodot_plat}', + 'purelib': '{userbase}/Python{py_version_nodot_plat}/site-packages', + 'platlib': '{userbase}/Python{py_version_nodot_plat}/site-packages', + 'include': '{userbase}/Python{py_version_nodot_plat}/Include', + 'scripts': '{userbase}/Python{py_version_nodot_plat}/Scripts', + 'data': '{userbase}', + }, + 'posix_user': { + 'stdlib': '{userbase}/{platlibdir}/python{py_version_short}', + 'platstdlib': '{userbase}/{platlibdir}/python{py_version_short}', + 'purelib': '{userbase}/lib/python{py_version_short}/site-packages', + 'platlib': '{userbase}/lib/python{py_version_short}/site-packages', + 'include': '{userbase}/include/python{py_version_short}', + 'scripts': '{userbase}/bin', + 'data': '{userbase}', + }, + 'osx_framework_user': { + 'stdlib': '{userbase}/lib/python', + 'platstdlib': '{userbase}/lib/python', + 'purelib': '{userbase}/lib/python/site-packages', + 'platlib': '{userbase}/lib/python/site-packages', + 'include': '{userbase}/include/python{py_version_short}', + 'scripts': '{userbase}/bin', + 'data': '{userbase}', + }, + } _SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include', 'scripts', 'data') - # FIXME don't rely on sys.version here, its format is an implementation detail - # of CPython, use sys.version_info or sys.hexversion _PY_VERSION = sys.version.split()[0] -_PY_VERSION_SHORT = '%d.%d' % sys.version_info[:2] -_PY_VERSION_SHORT_NO_DOT = '%d%d' % sys.version_info[:2] +_PY_VERSION_SHORT = f'{sys.version_info[0]}.{sys.version_info[1]}' +_PY_VERSION_SHORT_NO_DOT = f'{sys.version_info[0]}{sys.version_info[1]}' _PREFIX = os.path.normpath(sys.prefix) _BASE_PREFIX = os.path.normpath(sys.base_prefix) _EXEC_PREFIX = os.path.normpath(sys.exec_prefix) @@ -101,6 +130,12 @@ _CONFIG_VARS = None _USER_BASE = None +# Regexes needed for parsing Makefile (and similar syntaxes, +# like old-style Setup files). +_variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)" +_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)" +_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}" + def _safe_realpath(path): try: @@ -124,15 +159,22 @@ def _safe_realpath(path): _PROJECT_BASE = _safe_realpath(os.environ["_PYTHON_PROJECT_BASE"]) def _is_python_source_dir(d): - for fn in ("Setup.dist", "Setup.local"): + for fn in ("Setup", "Setup.local"): if os.path.isfile(os.path.join(d, "Modules", fn)): return True return False _sys_home = getattr(sys, '_home', None) -if (_sys_home and os.name == 'nt' and - _sys_home.lower().endswith(('\\pcbuild\\win32', '\\pcbuild\\amd64'))): - _sys_home = os.path.dirname(os.path.dirname(_sys_home)) + +if os.name == 'nt': + def _fix_pcbuild(d): + if d and os.path.normcase(d).startswith( + os.path.normcase(os.path.join(_PREFIX, "PCbuild"))): + return _PREFIX + return d + _PROJECT_BASE = _fix_pcbuild(_PROJECT_BASE) + _sys_home = _fix_pcbuild(_sys_home) + def is_python_build(check_home=False): if check_home and _sys_home: return _is_python_source_dir(_sys_home) @@ -142,18 +184,24 @@ def is_python_build(check_home=False): if _PYTHON_BUILD: for scheme in ('posix_prefix', 'posix_home'): - _INSTALL_SCHEMES[scheme]['include'] = '{srcdir}/Include' - _INSTALL_SCHEMES[scheme]['platinclude'] = '{projectbase}/.' + # On POSIX-y platforms, Python will: + # - Build from .h files in 'headers' (which is only added to the + # scheme when building CPython) + # - Install .h files to 'include' + scheme = _INSTALL_SCHEMES[scheme] + scheme['headers'] = scheme['include'] + scheme['include'] = '{srcdir}/Include' + scheme['platinclude'] = '{projectbase}/.' def _subst_vars(s, local_vars): try: return s.format(**local_vars) - except KeyError: + except KeyError as var: try: return s.format(**os.environ) - except KeyError as var: - raise AttributeError('{%s}' % var) + except KeyError: + raise AttributeError(f'{var}') from None def _extend_dict(target_dict, other_dict): target_keys = target_dict.keys() @@ -176,67 +224,62 @@ def _expand_vars(scheme, vars): return res -def _get_default_scheme(): - if os.name == 'posix': - # the default scheme for posix is posix_prefix - return 'posix_prefix' - return os.name - - -def _getuserbase(): - env_base = os.environ.get("PYTHONUSERBASE", None) +def _get_preferred_schemes(): + if os.name == 'nt': + return { + 'prefix': 'nt', + 'home': 'posix_home', + 'user': 'nt_user', + } + if sys.platform == 'darwin' and sys._framework: + return { + 'prefix': 'posix_prefix', + 'home': 'posix_home', + 'user': 'osx_framework_user', + } + return { + 'prefix': 'posix_prefix', + 'home': 'posix_home', + 'user': 'posix_user', + } - def joinuser(*args): - return os.path.expanduser(os.path.join(*args)) - if os.name == "nt": - base = os.environ.get("APPDATA") or "~" - if env_base: - return env_base - else: - return joinuser(base, "Python") +def get_preferred_scheme(key): + scheme = _get_preferred_schemes()[key] + if scheme not in _INSTALL_SCHEMES: + raise ValueError( + f"{key!r} returned {scheme!r}, which is not a valid scheme " + f"on this platform" + ) + return scheme - if sys.platform == "darwin": - framework = get_config_var("PYTHONFRAMEWORK") - if framework: - if env_base: - return env_base - else: - return joinuser("~", "Library", framework, "%d.%d" % - sys.version_info[:2]) - if env_base: - return env_base - else: - return joinuser("~", ".local") +def get_default_scheme(): + return get_preferred_scheme('prefix') -def _parse_makefile(filename, vars=None): +def _parse_makefile(filename, vars=None, keep_unresolved=True): """Parse a Makefile-style file. A dictionary containing name/value pairs is returned. If an optional dictionary is passed in as the second argument, it is used instead of a new dictionary. """ - # Regexes needed for parsing Makefile (and similar syntaxes, - # like old-style Setup files). import re - _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)") - _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)") - _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}") if vars is None: vars = {} done = {} notdone = {} - with open(filename, errors="surrogateescape") as f: + with open(filename, encoding=sys.getfilesystemencoding(), + errors="surrogateescape") as f: lines = f.readlines() for line in lines: if line.startswith('#') or line.strip() == '': continue - m = _variable_rx.match(line) + m = re.match(_variable_rx, line) if m: n, v = m.group(1, 2) v = v.strip() @@ -247,6 +290,9 @@ def _parse_makefile(filename, vars=None): notdone[n] = v else: try: + if n in _ALWAYS_STR: + raise ValueError + v = int(v) except ValueError: # insert literal `$' @@ -266,8 +312,8 @@ def _parse_makefile(filename, vars=None): while len(variables) > 0: for name in tuple(variables): value = notdone[name] - m1 = _findvar1_rx.search(value) - m2 = _findvar2_rx.search(value) + m1 = re.search(_findvar1_rx, value) + m2 = re.search(_findvar2_rx, value) if m1 and m2: m = m1 if m1.start() < m2.start() else m2 else: @@ -305,6 +351,8 @@ def _parse_makefile(filename, vars=None): notdone[name] = value else: try: + if name in _ALWAYS_STR: + raise ValueError value = int(value) except ValueError: done[name] = value.strip() @@ -320,9 +368,12 @@ def _parse_makefile(filename, vars=None): done[name] = value else: + # Adds unresolved variables to the done dict. + # This is disabled when called from distutils.sysconfig + if keep_unresolved: + done[name] = value # bogus variable reference (e.g. "prefix=$/opt/python"); # just drop it since we can't deal - done[name] = value variables.remove(name) # strip spurious spaces @@ -340,21 +391,20 @@ def get_makefile_filename(): if _PYTHON_BUILD: return os.path.join(_sys_home or _PROJECT_BASE, "Makefile") if hasattr(sys, 'abiflags'): - config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags) + config_dir_name = f'config-{_PY_VERSION_SHORT}{sys.abiflags}' else: config_dir_name = 'config' if hasattr(sys.implementation, '_multiarch'): - config_dir_name += '-%s' % sys.implementation._multiarch + config_dir_name += f'-{sys.implementation._multiarch}' return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile') def _get_sysconfigdata_name(): - return os.environ.get('_PYTHON_SYSCONFIGDATA_NAME', - '_sysconfigdata_{abi}_{platform}_{multiarch}'.format( - abi=sys.abiflags, - platform=sys.platform, - multiarch=getattr(sys.implementation, '_multiarch', ''), - )) + multiarch = getattr(sys.implementation, '_multiarch', '') + return os.environ.get( + '_PYTHON_SYSCONFIGDATA_NAME', + f'_sysconfigdata_{sys.abiflags}_{sys.platform}_{multiarch}', + ) def _generate_posix_vars(): @@ -366,19 +416,19 @@ def _generate_posix_vars(): try: _parse_makefile(makefile, vars) except OSError as e: - msg = "invalid Python installation: unable to open %s" % makefile + msg = f"invalid Python installation: unable to open {makefile}" if hasattr(e, "strerror"): - msg = msg + " (%s)" % e.strerror + msg = f"{msg} ({e.strerror})" raise OSError(msg) # load the installed pyconfig.h: config_h = get_config_h_filename() try: - with open(config_h) as f: + with open(config_h, encoding="utf-8") as f: parse_config_h(f, vars) except OSError as e: - msg = "invalid Python installation: unable to open %s" % config_h + msg = f"invalid Python installation: unable to open {config_h}" if hasattr(e, "strerror"): - msg = msg + " (%s)" % e.strerror + msg = f"{msg} ({e.strerror})" raise OSError(msg) # On AIX, there are wrong paths to the linker scripts in the Makefile # -- these paths are relative to the Python source, but when installed @@ -404,7 +454,7 @@ def _generate_posix_vars(): module.build_time_vars = vars sys.modules[name] = module - pybuilddir = 'build/lib.%s-%s' % (get_platform(), _PY_VERSION_SHORT) + pybuilddir = f'build/lib.{get_platform()}-{_PY_VERSION_SHORT}' if hasattr(sys, "gettotalrefcount"): pybuilddir += '-pydebug' os.makedirs(pybuilddir, exist_ok=True) @@ -417,7 +467,7 @@ def _generate_posix_vars(): pprint.pprint(vars, stream=f) # Create file used for sys.path fixup -- see Modules/getpath.c - with open('pybuilddir.txt', 'w', encoding='ascii') as f: + with open('pybuilddir.txt', 'w', encoding='utf8') as f: f.write(pybuilddir) def _init_posix(vars): @@ -431,13 +481,15 @@ def _init_posix(vars): def _init_non_posix(vars): """Initialize the module as appropriate for NT""" # set basic install directories + import _imp vars['LIBDEST'] = get_path('stdlib') vars['BINLIBDEST'] = get_path('platstdlib') vars['INCLUDEPY'] = get_path('include') - vars['EXT_SUFFIX'] = '.pyd' + vars['EXT_SUFFIX'] = _imp.extension_suffixes()[0] vars['EXE'] = '.exe' vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable)) + vars['TZPATH'] = '' # # public APIs @@ -465,6 +517,8 @@ def parse_config_h(fp, vars=None): if m: n, v = m.group(1, 2) try: + if n in _ALWAYS_STR: + raise ValueError v = int(v) except ValueError: pass @@ -498,7 +552,7 @@ def get_path_names(): return _SCHEME_KEYS -def get_paths(scheme=_get_default_scheme(), vars=None, expand=True): +def get_paths(scheme=get_default_scheme(), vars=None, expand=True): """Return a mapping containing an install scheme. ``scheme`` is the install scheme name. If not provided, it will @@ -510,7 +564,7 @@ def get_paths(scheme=_get_default_scheme(), vars=None, expand=True): return _INSTALL_SCHEMES[scheme] -def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True): +def get_path(name, scheme=get_default_scheme(), vars=None, expand=True): """Return a path corresponding to the scheme. ``scheme`` is the install scheme name. @@ -544,11 +598,16 @@ def get_config_vars(*args): _CONFIG_VARS['installed_platbase'] = _BASE_EXEC_PREFIX _CONFIG_VARS['platbase'] = _EXEC_PREFIX _CONFIG_VARS['projectbase'] = _PROJECT_BASE + _CONFIG_VARS['platlibdir'] = sys.platlibdir try: _CONFIG_VARS['abiflags'] = sys.abiflags except AttributeError: # sys.abiflags may not be defined on all platforms. _CONFIG_VARS['abiflags'] = '' + try: + _CONFIG_VARS['py_version_nodot_plat'] = sys.winver.replace('.', '') + except AttributeError: + _CONFIG_VARS['py_version_nodot_plat'] = '' if os.name == 'nt': _init_non_posix(_CONFIG_VARS) @@ -558,10 +617,11 @@ def get_config_vars(*args): SO = _CONFIG_VARS.get('EXT_SUFFIX') if SO is not None: _CONFIG_VARS['SO'] = SO - # Setting 'userbase' is done below the call to the - # init function to enable using 'get_config_var' in - # the init-function. - _CONFIG_VARS['userbase'] = _getuserbase() + if _HAS_USER_BASE: + # Setting 'userbase' is done below the call to the + # init function to enable using 'get_config_var' in + # the init-function. + _CONFIG_VARS['userbase'] = _getuserbase() # Always convert srcdir to an absolute path srcdir = _CONFIG_VARS.get('srcdir', _PROJECT_BASE) @@ -611,39 +671,30 @@ def get_platform(): """Return a string that identifies the current platform. This is used mainly to distinguish platform-specific build directories and - platform-specific built distributions. Typically includes the OS name - and version and the architecture (as supplied by 'os.uname()'), - although the exact information included depends on the OS; eg. for IRIX - the architecture isn't particularly important (IRIX only runs on SGI - hardware), but for Linux the kernel version isn't particularly - important. + platform-specific built distributions. Typically includes the OS name and + version and the architecture (as supplied by 'os.uname()'), although the + exact information included depends on the OS; on Linux, the kernel version + isn't particularly important. Examples of returned values: linux-i586 linux-alpha (?) solaris-2.6-sun4u - irix-5.3 - irix64-6.2 Windows will return one of: win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc) - win-ia64 (64bit Windows on Itanium) win32 (all others - specifically, sys.platform is returned) For other non-POSIX platforms, currently just returns 'sys.platform'. + """ if os.name == 'nt': - # sniff sys.version for architecture. - prefix = " bit (" - i = sys.version.find(prefix) - if i == -1: - return sys.platform - j = sys.version.find(")", i) - look = sys.version[i+len(prefix):j].lower() - if look == 'amd64': + if 'amd64' in sys.version.lower(): return 'win-amd64' - if look == 'itanium': - return 'win-ia64' + if '(arm)' in sys.version.lower(): + return 'win-arm32' + if '(arm64)' in sys.version.lower(): + return 'win-arm64' return sys.platform if os.name != "posix" or not hasattr(os, 'uname'): @@ -657,8 +708,8 @@ def get_platform(): # Try to distinguish various flavours of Unix osname, host, release, version, machine = os.uname() - # Convert the OS name to lowercase, remove '/' characters - # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh") + # Convert the OS name to lowercase, remove '/' characters, and translate + # spaces (for "Power Macintosh") osname = osname.lower().replace('/', '') machine = machine.replace(' ', '_') machine = machine.replace('/', '-') @@ -667,21 +718,20 @@ def get_platform(): # At least on Linux/Intel, 'machine' is the processor -- # i386, etc. # XXX what about Alpha, SPARC, etc? - return "%s-%s" % (osname, machine) + return f"{osname}-{machine}" elif osname[:5] == "sunos": if release[0] >= "5": # SunOS 5 == Solaris 2 osname = "solaris" - release = "%d.%s" % (int(release[0]) - 3, release[2:]) + release = f"{int(release[0]) - 3}.{release[2:]}" # We can't use "platform.architecture()[0]" because a # bootstrap problem. We use a dict to get an error # if some suspicious happens. bitness = {2147483647:"32bit", 9223372036854775807:"64bit"} - machine += ".%s" % bitness[sys.maxsize] + machine += f".{bitness[sys.maxsize]}" # fall through to standard osname-release-machine representation - elif osname[:4] == "irix": # could be "irix64"! - return "%s-%s" % (osname, release) elif osname[:3] == "aix": - return "%s-%s.%s" % (osname, version, release) + from _aix_support import aix_platform + return aix_platform() elif osname[:6] == "cygwin": osname = "cygwin" import re @@ -695,18 +745,44 @@ def get_platform(): get_config_vars(), osname, release, machine) - return "%s-%s-%s" % (osname, release, machine) + return f"{osname}-{release}-{machine}" def get_python_version(): return _PY_VERSION_SHORT +def expand_makefile_vars(s, vars): + """Expand Makefile-style variables -- "${foo}" or "$(foo)" -- in + 'string' according to 'vars' (a dictionary mapping variable names to + values). Variables not present in 'vars' are silently expanded to the + empty string. The variable values in 'vars' should not contain further + variable expansions; if 'vars' is the output of 'parse_makefile()', + you're fine. Returns a variable-expanded version of 's'. + """ + import re + + # This algorithm does multiple expansion, so if vars['foo'] contains + # "${bar}", it will expand ${foo} to ${bar}, and then expand + # ${bar}... and so forth. This is fine as long as 'vars' comes from + # 'parse_makefile()', which takes care of such expansions eagerly, + # according to make's variable expansion semantics. + + while True: + m = re.search(_findvar1_rx, s) or re.search(_findvar2_rx, s) + if m: + (beg, end) = m.span() + s = s[0:beg] + vars.get(m.group(1)) + s[end:] + else: + break + return s + + def _print_dict(title, data): for index, (key, value) in enumerate(sorted(data.items())): if index == 0: - print('%s: ' % (title)) - print('\t%s = "%s"' % (key, value)) + print(f'{title}: ') + print(f'\t{key} = "{value}"') def _main(): @@ -714,9 +790,9 @@ def _main(): if '--generate-posix-vars' in sys.argv: _generate_posix_vars() return - print('Platform: "%s"' % get_platform()) - print('Python version: "%s"' % get_python_version()) - print('Current installation scheme: "%s"' % _get_default_scheme()) + print(f'Platform: "{get_platform()}"') + print(f'Python version: "{get_python_version()}"') + print(f'Current installation scheme: "{get_default_scheme()}"') print() _print_dict('Paths', get_paths()) print() diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 46332c6543..4be199a630 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1,6 +1,6 @@ import builtins import codecs -# import gc +import gc import locale import operator import os @@ -1111,8 +1111,8 @@ class X(Exception): with test.support.captured_stderr() as stderr, \ test.support.swap_attr(sys, 'unraisablehook', sys.__unraisablehook__): - expected = self.write_unraisable_exc( - A.B.X(), "msg", "obj"); + expected = self.write_unraisable_exc( + A.B.X(), "msg", "obj"); report = stderr.getvalue() testName = 'test_original_unraisablehook_exception_qualname' self.assertIn(f"{testName}..A.B.X", report) diff --git a/Lib/test/test_sysconfig.py b/Lib/test/test_sysconfig.py index de3cfae4d5..b5e6c6e63b 100644 --- a/Lib/test/test_sysconfig.py +++ b/Lib/test/test_sysconfig.py @@ -5,19 +5,23 @@ import shutil from copy import copy -from test.support import captured_stdout, PythonSymlink +from test.support import (captured_stdout, PythonSymlink) +from test.support.import_helper import import_module from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink, change_cwd) -from test.support.import_helper import import_module from test.support.warnings_helper import check_warnings import sysconfig from sysconfig import (get_paths, get_platform, get_config_vars, get_path, get_path_names, _INSTALL_SCHEMES, - _get_default_scheme, _expand_vars, - get_scheme_names, get_config_var, _main) + get_default_scheme, get_scheme_names, get_config_var, + _expand_vars, _get_preferred_schemes, _main) import _osx_support + +HAS_USER_BASE = sysconfig._HAS_USER_BASE + + class TestSysConfig(unittest.TestCase): def setUp(self): @@ -90,17 +94,46 @@ def test_get_path_names(self): def test_get_paths(self): scheme = get_paths() - default_scheme = _get_default_scheme() + default_scheme = get_default_scheme() wanted = _expand_vars(default_scheme, None) wanted = sorted(wanted.items()) scheme = sorted(scheme.items()) self.assertEqual(scheme, wanted) def test_get_path(self): - # XXX make real tests here + config_vars = get_config_vars() for scheme in _INSTALL_SCHEMES: for name in _INSTALL_SCHEMES[scheme]: - res = get_path(name, scheme) + expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars) + self.assertEqual( + os.path.normpath(get_path(name, scheme)), + os.path.normpath(expected), + ) + + def test_get_default_scheme(self): + self.assertIn(get_default_scheme(), _INSTALL_SCHEMES) + + def test_get_preferred_schemes(self): + expected_schemes = {'prefix', 'home', 'user'} + + # Windows. + os.name = 'nt' + schemes = _get_preferred_schemes() + self.assertIsInstance(schemes, dict) + self.assertEqual(set(schemes), expected_schemes) + + # Mac and Linux, shared library build. + os.name = 'posix' + schemes = _get_preferred_schemes() + self.assertIsInstance(schemes, dict) + self.assertEqual(set(schemes), expected_schemes) + + # Mac, framework build. + os.name = 'posix' + sys.platform = 'darwin' + sys._framework = True + self.assertIsInstance(schemes, dict) + self.assertEqual(set(schemes), expected_schemes) def test_get_config_vars(self): cvars = get_config_vars() @@ -232,9 +265,10 @@ def test_get_config_h_filename(self): self.assertTrue(os.path.isfile(config_h), config_h) def test_get_scheme_names(self): - wanted = ('nt', 'nt_user', 'osx_framework_user', - 'posix_home', 'posix_prefix', 'posix_user') - self.assertEqual(get_scheme_names(), wanted) + wanted = ['nt', 'posix_home', 'posix_prefix'] + if HAS_USER_BASE: + wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) + self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) @skip_unless_symlink def test_symlink(self): # Issue 7880 @@ -250,7 +284,8 @@ def test_user_similar(self): # Issue #8759: make sure the posix scheme for the users # is similar to the global posix_prefix one base = get_config_var('base') - user = get_config_var('userbase') + if HAS_USER_BASE: + user = get_config_var('userbase') # the global scheme mirrors the distinction between prefix and # exec-prefix but not the user scheme, so we have to adapt the paths # before comparing (issue #9100) @@ -265,8 +300,19 @@ def test_user_similar(self): # before comparing global_path = global_path.replace(sys.base_prefix, sys.prefix) base = base.replace(sys.base_prefix, sys.prefix) - user_path = get_path(name, 'posix_user') - self.assertEqual(user_path, global_path.replace(base, user, 1)) + if HAS_USER_BASE: + user_path = get_path(name, 'posix_user') + expected = global_path.replace(base, user, 1) + # bpo-44860: platlib of posix_user doesn't use sys.platlibdir, + # whereas posix_prefix does. + if name == 'platlib': + # Replace "/lib64/python3.11/site-packages" suffix + # with "/lib/python3.11/site-packages". + py_version_short = sysconfig.get_python_version() + suffix = f'python{py_version_short}/site-packages' + expected = expected.replace(f'/{sys.platlibdir}/{suffix}', + f'/lib/{suffix}') + self.assertEqual(user_path, expected) def test_main(self): # just making sure _main() runs and returns things in the stdout diff --git a/Lib/test/test_syslog.py b/Lib/test/test_syslog.py index f22b5a03a4..b29f21776e 100644 --- a/Lib/test/test_syslog.py +++ b/Lib/test/test_syslog.py @@ -1,42 +1,41 @@ - -from test.support import import_helper -syslog = import_helper.import_module("syslog") #skip if not supported -import unittest - -# XXX(nnorwitz): This test sucks. I don't know of a platform independent way -# to verify that the messages were really logged. -# The only purpose of this test is to verify the code doesn't crash or leak. - -class Test(unittest.TestCase): - - # TODO: RUSTPYTHON - @unittest.expectedFailure - def test_openlog(self): - syslog.openlog('python') - # Issue #6697. - self.assertRaises(UnicodeEncodeError, syslog.openlog, '\uD800') - - def test_syslog(self): - syslog.openlog('python') - syslog.syslog('test message from python test_syslog') - syslog.syslog(syslog.LOG_ERR, 'test error from python test_syslog') - - def test_closelog(self): - syslog.openlog('python') - syslog.closelog() - - def test_setlogmask(self): - syslog.setlogmask(syslog.LOG_DEBUG) - - def test_log_mask(self): - syslog.LOG_MASK(syslog.LOG_INFO) - - def test_log_upto(self): - syslog.LOG_UPTO(syslog.LOG_INFO) - - def test_openlog_noargs(self): - syslog.openlog() - syslog.syslog('test message from python test_syslog') - -if __name__ == "__main__": - unittest.main() +from test.support import import_helper +syslog = import_helper.import_module("syslog") #skip if not supported +import unittest + +# XXX(nnorwitz): This test sucks. I don't know of a platform independent way +# to verify that the messages were really logged. +# The only purpose of this test is to verify the code doesn't crash or leak. + +class Test(unittest.TestCase): + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_openlog(self): + syslog.openlog('python') + # Issue #6697. + self.assertRaises(UnicodeEncodeError, syslog.openlog, '\uD800') + + def test_syslog(self): + syslog.openlog('python') + syslog.syslog('test message from python test_syslog') + syslog.syslog(syslog.LOG_ERR, 'test error from python test_syslog') + + def test_closelog(self): + syslog.openlog('python') + syslog.closelog() + + def test_setlogmask(self): + syslog.setlogmask(syslog.LOG_DEBUG) + + def test_log_mask(self): + syslog.LOG_MASK(syslog.LOG_INFO) + + def test_log_upto(self): + syslog.LOG_UPTO(syslog.LOG_INFO) + + def test_openlog_noargs(self): + syslog.openlog() + syslog.syslog('test message from python test_syslog') + +if __name__ == "__main__": + unittest.main()