diff --git a/cleo/application.py b/cleo/application.py index 82180799..8c6683db 100644 --- a/cleo/application.py +++ b/cleo/application.py @@ -490,7 +490,8 @@ def render_error(self, error: Exception, io: IO) -> None: trace = ExceptionTrace( error, solution_provider_repository=self._solution_provider_repository ) - trace.render(io.error_output, isinstance(error, CleoSimpleException)) + simple = not io.is_verbose() or isinstance(error, CleoSimpleException) + trace.render(io.error_output, simple) def _configure_io(self, io: IO) -> None: if io.input.has_parameter_option("--ansi", True): diff --git a/cleo/ui/exception_trace.py b/cleo/ui/exception_trace.py index 0b656adf..b9dad735 100644 --- a/cleo/ui/exception_trace.py +++ b/cleo/ui/exception_trace.py @@ -12,7 +12,6 @@ from typing import TYPE_CHECKING from crashtest.frame_collection import FrameCollection -from crashtest.inspector import Inspector from cleo.formatters.formatter import Formatter @@ -255,9 +254,10 @@ def render(self, io: IO | Output, simple: bool = False) -> None: if simple: io.write_line("") io.write_line(f"{str(self._exception)}") - return + else: + self._render_exception(io, self._exception) - return self._render_exception(io, self._exception) + self._render_solution(io, self._exception) def _render_exception(self, io: IO | Output, exception: Exception) -> None: from crashtest.inspector import Inspector @@ -286,8 +286,6 @@ def _render_exception(self, io: IO | Output, exception: Exception) -> None: current_frame = inspector.frames[-1] self._render_snippet(io, current_frame) - self._render_solution(io, inspector) - def _render_snippet(self, io: IO | Output, frame: Frame) -> None: self._render_line( io, @@ -306,12 +304,12 @@ def _render_snippet(self, io: IO | Output, frame: Frame) -> None: for code_line in code_lines: self._render_line(io, code_line, indent=4) - def _render_solution(self, io: IO | Output, inspector: Inspector) -> None: + def _render_solution(self, io: IO | Output, exception: Exception) -> None: if self._solution_provider_repository is None: return solutions = self._solution_provider_repository.get_solutions_for_exception( - inspector.exception + exception ) symbol = "•" if not io.supports_utf8(): @@ -348,7 +346,7 @@ def _render_trace(self, io: IO | Output, frames: FrameCollection) -> None: stack_frames.append(frame) remaining_frames_length = len(stack_frames) - 1 - if io.is_verbose() and remaining_frames_length: + if io.is_very_verbose() and remaining_frames_length: self._render_line(io, "Stack trace:", True) max_frame_length = len(str(remaining_frames_length)) frame_collections = stack_frames.compact() diff --git a/tests/ui/test_exception_trace.py b/tests/ui/test_exception_trace.py index 17ace60c..1388c1f3 100644 --- a/tests/ui/test_exception_trace.py +++ b/tests/ui/test_exception_trace.py @@ -144,9 +144,9 @@ def test_render_debug_better_error_message_recursion_error(): assert re.match(expected, io.fetch_output()) is not None -def test_render_verbose_better_error_message(): +def test_render_very_verbose_better_error_message(): io = BufferedIO() - io.set_verbosity(Verbosity.VERBOSE) + io.set_verbosity(Verbosity.VERY_VERBOSE) try: fail() @@ -158,7 +158,7 @@ def test_render_verbose_better_error_message(): expected = r"""^ Stack trace: - 1 {}:152 in test_render_verbose_better_error_message + 1 {}:152 in test_render_very_verbose_better_error_message fail\(\) Exception @@ -192,7 +192,7 @@ def second(): def test_render_debug_better_error_message_recursion_error_with_multiple_duplicated_frames(): io = BufferedIO() - io.set_verbosity(Verbosity.VERBOSE) + io.set_verbosity(Verbosity.VERY_VERBOSE) with pytest.raises(RecursionError) as e: first() @@ -212,7 +212,7 @@ def test_render_can_ignore_given_files(): from tests.ui.helpers import outer io = BufferedIO() - io.set_verbosity(Verbosity.VERBOSE) + io.set_verbosity(Verbosity.VERY_VERBOSE) def call(): def run(): @@ -477,3 +477,60 @@ def test(): " ...", "", ] + + +def test_simple_render(): + io = BufferedIO() + + with pytest.raises(Exception) as e: + fail() + + trace = ExceptionTrace(e.value) + + trace.render(io, simple=True) + + expected = """ +Failed +""" + + assert expected == io.fetch_output() + + +def test_simple_render_supports_solutions(): + from crashtest.contracts.base_solution import BaseSolution + from crashtest.contracts.provides_solution import ProvidesSolution + from crashtest.solution_providers.solution_provider_repository import ( + SolutionProviderRepository, + ) + + class CustomError(ProvidesSolution, Exception): + @property + def solution(self): + solution = BaseSolution("Solution Title.", "Solution Description") + solution.documentation_links.append("https://example.com") + solution.documentation_links.append("https://example2.com") + + return solution + + io = BufferedIO() + + def call(): + raise CustomError("Error with solution") + + with pytest.raises(CustomError) as e: + call() + + trace = ExceptionTrace( + e.value, solution_provider_repository=SolutionProviderRepository() + ) + + trace.render(io, simple=True) + + expected = """ +Error with solution + + • Solution Title: Solution Description + https://example.com, + https://example2.com +""" + assert expected == io.fetch_output()