diff --git a/test/serena/test_get_diagnostics_tool.py b/test/serena/test_get_diagnostics_tool.py deleted file mode 100644 index 786bd76b..00000000 --- a/test/serena/test_get_diagnostics_tool.py +++ /dev/null @@ -1,229 +0,0 @@ -"""Tests for the GetDiagnosticsTool.""" - -import json -from unittest.mock import Mock, patch - -import pytest - -from src.serena.agent import SerenaAgent -from src.serena.project import Project -from src.serena.tools.symbol_tools import GetDiagnosticsTool - - -class TestGetDiagnosticsTool: - """Unit tests for GetDiagnosticsTool.""" - - def setup_method(self): - """Set up test fixtures.""" - # Mock agent - self.mock_agent = Mock(spec=SerenaAgent) - self.mock_agent.is_using_language_server.return_value = True - - # Mock language server - self.mock_language_server = Mock() - self.mock_agent.language_server = self.mock_language_server - - # Mock project - self.mock_project = Mock(spec=Project) - self.mock_project.project_root = "/test/project" - self.mock_agent.get_active_project_or_raise.return_value = self.mock_project - - # Create tool instance - self.tool = GetDiagnosticsTool(self.mock_agent) - - def test_get_severity_name(self): - """Test the _get_severity_name helper method.""" - assert self.tool._get_severity_name(1) == "error" - assert self.tool._get_severity_name(2) == "warning" - assert self.tool._get_severity_name(3) == "information" - assert self.tool._get_severity_name(4) == "hint" - assert self.tool._get_severity_name(None) == "unknown" - assert self.tool._get_severity_name(999) == "unknown(999)" - - @patch("os.path.exists") - @patch("os.path.isdir") - def test_apply_success_with_diagnostics(self, mock_isdir, mock_exists): - """Test successful diagnostic retrieval with results.""" - # Setup mocks - mock_exists.return_value = True - mock_isdir.return_value = False - - # Mock diagnostic data - mock_diagnostics = [ - { - "severity": 1, - "message": "Undefined variable 'foo'", - "code": "undefined-variable", - "source": "pylsp", - "range": {"start": {"line": 10, "character": 5}, "end": {"line": 10, "character": 8}}, - }, - { - "severity": 2, - "message": "Line too long", - "code": "E501", - "source": "flake8", - "range": {"start": {"line": 20, "character": 0}, "end": {"line": 20, "character": 100}}, - }, - ] - - self.mock_language_server.request_text_document_diagnostics.return_value = mock_diagnostics - - # Mock the _limit_length method - with patch.object(self.tool, "_limit_length", side_effect=lambda x, _: x): - result = self.tool.apply("test_file.py") - - # Verify result - result_data = json.loads(result) - assert len(result_data) == 2 - - # Check first diagnostic - diag1 = result_data[0] - assert diag1["severity"] == 1 - assert diag1["severity_name"] == "error" - assert diag1["message"] == "Undefined variable 'foo'" - assert diag1["code"] == "undefined-variable" - assert diag1["source"] == "pylsp" - assert diag1["range"]["start"]["line"] == 10 - assert diag1["range"]["start"]["character"] == 5 - - # Check second diagnostic - diag2 = result_data[1] - assert diag2["severity"] == 2 - assert diag2["severity_name"] == "warning" - assert diag2["message"] == "Line too long" - - # Verify method calls - self.mock_language_server.request_text_document_diagnostics.assert_called_once_with("test_file.py") - - @patch("os.path.exists") - @patch("os.path.isdir") - def test_apply_success_no_diagnostics(self, mock_isdir, mock_exists): - """Test successful diagnostic retrieval with no results.""" - # Setup mocks - mock_exists.return_value = True - mock_isdir.return_value = False - - self.mock_language_server.request_text_document_diagnostics.return_value = [] - - # Mock the _limit_length method - with patch.object(self.tool, "_limit_length", side_effect=lambda x, _: x): - result = self.tool.apply("clean_file.py") - - # Verify result - result_data = json.loads(result) - assert len(result_data) == 0 - assert result_data == [] - - def test_apply_no_language_server(self): - """Test error when language server is not available.""" - self.mock_agent.is_using_language_server.return_value = False - - with pytest.raises(Exception, match="Cannot get diagnostics; agent is not in language server mode"): - self.tool.apply("test_file.py") - - @patch("os.path.exists") - def test_apply_file_not_found(self, mock_exists): - """Test error when file does not exist.""" - mock_exists.return_value = False - - with pytest.raises(FileNotFoundError, match=r"File test_file.py does not exist in the project"): - self.tool.apply("test_file.py") - - @patch("os.path.exists") - @patch("os.path.isdir") - def test_apply_is_directory(self, mock_isdir, mock_exists): - """Test error when path is a directory instead of a file.""" - mock_exists.return_value = True - mock_isdir.return_value = True - - with pytest.raises(ValueError, match="Expected a file path, but got a directory path: test_dir"): - self.tool.apply("test_dir") - - @patch("os.path.exists") - @patch("os.path.isdir") - def test_apply_language_server_error(self, mock_isdir, mock_exists): - """Test handling of language server errors.""" - mock_exists.return_value = True - mock_isdir.return_value = False - - # Mock language server to raise an exception - self.mock_language_server.request_text_document_diagnostics.side_effect = Exception("Language server failed") - - with pytest.raises(RuntimeError, match=r"Failed to retrieve diagnostics for test_file.py: Language server failed"): - self.tool.apply("test_file.py") - - @patch("os.path.exists") - @patch("os.path.isdir") - def test_apply_with_missing_fields(self, mock_isdir, mock_exists): - """Test handling of diagnostics with missing optional fields.""" - # Setup mocks - mock_exists.return_value = True - mock_isdir.return_value = False - - # Mock diagnostic with missing optional fields - mock_diagnostics = [ - { - "message": "Error without optional fields", - "range": {"start": {"line": 5, "character": 0}, "end": {"line": 5, "character": 10}}, - # Missing severity, code, source - } - ] - - self.mock_language_server.request_text_document_diagnostics.return_value = mock_diagnostics - - # Mock the _limit_length method - with patch.object(self.tool, "_limit_length", side_effect=lambda x, _: x): - result = self.tool.apply("test_file.py") - - # Verify result handles missing fields gracefully - result_data = json.loads(result) - assert len(result_data) == 1 - - diag = result_data[0] - assert diag["severity"] == "unknown" - assert diag["severity_name"] == "unknown" - assert diag["message"] == "Error without optional fields" - assert diag["code"] == "" - assert diag["source"] == "" - - @patch("os.path.exists") - @patch("os.path.isdir") - def test_apply_with_max_answer_chars(self, mock_isdir, mock_exists): - """Test limiting output with max_answer_chars parameter.""" - mock_exists.return_value = True - mock_isdir.return_value = False - - mock_diagnostics = [ - { - "severity": 1, - "message": "Test error", - "code": "test", - "source": "test", - "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}, - } - ] - - self.mock_language_server.request_text_document_diagnostics.return_value = mock_diagnostics - - # Mock _limit_length to return truncated result - with patch.object(self.tool, "_limit_length", return_value="TRUNCATED"): - result = self.tool.apply("test_file.py", max_answer_chars=100) - - assert result == "TRUNCATED" - - def test_path_construction(self): - """Test that file paths are constructed correctly.""" - relative_path = "src/module/file.py" - - with ( - patch("os.path.exists", return_value=True), - patch("os.path.isdir", return_value=False), - patch.object(self.tool, "_limit_length", side_effect=lambda x, _: x), - ): - - self.mock_language_server.request_text_document_diagnostics.return_value = [] - self.tool.apply(relative_path) - - # Verify os.path.join was called with correct arguments - # The actual path checking happens in the method, we verify the LS call - self.mock_language_server.request_text_document_diagnostics.assert_called_once_with(relative_path) diff --git a/test/serena/test_get_diagnostics_tool_go_integration.py b/test/serena/test_get_diagnostics_tool_go_integration.py deleted file mode 100644 index 66f6e198..00000000 --- a/test/serena/test_get_diagnostics_tool_go_integration.py +++ /dev/null @@ -1,172 +0,0 @@ -"""Integration tests for GetDiagnosticsTool with Go language server.""" - -import json -import os -from unittest.mock import Mock - -import pytest - -from solidlsp.ls_config import Language -from src.serena.agent import SerenaAgent -from src.serena.project import Project -from src.serena.tools.symbol_tools import GetDiagnosticsTool -from test.conftest import create_ls - - -@pytest.mark.go -class TestGetDiagnosticsToolGoIntegration: - """Integration tests for GetDiagnosticsTool with Go language server.""" - - def setup_method(self): - """Set up test fixtures with real Go language server.""" - # Path to our diagnostic test repository - self.repo_path = os.path.join(os.path.dirname(__file__), "..", "resources", "repos", "go", "diagnostic_test_repo") - self.repo_path = os.path.abspath(self.repo_path) - - # Create real language server for Go - self.language_server = create_ls(Language.GO, repo_path=self.repo_path) - - # Mock agent with real language server - self.mock_agent = Mock(spec=SerenaAgent) - self.mock_agent.is_using_language_server.return_value = True - self.mock_agent.language_server = self.language_server - - # Mock project - self.mock_project = Mock(spec=Project) - self.mock_project.project_root = self.repo_path - self.mock_agent.get_active_project_or_raise.return_value = self.mock_project - - # Create tool instance - self.tool = GetDiagnosticsTool(self.mock_agent) - - def teardown_method(self): - """Clean up language server.""" - if hasattr(self, "language_server"): - self.language_server.stop() - - def test_get_diagnostics_with_go_errors(self): - """Test GetDiagnosticsTool retrieves actual Go diagnostics from language server.""" - - # Mock the _limit_length method to avoid character limits in tests - def mock_limit_length(content, max_chars): - return content - - self.tool._limit_length = mock_limit_length - - try: - print(f"Starting Go language server for repo: {self.repo_path}") - # Start the language server - self.language_server.start() - print("Language server started successfully") - - # Get diagnostics for the main.go file with intentional errors - result = self.tool.apply("main.go") - - # Parse the JSON result - diagnostics = json.loads(result) - - # We should have at least some diagnostics due to the intentional errors - assert isinstance(diagnostics, list) - assert len(diagnostics) > 0, f"Expected diagnostics but got none. Result: {result}" - - # Check that we have actual diagnostic data - for diagnostic in diagnostics: - assert "severity" in diagnostic - assert "severity_name" in diagnostic - assert "message" in diagnostic - assert "range" in diagnostic - assert "start" in diagnostic["range"] - assert "end" in diagnostic["range"] - assert "line" in diagnostic["range"]["start"] - assert "character" in diagnostic["range"]["start"] - - # Severity should be a known value - assert diagnostic["severity_name"] in ["error", "warning", "information", "hint", "unknown"] - - # Message should not be empty for real diagnostics - assert len(diagnostic["message"]) > 0 - - # Check that we have at least one error (due to undefined variables/functions) - error_diagnostics = [d for d in diagnostics if d["severity_name"] == "error"] - assert len(error_diagnostics) > 0, f"Expected at least one error diagnostic. Got diagnostics: {diagnostics}" - - print(f"Successfully retrieved {len(diagnostics)} diagnostics:") - for i, diag in enumerate(diagnostics[:5]): # Print first 5 diagnostics - print(f" {i+1}. [{diag['severity_name']}] {diag['message']} at line {diag['range']['start']['line']}") - - except Exception as e: - pytest.skip(f"Go language server not available or failed: {e}") - - def test_get_diagnostics_empty_file(self): - """Test GetDiagnosticsTool with a valid Go file that has no errors.""" - # Create a valid Go file with no errors - valid_go_content = """package utils - -import "fmt" - -func PrintMessage() { - fmt.Println("Hello, World!") -} -""" - - # Create subdirectory for the valid file to avoid package conflicts - valid_dir = os.path.join(self.repo_path, "utils") - os.makedirs(valid_dir, exist_ok=True) - - # Write the valid content to a test file in subdirectory - valid_file_path = os.path.join(valid_dir, "valid.go") - try: - with open(valid_file_path, "w") as f: - f.write(valid_go_content) - - # Mock the _limit_length method - def mock_limit_length(content, max_chars): - return content - - self.tool._limit_length = mock_limit_length - - # Start the language server - self.language_server.start() - - # Get diagnostics for the valid file - result = self.tool.apply("utils/valid.go") - - # Parse the JSON result - diagnostics = json.loads(result) - - # Should be an empty list or only contain minor warnings - assert isinstance(diagnostics, list) - - # If there are any diagnostics, they should not be errors - error_diagnostics = [d for d in diagnostics if d["severity_name"] == "error"] - assert len(error_diagnostics) == 0, f"Valid Go file should not have errors. Got: {error_diagnostics}" - - print(f"Valid Go file has {len(diagnostics)} diagnostics (expected 0 or only warnings/hints)") - - except Exception as e: - pytest.skip(f"Go language server not available or failed: {e}") - finally: - # Clean up the test file and directory - if os.path.exists(valid_file_path): - os.remove(valid_file_path) - if os.path.exists(valid_dir) and os.path.isdir(valid_dir): - os.rmdir(valid_dir) - - def test_get_diagnostics_nonexistent_file(self): - """Test GetDiagnosticsTool error handling with nonexistent file.""" - with pytest.raises(FileNotFoundError, match=r"File nonexistent.go does not exist in the project"): - self.tool.apply("nonexistent.go") - - def test_get_diagnostics_directory_path(self): - """Test GetDiagnosticsTool error handling when given a directory path.""" - # Create a test directory - test_dir = os.path.join(self.repo_path, "test_dir") - os.makedirs(test_dir, exist_ok=True) - - try: - with pytest.raises(ValueError, match="Expected a file path, but got a directory path"): - self.tool.apply("test_dir") - finally: - # Clean up - if os.path.exists(test_dir): - os.rmdir(test_dir)