hud-python 0.4.1__py3-none-any.whl → 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,197 +1,197 @@
|
|
|
1
|
-
"""Extended tests for bash tool to improve coverage."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
from unittest.mock import AsyncMock, MagicMock, patch
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from hud.tools.bash import ToolError, _BashSession
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TestBashSessionExtended:
|
|
14
|
-
"""Extended tests for _BashSession to improve coverage."""
|
|
15
|
-
|
|
16
|
-
@pytest.mark.asyncio
|
|
17
|
-
async def test_session_start_already_started(self):
|
|
18
|
-
"""Test starting a session that's already started."""
|
|
19
|
-
session = _BashSession()
|
|
20
|
-
session._started = True
|
|
21
|
-
|
|
22
|
-
with patch("asyncio.sleep") as mock_sleep:
|
|
23
|
-
mock_sleep.return_value = None
|
|
24
|
-
await session.start()
|
|
25
|
-
|
|
26
|
-
# Should call sleep and return early
|
|
27
|
-
mock_sleep.assert_called_once_with(0)
|
|
28
|
-
|
|
29
|
-
@pytest.mark.asyncio
|
|
30
|
-
@pytest.mark.skipif(sys.platform == "win32", reason="Unix-specific test")
|
|
31
|
-
async def test_session_start_unix_preexec(self):
|
|
32
|
-
"""Test session start on Unix systems uses preexec_fn."""
|
|
33
|
-
session = _BashSession()
|
|
34
|
-
|
|
35
|
-
with patch("asyncio.create_subprocess_shell") as mock_create:
|
|
36
|
-
mock_process = MagicMock()
|
|
37
|
-
mock_create.return_value = mock_process
|
|
38
|
-
|
|
39
|
-
await session.start()
|
|
40
|
-
|
|
41
|
-
# Check that preexec_fn was passed
|
|
42
|
-
call_kwargs = mock_create.call_args[1]
|
|
43
|
-
assert "preexec_fn" in call_kwargs
|
|
44
|
-
assert call_kwargs["preexec_fn"] is not None
|
|
45
|
-
|
|
46
|
-
def test_session_stop_with_terminated_process(self):
|
|
47
|
-
"""Test stopping a session with already terminated process."""
|
|
48
|
-
session = _BashSession()
|
|
49
|
-
session._started = True
|
|
50
|
-
|
|
51
|
-
# Mock process that's already terminated
|
|
52
|
-
mock_process = MagicMock()
|
|
53
|
-
mock_process.returncode = 0 # Process already exited
|
|
54
|
-
session._process = mock_process
|
|
55
|
-
|
|
56
|
-
# Should not raise error and not call terminate
|
|
57
|
-
session.stop()
|
|
58
|
-
mock_process.terminate.assert_not_called()
|
|
59
|
-
|
|
60
|
-
def test_session_stop_with_running_process(self):
|
|
61
|
-
"""Test stopping a session with running process."""
|
|
62
|
-
session = _BashSession()
|
|
63
|
-
session._started = True
|
|
64
|
-
|
|
65
|
-
# Mock process that's still running
|
|
66
|
-
mock_process = MagicMock()
|
|
67
|
-
mock_process.returncode = None
|
|
68
|
-
session._process = mock_process
|
|
69
|
-
|
|
70
|
-
session.stop()
|
|
71
|
-
mock_process.terminate.assert_called_once()
|
|
72
|
-
|
|
73
|
-
@pytest.mark.asyncio
|
|
74
|
-
async def test_session_run_with_exited_process(self):
|
|
75
|
-
"""Test running command when process has already exited."""
|
|
76
|
-
session = _BashSession()
|
|
77
|
-
session._started = True
|
|
78
|
-
|
|
79
|
-
# Mock process that has exited
|
|
80
|
-
mock_process = MagicMock()
|
|
81
|
-
mock_process.returncode = 1
|
|
82
|
-
session._process = mock_process
|
|
83
|
-
|
|
84
|
-
with patch("asyncio.sleep") as mock_sleep:
|
|
85
|
-
mock_sleep.return_value = None
|
|
86
|
-
result = await session.run("echo test")
|
|
87
|
-
|
|
88
|
-
assert result.system == "tool must be restarted"
|
|
89
|
-
assert result.error == "bash has exited with returncode 1"
|
|
90
|
-
mock_sleep.assert_called_once_with(0)
|
|
91
|
-
|
|
92
|
-
@pytest.mark.asyncio
|
|
93
|
-
async def test_session_run_with_stderr_output(self):
|
|
94
|
-
"""Test command execution with stderr output."""
|
|
95
|
-
session = _BashSession()
|
|
96
|
-
session._started = True
|
|
97
|
-
|
|
98
|
-
# Mock process
|
|
99
|
-
mock_process = MagicMock()
|
|
100
|
-
mock_process.returncode = None
|
|
101
|
-
mock_process.stdin = MagicMock()
|
|
102
|
-
mock_process.stdin.write = MagicMock()
|
|
103
|
-
mock_process.stdin.drain = AsyncMock()
|
|
104
|
-
mock_process.stdout = MagicMock()
|
|
105
|
-
mock_process.stdout.readuntil = AsyncMock(return_value=b"stdout output\n<<exit>>\n")
|
|
106
|
-
mock_process.stderr = MagicMock()
|
|
107
|
-
mock_process.stderr.read = AsyncMock(return_value=b"stderr output\n")
|
|
108
|
-
|
|
109
|
-
session._process = mock_process
|
|
110
|
-
|
|
111
|
-
result = await session.run("command")
|
|
112
|
-
|
|
113
|
-
assert result.output == "stdout output\n"
|
|
114
|
-
assert result.error == "stderr output" # .strip() is called on stderr
|
|
115
|
-
|
|
116
|
-
@pytest.mark.asyncio
|
|
117
|
-
async def test_session_run_with_asyncio_timeout(self):
|
|
118
|
-
"""Test command execution timing out."""
|
|
119
|
-
session = _BashSession()
|
|
120
|
-
session._started = True
|
|
121
|
-
|
|
122
|
-
# Mock process
|
|
123
|
-
mock_process = MagicMock()
|
|
124
|
-
mock_process.returncode = None
|
|
125
|
-
mock_process.stdin = MagicMock()
|
|
126
|
-
mock_process.stdin.write = MagicMock()
|
|
127
|
-
mock_process.stdin.drain = AsyncMock()
|
|
128
|
-
mock_process.stdout = MagicMock()
|
|
129
|
-
# Simulate timeout
|
|
130
|
-
mock_process.stdout.readuntil = AsyncMock(side_effect=TimeoutError())
|
|
131
|
-
|
|
132
|
-
session._process = mock_process
|
|
133
|
-
|
|
134
|
-
# Should raise ToolError on timeout
|
|
135
|
-
with pytest.raises(ToolError) as exc_info:
|
|
136
|
-
await session.run("slow command")
|
|
137
|
-
|
|
138
|
-
assert "timed out" in str(exc_info.value)
|
|
139
|
-
assert "120.0 seconds" in str(exc_info.value)
|
|
140
|
-
|
|
141
|
-
@pytest.mark.asyncio
|
|
142
|
-
async def test_session_run_with_stdout_exception(self):
|
|
143
|
-
"""Test command execution with exception reading stdout."""
|
|
144
|
-
session = _BashSession()
|
|
145
|
-
session._started = True
|
|
146
|
-
|
|
147
|
-
# Mock process
|
|
148
|
-
mock_process = MagicMock()
|
|
149
|
-
mock_process.returncode = None
|
|
150
|
-
mock_process.stdin = MagicMock()
|
|
151
|
-
mock_process.stdin.write = MagicMock()
|
|
152
|
-
mock_process.stdin.drain = AsyncMock()
|
|
153
|
-
mock_process.stdout = MagicMock()
|
|
154
|
-
# Simulate other exception
|
|
155
|
-
mock_process.stdout.readuntil = AsyncMock(side_effect=Exception("Read error"))
|
|
156
|
-
|
|
157
|
-
session._process = mock_process
|
|
158
|
-
|
|
159
|
-
# The exception should bubble up
|
|
160
|
-
with pytest.raises(Exception) as exc_info:
|
|
161
|
-
await session.run("bad command")
|
|
162
|
-
|
|
163
|
-
assert "Read error" in str(exc_info.value)
|
|
164
|
-
|
|
165
|
-
@pytest.mark.asyncio
|
|
166
|
-
async def test_session_run_with_stderr_exception(self):
|
|
167
|
-
"""Test command execution with exception reading stderr."""
|
|
168
|
-
session = _BashSession()
|
|
169
|
-
session._started = True
|
|
170
|
-
|
|
171
|
-
# Mock process
|
|
172
|
-
mock_process = MagicMock()
|
|
173
|
-
mock_process.returncode = None
|
|
174
|
-
mock_process.stdin = MagicMock()
|
|
175
|
-
mock_process.stdin.write = MagicMock()
|
|
176
|
-
mock_process.stdin.drain = AsyncMock()
|
|
177
|
-
mock_process.stdout = MagicMock()
|
|
178
|
-
mock_process.stdout.readuntil = AsyncMock(return_value=b"output\n<<exit>>\n")
|
|
179
|
-
mock_process.stderr = MagicMock()
|
|
180
|
-
# Simulate stderr read error
|
|
181
|
-
mock_process.stderr.read = AsyncMock(side_effect=Exception("Stderr read error"))
|
|
182
|
-
|
|
183
|
-
session._process = mock_process
|
|
184
|
-
|
|
185
|
-
# stderr exceptions should also bubble up
|
|
186
|
-
with pytest.raises(Exception) as exc_info:
|
|
187
|
-
await session.run("command")
|
|
188
|
-
|
|
189
|
-
assert "Stderr read error" in str(exc_info.value)
|
|
190
|
-
|
|
191
|
-
def test_bash_session_different_shells(self):
|
|
192
|
-
"""Test that different shells are used on different platforms."""
|
|
193
|
-
session = _BashSession()
|
|
194
|
-
|
|
195
|
-
# Currently, _BashSession always uses /bin/bash regardless of platform
|
|
196
|
-
# This test should verify the actual implementation
|
|
197
|
-
assert session.command == "/bin/bash"
|
|
1
|
+
"""Extended tests for bash tool to improve coverage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from hud.tools.bash import ToolError, _BashSession
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestBashSessionExtended:
|
|
14
|
+
"""Extended tests for _BashSession to improve coverage."""
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_session_start_already_started(self):
|
|
18
|
+
"""Test starting a session that's already started."""
|
|
19
|
+
session = _BashSession()
|
|
20
|
+
session._started = True
|
|
21
|
+
|
|
22
|
+
with patch("asyncio.sleep") as mock_sleep:
|
|
23
|
+
mock_sleep.return_value = None
|
|
24
|
+
await session.start()
|
|
25
|
+
|
|
26
|
+
# Should call sleep and return early
|
|
27
|
+
mock_sleep.assert_called_once_with(0)
|
|
28
|
+
|
|
29
|
+
@pytest.mark.asyncio
|
|
30
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="Unix-specific test")
|
|
31
|
+
async def test_session_start_unix_preexec(self):
|
|
32
|
+
"""Test session start on Unix systems uses preexec_fn."""
|
|
33
|
+
session = _BashSession()
|
|
34
|
+
|
|
35
|
+
with patch("asyncio.create_subprocess_shell") as mock_create:
|
|
36
|
+
mock_process = MagicMock()
|
|
37
|
+
mock_create.return_value = mock_process
|
|
38
|
+
|
|
39
|
+
await session.start()
|
|
40
|
+
|
|
41
|
+
# Check that preexec_fn was passed
|
|
42
|
+
call_kwargs = mock_create.call_args[1]
|
|
43
|
+
assert "preexec_fn" in call_kwargs
|
|
44
|
+
assert call_kwargs["preexec_fn"] is not None
|
|
45
|
+
|
|
46
|
+
def test_session_stop_with_terminated_process(self):
|
|
47
|
+
"""Test stopping a session with already terminated process."""
|
|
48
|
+
session = _BashSession()
|
|
49
|
+
session._started = True
|
|
50
|
+
|
|
51
|
+
# Mock process that's already terminated
|
|
52
|
+
mock_process = MagicMock()
|
|
53
|
+
mock_process.returncode = 0 # Process already exited
|
|
54
|
+
session._process = mock_process
|
|
55
|
+
|
|
56
|
+
# Should not raise error and not call terminate
|
|
57
|
+
session.stop()
|
|
58
|
+
mock_process.terminate.assert_not_called()
|
|
59
|
+
|
|
60
|
+
def test_session_stop_with_running_process(self):
|
|
61
|
+
"""Test stopping a session with running process."""
|
|
62
|
+
session = _BashSession()
|
|
63
|
+
session._started = True
|
|
64
|
+
|
|
65
|
+
# Mock process that's still running
|
|
66
|
+
mock_process = MagicMock()
|
|
67
|
+
mock_process.returncode = None
|
|
68
|
+
session._process = mock_process
|
|
69
|
+
|
|
70
|
+
session.stop()
|
|
71
|
+
mock_process.terminate.assert_called_once()
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_session_run_with_exited_process(self):
|
|
75
|
+
"""Test running command when process has already exited."""
|
|
76
|
+
session = _BashSession()
|
|
77
|
+
session._started = True
|
|
78
|
+
|
|
79
|
+
# Mock process that has exited
|
|
80
|
+
mock_process = MagicMock()
|
|
81
|
+
mock_process.returncode = 1
|
|
82
|
+
session._process = mock_process
|
|
83
|
+
|
|
84
|
+
with patch("asyncio.sleep") as mock_sleep:
|
|
85
|
+
mock_sleep.return_value = None
|
|
86
|
+
result = await session.run("echo test")
|
|
87
|
+
|
|
88
|
+
assert result.system == "tool must be restarted"
|
|
89
|
+
assert result.error == "bash has exited with returncode 1"
|
|
90
|
+
mock_sleep.assert_called_once_with(0)
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_session_run_with_stderr_output(self):
|
|
94
|
+
"""Test command execution with stderr output."""
|
|
95
|
+
session = _BashSession()
|
|
96
|
+
session._started = True
|
|
97
|
+
|
|
98
|
+
# Mock process
|
|
99
|
+
mock_process = MagicMock()
|
|
100
|
+
mock_process.returncode = None
|
|
101
|
+
mock_process.stdin = MagicMock()
|
|
102
|
+
mock_process.stdin.write = MagicMock()
|
|
103
|
+
mock_process.stdin.drain = AsyncMock()
|
|
104
|
+
mock_process.stdout = MagicMock()
|
|
105
|
+
mock_process.stdout.readuntil = AsyncMock(return_value=b"stdout output\n<<exit>>\n")
|
|
106
|
+
mock_process.stderr = MagicMock()
|
|
107
|
+
mock_process.stderr.read = AsyncMock(return_value=b"stderr output\n")
|
|
108
|
+
|
|
109
|
+
session._process = mock_process
|
|
110
|
+
|
|
111
|
+
result = await session.run("command")
|
|
112
|
+
|
|
113
|
+
assert result.output == "stdout output\n"
|
|
114
|
+
assert result.error == "stderr output" # .strip() is called on stderr
|
|
115
|
+
|
|
116
|
+
@pytest.mark.asyncio
|
|
117
|
+
async def test_session_run_with_asyncio_timeout(self):
|
|
118
|
+
"""Test command execution timing out."""
|
|
119
|
+
session = _BashSession()
|
|
120
|
+
session._started = True
|
|
121
|
+
|
|
122
|
+
# Mock process
|
|
123
|
+
mock_process = MagicMock()
|
|
124
|
+
mock_process.returncode = None
|
|
125
|
+
mock_process.stdin = MagicMock()
|
|
126
|
+
mock_process.stdin.write = MagicMock()
|
|
127
|
+
mock_process.stdin.drain = AsyncMock()
|
|
128
|
+
mock_process.stdout = MagicMock()
|
|
129
|
+
# Simulate timeout
|
|
130
|
+
mock_process.stdout.readuntil = AsyncMock(side_effect=TimeoutError())
|
|
131
|
+
|
|
132
|
+
session._process = mock_process
|
|
133
|
+
|
|
134
|
+
# Should raise ToolError on timeout
|
|
135
|
+
with pytest.raises(ToolError) as exc_info:
|
|
136
|
+
await session.run("slow command")
|
|
137
|
+
|
|
138
|
+
assert "timed out" in str(exc_info.value)
|
|
139
|
+
assert "120.0 seconds" in str(exc_info.value)
|
|
140
|
+
|
|
141
|
+
@pytest.mark.asyncio
|
|
142
|
+
async def test_session_run_with_stdout_exception(self):
|
|
143
|
+
"""Test command execution with exception reading stdout."""
|
|
144
|
+
session = _BashSession()
|
|
145
|
+
session._started = True
|
|
146
|
+
|
|
147
|
+
# Mock process
|
|
148
|
+
mock_process = MagicMock()
|
|
149
|
+
mock_process.returncode = None
|
|
150
|
+
mock_process.stdin = MagicMock()
|
|
151
|
+
mock_process.stdin.write = MagicMock()
|
|
152
|
+
mock_process.stdin.drain = AsyncMock()
|
|
153
|
+
mock_process.stdout = MagicMock()
|
|
154
|
+
# Simulate other exception
|
|
155
|
+
mock_process.stdout.readuntil = AsyncMock(side_effect=Exception("Read error"))
|
|
156
|
+
|
|
157
|
+
session._process = mock_process
|
|
158
|
+
|
|
159
|
+
# The exception should bubble up
|
|
160
|
+
with pytest.raises(Exception) as exc_info:
|
|
161
|
+
await session.run("bad command")
|
|
162
|
+
|
|
163
|
+
assert "Read error" in str(exc_info.value)
|
|
164
|
+
|
|
165
|
+
@pytest.mark.asyncio
|
|
166
|
+
async def test_session_run_with_stderr_exception(self):
|
|
167
|
+
"""Test command execution with exception reading stderr."""
|
|
168
|
+
session = _BashSession()
|
|
169
|
+
session._started = True
|
|
170
|
+
|
|
171
|
+
# Mock process
|
|
172
|
+
mock_process = MagicMock()
|
|
173
|
+
mock_process.returncode = None
|
|
174
|
+
mock_process.stdin = MagicMock()
|
|
175
|
+
mock_process.stdin.write = MagicMock()
|
|
176
|
+
mock_process.stdin.drain = AsyncMock()
|
|
177
|
+
mock_process.stdout = MagicMock()
|
|
178
|
+
mock_process.stdout.readuntil = AsyncMock(return_value=b"output\n<<exit>>\n")
|
|
179
|
+
mock_process.stderr = MagicMock()
|
|
180
|
+
# Simulate stderr read error
|
|
181
|
+
mock_process.stderr.read = AsyncMock(side_effect=Exception("Stderr read error"))
|
|
182
|
+
|
|
183
|
+
session._process = mock_process
|
|
184
|
+
|
|
185
|
+
# stderr exceptions should also bubble up
|
|
186
|
+
with pytest.raises(Exception) as exc_info:
|
|
187
|
+
await session.run("command")
|
|
188
|
+
|
|
189
|
+
assert "Stderr read error" in str(exc_info.value)
|
|
190
|
+
|
|
191
|
+
def test_bash_session_different_shells(self):
|
|
192
|
+
"""Test that different shells are used on different platforms."""
|
|
193
|
+
session = _BashSession()
|
|
194
|
+
|
|
195
|
+
# Currently, _BashSession always uses /bin/bash regardless of platform
|
|
196
|
+
# This test should verify the actual implementation
|
|
197
|
+
assert session.command == "/bin/bash"
|