mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
Convert RustAnalyzer to use LanguageServerDependencyProviderSinglePath
This commit is contained in:
1 parent
01dc6f8734
commit
6b87ffd758
3 files changed
+154
-148
No files matched your search
@@ -173,7 +173,7 @@ You can change this location by setting the `SERENA_HOME` environment variable t
|
||||
(ls-specific-settings)=
|
||||
### Language Server-Specific Settings
|
||||
|
||||
:::note
|
||||
:::{note}
|
||||
**Advanced Users Only**: The settings described in this section are intended for advanced users who need to fine-tune language server behavior.
|
||||
Most users will not need to adjust these settings.
|
||||
:::
|
||||
@@ -209,7 +209,7 @@ ls_specific_settings:
|
||||
```
|
||||
|
||||
This is supported by all language servers deriving their dependency provider from `LanguageServerDependencyProviderSinglePath`.
|
||||
Currently, this includes the following languages: `clojure`, `cpp`, `php`, `python`, `typescript`.
|
||||
Currently, this includes the following languages: `clojure`, `cpp`, `php`, `python`, `rust`, `typescript`.
|
||||
We will add support for more languages over time.
|
||||
|
||||
#### Go (`gopls`)
|
||||
|
||||
@@ -13,10 +13,9 @@ from typing import cast
|
||||
|
||||
from overrides import override
|
||||
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
|
||||
from solidlsp.ls_config import LanguageServerConfig
|
||||
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -43,136 +42,140 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
|
||||
return SolidLanguageServer._determine_log_level(line)
|
||||
|
||||
@staticmethod
|
||||
def _get_rustup_version() -> str | None:
|
||||
"""Get installed rustup version or None if not found."""
|
||||
try:
|
||||
result = subprocess.run(["rustup", "--version"], capture_output=True, text=True, check=False)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
class DependencyProvider(LanguageServerDependencyProviderSinglePath):
|
||||
@staticmethod
|
||||
def _get_rustup_version() -> str | None:
|
||||
"""Get installed rustup version or None if not found."""
|
||||
try:
|
||||
result = subprocess.run(["rustup", "--version"], capture_output=True, text=True, check=False)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_rust_analyzer_via_rustup() -> str | None:
|
||||
"""Get rust-analyzer path via rustup. Returns None if not found."""
|
||||
try:
|
||||
result = subprocess.run(["rustup", "which", "rust-analyzer"], capture_output=True, text=True, check=False)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
@staticmethod
|
||||
def _get_rust_analyzer_via_rustup() -> str | None:
|
||||
"""Get rust-analyzer path via rustup. Returns None if not found."""
|
||||
try:
|
||||
result = subprocess.run(["rustup", "which", "rust-analyzer"], capture_output=True, text=True, check=False)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _ensure_rust_analyzer_installed() -> str:
|
||||
"""
|
||||
Ensure rust-analyzer is available.
|
||||
@staticmethod
|
||||
def _ensure_rust_analyzer_installed() -> str:
|
||||
"""
|
||||
Ensure rust-analyzer is available.
|
||||
|
||||
Priority order:
|
||||
1. Rustup existing installation (preferred - matches toolchain version)
|
||||
2. Rustup auto-install if rustup is available (ensures correct version)
|
||||
3. Common installation locations as fallback (only if rustup not available)
|
||||
4. System PATH last (can pick up incompatible versions)
|
||||
Priority order:
|
||||
1. Rustup existing installation (preferred - matches toolchain version)
|
||||
2. Rustup auto-install if rustup is available (ensures correct version)
|
||||
3. Common installation locations as fallback (only if rustup not available)
|
||||
4. System PATH last (can pick up incompatible versions)
|
||||
|
||||
:return: path to rust-analyzer executable
|
||||
"""
|
||||
# Try rustup FIRST (preferred - avoids picking up incompatible versions from PATH)
|
||||
rustup_path = RustAnalyzer._get_rust_analyzer_via_rustup()
|
||||
if rustup_path:
|
||||
return rustup_path
|
||||
:return: path to rust-analyzer executable
|
||||
"""
|
||||
# Try rustup FIRST (preferred - avoids picking up incompatible versions from PATH)
|
||||
rustup_path = RustAnalyzer.DependencyProvider._get_rust_analyzer_via_rustup()
|
||||
if rustup_path:
|
||||
return rustup_path
|
||||
|
||||
# If rustup is available but rust-analyzer not installed, auto-install it BEFORE
|
||||
# checking common paths. This ensures we get the correct version matching the toolchain.
|
||||
if RustAnalyzer._get_rustup_version():
|
||||
result = subprocess.run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
# Verify installation worked
|
||||
rustup_path = RustAnalyzer._get_rust_analyzer_via_rustup()
|
||||
if rustup_path:
|
||||
return rustup_path
|
||||
# If auto-install failed, fall through to common paths as last resort
|
||||
# If rustup is available but rust-analyzer not installed, auto-install it BEFORE
|
||||
# checking common paths. This ensures we get the correct version matching the toolchain.
|
||||
if RustAnalyzer.DependencyProvider._get_rustup_version():
|
||||
result = subprocess.run(["rustup", "component", "add", "rust-analyzer"], check=False, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
# Verify installation worked
|
||||
rustup_path = RustAnalyzer.DependencyProvider._get_rust_analyzer_via_rustup()
|
||||
if rustup_path:
|
||||
return rustup_path
|
||||
# If auto-install failed, fall through to common paths as last resort
|
||||
|
||||
# Determine platform-specific binary name and paths
|
||||
is_windows = platform.system() == "Windows"
|
||||
binary_name = "rust-analyzer.exe" if is_windows else "rust-analyzer"
|
||||
# Determine platform-specific binary name and paths
|
||||
is_windows = platform.system() == "Windows"
|
||||
binary_name = "rust-analyzer.exe" if is_windows else "rust-analyzer"
|
||||
|
||||
# Fallback to common installation locations (only used if rustup not available)
|
||||
common_paths: list[str | None] = []
|
||||
# Fallback to common installation locations (only used if rustup not available)
|
||||
common_paths: list[str | None] = []
|
||||
|
||||
if is_windows:
|
||||
# Windows-specific paths
|
||||
home = pathlib.Path.home()
|
||||
common_paths.extend(
|
||||
[
|
||||
str(home / ".cargo" / "bin" / binary_name), # cargo install / rustup
|
||||
str(home / "scoop" / "shims" / binary_name), # Scoop package manager
|
||||
str(home / "scoop" / "apps" / "rust-analyzer" / "current" / binary_name), # Scoop direct
|
||||
str(
|
||||
pathlib.Path(os.environ.get("LOCALAPPDATA", "")) / "Programs" / "rust-analyzer" / binary_name
|
||||
), # Standalone install
|
||||
]
|
||||
)
|
||||
else:
|
||||
# Unix-like paths (macOS, Linux)
|
||||
common_paths.extend(
|
||||
[
|
||||
"/opt/homebrew/bin/rust-analyzer", # macOS Homebrew (Apple Silicon)
|
||||
"/usr/local/bin/rust-analyzer", # macOS Homebrew (Intel) / Linux system
|
||||
os.path.expanduser("~/.cargo/bin/rust-analyzer"), # cargo install
|
||||
os.path.expanduser("~/.local/bin/rust-analyzer"), # User local bin
|
||||
]
|
||||
if is_windows:
|
||||
# Windows-specific paths
|
||||
home = pathlib.Path.home()
|
||||
common_paths.extend(
|
||||
[
|
||||
str(home / ".cargo" / "bin" / binary_name), # cargo install / rustup
|
||||
str(home / "scoop" / "shims" / binary_name), # Scoop package manager
|
||||
str(home / "scoop" / "apps" / "rust-analyzer" / "current" / binary_name), # Scoop direct
|
||||
str(
|
||||
pathlib.Path(os.environ.get("LOCALAPPDATA", "")) / "Programs" / "rust-analyzer" / binary_name
|
||||
), # Standalone install
|
||||
]
|
||||
)
|
||||
else:
|
||||
# Unix-like paths (macOS, Linux)
|
||||
common_paths.extend(
|
||||
[
|
||||
"/opt/homebrew/bin/rust-analyzer", # macOS Homebrew (Apple Silicon)
|
||||
"/usr/local/bin/rust-analyzer", # macOS Homebrew (Intel) / Linux system
|
||||
os.path.expanduser("~/.cargo/bin/rust-analyzer"), # cargo install
|
||||
os.path.expanduser("~/.local/bin/rust-analyzer"), # User local bin
|
||||
]
|
||||
)
|
||||
|
||||
for path in common_paths:
|
||||
if path and os.path.isfile(path) and os.access(path, os.X_OK):
|
||||
return path
|
||||
|
||||
# Last resort: check system PATH (can pick up incorrect aliases, hence checked last)
|
||||
path_result = shutil.which("rust-analyzer")
|
||||
if path_result and os.path.isfile(path_result) and os.access(path_result, os.X_OK):
|
||||
return path_result
|
||||
|
||||
# Provide helpful error message with all searched locations
|
||||
searched = [p for p in common_paths if p]
|
||||
install_instructions = [
|
||||
" - Rustup: rustup component add rust-analyzer",
|
||||
" - Cargo: cargo install rust-analyzer",
|
||||
]
|
||||
if is_windows:
|
||||
install_instructions.extend(
|
||||
[
|
||||
" - Scoop: scoop install rust-analyzer",
|
||||
" - Chocolatey: choco install rust-analyzer",
|
||||
" - Standalone: Download from https://github.com/rust-lang/rust-analyzer/releases",
|
||||
]
|
||||
)
|
||||
else:
|
||||
install_instructions.extend(
|
||||
[
|
||||
" - Homebrew (macOS): brew install rust-analyzer",
|
||||
" - System package manager (Linux): apt/dnf/pacman install rust-analyzer",
|
||||
]
|
||||
)
|
||||
|
||||
raise RuntimeError(
|
||||
"rust-analyzer is not installed or not in PATH.\n"
|
||||
"Searched locations:\n" + "\n".join(f" - {p}" for p in searched) + "\n"
|
||||
"Please install rust-analyzer via:\n" + "\n".join(install_instructions)
|
||||
)
|
||||
|
||||
for path in common_paths:
|
||||
if path and os.path.isfile(path) and os.access(path, os.X_OK):
|
||||
return path
|
||||
def _get_or_install_core_dependency(self) -> str:
|
||||
return self._ensure_rust_analyzer_installed()
|
||||
|
||||
# Last resort: check system PATH (can pick up incorrect aliases, hence checked last)
|
||||
path_result = shutil.which("rust-analyzer")
|
||||
if path_result and os.path.isfile(path_result) and os.access(path_result, os.X_OK):
|
||||
return path_result
|
||||
|
||||
# Provide helpful error message with all searched locations
|
||||
searched = [p for p in common_paths if p]
|
||||
install_instructions = [
|
||||
" - Rustup: rustup component add rust-analyzer",
|
||||
" - Cargo: cargo install rust-analyzer",
|
||||
]
|
||||
if is_windows:
|
||||
install_instructions.extend(
|
||||
[
|
||||
" - Scoop: scoop install rust-analyzer",
|
||||
" - Chocolatey: choco install rust-analyzer",
|
||||
" - Standalone: Download from https://github.com/rust-lang/rust-analyzer/releases",
|
||||
]
|
||||
)
|
||||
else:
|
||||
install_instructions.extend(
|
||||
[
|
||||
" - Homebrew (macOS): brew install rust-analyzer",
|
||||
" - System package manager (Linux): apt/dnf/pacman install rust-analyzer",
|
||||
]
|
||||
)
|
||||
|
||||
raise RuntimeError(
|
||||
"rust-analyzer is not installed or not in PATH.\n"
|
||||
"Searched locations:\n" + "\n".join(f" - {p}" for p in searched) + "\n"
|
||||
"Please install rust-analyzer via:\n" + "\n".join(install_instructions)
|
||||
)
|
||||
def _create_launch_command(self, core_path: str) -> list[str] | str:
|
||||
return [core_path]
|
||||
|
||||
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
|
||||
"""
|
||||
Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
rustanalyzer_executable_path = self._ensure_rust_analyzer_installed()
|
||||
log.info(f"Using rust-analyzer at: {rustanalyzer_executable_path}")
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=rustanalyzer_executable_path, cwd=repository_root_path),
|
||||
None,
|
||||
"rust",
|
||||
solidlsp_settings,
|
||||
)
|
||||
@@ -181,6 +184,9 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
|
||||
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["target"]
|
||||
|
||||
@@ -48,7 +48,7 @@ class TestRustAnalyzerDetection:
|
||||
from solidlsp.language_servers.rust_analyzer import RustAnalyzer
|
||||
|
||||
# Mock rustup to be unavailable
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
# Mock common locations to NOT exist
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
# Mock PATH to have rust-analyzer
|
||||
@@ -60,7 +60,7 @@ class TestRustAnalyzerDetection:
|
||||
return path == "/custom/bin/rust-analyzer"
|
||||
|
||||
with patch("os.path.isfile", side_effect=selective_isfile):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == "/custom/bin/rust-analyzer"
|
||||
mock_which.assert_called_with("rust-analyzer")
|
||||
@@ -86,11 +86,11 @@ class TestRustAnalyzerDetection:
|
||||
def mock_access(path, mode):
|
||||
return path == "/opt/homebrew/bin/rust-analyzer"
|
||||
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", side_effect=mock_isfile):
|
||||
with patch("os.access", side_effect=mock_access):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == "/opt/homebrew/bin/rust-analyzer"
|
||||
|
||||
@@ -115,11 +115,11 @@ class TestRustAnalyzerDetection:
|
||||
def mock_access(path, mode):
|
||||
return path == "/usr/local/bin/rust-analyzer"
|
||||
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", side_effect=mock_isfile):
|
||||
with patch("os.access", side_effect=mock_access):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == "/usr/local/bin/rust-analyzer"
|
||||
|
||||
@@ -146,11 +146,11 @@ class TestRustAnalyzerDetection:
|
||||
def mock_access(path, mode):
|
||||
return path == cargo_path
|
||||
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", side_effect=mock_isfile):
|
||||
with patch("os.access", side_effect=mock_access):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == cargo_path
|
||||
|
||||
@@ -169,11 +169,11 @@ class TestRustAnalyzerDetection:
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
with patch.object(
|
||||
RustAnalyzer,
|
||||
RustAnalyzer.DependencyProvider,
|
||||
"_get_rust_analyzer_via_rustup",
|
||||
return_value="/home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rust-analyzer",
|
||||
):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert "rustup" in result or ".rustup" in result
|
||||
|
||||
@@ -193,10 +193,10 @@ class TestRustAnalyzerDetection:
|
||||
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer, "_get_rustup_version", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None):
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
error_message = str(exc_info.value)
|
||||
# Error should list the locations that were searched (Unix paths)
|
||||
@@ -222,11 +222,11 @@ class TestRustAnalyzerDetection:
|
||||
rustup_path = "/home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/rust-analyzer"
|
||||
|
||||
# Rustup has rust-analyzer, PATH also has it, common locations also exist
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=rustup_path):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=rustup_path):
|
||||
with patch("shutil.which", return_value="/custom/path/rust-analyzer"):
|
||||
with patch("os.path.isfile", return_value=True):
|
||||
with patch("os.access", return_value=True):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
# Should use rustup version, NOT PATH or common locations
|
||||
assert result == rustup_path
|
||||
@@ -260,11 +260,11 @@ class TestRustAnalyzerDetection:
|
||||
def mock_isfile_for_cargo(path):
|
||||
return path in ["/opt/homebrew/bin/rust-analyzer", os.path.expanduser("~/.cargo/bin/rust-analyzer")]
|
||||
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", side_effect=mock_isfile_for_cargo):
|
||||
with patch("os.access", side_effect=mock_access):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
# Should skip non-executable Homebrew and use cargo
|
||||
assert result == os.path.expanduser("~/.cargo/bin/rust-analyzer")
|
||||
@@ -292,12 +292,12 @@ class TestRustAnalyzerDetection:
|
||||
def mock_access(path, mode):
|
||||
return path == scoop_path
|
||||
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch("platform.system", return_value="Windows"):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", side_effect=mock_isfile):
|
||||
with patch("os.access", side_effect=mock_access):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == scoop_path
|
||||
|
||||
@@ -324,12 +324,12 @@ class TestRustAnalyzerDetection:
|
||||
def mock_access(path, mode):
|
||||
return path == cargo_path
|
||||
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch("platform.system", return_value="Windows"):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", side_effect=mock_isfile):
|
||||
with patch("os.access", side_effect=mock_access):
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == cargo_path
|
||||
|
||||
@@ -349,10 +349,10 @@ class TestRustAnalyzerDetection:
|
||||
with patch("platform.system", return_value="Windows"):
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer, "_get_rustup_version", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value=None):
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
error_message = str(exc_info.value)
|
||||
# Error should suggest Windows-specific package managers
|
||||
@@ -377,13 +377,13 @@ class TestRustAnalyzerDetection:
|
||||
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup") as mock_rustup_path:
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup") as mock_rustup_path:
|
||||
# First call returns None (not installed), second returns path (after install)
|
||||
mock_rustup_path.side_effect = [None, "/home/user/.rustup/toolchains/stable/bin/rust-analyzer"]
|
||||
with patch.object(RustAnalyzer, "_get_rustup_version", return_value="1.70.0"):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value="1.70.0"):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result == "/home/user/.rustup/toolchains/stable/bin/rust-analyzer"
|
||||
mock_run.assert_called_once()
|
||||
@@ -407,14 +407,14 @@ class TestRustAnalyzerDetection:
|
||||
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer, "_get_rustup_version", return_value="1.70.0"):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value="1.70.0"):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(
|
||||
returncode=1, stdout="", stderr="error: component 'rust-analyzer' is not available"
|
||||
)
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
error_message = str(exc_info.value)
|
||||
# Error should provide helpful installation instructions
|
||||
@@ -439,12 +439,12 @@ class TestRustAnalyzerDetection:
|
||||
|
||||
with patch("shutil.which", return_value=None):
|
||||
with patch("os.path.isfile", return_value=False):
|
||||
with patch.object(RustAnalyzer, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer, "_get_rustup_version", return_value="1.70.0"):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rust_analyzer_via_rustup", return_value=None):
|
||||
with patch.object(RustAnalyzer.DependencyProvider, "_get_rustup_version", return_value="1.70.0"):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
error_message = str(exc_info.value)
|
||||
# Error should indicate rust-analyzer is not available and provide install instructions
|
||||
@@ -482,7 +482,7 @@ class TestRustAnalyzerDetectionIntegration:
|
||||
if not any(os.path.isfile(p) and os.access(p, os.X_OK) for p in common_paths):
|
||||
pytest.skip("rust-analyzer not installed on this system")
|
||||
|
||||
result = RustAnalyzer._ensure_rust_analyzer_installed()
|
||||
result = RustAnalyzer.DependencyProvider._ensure_rust_analyzer_installed()
|
||||
|
||||
assert result is not None
|
||||
assert os.path.isfile(result)
|
||||
|
||||
Reference in new issue
Block a user