Skip to main content
Glama
test_haskell_basic.py10.7 kB
""" 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 sys import pytest from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import Language @pytest.mark.haskell @pytest.mark.skipif(sys.platform == "win32", reason="HLS not installed on Windows CI") 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}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/oraios/serena'

If you have feedback or need assistance with the MCP directory API, please join our Discord server