Skip to content

Commit d36e678

Browse files
committed
test: Add ProcessManager tests
- Process cleanup with proper mocking - AsyncMock and MagicMock combination for async operations
1 parent 157eb30 commit d36e678

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

tests/test_process_manager.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""Tests for the ProcessManager class."""
2+
3+
import asyncio
4+
from unittest.mock import AsyncMock, MagicMock, patch
5+
6+
import pytest
7+
8+
from mcp_shell_server.process_manager import ProcessManager
9+
10+
11+
@pytest.fixture
12+
def process_manager():
13+
"""Fixture for ProcessManager instance."""
14+
return ProcessManager()
15+
16+
17+
@pytest.mark.asyncio
18+
async def test_create_process(process_manager):
19+
"""Test creating a process with basic parameters."""
20+
mock_process = AsyncMock()
21+
mock_process.returncode = 0
22+
23+
with patch(
24+
"asyncio.create_subprocess_shell", return_value=mock_process
25+
) as mock_create:
26+
process = await process_manager.create_process(
27+
"echo 'test'",
28+
directory="/tmp",
29+
stdin="input",
30+
)
31+
32+
assert process == mock_process
33+
mock_create.assert_called_once()
34+
args = mock_create.call_args[0]
35+
kwargs = mock_create.call_args[1]
36+
assert args[0] == "echo 'test'"
37+
assert kwargs["stdin"] == asyncio.subprocess.PIPE
38+
assert kwargs["stdout"] == asyncio.subprocess.PIPE
39+
assert kwargs["stderr"] == asyncio.subprocess.PIPE
40+
assert kwargs["cwd"] == "/tmp"
41+
42+
43+
@pytest.mark.asyncio
44+
async def test_execute_with_timeout_success(process_manager):
45+
"""Test executing a process with successful completion."""
46+
mock_process = AsyncMock()
47+
mock_process.communicate.return_value = (b"output", b"error")
48+
mock_process.returncode = 0
49+
50+
stdout, stderr = await process_manager.execute_with_timeout(
51+
mock_process,
52+
stdin="input",
53+
timeout=10,
54+
)
55+
56+
assert stdout == b"output"
57+
assert stderr == b"error"
58+
mock_process.communicate.assert_called_once_with(input=b"input")
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_execute_with_timeout_timeout(process_manager):
63+
"""Test executing a process that times out."""
64+
mock_process = AsyncMock()
65+
mock_process.communicate.side_effect = asyncio.TimeoutError()
66+
mock_process.returncode = None
67+
68+
with pytest.raises(asyncio.TimeoutError):
69+
await process_manager.execute_with_timeout(
70+
mock_process,
71+
timeout=1,
72+
)
73+
74+
75+
@pytest.mark.asyncio
76+
async def test_execute_pipeline_success(process_manager):
77+
"""Test executing a pipeline of commands successfully."""
78+
mock_process1 = AsyncMock()
79+
mock_process1.communicate.return_value = (b"output1", b"")
80+
mock_process1.returncode = 0
81+
82+
mock_process2 = AsyncMock()
83+
mock_process2.communicate.return_value = (b"final output", b"")
84+
mock_process2.returncode = 0
85+
86+
create_process_mock = AsyncMock(side_effect=[mock_process1, mock_process2])
87+
88+
with patch.object(process_manager, "create_process", create_process_mock):
89+
stdout, stderr, return_code = await process_manager.execute_pipeline(
90+
["echo 'test'", "grep test"],
91+
directory="/tmp",
92+
timeout=10,
93+
)
94+
95+
assert stdout == b"final output"
96+
assert stderr == b""
97+
assert return_code == 0
98+
assert create_process_mock.call_count == 2
99+
100+
101+
@pytest.mark.asyncio
102+
async def test_execute_pipeline_with_error(process_manager):
103+
"""Test executing a pipeline where a command fails."""
104+
mock_process = AsyncMock()
105+
mock_process.communicate.return_value = (b"", b"error message")
106+
mock_process.returncode = 1
107+
mock_process.kill = MagicMock() # killはMagicMockに
108+
mock_process.wait = AsyncMock() # waitはそのままAsyncMockに
109+
110+
create_process_mock = AsyncMock(return_value=mock_process)
111+
112+
with patch.object(process_manager, "create_process", create_process_mock):
113+
with pytest.raises(ValueError, match="error message"):
114+
await process_manager.execute_pipeline(
115+
["invalid_command"],
116+
directory="/tmp",
117+
)
118+
119+
mock_process.kill.assert_called_once()
120+
121+
122+
@pytest.mark.asyncio
123+
async def test_cleanup_processes(process_manager):
124+
"""Test cleaning up processes."""
125+
mock_process1 = AsyncMock()
126+
mock_process1.returncode = None
127+
mock_process1.kill = MagicMock() # killはMagicMockに
128+
mock_process1.wait = AsyncMock() # waitはそのままAsyncMockに
129+
130+
mock_process2 = AsyncMock()
131+
mock_process2.returncode = 0
132+
mock_process2.kill = MagicMock() # killはMagicMockに
133+
mock_process2.wait = AsyncMock() # waitはそのままAsyncMockに
134+
135+
await process_manager.cleanup_processes([mock_process1, mock_process2])
136+
137+
mock_process1.kill.assert_called_once()
138+
assert mock_process1.wait.await_count == 1
139+
assert not mock_process2.kill.called
140+
assert mock_process2.wait.await_count == 0 # waitも呼び出されていないことを確認

0 commit comments

Comments
 (0)