mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
* fix(scripts): find Git Bash under a PortableGit mingw64 layout `scripts/run-with-git-bash.cmd` derives `bash.exe` from `git.exe`'s parent, which only holds for the Git for Windows `<root>/cmd` and `<root>/bin` layouts. PortableGit and GitHub Desktop ship `git.exe` in `<root>/mingw64/bin`, so the wrapper looked for `<root>/mingw64/bin/bash.exe` and reported "Could not locate Git for Windows Bash" even though `<root>/bin/bash.exe` exists. Every Makefile recipe that runs a repository shell script failed on those hosts. Resolve the shell under the Git installation root instead, and mirror the rule in `backend/tests/support/shell.py` (documented as mirroring the wrapper) so the shell-script suites stop skipping on the same hosts. * test(backend): pin both Git layouts in one shell-discovery suite Review follow-up for the PortableGit probe: - `_git_roots` now probes `git.exe`'s parent and grandparent unconditionally — exactly the two directories the Makefile wrapper checks — so the batch probe and the Python mirror cannot drift; the docstring records that the first `mingw64` candidate normally misses and is kept only to mirror it. - fold the candidate-layout cases and the Windows-only wrapper case into `tests/test_support_shell.py`, which already owns this module's unit tests, instead of a second file with a duplicate `_touch` helper. - report the wrapper's stdout in the failure message: its diagnostic goes to stdout, so `stderr` was empty on the discovery-failure path.
205 lines
7.6 KiB
Python
205 lines
7.6 KiB
Python
"""Unit tests for the Windows shell discovery in tests/support/shell.py.
|
|
|
|
``find_script_bash``/``find_posix_sh`` return at the ``os.name != "nt"``
|
|
early-out on POSIX CI, so the candidate-rejection rules would otherwise only
|
|
ever run on contributors' Windows machines -- exactly the failure mode the
|
|
discovery exists to fix. The candidate construction and the rejection scan are
|
|
pinned here against real files (``tmp_path``) with faked inputs, so the logic
|
|
is exercised on every CI leg.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from support import shell
|
|
|
|
|
|
def _touch(path: Path) -> Path:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("", encoding="utf-8")
|
|
return path
|
|
|
|
|
|
@pytest.fixture()
|
|
def fake_windows(tmp_path: Path) -> Path:
|
|
"""A fake Windows layout: git root, ProgramFiles Git, WSL + Store stubs."""
|
|
_touch(tmp_path / "git" / "cmd" / "git.exe")
|
|
_touch(tmp_path / "git" / "bin" / "bash.exe")
|
|
_touch(tmp_path / "git" / "bin" / "sh.exe")
|
|
_touch(tmp_path / "git" / "usr" / "bin" / "sh.exe")
|
|
_touch(tmp_path / "ProgramFiles" / "Git" / "bin" / "bash.exe")
|
|
_touch(tmp_path / "ProgramFiles" / "Git" / "usr" / "bin" / "sh.exe")
|
|
_touch(tmp_path / "Windows" / "System32" / "bash.exe") # WSL launcher
|
|
_touch(tmp_path / "Windows" / "SysWOW64" / "bash.exe") # 32-bit WSL launcher
|
|
_touch(tmp_path / "WindowsApps" / "bash.exe") # Microsoft Store alias stub
|
|
return tmp_path
|
|
|
|
|
|
def _system_root(root: Path) -> Path:
|
|
return root / "Windows"
|
|
|
|
|
|
def test_system32_bash_rejected_even_when_only_path_hit(fake_windows: Path):
|
|
wsl_launcher = fake_windows / "Windows" / "System32" / "bash.exe"
|
|
|
|
result = shell._first_runnable_shell([wsl_launcher], _system_root(fake_windows))
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_syswow64_bash_rejected(fake_windows: Path):
|
|
syswow64 = fake_windows / "Windows" / "SysWOW64" / "bash.exe"
|
|
|
|
result = shell._first_runnable_shell([syswow64], _system_root(fake_windows))
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_windowsapps_store_stub_rejected(fake_windows: Path):
|
|
store_stub = fake_windows / "WindowsApps" / "bash.exe"
|
|
|
|
result = shell._first_runnable_shell([store_stub], _system_root(fake_windows))
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_rejected_candidates_fall_through_to_real_shell(fake_windows: Path):
|
|
wsl_launcher = fake_windows / "Windows" / "System32" / "bash.exe"
|
|
git_bash = fake_windows / "git" / "bin" / "bash.exe"
|
|
|
|
result = shell._first_runnable_shell([wsl_launcher, git_bash], _system_root(fake_windows))
|
|
|
|
assert result == str(git_bash.resolve())
|
|
|
|
|
|
def test_missing_candidates_are_skipped(fake_windows: Path):
|
|
missing = fake_windows / "no-such-dir" / "bash.exe"
|
|
git_bash = fake_windows / "git" / "bin" / "bash.exe"
|
|
|
|
result = shell._first_runnable_shell([missing, git_bash], _system_root(fake_windows))
|
|
|
|
assert result == str(git_bash.resolve())
|
|
|
|
|
|
def test_no_runnable_candidate_returns_none(fake_windows: Path):
|
|
missing = fake_windows / "no-such-dir" / "bash.exe"
|
|
wsl_launcher = fake_windows / "Windows" / "System32" / "bash.exe"
|
|
|
|
result = shell._first_runnable_shell([missing, wsl_launcher], _system_root(fake_windows))
|
|
|
|
assert result is None
|
|
|
|
|
|
def test_git_derived_bash_candidate_comes_first(fake_windows: Path):
|
|
git_exe = str(fake_windows / "git" / "cmd" / "git.exe")
|
|
path_bash = str(fake_windows / "elsewhere" / "bash.exe")
|
|
|
|
candidates = shell._script_bash_candidates(git=git_exe, program_files=None, path_bash=path_bash)
|
|
|
|
git_root = (fake_windows / "git" / "cmd" / "git.exe").resolve().parent.parent
|
|
assert candidates == [
|
|
git_root / "bin" / "bash.exe",
|
|
git_root.parent / "bin" / "bash.exe",
|
|
Path(path_bash),
|
|
]
|
|
|
|
|
|
def test_program_files_bash_used_when_git_absent(fake_windows: Path):
|
|
program_files = fake_windows / "ProgramFiles"
|
|
|
|
candidates = shell._script_bash_candidates(git=None, program_files=str(program_files), path_bash=None)
|
|
|
|
assert candidates == [program_files / "Git" / "bin" / "bash.exe"]
|
|
|
|
|
|
def test_posix_sh_prefers_git_sh_exe(fake_windows: Path):
|
|
git_exe = str(fake_windows / "git" / "cmd" / "git.exe")
|
|
|
|
candidates = shell._script_sh_candidates(git=git_exe, program_files=None)
|
|
|
|
git_root = (fake_windows / "git" / "cmd" / "git.exe").resolve().parent.parent
|
|
assert candidates == [
|
|
git_root / "bin" / "sh.exe",
|
|
git_root / "usr" / "bin" / "sh.exe",
|
|
git_root.parent / "bin" / "sh.exe",
|
|
git_root.parent / "usr" / "bin" / "sh.exe",
|
|
]
|
|
|
|
|
|
def test_posix_sh_falls_back_to_program_files(fake_windows: Path):
|
|
program_files = fake_windows / "ProgramFiles"
|
|
|
|
candidates = shell._script_sh_candidates(git=None, program_files=str(program_files))
|
|
|
|
assert candidates == [
|
|
program_files / "Git" / "bin" / "sh.exe",
|
|
program_files / "Git" / "usr" / "bin" / "sh.exe",
|
|
]
|
|
|
|
|
|
def _portablegit_git(fake_windows: Path) -> tuple[Path, str]:
|
|
"""Create a PortableGit layout (``<root>/mingw64/bin/git.exe``) and return it."""
|
|
git_exe = _touch(fake_windows / "PortableGit" / "mingw64" / "bin" / "git.exe")
|
|
return (fake_windows / "PortableGit").resolve(), str(git_exe)
|
|
|
|
|
|
def test_portablegit_bash_candidate_reaches_the_git_root(fake_windows: Path):
|
|
git_root, git_exe = _portablegit_git(fake_windows)
|
|
|
|
candidates = shell._script_bash_candidates(git=git_exe, program_files=None, path_bash=None)
|
|
|
|
assert candidates == [git_root / "mingw64" / "bin" / "bash.exe", git_root / "bin" / "bash.exe"]
|
|
|
|
|
|
def test_portablegit_posix_sh_candidate_reaches_the_git_root(fake_windows: Path):
|
|
git_root, git_exe = _portablegit_git(fake_windows)
|
|
|
|
candidates = shell._script_sh_candidates(git=git_exe, program_files=None)
|
|
|
|
assert candidates == [
|
|
git_root / "mingw64" / "bin" / "sh.exe",
|
|
git_root / "mingw64" / "usr" / "bin" / "sh.exe",
|
|
git_root / "bin" / "sh.exe",
|
|
git_root / "usr" / "bin" / "sh.exe",
|
|
]
|
|
|
|
|
|
@pytest.mark.skipif(os.name != "nt", reason="the Makefile shell wrapper is a cmd.exe script")
|
|
def test_makefile_shell_wrapper_runs_portablegit_bash(tmp_path: Path):
|
|
"""`make` reaches Git Bash when `where git` reports the mingw64 layout.
|
|
|
|
A bare ``cmd.exe`` copy stands in for ``bash.exe``: the wrapper only has to
|
|
locate and launch it, and ``cmd /c echo`` keeps the assertion offline.
|
|
"""
|
|
git_root = tmp_path / "PortableGit"
|
|
_touch(git_root / "mingw64" / "bin" / "git.exe")
|
|
bash = git_root / "bin" / "bash.exe"
|
|
system_root = Path(os.environ.get("SystemRoot", r"C:\Windows"))
|
|
bash.parent.mkdir()
|
|
shutil.copy(system_root / "System32" / "cmd.exe", bash)
|
|
wrapper = Path(__file__).resolve().parents[2] / "scripts" / "run-with-git-bash.cmd"
|
|
|
|
env = {**os.environ, "PATH": os.pathsep.join([str(git_root / "mingw64" / "bin"), str(system_root / "System32")])}
|
|
result = subprocess.run(["cmd", "/c", str(wrapper), "/c", "echo", "BASH_OK"], capture_output=True, text=True, env=env, timeout=60)
|
|
|
|
assert result.returncode == 0, result.stdout + result.stderr
|
|
assert "BASH_OK" in result.stdout
|
|
|
|
|
|
@pytest.mark.skipif(os.name != "nt", reason="real Windows environment reads")
|
|
def test_find_script_bash_never_returns_wsl_or_store_stubs():
|
|
"""On a real Windows host the result must not be a stub launcher."""
|
|
result = shell.find_script_bash()
|
|
|
|
if result is None:
|
|
pytest.skip("no Git Bash installed on this machine")
|
|
assert "WindowsApps" not in Path(result).parts
|
|
system_root = Path(os.environ.get("SystemRoot", r"C:\Windows"))
|
|
assert system_root / "System32" not in Path(result).parents
|
|
assert system_root / "SysWOW64" not in Path(result).parents
|