| import os |
| import subprocess |
| from unittest.mock import patch |
|
|
| import pytest |
|
|
| from minisweagent.environments.docker import DockerEnvironment, DockerEnvironmentConfig |
|
|
|
|
| def is_docker_available(): |
| """Check if Docker is available and running.""" |
| try: |
| subprocess.run(["docker", "version"], capture_output=True, check=True, timeout=5) |
| return True |
| except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): |
| return False |
|
|
|
|
| def is_podman_available(): |
| """Check if Podman is available and running.""" |
| try: |
| subprocess.run(["podman", "version"], capture_output=True, check=True, timeout=5) |
| return True |
| except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired): |
| return False |
|
|
|
|
| |
| environment_params = [ |
| pytest.param( |
| "docker", |
| marks=pytest.mark.skipif(not is_docker_available(), reason="Docker not available"), |
| id="docker", |
| ), |
| pytest.param( |
| "podman", |
| marks=pytest.mark.skipif(not is_podman_available(), reason="Podman not available"), |
| id="podman", |
| ), |
| ] |
|
|
|
|
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_config_defaults(executable): |
| """Test that DockerEnvironmentConfig has correct default values.""" |
| config = DockerEnvironmentConfig(image="python:3.11", executable=executable) |
|
|
| assert config.image == "python:3.11" |
| assert config.cwd == "/" |
| assert config.env == {} |
| assert config.forward_env == [] |
| assert config.timeout == 30 |
| assert config.executable == executable |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_basic_execution(executable): |
| """Test basic command execution in Docker container.""" |
| env = DockerEnvironment(image="python:3.11", executable=executable) |
|
|
| try: |
| result = env.execute("echo 'hello world'") |
| assert result["returncode"] == 0 |
| assert "hello world" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_set_env_variables(executable): |
| """Test setting environment variables in the container.""" |
| env = DockerEnvironment( |
| image="python:3.11", executable=executable, env={"TEST_VAR": "test_value", "ANOTHER_VAR": "another_value"} |
| ) |
|
|
| try: |
| |
| result = env.execute("echo $TEST_VAR") |
| assert result["returncode"] == 0 |
| assert "test_value" in result["output"] |
|
|
| |
| result = env.execute("echo $TEST_VAR $ANOTHER_VAR") |
| assert result["returncode"] == 0 |
| assert "test_value another_value" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_forward_env_variables(executable): |
| """Test forwarding environment variables from host to container.""" |
| with patch.dict(os.environ, {"HOST_VAR": "host_value", "ANOTHER_HOST_VAR": "another_host_value"}): |
| env = DockerEnvironment( |
| image="python:3.11", executable=executable, forward_env=["HOST_VAR", "ANOTHER_HOST_VAR"] |
| ) |
|
|
| try: |
| |
| result = env.execute("echo $HOST_VAR") |
| assert result["returncode"] == 0 |
| assert "host_value" in result["output"] |
|
|
| |
| result = env.execute("echo $HOST_VAR $ANOTHER_HOST_VAR") |
| assert result["returncode"] == 0 |
| assert "host_value another_host_value" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_forward_nonexistent_env_variables(executable): |
| """Test forwarding non-existent environment variables (should be empty).""" |
| env = DockerEnvironment(image="python:3.11", executable=executable, forward_env=["NONEXISTENT_VAR"]) |
|
|
| try: |
| result = env.execute('echo "[$NONEXISTENT_VAR]"') |
| assert result["returncode"] == 0 |
| assert "[]" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_combined_env_and_forward(executable): |
| """Test both setting and forwarding environment variables together.""" |
| with patch.dict(os.environ, {"HOST_VAR": "from_host"}): |
| env = DockerEnvironment( |
| image="python:3.11", executable=executable, env={"SET_VAR": "from_config"}, forward_env=["HOST_VAR"] |
| ) |
|
|
| try: |
| result = env.execute("echo $SET_VAR $HOST_VAR") |
| assert result["returncode"] == 0 |
| assert "from_config from_host" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_env_override_forward(executable): |
| """Test that explicitly set env variables take precedence over forwarded ones.""" |
| with patch.dict(os.environ, {"CONFLICT_VAR": "from_host"}): |
| env = DockerEnvironment( |
| image="python:3.11", |
| executable=executable, |
| env={"CONFLICT_VAR": "from_config"}, |
| forward_env=["CONFLICT_VAR"], |
| ) |
|
|
| try: |
| result = env.execute("echo $CONFLICT_VAR") |
| assert result["returncode"] == 0 |
| |
| assert "from_config" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_custom_cwd(executable): |
| """Test executing commands in a custom working directory.""" |
| env = DockerEnvironment(image="python:3.11", executable=executable, cwd="/tmp") |
|
|
| try: |
| result = env.execute("pwd") |
| assert result["returncode"] == 0 |
| assert "/tmp" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_cwd_parameter_override(executable): |
| """Test that the cwd parameter in execute() overrides the config cwd.""" |
| env = DockerEnvironment(image="python:3.11", executable=executable, cwd="/") |
|
|
| try: |
| result = env.execute("pwd", cwd="/tmp") |
| assert result["returncode"] == 0 |
| assert "/tmp" in result["output"] |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_command_failure(executable): |
| """Test that command failures are properly captured.""" |
| env = DockerEnvironment(image="python:3.11", executable=executable) |
|
|
| try: |
| result = env.execute("exit 42") |
| assert result["returncode"] == 42 |
| finally: |
| env.cleanup() |
|
|
|
|
| @pytest.mark.slow |
| @pytest.mark.parametrize("executable", environment_params) |
| def test_docker_environment_custom_container_timeout(executable): |
| """Test that custom container_timeout is respected.""" |
| import time |
|
|
| env = DockerEnvironment(image="python:3.11", executable=executable, container_timeout="3s") |
|
|
| try: |
| result = env.execute("echo 'container is running'") |
| assert result["returncode"] == 0 |
| assert "container is running" in result["output"] |
| time.sleep(5) |
| with pytest.raises((subprocess.CalledProcessError, subprocess.TimeoutExpired)): |
| |
| subprocess.run( |
| [executable, "exec", env.container_id, "echo", "still running"], |
| check=True, |
| capture_output=True, |
| timeout=2, |
| ) |
| finally: |
| env.cleanup() |
|
|