193 lines
5.9 KiB
Python
193 lines
5.9 KiB
Python
"""Tests for shell resolution helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from openharness.config.settings import Settings
|
|
from openharness.utils.shell import _bash_is_usable, create_shell_subprocess, resolve_shell_command
|
|
|
|
|
|
def test_resolve_shell_command_prefers_bash_on_linux(monkeypatch):
|
|
monkeypatch.setattr(
|
|
"openharness.utils.shell.shutil.which",
|
|
lambda name: "/usr/bin/bash" if name == "bash" else None,
|
|
)
|
|
|
|
command = resolve_shell_command("echo hi", platform_name="linux")
|
|
|
|
assert command == ["/usr/bin/bash", "-lc", "echo hi"]
|
|
|
|
|
|
def test_resolve_shell_command_wraps_with_script_when_pty_requested(monkeypatch):
|
|
def fake_which(name: str) -> str | None:
|
|
mapping = {
|
|
"bash": "/usr/bin/bash",
|
|
"script": "/usr/bin/script",
|
|
}
|
|
return mapping.get(name)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.shutil.which", fake_which)
|
|
|
|
command = resolve_shell_command("echo hi", platform_name="linux", prefer_pty=True)
|
|
|
|
assert command == ["/usr/bin/script", "-qefc", "echo hi", "/dev/null"]
|
|
|
|
|
|
def test_resolve_shell_command_uses_powershell_on_windows(monkeypatch):
|
|
def fake_which(name: str) -> str | None:
|
|
mapping = {
|
|
"pwsh": "C:/Program Files/PowerShell/7/pwsh.exe",
|
|
}
|
|
return mapping.get(name)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.shutil.which", fake_which)
|
|
monkeypatch.setattr("openharness.utils.shell._bash_is_usable", lambda _: False)
|
|
|
|
command = resolve_shell_command("Write-Output hi", platform_name="windows")
|
|
|
|
assert command == [
|
|
"C:/Program Files/PowerShell/7/pwsh.exe",
|
|
"-NoLogo",
|
|
"-NoProfile",
|
|
"-Command",
|
|
"Write-Output hi",
|
|
]
|
|
|
|
|
|
def test_resolve_shell_command_skips_script_on_macos(monkeypatch):
|
|
def fake_which(name: str) -> str | None:
|
|
mapping = {
|
|
"bash": "/bin/bash",
|
|
"script": "/usr/bin/script",
|
|
}
|
|
return mapping.get(name)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.shutil.which", fake_which)
|
|
|
|
command = resolve_shell_command("echo hi", platform_name="macos", prefer_pty=True)
|
|
|
|
assert command == ["/bin/bash", "-lc", "echo hi"]
|
|
|
|
|
|
def test_resolve_shell_command_linux_without_script_falls_back(monkeypatch):
|
|
def fake_which(name: str) -> str | None:
|
|
mapping = {
|
|
"bash": "/usr/bin/bash",
|
|
}
|
|
return mapping.get(name)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.shutil.which", fake_which)
|
|
|
|
command = resolve_shell_command("echo hi", platform_name="linux", prefer_pty=True)
|
|
|
|
assert command == ["/usr/bin/bash", "-lc", "echo hi"]
|
|
|
|
|
|
def test_resolve_shell_command_windows_skips_unusable_bash(monkeypatch):
|
|
def fake_which(name: str) -> str | None:
|
|
mapping = {
|
|
"bash": "C:/Windows/System32/bash.exe",
|
|
"powershell": "C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
|
|
}
|
|
return mapping.get(name)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.shutil.which", fake_which)
|
|
monkeypatch.setattr("openharness.utils.shell._bash_is_usable", lambda _: False)
|
|
|
|
command = resolve_shell_command("Write-Output hi", platform_name="windows")
|
|
|
|
assert command == [
|
|
"C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
|
|
"-NoLogo",
|
|
"-NoProfile",
|
|
"-Command",
|
|
"Write-Output hi",
|
|
]
|
|
|
|
|
|
def test_resolve_shell_command_windows_uses_usable_bash(monkeypatch):
|
|
def fake_which(name: str) -> str | None:
|
|
mapping = {
|
|
"bash": "C:/Program Files/Git/bin/bash.exe",
|
|
"powershell": "C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
|
|
}
|
|
return mapping.get(name)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.shutil.which", fake_which)
|
|
monkeypatch.setattr("openharness.utils.shell._bash_is_usable", lambda _: True)
|
|
|
|
command = resolve_shell_command("echo hi", platform_name="windows")
|
|
|
|
assert command == ["C:/Program Files/Git/bin/bash.exe", "-lc", "echo hi"]
|
|
|
|
|
|
def test_bash_is_usable_returns_true_for_zero_exit(monkeypatch):
|
|
class _Result:
|
|
returncode = 0
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.subprocess.run", lambda *args, **kwargs: _Result())
|
|
|
|
assert _bash_is_usable("bash") is True
|
|
|
|
|
|
def test_bash_is_usable_returns_false_for_nonzero_exit(monkeypatch):
|
|
class _Result:
|
|
returncode = 1
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.subprocess.run", lambda *args, **kwargs: _Result())
|
|
|
|
assert _bash_is_usable("bash") is False
|
|
|
|
|
|
def test_bash_is_usable_returns_false_for_spawn_errors(monkeypatch):
|
|
def raise_timeout(*args, **kwargs):
|
|
raise subprocess.TimeoutExpired(cmd="bash", timeout=5)
|
|
|
|
monkeypatch.setattr("openharness.utils.shell.subprocess.run", raise_timeout)
|
|
|
|
assert _bash_is_usable("bash") is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_shell_subprocess_defaults_stdin_to_devnull(monkeypatch, tmp_path: Path):
|
|
captured: dict[str, object] = {}
|
|
|
|
async def fake_create_subprocess_exec(*args, **kwargs):
|
|
captured["args"] = args
|
|
captured["kwargs"] = kwargs
|
|
|
|
class _FakeProcess:
|
|
returncode = 0
|
|
|
|
async def wait(self):
|
|
return 0
|
|
|
|
return _FakeProcess()
|
|
|
|
monkeypatch.setattr(
|
|
"openharness.utils.shell.asyncio.create_subprocess_exec",
|
|
fake_create_subprocess_exec,
|
|
)
|
|
monkeypatch.setattr(
|
|
"openharness.utils.shell.wrap_command_for_sandbox",
|
|
lambda argv, settings=None: (argv, None),
|
|
)
|
|
monkeypatch.setattr(
|
|
"openharness.utils.shell.shutil.which",
|
|
lambda name: "/usr/bin/bash" if name == "bash" else None,
|
|
)
|
|
|
|
await create_shell_subprocess(
|
|
"echo hi",
|
|
cwd=tmp_path,
|
|
settings=Settings(),
|
|
)
|
|
|
|
assert captured["args"] == ("/usr/bin/bash", "-lc", "echo hi")
|
|
assert captured["kwargs"]["stdin"] is asyncio.subprocess.DEVNULL
|