diff options
author | Steve Dower <steve.dower@python.org> | 2019-06-29 17:34:11 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-29 17:34:11 (GMT) |
commit | 9048c49322a5229ff99610aba35913ffa295ebb7 (patch) | |
tree | caad6f4a3b44e547208ac70cc1746c4df349ac8f /Lib/test/support | |
parent | 80097e089ba22a42d804e65fbbcf35e5e49eed00 (diff) | |
download | cpython-9048c49322a5229ff99610aba35913ffa295ebb7.zip cpython-9048c49322a5229ff99610aba35913ffa295ebb7.tar.gz cpython-9048c49322a5229ff99610aba35913ffa295ebb7.tar.bz2 |
bpo-37369: Fix initialization of sys members when launched via an app container (GH-14428)
sys._base_executable is now always defined on all platforms, and can be overridden through configuration.
Also adds test.support.PythonSymlink to encapsulate platform-specific logic for symlinking sys.executable
Diffstat (limited to 'Lib/test/support')
-rw-r--r-- | Lib/test/support/__init__.py | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index b5538d2..19ea976 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -12,6 +12,7 @@ import faulthandler import fnmatch import functools import gc +import glob import importlib import importlib.util import io @@ -2500,6 +2501,84 @@ def skip_unless_symlink(test): msg = "Requires functional symlink implementation" return test if ok else unittest.skip(msg)(test) +class PythonSymlink: + """Creates a symlink for the current Python executable""" + def __init__(self, link=None): + self.link = link or os.path.abspath(TESTFN) + self._linked = [] + self.real = os.path.realpath(sys.executable) + self._also_link = [] + + self._env = None + + self._platform_specific() + + def _platform_specific(self): + pass + + if sys.platform == "win32": + def _platform_specific(self): + import _winapi + + if os.path.lexists(self.real) and not os.path.exists(self.real): + # App symlink appears to not exist, but we want the + # real executable here anyway + self.real = _winapi.GetModuleFileName(0) + + dll = _winapi.GetModuleFileName(sys.dllhandle) + src_dir = os.path.dirname(dll) + dest_dir = os.path.dirname(self.link) + self._also_link.append(( + dll, + os.path.join(dest_dir, os.path.basename(dll)) + )) + for runtime in glob.glob(os.path.join(src_dir, "vcruntime*.dll")): + self._also_link.append(( + runtime, + os.path.join(dest_dir, os.path.basename(runtime)) + )) + + self._env = {k.upper(): os.getenv(k) for k in os.environ} + self._env["PYTHONHOME"] = os.path.dirname(self.real) + if sysconfig.is_python_build(True): + self._env["PYTHONPATH"] = os.path.dirname(os.__file__) + + def __enter__(self): + os.symlink(self.real, self.link) + self._linked.append(self.link) + for real, link in self._also_link: + os.symlink(real, link) + self._linked.append(link) + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + for link in self._linked: + try: + os.remove(link) + except IOError as ex: + if verbose: + print("failed to clean up {}: {}".format(link, ex)) + + def _call(self, python, args, env, returncode): + cmd = [python, *args] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env) + r = p.communicate() + if p.returncode != returncode: + if verbose: + print(repr(r[0])) + print(repr(r[1]), file=sys.stderr) + raise RuntimeError( + 'unexpected return code: {0} (0x{0:08X})'.format(p.returncode)) + return r + + def call_real(self, *args, returncode=0): + return self._call(self.real, args, None, returncode) + + def call_link(self, *args, returncode=0): + return self._call(self.link, args, self._env, returncode) + + _can_xattr = None def can_xattr(): global _can_xattr |