test(webui): make Windows git-fallback tests platform-independent
The known-dirs-probe test built its expected path from os.environ['ProgramFiles']
+ os.path.join, but the production code uses os.path.expandvars('%ProgramFiles%..').
On the Linux CI runners expandvars does not expand %VAR% syntax and os.path.join
uses '/', so the expected path diverged from what the code produced and the test
failed (assert None == 'C:\Program Files/Git/cmd/git.exe').
Mock the OS-specific surfaces (winreg, os.path.exists/expandvars/join) so the
Windows code path is exercised deterministically on any host. Verified the two
previously-failing assertions now pass under posix os.path semantics, and a
mutation check (reverting the fix) still makes the three behavior tests fail.
This commit is contained in:
1 parent
861cc9da77
commit
2c03277a97
1 file changed
+46
-19
@@ -10,15 +10,26 @@ after the server restarted with fixed code.
|
||||
|
||||
The fix adds a Windows fallback that resolves git.exe from the Git-for-Windows
|
||||
registry ``InstallPath`` (and common install dirs) without relying on PATH.
|
||||
|
||||
These tests mock the OS-specific surfaces (winreg, os.path.exists,
|
||||
os.path.expandvars) so they exercise the Windows code path deterministically on
|
||||
ANY host — the CI runners are Linux, where %VAR% expansion and native path
|
||||
separators differ, so nothing here may depend on the running platform's real
|
||||
environment or filesystem.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import api.updates as updates
|
||||
|
||||
# Deterministic install root used by the winreg stub. Kept as a forward-slash
|
||||
# string so the expected git path is identical regardless of the host's
|
||||
# os.path.join separator (CI is Linux; dev is Windows).
|
||||
FAKE_GIT_ROOT = 'C:/Program Files/Git'
|
||||
FAKE_GIT_EXE = FAKE_GIT_ROOT + '/cmd/git.exe'
|
||||
|
||||
def _fake_winreg():
|
||||
|
||||
def _fake_winreg(install_path=FAKE_GIT_ROOT):
|
||||
"""Minimal winreg stub whose OpenKey/QueryValueEx yield a Git InstallPath."""
|
||||
mod = MagicMock()
|
||||
mod.HKEY_LOCAL_MACHINE = 0
|
||||
@@ -35,43 +46,58 @@ def _fake_winreg():
|
||||
return False
|
||||
|
||||
mod.OpenKey.return_value = _Key()
|
||||
mod.QueryValueEx.return_value = (r'C:\Program Files\Git', 1)
|
||||
mod.QueryValueEx.return_value = (install_path, 1)
|
||||
return mod
|
||||
|
||||
|
||||
def _patch_join_forward_slash():
|
||||
"""Make os.path.join deterministic (forward slash) regardless of host OS.
|
||||
|
||||
The production code builds candidates with os.path.join; on Linux CI that
|
||||
uses '/', on Windows '\\'. Pin it so expected paths match on both.
|
||||
"""
|
||||
def fake_join(*parts):
|
||||
return '/'.join(p.rstrip('/\\') for p in parts)
|
||||
return patch.object(updates.os.path, 'join', side_effect=fake_join)
|
||||
|
||||
|
||||
def test_resolve_git_executable_uses_registry_on_windows():
|
||||
winreg = _fake_winreg()
|
||||
expected = os.path.join(r'C:\Program Files\Git', 'cmd', 'git.exe')
|
||||
|
||||
def fake_exists(p):
|
||||
return p == expected
|
||||
|
||||
with patch.object(updates.shutil, 'which', return_value=None), \
|
||||
patch.object(sys, 'platform', 'win32'), \
|
||||
patch.dict('sys.modules', {'winreg': winreg}), \
|
||||
patch.object(updates.os.path, 'exists', side_effect=fake_exists):
|
||||
_patch_join_forward_slash(), \
|
||||
patch.object(updates.os.path, 'exists', side_effect=lambda p: p == FAKE_GIT_EXE):
|
||||
resolved = updates._resolve_git_executable()
|
||||
|
||||
assert resolved == expected
|
||||
assert resolved == FAKE_GIT_EXE
|
||||
|
||||
|
||||
def test_resolve_git_executable_probes_known_dirs_when_registry_absent():
|
||||
winreg = _fake_winreg()
|
||||
# Registry has no value -> QueryValueEx raises -> fall through to dir probe.
|
||||
winreg = _fake_winreg()
|
||||
winreg.QueryValueEx.side_effect = OSError('no InstallPath')
|
||||
program_files = os.environ.get('ProgramFiles', r'C:\Program Files')
|
||||
expected = os.path.join(program_files, 'Git', 'cmd', 'git.exe')
|
||||
|
||||
def fake_exists(p):
|
||||
return p == expected
|
||||
# The code probes os.path.expandvars('%ProgramFiles%\\Git\\cmd\\git.exe');
|
||||
# mock expandvars deterministically so the test does not depend on the
|
||||
# host's %VAR% expansion (Linux CI does not expand %ProgramFiles%).
|
||||
probed = 'C:/Program Files/Git/cmd/git.exe'
|
||||
|
||||
def fake_expandvars(s):
|
||||
if 'ProgramFiles%' in s and '(x86)' not in s:
|
||||
return probed
|
||||
# Other candidates resolve to a sentinel that never "exists".
|
||||
return '/nonexistent/' + s
|
||||
|
||||
with patch.object(updates.shutil, 'which', return_value=None), \
|
||||
patch.object(sys, 'platform', 'win32'), \
|
||||
patch.dict('sys.modules', {'winreg': winreg}), \
|
||||
patch.object(updates.os.path, 'exists', side_effect=fake_exists):
|
||||
patch.object(updates.os.path, 'expandvars', side_effect=fake_expandvars), \
|
||||
patch.object(updates.os.path, 'exists', side_effect=lambda p: p == probed):
|
||||
resolved = updates._resolve_git_executable()
|
||||
|
||||
assert resolved == expected
|
||||
assert resolved == probed
|
||||
|
||||
|
||||
def test_resolve_git_executable_returns_none_on_windows_when_git_truly_absent():
|
||||
@@ -81,6 +107,7 @@ def test_resolve_git_executable_returns_none_on_windows_when_git_truly_absent():
|
||||
with patch.object(updates.shutil, 'which', return_value=None), \
|
||||
patch.object(sys, 'platform', 'win32'), \
|
||||
patch.dict('sys.modules', {'winreg': winreg}), \
|
||||
patch.object(updates.os.path, 'expandvars', side_effect=lambda s: s), \
|
||||
patch.object(updates.os.path, 'exists', return_value=False):
|
||||
resolved = updates._resolve_git_executable()
|
||||
|
||||
@@ -103,14 +130,13 @@ def test_non_windows_never_touches_registry_fallback():
|
||||
|
||||
def test_detect_webui_version_recovers_via_windows_registry_fallback(tmp_path):
|
||||
winreg = _fake_winreg()
|
||||
expected_git = os.path.join(r'C:\Program Files\Git', 'cmd', 'git.exe')
|
||||
|
||||
def fake_exists(p):
|
||||
# git.exe resolves; no api/_version.py fallback file present.
|
||||
return p == expected_git
|
||||
return p == FAKE_GIT_EXE
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
assert cmd[0] == expected_git
|
||||
assert cmd[0] == FAKE_GIT_EXE
|
||||
if cmd[1:] == ['describe', '--tags', '--always']:
|
||||
return MagicMock(returncode=0, stdout='v0.51.999\n', stderr='')
|
||||
if cmd[1:] == ['diff-index', '--quiet', 'HEAD', '--']:
|
||||
@@ -120,6 +146,7 @@ def test_detect_webui_version_recovers_via_windows_registry_fallback(tmp_path):
|
||||
with patch.object(updates.shutil, 'which', return_value=None), \
|
||||
patch.object(sys, 'platform', 'win32'), \
|
||||
patch.dict('sys.modules', {'winreg': winreg}), \
|
||||
_patch_join_forward_slash(), \
|
||||
patch.object(updates.os.path, 'exists', side_effect=fake_exists), \
|
||||
patch.object(updates, 'REPO_ROOT', tmp_path), \
|
||||
patch.object(updates.subprocess, 'run', side_effect=fake_run):
|
||||
|
||||
Reference in new issue
Block a user