From: Simon Glass <simon.glass@canonical.com> 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 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- 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 -- 2.43.0