Skip to main content
Glama
test_resource_cleanup.py8.9 kB
"""Tests for GitPython resource cleanup and new helper functions. These tests verify that: 1. _git_repo context manager properly cleans up resources 2. _open_repo_if_available closes repos on error paths 3. _collect_file_reservation_statuses properly cleans up repos """ from __future__ import annotations import asyncio from pathlib import Path from unittest.mock import patch import pytest from fastmcp import Client from git import Repo from mcp_agent_mail.app import ( _git_repo, _open_repo_if_available, build_mcp_server, ) def test_git_repo_context_manager_normal_operation(tmp_path: Path): """Test that _git_repo properly yields a working repo and closes it.""" # Create a minimal git repo repo_path = tmp_path / "test_repo" repo_path.mkdir() Repo.init(str(repo_path)) close_called = False original_close = Repo.close def tracked_close(self): nonlocal close_called close_called = True return original_close(self) with patch.object(Repo, 'close', tracked_close), _git_repo(repo_path) as repo: assert repo is not None assert repo.working_tree_dir is not None assert close_called, "repo.close() should have been called" def test_git_repo_context_manager_closes_on_exception(tmp_path: Path): """Test that _git_repo closes the repo even when exception is raised inside.""" repo_path = tmp_path / "test_repo" repo_path.mkdir() Repo.init(str(repo_path)) close_called = False original_close = Repo.close def tracked_close(self): nonlocal close_called close_called = True return original_close(self) with patch.object(Repo, 'close', tracked_close), pytest.raises(ValueError, match="test exception"), _git_repo(repo_path) as repo: assert repo is not None raise ValueError("test exception") assert close_called, "repo.close() should be called even on exception" def test_git_repo_context_manager_handles_invalid_repo(tmp_path: Path): """Test that _git_repo handles non-repo paths gracefully.""" non_repo_path = tmp_path / "not_a_repo" non_repo_path.mkdir() # Should raise InvalidGitRepositoryError when trying to open non-repo from git.exc import InvalidGitRepositoryError with pytest.raises(InvalidGitRepositoryError), _git_repo(non_repo_path, search_parent_directories=False): pass def test_open_repo_if_available_returns_none_for_non_repo(tmp_path: Path): """Test that _open_repo_if_available returns None for non-git directories.""" non_repo = tmp_path / "not_git" non_repo.mkdir() result = _open_repo_if_available(non_repo) assert result is None def test_open_repo_if_available_returns_none_for_none(): """Test that _open_repo_if_available returns None when passed None.""" result = _open_repo_if_available(None) assert result is None def test_open_repo_if_available_returns_repo_for_valid_git(tmp_path: Path): """Test that _open_repo_if_available returns valid repo for git directories.""" repo_path = tmp_path / "git_repo" repo_path.mkdir() Repo.init(str(repo_path)) result = _open_repo_if_available(repo_path) assert result is not None try: assert result.working_tree_dir is not None finally: result.close() def test_open_repo_if_available_closes_on_validation_failure(tmp_path: Path): """Test that _open_repo_if_available closes repo when validation fails. This tests the bug fix where a repo opened but failing validation (e.g., workspace not relative to repo root) would leak file handles. """ # Create a git repo in one location repo_path = tmp_path / "git_repo" repo_path.mkdir() Repo.init(str(repo_path)) # Create a subdirectory inside the repo that we'll test with # But make the workspace resolve check fail by creating a scenario # where the path is valid but doesn't match repo root validation subdir = repo_path / "subdir" subdir.mkdir() # The function should return a valid repo for paths inside the repo result = _open_repo_if_available(subdir) if result is not None: # If repo was returned, ensure we clean it up result.close() # This path should succeed, so let's test the None case differently # Test with a path outside any git repo outside_path = tmp_path / "outside" outside_path.mkdir() result2 = _open_repo_if_available(outside_path) assert result2 is None, "Should return None for path outside any git repo" def test_open_repo_if_available_closes_on_working_tree_exception(tmp_path: Path): """Test that repo is closed if working_tree_dir access raises exception.""" repo_path = tmp_path / "git_repo" repo_path.mkdir() Repo.init(str(repo_path)) close_called = False original_close = Repo.close def tracked_close(self): nonlocal close_called close_called = True return original_close(self) # Mock working_tree_dir to raise an exception with patch.object(Repo, 'close', tracked_close), patch.object( Repo, 'working_tree_dir', property(lambda self: (_ for _ in ()).throw(OSError("mocked error"))) ): result = _open_repo_if_available(repo_path) # The function should return None when working_tree_dir raises assert result is None, "Should return None when working_tree_dir fails" # And repo.close() should have been called to prevent file handle leak assert close_called, "repo.close() should be called when working_tree_dir fails" @pytest.mark.asyncio async def test_file_reservation_statuses_cleanup_on_exception(isolated_env, tmp_path: Path): """Test that _collect_file_reservation_statuses cleans up repos on exception.""" server = build_mcp_server() # Create a git repo to use as workspace workspace = tmp_path / "workspace" workspace.mkdir() proc = await asyncio.create_subprocess_exec("git", "init", cwd=str(workspace)) await proc.wait() proc = await asyncio.create_subprocess_exec( "git", "config", "user.email", "test@example.com", cwd=str(workspace) ) await proc.wait() proc = await asyncio.create_subprocess_exec( "git", "config", "user.name", "Test User", cwd=str(workspace) ) await proc.wait() async with Client(server) as client: # Set up project and agent await client.call_tool("ensure_project", {"human_key": str(workspace)}) await client.call_tool( "register_agent", { "project_key": str(workspace), "program": "test-cli", "model": "test-model", "name": "GreenLake", }, ) # Create a file reservation result = await client.call_tool( "file_reservation_paths", { "project_key": str(workspace), "agent_name": "GreenLake", "paths": ["test.py"], "ttl_seconds": 3600, "exclusive": True, }, ) # Verify file reservation was created assert result.data.get("granted") or result.data.get("conflicts") is not None @pytest.mark.asyncio async def test_file_reservation_release_works(isolated_env, tmp_path: Path): """Test that file reservations can be properly reserved and released.""" server = build_mcp_server() workspace = tmp_path / "workspace" workspace.mkdir() proc = await asyncio.create_subprocess_exec("git", "init", cwd=str(workspace)) await proc.wait() proc = await asyncio.create_subprocess_exec( "git", "config", "user.email", "test@example.com", cwd=str(workspace) ) await proc.wait() proc = await asyncio.create_subprocess_exec( "git", "config", "user.name", "Test User", cwd=str(workspace) ) await proc.wait() async with Client(server) as client: await client.call_tool("ensure_project", {"human_key": str(workspace)}) await client.call_tool( "register_agent", { "project_key": str(workspace), "program": "test-cli", "model": "test-model", "name": "BlueDog", }, ) # Reserve res1 = await client.call_tool( "file_reservation_paths", { "project_key": str(workspace), "agent_name": "BlueDog", "paths": ["*.py"], "ttl_seconds": 3600, }, ) granted = res1.data.get("granted", []) assert len(granted) > 0 # Release res2 = await client.call_tool( "release_file_reservations", {"project_key": str(workspace), "agent_name": "BlueDog"}, ) assert res2.data.get("released", 0) > 0

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/Dicklesworthstone/mcp_agent_mail'

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