/
niceSOFT
/
pip
Обзор
Документация
Войти
/
niceSOFT
/
pip
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/unit/test_exceptions.py
611 строк
18 KB
Richard Si
feat: Add five diagnostic connection errors (#14115)
14 июл 2026, 04:18
Не верифицирован
14 июл 2026, 04:18
e7a2ec0
Код
Авторство
О чём код?
"""Tests the presentation style of exceptions.""" from __future__ import annotations import io import locale import logging import pathlib import sys import textwrap import pytest from pip._vendor import rich from pip._internal.exceptions import DiagnosticPipError, ExternallyManagedEnvironment from tests.lib.output import render_to_text as rendered class TestDiagnosticPipErrorCreation: def test_fails_without_reference(self) -> None: class DerivedError(DiagnosticPipError): pass with pytest.raises(AssertionError) as exc_info: DerivedError(message="", context=None, hint_stmt=None) assert str(exc_info.value) == "error reference not provided!" def test_can_fetch_reference_from_subclass(self) -> None: class DerivedError(DiagnosticPipError): reference = "subclass-reference" obj = DerivedError(message="", context=None, hint_stmt=None) assert obj.reference == "subclass-reference" def test_can_fetch_reference_from_arguments(self) -> None: class DerivedError(DiagnosticPipError): pass obj = DerivedError( message="", context=None, hint_stmt=None, reference="subclass-reference" ) assert obj.reference == "subclass-reference" @pytest.mark.parametrize( "name", [ "BADNAME", "BadName", "bad_name", "BAD_NAME", "_bad", "bad-name-", "bad--name", "-bad-name", "bad-name-due-to-1-number", ], ) def test_rejects_non_kebab_case_names(self, name: str) -> None: class DerivedError(DiagnosticPipError): reference = name with pytest.raises(AssertionError) as exc_info: DerivedError(message="", context=None, hint_stmt=None) assert str(exc_info.value) == "error reference must be kebab-case!" def rendered_in_ascii(error: DiagnosticPipError, *, color: bool = False) -> str: with io.BytesIO() as stream: console = rich.console.Console( force_terminal=False, file=io.TextIOWrapper(stream, encoding="ascii", newline=""), color_system="truecolor" if color else None, ) console.print(error) return stream.getvalue().decode("ascii") class TestDiagnosticPipErrorPresentation_ASCII: def test_complete(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt="You did something wrong, which is what caused this error.", hint_stmt="Do it better next time, by trying harder.", ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( Something went wrong very wrong. note: You did something wrong, which is what caused this error. hint: Do it better next time, by trying harder. """) def test_complete_color(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke.", context="Something went wrong\nvery wrong.", note_stmt="You did something wrong.", hint_stmt="Do it better next time, by trying harder.", ) def esc(code: str = "0") -> str: return f"\x1b[{code}m" assert rendered_in_ascii(err, color=True) == textwrap.dedent(f"""\ {esc("1;31")}error{esc("0")}: {esc("1")}test-diagnostic{esc("0")} Oh no! It broke. Something went wrong very wrong. {esc("1;35")}note{esc("0")}: You did something wrong. {esc("1;36")}hint{esc("0")}: Do it better next time, by trying harder. """) def test_no_context(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, note_stmt="You did something wrong, which is what caused this error.", hint_stmt="Do it better next time, by trying harder.", ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( note: You did something wrong, which is what caused this error. hint: Do it better next time, by trying harder. """) def test_no_note(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt=None, hint_stmt="Do it better next time, by trying harder.", ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( Something went wrong very wrong. hint: Do it better next time, by trying harder. """) def test_no_hint(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt="You did something wrong, which is what caused this error.", hint_stmt=None, ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( Something went wrong very wrong. note: You did something wrong, which is what caused this error. """) def test_no_context_no_hint(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, note_stmt="You did something wrong, which is what caused this error.", hint_stmt=None, ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( note: You did something wrong, which is what caused this error. """) def test_no_context_no_note(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, note_stmt=None, hint_stmt="Do it better next time, by trying harder.", ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( hint: Do it better next time, by trying harder. """) def test_no_hint_no_note(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt=None, hint_stmt=None, ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( Something went wrong very wrong. """) def test_no_hint_no_note_no_context(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, hint_stmt=None, note_stmt=None, ) assert rendered_in_ascii(err) == textwrap.dedent("""\ error: test-diagnostic Oh no! It broke. :( """) class TestDiagnosticPipErrorPresentation_Unicode: def test_complete(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt="You did something wrong, which is what caused this error.", hint_stmt="Do it better next time, by trying harder.", ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! │ It broke. :( ╰─> Something went wrong very wrong. note: You did something wrong, which is what caused this error. hint: Do it better next time, by trying harder. """) def test_complete_color(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke.", context="Something went wrong\nvery wrong.", note_stmt="You did something wrong.", hint_stmt="Do it better next time, by trying harder.", ) def esc(code: str = "0") -> str: return f"\x1b[{code}m" assert rendered(err, color=True) == textwrap.dedent(f"""\ {esc("1;31")}error{esc("0")}: {esc("1")}test-diagnostic{esc("0")} {esc("31")}×{esc("0")} Oh no! {esc("31")}│{esc("0")} It broke. {esc("31")}╰─>{esc("0")} Something went wrong {esc("31")} {esc("0")} very wrong. {esc("1;35")}note{esc("0")}: You did something wrong. {esc("1;36")}hint{esc("0")}: Do it better next time, by trying harder. """) def test_no_context(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, note_stmt="You did something wrong, which is what caused this error.", hint_stmt="Do it better next time, by trying harder.", ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! It broke. :( note: You did something wrong, which is what caused this error. hint: Do it better next time, by trying harder. """) def test_no_note(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt=None, hint_stmt="Do it better next time, by trying harder.", ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! │ It broke. :( ╰─> Something went wrong very wrong. hint: Do it better next time, by trying harder. """) def test_no_hint(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt="You did something wrong, which is what caused this error.", hint_stmt=None, ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! │ It broke. :( ╰─> Something went wrong very wrong. note: You did something wrong, which is what caused this error. """) def test_no_context_no_hint(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, note_stmt="You did something wrong, which is what caused this error.", hint_stmt=None, ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! It broke. :( note: You did something wrong, which is what caused this error. """) def test_no_context_no_note(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, note_stmt=None, hint_stmt="Do it better next time, by trying harder.", ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! It broke. :( hint: Do it better next time, by trying harder. """) def test_no_hint_no_note(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context="Something went wrong\nvery wrong.", note_stmt=None, hint_stmt=None, ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! │ It broke. :( ╰─> Something went wrong very wrong. """) def test_no_hint_no_note_no_context(self) -> None: err = DiagnosticPipError( reference="test-diagnostic", message="Oh no!\nIt broke. :(", context=None, hint_stmt=None, note_stmt=None, ) assert rendered(err) == textwrap.dedent("""\ error: test-diagnostic × Oh no! It broke. :( """) class TestExternallyManagedEnvironment: default_text = ( f"The Python environment under {sys.prefix} is managed externally, " f"and may not be\nmanipulated by the user. Please use specific " f"tooling from the distributor of\nthe Python installation to " f"interact with this environment instead.\n" ) @pytest.fixture(autouse=True) def patch_locale(self, monkeypatch: pytest.MonkeyPatch) -> None: orig_getlocal = locale.getlocale def fake_getlocale(category: int) -> tuple[str | None, str | None]: """Fake getlocale() that always reports zh_Hant for LC_MESSAGES.""" result = orig_getlocal(category) if category == getattr(locale, "LC_MESSAGES", None): return "zh_Hant", result[1] return result monkeypatch.setattr(locale, "getlocale", fake_getlocale) @pytest.fixture def marker(self, tmp_path: pathlib.Path) -> pathlib.Path: marker = tmp_path.joinpath("EXTERNALLY-MANAGED") marker.touch() return marker def test_invalid_config_format( self, caplog: pytest.LogCaptureFixture, marker: pathlib.Path, ) -> None: marker.write_text("invalid", encoding="utf8") with caplog.at_level(logging.WARNING, "pip._internal.exceptions"): exc = ExternallyManagedEnvironment.from_config(marker) assert len(caplog.records) == 1 assert caplog.records[-1].getMessage() == f"Failed to read {marker}" assert str(exc.context) == self.default_text @pytest.mark.parametrize( "config", [ pytest.param("", id="empty"), pytest.param("[foo]\nblah = blah", id="no-section"), pytest.param("[externally-managed]\nblah = blah", id="no-key"), ], ) def test_config_without_key( self, caplog: pytest.LogCaptureFixture, marker: pathlib.Path, config: str, ) -> None: marker.write_text(config, encoding="utf8") with caplog.at_level(logging.WARNING, "pip._internal.exceptions"): exc = ExternallyManagedEnvironment.from_config(marker) assert not caplog.records assert str(exc.context) == self.default_text @pytest.mark.skipif( sys.platform == "win32", reason="Localization disabled on Windows", ) @pytest.mark.parametrize( "config, expected", [ pytest.param( """\ [externally-managed] Error = 最後 Error-en = English Error-zh = 中文 Error-zh_Hant = 繁體 Error-zh_Hans = 简体 """, "繁體", id="full", ), pytest.param( """\ [externally-managed] Error = 最後 Error-en = English Error-zh = 中文 Error-zh_Hans = 简体 """, "中文", id="no-variant", ), pytest.param( """\ [externally-managed] Error = 最後 Error-en = English """, "最後", id="fallback", ), ], ) def test_config_canonical( self, caplog: pytest.LogCaptureFixture, marker: pathlib.Path, config: str, expected: str, ) -> None: marker.write_text( textwrap.dedent(config), encoding="utf8", ) with caplog.at_level(logging.WARNING, "pip._internal.exceptions"): exc = ExternallyManagedEnvironment.from_config(marker) assert not caplog.records assert str(exc.context) == expected @pytest.mark.skipif( sys.platform != "win32", reason="Non-Windows should implement localization", ) @pytest.mark.parametrize( "config", [ pytest.param( """\ [externally-managed] Error = 最後 Error-en = English Error-zh = 中文 Error-zh_Hant = 繁體 Error-zh_Hans = 简体 """, id="full", ), pytest.param( """\ [externally-managed] Error = 最後 Error-en = English Error-zh = 中文 Error-zh_Hans = 简体 """, id="no-variant", ), pytest.param( """\ [externally-managed] Error = 最後 Error-en = English """, id="fallback", ), ], ) def test_config_canonical_no_localization( self, caplog: pytest.LogCaptureFixture, marker: pathlib.Path, config: str, ) -> None: marker.write_text( textwrap.dedent(config), encoding="utf8", ) with caplog.at_level(logging.WARNING, "pip._internal.exceptions"): exc = ExternallyManagedEnvironment.from_config(marker) assert not caplog.records assert str(exc.context) == "最後"