Skip to content

Commit 3d1937f

Browse files
committed
Add tests for EXTERNALLY-MANAGED parser
1 parent 095fd85 commit 3d1937f

File tree

2 files changed

+137
-10
lines changed

2 files changed

+137
-10
lines changed

src/pip/_internal/exceptions.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import contextlib
1010
import locale
1111
import logging
12+
import pathlib
1213
import re
1314
import sys
1415
from itertools import chain, groupby, repeat
@@ -683,6 +684,8 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
683684
:param error: The error message read from ``EXTERNALLY-MANAGED``.
684685
"""
685686

687+
reference = "externally-managed-environment"
688+
686689
def __init__(self, error: Optional[str]) -> None:
687690
if error is None:
688691
context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
@@ -711,20 +714,22 @@ def _iter_externally_managed_error_keys() -> Iterator[str]:
711714
yield "Error"
712715

713716
@classmethod
714-
def from_config(cls, config: str) -> "ExternallyManagedEnvironment":
717+
def from_config(
718+
cls,
719+
config: Union[pathlib.Path, str],
720+
) -> "ExternallyManagedEnvironment":
715721
parser = configparser.ConfigParser(interpolation=None)
716722
try:
717723
parser.read(config, encoding="utf-8")
718-
except (OSError, UnicodeDecodeError):
724+
section = parser["externally-managed"]
725+
for key in cls._iter_externally_managed_error_keys():
726+
with contextlib.suppress(KeyError):
727+
return cls(section[key])
728+
except KeyError:
729+
pass
730+
except (OSError, UnicodeDecodeError, configparser.ParsingError):
719731
from pip._internal.utils._log import VERBOSE
720732

721733
exc_info = logger.isEnabledFor(VERBOSE)
722734
logger.warning("Failed to read %s", config, exc_info=exc_info)
723-
try:
724-
section = parser["externally-managed"]
725-
except KeyError:
726-
return cls(None)
727-
for key in cls._iter_externally_managed_error_keys():
728-
with contextlib.suppress(KeyError):
729-
return cls(section[key])
730735
return cls(None)

tests/unit/test_exceptions.py

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
"""Tests the presentation style of exceptions."""
22

33
import io
4+
import locale
5+
import logging
6+
import pathlib
7+
import sys
48
import textwrap
9+
from typing import Optional, Tuple
510

611
import pytest
712
from pip._vendor import rich
813

9-
from pip._internal.exceptions import DiagnosticPipError
14+
from pip._internal.exceptions import DiagnosticPipError, ExternallyManagedEnvironment
1015

1116

1217
class TestDiagnosticPipErrorCreation:
@@ -472,3 +477,120 @@ def test_no_hint_no_note_no_context(self) -> None:
472477
It broke. :(
473478
"""
474479
)
480+
481+
482+
class TestExternallyManagedEnvironment:
483+
default_text = (
484+
f"The Python environment under {sys.prefix} is managed externally, "
485+
f"and may not be\nmanipulated by the user. Please use specific "
486+
f"tooling from the distributor of\nthe Python installation to "
487+
f"interact with this environment instead.\n"
488+
)
489+
490+
@pytest.fixture(autouse=True)
491+
def patch_locale(self, monkeypatch: pytest.MonkeyPatch) -> None:
492+
orig_getlocal = locale.getlocale
493+
494+
def fake_getlocale(category: int) -> Tuple[Optional[str], Optional[str]]:
495+
"""Fake getlocale() that always report zh_Hant."""
496+
result = orig_getlocal(category)
497+
if category == locale.LC_MESSAGES:
498+
return "zh_Hant", result[1]
499+
return result
500+
501+
monkeypatch.setattr(locale, "getlocale", fake_getlocale)
502+
503+
@pytest.fixture()
504+
def marker(self, tmp_path: pathlib.Path) -> pathlib.Path:
505+
marker = tmp_path.joinpath("EXTERNALLY-MANAGED")
506+
marker.touch()
507+
return marker
508+
509+
def test_invalid_config_format(
510+
self,
511+
caplog: pytest.LogCaptureFixture,
512+
marker: pathlib.Path,
513+
) -> None:
514+
marker.write_text("invalid", encoding="utf8")
515+
516+
with caplog.at_level(logging.WARNING, "pip._internal.exceptions"):
517+
exc = ExternallyManagedEnvironment.from_config(marker)
518+
assert len(caplog.records) == 1
519+
assert caplog.records[-1].getMessage() == f"Failed to read {marker}"
520+
521+
assert str(exc.context) == self.default_text
522+
523+
@pytest.mark.parametrize(
524+
"config",
525+
[
526+
pytest.param("", id="empty"),
527+
pytest.param("[foo]\nblah = blah", id="no-section"),
528+
pytest.param("[externally-managed]\nblah = blah", id="no-key"),
529+
],
530+
)
531+
def test_config_without_key(
532+
self,
533+
caplog: pytest.LogCaptureFixture,
534+
marker: pathlib.Path,
535+
config: str,
536+
) -> None:
537+
marker.write_text(config, encoding="utf8")
538+
539+
with caplog.at_level(logging.WARNING, "pip._internal.exceptions"):
540+
exc = ExternallyManagedEnvironment.from_config(marker)
541+
assert not caplog.records
542+
assert str(exc.context) == self.default_text
543+
544+
@pytest.mark.parametrize(
545+
"config, expected",
546+
[
547+
pytest.param(
548+
"""\
549+
[externally-managed]
550+
Error = 最後
551+
Error-en = English
552+
Error-zh = 中文
553+
Error-zh_Hant = 繁體
554+
Error-zh_Hans = 简体
555+
""",
556+
"繁體",
557+
id="full",
558+
),
559+
pytest.param(
560+
"""\
561+
[externally-managed]
562+
Error = 最後
563+
Error-en = English
564+
Error-zh = 中文
565+
Error-zh_Hans = 简体
566+
""",
567+
"中文",
568+
id="no-variant",
569+
),
570+
pytest.param(
571+
"""\
572+
[externally-managed]
573+
Error = 最後
574+
Error-en = English
575+
""",
576+
"最後",
577+
id="fallback",
578+
),
579+
],
580+
)
581+
def test_config_canonical(
582+
self,
583+
caplog: pytest.LogCaptureFixture,
584+
marker: pathlib.Path,
585+
config: str,
586+
expected: str,
587+
) -> None:
588+
marker.write_text(
589+
textwrap.dedent(config),
590+
encoding="utf8",
591+
)
592+
593+
with caplog.at_level(logging.WARNING, "pip._internal.exceptions"):
594+
exc = ExternallyManagedEnvironment.from_config(marker)
595+
assert not caplog.records
596+
assert str(exc.context) == expected

0 commit comments

Comments
 (0)