From 4ce0ecf660c681b17c4a1b508acff5e84216ec0d Mon Sep 17 00:00:00 2001 From: Yashvardhan Thanvi Date: Mon, 19 Oct 2026 23:47:12 +0530 Subject: [PATCH 1/2] widget.py: Avoid extra empty lines when message-lines or error-lines is 0 With message-lines set to 0, the detail of a message is truncated away entirely, but LogLine still prints an empty line, a line with only the indentation and another empty line after the message. Likewise, a failure message in verbose mode with error-lines set to 0 skips the log tail, but is still followed by two empty lines instead of the usual one. Only print the message detail when some of it is left, and only add the newline that introduces the failure details when they are printed. Failure messages keep the single empty line that separates them from the next message, and from the next entry in the failure summary. Fixes #2145 --- src/buildstream/_frontend/widget.py | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/buildstream/_frontend/widget.py b/src/buildstream/_frontend/widget.py index 0ed1b4977..20e9c47a8 100644 --- a/src/buildstream/_frontend/widget.py +++ b/src/buildstream/_frontend/widget.py @@ -782,29 +782,31 @@ def _render(self, message): else: lines[n_lines - 1] = lines[n_lines - 1].rstrip("\n") - detail = self._indent + self._indent.join(lines) + # Nothing is left to print when message-lines is 0 + if lines: + detail = self._indent + self._indent.join(lines) - text += "\n" - if message.message_type in ERROR_MESSAGES: - text += self._err_profile.fmt(detail, bold=True) - else: - text += self._detail_profile.fmt(detail) + text += "\n" + if message.message_type in ERROR_MESSAGES: + text += self._err_profile.fmt(detail, bold=True) + else: + text += self._detail_profile.fmt(detail) - if abbrev: - text += self._indent + self.content_profile.fmt( - "Message contains {} additional lines".format(n_lines - self._message_lines), dim=True - ) - text += "\n" + if abbrev: + text += self._indent + self.content_profile.fmt( + "Message contains {} additional lines".format(n_lines - self._message_lines), dim=True + ) + text += "\n" - extra_nl = True + extra_nl = True if message.scheduler and message.message_type == MessageType.FAIL: - text += "\n" - if self.context is not None and not self.context.log_verbose: + text += "\n" text += self._indent + self._err_profile.fmt("Log file: ") text += self._indent + self._logfile_widget.render(message) + "\n" elif self._log_lines > 0: + text += "\n" text += ( self._indent + self._err_profile.fmt("Printing the last {} lines from log file:".format(self._log_lines)) From ae2c2a9b04e25cb6e306bf39f7bcc6f5937ff88c Mon Sep 17 00:00:00 2001 From: Yashvardhan Thanvi Date: Mon, 19 Oct 2026 23:47:13 +0530 Subject: [PATCH 2/2] tests/frontend/logging.py: Test message-lines and error-lines set to 0 Render messages with LogLine and check that no empty lines are left behind when message-lines is 0, that a truncated message still reports how many lines were left out, and that a failure message is followed by a single empty line when error-lines is 0. This is a part of #2145 --- tests/frontend/logging.py | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/frontend/logging.py b/tests/frontend/logging.py index 0ab0fc5e0..03309135f 100644 --- a/tests/frontend/logging.py +++ b/tests/frontend/logging.py @@ -15,17 +15,24 @@ # Pylint doesn't play well with fixtures and dependency injection from pytest # pylint: disable=redefined-outer-name +import datetime import os import re +import click import pytest from buildstream._testing import create_repo from buildstream import _yaml from buildstream.exceptions import ErrorDomain +from buildstream._frontend.profile import Profile +from buildstream._frontend.widget import LogLine +from buildstream._message import Message, MessageType from buildstream._testing import cli # pylint: disable=unused-import +from tests.testutils import dummy_context + # Project directory DATA_DIR = os.path.dirname(os.path.realpath(__file__)) @@ -225,3 +232,53 @@ def test_log_line_element_names(cli, datafiles): assert log_name == staged_name assert log_key == staged_key assert log_key == keys[log_name] + + +# Render a message with LogLine the same way the frontend does, without colors +# +def render_log_line(context, message): + profile = Profile() + logger = LogLine(context, None, profile, profile, profile, profile, profile) + return click.unstyle(logger.render(message)) + + +# Test that message details are truncated to the configured message-lines, +# and that setting it to 0 does not leave empty lines behind. +# +@pytest.mark.parametrize( + "message_lines,expected", + [ + (0, "Running commands\n"), + (2, "Running commands\n\n line 1\n line 2\n Message contains 1 additional lines\n\n"), + ], + ids=["zero", "two"], +) +def test_message_lines(message_lines, expected): + with dummy_context() as context: + context.log_message_format = "%{message}" + context.log_message_lines = message_lines + + message = Message( + MessageType.INFO, "Running commands", element_name="test.bst", detail="line 1\nline 2\nline 3" + ) + assert render_log_line(context, message) == expected + + +# Test that a failure message is still followed by a single empty line, +# and not two, when error-lines is set to 0. +# +def test_error_lines_zero(): + with dummy_context() as context: + context.log_message_format = "%{message}" + context.log_verbose = True + context.log_error_lines = 0 + + message = Message( + MessageType.FAIL, + "Running commands", + element_name="test.bst", + elapsed=datetime.timedelta(), + logfile="test.log", + scheduler=True, + ) + assert render_log_line(context, message) == "Running commands\n\n"