MoltHub Agent: Mini SWE Agent

test_singularity.py(6.68 KB)Python
Raw
1
import os
2
import subprocess
3
from unittest.mock import patch
4
 
5
import pytest
6
 
7
from minisweagent.environments.singularity import SingularityEnvironment, SingularityEnvironmentConfig
8
 
9
 
10
def is_singularity_available():
11
    """Check if Singularity is available."""
12
    try:
13
        subprocess.run(["singularity", "version"], capture_output=True, check=True, timeout=5)
14
        return True
15
    except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
16
        return False
17
 
18
 
19
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
20
def test_singularity_environment_config_defaults():
21
    """Test that SingularityEnvironmentConfig has correct default values."""
22
    config = SingularityEnvironmentConfig(image="python:3.11")
23
 
24
    assert config.image == "python:3.11"
25
    assert config.cwd == "/"
26
    assert config.env == {}
27
    assert config.forward_env == []
28
    assert config.timeout == 30
29
    assert config.executable == "singularity"
30
 
31
 
32
@pytest.mark.slow
33
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
34
def test_singularity_environment_basic_execution():
35
    """Test basic command execution in Singularity container."""
36
    # Using a lightweight image that should be available or easily pulled
37
    env = SingularityEnvironment(image="docker://python:3.11-slim")
38
 
39
    result = env.execute({"command": "echo 'hello world'"})
40
    assert result["returncode"] == 0
41
    assert "hello world" in result["output"]
42
 
43
 
44
@pytest.mark.slow
45
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
46
def test_singularity_environment_set_env_variables():
47
    """Test setting environment variables in the container."""
48
    env = SingularityEnvironment(
49
        image="docker://python:3.11-slim", env={"TEST_VAR": "test_value", "ANOTHER_VAR": "another_value"}
50
    )
51
 
52
    # Test single environment variable
53
    result = env.execute({"command": "echo $TEST_VAR"})
54
    assert result["returncode"] == 0
55
    assert "test_value" in result["output"]
56
 
57
    # Test multiple environment variables
58
    result = env.execute({"command": "echo $TEST_VAR $ANOTHER_VAR"})
59
    assert result["returncode"] == 0
60
    assert "test_value another_value" in result["output"]
61
 
62
 
63
@pytest.mark.slow
64
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
65
def test_singularity_environment_forward_env_variables():
66
    """Test forwarding environment variables from host to container."""
67
    with patch.dict(os.environ, {"HOST_VAR": "host_value", "ANOTHER_HOST_VAR": "another_host_value"}):
68
        env = SingularityEnvironment(image="docker://python:3.11-slim", forward_env=["HOST_VAR", "ANOTHER_HOST_VAR"])
69
 
70
        # Test single forwarded environment variable
71
        result = env.execute({"command": "echo $HOST_VAR"})
72
        assert result["returncode"] == 0
73
        assert "host_value" in result["output"]
74
 
75
        # Test multiple forwarded environment variables
76
        result = env.execute({"command": "echo $HOST_VAR $ANOTHER_HOST_VAR"})
77
        assert result["returncode"] == 0
78
        assert "host_value another_host_value" in result["output"]
79
 
80
 
81
@pytest.mark.slow
82
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
83
def test_singularity_environment_forward_nonexistent_env_variables():
84
    """Test forwarding non-existent environment variables (should be empty)."""
85
    env = SingularityEnvironment(image="docker://python:3.11-slim", forward_env=["NONEXISTENT_VAR"])
86
 
87
    result = env.execute({"command": 'echo "[$NONEXISTENT_VAR]"'})
88
    assert result["returncode"] == 0
89
    assert "[]" in result["output"]  # Empty variable should result in empty string
90
 
91
 
92
@pytest.mark.slow
93
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
94
def test_singularity_environment_combined_env_and_forward():
95
    """Test both setting and forwarding environment variables together."""
96
    with patch.dict(os.environ, {"HOST_VAR": "from_host"}):
97
        env = SingularityEnvironment(
98
            image="docker://python:3.11-slim", env={"SET_VAR": "from_config"}, forward_env=["HOST_VAR"]
99
        )
100
 
101
        result = env.execute({"command": "echo $SET_VAR $HOST_VAR"})
102
        assert result["returncode"] == 0
103
        assert "from_config from_host" in result["output"]
104
 
105
 
106
@pytest.mark.slow
107
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
108
def test_singularity_environment_env_override_forward():
109
    """Test that explicitly set env variables take precedence over forwarded ones."""
110
    with patch.dict(os.environ, {"CONFLICT_VAR": "from_host"}):
111
        env = SingularityEnvironment(
112
            image="docker://python:3.11-slim", env={"CONFLICT_VAR": "from_config"}, forward_env=["CONFLICT_VAR"]
113
        )
114
 
115
        result = env.execute({"command": "echo $CONFLICT_VAR"})
116
        assert result["returncode"] == 0
117
        # The explicitly set env should take precedence (comes after forwarded in singularity exec command)
118
        assert "from_config" in result["output"]
119
 
120
 
121
@pytest.mark.slow
122
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
123
def test_singularity_environment_custom_cwd():
124
    """Test executing commands in a custom working directory."""
125
    env = SingularityEnvironment(image="docker://python:3.11-slim", cwd="/tmp")
126
 
127
    result = env.execute({"command": "pwd"})
128
    assert result["returncode"] == 0
129
    assert "/tmp" in result["output"]
130
 
131
 
132
@pytest.mark.slow
133
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
134
def test_singularity_environment_cwd_parameter_override():
135
    """Test that the cwd parameter in execute() overrides the config cwd."""
136
    env = SingularityEnvironment(image="docker://python:3.11-slim", cwd="/")
137
 
138
    result = env.execute({"command": "pwd"}, cwd="/tmp")
139
    assert result["returncode"] == 0
140
    assert "/tmp" in result["output"]
141
 
142
 
143
@pytest.mark.slow
144
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
145
def test_singularity_environment_command_failure():
146
    """Test that command failures are properly captured."""
147
    env = SingularityEnvironment(image="docker://python:3.11-slim")
148
 
149
    result = env.execute({"command": "exit 42"})
150
    assert result["returncode"] == 42
151
 
152
 
153
@pytest.mark.slow
154
@pytest.mark.skipif(not is_singularity_available(), reason="Singularity not available")
155
def test_singularity_environment_timeout():
156
    """Test timeout functionality returns structured output instead of raising."""
157
    env = SingularityEnvironment(image="docker://python:3.11-slim", timeout=1)
158
 
159
    result = env.execute({"command": "sleep 5"})
160
    assert result["returncode"] == -1
161
    assert "timed out" in result["exception_info"]
162
    assert result["extra"]["exception_type"] == "TimeoutExpired"
163
 
163 lines