mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 00:15:05 +00:00
updated: elixir language server, replaced with expert (#783)
This commit is contained in:
1 parent
fb01cc38f7
commit
2ec96089f6
11 files changed
+210
-200
No files matched your search
@@ -1,12 +1,5 @@
|
||||
import platform
|
||||
|
||||
|
||||
def _test_nextls_available() -> str:
|
||||
"""Test if Next LS is available and return error reason if not."""
|
||||
# Check if we're on Windows (Next LS doesn't support Windows)
|
||||
if platform.system() == "Windows":
|
||||
return "Next LS does not support Windows"
|
||||
|
||||
def _test_expert_available() -> str:
|
||||
"""Test if Expert is available and return error reason if not."""
|
||||
# Try to import and check Elixir availability
|
||||
try:
|
||||
from solidlsp.language_servers.elixir_tools.elixir_tools import ElixirTools
|
||||
@@ -16,13 +9,13 @@ def _test_nextls_available() -> str:
|
||||
if not elixir_version:
|
||||
return "Elixir is not installed or not in PATH"
|
||||
|
||||
return "" # No error, Next LS should be available
|
||||
return "" # No error, Expert should be available
|
||||
|
||||
except ImportError as e:
|
||||
return f"Failed to import ElixirTools: {e}"
|
||||
except Exception as e:
|
||||
return f"Error checking Next LS availability: {e}"
|
||||
return f"Error checking Expert availability: {e}"
|
||||
|
||||
|
||||
NEXTLS_UNAVAILABLE_REASON = _test_nextls_available()
|
||||
NEXTLS_UNAVAILABLE = bool(NEXTLS_UNAVAILABLE_REASON)
|
||||
EXPERT_UNAVAILABLE_REASON = _test_expert_available()
|
||||
EXPERT_UNAVAILABLE = bool(EXPERT_UNAVAILABLE_REASON)
|
||||
@@ -12,10 +12,10 @@ import pytest
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
from . import NEXTLS_UNAVAILABLE, NEXTLS_UNAVAILABLE_REASON
|
||||
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
|
||||
|
||||
# These marks will be applied to all tests in this module
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(NEXTLS_UNAVAILABLE, reason=f"Next LS not available: {NEXTLS_UNAVAILABLE_REASON}")]
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(EXPERT_UNAVAILABLE, reason=f"Next LS not available: {EXPERT_UNAVAILABLE_REASON}")]
|
||||
|
||||
|
||||
class TestElixirBasic:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
@@ -7,23 +8,38 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import start_ls_context
|
||||
|
||||
from . import NEXTLS_UNAVAILABLE, NEXTLS_UNAVAILABLE_REASON
|
||||
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
|
||||
|
||||
# These marks will be applied to all tests in this module
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(NEXTLS_UNAVAILABLE, reason=f"Next LS not available: {NEXTLS_UNAVAILABLE_REASON}")]
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(EXPERT_UNAVAILABLE, reason=f"Expert not available: {EXPERT_UNAVAILABLE_REASON}")]
|
||||
|
||||
# Skip slow tests in CI - they require multiple Expert instances which is too slow
|
||||
IN_CI = bool(os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"))
|
||||
SKIP_SLOW_IN_CI = pytest.mark.skipif(
|
||||
IN_CI,
|
||||
reason="Slow tests skipped in CI - require multiple Expert instances (~60-90s each)",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@pytest.fixture(scope="session")
|
||||
def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
|
||||
"""Fixture to set up an LS for the elixir test repo with the 'scripts' directory ignored."""
|
||||
"""Fixture to set up an LS for the elixir test repo with the 'scripts' directory ignored.
|
||||
|
||||
Uses session scope to avoid restarting Expert for each test.
|
||||
"""
|
||||
ignored_paths = ["scripts", "ignored_dir"]
|
||||
with start_ls_context(language=Language.ELIXIR, ignored_paths=ignored_paths) as ls:
|
||||
yield ls
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ELIXIR], indirect=True)
|
||||
@pytest.mark.slow
|
||||
@SKIP_SLOW_IN_CI
|
||||
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that request_full_symbol_tree ignores the configured directory."""
|
||||
"""Tests that request_full_symbol_tree ignores the configured directory.
|
||||
|
||||
Note: This test uses a separate Expert instance with custom ignored paths,
|
||||
which adds ~60-90s startup time.
|
||||
"""
|
||||
root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
|
||||
root_children = root["children"]
|
||||
children_names = {child["name"] for child in root_children}
|
||||
@@ -35,9 +51,14 @@ def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
assert "ignored_dir" not in children_names, f"ignored_dir should not be in {children_names}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.ELIXIR], indirect=True)
|
||||
@pytest.mark.slow
|
||||
@SKIP_SLOW_IN_CI
|
||||
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that find_references ignores the configured directory."""
|
||||
"""Tests that find_references ignores the configured directory.
|
||||
|
||||
Note: This test uses a separate Expert instance with custom ignored paths,
|
||||
which adds ~60-90s startup time.
|
||||
"""
|
||||
# Location of User struct, which is referenced in scripts and ignored_dir
|
||||
definition_file = "lib/models.ex"
|
||||
|
||||
@@ -60,9 +81,15 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
assert not any("ignored_dir" in ref["relativePath"] for ref in references), "ignored_dir should be ignored"
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
@SKIP_SLOW_IN_CI
|
||||
@pytest.mark.parametrize("repo_path", [Language.ELIXIR], indirect=True)
|
||||
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
"""Tests that refs and symbols with glob patterns are ignored."""
|
||||
"""Tests that refs and symbols with glob patterns are ignored.
|
||||
|
||||
Note: This test uses a separate Expert instance with custom ignored paths,
|
||||
which adds ~60-90s startup time.
|
||||
"""
|
||||
ignored_paths = ["*cripts", "ignored_*"] # codespell:ignore cripts
|
||||
with start_ls_context(language=Language.ELIXIR, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
|
||||
|
||||
@@ -114,6 +141,10 @@ def test_default_ignored_directories(language_server: SolidLanguageServer):
|
||||
assert not language_server.is_ignored_dirname("priv"), "priv should not be ignored"
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="Expert 0.1.0 bug: document_symbols may return nil for some files (flaky)",
|
||||
raises=Exception,
|
||||
)
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_symbol_tree_excludes_build_dirs(language_server: SolidLanguageServer):
|
||||
"""Test that symbol tree excludes build and dependency directories."""
|
||||
|
||||
@@ -14,10 +14,10 @@ from serena.project import Project
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
from . import NEXTLS_UNAVAILABLE, NEXTLS_UNAVAILABLE_REASON
|
||||
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
|
||||
|
||||
# These marks will be applied to all tests in this module
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(NEXTLS_UNAVAILABLE, reason=f"Next LS not available: {NEXTLS_UNAVAILABLE_REASON}")]
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(EXPERT_UNAVAILABLE, reason=f"Next LS not available: {EXPERT_UNAVAILABLE_REASON}")]
|
||||
|
||||
|
||||
class TestElixirIntegration:
|
||||
|
||||
@@ -15,15 +15,18 @@ from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
|
||||
from . import NEXTLS_UNAVAILABLE, NEXTLS_UNAVAILABLE_REASON
|
||||
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
|
||||
|
||||
# These marks will be applied to all tests in this module
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(NEXTLS_UNAVAILABLE, reason=f"Next LS not available: {NEXTLS_UNAVAILABLE_REASON}")]
|
||||
pytestmark = [pytest.mark.elixir, pytest.mark.skipif(EXPERT_UNAVAILABLE, reason=f"Next LS not available: {EXPERT_UNAVAILABLE_REASON}")]
|
||||
|
||||
|
||||
class TestElixirLanguageServerSymbols:
|
||||
"""Test the Elixir language server's symbol-related functionality."""
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="Expert 0.1.0 bug: document_symbols returns nil for some files (FunctionClauseError in XPExpert.EngineApi.document_symbols/2)"
|
||||
)
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
@@ -159,6 +162,9 @@ class TestElixirLanguageServerSymbols:
|
||||
pass
|
||||
|
||||
# Tests for request_defining_symbol
|
||||
@pytest.mark.xfail(
|
||||
reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)"
|
||||
)
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_request_defining_symbol_function_call(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a function call."""
|
||||
@@ -183,6 +189,9 @@ class TestElixirLanguageServerSymbols:
|
||||
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
|
||||
assert "models.ex" in defining_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)"
|
||||
)
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_request_defining_symbol_struct_usage(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a struct usage."""
|
||||
@@ -205,10 +214,7 @@ class TestElixirLanguageServerSymbols:
|
||||
assert "User" in defining_symbol.get("name", "")
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="Known intermittent bug in Next LS v0.23.3: Protocol.UndefinedError for :timeout atom. "
|
||||
"Occurs in CI environments but may pass locally. "
|
||||
"See https://github.com/elixir-tools/next-ls/issues/543",
|
||||
strict=False,
|
||||
reason="Expert 0.1.0 bug: definition request crashes (FunctionClauseError in XPExpert.Protocol.Conversions.to_elixir/2)"
|
||||
)
|
||||
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
@@ -271,7 +277,7 @@ class TestElixirLanguageServerSymbols:
|
||||
break
|
||||
|
||||
if lib_dir:
|
||||
# Next LS returns module names instead of file names (e.g., 'services' instead of 'services.ex')
|
||||
# Expert returns module names instead of file names (e.g., 'services' instead of 'services.ex')
|
||||
file_names = [child["name"] for child in lib_dir.get("children", [])]
|
||||
expected_modules = ["models", "services", "examples", "utils", "test_repo"]
|
||||
found_modules = [name for name in expected_modules if name in file_names]
|
||||
@@ -284,7 +290,7 @@ class TestElixirLanguageServerSymbols:
|
||||
|
||||
# Should get an overview of the lib directory
|
||||
assert lib_overview is not None
|
||||
# Next LS returns keys like 'lib/services.ex' instead of just 'lib'
|
||||
# Expert returns keys like 'lib/services.ex' instead of just 'lib'
|
||||
overview_keys = list(lib_overview.keys()) if hasattr(lib_overview, "keys") else []
|
||||
lib_files = [key for key in overview_keys if key.startswith("lib/")]
|
||||
assert len(lib_files) > 0, f"Expected to find lib/ files in overview keys: {overview_keys}"
|
||||
@@ -298,10 +304,10 @@ class TestElixirLanguageServerSymbols:
|
||||
# @pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
|
||||
# def test_request_document_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
# """Test request_document_overview functionality."""
|
||||
# # COMMENTED OUT: Next LS document overview doesn't contain expected terms
|
||||
# # Next LS return value: [('TestRepo.Models', 2, 0, 0)] - only module info, no detailed content
|
||||
# # COMMENTED OUT: Expert document overview doesn't contain expected terms
|
||||
# # Expert return value: [('TestRepo.Models', 2, 0, 0)] - only module info, no detailed content
|
||||
# # Expected terms like 'user', 'item', 'order', 'struct', 'defmodule' are not present
|
||||
# # This appears to be a limitation of Next LS document overview functionality
|
||||
# # This appears to be a limitation of Expert document overview functionality
|
||||
# #
|
||||
# file_path = os.path.join("lib", "models.ex")
|
||||
# doc_overview = language_server.request_document_overview(file_path)
|
||||
|
||||
Reference in new issue
Block a user