mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
fix: disable Rich Live transient mode on Windows to prevent PS 5.1 hang (#2938)
* fix: disable Rich Live transient mode on Windows to prevent PS 5.1 hang PowerShell 5.1's legacy console host does not reliably support VT escape sequences. Rich's Live(transient=True) attempts cursor restoration on context exit, which hangs indefinitely on that console. Set transient=False when sys.platform == 'win32' in both init.py (progress tracker) and _console.py (select_with_arrows). The only cosmetic effect is that progress output remains visible after completion on Windows. Fixes #2927 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: address review feedback on test quality - Use captured['transient'] instead of .get() for clearer KeyError on failure - Source guards now assert both the platform check AND transient=_transient usage - Remove unused imports (MagicMock retained as it's used, removed pytest) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: use regex in source guards for resilience to formatting changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: use single DOTALL regex to verify assignment flows into Live() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: skip duplicate tracker print on Windows when transient=False When transient is False, Rich leaves the Live output on screen. The subsequent console.print(tracker.render()) would duplicate it. Gate it behind _transient so Windows users see the tracker exactly once. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
90
tests/test_live_transient_windows.py
Normal file
90
tests/test_live_transient_windows.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Tests for Rich Live transient=False on Windows (GitHub issue #2927).
|
||||
|
||||
PowerShell 5.1's legacy console host does not support VT escape sequences
|
||||
reliably. Rich's ``Live(transient=True)`` attempts cursor restoration on
|
||||
exit, which hangs indefinitely on that console. The fix disables transient
|
||||
mode when ``sys.platform == "win32"``.
|
||||
|
||||
These tests patch ``sys.platform`` and intercept the ``Live`` constructor
|
||||
to verify the correct ``transient`` value reaches Rich.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _console.py — Live in the select_with_arrows helper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _invoke_select_with_arrows(platform: str) -> bool:
|
||||
"""Patch sys.platform and Live, invoke select_with_arrows, return transient kwarg."""
|
||||
captured = {}
|
||||
|
||||
mock_live_instance = MagicMock()
|
||||
mock_live_instance.__enter__ = MagicMock(return_value=mock_live_instance)
|
||||
mock_live_instance.__exit__ = MagicMock(return_value=False)
|
||||
|
||||
def fake_live(*args, **kwargs):
|
||||
captured.update(kwargs)
|
||||
return mock_live_instance
|
||||
|
||||
# Patch readchar so the loop immediately returns "enter"
|
||||
import readchar
|
||||
|
||||
with (
|
||||
patch("sys.platform", platform),
|
||||
patch("specify_cli._console.Live", side_effect=fake_live),
|
||||
patch("specify_cli._console.readchar.readkey", return_value=readchar.key.ENTER),
|
||||
):
|
||||
from specify_cli._console import select_with_arrows
|
||||
|
||||
select_with_arrows({"a": "Option A", "b": "Option B"}, "Pick one", "a")
|
||||
|
||||
return captured["transient"]
|
||||
|
||||
|
||||
class TestSelectWithArrowsLiveTransient:
|
||||
"""Verify that select_with_arrows passes transient=False on Windows."""
|
||||
|
||||
def test_transient_false_on_windows(self):
|
||||
assert _invoke_select_with_arrows("win32") is False
|
||||
|
||||
def test_transient_true_on_linux(self):
|
||||
assert _invoke_select_with_arrows("linux") is True
|
||||
|
||||
def test_transient_true_on_macos(self):
|
||||
assert _invoke_select_with_arrows("darwin") is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# init.py — verify source contains the platform guard (regression check)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestSourceContainsPlatformGuard:
|
||||
"""Ensure the platform guard feeds into the Live() transient kwarg."""
|
||||
|
||||
# Single DOTALL regex: _transient assigned from win32 check, then used in Live()
|
||||
_GUARD_RE = r"_transient\s*=\s*sys\.platform\s*!=\s*['\"]win32['\"].*Live\(.*transient\s*=\s*_transient"
|
||||
|
||||
def test_init_has_win32_guard(self):
|
||||
"""init.py must assign _transient from platform check and pass it to Live."""
|
||||
import re
|
||||
|
||||
init_src = Path(__file__).resolve().parent.parent / "src" / "specify_cli" / "commands" / "init.py"
|
||||
content = init_src.read_text(encoding="utf-8")
|
||||
assert re.search(self._GUARD_RE, content, re.DOTALL)
|
||||
|
||||
def test_console_has_win32_guard(self):
|
||||
"""_console.py must assign _transient from platform check and pass it to Live."""
|
||||
import re
|
||||
|
||||
console_src = Path(__file__).resolve().parent.parent / "src" / "specify_cli" / "_console.py"
|
||||
content = console_src.read_text(encoding="utf-8")
|
||||
assert re.search(self._GUARD_RE, content, re.DOTALL)
|
||||
assert re.search(r"transient\s*=\s*_transient", content)
|
||||
assert "transient=_transient" in content
|
||||
Reference in New Issue
Block a user