Skip to main content
Glama
test_ruby_symbol_retrieval.py33.9 kB
""" Tests for the Ruby language server symbol-related functionality. These tests focus on the following methods: - request_containing_symbol - request_referencing_symbols - request_defining_symbol - request_document_symbols integration """ import os import pytest from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language from solidlsp.ls_types import SymbolKind pytestmark = pytest.mark.ruby class TestRubyLanguageServerSymbols: """Test the Ruby language server's symbol-related functionality.""" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol for a method.""" # Test for a position inside the create_user method file_path = os.path.join("services.rb") # Look for a position inside the create_user method body containing_symbol = language_server.request_containing_symbol(file_path, 11, 10, include_body=True) # Verify that we found the containing symbol assert containing_symbol is not None, "Should find containing symbol for method position" assert containing_symbol["name"] == "create_user", f"Expected 'create_user', got '{containing_symbol['name']}'" assert ( containing_symbol["kind"] == SymbolKind.Method.value ), f"Expected Method kind ({SymbolKind.Method.value}), got {containing_symbol['kind']}" # Verify location information assert "location" in containing_symbol, "Containing symbol should have location information" location = containing_symbol["location"] assert "range" in location, "Location should contain range information" assert "start" in location["range"], "Range should have start position" assert "end" in location["range"], "Range should have end position" # Verify container information if "containerName" in containing_symbol: assert containing_symbol["containerName"] in [ "Services::UserService", "UserService", ], f"Expected UserService container, got '{containing_symbol['containerName']}'" # Verify body content if available if "body" in containing_symbol: body = containing_symbol["body"] assert "def create_user" in body, "Method body should contain method definition" assert len(body.strip()) > 0, "Method body should not be empty" @pytest.mark.parametrize("language_server", [Language.RUBY], 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("services.rb") # Line around the class definition containing_symbol = language_server.request_containing_symbol(file_path, 5, 5) # Verify that we found the containing symbol assert containing_symbol is not None, "Should find containing symbol for class position" assert containing_symbol["name"] == "UserService", f"Expected 'UserService', got '{containing_symbol['name']}'" assert ( containing_symbol["kind"] == SymbolKind.Class.value ), f"Expected Class kind ({SymbolKind.Class.value}), got {containing_symbol['kind']}" # Verify location information exists assert "location" in containing_symbol, "Class symbol should have location information" location = containing_symbol["location"] assert "range" in location, "Location should contain range" assert "start" in location["range"] and "end" in location["range"], "Range should have start and end positions" # Verify the class is properly nested in the Services module if "containerName" in containing_symbol: assert ( containing_symbol["containerName"] == "Services" ), f"Expected 'Services' as container, got '{containing_symbol['containerName']}'" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol for a module context.""" # Test that we can find the Services module in document symbols file_path = os.path.join("services.rb") symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() # Verify Services module appears in document symbols services_module = None for symbol in symbols: if symbol.get("name") == "Services" and symbol.get("kind") == SymbolKind.Module: services_module = symbol break assert services_module is not None, "Services module not found in document symbols" # Test that UserService class has Services as container # Position inside UserService class containing_symbol = language_server.request_containing_symbol(file_path, 4, 8) assert containing_symbol is not None assert containing_symbol["name"] == "UserService" assert containing_symbol["kind"] == SymbolKind.Class # Verify the module context is preserved in containerName (if supported by the language server) # ruby-lsp doesn't provide containerName, but Solargraph does if "containerName" in containing_symbol: assert containing_symbol.get("containerName") == "Services" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_containing_symbol_nested_class(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol with nested classes.""" # Test for a position inside a nested class method file_path = os.path.join("nested.rb") # Position inside NestedClass.find_me method containing_symbol = language_server.request_containing_symbol(file_path, 20, 10) # Verify that we found the innermost containing symbol assert containing_symbol is not None assert containing_symbol["name"] == "find_me" assert containing_symbol["kind"] == SymbolKind.Method @pytest.mark.parametrize("language_server", [Language.RUBY], 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 class/method (e.g., in requires) file_path = os.path.join("services.rb") # Line 1 is a require statement, not inside any class or method containing_symbol = language_server.request_containing_symbol(file_path, 1, 5) # Should return None or an empty dictionary assert containing_symbol is None or containing_symbol == {} @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_referencing_symbols_method(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a method.""" # Test referencing symbols for create_user method file_path = os.path.join("services.rb") # Line containing the create_user method definition symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() create_user_symbol = None # Find create_user method in the document symbols (Ruby returns flat list) for symbol in symbols: if symbol.get("name") == "create_user": create_user_symbol = symbol break if not create_user_symbol or "selectionRange" not in create_user_symbol: pytest.skip("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"]) ] # We might not have references in our simple test setup, so just verify structure for symbol in ref_symbols: assert "name" in symbol assert "kind" in symbol @pytest.mark.parametrize("language_server", [Language.RUBY], 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("models.rb") # Find User class in document symbols symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() user_symbol = None for symbol in symbols: if symbol.get("name") == "User": user_symbol = symbol break if not user_symbol or "selectionRange" not in user_symbol: pytest.skip("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"]) ] # Verify 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", [Language.RUBY], 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 variable in a method file_path = os.path.join("services.rb") # Look for @users variable usage defining_symbol = language_server.request_defining_symbol(file_path, 12, 10) # This test might fail if the language server doesn't support it well if defining_symbol is not None: assert "name" in defining_symbol assert "kind" in defining_symbol @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_defining_symbol_class(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a class reference.""" # Test finding the definition of the User class used in services file_path = os.path.join("services.rb") # Line that references User class defining_symbol = language_server.request_defining_symbol(file_path, 11, 15) # This might not work perfectly in all Ruby language servers if defining_symbol is not None: assert "name" in defining_symbol # The name might be "User" or the method that contains it assert defining_symbol.get("name") is not None @pytest.mark.parametrize("language_server", [Language.RUBY], 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("services.rb") # Line 3 is likely a blank line or comment defining_symbol = language_server.request_defining_symbol(file_path, 3, 0) # Should return None for positions with no symbol assert defining_symbol is None or defining_symbol == {} @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_defining_symbol_nested_class(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for nested class access.""" # Test finding definition of NestedClass file_path = os.path.join("nested.rb") # Position where NestedClass is referenced defining_symbol = language_server.request_defining_symbol(file_path, 44, 25) # This is challenging for many language servers if defining_symbol is not None: assert "name" in defining_symbol assert defining_symbol.get("name") in ["NestedClass", "OuterClass"] @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None: """Test the integration between different symbol-related methods.""" file_path = os.path.join("models.rb") # Step 1: Find a method we know exists containing_symbol = language_server.request_containing_symbol(file_path, 8, 5) # inside initialize method if containing_symbol is not None: assert containing_symbol["name"] == "initialize" # Step 2: Get the defining symbol for the same position defining_symbol = language_server.request_defining_symbol(file_path, 8, 5) if defining_symbol is not None: assert defining_symbol["name"] == "initialize" # Step 3: Verify that they refer to the same symbol type assert defining_symbol["kind"] == containing_symbol["kind"] @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_symbol_tree_structure_basic(self, language_server: SolidLanguageServer) -> None: """Test that the symbol tree structure includes Ruby symbols.""" # Get all symbols in the test repository repo_structure = language_server.request_full_symbol_tree() assert len(repo_structure) >= 1 # Look for our Ruby files in the structure found_ruby_files = False for root in repo_structure: if "children" in root: for child in root["children"]: if child.get("name") in ["models", "services", "nested"]: found_ruby_files = True break # We should find at least some Ruby files in the symbol tree assert found_ruby_files, "Ruby files not found in symbol tree" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_document_symbols_detailed(self, language_server: SolidLanguageServer) -> None: """Test document symbols for detailed Ruby file structure.""" file_path = os.path.join("models.rb") symbols, roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() # Verify we have symbols assert len(symbols) > 0 or len(roots) > 0 # Look for expected class names symbol_names = set() all_symbols = symbols if symbols else roots for symbol in all_symbols: symbol_names.add(symbol.get("name")) # Add children names too if "children" in symbol: for child in symbol["children"]: symbol_names.add(child.get("name")) # We should find at least some of our defined classes/methods expected_symbols = {"User", "Item", "Order", "ItemHelpers"} found_symbols = symbol_names.intersection(expected_symbols) assert len(found_symbols) > 0, f"Expected symbols not found. Found: {symbol_names}" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_module_and_class_hierarchy(self, language_server: SolidLanguageServer) -> None: """Test symbol detection for modules and nested class hierarchies.""" file_path = os.path.join("nested.rb") symbols, roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() # Verify we can detect the nested structure assert len(symbols) > 0 or len(roots) > 0 # Look for OuterClass and its nested elements symbol_names = set() all_symbols = symbols if symbols else roots for symbol in all_symbols: symbol_names.add(symbol.get("name")) if "children" in symbol: for child in symbol["children"]: symbol_names.add(child.get("name")) # Check deeply nested too if "children" in child: for grandchild in child["children"]: symbol_names.add(grandchild.get("name")) # Should find the outer class at minimum assert "OuterClass" in symbol_names, f"OuterClass not found in symbols: {symbol_names}" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_references_to_variables(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a variable with detailed verification.""" file_path = os.path.join("variables.rb") # Test references to @status variable in DataContainer class (around line 9) ref_symbols = [ref.symbol for ref in language_server.request_referencing_symbols(file_path, 8, 4)] if len(ref_symbols) > 0: # Verify we have references assert len(ref_symbols) > 0, "Should find references to @status variable" # Check that we have location information ref_with_locations = [ref for ref in ref_symbols if "location" in ref and "range" in ref["location"]] assert len(ref_with_locations) > 0, "References should include location information" # Verify line numbers are reasonable (should be within the file) ref_lines = [ref["location"]["range"]["start"]["line"] for ref in ref_with_locations] assert all(line >= 0 for line in ref_lines), "Reference lines should be valid" # Check for specific reference locations we expect # Lines where @status is modified/accessed expected_line_ranges = [(20, 40), (45, 70)] # Approximate ranges found_in_expected_range = any(any(start <= line <= end for start, end in expected_line_ranges) for line in ref_lines) assert found_in_expected_range, f"Expected references in ranges {expected_line_ranges}, found lines: {ref_lines}" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a method parameter.""" # Test referencing symbols for a method parameter in get_user method file_path = os.path.join("services.rb") # Find get_user method and test parameter references symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots() get_user_symbol = None for symbol in symbols: if symbol.get("name") == "get_user": get_user_symbol = symbol break if not get_user_symbol or "selectionRange" not in get_user_symbol: pytest.skip("get_user symbol or its selectionRange not found") # Test parameter reference within method body method_start_line = get_user_symbol["selectionRange"]["start"]["line"] ref_symbols = [ ref.symbol for ref in language_server.request_referencing_symbols(file_path, method_start_line + 1, 10) # Position within method body ] # Verify structure of referencing symbols for symbol in ref_symbols: assert "name" in symbol, "Symbol should have name" assert "kind" in symbol, "Symbol should have kind" if "location" in symbol and "range" in symbol["location"]: range_info = symbol["location"]["range"] assert "start" in range_info, "Range should have start" assert "end" in range_info, "Range should have end" # Verify line number is valid (references can be before method definition too) assert range_info["start"]["line"] >= 0, "Reference line should be valid" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None: """Test request_referencing_symbols for a position with no symbol.""" # Test for a position with no symbol (comment or blank line) file_path = os.path.join("services.rb") # Try multiple positions that should have no symbols test_positions = [(1, 0), (2, 0)] # Comment/require lines for line, char in test_positions: try: ref_symbols = [ref.symbol for ref in language_server.request_referencing_symbols(file_path, line, char)] # If we get here, make sure we got an empty result or minimal results if ref_symbols: # Some language servers might return minimal info, verify it's reasonable assert len(ref_symbols) <= 3, f"Expected few/no references at line {line}, got {len(ref_symbols)}" except Exception as e: # Some language servers throw exceptions for invalid positions, which is acceptable assert ( "symbol" in str(e).lower() or "position" in str(e).lower() or "reference" in str(e).lower() ), f"Exception should be related to symbol/position/reference issues, got: {e}" @pytest.mark.parametrize("language_server", [Language.RUBY], 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 test repo directory overview = language_server.request_dir_overview(".") # Verify that we have entries for our main files expected_files = ["services.rb", "models.rb", "variables.rb", "nested.rb"] found_files = [] for file_path in overview.keys(): for expected in expected_files: if expected in file_path: found_files.append(expected) break assert len(found_files) >= 2, f"Should find at least 2 expected files, found: {found_files}" # Test specific symbols from services.rb if it exists services_file_key = None for file_path in overview.keys(): if "services.rb" in file_path: services_file_key = file_path break if services_file_key: services_symbols = overview[services_file_key] assert len(services_symbols) > 0, "services.rb should have symbols" # Check for expected symbols with detailed verification symbol_names = [s[0] for s in services_symbols if isinstance(s, tuple) and len(s) > 0] if not symbol_names: # If not tuples, try different format symbol_names = [s.get("name") for s in services_symbols if hasattr(s, "get")] expected_symbols = ["Services", "UserService", "ItemService"] found_expected = [name for name in expected_symbols if name in symbol_names] assert len(found_expected) >= 1, f"Should find at least one expected symbol, found: {found_expected} in {symbol_names}" @pytest.mark.parametrize("language_server", [Language.RUBY], 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.rb file file_path = os.path.join("examples", "user_management.rb") overview = language_server.request_document_overview(file_path) # Verify that we have symbol information assert len(overview) > 0, "Document overview should contain symbols" # Look for expected symbols from the file symbol_names = set() for s_info in overview: if isinstance(s_info, tuple) and len(s_info) > 0: symbol_names.add(s_info[0]) elif hasattr(s_info, "get"): symbol_names.add(s_info.get("name")) elif isinstance(s_info, str): symbol_names.add(s_info) # We should find some of our defined classes/methods expected_symbols = {"UserStats", "UserManager", "process_user_data", "main"} found_symbols = symbol_names.intersection(expected_symbols) assert len(found_symbols) > 0, f"Expected to find some symbols from {expected_symbols}, found: {symbol_names}" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol where the target is a variable.""" # Test for a position inside a variable definition or usage file_path = os.path.join("variables.rb") # Position around a variable assignment (e.g., @status = "pending") containing_symbol = language_server.request_containing_symbol(file_path, 10, 5) # Verify that we found a containing symbol (likely the method or class) if containing_symbol is not None: assert "name" in containing_symbol, "Containing symbol should have a name" assert "kind" in containing_symbol, "Containing symbol should have a kind" # The containing symbol should be a method, class, or similar construct expected_kinds = [SymbolKind.Method, SymbolKind.Class, SymbolKind.Function, SymbolKind.Constructor] assert containing_symbol["kind"] in [ k.value for k in expected_kinds ], f"Expected containing symbol to be method/class/function, got kind: {containing_symbol['kind']}" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None: """Test request_containing_symbol for a function (not method).""" # Test for a position inside a standalone function file_path = os.path.join("variables.rb") # Position inside the demonstrate_variable_usage function containing_symbol = language_server.request_containing_symbol(file_path, 100, 10) if containing_symbol is not None: assert containing_symbol["name"] in [ "demonstrate_variable_usage", "main", ], f"Expected function name, got: {containing_symbol['name']}" assert containing_symbol["kind"] in [ SymbolKind.Function.value, SymbolKind.Method.value, ], f"Expected function or method kind, got: {containing_symbol['kind']}" @pytest.mark.parametrize("language_server", [Language.RUBY], 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("services.rb") # Position inside create_user method within UserService class containing_symbol = language_server.request_containing_symbol(file_path, 12, 15) # 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 # Verify the container context is preserved if "containerName" in containing_symbol: assert "UserService" in containing_symbol["containerName"] @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None: """Test that the symbol tree structure correctly handles subdirectories.""" # Get symbols within the examples subdirectory examples_structure = language_server.request_full_symbol_tree(within_relative_path="examples") if len(examples_structure) > 0: # Should find the examples directory structure assert len(examples_structure) >= 1, "Should find examples directory structure" # Look for the user_management file in the structure found_user_management = False for root in examples_structure: if "children" in root: for child in root["children"]: if "user_management" in child.get("name", ""): found_user_management = True # Verify the structure includes symbol information if "children" in child: child_names = [c.get("name") for c in child["children"]] expected_names = ["UserStats", "UserManager", "process_user_data"] found_expected = [name for name in expected_names if name in child_names] assert ( len(found_expected) > 0 ), f"Should find symbols in user_management, expected {expected_names}, found {child_names}" break if not found_user_management: pytest.skip("user_management file not found in examples subdirectory structure") @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for an imported/required class.""" # Test finding the definition of a class used from another file file_path = os.path.join("examples", "user_management.rb") # Position where Services::UserService is referenced defining_symbol = language_server.request_defining_symbol(file_path, 25, 20) # This might not work perfectly in all Ruby language servers due to require complexity if defining_symbol is not None: assert "name" in defining_symbol # The defining symbol should relate to UserService or Services # The defining symbol should relate to UserService, Services, or the containing class # Different language servers may resolve this differently expected_names = ["UserService", "Services", "new", "UserManager"] assert defining_symbol.get("name") in expected_names, f"Expected one of {expected_names}, got: {defining_symbol.get('name')}" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a method call.""" # Test finding the definition of a method being called file_path = os.path.join("examples", "user_management.rb") # Position at a method call like create_user defining_symbol = language_server.request_defining_symbol(file_path, 30, 15) # Verify that we can find method definitions if defining_symbol is not None: assert "name" in defining_symbol assert "kind" in defining_symbol # Should be a method or constructor assert defining_symbol.get("kind") in [SymbolKind.Method.value, SymbolKind.Constructor.value, SymbolKind.Function.value] @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None: """Test request_defining_symbol for a nested function or block.""" # Test finding definition within nested contexts file_path = os.path.join("nested.rb") # Position inside or referencing nested functionality defining_symbol = language_server.request_defining_symbol(file_path, 15, 10) # This is challenging for many language servers if defining_symbol is not None: assert "name" in defining_symbol assert "kind" in defining_symbol # Could be method, function, or variable depending on implementation valid_kinds = [SymbolKind.Method.value, SymbolKind.Function.value, SymbolKind.Variable.value, SymbolKind.Class.value] assert defining_symbol.get("kind") in valid_kinds @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) def test_containing_symbol_of_var_is_file(self, language_server: SolidLanguageServer) -> None: """Test that the containing symbol of a file-level variable is handled appropriately.""" # Test behavior with file-level variables or constants file_path = os.path.join("variables.rb") # Position at file-level variable/constant containing_symbol = language_server.request_containing_symbol(file_path, 5, 5) # Different language servers handle file-level symbols differently # Some return None, others return file-level containers if containing_symbol is not None: # If we get a symbol, verify its structure assert "name" in containing_symbol assert "kind" in containing_symbol

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