"""Live tests for the win32 launcher mint (scripts/build/mint_launchers.py). These RUN the real mint on the current interpreter and then execute the minted launcher — the strongest proof the mechanism is intact (the research experiments proved it once; these tests keep proving it on every run): * the minted exe is a real PE with a `#!\\..` shebang, * the placeholder resolves against the launcher's OWN directory (proved by moving the tree before running), * the wrapper puts the payload repo + venv site-packages on sys.path and dispatches to the entry module with argv forwarded and the exit code returned. Runs on Windows with the locked ``test`` dependency group. POSIX payloads use $0-relative bash trampolines instead. """ from __future__ import annotations import json import os import shutil import subprocess import sys from pathlib import Path import pytest _REPO = Path(__file__).resolve().parents[2] _MINT = _REPO / "scripts" / "build" / "mint_launchers.py" pytestmark = [ pytest.mark.platforms("windows"), ] @pytest.fixture() def payload_tree(tmp_path: Path): """A miniature payload: bin/, repo snapshot with a stub hermes_cli, venv site-packages, and a REAL interpreter at the fake store path.""" bin_dir = tmp_path / "stage" / "bin" py_dir = tmp_path / "stage" / "tools" / "py-1" repo = tmp_path / "stage" / "repo" site = tmp_path / "stage" / "venv" / "Lib" / "site-packages" for d in (bin_dir, py_dir, repo / "hermes_cli", repo / "pm", site): d.mkdir(parents=True, exist_ok=True) # A real interpreter at the shebang's target: the launcher will create # the process from \..\tools\py-1\python.exe, so the exe # must actually be able to boot there. Copy from the BASE interpreter — # a venv python.exe is a launcher stub whose DLLs/Lib live beside the # real one. base = Path(sys.base_prefix) for name in os.listdir(base): if name in ("Lib", "Scripts", "share", "include"): continue src = base / name if src.is_dir(): shutil.copytree(src, py_dir / name) else: shutil.copy2(src, py_dir / name) shutil.copytree( base / "Lib", py_dir / "Lib", ignore=shutil.ignore_patterns("__pycache__", "site-packages", "test", "idlelib", "tkinter", "turtledemo", "config-*"), ) # Exercise the real bootstrap before the fixture entry point. for relative in ("hermes_bootstrap.py", "hermes_constants.py", "hermes_cli/__init__.py", "pm/environments.py", "pm/filesystem.py", "hermes_cli/runtime_state.py", "hermes_cli/_early_recovery.py", "hermes_cli/_parser.py", # prepare_launch returns early for a fixture repo (no .git), but the bootstrap # imports these two before it can tell. "hermes_cli/venv_sync.py", "hermes_cli/steward.py"): shutil.copy2(_REPO / relative, repo / relative) (repo / "hermes_cli" / "main.py").write_text( "import os, sys\n" "def main():\n" " import hermes_cli, stubdep\n" " assert sys.argv[0].lower().endswith('hermes.exe'), sys.argv[0]\n" " assert sys.argv[1:] == ['--version'], sys.argv\n" " print('OK', os.environ.get('PYTHONHOME'))\n" " return 7\n", encoding="utf-8", ) (site / "stubdep.py").write_text("X = 1\n", encoding="utf-8") # Real payloads ship this beside repo/ (scripts/build/agent.py); boot selects its venv from it. (tmp_path / "stage" / "manifest.json").write_text( json.dumps({"schema": 1, "repo": "repo", "venv": "venv", "store": "tools"}), encoding="utf-8") return {"bin": bin_dir, "root": tmp_path / "stage", "tmp": tmp_path, "site": site, "repo": repo} def _mint(bin_dir: Path, wrapper: Path, specs) -> list[str]: env = dict( os.environ, HERMES_MINT_BIN_DIR=str(bin_dir), HERMES_MINT_SPECS=json.dumps(specs), HERMES_MINT_WRAPPER=str(wrapper), HERMES_MINT_PYTHON=r"\..\tools\py-1\python.exe", ) proc = subprocess.run([sys.executable, str(_MINT)], capture_output=True, text=True, env=env) assert proc.returncode == 0, proc.stderr return proc.stdout.split() def _render_wrapper(tmp_path: Path) -> Path: from scripts.build.launchers import render_wrapper text = render_wrapper("hermes_cli.main:main", "../repo", "../venv/Lib/site-packages") out = tmp_path / "wrapper.py" out.write_text(text, encoding="utf-8") return out def test_mint_writes_exactly_one_named_exe_per_spec(payload_tree, tmp_path): wrapper = _render_wrapper(tmp_path) out = _mint(payload_tree["bin"], wrapper, [{"name": "hermes", "module": "hermes_cli.main", "func": "main"}]) assert out == ["hermes.exe"] or out == [str(payload_tree["bin"] / "hermes.exe")] assert sorted(p.name for p in payload_tree["bin"].iterdir()) == ["hermes.exe"], "no versioned twin may remain" def test_minted_launcher_shebang_carries_the_launcher_dir_placeholder(payload_tree, tmp_path): wrapper = _render_wrapper(tmp_path) _mint(payload_tree["bin"], wrapper, [{"name": "hermes", "module": "hermes_cli.main", "func": "main"}]) exe = payload_tree["bin"] / "hermes.exe" blob = exe.read_bytes() shebang = blob[blob.rfind(b"#!"):] assert shebang.startswith(b"#!\\..\\tools\\py-1\\python.exe\n"), shebang[:60] def test_minted_launcher_runs_relocated_and_forwards_exit_code(payload_tree, tmp_path): wrapper = _render_wrapper(tmp_path) _mint(payload_tree["bin"], wrapper, [{"name": "hermes", "module": "hermes_cli.main", "func": "main"}]) # RELOCATE the whole tree before running: the launcher must resolve the # interpreter and the payload paths relative to its own dir, wherever # the install lands. moved = payload_tree["tmp"] / "moved-install" shutil.move(str(payload_tree["root"]), str(moved)) exe = moved / "bin" / "hermes.exe" env = {k: v for k, v in os.environ.items() if k not in ("PYTHONPATH", "PYTHONHOME", "PYTHONPYCACHEPREFIX")} env["LOCALAPPDATA"] = str(payload_tree["tmp"] / "lad") proc = subprocess.run([str(exe), "--version"], capture_output=True, text=True, cwd=tmp_path, env=env) assert proc.returncode == 7, proc.stderr[-800:] assert proc.stdout.strip() == "OK None" # PYTHONHOME was dropped # The default pycache prefix kept bytecode out of the (sealed) repo. assert (payload_tree["tmp"] / "lad" / "hermes" / "pycache").exists() assert not (moved / "repo" / "hermes_cli" / "__pycache__").exists() def test_mint_rejects_a_non_launcher_dir_shebang(tmp_path): env = dict( os.environ, HERMES_MINT_BIN_DIR=str(tmp_path), HERMES_MINT_SPECS=json.dumps([{"name": "hermes", "module": "m", "func": "main"}]), HERMES_MINT_PYTHON=r"C:\abs\python.exe", ) wrapper = tmp_path / "w.py" wrapper.write_text("x = 1\n", encoding="utf-8") env["HERMES_MINT_WRAPPER"] = str(wrapper) proc = subprocess.run([sys.executable, str(_MINT)], capture_output=True, text=True, env=env) assert proc.returncode != 0 assert "launcher_dir" in proc.stderr