From patchwork Sat Jan 10 20:08:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1443 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768075803; bh=hRNW7iWp4W5akplKVWCzSEFpdlpMNuDbmcRHhgYaSpU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=YW2LmRoasSSVkZs+Kel7T0fm1jrmsZNKy7KMJ0PJci88ZbJGeJ9nmwxlnUB/+DlDY 1DR4tSJ+hAjWBJtu9pToFXtX3Qchg8N/0WT7E8ZnR1N60y1cszemjqg7ElJf1Auwt2 QlM0ywpQ5Jjz1rscsRv0VcO/vwj/7RRvGleswN6xzXlH6yFScCYF2K/5qk7Sw/XSXq 0abC1sq7cYYdeN6vuBgD+9/dMMeUCbIwFk/WJmIym8kBzi1E3LOV5/TqtVD2QjZbAk HeugdrcBxb5Y0f8YOJCtt9AxflQvekBqgpT/99c4VTpSBmatH4ZWlJNuR8bgi2xrBF VTeEekKqVVTMQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 15C6069286 for ; Sat, 10 Jan 2026 13:10:03 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id dHvG5ZRKlIlC for ; Sat, 10 Jan 2026 13:10:03 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768075803; bh=hRNW7iWp4W5akplKVWCzSEFpdlpMNuDbmcRHhgYaSpU=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=YW2LmRoasSSVkZs+Kel7T0fm1jrmsZNKy7KMJ0PJci88ZbJGeJ9nmwxlnUB/+DlDY 1DR4tSJ+hAjWBJtu9pToFXtX3Qchg8N/0WT7E8ZnR1N60y1cszemjqg7ElJf1Auwt2 QlM0ywpQ5Jjz1rscsRv0VcO/vwj/7RRvGleswN6xzXlH6yFScCYF2K/5qk7Sw/XSXq 0abC1sq7cYYdeN6vuBgD+9/dMMeUCbIwFk/WJmIym8kBzi1E3LOV5/TqtVD2QjZbAk HeugdrcBxb5Y0f8YOJCtt9AxflQvekBqgpT/99c4VTpSBmatH4ZWlJNuR8bgi2xrBF VTeEekKqVVTMQ== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 02AB66927E for ; Sat, 10 Jan 2026 13:10:03 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768075801; bh=lAsnurcUH2Qktyoenz6V7wExaEb6V0rY7K7ky98Atok=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qcyp1vZgrbsNvy3XEFmIAabA7PNA7zUqvHh1UPKvreKH2f+PLA/6NttEZsfw0V12r 3jgabtwVvK85GW4DyFGC5YBKjcqmJNl5a80BYQ10V7ZjC2x294Py0K/qJTSlM11cYr 6eCBVvlJ55gAMEZMyrB9J8VV0bFnohZq5id8v4VEPmaPFAqC4P5TT5o7amXWo3rGto eTxgcMUrMEUun+j4flO/KnOJ2mH6DGXAR6GIzN16mNc05b3uu4j3/a+ROMuxcJiNj+ 1z2YVsS5ruRT20m+jgZO6BVjqg5NjbI5/n6eFSBdoW6Wa9kGuSIg0ThS9jezm3gbMe cZYp9xsAeGhYQ== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 00A8469275; Sat, 10 Jan 2026 13:10:01 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id cPUNLd_-NEnR; Sat, 10 Jan 2026 13:10:00 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1768075796; bh=JNp7ka14wjf8ml2X0L5E5rP200gbL4S/WZYOElodZ6c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oBSamjqw/AAaq1QsKtUn8ywnV5CrPf0H9d+U0PwUZi1VBXLWkBsXxHZXG5R+mCdYT GGw3IzU9+dDQSu+g4Gd/vpzcfvbyqOBfz9baS7iOnGPFCttqlTRtL2I5+FdbjNhQJi 3j/0mm/4qeIu1oqo4q0VWBjraprwUdj+3yPhVWOYDnEKWZ0jGUm7P3zMGYTdYoRCzT QnSKOrLKNj/V56tWmqtY3+Zya7kDv3L/57+BZ/ZTUyHridY1162SnVDBFrpuIBZCVr zUjjAZkA93JdFA723mMSL8rf1mbQOienoNFDL8d3KRi15H0j2Pbc5OEx4wfJAzFvdf fG5z1FvPbY/gA== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id C1F96690E7; Sat, 10 Jan 2026 13:09:55 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Sat, 10 Jan 2026 13:08:18 -0700 Message-ID: <20260110200828.168672-16-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260110200828.168672-1-sjg@u-boot.org> References: <20260110200828.168672-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: EABALP5JSM4DEWILCBJRPPZ2XSACEGWR X-Message-ID-Hash: EABALP5JSM4DEWILCBJRPPZ2XSACEGWR X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 15/18] buildman: Move print_build_summary() to ResultHandler List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Move the build-completion, summary display from Builder to ResultHandler. This method prints the final build statistics including total builds, previously done, newly built, kconfig reconfigs, duration, rate, and any thread exceptions. The method now takes all required values as parameters rather than accessing Builder instance attributes. Update the tests to call the ResultHandler method directly with explicit parameters. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- tools/buildman/builder.py | 35 ++------------------- tools/buildman/resulthandler.py | 40 ++++++++++++++++++++++++ tools/buildman/test_builder.py | 54 ++++++++------------------------- 3 files changed, 55 insertions(+), 74 deletions(-) diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py index 412acfa9e4b..5df6f915285 100644 --- a/tools/buildman/builder.py +++ b/tools/buildman/builder.py @@ -1251,37 +1251,8 @@ class Builder: # Wait until we have processed all output self.out_queue.join() if not self._opts.ide: - self._print_build_summary() + self._result_handler.print_build_summary( + self.count, self.already_done, self.kconfig_reconfig, + self._start_time, self.thread_exceptions) return (self.fail, self.warned, self.thread_exceptions) - - def _print_build_summary(self): - """Print a summary of the build results - - Show the number of boards built, how many were already done, duration - and build rate. Also show any thread exceptions that occurred. - """ - tprint() - - msg = f'Completed: {self.count} total built' - if self.already_done or self.kconfig_reconfig: - parts = [] - if self.already_done: - parts.append(f'{self.already_done} previously') - if self.already_done != self.count: - parts.append(f'{self.count - self.already_done} newly') - if self.kconfig_reconfig: - parts.append(f'{self.kconfig_reconfig} reconfig') - msg += ' (' + ', '.join(parts) + ')' - duration = datetime.now() - self._start_time - if duration > timedelta(microseconds=1000000): - if duration.microseconds >= 500000: - duration = duration + timedelta(seconds=1) - duration -= timedelta(microseconds=duration.microseconds) - rate = float(self.count) / duration.total_seconds() - msg += f', duration {duration}, rate {rate:1.2f}' - tprint(msg) - if self.thread_exceptions: - tprint( - f'Failed: {len(self.thread_exceptions)} thread exceptions', - colour=self.col.RED) diff --git a/tools/buildman/resulthandler.py b/tools/buildman/resulthandler.py index d1d6232ff97..2c5488c7154 100644 --- a/tools/buildman/resulthandler.py +++ b/tools/buildman/resulthandler.py @@ -6,6 +6,7 @@ """Result writer for buildman build results""" +from datetime import datetime, timedelta import sys from buildman.outcome import (BoardStatus, ErrLine, Outcome, @@ -185,6 +186,45 @@ class ResultHandler: """ return self._error_lines + def print_build_summary(self, count, already_done, kconfig_reconfig, + start_time, thread_exceptions): + """Print a summary of the build results + + Show the number of boards built, how many were already done, duration + and build rate. Also show any thread exceptions that occurred. + + Args: + count (int): Total number of builds + already_done (int): Number of builds already completed previously + kconfig_reconfig (int): Number of builds triggered by Kconfig changes + start_time (datetime): When the build started + thread_exceptions (list): List of thread exceptions that occurred + """ + tprint() + + msg = f'Completed: {count} total built' + if already_done or kconfig_reconfig: + parts = [] + if already_done: + parts.append(f'{already_done} previously') + if already_done != count: + parts.append(f'{count - already_done} newly') + if kconfig_reconfig: + parts.append(f'{kconfig_reconfig} reconfig') + msg += ' (' + ', '.join(parts) + ')' + duration = datetime.now() - start_time + if duration > timedelta(microseconds=1000000): + if duration.microseconds >= 500000: + duration = duration + timedelta(seconds=1) + duration -= timedelta(microseconds=duration.microseconds) + rate = float(count) / duration.total_seconds() + msg += f', duration {duration}, rate {rate:1.2f}' + tprint(msg) + if thread_exceptions: + tprint( + f'Failed: {len(thread_exceptions)} thread exceptions', + colour=self._col.RED) + def colour_num(self, num): """Format a number with colour depending on its value diff --git a/tools/buildman/test_builder.py b/tools/buildman/test_builder.py index 6d75cfbc04f..88d859d145c 100644 --- a/tools/buildman/test_builder.py +++ b/tools/buildman/test_builder.py @@ -769,7 +769,7 @@ class TestMake(unittest.TestCase): class TestPrintBuildSummary(unittest.TestCase): - """Tests for Builder._print_build_summary()""" + """Tests for ResultHandler.print_build_summary()""" def setUp(self): """Set up test fixtures""" @@ -785,8 +785,7 @@ class TestPrintBuildSummary(unittest.TestCase): result_handler=self.result_handler) # Set a start time in the past (less than 1 second ago to avoid # duration output) - self.builder._start_time = datetime.now() - self.builder.thread_exceptions = [] + self.start_time = datetime.now() terminal.set_print_test_mode() def tearDown(self): @@ -795,12 +794,8 @@ class TestPrintBuildSummary(unittest.TestCase): def test_basic_count(self): """Test basic completed message with just count""" - self.builder.count = 10 - self.builder.already_done = 0 - self.builder.kconfig_reconfig = 0 - terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(10, 0, 0, self.start_time, []) lines = terminal.get_print_test_lines() # First line is blank, second is the message @@ -811,12 +806,8 @@ class TestPrintBuildSummary(unittest.TestCase): def test_all_previously_done(self): """Test message when all builds were already done""" - self.builder.count = 5 - self.builder.already_done = 5 - self.builder.kconfig_reconfig = 0 - terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(5, 5, 0, self.start_time, []) lines = terminal.get_print_test_lines() self.assertIn('5 previously', lines[1].text) @@ -824,12 +815,8 @@ class TestPrintBuildSummary(unittest.TestCase): def test_some_newly_built(self): """Test message with some previously done and some new""" - self.builder.count = 10 - self.builder.already_done = 6 - self.builder.kconfig_reconfig = 0 - terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(10, 6, 0, self.start_time, []) lines = terminal.get_print_test_lines() self.assertIn('6 previously', lines[1].text) @@ -837,68 +824,51 @@ class TestPrintBuildSummary(unittest.TestCase): def test_with_kconfig_reconfig(self): """Test message with kconfig reconfigurations""" - self.builder.count = 8 - self.builder.already_done = 0 - self.builder.kconfig_reconfig = 3 - terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(8, 0, 3, self.start_time, []) lines = terminal.get_print_test_lines() self.assertIn('3 reconfig', lines[1].text) def test_thread_exceptions(self): """Test message with thread exceptions""" - self.builder.count = 5 - self.builder.already_done = 0 - self.builder.kconfig_reconfig = 0 - self.builder.thread_exceptions = [Exception('err1'), Exception('err2')] + exceptions = [Exception('err1'), Exception('err2')] terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(5, 0, 0, self.start_time, exceptions) lines = terminal.get_print_test_lines() self.assertEqual(len(lines), 3) self.assertIn('Failed: 2 thread exceptions', lines[2].text) - @mock.patch('buildman.builder.datetime') + @mock.patch('buildman.resulthandler.datetime') def test_duration_and_rate(self, mock_datetime): """Test message includes duration and rate for long builds""" - self.builder.count = 100 - self.builder.already_done = 0 - self.builder.kconfig_reconfig = 0 - # Mock datetime to simulate a 10 second build start_time = datetime(2024, 1, 1, 12, 0, 0) end_time = datetime(2024, 1, 1, 12, 0, 10) - self.builder._start_time = start_time mock_datetime.now.return_value = end_time mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs) terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(100, 0, 0, start_time, []) lines = terminal.get_print_test_lines() self.assertIn('duration', lines[1].text) self.assertIn('rate', lines[1].text) self.assertIn('10.00', lines[1].text) # 100 boards / 10 seconds - @mock.patch('buildman.builder.datetime') + @mock.patch('buildman.resulthandler.datetime') def test_duration_rounds_up(self, mock_datetime): """Test duration rounds up when microseconds >= 500000""" - self.builder.count = 100 - self.builder.already_done = 0 - self.builder.kconfig_reconfig = 0 - # Mock datetime to simulate a 10.6 second build (should round to 11) start_time = datetime(2024, 1, 1, 12, 0, 0) end_time = datetime(2024, 1, 1, 12, 0, 10, 600000) # 10.6 seconds - self.builder._start_time = start_time mock_datetime.now.return_value = end_time mock_datetime.side_effect = lambda *args, **kwargs: datetime(*args, **kwargs) terminal.get_print_test_lines() # Clear - self.builder._print_build_summary() + self.result_handler.print_build_summary(100, 0, 0, start_time, []) lines = terminal.get_print_test_lines() # Duration should be rounded up to 11 seconds