summaryrefslogtreecommitdiffstats
path: root/Lib/test/support
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2019-06-29 17:34:11 (GMT)
committerGitHub <noreply@github.com>2019-06-29 17:34:11 (GMT)
commit9048c49322a5229ff99610aba35913ffa295ebb7 (patch)
treecaad6f4a3b44e547208ac70cc1746c4df349ac8f /Lib/test/support
parent80097e089ba22a42d804e65fbbcf35e5e49eed00 (diff)
downloadcpython-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__.py79
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