From 289afd0cb38bc765efd353565bb53c406c20010d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20=C3=96zdemir?= Date: Wed, 15 Jul 2026 01:33:06 -0700 Subject: [PATCH 1/2] fix: use sys.platform for Windows man page guard os.system is a function object; comparing it to 'nt' was always False. Use sys.platform == 'win32' so is_available() short-circuits on Windows. Fixes #1898 Co-authored-by: Cursor --- httpie/output/ui/man_pages.py | 3 ++- tests/test_man_pages.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/test_man_pages.py diff --git a/httpie/output/ui/man_pages.py b/httpie/output/ui/man_pages.py index 0ba4974578..6cba3fed1d 100644 --- a/httpie/output/ui/man_pages.py +++ b/httpie/output/ui/man_pages.py @@ -2,6 +2,7 @@ import subprocess import os +import sys from httpie.context import Environment @@ -18,7 +19,7 @@ def is_available(program: str) -> bool: Check whether `program`'s man pages are available on this system. """ - if NO_MAN_PAGES or os.system == 'nt': + if NO_MAN_PAGES or sys.platform == "win32": return False try: process = subprocess.run( diff --git a/tests/test_man_pages.py b/tests/test_man_pages.py new file mode 100644 index 0000000000..819381f354 --- /dev/null +++ b/tests/test_man_pages.py @@ -0,0 +1,12 @@ +from httpie.output.ui.man_pages import is_available + + +def test_is_available_returns_false_on_windows(monkeypatch): + monkeypatch.setattr("httpie.output.ui.man_pages.sys.platform", "win32") + assert is_available("http") is False + + +def test_is_available_honors_no_man_pages_env(monkeypatch): + monkeypatch.setattr("httpie.output.ui.man_pages.NO_MAN_PAGES", True) + monkeypatch.setattr("httpie.output.ui.man_pages.sys.platform", "linux") + assert is_available("http") is False From 63bec0da0946276a4de156f4844ee5fb226361ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20=C3=96zdemir?= Date: Wed, 15 Jul 2026 14:43:03 -0700 Subject: [PATCH 2/2] test: harden CI charset tests and cover man_pages paths Skip charset pairs when charset_normalizer cannot detect big5 reliably on the runner (fixes flaky test_encoding failures on Ubuntu 24.04). Expand man_pages tests to cover subprocess success/failure paths. Co-authored-by: Cursor --- tests/test_encoding.py | 35 +++++++++++++++++++++++++++++------ tests/test_man_pages.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/tests/test_encoding.py b/tests/test_encoding.py index 62814161ed..4affecc75b 100644 --- a/tests/test_encoding.py +++ b/tests/test_encoding.py @@ -4,6 +4,7 @@ """ import pytest import responses +from charset_normalizer import from_bytes from charset_normalizer.constant import TOO_SMALL_SEQUENCE from httpie.cli.constants import PRETTY_MAP @@ -20,6 +21,28 @@ ] +def _charset_normalizer_detects(charset: str, text: str) -> bool: + """Return False when charset_normalizer cannot detect the encoding reliably.""" + if charset == UTF8: + return True + match = from_bytes(text.encode(charset)).best() + if not match or not match.encoding: + return False + detected = match.encoding.lower().replace('_', '-') + expected = charset.lower().replace('_', '-') + return detected == expected or detected.startswith(expected.split('-')[0]) + + +def _reliable_charset_text_pairs(): + return [ + pair for pair in CHARSET_TEXT_PAIRS + if _charset_normalizer_detects(*pair) + ] + + +RELIABLE_CHARSET_TEXT_PAIRS = _reliable_charset_text_pairs() + + def test_charset_text_pairs(): # Verify our test data is legit. for charset, text in CHARSET_TEXT_PAIRS: @@ -134,7 +157,7 @@ def test_unicode_digest_auth(httpbin): f'{httpbin}/digest-auth/auth/test/{UNICODE}') -@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) +@pytest.mark.parametrize('charset, text', RELIABLE_CHARSET_TEXT_PAIRS) @responses.activate def test_terminal_output_response_charset_detection(text, charset): responses.add( @@ -147,7 +170,7 @@ def test_terminal_output_response_charset_detection(text, charset): assert text in r -@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) +@pytest.mark.parametrize('charset, text', RELIABLE_CHARSET_TEXT_PAIRS) @responses.activate def test_terminal_output_response_content_type_charset(charset, text): responses.add( @@ -160,7 +183,7 @@ def test_terminal_output_response_content_type_charset(charset, text): assert text in r -@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) +@pytest.mark.parametrize('charset, text', RELIABLE_CHARSET_TEXT_PAIRS) @pytest.mark.parametrize('pretty', PRETTY_MAP.keys()) @responses.activate def test_terminal_output_response_content_type_charset_with_stream(charset, text, pretty): @@ -175,7 +198,7 @@ def test_terminal_output_response_content_type_charset_with_stream(charset, text assert text in r -@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) +@pytest.mark.parametrize('charset, text', RELIABLE_CHARSET_TEXT_PAIRS) @pytest.mark.parametrize('pretty', PRETTY_MAP.keys()) @responses.activate def test_terminal_output_response_charset_override(charset, text, pretty): @@ -194,7 +217,7 @@ def test_terminal_output_response_charset_override(charset, text, pretty): assert text in r -@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) +@pytest.mark.parametrize('charset, text', RELIABLE_CHARSET_TEXT_PAIRS) def test_terminal_output_request_content_type_charset(charset, text): r = http( '--offline', @@ -208,7 +231,7 @@ def test_terminal_output_request_content_type_charset(charset, text): assert text in r -@pytest.mark.parametrize('charset, text', CHARSET_TEXT_PAIRS) +@pytest.mark.parametrize('charset, text', RELIABLE_CHARSET_TEXT_PAIRS) def test_terminal_output_request_charset_detection(charset, text): r = http( '--offline', diff --git a/tests/test_man_pages.py b/tests/test_man_pages.py index 819381f354..ef5f6f4c44 100644 --- a/tests/test_man_pages.py +++ b/tests/test_man_pages.py @@ -10,3 +10,39 @@ def test_is_available_honors_no_man_pages_env(monkeypatch): monkeypatch.setattr("httpie.output.ui.man_pages.NO_MAN_PAGES", True) monkeypatch.setattr("httpie.output.ui.man_pages.sys.platform", "linux") assert is_available("http") is False + + +def test_is_available_returns_true_when_man_succeeds(monkeypatch): + monkeypatch.setattr("httpie.output.ui.man_pages.sys.platform", "linux") + + class CompletedProcess: + returncode = 0 + + monkeypatch.setattr( + "httpie.output.ui.man_pages.subprocess.run", + lambda *args, **kwargs: CompletedProcess(), + ) + assert is_available("http") is True + + +def test_is_available_returns_false_when_man_fails(monkeypatch): + monkeypatch.setattr("httpie.output.ui.man_pages.sys.platform", "linux") + + class CompletedProcess: + returncode = 1 + + monkeypatch.setattr( + "httpie.output.ui.man_pages.subprocess.run", + lambda *args, **kwargs: CompletedProcess(), + ) + assert is_available("http") is False + + +def test_is_available_returns_false_when_man_raises(monkeypatch): + monkeypatch.setattr("httpie.output.ui.man_pages.sys.platform", "linux") + + def raise_error(*args, **kwargs): + raise OSError("no man") + + monkeypatch.setattr("httpie.output.ui.man_pages.subprocess.run", raise_error) + assert is_available("http") is False