Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions tests/tools/test_code_execution_container_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Tests for docker container_config key propagation in code_execution_tool.

Mirrors test_file_tools_container_config.py: _get_or_create_env() builds its
own container_config dict independently of terminal_tool._create_environment's
caller, so it must be pinned separately to catch the same class of drift
(container comes up missing docker_env/docker_extra_args/etc. depending on
which tool wins the race to create the shared per-task container).
"""

from unittest.mock import patch, MagicMock
import tools.code_execution_tool as code_execution_tool


def _make_env_config(**overrides):
base = {
"env_type": "docker",
"docker_image": "test-image:latest",
"singularity_image": "docker://test",
"modal_image": "test",
"daytona_image": "test",
"cwd": "/workspace",
"host_cwd": None,
"timeout": 180,
"container_cpu": 2,
"container_memory": 4096,
"container_disk": 20480,
"container_persistent": False,
"docker_volumes": [],
"docker_run_as_host_user": False,
"docker_network": True,
"docker_mount_cwd_to_workspace": True,
"docker_forward_env": ["MY_SECRET", "API_KEY"],
"docker_env": {},
"docker_extra_args": [],
"modal_mode": "auto",
"docker_persist_across_processes": True,
"docker_orphan_reaper": True,
}
base.update(overrides)
return base


class TestCodeExecutionContainerConfig:
def _run(self, env_config, task_id):
captured = {}
mock_env = MagicMock()

def fake_create_env(**kwargs):
captured.update(kwargs)
return mock_env

with patch("tools.terminal_tool._get_env_config", return_value=env_config), \
patch("tools.terminal_tool._task_env_overrides", {}), \
patch("tools.terminal_tool._active_environments", {}), \
patch("tools.terminal_tool._creation_locks", {}), \
patch("tools.terminal_tool._creation_locks_lock", __import__("threading").Lock()), \
patch("tools.terminal_tool._create_environment", side_effect=fake_create_env), \
patch("tools.terminal_tool._start_cleanup_thread"), \
patch("tools.terminal_tool._last_activity", {}), \
patch("tools.terminal_tool._env_lock", __import__("threading").Lock()), \
patch("tools.terminal_tool._resolve_container_task_id", side_effect=lambda tid: tid):
code_execution_tool._get_or_create_env(task_id)

return captured

def test_docker_env_passed(self):
cc = self._run(_make_env_config(docker_env={"HTTP_PROXY": "http://proxy:8080"}), "t1").get("container_config", {})
assert cc.get("docker_env") == {"HTTP_PROXY": "http://proxy:8080"}

def test_docker_env_defaults_to_empty_dict(self):
cfg = _make_env_config()
del cfg["docker_env"]
cc = self._run(cfg, "t2").get("container_config", {})
assert cc.get("docker_env") == {}

def test_docker_extra_args_passed(self):
cc = self._run(_make_env_config(docker_extra_args=["--network=fetcher-net"]), "t3").get("container_config", {})
assert cc.get("docker_extra_args") == ["--network=fetcher-net"]

def test_docker_extra_args_defaults_to_empty_list(self):
cfg = _make_env_config()
del cfg["docker_extra_args"]
cc = self._run(cfg, "t4").get("container_config", {})
assert cc.get("docker_extra_args") == []

def test_docker_mount_cwd_to_workspace_passed(self):
cc = self._run(_make_env_config(docker_mount_cwd_to_workspace=True), "t5").get("container_config", {})
assert cc.get("docker_mount_cwd_to_workspace") is True

def test_docker_forward_env_passed(self):
cc = self._run(_make_env_config(docker_forward_env=["MY_SECRET"]), "t6").get("container_config", {})
assert cc.get("docker_forward_env") == ["MY_SECRET"]

def test_docker_persist_across_processes_passed(self):
cc = self._run(_make_env_config(docker_persist_across_processes=False), "t7").get("container_config", {})
assert cc.get("docker_persist_across_processes") is False

def test_docker_orphan_reaper_passed(self):
cc = self._run(_make_env_config(docker_orphan_reaper=False), "t8").get("container_config", {})
assert cc.get("docker_orphan_reaper") is False

def test_modal_mode_passed(self):
cc = self._run(_make_env_config(modal_mode="sandbox"), "t9").get("container_config", {})
assert cc.get("modal_mode") == "sandbox"
29 changes: 29 additions & 0 deletions tests/tools/test_file_tools_container_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def _make_env_config(**overrides):
"docker_volumes": [],
"docker_mount_cwd_to_workspace": True,
"docker_forward_env": ["MY_SECRET", "API_KEY"],
"docker_env": {},
"docker_extra_args": [],
"modal_mode": "auto",
"docker_persist_across_processes": True,
"docker_orphan_reaper": True,
}
base.update(overrides)
return base
Expand Down Expand Up @@ -73,6 +78,30 @@ def test_docker_forward_env_defaults_to_empty_list(self):
cc = self._run(cfg, "t4").get("container_config", {})
assert cc.get("docker_forward_env") == []

def test_docker_env_passed(self):
"""docker_env is forwarded to container_config (order-dependent-container regression)."""
cc = self._run(_make_env_config(docker_env={"HTTP_PROXY": "http://proxy:8080"}), "t5").get("container_config", {})
assert cc.get("docker_env") == {"HTTP_PROXY": "http://proxy:8080"}

def test_docker_env_defaults_to_empty_dict(self):
"""docker_env defaults to {} when absent from config."""
cfg = _make_env_config()
del cfg["docker_env"]
cc = self._run(cfg, "t6").get("container_config", {})
assert cc.get("docker_env") == {}

def test_docker_extra_args_passed(self):
"""docker_extra_args is forwarded to container_config (order-dependent-container regression)."""
cc = self._run(_make_env_config(docker_extra_args=["--network=fetcher-net"]), "t7").get("container_config", {})
assert cc.get("docker_extra_args") == ["--network=fetcher-net"]

def test_docker_extra_args_defaults_to_empty_list(self):
"""docker_extra_args defaults to [] when absent from config."""
cfg = _make_env_config()
del cfg["docker_extra_args"]
cc = self._run(cfg, "t8").get("container_config", {})
assert cc.get("docker_extra_args") == []

def test_cwd_only_raw_task_override_reaches_file_environment(self):
"""CWD-only task overrides collapse to default but must keep their cwd."""
captured = self._run(
Expand Down
7 changes: 7 additions & 0 deletions tools/code_execution_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,9 +682,16 @@ def _get_or_create_env(task_id: str):
"container_memory": config.get("container_memory", 5120),
"container_disk": config.get("container_disk", 51200),
"container_persistent": config.get("container_persistent", True),
"modal_mode": config.get("modal_mode", "auto"),
"docker_volumes": config.get("docker_volumes", []),
"docker_mount_cwd_to_workspace": config.get("docker_mount_cwd_to_workspace", False),
"docker_forward_env": config.get("docker_forward_env", []),
"docker_env": config.get("docker_env", {}),
"docker_run_as_host_user": config.get("docker_run_as_host_user", False),
"docker_extra_args": config.get("docker_extra_args", []),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two keys fix part of the divergence, but the resulting config still differs from terminal_tool's container config: it omits settings such as docker_forward_env and docker_mount_cwd_to_workspace. Either narrow the PR claim or align with the linked parity work (#35660).

"docker_network": config.get("docker_network", True),
"docker_persist_across_processes": config.get("docker_persist_across_processes", True),
"docker_orphan_reaper": config.get("docker_orphan_reaper", True),
}

ssh_config = None
Expand Down
5 changes: 5 additions & 0 deletions tools/file_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1143,11 +1143,16 @@ def _get_file_ops(task_id: str = "default") -> ShellFileOperations:
"container_memory": config.get("container_memory", 5120),
"container_disk": config.get("container_disk", 51200),
"container_persistent": config.get("container_persistent", True),
"modal_mode": config.get("modal_mode", "auto"),
"docker_volumes": config.get("docker_volumes", []),
"docker_mount_cwd_to_workspace": config.get("docker_mount_cwd_to_workspace", False),
"docker_forward_env": config.get("docker_forward_env", []),
"docker_env": config.get("docker_env", {}),
"docker_run_as_host_user": config.get("docker_run_as_host_user", False),
"docker_extra_args": config.get("docker_extra_args", []),
"docker_network": config.get("docker_network", True),
"docker_persist_across_processes": config.get("docker_persist_across_processes", True),
"docker_orphan_reaper": config.get("docker_orphan_reaper", True),
}

ssh_config = None
Expand Down