[Concept,16/18] buildman: Add unit tests for make()

Message ID 20260109183116.3262115-17-sjg@u-boot.org
State New
Headers
Series buildman: Improve test coverage for builder.py |

Commit Message

Simon Glass Jan. 9, 2026, 6:31 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

Add unit tests for the make() method covering:
- Basic make execution with correct arguments
- Loop detection appends helpful message to stderr
- Verbose build mode prepends command to output
- State flags are reset at the start of each call

Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

 tools/buildman/main.py         |  1 +
 tools/buildman/test_builder.py | 92 ++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+)
  

Patch

diff --git a/tools/buildman/main.py b/tools/buildman/main.py
index dadfd629506..044f7a32ebb 100755
--- a/tools/buildman/main.py
+++ b/tools/buildman/main.py
@@ -59,6 +59,7 @@  def run_tests(skip_net_tests, debug, verbose, args):
          test_builder.TestShowNotBuilt,
          test_builder.TestPrepareOutputSpace,
          test_builder.TestCheckOutputForLoop,
+         test_builder.TestMake,
          'buildman.toolchain'])
 
     return (0 if result.wasSuccessful() else 1)
diff --git a/tools/buildman/test_builder.py b/tools/buildman/test_builder.py
index 09809a07706..1ec371e7821 100644
--- a/tools/buildman/test_builder.py
+++ b/tools/buildman/test_builder.py
@@ -627,5 +627,97 @@  class TestCheckOutputForLoop(unittest.TestCase):
         self.assertTrue(self.builder._terminated)
 
 
+class TestMake(unittest.TestCase):
+    """Tests for Builder.make()"""
+
+    def setUp(self):
+        """Set up test fixtures"""
+        self.builder = builder.Builder(
+            toolchains=None, base_dir='/tmp/test', git_dir='/src/repo',
+            num_threads=4, num_jobs=1)
+
+    @mock.patch('buildman.builder.command.run_one')
+    def test_make_basic(self, mock_run_one):
+        """Test basic make execution"""
+        mock_result = mock.Mock()
+        mock_result.stdout = 'build output'
+        mock_result.stderr = ''
+        mock_result.combined = 'build output'
+        mock_run_one.return_value = mock_result
+
+        result = self.builder.make(None, None, None, '/tmp/build', 'all')
+
+        self.assertEqual(result, mock_result)
+        mock_run_one.assert_called_once()
+        # Check make was called with correct args
+        call_args = mock_run_one.call_args
+        self.assertEqual(call_args[0][0], 'make')
+        self.assertEqual(call_args[0][1], 'all')
+        self.assertEqual(call_args[1]['cwd'], '/tmp/build')
+
+    @mock.patch('buildman.builder.command.run_one')
+    def test_make_with_loop_detection(self, mock_run_one):
+        """Test make adds helpful message when loop is detected"""
+        mock_result = mock.Mock()
+        mock_result.stdout = ''
+        mock_result.stderr = 'config error'
+        mock_result.combined = 'config error'
+        mock_run_one.return_value = mock_result
+
+        # Simulate loop detection by setting _terminated during the call
+        def side_effect(*args, **kwargs):
+            # Simulate output_func being called with loop data
+            output_func = kwargs.get('output_func')
+            if output_func:
+                self.builder._restarting_config = True
+                output_func(None, b'(CONFIG_X) [] (NEW)\n(CONFIG_X) [] (NEW)')
+            return mock_result
+
+        mock_run_one.side_effect = side_effect
+
+        result = self.builder.make(None, None, None, '/tmp/build', 'defconfig')
+
+        # Check helpful message was appended
+        self.assertIn('did you define an int/hex Kconfig', result.stderr)
+
+    @mock.patch('buildman.builder.command.run_one')
+    def test_make_verbose_build(self, mock_run_one):
+        """Test make prepends command in verbose mode"""
+        mock_result = mock.Mock()
+        mock_result.stdout = 'output'
+        mock_result.stderr = ''
+        mock_result.combined = 'output'
+        mock_run_one.return_value = mock_result
+
+        self.builder.verbose_build = True
+
+        result = self.builder.make(None, None, None, '/tmp/build', 'all', '-j4')
+
+        # Check command was prepended to stdout and combined
+        self.assertIn('make all -j4', result.stdout)
+        self.assertIn('make all -j4', result.combined)
+
+    @mock.patch('buildman.builder.command.run_one')
+    def test_make_resets_state(self, mock_run_one):
+        """Test make resets _restarting_config and _terminated flags"""
+        mock_result = mock.Mock()
+        mock_result.stdout = ''
+        mock_result.stderr = ''
+        mock_result.combined = ''
+        mock_run_one.return_value = mock_result
+
+        # Set flags to non-default values
+        self.builder._restarting_config = True
+        self.builder._terminated = True
+
+        self.builder.make(None, None, None, '/tmp/build', 'all')
+
+        # Flags should be reset at the start of make()
+        # (they may be set again by output_func, but start fresh)
+        # Since mock doesn't call output_func, they stay False
+        self.assertFalse(self.builder._restarting_config)
+        self.assertFalse(self.builder._terminated)
+
+
 if __name__ == '__main__':
     unittest.main()