mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
feat: Expand Ruby language server test coverage (#438)
This commit is contained in:
1 parent
f12547d55a
commit
50d5abc498
6 files changed
+1087
No files matched your search
@@ -0,0 +1,116 @@
|
||||
require '../services.rb'
|
||||
require '../models.rb'
|
||||
|
||||
class UserStats
|
||||
attr_reader :user_count, :active_users, :last_updated
|
||||
|
||||
def initialize
|
||||
@user_count = 0
|
||||
@active_users = 0
|
||||
@last_updated = Time.now
|
||||
end
|
||||
|
||||
def update_stats(total, active)
|
||||
@user_count = total
|
||||
@active_users = active
|
||||
@last_updated = Time.now
|
||||
end
|
||||
|
||||
def activity_ratio
|
||||
return 0.0 if @user_count == 0
|
||||
(@active_users.to_f / @user_count * 100).round(2)
|
||||
end
|
||||
|
||||
def formatted_stats
|
||||
"Users: #{@user_count}, Active: #{@active_users} (#{activity_ratio}%)"
|
||||
end
|
||||
end
|
||||
|
||||
class UserManager
|
||||
def initialize
|
||||
@service = Services::UserService.new
|
||||
@stats = UserStats.new
|
||||
end
|
||||
|
||||
def create_user_with_tracking(id, name, email = nil)
|
||||
user = @service.create_user(id, name)
|
||||
user.email = email if email
|
||||
|
||||
update_statistics
|
||||
notify_user_created(user)
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def get_user_details(id)
|
||||
user = @service.get_user(id)
|
||||
return nil unless user
|
||||
|
||||
{
|
||||
user_info: user.full_info,
|
||||
created_at: Time.now,
|
||||
stats: @stats.formatted_stats
|
||||
}
|
||||
end
|
||||
|
||||
def bulk_create_users(user_data_list)
|
||||
created_users = []
|
||||
|
||||
user_data_list.each do |data|
|
||||
user = create_user_with_tracking(data[:id], data[:name], data[:email])
|
||||
created_users << user
|
||||
end
|
||||
|
||||
created_users
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_statistics
|
||||
total_users = @service.users.length
|
||||
# For demo purposes, assume all users are active
|
||||
@stats.update_stats(total_users, total_users)
|
||||
end
|
||||
|
||||
def notify_user_created(user)
|
||||
puts "User created: #{user.name} (ID: #{user.id})"
|
||||
end
|
||||
end
|
||||
|
||||
def process_user_data(raw_data)
|
||||
processed = raw_data.map do |entry|
|
||||
{
|
||||
id: entry["id"] || entry[:id],
|
||||
name: entry["name"] || entry[:name],
|
||||
email: entry["email"] || entry[:email]
|
||||
}
|
||||
end
|
||||
|
||||
processed.reject { |entry| entry[:name].nil? || entry[:name].empty? }
|
||||
end
|
||||
|
||||
def main
|
||||
# Example usage
|
||||
manager = UserManager.new
|
||||
|
||||
sample_data = [
|
||||
{ id: 1, name: "Alice Johnson", email: "alice@example.com" },
|
||||
{ id: 2, name: "Bob Smith", email: "bob@example.com" },
|
||||
{ id: 3, name: "Charlie Brown" }
|
||||
]
|
||||
|
||||
users = manager.bulk_create_users(sample_data)
|
||||
|
||||
users.each do |user|
|
||||
details = manager.get_user_details(user.id)
|
||||
puts details[:user_info]
|
||||
end
|
||||
|
||||
puts "\nFinal statistics:"
|
||||
stats = UserStats.new
|
||||
stats.update_stats(users.length, users.length)
|
||||
puts stats.formatted_stats
|
||||
end
|
||||
|
||||
# Execute if this file is run directly
|
||||
main if __FILE__ == $0
|
||||
@@ -0,0 +1,86 @@
|
||||
class User
|
||||
attr_accessor :id, :name, :email
|
||||
|
||||
def initialize(id, name, email = nil)
|
||||
@id = id
|
||||
@name = name
|
||||
@email = email
|
||||
end
|
||||
|
||||
def full_info
|
||||
info = "User: #{@name} (ID: #{@id})"
|
||||
info += ", Email: #{@email}" if @email
|
||||
info
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{
|
||||
id: @id,
|
||||
name: @name,
|
||||
email: @email
|
||||
}
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
new(hash[:id], hash[:name], hash[:email])
|
||||
end
|
||||
|
||||
class << self
|
||||
def default_user
|
||||
new(0, "Guest")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Item
|
||||
attr_reader :id, :name, :price
|
||||
|
||||
def initialize(id, name, price)
|
||||
@id = id
|
||||
@name = name
|
||||
@price = price
|
||||
end
|
||||
|
||||
def discounted_price(discount_percent)
|
||||
@price * (1 - discount_percent / 100.0)
|
||||
end
|
||||
|
||||
def description
|
||||
"#{@name}: $#{@price}"
|
||||
end
|
||||
end
|
||||
|
||||
module ItemHelpers
|
||||
def format_price(price)
|
||||
"$#{sprintf('%.2f', price)}"
|
||||
end
|
||||
|
||||
def calculate_tax(price, tax_rate = 0.08)
|
||||
price * tax_rate
|
||||
end
|
||||
end
|
||||
|
||||
class Order
|
||||
include ItemHelpers
|
||||
|
||||
def initialize
|
||||
@items = []
|
||||
@total = 0
|
||||
end
|
||||
|
||||
def add_item(item, quantity = 1)
|
||||
@items << { item: item, quantity: quantity }
|
||||
calculate_total
|
||||
end
|
||||
|
||||
def total_with_tax
|
||||
tax = calculate_tax(@total)
|
||||
@total + tax
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_total
|
||||
@total = @items.sum { |entry| entry[:item].price * entry[:quantity] }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
class OuterClass
|
||||
def initialize
|
||||
@value = "outer"
|
||||
end
|
||||
|
||||
def outer_method
|
||||
inner_function = lambda do |x|
|
||||
x * 2
|
||||
end
|
||||
|
||||
result = inner_function.call(5)
|
||||
puts "Result: #{result}"
|
||||
end
|
||||
|
||||
class NestedClass
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def find_me
|
||||
"Found in NestedClass: #{@name}"
|
||||
end
|
||||
|
||||
def nested_method
|
||||
puts "Nested method called"
|
||||
end
|
||||
|
||||
class DeeplyNested
|
||||
def deep_method
|
||||
"Deep inside"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module NestedModule
|
||||
def module_method
|
||||
"Module method"
|
||||
end
|
||||
|
||||
class ModuleClass
|
||||
def module_class_method
|
||||
"Module class method"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test usage of nested classes
|
||||
outer = OuterClass.new
|
||||
nested = OuterClass::NestedClass.new("test")
|
||||
result = nested.find_me
|
||||
@@ -0,0 +1,60 @@
|
||||
require './lib.rb'
|
||||
require './models.rb'
|
||||
|
||||
module Services
|
||||
class UserService
|
||||
attr_reader :users
|
||||
|
||||
def initialize
|
||||
@users = {}
|
||||
end
|
||||
|
||||
def create_user(id, name)
|
||||
user = User.new(id, name)
|
||||
@users[id] = user
|
||||
user
|
||||
end
|
||||
|
||||
def get_user(id)
|
||||
@users[id]
|
||||
end
|
||||
|
||||
def delete_user(id)
|
||||
@users.delete(id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_user_data(id, name)
|
||||
return false if id.nil? || name.nil?
|
||||
return false if name.empty?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class ItemService
|
||||
def initialize
|
||||
@items = []
|
||||
end
|
||||
|
||||
def add_item(item)
|
||||
@items << item
|
||||
end
|
||||
|
||||
def find_item(id)
|
||||
@items.find { |item| item.id == id }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Module-level function
|
||||
def create_service_container
|
||||
{
|
||||
user_service: Services::UserService.new,
|
||||
item_service: Services::ItemService.new
|
||||
}
|
||||
end
|
||||
|
||||
# Variables for testing
|
||||
user_service_instance = Services::UserService.new
|
||||
item_service_instance = Services::ItemService.new
|
||||
@@ -0,0 +1,150 @@
|
||||
require './models.rb'
|
||||
|
||||
# Global variables for testing references
|
||||
$global_counter = 0
|
||||
$global_config = {
|
||||
debug: true,
|
||||
timeout: 30
|
||||
}
|
||||
|
||||
class DataContainer
|
||||
attr_accessor :status, :data, :metadata
|
||||
|
||||
def initialize
|
||||
@status = "pending"
|
||||
@data = {}
|
||||
@metadata = {
|
||||
created_at: Time.now,
|
||||
version: "1.0"
|
||||
}
|
||||
end
|
||||
|
||||
def update_status(new_status)
|
||||
old_status = @status
|
||||
@status = new_status
|
||||
log_status_change(old_status, new_status)
|
||||
end
|
||||
|
||||
def process_data(input_data)
|
||||
@data = input_data
|
||||
@status = "processing"
|
||||
|
||||
# Process the data
|
||||
result = @data.transform_values { |v| v.to_s.upcase }
|
||||
@status = "completed"
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def get_metadata_info
|
||||
info = "Status: #{@status}, Version: #{@metadata[:version]}"
|
||||
info += ", Created: #{@metadata[:created_at]}"
|
||||
info
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_status_change(old_status, new_status)
|
||||
puts "Status changed from #{old_status} to #{new_status}"
|
||||
end
|
||||
end
|
||||
|
||||
class StatusTracker
|
||||
def initialize
|
||||
@tracked_items = []
|
||||
end
|
||||
|
||||
def add_item(item)
|
||||
@tracked_items << item
|
||||
item.status = "tracked" if item.respond_to?(:status=)
|
||||
end
|
||||
|
||||
def find_by_status(target_status)
|
||||
@tracked_items.select { |item| item.status == target_status }
|
||||
end
|
||||
|
||||
def update_all_status(new_status)
|
||||
@tracked_items.each do |item|
|
||||
item.status = new_status if item.respond_to?(:status=)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Module level variables and functions
|
||||
module ProcessingHelper
|
||||
PROCESSING_MODES = ["sync", "async", "batch"].freeze
|
||||
|
||||
@@instance_count = 0
|
||||
|
||||
def self.create_processor(mode = "sync")
|
||||
@@instance_count += 1
|
||||
{
|
||||
id: @@instance_count,
|
||||
mode: mode,
|
||||
created_at: Time.now
|
||||
}
|
||||
end
|
||||
|
||||
def self.get_instance_count
|
||||
@@instance_count
|
||||
end
|
||||
end
|
||||
|
||||
# Test instances for reference testing
|
||||
dataclass_instance = DataContainer.new
|
||||
dataclass_instance.status = "initialized"
|
||||
|
||||
second_dataclass = DataContainer.new
|
||||
second_dataclass.update_status("ready")
|
||||
|
||||
tracker = StatusTracker.new
|
||||
tracker.add_item(dataclass_instance)
|
||||
tracker.add_item(second_dataclass)
|
||||
|
||||
# Function that uses the variables
|
||||
def demonstrate_variable_usage
|
||||
puts "Global counter: #{$global_counter}"
|
||||
|
||||
container = DataContainer.new
|
||||
container.status = "demo"
|
||||
|
||||
processor = ProcessingHelper.create_processor("async")
|
||||
puts "Created processor #{processor[:id]} in #{processor[:mode]} mode"
|
||||
|
||||
container
|
||||
end
|
||||
|
||||
# More complex variable interactions
|
||||
class VariableInteractionTest
|
||||
def initialize
|
||||
@internal_status = "created"
|
||||
@data_containers = []
|
||||
end
|
||||
|
||||
def add_container(container)
|
||||
@data_containers << container
|
||||
container.status = "added_to_collection"
|
||||
@internal_status = "modified"
|
||||
end
|
||||
|
||||
def process_all_containers
|
||||
@data_containers.each do |container|
|
||||
container.status = "batch_processed"
|
||||
end
|
||||
@internal_status = "processing_complete"
|
||||
end
|
||||
|
||||
def get_status_summary
|
||||
statuses = @data_containers.map(&:status)
|
||||
{
|
||||
internal: @internal_status,
|
||||
containers: statuses,
|
||||
count: @data_containers.length
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Create instances for testing
|
||||
interaction_test = VariableInteractionTest.new
|
||||
interaction_test.add_container(dataclass_instance)
|
||||
interaction_test.add_container(second_dataclass)
|
||||
@@ -0,0 +1,624 @@
|
||||
"""
|
||||
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)
|
||||
|
||||
# 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
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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_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
|
||||
assert defining_symbol.get("name") in [
|
||||
"UserService",
|
||||
"Services",
|
||||
"new",
|
||||
], f"Expected UserService related symbol, 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
|
||||
Reference in new issue
Block a user