| import contextlib |
| import itertools |
| import os |
| import re |
| import shutil |
| import subprocess |
| import sys |
| import sysconfig |
| import tempfile |
| import unittest |
| from pathlib import Path |
| from test import support |
| |
| if sys.platform != "win32": |
| raise unittest.SkipTest("test only applies to Windows") |
| |
| # Get winreg after the platform check |
| import winreg |
| |
| |
| PY_EXE = "py.exe" |
| if sys.executable.casefold().endswith("_d.exe".casefold()): |
| PY_EXE = "py_d.exe" |
| |
| # Registry data to create. On removal, everything beneath top-level names will |
| # be deleted. |
| TEST_DATA = { |
| "PythonTestSuite": { |
| "DisplayName": "Python Test Suite", |
| "SupportUrl": "https://www.python.org/", |
| "3.100": { |
| "DisplayName": "X.Y version", |
| "InstallPath": { |
| None: sys.prefix, |
| "ExecutablePath": "X.Y.exe", |
| } |
| }, |
| "3.100-32": { |
| "DisplayName": "X.Y-32 version", |
| "InstallPath": { |
| None: sys.prefix, |
| "ExecutablePath": "X.Y-32.exe", |
| } |
| }, |
| "3.100-arm64": { |
| "DisplayName": "X.Y-arm64 version", |
| "InstallPath": { |
| None: sys.prefix, |
| "ExecutablePath": "X.Y-arm64.exe", |
| "ExecutableArguments": "-X fake_arg_for_test", |
| } |
| }, |
| "ignored": { |
| "DisplayName": "Ignored because no ExecutablePath", |
| "InstallPath": { |
| None: sys.prefix, |
| } |
| }, |
| }, |
| "PythonTestSuite1": { |
| "DisplayName": "Python Test Suite Single", |
| "3.100": { |
| "DisplayName": "Single Interpreter", |
| "InstallPath": { |
| None: sys.prefix, |
| "ExecutablePath": sys.executable, |
| } |
| } |
| }, |
| } |
| |
| |
| TEST_PY_ENV = dict( |
| PY_PYTHON="PythonTestSuite/3.100", |
| PY_PYTHON2="PythonTestSuite/3.100-32", |
| PY_PYTHON3="PythonTestSuite/3.100-arm64", |
| ) |
| |
| |
| TEST_PY_DEFAULTS = "\n".join([ |
| "[defaults]", |
| *[f"{k[3:].lower()}={v}" for k, v in TEST_PY_ENV.items()], |
| ]) |
| |
| |
| TEST_PY_COMMANDS = "\n".join([ |
| "[commands]", |
| "test-command=TEST_EXE.exe", |
| ]) |
| |
| def create_registry_data(root, data): |
| def _create_registry_data(root, key, value): |
| if isinstance(value, dict): |
| # For a dict, we recursively create keys |
| with winreg.CreateKeyEx(root, key) as hkey: |
| for k, v in value.items(): |
| _create_registry_data(hkey, k, v) |
| elif isinstance(value, str): |
| # For strings, we set values. 'key' may be None in this case |
| winreg.SetValueEx(root, key, None, winreg.REG_SZ, value) |
| else: |
| raise TypeError("don't know how to create data for '{}'".format(value)) |
| |
| for k, v in data.items(): |
| _create_registry_data(root, k, v) |
| |
| |
| def enum_keys(root): |
| for i in itertools.count(): |
| try: |
| yield winreg.EnumKey(root, i) |
| except OSError as ex: |
| if ex.winerror == 259: |
| break |
| raise |
| |
| |
| def delete_registry_data(root, keys): |
| ACCESS = winreg.KEY_WRITE | winreg.KEY_ENUMERATE_SUB_KEYS |
| for key in list(keys): |
| with winreg.OpenKey(root, key, access=ACCESS) as hkey: |
| delete_registry_data(hkey, enum_keys(hkey)) |
| winreg.DeleteKey(root, key) |
| |
| |
| def is_installed(tag): |
| key = rf"Software\Python\PythonCore\{tag}\InstallPath" |
| for root, flag in [ |
| (winreg.HKEY_CURRENT_USER, 0), |
| (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_64KEY), |
| (winreg.HKEY_LOCAL_MACHINE, winreg.KEY_WOW64_32KEY), |
| ]: |
| try: |
| winreg.CloseKey(winreg.OpenKey(root, key, access=winreg.KEY_READ | flag)) |
| return True |
| except OSError: |
| pass |
| return False |
| |
| |
| class PreservePyIni: |
| def __init__(self, path, content): |
| self.path = Path(path) |
| self.content = content |
| self._preserved = None |
| |
| def __enter__(self): |
| try: |
| self._preserved = self.path.read_bytes() |
| except FileNotFoundError: |
| self._preserved = None |
| self.path.write_text(self.content, encoding="utf-16") |
| |
| def __exit__(self, *exc_info): |
| if self._preserved is None: |
| self.path.unlink() |
| else: |
| self.path.write_bytes(self._preserved) |
| |
| |
| class RunPyMixin: |
| py_exe = None |
| |
| @classmethod |
| def find_py(cls): |
| py_exe = None |
| if sysconfig.is_python_build(): |
| py_exe = Path(sys.executable).parent / PY_EXE |
| else: |
| for p in os.getenv("PATH").split(";"): |
| if p: |
| py_exe = Path(p) / PY_EXE |
| if py_exe.is_file(): |
| break |
| else: |
| py_exe = None |
| |
| # Test launch and check version, to exclude installs of older |
| # releases when running outside of a source tree |
| if py_exe: |
| try: |
| with subprocess.Popen( |
| [py_exe, "-h"], |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| encoding="ascii", |
| errors="ignore", |
| ) as p: |
| p.stdin.close() |
| version = next(p.stdout, "\n").splitlines()[0].rpartition(" ")[2] |
| p.stdout.read() |
| p.wait(10) |
| if not sys.version.startswith(version): |
| py_exe = None |
| except OSError: |
| py_exe = None |
| |
| if not py_exe: |
| raise unittest.SkipTest( |
| "cannot locate '{}' for test".format(PY_EXE) |
| ) |
| return py_exe |
| |
| def get_py_exe(self): |
| if not self.py_exe: |
| self.py_exe = self.find_py() |
| return self.py_exe |
| |
| def run_py(self, args, env=None, allow_fail=False, expect_returncode=0, argv=None): |
| if not self.py_exe: |
| self.py_exe = self.find_py() |
| |
| ignore = {"VIRTUAL_ENV", "PY_PYTHON", "PY_PYTHON2", "PY_PYTHON3"} |
| env = { |
| **{k.upper(): v for k, v in os.environ.items() if k.upper() not in ignore}, |
| "PYLAUNCHER_DEBUG": "1", |
| "PYLAUNCHER_DRYRUN": "1", |
| "PYLAUNCHER_LIMIT_TO_COMPANY": "", |
| **{k.upper(): v for k, v in (env or {}).items()}, |
| } |
| if not argv: |
| argv = [self.py_exe, *args] |
| with subprocess.Popen( |
| argv, |
| env=env, |
| executable=self.py_exe, |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| ) as p: |
| p.stdin.close() |
| p.wait(10) |
| out = p.stdout.read().decode("utf-8", "replace") |
| err = p.stderr.read().decode("ascii", "replace") |
| if p.returncode != expect_returncode and support.verbose and not allow_fail: |
| print("++ COMMAND ++") |
| print([self.py_exe, *args]) |
| print("++ STDOUT ++") |
| print(out) |
| print("++ STDERR ++") |
| print(err) |
| if allow_fail and p.returncode != expect_returncode: |
| raise subprocess.CalledProcessError(p.returncode, [self.py_exe, *args], out, err) |
| else: |
| self.assertEqual(expect_returncode, p.returncode) |
| data = { |
| s.partition(":")[0]: s.partition(":")[2].lstrip() |
| for s in err.splitlines() |
| if not s.startswith("#") and ":" in s |
| } |
| data["stdout"] = out |
| data["stderr"] = err |
| return data |
| |
| def py_ini(self, content): |
| local_appdata = os.environ.get("LOCALAPPDATA") |
| if not local_appdata: |
| raise unittest.SkipTest("LOCALAPPDATA environment variable is " |
| "missing or empty") |
| return PreservePyIni(Path(local_appdata) / "py.ini", content) |
| |
| @contextlib.contextmanager |
| def script(self, content, encoding="utf-8"): |
| file = Path(tempfile.mktemp(dir=os.getcwd()) + ".py") |
| file.write_text(content, encoding=encoding) |
| try: |
| yield file |
| finally: |
| file.unlink() |
| |
| @contextlib.contextmanager |
| def fake_venv(self): |
| venv = Path.cwd() / "Scripts" |
| venv.mkdir(exist_ok=True, parents=True) |
| venv_exe = (venv / Path(sys.executable).name) |
| venv_exe.touch() |
| try: |
| yield venv_exe, {"VIRTUAL_ENV": str(venv.parent)} |
| finally: |
| shutil.rmtree(venv) |
| |
| |
| class TestLauncher(unittest.TestCase, RunPyMixin): |
| @classmethod |
| def setUpClass(cls): |
| with winreg.CreateKey(winreg.HKEY_CURRENT_USER, rf"Software\Python") as key: |
| create_registry_data(key, TEST_DATA) |
| |
| if support.verbose: |
| p = subprocess.check_output("reg query HKCU\\Software\\Python /s") |
| #print(p.decode('mbcs')) |
| |
| |
| @classmethod |
| def tearDownClass(cls): |
| with winreg.OpenKey(winreg.HKEY_CURRENT_USER, rf"Software\Python", access=winreg.KEY_WRITE | winreg.KEY_ENUMERATE_SUB_KEYS) as key: |
| delete_registry_data(key, TEST_DATA) |
| |
| |
| def test_version(self): |
| data = self.run_py(["-0"]) |
| self.assertEqual(self.py_exe, Path(data["argv0"])) |
| self.assertEqual(sys.version.partition(" ")[0], data["version"]) |
| |
| def test_help_option(self): |
| data = self.run_py(["-h"]) |
| self.assertEqual("True", data["SearchInfo.help"]) |
| |
| def test_list_option(self): |
| for opt, v1, v2 in [ |
| ("-0", "True", "False"), |
| ("-0p", "False", "True"), |
| ("--list", "True", "False"), |
| ("--list-paths", "False", "True"), |
| ]: |
| with self.subTest(opt): |
| data = self.run_py([opt]) |
| self.assertEqual(v1, data["SearchInfo.list"]) |
| self.assertEqual(v2, data["SearchInfo.listPaths"]) |
| |
| def test_list(self): |
| data = self.run_py(["--list"]) |
| found = {} |
| expect = {} |
| for line in data["stdout"].splitlines(): |
| m = re.match(r"\s*(.+?)\s+?(\*\s+)?(.+)$", line) |
| if m: |
| found[m.group(1)] = m.group(3) |
| for company in TEST_DATA: |
| company_data = TEST_DATA[company] |
| tags = [t for t in company_data if isinstance(company_data[t], dict)] |
| for tag in tags: |
| arg = f"-V:{company}/{tag}" |
| expect[arg] = company_data[tag]["DisplayName"] |
| expect.pop(f"-V:{company}/ignored", None) |
| |
| actual = {k: v for k, v in found.items() if k in expect} |
| try: |
| self.assertDictEqual(expect, actual) |
| except: |
| if support.verbose: |
| print("*** STDOUT ***") |
| print(data["stdout"]) |
| raise |
| |
| def test_list_paths(self): |
| data = self.run_py(["--list-paths"]) |
| found = {} |
| expect = {} |
| for line in data["stdout"].splitlines(): |
| m = re.match(r"\s*(.+?)\s+?(\*\s+)?(.+)$", line) |
| if m: |
| found[m.group(1)] = m.group(3) |
| for company in TEST_DATA: |
| company_data = TEST_DATA[company] |
| tags = [t for t in company_data if isinstance(company_data[t], dict)] |
| for tag in tags: |
| arg = f"-V:{company}/{tag}" |
| install = company_data[tag]["InstallPath"] |
| try: |
| expect[arg] = install["ExecutablePath"] |
| try: |
| expect[arg] += " " + install["ExecutableArguments"] |
| except KeyError: |
| pass |
| except KeyError: |
| expect[arg] = str(Path(install[None]) / Path(sys.executable).name) |
| |
| expect.pop(f"-V:{company}/ignored", None) |
| |
| actual = {k: v for k, v in found.items() if k in expect} |
| try: |
| self.assertDictEqual(expect, actual) |
| except: |
| if support.verbose: |
| print("*** STDOUT ***") |
| print(data["stdout"]) |
| raise |
| |
| def test_filter_to_company(self): |
| company = "PythonTestSuite" |
| data = self.run_py([f"-V:{company}/"]) |
| self.assertEqual("X.Y.exe", data["LaunchCommand"]) |
| self.assertEqual(company, data["env.company"]) |
| self.assertEqual("3.100", data["env.tag"]) |
| |
| def test_filter_to_company_with_default(self): |
| company = "PythonTestSuite" |
| data = self.run_py([f"-V:{company}/"], env=dict(PY_PYTHON="3.0")) |
| self.assertEqual("X.Y.exe", data["LaunchCommand"]) |
| self.assertEqual(company, data["env.company"]) |
| self.assertEqual("3.100", data["env.tag"]) |
| |
| def test_filter_to_tag(self): |
| company = "PythonTestSuite" |
| data = self.run_py([f"-V:3.100"]) |
| self.assertEqual("X.Y.exe", data["LaunchCommand"]) |
| self.assertEqual(company, data["env.company"]) |
| self.assertEqual("3.100", data["env.tag"]) |
| |
| data = self.run_py([f"-V:3.100-32"]) |
| self.assertEqual("X.Y-32.exe", data["LaunchCommand"]) |
| self.assertEqual(company, data["env.company"]) |
| self.assertEqual("3.100-32", data["env.tag"]) |
| |
| data = self.run_py([f"-V:3.100-arm64"]) |
| self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test", data["LaunchCommand"]) |
| self.assertEqual(company, data["env.company"]) |
| self.assertEqual("3.100-arm64", data["env.tag"]) |
| |
| def test_filter_to_company_and_tag(self): |
| company = "PythonTestSuite" |
| data = self.run_py([f"-V:{company}/3.1"], expect_returncode=103) |
| |
| data = self.run_py([f"-V:{company}/3.100"]) |
| self.assertEqual("X.Y.exe", data["LaunchCommand"]) |
| self.assertEqual(company, data["env.company"]) |
| self.assertEqual("3.100", data["env.tag"]) |
| |
| def test_filter_with_single_install(self): |
| company = "PythonTestSuite1" |
| data = self.run_py( |
| [f"-V:Nonexistent"], |
| env={"PYLAUNCHER_LIMIT_TO_COMPANY": company}, |
| expect_returncode=103, |
| ) |
| |
| def test_search_major_3(self): |
| try: |
| data = self.run_py(["-3"], allow_fail=True) |
| except subprocess.CalledProcessError: |
| raise unittest.SkipTest("requires at least one Python 3.x install") |
| self.assertEqual("PythonCore", data["env.company"]) |
| self.assertTrue(data["env.tag"].startswith("3."), data["env.tag"]) |
| |
| def test_search_major_3_32(self): |
| try: |
| data = self.run_py(["-3-32"], allow_fail=True) |
| except subprocess.CalledProcessError: |
| if not any(is_installed(f"3.{i}-32") for i in range(5, 11)): |
| raise unittest.SkipTest("requires at least one 32-bit Python 3.x install") |
| raise |
| self.assertEqual("PythonCore", data["env.company"]) |
| self.assertTrue(data["env.tag"].startswith("3."), data["env.tag"]) |
| self.assertTrue(data["env.tag"].endswith("-32"), data["env.tag"]) |
| |
| def test_search_major_2(self): |
| try: |
| data = self.run_py(["-2"], allow_fail=True) |
| except subprocess.CalledProcessError: |
| if not is_installed("2.7"): |
| raise unittest.SkipTest("requires at least one Python 2.x install") |
| self.assertEqual("PythonCore", data["env.company"]) |
| self.assertTrue(data["env.tag"].startswith("2."), data["env.tag"]) |
| |
| def test_py_default(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| data = self.run_py(["-arg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual("X.Y.exe -arg", data["stdout"].strip()) |
| |
| def test_py2_default(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| data = self.run_py(["-2", "-arg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-32", data["SearchInfo.tag"]) |
| self.assertEqual("X.Y-32.exe -arg", data["stdout"].strip()) |
| |
| def test_py3_default(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| data = self.run_py(["-3", "-arg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) |
| self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test -arg", data["stdout"].strip()) |
| |
| def test_py_default_env(self): |
| data = self.run_py(["-arg"], env=TEST_PY_ENV) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual("X.Y.exe -arg", data["stdout"].strip()) |
| |
| def test_py2_default_env(self): |
| data = self.run_py(["-2", "-arg"], env=TEST_PY_ENV) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-32", data["SearchInfo.tag"]) |
| self.assertEqual("X.Y-32.exe -arg", data["stdout"].strip()) |
| |
| def test_py3_default_env(self): |
| data = self.run_py(["-3", "-arg"], env=TEST_PY_ENV) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) |
| self.assertEqual("X.Y-arm64.exe -X fake_arg_for_test -arg", data["stdout"].strip()) |
| |
| def test_py_default_short_argv0(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| for argv0 in ['"py.exe"', 'py.exe', '"py"', 'py']: |
| with self.subTest(argv0): |
| data = self.run_py(["--version"], argv=f'{argv0} --version') |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual(f'X.Y.exe --version', data["stdout"].strip()) |
| |
| def test_py_default_in_list(self): |
| data = self.run_py(["-0"], env=TEST_PY_ENV) |
| default = None |
| for line in data["stdout"].splitlines(): |
| m = re.match(r"\s*-V:(.+?)\s+?\*\s+(.+)$", line) |
| if m: |
| default = m.group(1) |
| break |
| self.assertEqual("PythonTestSuite/3.100", default) |
| |
| def test_virtualenv_in_list(self): |
| with self.fake_venv() as (venv_exe, env): |
| data = self.run_py(["-0p"], env=env) |
| for line in data["stdout"].splitlines(): |
| m = re.match(r"\s*\*\s+(.+)$", line) |
| if m: |
| self.assertEqual(str(venv_exe), m.group(1)) |
| break |
| else: |
| self.fail("did not find active venv path") |
| |
| data = self.run_py(["-0"], env=env) |
| for line in data["stdout"].splitlines(): |
| m = re.match(r"\s*\*\s+(.+)$", line) |
| if m: |
| self.assertEqual("Active venv", m.group(1)) |
| break |
| else: |
| self.fail("did not find active venv entry") |
| |
| def test_virtualenv_with_env(self): |
| with self.fake_venv() as (venv_exe, env): |
| data1 = self.run_py([], env={**env, "PY_PYTHON": "PythonTestSuite/3"}) |
| data2 = self.run_py(["-V:PythonTestSuite/3"], env={**env, "PY_PYTHON": "PythonTestSuite/3"}) |
| # Compare stdout, because stderr goes via ascii |
| self.assertEqual(data1["stdout"].strip(), str(venv_exe)) |
| self.assertEqual(data1["SearchInfo.lowPriorityTag"], "True") |
| # Ensure passing the argument doesn't trigger the same behaviour |
| self.assertNotEqual(data2["stdout"].strip(), str(venv_exe)) |
| self.assertNotEqual(data2["SearchInfo.lowPriorityTag"], "True") |
| |
| def test_py_shebang(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python -prearg") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y.exe -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_python_shebang(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! python -prearg") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y.exe -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_py2_shebang(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python2 -prearg") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-32", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y-32.exe -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_py3_shebang(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python3 -prearg") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_py_shebang_nl(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python -prearg\n") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y.exe -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_py2_shebang_nl(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python2 -prearg\n") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-32", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y-32.exe -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_py3_shebang_nl(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python3 -prearg\n") as script: |
| data = self.run_py([script, "-postarg"]) |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100-arm64", data["SearchInfo.tag"]) |
| self.assertEqual(f"X.Y-arm64.exe -X fake_arg_for_test -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_py_shebang_short_argv0(self): |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script("#! /usr/bin/python -prearg") as script: |
| # Override argv to only pass "py.exe" as the command |
| data = self.run_py([script, "-postarg"], argv=f'"py.exe" "{script}" -postarg') |
| self.assertEqual("PythonTestSuite", data["SearchInfo.company"]) |
| self.assertEqual("3.100", data["SearchInfo.tag"]) |
| self.assertEqual(f'X.Y.exe -prearg "{script}" -postarg', data["stdout"].strip()) |
| |
| def test_py_handle_64_in_ini(self): |
| with self.py_ini("\n".join(["[defaults]", "python=3.999-64"])): |
| # Expect this to fail, but should get oldStyleTag flipped on |
| data = self.run_py([], allow_fail=True, expect_returncode=103) |
| self.assertEqual("3.999-64", data["SearchInfo.tag"]) |
| self.assertEqual("True", data["SearchInfo.oldStyleTag"]) |
| |
| def test_search_path(self): |
| stem = Path(sys.executable).stem |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script(f"#! /usr/bin/env {stem} -prearg") as script: |
| data = self.run_py( |
| [script, "-postarg"], |
| env={"PATH": f"{Path(sys.executable).parent};{os.getenv('PATH')}"}, |
| ) |
| self.assertEqual(f"{sys.executable} -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_search_path_exe(self): |
| # Leave the .exe on the name to ensure we don't add it a second time |
| name = Path(sys.executable).name |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script(f"#! /usr/bin/env {name} -prearg") as script: |
| data = self.run_py( |
| [script, "-postarg"], |
| env={"PATH": f"{Path(sys.executable).parent};{os.getenv('PATH')}"}, |
| ) |
| self.assertEqual(f"{sys.executable} -prearg {script} -postarg", data["stdout"].strip()) |
| |
| def test_recursive_search_path(self): |
| stem = self.get_py_exe().stem |
| with self.py_ini(TEST_PY_DEFAULTS): |
| with self.script(f"#! /usr/bin/env {stem}") as script: |
| data = self.run_py( |
| [script], |
| env={"PATH": f"{self.get_py_exe().parent};{os.getenv('PATH')}"}, |
| ) |
| # The recursive search is ignored and we get normal "py" behavior |
| self.assertEqual(f"X.Y.exe {script}", data["stdout"].strip()) |
| |
| def test_install(self): |
| data = self.run_py(["-V:3.10"], env={"PYLAUNCHER_ALWAYS_INSTALL": "1"}, expect_returncode=111) |
| cmd = data["stdout"].strip() |
| # If winget is runnable, we should find it. Otherwise, we'll be trying |
| # to open the Store. |
| try: |
| subprocess.check_call(["winget.exe", "--version"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| except FileNotFoundError: |
| self.assertIn("ms-windows-store://", cmd) |
| else: |
| self.assertIn("winget.exe", cmd) |
| # Both command lines include the store ID |
| self.assertIn("9PJPW5LDXLZ5", cmd) |
| |
| def test_literal_shebang_absolute(self): |
| with self.script(f"#! C:/some_random_app -witharg") as script: |
| data = self.run_py([script]) |
| self.assertEqual( |
| f"C:\\some_random_app -witharg {script}", |
| data["stdout"].strip(), |
| ) |
| |
| def test_literal_shebang_relative(self): |
| with self.script(f"#! ..\\some_random_app -witharg") as script: |
| data = self.run_py([script]) |
| self.assertEqual( |
| f"{script.parent.parent}\\some_random_app -witharg {script}", |
| data["stdout"].strip(), |
| ) |
| |
| def test_literal_shebang_quoted(self): |
| with self.script(f'#! "some random app" -witharg') as script: |
| data = self.run_py([script]) |
| self.assertEqual( |
| f'"{script.parent}\\some random app" -witharg {script}', |
| data["stdout"].strip(), |
| ) |
| |
| with self.script(f'#! some" random "app -witharg') as script: |
| data = self.run_py([script]) |
| self.assertEqual( |
| f'"{script.parent}\\some random app" -witharg {script}', |
| data["stdout"].strip(), |
| ) |
| |
| def test_literal_shebang_quoted_escape(self): |
| with self.script(f'#! some\\" random "app -witharg') as script: |
| data = self.run_py([script]) |
| self.assertEqual( |
| f'"{script.parent}\\some\\ random app" -witharg {script}', |
| data["stdout"].strip(), |
| ) |
| |
| def test_literal_shebang_command(self): |
| with self.py_ini(TEST_PY_COMMANDS): |
| with self.script('#! test-command arg1') as script: |
| data = self.run_py([script]) |
| self.assertEqual( |
| f"TEST_EXE.exe arg1 {script}", |
| data["stdout"].strip(), |
| ) |
| |
| def test_literal_shebang_invalid_template(self): |
| with self.script('#! /usr/bin/not-python arg1') as script: |
| data = self.run_py([script]) |
| expect = script.parent / "/usr/bin/not-python" |
| self.assertEqual( |
| f"{expect} arg1 {script}", |
| data["stdout"].strip(), |
| ) |