diff --git a/tests/tools/test_local_env_windows_msys.py b/tests/tools/test_local_env_windows_msys.py index 529e8b2f2ae..777935b17ec 100644 --- a/tests/tools/test_local_env_windows_msys.py +++ b/tests/tools/test_local_env_windows_msys.py @@ -26,6 +26,7 @@ from tools.environments.local import ( LocalEnvironment, _msys_to_windows_path, _resolve_safe_cwd, + _windows_to_msys_path, ) @@ -70,6 +71,35 @@ class TestMsysToWindowsPath: assert _msys_to_windows_path("") == "" +# --------------------------------------------------------------------------- +# _windows_to_msys_path — reverse translation for bash builtin cd +# --------------------------------------------------------------------------- + +class TestWindowsToMsysPath: + def test_noop_on_non_windows(self, monkeypatch): + monkeypatch.setattr(local_mod, "_IS_WINDOWS", False) + assert _windows_to_msys_path(r"C:\Users\NVIDIA") == r"C:\Users\NVIDIA" + + def test_translates_backslash_path(self, monkeypatch): + monkeypatch.setattr(local_mod, "_IS_WINDOWS", True) + assert _windows_to_msys_path(r"C:\Users\NVIDIA") == "/c/Users/NVIDIA" + assert _windows_to_msys_path(r"D:\Projects\foo bar") == "/d/Projects/foo bar" + + def test_translates_forward_slash_native_path(self, monkeypatch): + monkeypatch.setattr(local_mod, "_IS_WINDOWS", True) + assert _windows_to_msys_path("C:/Users/NVIDIA") == "/c/Users/NVIDIA" + + def test_translates_drive_root(self, monkeypatch): + monkeypatch.setattr(local_mod, "_IS_WINDOWS", True) + assert _windows_to_msys_path(r"C:\\") == "/c/" + assert _windows_to_msys_path("D:/") == "/d/" + + def test_does_not_translate_non_drive_path(self, monkeypatch): + monkeypatch.setattr(local_mod, "_IS_WINDOWS", True) + assert _windows_to_msys_path("/tmp/foo") == "/tmp/foo" + assert _windows_to_msys_path(r"\\server\share") == r"\\server\share" + + # --------------------------------------------------------------------------- # _resolve_safe_cwd — Windows fast path # --------------------------------------------------------------------------- @@ -196,3 +226,23 @@ class TestExtractCwdFromOutputWindowsMsys: env._extract_cwd_from_output(result) assert env.cwd == str(new_dir) + + +# --------------------------------------------------------------------------- +# Command wrapping — native Windows cwd must be Git Bash-friendly for cd +# --------------------------------------------------------------------------- + +class TestWrapCommandWindowsNativeCwd: + def test_wrap_command_converts_native_cwd_for_builtin_cd(self, monkeypatch): + monkeypatch.setattr(local_mod, "_IS_WINDOWS", True) + + 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 diff --git a/tools/environments/local.py b/tools/environments/local.py index 528fd53298c..bccba38882c 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -40,6 +40,24 @@ def _msys_to_windows_path(cwd: str) -> str: return f"{drive}:{tail or chr(92)}" # chr(92) = backslash, avoid raw-string escape +def _windows_to_msys_path(cwd: str) -> str: + """Translate a native Windows path (``C:\\Users\\x``) to Git Bash / + MSYS form (``/c/Users/x``) so ``builtin cd`` resolves it reliably. + + No-ops on non-Windows hosts or for paths that aren't drive-qualified + native Windows paths. Returns the input unchanged when no translation + applies. + """ + if not _IS_WINDOWS or not cwd: + return cwd + m = re.match(r'^([a-zA-Z]):[\\/]*(.*)$', cwd) + if not m: + return cwd + drive = m.group(1).lower() + tail = (m.group(2) or "").replace('\\', '/').lstrip('/') + return f"/{drive}/{tail}" if tail else f"/{drive}/" + + def _resolve_safe_cwd(cwd: str) -> str: """Return ``cwd`` if it exists as a directory, else the nearest existing ancestor. Falls back to ``tempfile.gettempdir()`` only if walking up the @@ -921,6 +939,11 @@ class LocalEnvironment(BaseEnvironment): return "/tmp" + @staticmethod + def _quote_cwd_for_cd(cwd: str) -> str: + """Use native paths for Python, but Git Bash-friendly paths for cd.""" + return BaseEnvironment._quote_cwd_for_cd(_windows_to_msys_path(cwd)) + def _run_bash(self, cmd_string: str, *, login: bool = False, timeout: int = 120, stdin_data: str | None = None) -> subprocess.Popen: