fix: harden Windows exec shell default
Maintainer edit: prefer pwsh when available so single-line commands support modern PowerShell operators, add -NonInteractive, and parse explicit cmd.exe paths with Windows path semantics for cross-platform tests.
This commit is contained in:
@@ -6,6 +6,7 @@ platform-specific binaries (all subprocess calls are mocked).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import shutil
|
||||
import sys
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
@@ -127,8 +128,9 @@ class TestSpawnWindows:
|
||||
await ExecTool._spawn("dir", r"C:\work", env)
|
||||
|
||||
args = mock_exec.call_args[0]
|
||||
assert "powershell" in args[0].lower()
|
||||
assert any(shell in args[0].lower() for shell in ("pwsh", "powershell"))
|
||||
assert "-NoProfile" in args
|
||||
assert "-NonInteractive" in args
|
||||
assert "-Command" in args
|
||||
assert "dir" in args[-1]
|
||||
|
||||
@@ -161,8 +163,9 @@ class TestSpawnWindows:
|
||||
await ExecTool._spawn('python -c "print(1)\nprint(2)"', r"C:\work", env)
|
||||
|
||||
args = mock_exec.call_args[0]
|
||||
assert "powershell" in args[0].lower()
|
||||
assert any(shell in args[0].lower() for shell in ("pwsh", "powershell"))
|
||||
assert "-NoProfile" in args
|
||||
assert "-NonInteractive" in args
|
||||
assert "-Command" in args
|
||||
assert "print(1)" in args[-1]
|
||||
assert "print(2)" in args[-1]
|
||||
@@ -189,6 +192,28 @@ class TestSpawnWindows:
|
||||
kwargs = mock_shell.call_args[1]
|
||||
assert kwargs["cwd"] == r"C:\work"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prefers_pwsh_when_available(self):
|
||||
env = {"PATH": ""}
|
||||
|
||||
def fake_which(command):
|
||||
if command == "pwsh":
|
||||
return r"C:\Program Files\PowerShell\7\pwsh.exe"
|
||||
if command == "powershell":
|
||||
return r"C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
return None
|
||||
|
||||
with (
|
||||
patch("nanobot.agent.tools.shell._IS_WINDOWS", True),
|
||||
patch("nanobot.agent.tools.shell.shutil.which", side_effect=fake_which),
|
||||
patch("asyncio.create_subprocess_exec", new_callable=AsyncMock) as mock_exec,
|
||||
):
|
||||
mock_exec.return_value = AsyncMock()
|
||||
await ExecTool._spawn("dir", r"C:\work", env)
|
||||
|
||||
args = mock_exec.call_args[0]
|
||||
assert "pwsh" in args[0].lower()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# path_append
|
||||
@@ -532,7 +557,7 @@ class TestWindowsMultilineExec:
|
||||
assert "2" in result
|
||||
assert "Exit code: 0" in result
|
||||
args = mock_exec.call_args[0]
|
||||
assert "powershell" in args[0].lower()
|
||||
assert any(shell in args[0].lower() for shell in ("pwsh", "powershell"))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiline_node_uses_powershell(self):
|
||||
@@ -551,7 +576,7 @@ class TestWindowsMultilineExec:
|
||||
|
||||
assert "1" in result
|
||||
args = mock_exec.call_args[0]
|
||||
assert "powershell" in args[0].lower()
|
||||
assert any(shell in args[0].lower() for shell in ("pwsh", "powershell"))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_single_line_uses_powershell(self):
|
||||
@@ -615,6 +640,7 @@ class TestResolveShellWindows:
|
||||
assert "hello" in result
|
||||
args = mock_exec.call_args[0]
|
||||
assert "powershell" in args[0].lower()
|
||||
assert "-NonInteractive" in args
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shell_cmd_accepted(self):
|
||||
@@ -644,3 +670,18 @@ class TestResolveShellWindows:
|
||||
|
||||
assert "Error: unsupported shell" in result
|
||||
assert "Allowed: powershell, pwsh, cmd" in result
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform != "win32" or shutil.which("pwsh") is None,
|
||||
reason="requires Windows with PowerShell 7",
|
||||
)
|
||||
class TestWindowsRealExec:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_single_line_and_separator_uses_pwsh(self):
|
||||
result = await ExecTool(timeout=10).execute(command="echo before && echo after")
|
||||
|
||||
assert "before" in result
|
||||
assert "after" in result
|
||||
assert "Exit code: 0" in result
|
||||
|
||||
Reference in New Issue
Block a user