"""Tests for build execution tools."""
import pytest
from unittest.mock import patch, AsyncMock
from xcsift_mcp.tools.build import _run_command
class TestRunCommand:
"""Tests for _run_command function."""
@pytest.mark.asyncio
async def test_runs_command_successfully(self):
"""Should run command and return output."""
with patch("asyncio.create_subprocess_exec") as mock_exec:
mock_proc = AsyncMock()
mock_proc.communicate.return_value = (b"Build succeeded", None)
mock_proc.returncode = 0
mock_exec.return_value = mock_proc
output, code = await _run_command(["echo", "hello"])
assert output == "Build succeeded"
assert code == 0
@pytest.mark.asyncio
async def test_captures_combined_output(self):
"""Should capture both stdout and stderr."""
with patch("asyncio.create_subprocess_exec") as mock_exec:
mock_proc = AsyncMock()
mock_proc.communicate.return_value = (
b"output\nerror output",
None,
)
mock_proc.returncode = 0
mock_exec.return_value = mock_proc
output, code = await _run_command(["test"])
assert "output" in output
assert "error output" in output
@pytest.mark.asyncio
async def test_respects_working_directory(self):
"""Should set working directory."""
with patch("asyncio.create_subprocess_exec") as mock_exec:
mock_proc = AsyncMock()
mock_proc.communicate.return_value = (b"", None)
mock_proc.returncode = 0
mock_exec.return_value = mock_proc
await _run_command(["test"], cwd="/tmp/project")
# Check cwd was passed
assert mock_exec.call_args.kwargs.get("cwd") == "/tmp/project"
@pytest.mark.asyncio
async def test_handles_timeout(self):
"""Should raise TimeoutError on timeout."""
import asyncio
with patch("asyncio.create_subprocess_exec") as mock_exec:
mock_proc = AsyncMock()
mock_proc.communicate.side_effect = asyncio.TimeoutError()
mock_proc.kill = AsyncMock()
mock_proc.wait = AsyncMock()
mock_exec.return_value = mock_proc
with pytest.raises(TimeoutError) as exc_info:
await _run_command(["slow_command"], timeout=1)
assert "timed out" in str(exc_info.value)
mock_proc.kill.assert_called_once()
@pytest.mark.asyncio
async def test_returns_exit_code(self):
"""Should return the command's exit code."""
with patch("asyncio.create_subprocess_exec") as mock_exec:
mock_proc = AsyncMock()
mock_proc.communicate.return_value = (b"error", None)
mock_proc.returncode = 1
mock_exec.return_value = mock_proc
output, code = await _run_command(["failing_command"])
assert code == 1
class TestXcodebuildToolArgs:
"""Tests for xcodebuild tool argument construction."""
def test_basic_args(self):
"""Should construct basic xcodebuild args."""
# This tests the argument construction logic
args = ["xcodebuild", "build"]
scheme = "MyApp"
args.extend(["-scheme", scheme])
assert args == ["xcodebuild", "build", "-scheme", "MyApp"]
def test_with_destination(self):
"""Should include destination argument."""
args = ["xcodebuild", "test"]
destination = "platform=iOS Simulator,name=iPhone 15"
args.extend(["-destination", destination])
assert "-destination" in args
assert "iPhone 15" in args[-1]
def test_with_coverage(self):
"""Should include coverage flags for test action."""
args = ["xcodebuild", "test"]
args.append("-enableCodeCoverage")
args.append("YES")
assert "-enableCodeCoverage" in args
assert "YES" in args
class TestSwiftBuildToolArgs:
"""Tests for swift build tool argument construction."""
def test_debug_configuration(self):
"""Should use debug configuration by default."""
args = ["swift", "build", "-c", "debug"]
assert "-c" in args
assert "debug" in args
def test_release_configuration(self):
"""Should support release configuration."""
args = ["swift", "build", "-c", "release"]
assert "release" in args
def test_with_target(self):
"""Should include target argument."""
args = ["swift", "build"]
target = "MyLibrary"
args.extend(["--target", target])
assert "--target" in args
assert "MyLibrary" in args
class TestSwiftTestToolArgs:
"""Tests for swift test tool argument construction."""
def test_with_filter(self):
"""Should include test filter."""
args = ["swift", "test"]
filter_test = "UserTests.testValidation"
args.extend(["--filter", filter_test])
assert "--filter" in args
assert "UserTests.testValidation" in args
def test_with_coverage(self):
"""Should include coverage flag."""
args = ["swift", "test"]
args.append("--enable-code-coverage")
assert "--enable-code-coverage" in args
def test_no_parallel(self):
"""Should include no-parallel flag."""
args = ["swift", "test"]
args.append("--no-parallel")
assert "--no-parallel" in args