initial commit after one day coding agent session
This commit is contained in:
commit
372af75b90
88 changed files with 22694 additions and 0 deletions
325
tests/execution/jupyter/test_kernel.py
Normal file
325
tests/execution/jupyter/test_kernel.py
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
"""Tests for the Jupyter Kernel Manager module."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from mcp_forge.execution.jupyter.kernel import (
|
||||
JupyterKernelManager,
|
||||
KernelInfo,
|
||||
KernelError
|
||||
)
|
||||
from mcp_forge.podman.containers import SecureContainerManager
|
||||
from mcp_forge.security.resource_limits import ResourceLimits
|
||||
from mcp_forge.execution.simple.executor import ExecutionResult
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resource_limits():
|
||||
"""Standard resource limits for testing."""
|
||||
return ResourceLimits(
|
||||
memory="512m",
|
||||
cpu_quota=50000,
|
||||
storage="1g",
|
||||
timeout=300
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_container_manager():
|
||||
"""Mock SecureContainerManager."""
|
||||
manager = Mock(spec=SecureContainerManager)
|
||||
manager.create_container.return_value = "test-container-123"
|
||||
manager.start_container.return_value = None
|
||||
manager.stop_container.return_value = None
|
||||
manager.remove_container.return_value = None
|
||||
manager.get_container_logs.return_value = ("", "")
|
||||
return manager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kernel_manager(mock_container_manager, resource_limits):
|
||||
"""JupyterKernelManager instance with mocked dependencies."""
|
||||
return JupyterKernelManager(
|
||||
container_manager=mock_container_manager,
|
||||
image="mcp-forge/jupyter:latest",
|
||||
resource_limits=resource_limits
|
||||
)
|
||||
|
||||
|
||||
def test_start_kernel_creates_container(kernel_manager, mock_container_manager):
|
||||
"""Test start_kernel creates and starts a container."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
assert kernel_id is not None
|
||||
assert kernel_id.startswith("kernel-")
|
||||
|
||||
# Verify container was created and started
|
||||
mock_container_manager.create_container.assert_called_once()
|
||||
mock_container_manager.start_container.assert_called_once()
|
||||
|
||||
|
||||
def test_start_kernel_returns_kernel_info(kernel_manager):
|
||||
"""Test start_kernel returns valid kernel info."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
# Kernel should be registered
|
||||
assert kernel_id in kernel_manager.kernels
|
||||
|
||||
kernel_info = kernel_manager.kernels[kernel_id]
|
||||
assert kernel_info.kernel_id == kernel_id
|
||||
assert kernel_info.container_id == "test-container-123"
|
||||
assert kernel_info.session_id == "session-1"
|
||||
assert isinstance(kernel_info.started_at, datetime)
|
||||
|
||||
|
||||
def test_execute_code_in_kernel_returns_result(kernel_manager):
|
||||
"""Test execute_code runs code and returns result."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
result = kernel_manager.execute_code(kernel_id, "2 + 2")
|
||||
|
||||
assert isinstance(result, ExecutionResult)
|
||||
assert result.success is True
|
||||
|
||||
|
||||
def test_execute_code_with_nonexistent_kernel_raises_error(kernel_manager):
|
||||
"""Test execute_code raises error for nonexistent kernel."""
|
||||
with pytest.raises(KernelError, match="Kernel.*not found"):
|
||||
kernel_manager.execute_code("nonexistent-kernel", "pass")
|
||||
|
||||
|
||||
def test_execute_code_preserves_namespace(kernel_manager):
|
||||
"""Test namespace persists between executions."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
# Set a variable
|
||||
result1 = kernel_manager.execute_code(kernel_id, "x = 42")
|
||||
assert result1.success is True
|
||||
|
||||
# Access the variable
|
||||
result2 = kernel_manager.execute_code(kernel_id, "x")
|
||||
assert result2.success is True
|
||||
# In real implementation, result2.result would be 42
|
||||
|
||||
|
||||
def test_shutdown_kernel_removes_container(kernel_manager, mock_container_manager):
|
||||
"""Test shutdown_kernel cleans up container."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
kernel_manager.shutdown_kernel(kernel_id)
|
||||
|
||||
# Verify container was stopped and removed
|
||||
mock_container_manager.stop_container.assert_called_once_with("test-container-123", timeout=10)
|
||||
mock_container_manager.remove_container.assert_called_once_with("test-container-123")
|
||||
|
||||
# Kernel should be removed from registry
|
||||
assert kernel_id not in kernel_manager.kernels
|
||||
|
||||
|
||||
def test_shutdown_nonexistent_kernel_raises_error(kernel_manager):
|
||||
"""Test shutdown_kernel raises error for nonexistent kernel."""
|
||||
with pytest.raises(KernelError, match="Kernel.*not found"):
|
||||
kernel_manager.shutdown_kernel("nonexistent-kernel")
|
||||
|
||||
|
||||
def test_inspect_namespace_returns_variables(kernel_manager):
|
||||
"""Test inspect_namespace returns list of variables."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
# Execute some code to create variables
|
||||
kernel_manager.execute_code(kernel_id, "x = 1; y = 2; z = 3")
|
||||
|
||||
variables = kernel_manager.inspect_namespace(kernel_id)
|
||||
|
||||
assert isinstance(variables, list)
|
||||
# In real implementation, would contain ['x', 'y', 'z']
|
||||
|
||||
|
||||
def test_inspect_namespace_filters_private_vars(kernel_manager):
|
||||
"""Test inspect_namespace filters out private variables."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
kernel_manager.execute_code(kernel_id, "x = 1; _private = 2; __dunder__ = 3")
|
||||
|
||||
variables = kernel_manager.inspect_namespace(kernel_id)
|
||||
|
||||
# Private variables should be filtered
|
||||
# In real implementation: assert '_private' not in variables
|
||||
|
||||
|
||||
def test_get_variable_info_returns_metadata(kernel_manager):
|
||||
"""Test get_variable_info returns variable metadata."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
kernel_manager.execute_code(kernel_id, "x = [1, 2, 3, 4, 5]")
|
||||
|
||||
info = kernel_manager.get_variable_info(kernel_id, "x")
|
||||
|
||||
assert isinstance(info, dict)
|
||||
assert "type" in info
|
||||
# In real implementation: assert info["type"] == "list"
|
||||
|
||||
|
||||
def test_restart_kernel_resets_namespace(kernel_manager, mock_container_manager):
|
||||
"""Test restart_kernel resets namespace but keeps container."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
original_container_id = kernel_manager.kernels[kernel_id].container_id
|
||||
|
||||
# Set a variable
|
||||
kernel_manager.execute_code(kernel_id, "x = 42")
|
||||
|
||||
# Restart
|
||||
kernel_manager.restart_kernel(kernel_id)
|
||||
|
||||
# Container should be the same
|
||||
assert kernel_manager.kernels[kernel_id].container_id == original_container_id
|
||||
|
||||
# Namespace should be reset (variable no longer accessible)
|
||||
# In real implementation, executing "x" would raise NameError
|
||||
|
||||
|
||||
def test_cleanup_idle_kernels_removes_old_kernels(kernel_manager, mock_container_manager):
|
||||
"""Test cleanup_idle_kernels removes kernels idle too long."""
|
||||
# Start two kernels
|
||||
kernel1 = kernel_manager.start_kernel("session-1")
|
||||
kernel2 = kernel_manager.start_kernel("session-2")
|
||||
|
||||
# Make kernel1 appear old
|
||||
kernel_manager.kernels[kernel1].last_activity = datetime.utcnow() - timedelta(hours=2)
|
||||
|
||||
# Cleanup kernels idle > 1 hour
|
||||
count = kernel_manager.cleanup_idle_kernels(timedelta(hours=1))
|
||||
|
||||
assert count == 1
|
||||
assert kernel1 not in kernel_manager.kernels
|
||||
assert kernel2 in kernel_manager.kernels
|
||||
|
||||
|
||||
def test_kernel_with_volumes(kernel_manager, mock_container_manager):
|
||||
"""Test kernel can be started with volume mounts."""
|
||||
volumes = {
|
||||
"/mcp-forge/sessions/session-1/workspace": {"bind": "/workspace", "mode": "rw"}
|
||||
}
|
||||
|
||||
kernel_id = kernel_manager.start_kernel("session-1", volumes=volumes)
|
||||
|
||||
assert kernel_id is not None
|
||||
# Verify volumes were passed to container creation
|
||||
call_args = mock_container_manager.create_container.call_args
|
||||
# In real implementation, would verify volumes in ContainerConfig
|
||||
|
||||
|
||||
def test_execute_code_with_timeout(kernel_manager):
|
||||
"""Test execute_code respects timeout parameter."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
# Execute with custom timeout
|
||||
result = kernel_manager.execute_code(kernel_id, "import time; time.sleep(0.1)", timeout=10)
|
||||
|
||||
assert isinstance(result, ExecutionResult)
|
||||
|
||||
|
||||
def test_execute_code_handles_syntax_error(kernel_manager):
|
||||
"""Test execute_code handles syntax errors gracefully."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
result = kernel_manager.execute_code(kernel_id, "def foo( :")
|
||||
|
||||
assert result.success is False
|
||||
assert result.error is not None
|
||||
assert "SyntaxError" in result.error or "syntax" in result.error.lower()
|
||||
|
||||
|
||||
def test_execute_code_handles_runtime_error(kernel_manager):
|
||||
"""Test execute_code handles runtime errors gracefully."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
result = kernel_manager.execute_code(kernel_id, "1 / 0")
|
||||
|
||||
assert result.success is False
|
||||
assert result.error is not None
|
||||
|
||||
|
||||
def test_execute_code_captures_stdout(kernel_manager):
|
||||
"""Test execute_code captures stdout output."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
result = kernel_manager.execute_code(kernel_id, 'print("Hello, World!")')
|
||||
|
||||
assert result.success is True
|
||||
# In real implementation: assert "Hello, World!" in result.stdout
|
||||
|
||||
|
||||
def test_execute_code_captures_stderr(kernel_manager):
|
||||
"""Test execute_code captures stderr output."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
result = kernel_manager.execute_code(kernel_id, 'import sys; print("warning", file=sys.stderr)')
|
||||
|
||||
assert result.success is True
|
||||
# In real implementation: assert "warning" in result.stderr
|
||||
|
||||
|
||||
def test_multiple_kernels_are_isolated(kernel_manager):
|
||||
"""Test multiple kernels have isolated namespaces."""
|
||||
kernel1 = kernel_manager.start_kernel("session-1")
|
||||
kernel2 = kernel_manager.start_kernel("session-2")
|
||||
|
||||
# Set variable in kernel1
|
||||
kernel_manager.execute_code(kernel1, "x = 1")
|
||||
|
||||
# Set different value in kernel2
|
||||
kernel_manager.execute_code(kernel2, "x = 2")
|
||||
|
||||
# Values should be independent
|
||||
result1 = kernel_manager.execute_code(kernel1, "x")
|
||||
result2 = kernel_manager.execute_code(kernel2, "x")
|
||||
|
||||
# In real implementation: verify result1.result == 1 and result2.result == 2
|
||||
|
||||
|
||||
def test_kernel_info_to_dict(resource_limits):
|
||||
"""Test KernelInfo.to_dict() serialization."""
|
||||
now = datetime.utcnow()
|
||||
kernel_info = KernelInfo(
|
||||
kernel_id="kernel-123",
|
||||
container_id="container-456",
|
||||
session_id="session-789",
|
||||
started_at=now,
|
||||
last_activity=now
|
||||
)
|
||||
|
||||
info_dict = kernel_info.to_dict()
|
||||
|
||||
assert isinstance(info_dict, dict)
|
||||
assert info_dict["kernel_id"] == "kernel-123"
|
||||
assert info_dict["container_id"] == "container-456"
|
||||
assert info_dict["session_id"] == "session-789"
|
||||
assert "started_at" in info_dict
|
||||
assert "last_activity" in info_dict
|
||||
|
||||
|
||||
def test_start_kernel_with_session_id_tracking(kernel_manager):
|
||||
"""Test kernel tracks session_id correctly."""
|
||||
kernel_id = kernel_manager.start_kernel("my-session")
|
||||
|
||||
kernel_info = kernel_manager.kernels[kernel_id]
|
||||
assert kernel_info.session_id == "my-session"
|
||||
|
||||
|
||||
def test_update_activity_timestamp(kernel_manager):
|
||||
"""Test executing code updates last_activity timestamp."""
|
||||
kernel_id = kernel_manager.start_kernel("session-1")
|
||||
|
||||
original_activity = kernel_manager.kernels[kernel_id].last_activity
|
||||
|
||||
# Small delay to ensure timestamp difference
|
||||
import time
|
||||
time.sleep(0.01)
|
||||
|
||||
kernel_manager.execute_code(kernel_id, "pass")
|
||||
|
||||
new_activity = kernel_manager.kernels[kernel_id].last_activity
|
||||
assert new_activity > original_activity
|
||||
Loading…
Add table
Add a link
Reference in a new issue