c6af9e284a
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
253 lines
12 KiB
Python
253 lines
12 KiB
Python
"""
|
|
Rigorous tests for Haskell Language Server integration with Serena.
|
|
|
|
Tests prove that Serena's symbol tools can:
|
|
1. Discover all expected symbols with precise matching
|
|
2. Track cross-file references accurately
|
|
3. Identify data type structures and record fields
|
|
4. Navigate between definitions and usages
|
|
|
|
Test Repository Structure:
|
|
- src/Calculator.hs: Calculator data type, arithmetic functions (add, subtract, multiply, divide, calculate)
|
|
- src/Helper.hs: Helper functions (validateNumber, isPositive, isNegative, absolute)
|
|
- app/Main.hs: Main entry point using Calculator and Helper modules
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from solidlsp.ls import SolidLanguageServer
|
|
from solidlsp.ls_config import Language
|
|
from solidlsp.ls_types import SymbolKind
|
|
from test.conftest import language_tests_enabled
|
|
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
|
|
from test.solidlsp.util.diagnostics import assert_file_diagnostics
|
|
|
|
pytestmark = pytest.mark.skipif(not language_tests_enabled(Language.HASKELL), reason="Haskell tests are disabled (HLS not available)")
|
|
|
|
|
|
@pytest.mark.haskell
|
|
class TestHaskellLanguageServer:
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_calculator_module_symbols(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test precise symbol discovery in Calculator.hs.
|
|
|
|
Verifies that Serena can identify:
|
|
- Data type definition (Calculator with record fields)
|
|
- All exported functions with correct names
|
|
- Module structure
|
|
"""
|
|
all_symbols, _ = language_server.request_document_symbols("src/Calculator.hs").get_all_symbols_and_roots()
|
|
symbol_names = {s["name"] for s in all_symbols}
|
|
|
|
# Verify exact set of expected top-level symbols
|
|
expected_symbols = {
|
|
"Calculator", # Data type
|
|
"add", # Function: Int -> Int -> Int
|
|
"subtract", # Function: Int -> Int -> Int
|
|
"multiply", # Function: Int -> Int -> Int
|
|
"divide", # Function: Int -> Int -> Maybe Int
|
|
"calculate", # Function: Calculator -> String -> Int -> Int -> Maybe Int
|
|
}
|
|
|
|
# Verify all expected symbols are present
|
|
missing = expected_symbols - symbol_names
|
|
assert not missing, f"Missing expected symbols in Calculator.hs: {missing}"
|
|
|
|
# Verify Calculator data type exists
|
|
calculator_symbol = next((s for s in all_symbols if s["name"] == "Calculator"), None)
|
|
assert calculator_symbol is not None, "Calculator data type not found"
|
|
|
|
# The Calculator should be identified as a data type
|
|
# HLS may use different SymbolKind values (1=File, 5=Class, 23=Struct)
|
|
assert calculator_symbol["kind"] in [
|
|
1,
|
|
5,
|
|
23,
|
|
], f"Calculator should be a data type (kind 1, 5, or 23), got kind {calculator_symbol['kind']}"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_helper_module_symbols(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test precise symbol discovery in Helper.hs.
|
|
|
|
Verifies Serena identifies all helper functions that are imported
|
|
and used by Calculator module.
|
|
"""
|
|
all_symbols, _ = language_server.request_document_symbols("src/Helper.hs").get_all_symbols_and_roots()
|
|
symbol_names = {s["name"] for s in all_symbols}
|
|
|
|
# Verify expected helper functions (module name may also appear)
|
|
expected_symbols = {
|
|
"validateNumber", # Function used by Calculator.add and Calculator.subtract
|
|
"isPositive", # Predicate function
|
|
"isNegative", # Predicate function used by absolute
|
|
"absolute", # Function that uses isNegative
|
|
}
|
|
|
|
# All expected symbols should be present (module name is optional)
|
|
missing = expected_symbols - symbol_names
|
|
assert not missing, f"Missing expected symbols in Helper.hs: {missing}"
|
|
|
|
# Verify no unexpected symbols beyond the module name
|
|
extra = symbol_names - expected_symbols - {"Helper"}
|
|
assert not extra, f"Unexpected symbols in Helper.hs: {extra}"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_main_module_imports(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test that Main.hs properly references both Calculator and Helper modules.
|
|
|
|
Verifies Serena can identify cross-module dependencies.
|
|
"""
|
|
all_symbols, _ = language_server.request_document_symbols("app/Main.hs").get_all_symbols_and_roots()
|
|
symbol_names = {s["name"] for s in all_symbols}
|
|
|
|
# Main.hs should have the main function
|
|
assert "main" in symbol_names, "Main.hs should contain 'main' function"
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_cross_file_references_validateNumber(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test cross-file reference tracking for validateNumber function.
|
|
|
|
validateNumber is defined in Helper.hs:9 and used in:
|
|
- Calculator.hs:21 (in add function)
|
|
- Calculator.hs:25 (in subtract function)
|
|
|
|
This proves Serena can track function usage across module boundaries.
|
|
"""
|
|
# Get references to validateNumber (defined at line 9, 0-indexed = line 8)
|
|
references = language_server.request_references("src/Helper.hs", line=8, column=0)
|
|
|
|
# Should find at least: definition in Helper.hs + 2 usages in Calculator.hs
|
|
assert len(references) >= 2, f"Expected at least 2 references to validateNumber (used in add and subtract), got {len(references)}"
|
|
|
|
# Verify we have references in Calculator.hs
|
|
reference_paths = [ref["relativePath"] for ref in references]
|
|
calculator_refs = [path for path in reference_paths if "Calculator.hs" in path]
|
|
|
|
assert len(calculator_refs) >= 2, (
|
|
f"Expected at least 2 references in Calculator.hs (add and subtract functions), "
|
|
f"got {len(calculator_refs)} references in Calculator.hs"
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_within_file_references_isNegative(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test within-file reference tracking for isNegative function.
|
|
|
|
isNegative is defined in Helper.hs:17 and used in Helper.hs:22 (absolute function).
|
|
This proves Serena can track intra-module function calls.
|
|
"""
|
|
# isNegative defined at line 17 (0-indexed = line 16)
|
|
references = language_server.request_references("src/Helper.hs", line=16, column=0)
|
|
|
|
# Should find: definition + usage in absolute function
|
|
assert len(references) >= 1, f"Expected at least 1 reference to isNegative (used in absolute), got {len(references)}"
|
|
|
|
# All references should be in Helper.hs
|
|
reference_paths = [ref["relativePath"] for ref in references]
|
|
assert all("Helper.hs" in path for path in reference_paths), (
|
|
f"All isNegative references should be in Helper.hs, got: {reference_paths}"
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_function_references_from_main(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test that functions used in Main.hs can be traced back to their definitions.
|
|
|
|
Main.hs:12 calls 'add' from Calculator module.
|
|
Main.hs:25 calls 'isPositive' from Helper module.
|
|
Main.hs:26 calls 'absolute' from Helper module.
|
|
|
|
This proves Serena can track cross-module function calls from executable code.
|
|
"""
|
|
# Test 'add' function references (defined in Calculator.hs:20, 0-indexed = line 19)
|
|
add_refs = language_server.request_references("src/Calculator.hs", line=19, column=0)
|
|
|
|
# Should find references in Main.hs and possibly Calculator.hs (calculate function uses it)
|
|
assert len(add_refs) >= 1, f"Expected at least 1 reference to 'add', got {len(add_refs)}"
|
|
|
|
add_ref_paths = [ref["relativePath"] for ref in add_refs]
|
|
# Should have at least one reference in Main.hs or Calculator.hs
|
|
assert any("Main.hs" in path or "Calculator.hs" in path for path in add_ref_paths), (
|
|
f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}"
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test that multiply function usage is tracked within Calculator module.
|
|
|
|
multiply is defined in Calculator.hs:28 and used in:
|
|
- Calculator.hs:41 (in calculate function via pattern matching)
|
|
- Main.hs:20 (via calculate call with "multiply" operator)
|
|
|
|
This proves Serena can track function references even when called indirectly.
|
|
"""
|
|
# multiply defined at line 28 (0-indexed = line 27)
|
|
multiply_refs = language_server.request_references("src/Calculator.hs", line=27, column=0)
|
|
|
|
# Should find at least the usage in calculate function
|
|
assert len(multiply_refs) >= 1, f"Expected at least 1 reference to 'multiply', got {len(multiply_refs)}"
|
|
|
|
# Should have reference in Calculator.hs (calculate function)
|
|
multiply_ref_paths = [ref["relativePath"] for ref in multiply_refs]
|
|
assert any("Calculator.hs" in path for path in multiply_ref_paths), (
|
|
f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}"
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_data_type_constructor_references(self, language_server: SolidLanguageServer):
|
|
"""
|
|
Test that Calculator data type constructor usage is tracked.
|
|
|
|
Calculator is defined in Calculator.hs:14 and used in:
|
|
- Main.hs:8 (constructor call: Calculator "TestCalc" 1)
|
|
- Calculator.hs:37 (type signature for calculate function)
|
|
|
|
This proves Serena can track data type constructor references.
|
|
"""
|
|
# Calculator data type defined at line 14 (0-indexed = line 13)
|
|
calculator_refs = language_server.request_references("src/Calculator.hs", line=13, column=5)
|
|
|
|
# Should find usage in Main.hs
|
|
assert len(calculator_refs) >= 1, f"Expected at least 1 reference to Calculator constructor, got {len(calculator_refs)}"
|
|
|
|
# Should have at least one reference in Main.hs or Calculator.hs
|
|
calc_ref_paths = [ref["relativePath"] for ref in calculator_refs]
|
|
assert any("Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths), (
|
|
f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
|
|
)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], 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 s["kind"] == SymbolKind.Module:
|
|
continue
|
|
if has_malformed_name(s):
|
|
malformed_symbols.append(s)
|
|
if malformed_symbols:
|
|
diagnostics = [
|
|
{
|
|
"formatted": format_symbol_for_assert(sym),
|
|
"name": sym["name"],
|
|
"kind": SymbolKind(sym["kind"]).name,
|
|
"detail": sym.get("detail"),
|
|
}
|
|
for sym in malformed_symbols
|
|
]
|
|
pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)
|
|
|
|
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
|
|
def test_file_diagnostics(self, language_server: SolidLanguageServer) -> None:
|
|
assert_file_diagnostics(
|
|
language_server,
|
|
"src/DiagnosticsSample.hs",
|
|
(),
|
|
min_count=1,
|
|
)
|