diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bbd1973..5336384 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -172,19 +172,10 @@ jobs: PYTHON_MANAGER_CONFIG: .\test-config.json PYMANAGER_DEBUG: true - - name: 'Validate entrypoint script' + - name: 'Validate entrypoint scripts' run: | $env:PYTHON_MANAGER_CONFIG = (gi $env:PYTHON_MANAGER_CONFIG).FullName - cd .\test_installs\_bin - del pip* -Verbose - pymanager install --refresh - dir pip* - Get-Item .\pip.exe - Get-Item .\pip.exe.__target__ - Get-Content .\pip.exe.__target__ - Get-Item .\pip.exe.__script__.py - Get-Content .\pip.exe.__script__.py - .\pip.exe --version + pymanager exec tests\run-eptest.py env: PYTHON_MANAGER_INCLUDE_UNMANAGED: false PYTHON_MANAGER_CONFIG: .\test-config.json diff --git a/ci/release.yml b/ci/release.yml index 199e0e3..81e5a46 100644 --- a/ci/release.yml +++ b/ci/release.yml @@ -313,6 +313,16 @@ stages: PYTHON_MANAGER_CONFIG: .\test-config.json PYMANAGER_DEBUG: true + - powershell: | + $env:PYTHON_MANAGER_CONFIG = (gi $env:PYTHON_MANAGER_CONFIG).FullName + pymanager exec tests\run-eptest.py + displayName: 'Validate entrypoint scripts' + timeoutInMinutes: 5 + env: + PYTHON_MANAGER_INCLUDE_UNMANAGED: false + PYTHON_MANAGER_CONFIG: .\test-config.json + PYMANAGER_DEBUG: true + - powershell: | pymanager list --online 3 3-32 3-64 3-arm64 pymanager install --download .\bundle 3 3-32 3-64 3-arm64 diff --git a/src/manage/commands.py b/src/manage/commands.py index f589bf6..e58c1b1 100644 --- a/src/manage/commands.py +++ b/src/manage/commands.py @@ -425,6 +425,8 @@ def __init__(self, args, root=None): LOGGER.warn("Failed to read configuration file from %s", self.config_file) raise + self.config = config + # Top-level arguments get updated manually from the config # (per-command config gets loaded automatically below) diff --git a/src/manage/list_command.py b/src/manage/list_command.py index 1f3b45b..7d11dd0 100644 --- a/src/manage/list_command.py +++ b/src/manage/list_command.py @@ -222,6 +222,11 @@ def list_formats(cmd, installs): LOGGER.print(f"{k:<{max_key_width}} {doc}", always=True) +def list_config(cmd, installs): + "List the current config" + LOGGER.print(json.dumps(cmd.config, default=str), always=True) + + def format_legacy(cmd, installs, paths=False): "List runtimes using the old format" seen_default = False @@ -262,6 +267,7 @@ def format_legacy_paths(cmd, installs): "legacy": format_legacy, "legacy-paths": format_legacy_paths, "formats": list_formats, + "config": list_config, } diff --git a/tests/eptestpackage/eptestpackage.dist-info/entry_points.txt b/tests/eptestpackage/eptestpackage.dist-info/entry_points.txt new file mode 100644 index 0000000..0f04d1b --- /dev/null +++ b/tests/eptestpackage/eptestpackage.dist-info/entry_points.txt @@ -0,0 +1,6 @@ +[console_scripts] +eptest=eptestpackage:main +eptest-refresh=eptestpackage:do_refresh + +[gui_scripts] +eptestw=eptestpackage:mainw diff --git a/tests/eptestpackage/eptestpackage.py b/tests/eptestpackage/eptestpackage.py new file mode 100644 index 0000000..1841d76 --- /dev/null +++ b/tests/eptestpackage/eptestpackage.py @@ -0,0 +1,19 @@ +def main(): + print("eptestpackage:main") + +def mainw(): + print("eptestpackage:mainw") + +def do_refresh(): + import subprocess + with subprocess.Popen( + ["pymanager", "install", "-v", "--refresh"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="ascii", + errors="replace", + ) as p: + out, _ = p.communicate(None) + print(out) + + print("eptestpackage:do_refresh") diff --git a/tests/run-eptest.py b/tests/run-eptest.py new file mode 100644 index 0000000..24a49c1 --- /dev/null +++ b/tests/run-eptest.py @@ -0,0 +1,129 @@ +# This is an integration test, rather than a unit test. +# It should be run in a Python runtime that has been installed. +# The 'pymanager.exe' under test should be first on PATH + +import json +import shutil +import subprocess +import sys + +from pathlib import Path + +EXIT_SETUP_FAILED = 1 +EXIT_ALIAS_NOT_CREATED = 2 +EXIT_ALIAS_INVALID = 3 + +CLEANUP = [] + +def run(*args, **kwargs): + print("##[command]", end="") + print(*args) + with subprocess.Popen( + args, + stdout=kwargs.pop("stdout", subprocess.PIPE), + stderr=kwargs.pop("stderr", subprocess.STDOUT), + encoding=kwargs.pop("encoding", "ascii"), + errors=kwargs.pop("errors", "replace"), + **kwargs, + ) as p: + out, err = p.communicate(None) + if p.returncode: + raise subprocess.CalledProcessError(p.returncode, args, out, err) + return out, err + +def main(): + out, _ = run("pymanager", "list", "-f=json", "-q") + for install in json.loads(out)["versions"]: + if not install.get("unmanaged"): + break + else: + print("##[error]No suitable (managed) runtime found.") + sys.exit(EXIT_SETUP_FAILED) + + print("Using", install["display-name"], "from", install["prefix"], "for test") + + prefix = install["prefix"] + exe = install["executable"] + + site = Path(prefix) / "Lib/site-packages" + if not site.is_dir(): + print("##[error]Selected runtime has no site-packages folder.") + sys.exit(EXIT_SETUP_FAILED) + + eptest_src = Path(__file__).parent / "eptestpackage" + if not eptest_src.is_dir(): + print("##[error]eptestpackage is missing from test script location.") + sys.exit(EXIT_SETUP_FAILED) + + dist_info = site / "eptestpackage-1.0.dist-info" + dist_info.mkdir(parents=True, exist_ok=True) + CLEANUP.append(lambda: shutil.rmtree(dist_info)) + for f in (eptest_src / "eptestpackage.dist-info").glob("*"): + (dist_info / f.name).write_bytes(f.read_bytes()) + (site / "eptestpackage.py").write_bytes((eptest_src / "eptestpackage.py").read_bytes()) + CLEANUP.append((site / "eptestpackage.py").unlink) + + print("Listing 'installed' packages (should include eptestpackage)") + print(*site.glob("*"), sep="\n") + print() + + out, _ = run(exe, "-c", "import eptestpackage; eptestpackage.main()") + if out.strip() != "eptestpackage:main": + print(out) + print("##[error]Failed to import eptestpackage") + sys.exit(EXIT_SETUP_FAILED) + print("Confirmed eptestpackage is importable") + + out, _ = run("pymanager", "list", "-f=config", "-q") + try: + config = json.loads(out) + except json.JSONDecodeError: + print("py list -f=config output:") + print(out) + raise + bin_dir = Path(config["global_dir"]) + print(bin_dir) + + refresh_log, _ = run("pymanager", "install", "--refresh", "-vv") + CLEANUP.append(lambda: run("pymanager", "install", "--refresh")) + + print("Listing global aliases (should include eptest, eptestw, eptest-refresh)") + print(*bin_dir.glob("eptest*"), sep="\n") + + for n in ["eptest.exe", "eptestw.exe", "eptest-refresh.exe"]: + if not (bin_dir / n).is_file(): + print("--refresh log follows") + print(refresh_log) + print("##[error]Did not create", n) + sys.exit(EXIT_ALIAS_NOT_CREATED) + + out, _ = run(bin_dir / "eptest.exe") + print(out) + if out.strip() != "eptestpackage:main": + print("##[error]eptest.exe alias failed") + sys.exit(EXIT_ALIAS_INVALID) + + out, _ = run(bin_dir / "eptestw.exe") + print(out) + if out.strip() != "eptestpackage:mainw": + print("##[error]eptestw.exe alias failed") + sys.exit(EXIT_ALIAS_INVALID) + + out, _ = run(bin_dir / "eptest-refresh.exe") + print(out) + if not out.strip().endswith("eptestpackage:do_refresh"): + print("##[error]eptest-refresh.exe alias failed") + sys.exit(EXIT_ALIAS_INVALID) + + +try: + main() +finally: + print("Beginning cleanup") + while CLEANUP: + try: + CLEANUP.pop()() + except subprocess.CalledProcessError as ex: + print("Subprocess failed during cleanup:") + print(ex.args, ex.returncode) + print(ex.output)