mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-04 22:13:47 +00:00
* Introduce abstractions: FileCollection, FileProxy * Unify handling of pattern search in project files (source code vs. all files) This prepares for processing of external files, which can implement FileProxy differently.
244 lines
12 KiB
Python
244 lines
12 KiB
Python
import pytest
|
|
|
|
from serena.project import Project
|
|
from solidlsp.ls import SolidLanguageServer
|
|
from solidlsp.ls_config import Language
|
|
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
|
|
from test.conftest import language_tests_enabled
|
|
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
|
|
|
from . import CORE_PATH, UTILS_PATH
|
|
|
|
|
|
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled")
|
|
@pytest.mark.clojure
|
|
class TestLanguageServerBasics:
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_basic_definition(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test finding definition of 'greet' function call in core.clj
|
|
"""
|
|
result = language_server.request_definition(CORE_PATH, 20, 12) # Position of 'greet' in (greet "World")
|
|
|
|
assert isinstance(result, list)
|
|
assert len(result) >= 1
|
|
|
|
definition = result[0]
|
|
assert definition["relativePath"] == CORE_PATH
|
|
assert definition["range"]["start"]["line"] == 2, "Should find the definition of greet function at line 2"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_cross_file_references(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test finding references to 'multiply' function from core.clj
|
|
"""
|
|
result = language_server.request_references(CORE_PATH, 12, 6)
|
|
|
|
assert isinstance(result, list) and len(result) >= 2, "Should find definition + usage in utils.clj"
|
|
|
|
usage_found = any(
|
|
item["relativePath"] == UTILS_PATH and item["range"]["start"]["line"] == 6 # multiply usage in calculate-area
|
|
for item in result
|
|
)
|
|
assert usage_found, "Should find multiply usage in utils.clj"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_completions(self, language_server: SolidLanguageServer):
|
|
with language_server.open_file(UTILS_PATH):
|
|
# After "core/" in calculate-area
|
|
result = language_server.request_completions(UTILS_PATH, 6, 8)
|
|
|
|
assert isinstance(result, list) and len(result) > 0
|
|
|
|
completion_texts = [item["completionText"] for item in result]
|
|
assert any("multiply" in text for text in completion_texts), "Should find 'multiply' function in completions after 'core/'"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_document_symbols(self, language_server: SolidLanguageServer):
|
|
symbols, _ = language_server.request_document_symbols(CORE_PATH).get_all_symbols_and_roots()
|
|
|
|
assert isinstance(symbols, list) and len(symbols) >= 4, "greet, add, multiply, -main functions"
|
|
|
|
# Check that we find the expected function symbols
|
|
symbol_names = [symbol["name"] for symbol in symbols]
|
|
expected_functions = ["greet", "add", "multiply", "-main"]
|
|
|
|
for func_name in expected_functions:
|
|
assert func_name in symbol_names, f"Should find {func_name} function in symbols"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_hover(self, language_server: SolidLanguageServer):
|
|
"""Test hover on greet function"""
|
|
result = language_server.request_hover(CORE_PATH, 2, 7)
|
|
|
|
assert result is not None, "Hover should return information for greet function"
|
|
assert "contents" in result
|
|
# Should contain function signature or documentation
|
|
contents = result["contents"]
|
|
if isinstance(contents, str):
|
|
assert "greet" in contents.lower()
|
|
elif isinstance(contents, dict) and "value" in contents:
|
|
assert "greet" in contents["value"].lower()
|
|
else:
|
|
assert False, f"Unexpected contents format: {type(contents)}"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_workspace_symbols(self, language_server: SolidLanguageServer):
|
|
# Search for functions containing "add"
|
|
result = language_server.request_workspace_symbol("add")
|
|
|
|
assert isinstance(result, list) and len(result) > 0, "Should find at least one symbol containing 'add'"
|
|
|
|
# Should find the 'add' function
|
|
symbol_names = [symbol["name"] for symbol in result]
|
|
assert any("add" in name.lower() for name in symbol_names), f"Should find 'add' function in symbols: {symbol_names}"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_namespace_functions(self, language_server: SolidLanguageServer):
|
|
"""Test definition lookup for core/greet usage in utils.clj"""
|
|
# Position of 'greet' in core/greet call
|
|
result = language_server.request_definition(UTILS_PATH, 11, 25)
|
|
|
|
assert isinstance(result, list)
|
|
assert len(result) >= 1
|
|
|
|
definition = result[0]
|
|
assert definition["relativePath"] == CORE_PATH, "Should find the definition of greet in core.clj"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_request_references_with_content(self, language_server: SolidLanguageServer):
|
|
"""Test references to multiply function with content"""
|
|
references = language_server.request_references(CORE_PATH, 12, 6)
|
|
result = [
|
|
language_server.retrieve_content_around_line(ref1["relativePath"], ref1["range"]["start"]["line"], 3, 0) for ref1 in references
|
|
]
|
|
|
|
assert result is not None, "Should find references with content"
|
|
assert isinstance(result, list)
|
|
assert len(result) >= 2, "Should find definition + usage in utils.clj"
|
|
|
|
for ref in result:
|
|
assert ref.source_file_path is not None, "Each reference should have a source file path"
|
|
content_str = ref.to_display_string()
|
|
assert len(content_str) > 0, "Content should not be empty"
|
|
|
|
# Verify we find the reference in utils.clj with context
|
|
utils_refs = [ref for ref in result if ref.source_file_path and "utils.clj" in ref.source_file_path]
|
|
assert len(utils_refs) > 0, "Should find reference in utils.clj"
|
|
|
|
# The context should contain the calculate-area function
|
|
utils_content = utils_refs[0].to_display_string()
|
|
assert "calculate-area" in utils_content
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_request_full_symbol_tree(self, language_server: SolidLanguageServer):
|
|
"""Test retrieving the full symbol tree for project overview
|
|
We just check that we find some expected symbols.
|
|
"""
|
|
result = language_server.request_full_symbol_tree()
|
|
|
|
assert result is not None, "Should return symbol tree"
|
|
assert isinstance(result, list), "Symbol tree should be a list"
|
|
assert len(result) > 0, "Should find symbols in the project"
|
|
|
|
def traverse_symbols(symbols, indent=0):
|
|
"""Recursively traverse symbols to print their structure"""
|
|
info = []
|
|
for s in symbols:
|
|
name = getattr(s, "name", "NO_NAME")
|
|
kind = getattr(s, "kind", "NO_KIND")
|
|
info.append(f"{' ' * indent}Symbol: {name}, Kind: {kind}")
|
|
if hasattr(s, "children") and s.children:
|
|
info.append(" " * indent + "Children:")
|
|
info.extend(traverse_symbols(s.children, indent + 2))
|
|
return info
|
|
|
|
def list_all_symbols(symbols: list[UnifiedSymbolInformation]):
|
|
found = []
|
|
for symbol in symbols:
|
|
found.append(symbol["name"])
|
|
found.extend(list_all_symbols(symbol["children"]))
|
|
return found
|
|
|
|
all_symbol_names = list_all_symbols(result)
|
|
|
|
expected_symbols = ["greet", "add", "multiply", "-main", "calculate-area", "format-greeting", "sum-list"]
|
|
found_expected = [name for name in expected_symbols if any(name in symbol_name for symbol_name in all_symbol_names)]
|
|
|
|
if len(found_expected) < 7:
|
|
pytest.fail(
|
|
f"Expected to find at least 3 symbols from {expected_symbols}, but found: {found_expected}.\n"
|
|
f"All symbol names: {all_symbol_names}\n"
|
|
f"Symbol tree structure:\n{traverse_symbols(result)}"
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_request_referencing_symbols(self, language_server: SolidLanguageServer):
|
|
"""Test finding symbols that reference a given symbol
|
|
Finds references to the 'multiply' function.
|
|
"""
|
|
result = language_server.request_referencing_symbols(CORE_PATH, 12, 6)
|
|
assert isinstance(result, list) and len(result) > 0, "Should find at least one referencing symbol"
|
|
found_relevant_references = False
|
|
for ref in result:
|
|
if hasattr(ref, "symbol") and "calculate-area" in ref.symbol["name"]:
|
|
found_relevant_references = True
|
|
break
|
|
|
|
assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}"
|
|
|
|
|
|
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled")
|
|
@pytest.mark.clojure
|
|
class TestProjectBasics:
|
|
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
|
|
def test_retrieve_content_around_line(self, project: Project):
|
|
"""Test retrieving content around specific lines"""
|
|
# Test retrieving content around the greet function definition (line 2)
|
|
result = project.retrieve_content_around_line(CORE_PATH, 2, 2)
|
|
|
|
assert result is not None, "Should retrieve content around line 2"
|
|
content_str = result.to_display_string()
|
|
assert "greet" in content_str, "Should contain the greet function definition"
|
|
assert "defn" in content_str, "Should contain defn keyword"
|
|
|
|
# Test retrieving content around multiply function (around line 13)
|
|
result = project.retrieve_content_around_line(CORE_PATH, 13, 1)
|
|
|
|
assert result is not None, "Should retrieve content around line 13"
|
|
content_str = result.to_display_string()
|
|
assert "multiply" in content_str, "Should contain multiply function"
|
|
|
|
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
|
|
def test_search_files_for_pattern(self, project: Project) -> None:
|
|
result = project.search_project_files_for_pattern("defn.*greet")
|
|
|
|
assert result is not None, "Pattern search should return results"
|
|
assert len(result) > 0, "Should find at least one match for 'defn.*greet'"
|
|
|
|
core_matches = [match for match in result if match.source_file_path and "core.clj" in match.source_file_path]
|
|
assert len(core_matches) > 0, "Should find greet function in core.clj"
|
|
|
|
result = project.search_project_files_for_pattern(":require")
|
|
|
|
assert result is not None, "Should find require statements"
|
|
utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path]
|
|
assert len(utils_matches) > 0, "Should find require statement in utils.clj"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
|
|
def test_bare_symbol_names(self, language_server) -> None:
|
|
all_symbols = request_all_symbols(language_server)
|
|
malformed_symbols = []
|
|
for s in all_symbols:
|
|
# clojure-lsp exposes namespace and dependency/container entries in addition to real vars/functions.
|
|
# Those qualified names are not the target of this regression check.
|
|
if s["kind"] in {SymbolKind.Namespace, SymbolKind.Struct}:
|
|
continue
|
|
if has_malformed_name(s):
|
|
malformed_symbols.append(s)
|
|
if malformed_symbols:
|
|
pytest.fail(
|
|
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
|
|
pytrace=False,
|
|
)
|