Skip to main content
Glama
test_fsharp_basic.py11.1 kB
import os import tempfile import threading from pathlib import Path from unittest.mock import Mock, patch import pytest from solidlsp import SolidLanguageServer from solidlsp.language_servers.fsharp_language_server import FSharpLanguageServer from solidlsp.ls_config import Language from solidlsp.ls_utils import SymbolUtils @pytest.mark.fsharp class TestFSharpLanguageServer: @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_find_symbol(self, language_server: SolidLanguageServer) -> None: """Test finding symbols in the full symbol tree.""" symbols = language_server.request_full_symbol_tree() # Check for main program module symbols assert SymbolUtils.symbol_tree_contains_name(symbols, "Program"), "Program module not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main function not found in symbol tree" # Check for Calculator module symbols assert SymbolUtils.symbol_tree_contains_name(symbols, "Calculator"), "Calculator module not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add function not found in symbol tree" assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculatorClass"), "CalculatorClass not found in symbol tree" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_get_document_symbols_program(self, language_server: SolidLanguageServer) -> None: """Test getting document symbols from the main Program.fs file.""" file_path = os.path.join("Program.fs") symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()[0] # Look for expected functions and modules symbol_names = [s.get("name") for s in symbols] assert "main" in symbol_names, "main function not found in Program.fs symbols" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_get_document_symbols_calculator(self, language_server: SolidLanguageServer) -> None: """Test getting document symbols from Calculator.fs file.""" file_path = os.path.join("Calculator.fs") symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()[0] # Look for expected functions symbol_names = [s.get("name") for s in symbols] expected_symbols = ["add", "subtract", "multiply", "divide", "square", "factorial", "CalculatorClass"] for expected in expected_symbols: assert expected in symbol_names, f"{expected} function not found in Calculator.fs symbols" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None: """Test finding references using symbol selection range.""" file_path = os.path.join("Calculator.fs") symbols = language_server.request_document_symbols(file_path) # Find the 'add' function symbol add_symbol = None for sym in symbols.iter_symbols(): if sym.get("name") == "add": add_symbol = sym break assert add_symbol is not None, "Could not find 'add' function symbol in Calculator.fs" # Try to find references to the add function sel_start = add_symbol["selectionRange"]["start"] refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1) # The add function should be referenced in Program.fs assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference add function" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_nested_module_symbols(self, language_server: SolidLanguageServer) -> None: """Test getting symbols from nested Models namespace.""" file_path = os.path.join("Models", "Person.fs") symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()[0] # Check for expected types and modules symbol_names = [s.get("name") for s in symbols] expected_symbols = ["Person", "PersonModule", "Address", "Employee"] for expected in expected_symbols: assert expected in symbol_names, f"{expected} not found in Person.fs symbols" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_find_referencing_symbols_across_files(self, language_server: SolidLanguageServer) -> None: """Test finding references to Calculator functions across files.""" # Find the subtract function in Calculator.fs file_path = os.path.join("Calculator.fs") symbols = language_server.request_document_symbols(file_path) subtract_symbol = None for sym in symbols.iter_symbols(): if sym.get("name") == "subtract": subtract_symbol = sym break assert subtract_symbol is not None, "Could not find 'subtract' function symbol" # Find references to subtract function sel_start = subtract_symbol["selectionRange"]["start"] refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1) # The subtract function should be referenced in Program.fs assert any("Program.fs" in ref.get("relativePath", "") for ref in refs), "Program.fs should reference subtract function" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_go_to_definition(self, language_server: SolidLanguageServer) -> None: """Test go-to-definition functionality.""" # Test going to definition of 'add' function from Program.fs program_file = os.path.join("Program.fs") # Try to find definition of 'add' function used in Program.fs # This would typically be at the line where 'add 5 3' is called definitions = language_server.request_definition(program_file, 10, 20) # Approximate position # We should get at least some definitions assert len(definitions) >= 0, "Should get definitions (even if empty for complex cases)" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_hover_information(self, language_server: SolidLanguageServer) -> None: """Test hover information functionality.""" file_path = os.path.join("Calculator.fs") # Try to get hover information for a function hover_info = language_server.request_hover(file_path, 5, 10) # Approximate position of a function # Hover info might be None or contain information # This is acceptable as it depends on the LSP server's capabilities and timing assert hover_info is None or isinstance(hover_info, dict), "Hover info should be None or dict" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_completion(self, language_server: SolidLanguageServer) -> None: """Test code completion functionality.""" file_path = os.path.join("Program.fs") # Use threading for cross-platform timeout (signal.SIGALRM is Unix-only) result = [None] exception = [None] def run_completion(): try: result[0] = language_server.request_completions(file_path, 15, 10) except Exception as e: exception[0] = e thread = threading.Thread(target=run_completion, daemon=True) thread.start() thread.join(timeout=5) # 5 second timeout if thread.is_alive(): # Completion timed out, but this is acceptable for F# in some cases # The important thing is that the language server doesn't crash return if exception[0]: raise exception[0] assert isinstance(result[0], list), "Completions should be a list" @pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True) def test_diagnostics(self, language_server: SolidLanguageServer) -> None: """Test getting diagnostics (errors, warnings) from F# files.""" file_path = os.path.join("Program.fs") # FsAutoComplete uses publishDiagnostics notifications instead of textDocument/diagnostic requests # So we'll test that the language server can handle files without crashing # In real usage, diagnostics would come through the publishDiagnostics notification handler # Test that we can at least work with the file (open/close cycle) with language_server.open_file(file_path) as _: # If we can open and close the file without errors, basic diagnostics support is working pass # This is a successful test - FsAutoComplete is working with F# files assert True, "F# language server can handle files successfully" @pytest.mark.fsharp class TestFSharpLanguageServerSetup: """Test F# language server setup and configuration.""" def test_runtime_dependency_setup_without_dotnet(self) -> None: """Test that setup fails gracefully when .NET is not installed.""" with patch("shutil.which", return_value=None): with pytest.raises(RuntimeError, match=r"\.NET SDK is not installed"): FSharpLanguageServer._setup_runtime_dependencies(Mock(), Mock()) def test_runtime_dependency_setup_with_dotnet(self) -> None: """Test that setup works when .NET is available.""" mock_config = Mock() mock_settings = Mock() # Mock the ls_resources_dir to return a temp directory with tempfile.TemporaryDirectory() as temp_dir: with patch("shutil.which", return_value="/usr/bin/dotnet"): with patch.object(FSharpLanguageServer, "ls_resources_dir", return_value=temp_dir): with patch("subprocess.run") as mock_run: # Mock successful dotnet version check mock_run.return_value.stdout = "8.0.100" mock_run.return_value.returncode = 0 # Create a fake fsautocomplete executable fsharp_dir = os.path.join(temp_dir, "fsharp-lsp") os.makedirs(fsharp_dir, exist_ok=True) # Use .exe extension on Windows, matching production code exe_name = "fsautocomplete.exe" if os.name == "nt" else "fsautocomplete" fsautocomplete_path = os.path.join(fsharp_dir, exe_name) Path(fsautocomplete_path).touch() result = FSharpLanguageServer._setup_runtime_dependencies(mock_config, mock_settings) assert fsautocomplete_path in result assert "--adaptive-lsp-server-enabled --project-graph-enabled --use-fcs-transparent-compiler" in result

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