Files
hermes-agent/tests/tools/test_local_env_windows_msys.py
T
ethernet 30da5d0a89 test: run os-specific tests on their real host, not a faked one
many tests patched sys.platform or a module's _IS_WINDOWS flag, then
ran on linux ci. the patch selects the branch under test, but the host
does not have the behavior the branch exists for. the test proves the
patch, not the platform. some gated assertions never ran on any host.

this commit adds three markers: linux_only, macos_only, windows_only.
a conftest hook skips a marked test on the other hosts, with a clear
reason. no test fakes a host now. two documented fakes remain
(android/termux, freebsd) because no ci runner exists for them.

each fake site got one of four treatments:
- gate it: the real host supplies the platform; mocks cover real
  dependencies only, never host identity
- patch the module's own probe when the subject is the probe's consumer
- assert against the real host when the fake stood in for any non-x host
- delete the patch when it set the value the host already has

bare skipif(sys.platform != ...) guards became markers too. the lane
model skips these on linux and never imports them on windows, so they
ran on no host. platform parametrize tables are now one marked test
per os.

running on real hosts found real errors: a chrome-sandbox failure in
test_gui_command that main hides, and two windows failures fixed here.
the agents.md testing section now documents the policy.
2026-08-09 22:09:49 -04:00

335 lines
13 KiB
Python

