mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Graham Neubig <neubig@gmail.com> Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
174 lines
6.2 KiB
Python
174 lines
6.2 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from openhands.core.config import OpenHandsConfig
|
|
from openhands.events import EventStream
|
|
from openhands.llm.llm_registry import LLMRegistry
|
|
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_docker_client():
|
|
with patch('docker.from_env') as mock_client:
|
|
container_mock = MagicMock()
|
|
container_mock.status = 'running'
|
|
container_mock.attrs = {
|
|
'Config': {
|
|
'Env': ['port=12345', 'VSCODE_PORT=54321'],
|
|
'ExposedPorts': {'12345/tcp': {}, '54321/tcp': {}},
|
|
}
|
|
}
|
|
mock_client.return_value.containers.get.return_value = container_mock
|
|
mock_client.return_value.containers.run.return_value = container_mock
|
|
# Mock version info for BuildKit check
|
|
mock_client.return_value.version.return_value = {
|
|
'Version': '20.10.0',
|
|
'Components': [{'Name': 'Engine', 'Version': '20.10.0'}],
|
|
} # Ensure version is >= 18.09
|
|
yield mock_client.return_value
|
|
|
|
|
|
@pytest.fixture
|
|
def config():
|
|
config = OpenHandsConfig()
|
|
config.sandbox.keep_runtime_alive = False
|
|
return config
|
|
|
|
|
|
@pytest.fixture
|
|
def event_stream():
|
|
return MagicMock(spec=EventStream)
|
|
|
|
|
|
@pytest.fixture
|
|
def llm_registry():
|
|
return MagicMock(spec=LLMRegistry)
|
|
|
|
|
|
@patch('openhands.runtime.impl.docker.docker_runtime.stop_all_containers')
|
|
def test_container_stopped_when_keep_runtime_alive_false(
|
|
mock_stop_containers, mock_docker_client, config, event_stream, llm_registry
|
|
):
|
|
# Arrange
|
|
runtime = DockerRuntime(config, event_stream, llm_registry, sid='test-sid')
|
|
runtime.container = mock_docker_client.containers.get.return_value
|
|
|
|
# Act
|
|
runtime.close()
|
|
|
|
# Assert
|
|
mock_stop_containers.assert_called_once_with('openhands-runtime-test-sid')
|
|
|
|
|
|
@patch('openhands.runtime.impl.docker.docker_runtime.stop_all_containers')
|
|
def test_container_not_stopped_when_keep_runtime_alive_true(
|
|
mock_stop_containers, mock_docker_client, config, event_stream, llm_registry
|
|
):
|
|
# Arrange
|
|
config.sandbox.keep_runtime_alive = True
|
|
runtime = DockerRuntime(config, event_stream, llm_registry, sid='test-sid')
|
|
runtime.container = mock_docker_client.containers.get.return_value
|
|
|
|
# Act
|
|
runtime.close()
|
|
|
|
# Assert
|
|
mock_stop_containers.assert_not_called()
|
|
|
|
|
|
def test_volumes_mode_extraction():
|
|
"""Test that the mount mode is correctly extracted from sandbox.volumes."""
|
|
import os
|
|
|
|
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
|
|
|
# Create a DockerRuntime instance with a mock config
|
|
runtime = DockerRuntime.__new__(DockerRuntime)
|
|
runtime.config = MagicMock()
|
|
runtime.config.sandbox.volumes = '/host/path:/container/path:ro'
|
|
runtime.config.workspace_mount_path = '/host/path'
|
|
runtime.config.workspace_mount_path_in_sandbox = '/container/path'
|
|
|
|
# Call the actual method that processes volumes
|
|
volumes = runtime._process_volumes()
|
|
|
|
# Assert that the mode was correctly set to 'ro'
|
|
assert volumes[os.path.abspath('/host/path')]['mode'] == 'ro'
|
|
|
|
|
|
# This test has been replaced by test_volumes_multiple_mounts
|
|
|
|
|
|
def test_volumes_multiple_mounts():
|
|
"""Test that multiple mounts in sandbox.volumes are correctly processed."""
|
|
import os
|
|
|
|
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
|
|
|
# Create a DockerRuntime instance with a mock config
|
|
runtime = DockerRuntime.__new__(DockerRuntime)
|
|
runtime.config = MagicMock()
|
|
runtime.config.runtime_mount = None
|
|
runtime.config.sandbox.volumes = (
|
|
'/host/path1:/container/path1,/host/path2:/container/path2:ro'
|
|
)
|
|
runtime.config.workspace_mount_path = '/host/path1'
|
|
runtime.config.workspace_mount_path_in_sandbox = '/container/path1'
|
|
|
|
# Call the actual method that processes volumes
|
|
volumes = runtime._process_volumes()
|
|
|
|
# Assert that both mounts were processed correctly
|
|
assert len(volumes) == 2
|
|
assert volumes[os.path.abspath('/host/path1')]['bind'] == '/container/path1'
|
|
assert volumes[os.path.abspath('/host/path1')]['mode'] == 'rw' # Default mode
|
|
assert volumes[os.path.abspath('/host/path2')]['bind'] == '/container/path2'
|
|
assert volumes[os.path.abspath('/host/path2')]['mode'] == 'ro' # Specified mode
|
|
|
|
|
|
def test_multiple_volumes():
|
|
"""Test that multiple volumes are correctly processed."""
|
|
import os
|
|
|
|
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
|
|
|
# Create a DockerRuntime instance with a mock config
|
|
runtime = DockerRuntime.__new__(DockerRuntime)
|
|
runtime.config = MagicMock()
|
|
runtime.config.sandbox.volumes = '/host/path1:/container/path1,/host/path2:/container/path2,/host/path3:/container/path3:ro'
|
|
runtime.config.workspace_mount_path = '/host/path1'
|
|
runtime.config.workspace_mount_path_in_sandbox = '/container/path1'
|
|
|
|
# Call the actual method that processes volumes
|
|
volumes = runtime._process_volumes()
|
|
|
|
# Assert that all mounts were processed correctly
|
|
assert len(volumes) == 3
|
|
assert volumes[os.path.abspath('/host/path1')]['bind'] == '/container/path1'
|
|
assert volumes[os.path.abspath('/host/path1')]['mode'] == 'rw'
|
|
assert volumes[os.path.abspath('/host/path2')]['bind'] == '/container/path2'
|
|
assert volumes[os.path.abspath('/host/path2')]['mode'] == 'rw'
|
|
assert volumes[os.path.abspath('/host/path3')]['bind'] == '/container/path3'
|
|
assert volumes[os.path.abspath('/host/path3')]['mode'] == 'ro'
|
|
|
|
|
|
def test_volumes_default_mode():
|
|
"""Test that the default mount mode (rw) is used when not specified in sandbox.volumes."""
|
|
import os
|
|
|
|
from openhands.runtime.impl.docker.docker_runtime import DockerRuntime
|
|
|
|
# Create a DockerRuntime instance with a mock config
|
|
runtime = DockerRuntime.__new__(DockerRuntime)
|
|
runtime.config = MagicMock()
|
|
runtime.config.sandbox.volumes = '/host/path:/container/path'
|
|
runtime.config.workspace_mount_path = '/host/path'
|
|
runtime.config.workspace_mount_path_in_sandbox = '/container/path'
|
|
|
|
# Call the actual method that processes volumes
|
|
volumes = runtime._process_volumes()
|
|
|
|
# Assert that the mode remains 'rw' (default)
|
|
assert volumes[os.path.abspath('/host/path')]['mode'] == 'rw'
|