Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 27 additions & 11 deletions scripts/loopx
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,33 @@ if [[ "$loopx_command" == "quota" \
exec node --no-warnings --experimental-strip-types "$native_scheduler_entry" "$@"
fi

python_bin="${LOOPX_PYTHON:-}"
if [[ -z "$python_bin" && -f "$repo_root/.loopx-python" ]]; then
IFS= read -r python_bin <"$repo_root/.loopx-python"
fi
if [[ -z "$python_bin" ]]; then
python_bin="python3"
fi
if ! command -v "$python_bin" >/dev/null 2>&1; then
echo "loopx runtime error: configured Python executable not found: $python_bin" >&2
echo "Set LOOPX_PYTHON to Python 3.11+ and reinstall LoopX." >&2
exit 2
python_selector="$repo_root/scripts/loopx-python.sh"
if [[ -n "${LOOPX_PYTHON:-}" ]]; then
python_bin="$LOOPX_PYTHON"
if ! command -v "$python_bin" >/dev/null 2>&1; then
echo "loopx runtime error: configured Python executable not found: $python_bin" >&2
echo "Set LOOPX_PYTHON to Python 3.11+ and reinstall LoopX." >&2
exit 2
fi
elif [[ -f "$python_selector" ]]; then
if ! python_bin="$(bash "$python_selector")"; then
echo "Set LOOPX_PYTHON to Python 3.11+ and reinstall LoopX." >&2
exit 2
fi
else
# Keep old release snapshots runnable if the companion selector is absent.
python_bin="${LOOPX_PYTHON:-}"
if [[ -z "$python_bin" && -f "$repo_root/.loopx-python" ]]; then
IFS= read -r python_bin <"$repo_root/.loopx-python"
fi
if [[ -z "$python_bin" ]]; then
python_bin="python3"
fi
if ! command -v "$python_bin" >/dev/null 2>&1; then
echo "loopx runtime error: configured Python executable not found: $python_bin" >&2
echo "Set LOOPX_PYTHON to Python 3.11+ and reinstall LoopX." >&2
exit 2
fi
fi

if [[ "$loopx_command" == "canary" && "$loopx_subcommand" == "premerge" ]]; then
Expand Down
116 changes: 116 additions & 0 deletions tests/test_loopx_launcher_python_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from __future__ import annotations

import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys


REPO_ROOT = Path(__file__).resolve().parents[1]


def _write_stub(path: Path, body: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(f"#!/usr/bin/env bash\n{body}\n", encoding="utf-8")
path.chmod(0o755)


def test_source_launcher_uses_project_venv_without_path_activation(tmp_path: Path) -> None:
release_root = tmp_path / "release"
scripts = release_root / "scripts"
scripts.mkdir(parents=True)
package = release_root / "loopx"
package.mkdir()
(package / "cli.py").write_text("print('launcher selected the project interpreter')\n", encoding="utf-8")
shutil.copy2(REPO_ROOT / "scripts/loopx", scripts / "loopx")
shutil.copy2(REPO_ROOT / "scripts/loopx-python.sh", scripts / "loopx-python.sh")

selected = tmp_path / "selected-python.log"
_write_stub(
release_root / ".venv/bin/python",
"printf selected > "
f"{shlex.quote(str(selected))}\n"
f"exec {shlex.quote(sys.executable)} \"$@\"",
)

completed = subprocess.run(
["bash", str(scripts / "loopx"), "version"],
cwd=release_root,
env={
**{key: value for key, value in os.environ.items() if key != "LOOPX_PYTHON"},
"PATH": "/usr/bin:/bin",
},
text=True,
capture_output=True,
check=False,
)

assert completed.returncode == 0, completed.stderr
assert selected.read_text(encoding="utf-8") == "selected"
assert "Python 3.11+ is required" not in completed.stderr


def test_source_launcher_preserves_explicit_missing_python_diagnostic(
tmp_path: Path,
) -> None:
release_root = tmp_path / "release"
scripts = release_root / "scripts"
scripts.mkdir(parents=True)
(release_root / "loopx").mkdir()
shutil.copy2(REPO_ROOT / "scripts/loopx", scripts / "loopx")
shutil.copy2(REPO_ROOT / "scripts/loopx-python.sh", scripts / "loopx-python.sh")

missing = tmp_path / "missing-python"
completed = subprocess.run(
["bash", str(scripts / "loopx"), "version"],
cwd=release_root,
env={
**{key: value for key, value in os.environ.items() if key != "LOOPX_PYTHON"},
"LOOPX_PYTHON": str(missing),
"PATH": "/usr/bin:/bin",
},
text=True,
capture_output=True,
check=False,
)

assert completed.returncode == 2
assert "configured Python executable not found" in completed.stderr
assert str(missing) in completed.stderr


def test_source_launcher_preserves_explicit_old_python_diagnostic(
tmp_path: Path,
) -> None:
release_root = tmp_path / "release"
scripts = release_root / "scripts"
scripts.mkdir(parents=True)
(release_root / "loopx").mkdir()
shutil.copy2(REPO_ROOT / "scripts/loopx", scripts / "loopx")
shutil.copy2(REPO_ROOT / "scripts/loopx-python.sh", scripts / "loopx-python.sh")

legacy = tmp_path / "python-legacy"
_write_stub(
legacy,
"if [[ \"$1\" == \"-c\" ]]; then\n"
" printf '%s\\n' 'loopx runtime error: Python 3.11+ is required; selected Python is 3.9. Set LOOPX_PYTHON to Python 3.11+ and reinstall LoopX.' >&2\n"
"fi\n"
"exit 2",
)
clean_env = {
key: value for key, value in os.environ.items() if key != "LOOPX_PYTHON"
}

completed = subprocess.run(
["bash", str(scripts / "loopx"), "version"],
cwd=release_root,
env={**clean_env, "LOOPX_PYTHON": str(legacy), "PATH": "/usr/bin:/bin"},
text=True,
capture_output=True,
check=False,
)

assert completed.returncode == 2
assert "selected Python is 3.9" in completed.stderr