Pyright selected the Hermes interpreter ahead of an explicit or local project environment. Keep PM as the fallback rather than hiding the project's dependencies. The regression drives the real server registry and spawn options with disposable venvs, then executes each selected interpreter. Correct the neighboring Windows pip fixture to patch the installer seam it uses. No language-server subprocess or user project was changed.
112 lines
4.2 KiB
Python
112 lines
4.2 KiB
Python
"""Pyright uses the project environment before the Hermes runtime fallback."""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from agent.lsp.servers import _detect_python, _pm_store_python
|
|
|
|
|
|
def _seed_pm_python(tmp_path, monkeypatch, entry="python-3.11.13"):
|
|
"""Stage a pm bundled-install layout: HERMES_RUNTIME_DIR -> store with a
|
|
manifest sibling (bundled), a python entry, and facts recording it."""
|
|
payload = tmp_path / "payload"
|
|
store = payload / "tools"
|
|
python_entry = store / entry
|
|
python_entry.mkdir(parents=True)
|
|
exe = python_entry / ("python.exe" if sys.platform == "win32" else "bin/python3")
|
|
exe.parent.mkdir(parents=True, exist_ok=True)
|
|
exe.write_text("", encoding="utf-8")
|
|
(payload / "manifest.json").write_text("{}", encoding="utf-8")
|
|
(store / "facts.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"schema": 1,
|
|
"packages": {"python": {"entry": entry, "version": "3.11.13"}},
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(store))
|
|
return exe
|
|
|
|
|
|
def test_pm_store_python_resolved_without_virtual_env(tmp_path, monkeypatch):
|
|
"""No VIRTUAL_ENV anywhere — the pm store interpreter is the answer."""
|
|
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
|
exe = _seed_pm_python(tmp_path, monkeypatch)
|
|
|
|
assert _pm_store_python() == str(exe)
|
|
assert _detect_python(str(tmp_path / "some-workspace")) == str(exe)
|
|
|
|
|
|
def test_no_pm_python_falls_back_to_project_layouts(tmp_path, monkeypatch):
|
|
"""Without a pm python fact, project .venv candidates still resolve."""
|
|
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
|
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(tmp_path / "no-such-store"))
|
|
|
|
dot_venv_bin = tmp_path / "proj" / ".venv" / "bin"
|
|
dot_venv_bin.mkdir(parents=True)
|
|
python = dot_venv_bin / "python"
|
|
python.write_text("", encoding="utf-8")
|
|
|
|
assert Path(_detect_python(str(tmp_path / "proj"))) == python
|
|
|
|
|
|
def test_missing_store_entry_is_not_an_answer(tmp_path, monkeypatch):
|
|
"""A python fact whose store entry is gone resolves to None (pm only
|
|
vouches for what is on disk)."""
|
|
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
|
payload = tmp_path / "payload"
|
|
store = payload / "tools"
|
|
store.mkdir(parents=True)
|
|
(payload / "manifest.json").write_text("{}", encoding="utf-8")
|
|
(store / "facts.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"schema": 1,
|
|
"packages": {"python": {"entry": "python-3.11.13", "version": "3.11.13"}},
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
monkeypatch.setenv("HERMES_RUNTIME_DIR", str(store))
|
|
|
|
assert _pm_store_python() is None
|
|
|
|
|
|
def test_pyright_uses_the_project_interpreter_before_hermes(tmp_path, monkeypatch):
|
|
from agent.lsp import servers
|
|
|
|
project = tmp_path / "project"
|
|
project.mkdir()
|
|
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
|
|
monkeypatch.setattr(servers, "_pm_store_python", lambda: sys.executable)
|
|
context = servers.ServerContext(
|
|
workspace_root=str(project), install_strategy="off",
|
|
binary_overrides={"pyright": [sys.executable]},
|
|
)
|
|
server = servers.find_server_for_file(str(project / "app.py"))
|
|
assert server is not None
|
|
spec = server.build_spawn(str(project), context)
|
|
assert spec.initialization_options["python"]["pythonPath"] == sys.executable
|
|
|
|
for environment in (project / ".venv", tmp_path / "explicit-environment"):
|
|
subprocess.run(
|
|
[sys.executable, "-m", "venv", "--without-pip", str(environment)],
|
|
check=True, capture_output=True, text=True, timeout=30,
|
|
)
|
|
python = environment / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
|
if environment.name == "explicit-environment":
|
|
monkeypatch.setenv("VIRTUAL_ENV", str(environment))
|
|
spec = server.build_spawn(str(project), context)
|
|
selected = spec.initialization_options["python"]["pythonPath"]
|
|
assert Path(selected) == python
|
|
child = subprocess.run(
|
|
[selected, "-I", "-c", "import sys; print(sys.prefix)"],
|
|
check=True, capture_output=True, text=True, timeout=10,
|
|
)
|
|
assert Path(child.stdout.strip()) == environment
|