chore: import upstream snapshot with attribution
Tests / catch-all (windows-latest) (push) Has been cancelled
Tests / jvm (macos-latest) (push) Has been cancelled
Tests / jvm (ubuntu-latest) (push) Has been cancelled
Tests / jvm (windows-latest) (push) Has been cancelled
Tests / native (macos-latest) (push) Has been cancelled
Tests / native (ubuntu-latest) (push) Has been cancelled
Tests / native (windows-latest) (push) Has been cancelled
Tests / niche (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (macos-latest) (push) Has been cancelled
Tests / other-langs (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (windows-latest) (push) Has been cancelled
Tests / catch-all (macos-latest) (push) Has been cancelled
Tests / catch-all (ubuntu-latest) (push) Has been cancelled
Docs Build / build (push) Has been cancelled
Docs Build / deploy (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Build and Push Docker Images / build-and-push (push) Has been cancelled
Tests / catch-all (windows-latest) (push) Has been cancelled
Tests / jvm (macos-latest) (push) Has been cancelled
Tests / jvm (ubuntu-latest) (push) Has been cancelled
Tests / jvm (windows-latest) (push) Has been cancelled
Tests / native (macos-latest) (push) Has been cancelled
Tests / native (ubuntu-latest) (push) Has been cancelled
Tests / native (windows-latest) (push) Has been cancelled
Tests / niche (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (macos-latest) (push) Has been cancelled
Tests / other-langs (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (windows-latest) (push) Has been cancelled
Tests / catch-all (macos-latest) (push) Has been cancelled
Tests / catch-all (ubuntu-latest) (push) Has been cancelled
Docs Build / build (push) Has been cancelled
Docs Build / deploy (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Build and Push Docker Images / build-and-push (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
Basic integration tests for the language server functionality.
|
||||
|
||||
These tests validate the functionality of the language server APIs
|
||||
like request_references using the test repository.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.project import Project
|
||||
from serena.util.text_utils import LineType
|
||||
from solidlsp import SolidLanguageServer
|
||||
from test.conftest import PYTHON_LANGUAGE_BACKENDS
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES, format_symbol_for_assert, has_malformed_name, request_all_symbols
|
||||
|
||||
|
||||
@pytest.mark.python
|
||||
class TestPythonLanguageServerBasics:
|
||||
"""Test basic functionality of the language server."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_user_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on the User class."""
|
||||
# Get references to the User class in models.py
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
# Line 31 contains the User class definition
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
|
||||
if not user_symbol or "selectionRange" not in user_symbol:
|
||||
raise AssertionError("User symbol or its selectionRange not found")
|
||||
sel_start = user_symbol["selectionRange"]["start"]
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) > 1, "User class should be referenced in multiple files (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_item_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on the Item class."""
|
||||
# Get references to the Item class in models.py
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
# Line 56 contains the Item class definition
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
item_symbol = next((s for s in symbols[0] if s.get("name") == "Item"), None)
|
||||
if not item_symbol or "selectionRange" not in item_symbol:
|
||||
raise AssertionError("Item symbol or its selectionRange not found")
|
||||
sel_start = item_symbol["selectionRange"]["start"]
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
services_references = [ref for ref in references if "services.py" in ref["uri"]]
|
||||
assert len(services_references) > 0, "At least one reference should be in services.py (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_function_parameter(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_references on a function parameter."""
|
||||
# Get references to the id parameter in get_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 24 contains the get_user method with id parameter
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
get_user_symbol = next((s for s in symbols[0] if s.get("name") == "get_user"), None)
|
||||
if not get_user_symbol or "selectionRange" not in get_user_symbol:
|
||||
raise AssertionError("get_user symbol or its selectionRange not found")
|
||||
sel_start = get_user_symbol["selectionRange"]["start"]
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) > 0, "id parameter should be referenced within the method (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_references_create_user_method(self, language_server: SolidLanguageServer) -> None:
|
||||
# Get references to the create_user method in UserService
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 15 contains the create_user method definition
|
||||
# Use selectionRange only
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = next((s for s in symbols[0] if s.get("name") == "create_user"), None)
|
||||
if not create_user_symbol or "selectionRange" not in create_user_symbol:
|
||||
raise AssertionError("create_user symbol or its selectionRange not found")
|
||||
sel_start = create_user_symbol["selectionRange"]["start"]
|
||||
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
|
||||
assert len(references) > 1, "Should get valid references for create_user (using selectionRange if present)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_LANGUAGE_BACKENDS, indirect=True)
|
||||
def test_request_text_document_diagnostics_with_filters(self, language_server: SolidLanguageServer) -> None:
|
||||
file_path = os.path.join("test_repo", "diagnostics_sample.py")
|
||||
|
||||
diagnostics = language_server.request_text_document_diagnostics(file_path)
|
||||
assert len(diagnostics) >= 2
|
||||
diagnostic_messages = [diagnostic["message"] for diagnostic in diagnostics]
|
||||
assert any("missing_user" in message for message in diagnostic_messages), diagnostic_messages
|
||||
assert any("undefined_name" in message for message in diagnostic_messages), diagnostic_messages
|
||||
|
||||
factory_diagnostics = language_server.request_text_document_diagnostics(file_path, start_line=3, end_line=5, min_severity=1)
|
||||
factory_messages = [diagnostic["message"] for diagnostic in factory_diagnostics]
|
||||
assert factory_messages, "Expected diagnostics in broken_factory range"
|
||||
assert all("missing_user" in message for message in factory_messages), factory_messages
|
||||
|
||||
consumer_diagnostics = language_server.request_text_document_diagnostics(file_path, start_line=7, end_line=10, min_severity=1)
|
||||
consumer_messages = [diagnostic["message"] for diagnostic in consumer_diagnostics]
|
||||
assert consumer_messages, "Expected diagnostics in broken_consumer range"
|
||||
assert all("undefined_name" in message for message in consumer_messages), consumer_messages
|
||||
|
||||
|
||||
class TestProjectBasics:
|
||||
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_retrieve_content_around_line(self, project: Project) -> None:
|
||||
"""Test retrieve_content_around_line functionality with various scenarios."""
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
|
||||
# Scenario 1: Just a single line (User class definition)
|
||||
line_31 = project.retrieve_content_around_line(file_path, 31)
|
||||
assert len(line_31.lines) == 1
|
||||
assert "class User(BaseModel):" in line_31.lines[0].line_content
|
||||
assert line_31.lines[0].line_number == 31
|
||||
assert line_31.lines[0].match_type == LineType.MATCH
|
||||
|
||||
# Scenario 2: Context above and below
|
||||
with_context_around_user = project.retrieve_content_around_line(file_path, 31, 2, 2)
|
||||
assert len(with_context_around_user.lines) == 5
|
||||
# Check line content
|
||||
assert "class User(BaseModel):" in with_context_around_user.matched_lines[0].line_content
|
||||
assert with_context_around_user.num_matched_lines == 1
|
||||
assert " User model representing a system user." in with_context_around_user.lines[4].line_content
|
||||
# Check line numbers
|
||||
assert with_context_around_user.lines[0].line_number == 29
|
||||
assert with_context_around_user.lines[1].line_number == 30
|
||||
assert with_context_around_user.lines[2].line_number == 31
|
||||
assert with_context_around_user.lines[3].line_number == 32
|
||||
assert with_context_around_user.lines[4].line_number == 33
|
||||
# Check match types
|
||||
assert with_context_around_user.lines[0].match_type == LineType.BEFORE_MATCH
|
||||
assert with_context_around_user.lines[1].match_type == LineType.BEFORE_MATCH
|
||||
assert with_context_around_user.lines[2].match_type == LineType.MATCH
|
||||
assert with_context_around_user.lines[3].match_type == LineType.AFTER_MATCH
|
||||
assert with_context_around_user.lines[4].match_type == LineType.AFTER_MATCH
|
||||
|
||||
# Scenario 3a: Only context above
|
||||
with_context_above = project.retrieve_content_around_line(file_path, 31, 3, 0)
|
||||
assert len(with_context_above.lines) == 4
|
||||
assert "return cls(id=id, name=name)" in with_context_above.lines[0].line_content
|
||||
assert "class User(BaseModel):" in with_context_above.matched_lines[0].line_content
|
||||
assert with_context_above.num_matched_lines == 1
|
||||
# Check line numbers
|
||||
assert with_context_above.lines[0].line_number == 28
|
||||
assert with_context_above.lines[1].line_number == 29
|
||||
assert with_context_above.lines[2].line_number == 30
|
||||
assert with_context_above.lines[3].line_number == 31
|
||||
# Check match types
|
||||
assert with_context_above.lines[0].match_type == LineType.BEFORE_MATCH
|
||||
assert with_context_above.lines[1].match_type == LineType.BEFORE_MATCH
|
||||
assert with_context_above.lines[2].match_type == LineType.BEFORE_MATCH
|
||||
assert with_context_above.lines[3].match_type == LineType.MATCH
|
||||
|
||||
# Scenario 3b: Only context below
|
||||
with_context_below = project.retrieve_content_around_line(file_path, 31, 0, 3)
|
||||
assert len(with_context_below.lines) == 4
|
||||
assert "class User(BaseModel):" in with_context_below.matched_lines[0].line_content
|
||||
assert with_context_below.num_matched_lines == 1
|
||||
assert with_context_below.lines[0].line_number == 31
|
||||
assert with_context_below.lines[1].line_number == 32
|
||||
assert with_context_below.lines[2].line_number == 33
|
||||
assert with_context_below.lines[3].line_number == 34
|
||||
# Check match types
|
||||
assert with_context_below.lines[0].match_type == LineType.MATCH
|
||||
assert with_context_below.lines[1].match_type == LineType.AFTER_MATCH
|
||||
assert with_context_below.lines[2].match_type == LineType.AFTER_MATCH
|
||||
assert with_context_below.lines[3].match_type == LineType.AFTER_MATCH
|
||||
|
||||
# Scenario 4a: Edge case - context above but line is at 0
|
||||
first_line_with_context_around = project.retrieve_content_around_line(file_path, 0, 2, 1)
|
||||
assert len(first_line_with_context_around.lines) <= 4 # Should have at most 4 lines (line 0 + 1 below + up to 2 above)
|
||||
assert first_line_with_context_around.lines[0].line_number <= 2 # First line should be at most line 2
|
||||
# Check match type for the target line
|
||||
for line in first_line_with_context_around.lines:
|
||||
if line.line_number == 0:
|
||||
assert line.match_type == LineType.MATCH
|
||||
elif line.line_number < 0:
|
||||
assert line.match_type == LineType.BEFORE_MATCH
|
||||
else:
|
||||
assert line.match_type == LineType.AFTER_MATCH
|
||||
|
||||
# Scenario 4b: Edge case - context above but line is at 1
|
||||
second_line_with_context_above = project.retrieve_content_around_line(file_path, 1, 3, 1)
|
||||
assert len(second_line_with_context_above.lines) <= 5 # Should have at most 5 lines (line 1 + 1 below + up to 3 above)
|
||||
assert second_line_with_context_above.lines[0].line_number <= 1 # First line should be at most line 1
|
||||
# Check match type for the target line
|
||||
for line in second_line_with_context_above.lines:
|
||||
if line.line_number == 1:
|
||||
assert line.match_type == LineType.MATCH
|
||||
elif line.line_number < 1:
|
||||
assert line.match_type == LineType.BEFORE_MATCH
|
||||
else:
|
||||
assert line.match_type == LineType.AFTER_MATCH
|
||||
|
||||
# Scenario 4c: Edge case - context below but line is at the end of file
|
||||
# First get the total number of lines in the file
|
||||
all_content = project.read_file(file_path)
|
||||
total_lines = len(all_content.split("\n"))
|
||||
|
||||
last_line_with_context_around = project.retrieve_content_around_line(file_path, total_lines - 1, 1, 3)
|
||||
assert len(last_line_with_context_around.lines) <= 5 # Should have at most 5 lines (last line + 1 above + up to 3 below)
|
||||
assert last_line_with_context_around.lines[-1].line_number >= total_lines - 4 # Last line should be at least total_lines - 4
|
||||
# Check match type for the target line
|
||||
for line in last_line_with_context_around.lines:
|
||||
if line.line_number == total_lines - 1:
|
||||
assert line.match_type == LineType.MATCH
|
||||
elif line.line_number < total_lines - 1:
|
||||
assert line.match_type == LineType.BEFORE_MATCH
|
||||
else:
|
||||
assert line.match_type == LineType.AFTER_MATCH
|
||||
|
||||
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_search_files_for_pattern(self, project: Project) -> None:
|
||||
"""Test search_files_for_pattern with various patterns and glob filters."""
|
||||
# Test 1: Search for class definitions across all files
|
||||
class_pattern = r"class\s+\w+\s*(?:\([^{]*\)|:)"
|
||||
matches = project.search_project_files_for_pattern(class_pattern)
|
||||
assert len(matches) > 0
|
||||
# Should find multiple classes like User, Item, BaseModel, etc.
|
||||
assert len(matches) >= 5
|
||||
|
||||
# Test 2: Search for specific class with include glob
|
||||
user_class_pattern = r"class\s+User\s*(?:\([^{]*\)|:)"
|
||||
matches = project.search_project_files_for_pattern(user_class_pattern, paths_include_glob="**/models.py")
|
||||
assert len(matches) == 1 # Should only find User class in models.py
|
||||
assert matches[0].source_file_path is not None
|
||||
assert "models.py" in matches[0].source_file_path
|
||||
|
||||
# Test 3: Search for method definitions with exclude glob
|
||||
method_pattern = r"def\s+\w+\s*\([^)]*\):"
|
||||
matches = project.search_project_files_for_pattern(method_pattern, paths_exclude_glob="**/models.py")
|
||||
assert len(matches) > 0
|
||||
# Should find methods in services.py but not in models.py
|
||||
assert all(match.source_file_path is not None and "models.py" not in match.source_file_path for match in matches)
|
||||
|
||||
# Test 4: Search for specific method with both include and exclude globs
|
||||
create_user_pattern = r"def\s+create_user\s*\([^)]*\)(?:\s*->[^:]+)?:"
|
||||
matches = project.search_project_files_for_pattern(
|
||||
create_user_pattern, paths_include_glob="**/*.py", paths_exclude_glob="**/models.py"
|
||||
)
|
||||
assert len(matches) == 1 # Should only find create_user in services.py
|
||||
assert matches[0].source_file_path is not None
|
||||
assert "services.py" in matches[0].source_file_path
|
||||
|
||||
# Test 5: Search for a pattern that should appear in multiple files
|
||||
init_pattern = r"def\s+__init__\s*\([^)]*\):"
|
||||
matches = project.search_project_files_for_pattern(init_pattern)
|
||||
assert len(matches) > 1 # Should find __init__ in multiple classes
|
||||
# Should find __init__ in both models.py and services.py
|
||||
assert any(match.source_file_path is not None and "models.py" in match.source_file_path for match in matches)
|
||||
assert any(match.source_file_path is not None and "services.py" in match.source_file_path for match in matches)
|
||||
|
||||
# Test 6: Search with a pattern that should have no matches
|
||||
no_match_pattern = r"def\s+this_method_does_not_exist\s*\([^)]*\):"
|
||||
matches = project.search_project_files_for_pattern(no_match_pattern)
|
||||
assert len(matches) == 0
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, 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:
|
||||
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,
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from test.conftest import PYTHON_LANGUAGE_BACKENDS
|
||||
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
||||
|
||||
|
||||
@pytest.mark.python
|
||||
class TestPythonDiagnostics:
|
||||
@pytest.mark.parametrize("language_server", PYTHON_LANGUAGE_BACKENDS, indirect=True)
|
||||
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
|
||||
assert_file_diagnostics(
|
||||
language_server,
|
||||
os.path.join("test_repo", "diagnostics_sample.py"),
|
||||
("missing_user", "undefined_name"),
|
||||
min_count=2,
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import start_ls_context
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
# This mark will be applied to all tests in this module
|
||||
pytestmark = pytest.mark.python
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
|
||||
"""Fixture to set up an LS for the python test repo with the 'scripts' directory ignored."""
|
||||
ignored_paths = ["scripts", "custom_test"]
|
||||
with start_ls_context(language=Language.PYTHON, ignored_paths=ignored_paths) as ls:
|
||||
yield ls
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that request_full_symbol_tree ignores the configured directory."""
|
||||
root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
|
||||
root_children = root["children"]
|
||||
children_names = {child["name"] for child in root_children}
|
||||
assert children_names == {"test_repo", "examples"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
|
||||
"""Tests that find_references ignores the configured directory."""
|
||||
# Location of Item, which is referenced in scripts
|
||||
definition_file = "test_repo/models.py"
|
||||
definition_line = 56
|
||||
definition_col = 6
|
||||
|
||||
references = ls_with_ignored_dirs.request_references(definition_file, definition_line, definition_col)
|
||||
|
||||
# assert that scripts does not appear in the references
|
||||
assert not any("scripts" in ref["relativePath"] for ref in references)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("repo_path", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
|
||||
"""Tests that refs and symbols with glob patterns are ignored."""
|
||||
ignored_paths = ["*ipts", "custom_t*"]
|
||||
with start_ls_context(language=Language.PYTHON, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
|
||||
# same as in the above tests
|
||||
root = ls.request_full_symbol_tree()[0]
|
||||
root_children = root["children"]
|
||||
children_names = {child["name"] for child in root_children}
|
||||
assert children_names == {"test_repo", "examples"}
|
||||
|
||||
# test that the refs and symbols with glob patterns are ignored
|
||||
definition_file = "test_repo/models.py"
|
||||
definition_line = 56
|
||||
definition_col = 6
|
||||
|
||||
references = ls.request_references(definition_file, definition_line, definition_col)
|
||||
assert not any("scripts" in ref["relativePath"] for ref in references)
|
||||
@@ -0,0 +1,410 @@
|
||||
"""
|
||||
Tests for the language server symbol-related functionality.
|
||||
|
||||
These tests focus on the following methods:
|
||||
- request_containing_symbol
|
||||
- request_referencing_symbols
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from serena.symbol import LanguageServerSymbol
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
|
||||
|
||||
pytestmark = pytest.mark.python
|
||||
|
||||
|
||||
class TestLanguageServerSymbols:
|
||||
"""Test the language server's symbol-related functionality."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a function."""
|
||||
# Test for a position inside the create_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 17 is inside the create_user method body
|
||||
containing_symbol = language_server.request_containing_symbol(file_path, 17, 20, include_body=True)
|
||||
|
||||
# Verify that we found the containing symbol
|
||||
assert containing_symbol is not None
|
||||
assert containing_symbol["name"] == "create_user"
|
||||
assert containing_symbol["kind"] == SymbolKind.Method
|
||||
if "body" in containing_symbol:
|
||||
assert containing_symbol["body"].get_text().strip().startswith("def create_user(self")
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_references_to_variables(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a variable."""
|
||||
file_path = os.path.join("test_repo", "variables.py")
|
||||
# Line 75 contains the field status that is later modified
|
||||
ref_symbols = [ref.symbol for ref in language_server.request_referencing_symbols(file_path, 74, 4)]
|
||||
|
||||
assert len(ref_symbols) > 0
|
||||
ref_lines = [ref["location"]["range"]["start"]["line"] for ref in ref_symbols if "location" in ref and "range" in ref["location"]]
|
||||
ref_names = [ref["name"] for ref in ref_symbols]
|
||||
assert 87 in ref_lines
|
||||
assert 95 in ref_lines
|
||||
assert "dataclass_instance" in ref_names
|
||||
assert "second_dataclass" in ref_names
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a class."""
|
||||
# Test for a position inside the UserService class but outside any method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 9 is the class definition line for UserService
|
||||
containing_symbol = language_server.request_containing_symbol(file_path, 9, 7)
|
||||
|
||||
# Verify that we found the containing symbol
|
||||
assert containing_symbol is not None
|
||||
assert containing_symbol["name"] == "UserService"
|
||||
assert containing_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol with nested scopes."""
|
||||
# Test for a position inside a method which is inside a class
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 18 is inside the create_user method inside UserService class
|
||||
containing_symbol = language_server.request_containing_symbol(file_path, 18, 25)
|
||||
|
||||
# Verify that we found the innermost containing symbol (the method)
|
||||
assert containing_symbol is not None
|
||||
assert containing_symbol["name"] == "create_user"
|
||||
assert containing_symbol["kind"] == SymbolKind.Method
|
||||
|
||||
# Get the parent containing symbol
|
||||
if "location" in containing_symbol and "range" in containing_symbol["location"]:
|
||||
parent_symbol = language_server.request_containing_symbol(
|
||||
file_path,
|
||||
containing_symbol["location"]["range"]["start"]["line"],
|
||||
containing_symbol["location"]["range"]["start"]["character"] - 1,
|
||||
)
|
||||
|
||||
# Verify that the parent is the class
|
||||
assert parent_symbol is not None
|
||||
assert parent_symbol["name"] == "UserService"
|
||||
assert parent_symbol["kind"] == SymbolKind.Class
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol for a position with no containing symbol."""
|
||||
# Test for a position outside any function/class (e.g., in imports)
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 1 is in imports, not inside any function or class
|
||||
containing_symbol = language_server.request_containing_symbol(file_path, 1, 10)
|
||||
|
||||
# Should return None or an empty dictionary
|
||||
assert containing_symbol is None or containing_symbol == {}
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function."""
|
||||
# Test referencing symbols for create_user function
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 15 contains the create_user function definition
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
create_user_symbol = next((s for s in symbols[0] if s.get("name") == "create_user"), None)
|
||||
if not create_user_symbol or "selectionRange" not in create_user_symbol:
|
||||
raise AssertionError("create_user symbol or its selectionRange not found")
|
||||
sel_start = create_user_symbol["selectionRange"]["start"]
|
||||
ref_symbols = [
|
||||
ref.symbol for ref in language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])
|
||||
]
|
||||
assert len(ref_symbols) > 0, "No referencing symbols found for create_user (selectionRange)"
|
||||
|
||||
# Verify the structure of referencing symbols
|
||||
for symbol in ref_symbols:
|
||||
assert "name" in symbol
|
||||
assert "kind" in symbol
|
||||
if "location" in symbol and "range" in symbol["location"]:
|
||||
assert "start" in symbol["location"]["range"]
|
||||
assert "end" in symbol["location"]["range"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a class."""
|
||||
# Test referencing symbols for User class
|
||||
file_path = os.path.join("test_repo", "models.py")
|
||||
# Line 31 contains the User class definition
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
user_symbol = next((s for s in symbols[0] if s.get("name") == "User"), None)
|
||||
if not user_symbol or "selectionRange" not in user_symbol:
|
||||
raise AssertionError("User symbol or its selectionRange not found")
|
||||
sel_start = user_symbol["selectionRange"]["start"]
|
||||
ref_symbols = [
|
||||
ref.symbol for ref in language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])
|
||||
]
|
||||
services_references = [
|
||||
symbol
|
||||
for symbol in ref_symbols
|
||||
if "location" in symbol and "uri" in symbol["location"] and "services.py" in symbol["location"]["uri"]
|
||||
]
|
||||
assert len(services_references) > 0, "No referencing symbols from services.py for User (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a function parameter."""
|
||||
# Test referencing symbols for id parameter in get_user
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 24 contains the get_user method with id parameter
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
get_user_symbol = next((s for s in symbols[0] if s.get("name") == "get_user"), None)
|
||||
if not get_user_symbol or "selectionRange" not in get_user_symbol:
|
||||
raise AssertionError("get_user symbol or its selectionRange not found")
|
||||
sel_start = get_user_symbol["selectionRange"]["start"]
|
||||
ref_symbols = [
|
||||
ref.symbol for ref in language_server.request_referencing_symbols(file_path, sel_start["line"], sel_start["character"])
|
||||
]
|
||||
method_refs = [
|
||||
symbol
|
||||
for symbol in ref_symbols
|
||||
if "location" in symbol and "range" in symbol["location"] and symbol["location"]["range"]["start"]["line"] > sel_start["line"]
|
||||
]
|
||||
assert len(method_refs) > 0, "No referencing symbols within method body for get_user (selectionRange)"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_referencing_symbols for a position with no symbol."""
|
||||
# For positions with no symbol, the method might throw an error or return None/empty list
|
||||
# We'll modify our test to handle this by using a try-except block
|
||||
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 3 is a blank line or comment
|
||||
try:
|
||||
ref_symbols = [ref.symbol for ref in language_server.request_referencing_symbols(file_path, 3, 0)]
|
||||
# If we get here, make sure we got an empty result
|
||||
assert ref_symbols == [] or ref_symbols is None
|
||||
except Exception:
|
||||
# The method might raise an exception for invalid positions
|
||||
# which is acceptable behavior
|
||||
pass
|
||||
|
||||
# Tests for request_defining_symbol
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a variable usage."""
|
||||
# Test finding the definition of a symbol in the create_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 21 contains self.users[id] = user
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 21, 10)
|
||||
|
||||
# Verify that we found the defining symbol
|
||||
# The defining symbol method returns a dictionary with information about the defining symbol
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "create_user"
|
||||
|
||||
# Verify the location and kind of the symbol
|
||||
# SymbolKind.Method = 6 for a method
|
||||
assert defining_symbol.get("kind") == SymbolKind.Method.value
|
||||
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
|
||||
assert "services.py" in defining_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for an imported class."""
|
||||
# Test finding the definition of the 'User' class used in the UserService.create_user method
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 20 references 'User' which was imported from models
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 20, 15)
|
||||
|
||||
# Verify that we found the defining symbol - this should be the User class from models
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "User"
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a method call."""
|
||||
# Create an example file path for a file that calls UserService.create_user
|
||||
examples_file_path = os.path.join("examples", "user_management.py")
|
||||
|
||||
# Find the line number where create_user is called
|
||||
# This could vary, so we'll use a relative position that makes sense
|
||||
defining_symbol = language_server.request_defining_symbol(examples_file_path, 46, 29)
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "create_user"
|
||||
# The defining symbol should be in the services.py file
|
||||
assert "services.py" in defining_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a position with no symbol."""
|
||||
# Test for a position with no symbol (e.g., whitespace or comment)
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 3 is a blank line
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 3, 0)
|
||||
|
||||
# Should return None for positions with no symbol
|
||||
assert defining_symbol is None
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_containing_symbol where the symbol is a variable."""
|
||||
# Test for a position inside a variable definition
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
# Line 74 defines the 'user' variable
|
||||
containing_symbol = language_server.request_containing_symbol(file_path, 73, 1)
|
||||
|
||||
# Verify that we found the containing symbol
|
||||
assert containing_symbol is not None
|
||||
assert containing_symbol["name"] == "user_var_str"
|
||||
assert containing_symbol["kind"] == SymbolKind.Variable
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test request_defining_symbol for a nested function or closure."""
|
||||
# Use the existing nested.py file which contains nested classes and methods
|
||||
file_path = os.path.join("test_repo", "nested.py")
|
||||
|
||||
# Test 1: Find definition of nested method - line with 'b = OuterClass().NestedClass().find_me()'
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 15, 35) # Position of find_me() call
|
||||
|
||||
# This should resolve to the find_me method in the NestedClass
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "find_me"
|
||||
assert defining_symbol.get("kind") == SymbolKind.Method.value
|
||||
|
||||
# Test 2: Find definition of the nested class
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 15, 18) # Position of NestedClass
|
||||
|
||||
# This should resolve to the NestedClass
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "NestedClass"
|
||||
assert defining_symbol.get("kind") == SymbolKind.Class.value
|
||||
|
||||
# Test 3: Find definition of a method-local function
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 9, 15) # Position inside func_within_func
|
||||
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
|
||||
# Test 2: Find definition of the nested class
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 15, 18) # Position of NestedClass
|
||||
|
||||
# This should resolve to the NestedClass
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "NestedClass"
|
||||
assert defining_symbol.get("kind") == SymbolKind.Class.value
|
||||
|
||||
# Test 3: Find definition of a method-local function
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 9, 15) # Position inside func_within_func
|
||||
|
||||
# This is challenging for many language servers and may fail
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol.get("name") == "func_within_func"
|
||||
assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test the integration between different symbol-related methods."""
|
||||
# This test demonstrates using the various symbol methods together
|
||||
# by finding a symbol and then checking its definition
|
||||
|
||||
file_path = os.path.join("test_repo", "services.py")
|
||||
|
||||
# First approach: Use a method from the UserService class
|
||||
# Step 1: Find a method we know exists
|
||||
containing_symbol = language_server.request_containing_symbol(file_path, 15, 8) # create_user method
|
||||
assert containing_symbol is not None
|
||||
assert containing_symbol["name"] == "create_user"
|
||||
|
||||
# Step 2: Get the defining symbol for the same position
|
||||
# This should be the same method
|
||||
defining_symbol = language_server.request_defining_symbol(file_path, 15, 8)
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol["name"] == "create_user"
|
||||
|
||||
# Step 3: Verify that they refer to the same symbol
|
||||
assert defining_symbol["kind"] == containing_symbol["kind"]
|
||||
assert defining_symbol["location"]["uri"] == containing_symbol["location"]["uri"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
repo_structure = language_server.request_full_symbol_tree()
|
||||
assert len(repo_structure) == 1
|
||||
# Assert that the root symbol is the test_repo directory
|
||||
assert repo_structure[0]["name"] == "test_repo"
|
||||
assert repo_structure[0]["kind"] == SymbolKind.Package
|
||||
assert "children" in repo_structure[0]
|
||||
# Assert that the children are the top-level packages
|
||||
child_names = {child["name"] for child in repo_structure[0]["children"]}
|
||||
child_kinds = {child["kind"] for child in repo_structure[0]["children"]}
|
||||
assert child_names == {"test_repo", "custom_test", "examples", "scripts"}
|
||||
assert child_kinds == {SymbolKind.Package}
|
||||
examples_package = next(child for child in repo_structure[0]["children"] if child["name"] == "examples")
|
||||
# assert that children are __init__ and user_management
|
||||
assert {child["name"] for child in examples_package["children"]} == {"__init__", "user_management"}
|
||||
assert {child["kind"] for child in examples_package["children"]} == {SymbolKind.File}
|
||||
|
||||
# assert that tree of user_management node is same as retrieved directly
|
||||
user_management_node = next(child for child in examples_package["children"] if child["name"] == "user_management")
|
||||
if "location" in user_management_node and "relativePath" in user_management_node["location"]:
|
||||
user_management_rel_path = user_management_node["location"]["relativePath"]
|
||||
assert user_management_rel_path == os.path.join("examples", "user_management.py")
|
||||
_, user_management_roots = language_server.request_document_symbols(
|
||||
os.path.join("examples", "user_management.py")
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that the symbol tree structure is correctly built."""
|
||||
# Get all symbols in the test file
|
||||
examples_package_roots = language_server.request_full_symbol_tree(within_relative_path="examples")
|
||||
assert len(examples_package_roots) == 1
|
||||
examples_package = examples_package_roots[0]
|
||||
assert examples_package["name"] == "examples"
|
||||
assert examples_package["kind"] == SymbolKind.Package
|
||||
# assert that children are __init__ and user_management
|
||||
assert {child["name"] for child in examples_package["children"]} == {"__init__", "user_management"}
|
||||
assert {child["kind"] for child in examples_package["children"]} == {SymbolKind.File}
|
||||
|
||||
# assert that tree of user_management node is same as retrieved directly
|
||||
user_management_node = next(child for child in examples_package["children"] if child["name"] == "user_management")
|
||||
if "location" in user_management_node and "relativePath" in user_management_node["location"]:
|
||||
user_management_rel_path = user_management_node["location"]["relativePath"]
|
||||
assert user_management_rel_path == os.path.join("examples", "user_management.py")
|
||||
_, user_management_roots = language_server.request_document_symbols(
|
||||
os.path.join("examples", "user_management.py")
|
||||
).get_all_symbols_and_roots()
|
||||
assert user_management_roots == user_management_node["children"]
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that request_dir_overview returns correct symbol information for files in a directory."""
|
||||
# Get overview of the examples directory
|
||||
overview = language_server.request_dir_overview("test_repo")
|
||||
|
||||
# Verify that we have entries for both files
|
||||
assert os.path.join("test_repo", "nested.py") in overview
|
||||
|
||||
# Get the symbols for user_management.py
|
||||
services_symbols = overview[os.path.join("test_repo", "services.py")]
|
||||
assert len(services_symbols) > 0
|
||||
|
||||
# Check for specific symbols from services.py
|
||||
expected_symbols = {
|
||||
"UserService",
|
||||
"ItemService",
|
||||
"create_service_container",
|
||||
"user_var_str",
|
||||
"user_service",
|
||||
}
|
||||
retrieved_symbols = {symbol["name"] for symbol in services_symbols if "name" in symbol}
|
||||
assert expected_symbols.issubset(retrieved_symbols)
|
||||
|
||||
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
|
||||
def test_request_document_overview(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Test that request_document_overview returns correct symbol information for a file."""
|
||||
# Get overview of the user_management.py file
|
||||
overview = language_server.request_document_overview(os.path.join("examples", "user_management.py"))
|
||||
|
||||
# Verify that we have entries for both files
|
||||
symbol_names = {LanguageServerSymbol(s_info).name for s_info in overview}
|
||||
assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names)
|
||||
Reference in New Issue
Block a user