"""Tests for the Windows / Git Bash MSYS-path normalization in
``LocalEnvironment``.
Background
----------
On Windows, ``pwd -P`` inside Git Bash emits paths like
``/c/Users/NVIDIA``. ``subprocess.Popen(..., cwd=...)`` only accepts
native Windows paths (``C:\\Users\\NVIDIA``), and the validation done
by ``_resolve_safe_cwd`` was also checking the MSYS form against
``os.path.isdir``, which returns ``False`` on Windows. The combined
effect was a warning logged on every single terminal call:
LocalEnvironment cwd '/c/Users/NVIDIA' is missing on disk;
falling back to '/' so terminal commands keep working.
Platform gating
---------------
These tests used to fake Windows on Linux CI by patching
``local_mod._IS_WINDOWS`` (and sometimes ``os.path.isdir``) so an MSYS
path tested as "missing" exactly like on the real OS. That inverted the
thing under test: the bug was that ``os.path.isdir("/c/Users/x")`` is
False *on Windows*, and the fake had to recreate that condition by hand
on a host where the path semantics, the drive letters, the path
separator, and Git Bash itself are all absent.
So the Windows-behaviour tests are ``windows_only`` and run on the
Windows CI job against a real Git Bash layout. The "no-op off Windows"
cases assert genuine POSIX behaviour and are ``linux_only`` — on that
host ``_IS_WINDOWS`` is already False, so no patching is needed at all.
"""
import os
from unittest.mock import patch
import pytest
from tools.environments.base import BaseEnvironment
from tools.environments import local as local_mod
from tools.environments.local import (
LocalEnvironment,
_bash_safe_path,
_git_bash_bin_dirs,
_make_run_env,
_msys_to_windows_path,
_prepend_git_bash_dirs,
_quote_bash_path,
_resolve_safe_cwd,
_sanitize_subprocess_env,
_windows_to_msys_path,
hermes_subprocess_env,
)
# ---------------------------------------------------------------------------
# _msys_to_windows_path — pure-function unit tests
# ---------------------------------------------------------------------------
class TestMsysToWindowsPath:
@pytest.mark.linux_only
def test_noop_on_non_windows(self):
# On a non-Windows host the function must never rewrite the path
# — POSIX-style paths are real paths there.
assert _msys_to_windows_path("/c/Users/NVIDIA") == "/c/Users/NVIDIA"
assert _msys_to_windows_path("/home/teknium") == "/home/teknium"
@pytest.mark.windows_only
def test_translates_drive_path(self):
assert _msys_to_windows_path("/c/Users/NVIDIA") == r"C:\Users\NVIDIA"
assert _msys_to_windows_path("/d/Projects/foo bar") == r"D:\Projects\foo bar"
@pytest.mark.windows_only
def test_empty_string(self):
assert _msys_to_windows_path("") == ""
# ---------------------------------------------------------------------------
# _windows_to_msys_path — reverse translation for bash builtin cd
# ---------------------------------------------------------------------------
class TestWindowsToMsysPath:
@pytest.mark.linux_only
def test_noop_on_non_windows(self):
assert _windows_to_msys_path(r"C:\Users\NVIDIA") == r"C:\Users\NVIDIA"
@pytest.mark.windows_only
def test_does_not_translate_non_drive_path(self):
assert _windows_to_msys_path("/tmp/foo") == "/tmp/foo"
assert _windows_to_msys_path(r"\\server\share") == r"\\server\share"
# ---------------------------------------------------------------------------
# _bash_safe_path / _quote_bash_path — shell-script interpolation
# ---------------------------------------------------------------------------
@pytest.mark.windows_only
class TestBashSafePath:
def test_native_windows_path_becomes_msys(self):
assert _bash_safe_path(r"C:\Users\alice\notes.txt") == "/c/Users/alice/notes.txt"
def test_quote_bash_path_quotes_mixed_windows_path(self):
quoted = _quote_bash_path(
r"C:\Users\Alexander\AppData\Local\Temp\hermes-snap-abc.sh"
)
assert "/c/Users/Alexander/AppData/Local/Temp/hermes-snap-abc.sh" in quoted
assert "\\" not in quoted
# ---------------------------------------------------------------------------
# _resolve_safe_cwd — Windows fast path
# ---------------------------------------------------------------------------
@pytest.mark.windows_only
class TestResolveSafeCwdWindows:
def test_msys_path_resolves_to_native_when_native_exists(self, tmp_path):
"""The whole point of this fix: a Git Bash ``/c/Users/x`` value
should resolve to its native equivalent if that native dir exists,
WITHOUT falling back to the temp dir.
``tmp_path`` is a real native directory on the Windows runner, so
its MSYS spelling is a genuine round-trip rather than a stubbed
translation.
"""
native = str(tmp_path)
msys = _windows_to_msys_path(native)
assert msys != native, "expected a drive-letter path to translate"
assert _resolve_safe_cwd(msys) == native
# ---------------------------------------------------------------------------
# End-to-end: _update_cwd via stdout marker
# ---------------------------------------------------------------------------
@pytest.mark.windows_only
class TestUpdateCwdWindowsMsys:
def test_marker_output_msys_path_stored_in_native_form(self, tmp_path):
"""When Git Bash emits ``/c/Users/x`` in the cwd marker on Windows,
``_update_cwd`` must translate to native form before
validating and storing — otherwise ``os.path.isdir`` rejects a
perfectly real directory."""
original = tmp_path / "starting"
original.mkdir()
with patch.object(
LocalEnvironment, "init_session", autospec=True, return_value=None
):
env = LocalEnvironment(cwd=str(original), timeout=10)
new_dir = tmp_path / "next"
new_dir.mkdir()
marker = env._cwd_marker
# The real MSYS spelling of a real native dir — what Git Bash
# actually writes into the marker.
msys_new = _windows_to_msys_path(str(new_dir))
env._update_cwd(
{
"output": f"x\n{marker}{msys_new}{marker}\n",
"returncode": 0,
}
)
assert env.cwd == str(new_dir)
# ---------------------------------------------------------------------------
# End-to-end: _extract_cwd_from_output rollback when marker is invalid
# ---------------------------------------------------------------------------
@pytest.mark.windows_only
class TestExtractCwdFromOutputWindowsMsys:
def test_stale_msys_marker_does_not_clobber_cwd(self, tmp_path):
"""When the cwd marker in stdout points at a non-existent path,
``LocalEnvironment._extract_cwd_from_output`` must roll back to
the previous cwd instead of propagating a bad value."""
original = tmp_path / "starting"
original.mkdir()
with patch.object(
LocalEnvironment, "init_session", autospec=True, return_value=None
):
env = LocalEnvironment(cwd=str(original), timeout=10)
marker = env._cwd_marker
gone = _windows_to_msys_path(str(tmp_path / "definitely-does-not-exist"))
result = {
"output": f"some command output\n{marker}{gone}{marker}\n",
"returncode": 0,
}
env._extract_cwd_from_output(result)
assert env.cwd == str(original)
def test_valid_msys_marker_normalized_to_native(self, tmp_path):
original = tmp_path / "starting"
original.mkdir()
new_dir = tmp_path / "next"
new_dir.mkdir()
with patch.object(
LocalEnvironment, "init_session", autospec=True, return_value=None
):
env = LocalEnvironment(cwd=str(original), timeout=10)
marker = env._cwd_marker
msys_new = _windows_to_msys_path(str(new_dir))
result = {
"output": f"x\n{marker}{msys_new}{marker}\n",
"returncode": 0,
}
env._extract_cwd_from_output(result)
assert env.cwd == str(new_dir)
# ---------------------------------------------------------------------------
# MSYS_NO_PATHCONV — native Windows command flags (#56700)
# ---------------------------------------------------------------------------
@pytest.mark.windows_only
class TestWindowsMsysPathconvDefaults:
def test_make_run_env_sets_msys_no_pathconv_on_windows(self):
run_env = _make_run_env({})
assert run_env.get("MSYS_NO_PATHCONV") == "1"
def test_sanitize_subprocess_env_sets_msys_no_pathconv_on_windows(self):
env = _sanitize_subprocess_env({})
assert env.get("MSYS_NO_PATHCONV") == "1"
def test_hermes_subprocess_env_sets_msys_no_pathconv_on_windows(self):
env = hermes_subprocess_env()
assert env.get("MSYS_NO_PATHCONV") == "1"
def test_msys2_arg_conv_excl_respects_user_override(self):
run_env = _make_run_env({"MSYS2_ARG_CONV_EXCL": "/custom"})
assert run_env.get("MSYS2_ARG_CONV_EXCL") == "/custom"
# ---------------------------------------------------------------------------
# Git Bash coreutils on PATH — non-login ``bash -c`` fallback (empty
# write_file error / terminal exit 127 when login bash is broken)
# ---------------------------------------------------------------------------
class TestGitBashCoreutilsOnPath:
def _fake_isdir(self, existing):
existing = {e.replace("\\", "/") for e in existing}
return lambda p: p.replace("\\", "/") in existing
@pytest.mark.windows_only
def test_derives_dirs_from_portablegit_layout(self, monkeypatch):
"""The PortableGit layout probe, run on the real OS.
``_find_bash`` and ``os.path.isdir`` are still stubbed — the point of
this test is the *derivation* (which sibling dirs we compute from a
bash path, and in what order), and hard-coding a fake tree keeps it
independent of which Git flavour the runner happens to have installed.
What is no longer faked is the host: the ``_IS_WINDOWS`` gate this
function opens with is genuinely True here.
"""
monkeypatch.setattr(local_mod, "_git_bash_bin_dirs_cache", None)
monkeypatch.setattr(local_mod, "_find_bash", lambda: "/pg/bin/bash.exe")
existing = {"/pg/mingw64/bin", "/pg/usr/bin", "/pg/bin"}
monkeypatch.setattr(local_mod.os.path, "isdir", self._fake_isdir(existing))
dirs = _git_bash_bin_dirs()
# Compare separator-agnostically: the derivation uses os.path.join, so
# on real Windows these come back with backslashes ("/pg\\usr\\bin").
# The subject is WHICH dirs are derived and in what ORDER, not which
# separator the host's os.path uses.
norm = [d.replace("\\", "/") for d in dirs]
# usr/bin is the load-bearing coreutils dir; mingw64 precedes it.
assert "/pg/usr/bin" in norm
assert norm.index("/pg/mingw64/bin") < norm.index("/pg/usr/bin")
# Non-existent dirs (mingw32, usr/local/bin) are excluded.
assert "/pg/mingw32/bin" not in norm
@pytest.mark.linux_only
def test_empty_off_windows(self, monkeypatch):
monkeypatch.setattr(local_mod, "_git_bash_bin_dirs_cache", None)
assert _git_bash_bin_dirs() == []
@pytest.mark.linux_only
def test_make_run_env_noop_on_posix(self, monkeypatch):
monkeypatch.setattr(local_mod, "_git_bash_bin_dirs_cache", None)
run_env = _make_run_env({"PATH": "/usr/bin:/bin"})
# No Windows git dirs injected on POSIX.
assert "mingw64" not in run_env["PATH"]
# ---------------------------------------------------------------------------
# Command wrapping — native Windows cwd must be Git Bash-friendly for cd
# ---------------------------------------------------------------------------
@pytest.mark.windows_only
class TestWrapCommandWindowsNativeCwd:
def test_wrap_command_converts_native_cwd_for_builtin_cd(self):
with patch.object(
LocalEnvironment, "init_session", autospec=True, return_value=None
):
env = LocalEnvironment(cwd=r"C:\Users\liush", timeout=10)
env._snapshot_ready = True
wrapped = env._wrap_command("pwd", r"C:\Users\liush")
assert "builtin cd -- /c/Users/liush || exit 126" in wrapped
assert r"builtin cd -- C:\Users\liush || exit 126" not in wrapped
def test_init_session_bootstrap_rewrites_backslash_snapshot_paths(self, monkeypatch):
captured = {}
def fake_run_bash(self, cmd_string, *, login=False, timeout=120, stdin_data=None):
captured.setdefault("script", cmd_string) # bootstrap only; ignore the failure-path probe
raise RuntimeError("stop after capturing bootstrap")
monkeypatch.setattr(LocalEnvironment, "_run_bash", fake_run_bash)
snap = r"C:\Users\Alexander\AppData\Local\Temp\hermes-snap-deadbeef.sh"
with patch.object(LocalEnvironment, "__init__", lambda self, **kw: None):
env = LocalEnvironment.__new__(LocalEnvironment)
BaseEnvironment.__init__(
env,
cwd=r"C:\Users\Alexander\Documents",
timeout=10,
)
env._snapshot_path = snap
env._cwd_file = snap + ".cwd"
env.init_session()
script = captured["script"]
assert "/c/Users/Alexander/AppData/Local/Temp/hermes-snap-deadbeef.sh" in script
assert r"C:\Users\Alexander\AppData" not in script