diff --git a/craft_cli/messages.py b/craft_cli/messages.py index 2af5c39..18fb0fc 100644 --- a/craft_cli/messages.py +++ b/craft_cli/messages.py @@ -719,14 +719,13 @@ def ended_ok(self) -> None: """Finish the messaging system gracefully.""" self._stop() - def _report_error(self, error: errors.CraftError) -> None: # noqa: PLR0912 (too many branches) + def _report_error(self, error: errors.CraftError) -> None: """Report the different message lines from a CraftError.""" + use_timestamp = True + exception_stream = sys.stderr if self._mode in (EmitterMode.QUIET, EmitterMode.BRIEF, EmitterMode.VERBOSE): use_timestamp = False - full_stream = None - else: - use_timestamp = True - full_stream = sys.stderr + exception_stream = None # The initial message. Print every line individually to correctly clear # previous lines, if necessary. @@ -742,10 +741,13 @@ def _report_error(self, error: errors.CraftError) -> None: # noqa: PLR0912 (too # detailed information and/or original exception if error.details: text = f"Detailed information: {error.details}" - self._printer.show(full_stream, text, use_timestamp=use_timestamp, end_line=True) + details_stream = None if self._mode == EmitterMode.QUIET else sys.stderr + self._printer.show(details_stream, text, use_timestamp=use_timestamp, end_line=True) if error.__cause__: for line in _get_traceback_lines(error.__cause__): - self._printer.show(full_stream, line, use_timestamp=use_timestamp, end_line=True) + self._printer.show( + exception_stream, line, use_timestamp=use_timestamp, end_line=True + ) # hints for the user to know more if error.resolution: diff --git a/docs/changelog.rst b/docs/changelog.rst index a7bbb94..45e33c4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,11 @@ Changelog See the `Releases page`_ on GitHub for a complete list of commits that are included in each version. +2.13.0 (2024-Dec-XX) +-------------------- + +- Show error details in every mode except quiet. + 2.12.0 (2024-Dec-13) -------------------- diff --git a/tests/integration/test_messages_integration.py b/tests/integration/test_messages_integration.py index 62b8f95..52f7d5f 100644 --- a/tests/integration/test_messages_integration.py +++ b/tests/integration/test_messages_integration.py @@ -906,16 +906,39 @@ def test_simple_errors_debugish(capsys, mode): assert_outputs(capsys, emit, expected_err=expected, expected_log=expected) +@pytest.mark.parametrize("output_is_terminal", [True, False]) +def test_error_api_details_quiet(capsys): + """Somewhat expected API error, final user modes. + + Check that "quiet" is indeed quiet. + """ + emit = Emitter() + emit.init(EmitterMode.QUIET, "testapp", GREETING) + full_error = {"message": "Invalid channel.", "code": "BAD-CHANNEL"} + error = CraftError("Invalid channel.", details=str(full_error)) + emit.error(error) + + expected_err = [ + Line("Invalid channel."), + Line(f"Full execution log: {str(emit._log_filepath)!r}"), + ] + expected_log = [ + Line("Invalid channel."), + Line(f"Detailed information: {full_error}"), + Line(f"Full execution log: {str(emit._log_filepath)!r}"), + ] + assert_outputs(capsys, emit, expected_err=expected_err, expected_log=expected_log) + + @pytest.mark.parametrize( "mode", [ - EmitterMode.QUIET, EmitterMode.BRIEF, EmitterMode.VERBOSE, ], ) @pytest.mark.parametrize("output_is_terminal", [True, False]) -def test_error_api_quietly(capsys, mode): +def test_error_api_details(capsys, mode): """Somewhat expected API error, final user modes.""" emit = Emitter() emit.init(mode, "testapp", GREETING) @@ -925,6 +948,7 @@ def test_error_api_quietly(capsys, mode): expected_err = [ Line("Invalid channel."), + Line("Detailed information: {'message': 'Invalid channel.', 'code': 'BAD-CHANNEL'}"), Line(f"Full execution log: {str(emit._log_filepath)!r}"), ] expected_log = [ diff --git a/tests/unit/test_messages_emitter.py b/tests/unit/test_messages_emitter.py index 964a3ee..427c2a6 100644 --- a/tests/unit/test_messages_emitter.py +++ b/tests/unit/test_messages_emitter.py @@ -877,7 +877,25 @@ def test_reporterror_simple_message_developer_modes(mode, get_initiated_emitter) ] -@pytest.mark.parametrize("mode", [EmitterMode.QUIET, EmitterMode.BRIEF, EmitterMode.VERBOSE]) +def test_reporterror_detailed_info_quiet_modes(get_initiated_emitter): + """Report an error having detailed information, in final user modes. + + Check that "quiet" is indeed quiet. + """ + emitter = get_initiated_emitter(EmitterMode.QUIET) + error = CraftError("test message", details="boom") + emitter.error(error) + + full_log_message = f"Full execution log: {repr(emitter._log_filepath)}" + assert emitter.printer_calls == [ + call().show(sys.stderr, "test message", use_timestamp=False, end_line=True), + call().show(None, "Detailed information: boom", use_timestamp=False, end_line=True), + call().show(sys.stderr, full_log_message, use_timestamp=False, end_line=True), + call().stop(), + ] + + +@pytest.mark.parametrize("mode", [EmitterMode.BRIEF, EmitterMode.VERBOSE]) def test_reporterror_detailed_info_final_user_modes(mode, get_initiated_emitter): """Report an error having detailed information, in final user modes.""" emitter = get_initiated_emitter(mode) @@ -887,7 +905,7 @@ def test_reporterror_detailed_info_final_user_modes(mode, get_initiated_emitter) full_log_message = f"Full execution log: {repr(emitter._log_filepath)}" assert emitter.printer_calls == [ call().show(sys.stderr, "test message", use_timestamp=False, end_line=True), - call().show(None, "Detailed information: boom", use_timestamp=False, end_line=True), + call().show(sys.stderr, "Detailed information: boom", use_timestamp=False, end_line=True), call().show(sys.stderr, full_log_message, use_timestamp=False, end_line=True), call().stop(), ]