Files
rich-print/tests/test_pager.py
Yunxiao Xu 93c777cf6a Add opt-in debug logging across the CLI pipeline
Thread RP_DEBUG-driven logging through input, detection, rendering, and pager decisions so CLI behavior is inspectable without polluting stdout. Add focused coverage for debug wiring, env parsing, and stderr-only output.
2026-04-03 19:09:24 -07:00

189 lines
7.3 KiB
Python

"""Tests for pager integration."""
from __future__ import annotations
import signal
import subprocess
from unittest.mock import MagicMock, patch
from rp.pager import MousePager, _mouse_capable_less
class TestMouseCapableLess:
"""Tests for _mouse_capable_less()."""
def test_returns_path_when_less_supports_mouse(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
mock_result = MagicMock()
mock_result.stdout = "less 551 (POSIX regular expressions)"
mock_result.stderr = ""
with patch("rp.pager.subprocess.run", return_value=mock_result):
assert _mouse_capable_less() == "/usr/bin/less"
def test_returns_none_when_less_not_found(self) -> None:
with patch("rp.pager.shutil.which", return_value=None):
assert _mouse_capable_less() is None
def test_logs_less_not_found(self) -> None:
messages: list[str] = []
with patch("rp.pager.shutil.which", return_value=None):
assert _mouse_capable_less(messages.append) is None
assert messages == ["pager: 'less' was not found"]
def test_returns_none_when_less_too_old(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
mock_result = MagicMock()
mock_result.stdout = "less 444"
mock_result.stderr = ""
with patch("rp.pager.subprocess.run", return_value=mock_result):
assert _mouse_capable_less() is None
def test_returns_path_when_version_is_in_stderr(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
mock_result = MagicMock()
mock_result.stdout = ""
mock_result.stderr = "less 600"
with patch("rp.pager.subprocess.run", return_value=mock_result):
assert _mouse_capable_less() == "/usr/bin/less"
def test_returns_none_on_timeout(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
with patch(
"rp.pager.subprocess.run",
side_effect=subprocess.TimeoutExpired(cmd="less", timeout=5),
):
assert _mouse_capable_less() is None
def test_returns_none_on_os_error(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
with patch("rp.pager.subprocess.run", side_effect=OSError("nope")):
assert _mouse_capable_less() is None
def test_returns_none_when_version_not_parseable(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
mock_result = MagicMock()
mock_result.stdout = "some random output"
mock_result.stderr = ""
with patch("rp.pager.subprocess.run", return_value=mock_result):
assert _mouse_capable_less() is None
def test_boundary_version_543(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
mock_result = MagicMock()
mock_result.stdout = "less 543"
mock_result.stderr = ""
with patch("rp.pager.subprocess.run", return_value=mock_result):
assert _mouse_capable_less() == "/usr/bin/less"
def test_boundary_version_542(self) -> None:
with patch("rp.pager.shutil.which", return_value="/usr/bin/less"):
mock_result = MagicMock()
mock_result.stdout = "less 542"
mock_result.stderr = ""
with patch("rp.pager.subprocess.run", return_value=mock_result):
assert _mouse_capable_less() is None
class TestMousePager:
"""Tests for MousePager.show()."""
def test_falls_back_to_system_pager_when_no_less(self) -> None:
pager = MousePager()
with patch("rp.pager._mouse_capable_less", return_value=None):
with patch("rp.pager.SystemPager") as mock_system_pager:
mock_instance = MagicMock()
mock_system_pager.return_value = mock_instance
pager.show("content")
mock_instance.show.assert_called_once_with("content")
def test_logs_system_pager_fallback(self) -> None:
messages: list[str] = []
pager = MousePager(debug=messages.append)
with patch("rp.pager._mouse_capable_less", return_value=None):
with patch("rp.pager.SystemPager") as mock_system_pager:
mock_instance = MagicMock()
mock_system_pager.return_value = mock_instance
pager.show("content")
assert messages == ["pager: falling back to Rich's SystemPager"]
def test_uses_less_when_available(self) -> None:
pager = MousePager()
mock_result = MagicMock()
mock_result.returncode = 0
with patch("rp.pager._mouse_capable_less", return_value="/usr/bin/less"):
with patch("rp.pager.subprocess.run", return_value=mock_result) as mock_run:
pager.show("hello")
mock_run.assert_called_once_with(
["/usr/bin/less", "-R", "--mouse"],
input="hello",
text=True,
)
def test_sigint_does_not_trigger_fallback_for_negative_code(self) -> None:
pager = MousePager()
mock_result = MagicMock()
mock_result.returncode = -signal.SIGINT
with patch("rp.pager._mouse_capable_less", return_value="/usr/bin/less"):
with patch("rp.pager.subprocess.run", return_value=mock_result):
with patch("rp.pager.SystemPager") as mock_system_pager:
pager.show("content")
mock_system_pager.assert_not_called()
def test_sigint_does_not_trigger_fallback_for_128_plus_code(self) -> None:
pager = MousePager()
mock_result = MagicMock()
mock_result.returncode = 128 + signal.SIGINT
with patch("rp.pager._mouse_capable_less", return_value="/usr/bin/less"):
with patch("rp.pager.subprocess.run", return_value=mock_result):
with patch("rp.pager.SystemPager") as mock_system_pager:
pager.show("content")
mock_system_pager.assert_not_called()
def test_nonzero_exit_triggers_fallback(self) -> None:
pager = MousePager()
mock_result = MagicMock()
mock_result.returncode = 1
with patch("rp.pager._mouse_capable_less", return_value="/usr/bin/less"):
with patch("rp.pager.subprocess.run", return_value=mock_result):
with patch("rp.pager.SystemPager") as mock_system_pager:
mock_instance = MagicMock()
mock_system_pager.return_value = mock_instance
pager.show("content")
mock_instance.show.assert_called_once_with("content")
def test_oserror_triggers_fallback(self) -> None:
pager = MousePager()
with patch("rp.pager._mouse_capable_less", return_value="/usr/bin/less"):
with patch("rp.pager.subprocess.run", side_effect=OSError("fail")):
with patch("rp.pager.SystemPager") as mock_system_pager:
mock_instance = MagicMock()
mock_system_pager.return_value = mock_instance
pager.show("content")
mock_instance.show.assert_called_once_with("content")