diff options
Diffstat (limited to 'tests/unit/test_exceptions.py')
-rw-r--r-- | tests/unit/test_exceptions.py | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py new file mode 100644 index 000000000..8f8224dc8 --- /dev/null +++ b/tests/unit/test_exceptions.py @@ -0,0 +1,474 @@ +"""Tests the presentation style of exceptions.""" + +import io +import textwrap + +import pytest +from pip._vendor import rich + +from pip._internal.exceptions import DiagnosticPipError + + +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. :( + """ + ) + + +def rendered(error: DiagnosticPipError, *, color: bool = False) -> str: + with io.StringIO() as stream: + console = rich.console.Console( + force_terminal=False, + file=stream, + color_system="truecolor" if color else None, + ) + console.print(error) + return stream.getvalue() + + +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. :( + """ + ) |