blob: 4e12c671a3db7f2b4454456286b786cc65a84b1f [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassed098bb2014-09-05 19:00:13 -06002# Copyright (c) 2014 Google, Inc
3#
Simon Glassed098bb2014-09-05 19:00:13 -06004
Simon Glass62dc0942024-11-08 08:23:44 -07005import io
Simon Glassed098bb2014-09-05 19:00:13 -06006import os
Simon Glassada78d42023-07-19 17:48:16 -06007from pathlib import Path
Simon Glass62dc0942024-11-08 08:23:44 -07008import re
Simon Glassed098bb2014-09-05 19:00:13 -06009import shutil
10import sys
11import tempfile
Simon Glassada78d42023-07-19 17:48:16 -060012import time
Simon Glassed098bb2014-09-05 19:00:13 -060013import unittest
14
Simon Glassf0d9c102020-04-17 18:09:02 -060015from buildman import board
Simon Glass20751d62022-07-11 19:04:03 -060016from buildman import boards
Simon Glassf0d9c102020-04-17 18:09:02 -060017from buildman import bsettings
18from buildman import cmdline
19from buildman import control
20from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060021from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070022from u_boot_pylib import command
23from u_boot_pylib import terminal
24from u_boot_pylib import test_util
25from u_boot_pylib import tools
Simon Glassed098bb2014-09-05 19:00:13 -060026
Simon Glass5e0441d2014-09-05 19:00:15 -060027settings_data = '''
28# Buildman settings file
Tom Rini93ebd462022-11-09 19:14:53 -070029[global]
Simon Glass5e0441d2014-09-05 19:00:15 -060030
31[toolchain]
32
33[toolchain-alias]
34
35[make-flags]
36src=/home/sjg/c/src
37chroot=/home/sjg/c/chroot
Masahiro Yamada72e545a2018-08-06 20:47:38 +090038vboot=VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
Simon Glass5e0441d2014-09-05 19:00:15 -060039chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
40chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
41chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
42'''
43
Simon Glassd4c6c8a2022-07-11 19:03:58 -060044BOARDS = [
Simon Glass560bd4a2023-07-19 17:48:12 -060045 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''],
46 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''],
Simon Glasscbd36582014-09-05 19:00:16 -060047 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
Simon Glasscbd36582014-09-05 19:00:16 -060048 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
49]
50
Simon Glass8e959562014-09-05 19:00:20 -060051commit_shortlog = """4aca821 patman: Avoid changing the order of tags
5239403bb patman: Use --no-pager' to stop git from forking a pager
53db6e6f2 patman: Remove the -a option
54f2ccf03 patman: Correct unit tests to run correctly
551d097f9 patman: Fix indentation in terminal.py
56d073747 patman: Support the 'reverse' option for 'git log
57"""
58
59commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
60Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
61Date: Fri Aug 22 19:12:41 2014 +0900
62
63 buildman: refactor help message
64
65 "buildman [options]" is displayed by default.
66
67 Append the rest of help messages to parser.usage
68 instead of replacing it.
69
70 Besides, "-b <branch>" is not mandatory since commit fea5858e.
71 Drop it from the usage.
72
73 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
74""",
75"""commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
76Author: Simon Glass <sjg@chromium.org>
77Date: Thu Aug 14 16:48:25 2014 -0600
78
79 patman: Support the 'reverse' option for 'git log'
80
81 This option is currently not supported, but needs to be, for buildman to
82 operate as expected.
83
84 Series-changes: 7
85 - Add new patch to fix the 'reverse' bug
86
Simon Glass359b55a62014-09-05 19:00:23 -060087 Series-version: 8
Simon Glass8e959562014-09-05 19:00:20 -060088
89 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
90 Reported-by: York Sun <yorksun@freescale.com>
91 Signed-off-by: Simon Glass <sjg@chromium.org>
92
93""",
94"""commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
95Author: Simon Glass <sjg@chromium.org>
96Date: Sat Aug 9 11:44:32 2014 -0600
97
98 patman: Fix indentation in terminal.py
99
100 This code came from a different project with 2-character indentation. Fix
101 it for U-Boot.
102
103 Series-changes: 6
104 - Add new patch to fix indentation in teminal.py
105
106 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
107 Signed-off-by: Simon Glass <sjg@chromium.org>
108
109""",
110"""commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
111Author: Simon Glass <sjg@chromium.org>
112Date: Sat Aug 9 11:08:24 2014 -0600
113
114 patman: Correct unit tests to run correctly
115
116 It seems that doctest behaves differently now, and some of the unit tests
117 do not run. Adjust the tests to work correctly.
118
119 ./tools/patman/patman --test
120 <unittest.result.TestResult run=10 errors=0 failures=0>
121
122 Series-changes: 6
123 - Add new patch to fix patman unit tests
124
125 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
126
127""",
128"""commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
129Author: Simon Glass <sjg@chromium.org>
130Date: Sat Aug 9 12:06:02 2014 -0600
131
132 patman: Remove the -a option
133
134 It seems that this is no longer needed, since checkpatch.pl will catch
135 whitespace problems in patches. Also the option is not widely used, so
136 it seems safe to just remove it.
137
138 Series-changes: 6
139 - Add new patch to remove patman's -a option
140
141 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
142 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
143
144""",
145"""commit 39403bb4f838153028a6f21ca30bf100f3791133
146Author: Simon Glass <sjg@chromium.org>
147Date: Thu Aug 14 21:50:52 2014 -0600
148
149 patman: Use --no-pager' to stop git from forking a pager
150
151""",
152"""commit 4aca821e27e97925c039e69fd37375b09c6f129c
153Author: Simon Glass <sjg@chromium.org>
154Date: Fri Aug 22 15:57:39 2014 -0600
155
156 patman: Avoid changing the order of tags
157
158 patman collects tags that it sees in the commit and places them nicely
159 sorted at the end of the patch. However, this is not really necessary and
160 in fact is apparently not desirable.
161
162 Series-changes: 9
163 - Add new patch to avoid changing the order of tags
164
Simon Glass359b55a62014-09-05 19:00:23 -0600165 Series-version: 9
166
Simon Glass8e959562014-09-05 19:00:20 -0600167 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
168 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
169"""]
170
171TEST_BRANCH = '__testbranch'
172
Simon Glassed098bb2014-09-05 19:00:13 -0600173class TestFunctional(unittest.TestCase):
174 """Functional test for buildman.
175
176 This aims to test from just below the invocation of buildman (parsing
177 of arguments) to 'make' and 'git' invocation. It is not a true
178 emd-to-end test, as it mocks git, make and the tool chain. But this
179 makes it easier to detect when the builder is doing the wrong thing,
180 since in many cases this test code will fail. For example, only a
181 very limited subset of 'git' arguments is supported - anything
182 unexpected will fail.
183 """
184 def setUp(self):
185 self._base_dir = tempfile.mkdtemp()
Tom Rinie95eddc2019-10-07 17:17:36 -0400186 self._output_dir = tempfile.mkdtemp()
Simon Glassed098bb2014-09-05 19:00:13 -0600187 self._git_dir = os.path.join(self._base_dir, 'src')
188 self._buildman_pathname = sys.argv[0]
Simon Glass5d4a7872016-07-27 20:33:00 -0600189 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
Simon Glassed098bb2014-09-05 19:00:13 -0600190 command.test_result = self._HandleCommand
Simon Glass06b83a52023-07-19 17:49:05 -0600191 bsettings.setup(None)
192 bsettings.add_file(settings_data)
Simon Glass8e959562014-09-05 19:00:20 -0600193 self.setupToolchains()
194 self._toolchains.Add('arm-gcc', test=False)
195 self._toolchains.Add('powerpc-gcc', test=False)
Simon Glass20751d62022-07-11 19:04:03 -0600196 self._boards = boards.Boards()
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600197 for brd in BOARDS:
Simon Glass127a2392022-07-11 19:04:02 -0600198 self._boards.add_board(board.Board(*brd))
Simon Glassed098bb2014-09-05 19:00:13 -0600199
Simon Glass8e959562014-09-05 19:00:20 -0600200 # Directories where the source been cloned
201 self._clone_dirs = []
202 self._commits = len(commit_shortlog.splitlines()) + 1
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600203 self._total_builds = self._commits * len(BOARDS)
Simon Glass8e959562014-09-05 19:00:20 -0600204
205 # Number of calls to make
206 self._make_calls = 0
207
208 # Map of [board, commit] to error messages
209 self._error = {}
210
Simon Glass4aeceb92014-09-05 19:00:22 -0600211 self._test_branch = TEST_BRANCH
212
Tom Rini93ebd462022-11-09 19:14:53 -0700213 # Set to True to report missing blobs
214 self._missing = False
215
Simon Glasscef26b82023-07-19 17:48:15 -0600216 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
217 self._test_dir = os.path.join(self._buildman_dir, 'test')
218
219 # Set up some fake source files
220 shutil.copytree(self._test_dir, self._git_dir)
221
Simon Glass8e959562014-09-05 19:00:20 -0600222 # Avoid sending any output and clear all terminal output
Simon Glass02811582022-01-29 14:14:18 -0700223 terminal.set_print_test_mode()
224 terminal.get_print_test_lines()
Simon Glass8e959562014-09-05 19:00:20 -0600225
Simon Glassed098bb2014-09-05 19:00:13 -0600226 def tearDown(self):
227 shutil.rmtree(self._base_dir)
Simon Glass211c74b2022-11-09 19:14:52 -0700228 shutil.rmtree(self._output_dir)
Simon Glassed098bb2014-09-05 19:00:13 -0600229
Simon Glass8e959562014-09-05 19:00:20 -0600230 def setupToolchains(self):
231 self._toolchains = toolchain.Toolchains()
232 self._toolchains.Add('gcc', test=False)
233
Simon Glassed098bb2014-09-05 19:00:13 -0600234 def _RunBuildman(self, *args):
Simon Glass840be732022-01-29 14:14:05 -0700235 return command.run_pipe([[self._buildman_pathname] + list(args)],
Simon Glassed098bb2014-09-05 19:00:13 -0600236 capture=True, capture_stderr=True)
237
Simon Glass09afcb72023-07-19 17:48:28 -0600238 def _RunControl(self, *args, brds=False, clean_dir=False,
239 test_thread_exceptions=False, get_builder=True):
Simon Glassa29b3ea2021-04-11 16:27:25 +1200240 """Run buildman
241
242 Args:
243 args: List of arguments to pass
Simon Glass09afcb72023-07-19 17:48:28 -0600244 brds: Boards object, or False to pass self._boards, or None to pass
245 None
Simon Glassa29b3ea2021-04-11 16:27:25 +1200246 clean_dir: Used for tests only, indicates that the existing output_dir
247 should be removed before starting the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200248 test_thread_exceptions: Uses for tests only, True to make the threads
249 raise an exception instead of reporting their result. This simulates
250 a failure in the code somewhere
Simon Glass09afcb72023-07-19 17:48:28 -0600251 get_builder (bool): Set self._builder to the resulting builder
Simon Glassa29b3ea2021-04-11 16:27:25 +1200252
253 Returns:
254 result code from buildman
255 """
Simon Glassed098bb2014-09-05 19:00:13 -0600256 sys.argv = [sys.argv[0]] + list(args)
Simon Glassa56ac992023-07-19 17:49:04 -0600257 args = cmdline.parse_args()
Simon Glass09afcb72023-07-19 17:48:28 -0600258 if brds == False:
259 brds = self._boards
Simon Glassc1e1e1d2023-07-19 17:48:30 -0600260 result = control.do_buildman(
Simon Glassa56ac992023-07-19 17:49:04 -0600261 args, toolchains=self._toolchains, make_func=self._HandleMake,
262 brds=brds, clean_dir=clean_dir,
Simon Glassc1e1e1d2023-07-19 17:48:30 -0600263 test_thread_exceptions=test_thread_exceptions)
Simon Glass09afcb72023-07-19 17:48:28 -0600264 if get_builder:
Simon Glassaf0e29f2023-07-19 17:48:31 -0600265 self._builder = control.TEST_BUILDER
Simon Glass8e959562014-09-05 19:00:20 -0600266 return result
Simon Glassed098bb2014-09-05 19:00:13 -0600267
268 def testFullHelp(self):
269 command.test_result = None
270 result = self._RunBuildman('-H')
Simon Glass79cc9be2022-11-09 19:14:43 -0700271 help_file = os.path.join(self._buildman_dir, 'README.rst')
Tom Rinic3c0b6d2018-01-16 15:29:50 -0500272 # Remove possible extraneous strings
273 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
274 gothelp = result.stdout.replace(extra, '')
275 self.assertEqual(len(gothelp), os.path.getsize(help_file))
Simon Glassed098bb2014-09-05 19:00:13 -0600276 self.assertEqual(0, len(result.stderr))
277 self.assertEqual(0, result.return_code)
278
279 def testHelp(self):
280 command.test_result = None
281 result = self._RunBuildman('-h')
Simon Glass79cc9be2022-11-09 19:14:43 -0700282 help_file = os.path.join(self._buildman_dir, 'README.rst')
Simon Glassed098bb2014-09-05 19:00:13 -0600283 self.assertTrue(len(result.stdout) > 1000)
284 self.assertEqual(0, len(result.stderr))
285 self.assertEqual(0, result.return_code)
286
287 def testGitSetup(self):
288 """Test gitutils.Setup(), from outside the module itself"""
289 command.test_result = command.CommandResult(return_code=1)
Simon Glass761648b2022-01-29 14:14:11 -0700290 gitutil.setup()
Simon Glassed098bb2014-09-05 19:00:13 -0600291 self.assertEqual(gitutil.use_no_decorate, False)
292
293 command.test_result = command.CommandResult(return_code=0)
Simon Glass761648b2022-01-29 14:14:11 -0700294 gitutil.setup()
Simon Glassed098bb2014-09-05 19:00:13 -0600295 self.assertEqual(gitutil.use_no_decorate, True)
296
297 def _HandleCommandGitLog(self, args):
Simon Glass642e9a62016-03-12 18:50:31 -0700298 if args[-1] == '--':
299 args = args[:-1]
Simon Glassed098bb2014-09-05 19:00:13 -0600300 if '-n0' in args:
301 return command.CommandResult(return_code=0)
Simon Glass4aeceb92014-09-05 19:00:22 -0600302 elif args[-1] == 'upstream/master..%s' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600303 return command.CommandResult(return_code=0, stdout=commit_shortlog)
304 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
Simon Glass4aeceb92014-09-05 19:00:22 -0600305 if args[-1] == self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600306 count = int(args[3][2:])
307 return command.CommandResult(return_code=0,
308 stdout=''.join(commit_log[:count]))
Simon Glassed098bb2014-09-05 19:00:13 -0600309
310 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600311 print('git log', args)
Simon Glassed098bb2014-09-05 19:00:13 -0600312 sys.exit(1)
313
Simon Glass8e959562014-09-05 19:00:20 -0600314 def _HandleCommandGitConfig(self, args):
315 config = args[0]
316 if config == 'sendemail.aliasesfile':
317 return command.CommandResult(return_code=0)
318 elif config.startswith('branch.badbranch'):
319 return command.CommandResult(return_code=1)
Simon Glass4aeceb92014-09-05 19:00:22 -0600320 elif config == 'branch.%s.remote' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600321 return command.CommandResult(return_code=0, stdout='upstream\n')
Simon Glass4aeceb92014-09-05 19:00:22 -0600322 elif config == 'branch.%s.merge' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600323 return command.CommandResult(return_code=0,
324 stdout='refs/heads/master\n')
325
326 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600327 print('git config', args)
Simon Glass8e959562014-09-05 19:00:20 -0600328 sys.exit(1)
329
Simon Glassed098bb2014-09-05 19:00:13 -0600330 def _HandleCommandGit(self, in_args):
331 """Handle execution of a git command
332
333 This uses a hacked-up parser.
334
335 Args:
336 in_args: Arguments after 'git' from the command line
337 """
338 git_args = [] # Top-level arguments to git itself
339 sub_cmd = None # Git sub-command selected
340 args = [] # Arguments to the git sub-command
341 for arg in in_args:
342 if sub_cmd:
343 args.append(arg)
344 elif arg[0] == '-':
345 git_args.append(arg)
346 else:
Simon Glass8e959562014-09-05 19:00:20 -0600347 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
348 git_args.append(arg)
349 else:
350 sub_cmd = arg
Simon Glassed098bb2014-09-05 19:00:13 -0600351 if sub_cmd == 'config':
Simon Glass8e959562014-09-05 19:00:20 -0600352 return self._HandleCommandGitConfig(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600353 elif sub_cmd == 'log':
354 return self._HandleCommandGitLog(args)
Simon Glass8e959562014-09-05 19:00:20 -0600355 elif sub_cmd == 'clone':
356 return command.CommandResult(return_code=0)
357 elif sub_cmd == 'checkout':
358 return command.CommandResult(return_code=0)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300359 elif sub_cmd == 'worktree':
360 return command.CommandResult(return_code=0)
Simon Glassed098bb2014-09-05 19:00:13 -0600361
362 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600363 print('git', git_args, sub_cmd, args)
Simon Glassed098bb2014-09-05 19:00:13 -0600364 sys.exit(1)
365
366 def _HandleCommandNm(self, args):
367 return command.CommandResult(return_code=0)
368
369 def _HandleCommandObjdump(self, args):
370 return command.CommandResult(return_code=0)
371
Alex Kiernanf07ed232018-05-31 04:48:33 +0000372 def _HandleCommandObjcopy(self, args):
373 return command.CommandResult(return_code=0)
374
Simon Glassed098bb2014-09-05 19:00:13 -0600375 def _HandleCommandSize(self, args):
376 return command.CommandResult(return_code=0)
377
Simon Glass62dc0942024-11-08 08:23:44 -0700378 def _HandleCommandCpp(self, args):
379 # args ['-nostdinc', '-P', '-I', '/tmp/tmp7f17xk_o/src', '-undef',
380 # '-x', 'assembler-with-cpp', fname]
381 fname = args[7]
382 buf = io.StringIO()
383 for line in tools.read_file(fname, False).splitlines():
384 if line.startswith('#include'):
385 # Example: #include <configs/renesas_rcar2.config>
386 m_incfname = re.match('#include <(.*)>', line)
387 data = tools.read_file(m_incfname.group(1), False)
388 for line in data.splitlines():
389 print(line, file=buf)
390 else:
391 print(line, file=buf)
392 return command.CommandResult(stdout=buf.getvalue(), return_code=0)
393
Simon Glassed098bb2014-09-05 19:00:13 -0600394 def _HandleCommand(self, **kwargs):
395 """Handle a command execution.
396
397 The command is in kwargs['pipe-list'], as a list of pipes, each a
398 list of commands. The command should be emulated as required for
399 testing purposes.
400
401 Returns:
402 A CommandResult object
403 """
404 pipe_list = kwargs['pipe_list']
Simon Glass8e959562014-09-05 19:00:20 -0600405 wc = False
Simon Glassed098bb2014-09-05 19:00:13 -0600406 if len(pipe_list) != 1:
Simon Glass8e959562014-09-05 19:00:20 -0600407 if pipe_list[1] == ['wc', '-l']:
408 wc = True
409 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600410 print('invalid pipe', kwargs)
Simon Glass8e959562014-09-05 19:00:20 -0600411 sys.exit(1)
Simon Glassed098bb2014-09-05 19:00:13 -0600412 cmd = pipe_list[0][0]
413 args = pipe_list[0][1:]
Simon Glass8e959562014-09-05 19:00:20 -0600414 result = None
Simon Glassed098bb2014-09-05 19:00:13 -0600415 if cmd == 'git':
Simon Glass8e959562014-09-05 19:00:20 -0600416 result = self._HandleCommandGit(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600417 elif cmd == './scripts/show-gnu-make':
418 return command.CommandResult(return_code=0, stdout='make')
Simon Glass8e959562014-09-05 19:00:20 -0600419 elif cmd.endswith('nm'):
Simon Glassed098bb2014-09-05 19:00:13 -0600420 return self._HandleCommandNm(args)
Simon Glass8e959562014-09-05 19:00:20 -0600421 elif cmd.endswith('objdump'):
Simon Glassed098bb2014-09-05 19:00:13 -0600422 return self._HandleCommandObjdump(args)
Alex Kiernanf07ed232018-05-31 04:48:33 +0000423 elif cmd.endswith('objcopy'):
424 return self._HandleCommandObjcopy(args)
Simon Glass8e959562014-09-05 19:00:20 -0600425 elif cmd.endswith( 'size'):
Simon Glassed098bb2014-09-05 19:00:13 -0600426 return self._HandleCommandSize(args)
Simon Glass62dc0942024-11-08 08:23:44 -0700427 elif cmd.endswith( 'cpp'):
428 return self._HandleCommandCpp(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600429
Simon Glass8e959562014-09-05 19:00:20 -0600430 if not result:
431 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600432 print('unknown command', kwargs)
Simon Glass8e959562014-09-05 19:00:20 -0600433 sys.exit(1)
434
435 if wc:
436 result.stdout = len(result.stdout.splitlines())
437 return result
Simon Glassed098bb2014-09-05 19:00:13 -0600438
439 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
440 """Handle execution of 'make'
441
442 Args:
443 commit: Commit object that is being built
444 brd: Board object that is being built
445 stage: Stage that we are at (mrproper, config, build)
446 cwd: Directory where make should be run
447 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700448 kwargs: Arguments to pass to command.run_pipe()
Simon Glassed098bb2014-09-05 19:00:13 -0600449 """
Simon Glass8e959562014-09-05 19:00:20 -0600450 self._make_calls += 1
Simon Glass828d70d2023-02-21 12:40:29 -0700451 out_dir = ''
452 for arg in args:
453 if arg.startswith('O='):
454 out_dir = arg[2:]
Simon Glassed098bb2014-09-05 19:00:13 -0600455 if stage == 'mrproper':
456 return command.CommandResult(return_code=0)
457 elif stage == 'config':
Simon Glass828d70d2023-02-21 12:40:29 -0700458 fname = os.path.join(cwd or '', out_dir, '.config')
459 tools.write_file(fname, b'CONFIG_SOMETHING=1')
Simon Glassed098bb2014-09-05 19:00:13 -0600460 return command.CommandResult(return_code=0,
461 combined='Test configuration complete')
Simon Glassd6c1ec82023-10-26 14:31:10 -0400462 elif stage == 'oldconfig':
463 return command.CommandResult(return_code=0)
Simon Glassed098bb2014-09-05 19:00:13 -0600464 elif stage == 'build':
Simon Glass8e959562014-09-05 19:00:20 -0600465 stderr = ''
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600466 fname = os.path.join(cwd or '', out_dir, 'u-boot')
Simon Glass80025522022-01-29 14:14:04 -0700467 tools.write_file(fname, b'U-Boot')
Tom Rini93ebd462022-11-09 19:14:53 -0700468
469 # Handle missing blobs
470 if self._missing:
471 if 'BINMAN_ALLOW_MISSING=1' in args:
472 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
473Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
474
475Some images are invalid'''
476 else:
477 stderr = "binman: Filename 'fsp.bin' not found in input path"
478 elif type(commit) is not str:
Simon Glass8e959562014-09-05 19:00:20 -0600479 stderr = self._error.get((brd.target, commit.sequence))
Tom Rini93ebd462022-11-09 19:14:53 -0700480
Simon Glass8e959562014-09-05 19:00:20 -0600481 if stderr:
Tom Rini93ebd462022-11-09 19:14:53 -0700482 return command.CommandResult(return_code=2, stderr=stderr)
Simon Glassed098bb2014-09-05 19:00:13 -0600483 return command.CommandResult(return_code=0)
484
485 # Not handled, so abort
Simon Glassd6c1ec82023-10-26 14:31:10 -0400486 print('_HandleMake failure: make', stage)
Simon Glassed098bb2014-09-05 19:00:13 -0600487 sys.exit(1)
488
Simon Glass8e959562014-09-05 19:00:20 -0600489 # Example function to print output lines
490 def print_lines(self, lines):
Simon Glassc78ed662019-10-31 07:42:53 -0600491 print(len(lines))
Simon Glass8e959562014-09-05 19:00:20 -0600492 for line in lines:
Simon Glassc78ed662019-10-31 07:42:53 -0600493 print(line)
Simon Glass02811582022-01-29 14:14:18 -0700494 #self.print_lines(terminal.get_print_test_lines())
Simon Glass8e959562014-09-05 19:00:20 -0600495
Simon Glasscbd36582014-09-05 19:00:16 -0600496 def testNoBoards(self):
497 """Test that buildman aborts when there are no boards"""
Simon Glass20751d62022-07-11 19:04:03 -0600498 self._boards = boards.Boards()
Simon Glasscbd36582014-09-05 19:00:16 -0600499 with self.assertRaises(SystemExit):
500 self._RunControl()
501
Simon Glassed098bb2014-09-05 19:00:13 -0600502 def testCurrentSource(self):
503 """Very simple test to invoke buildman on the current source"""
Simon Glass8e959562014-09-05 19:00:20 -0600504 self.setupToolchains();
Tom Rinie95eddc2019-10-07 17:17:36 -0400505 self._RunControl('-o', self._output_dir)
Simon Glass02811582022-01-29 14:14:18 -0700506 lines = terminal.get_print_test_lines()
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600507 self.assertIn('Building current source for %d boards' % len(BOARDS),
Simon Glass8e959562014-09-05 19:00:20 -0600508 lines[0].text)
509
510 def testBadBranch(self):
511 """Test that we can detect an invalid branch"""
512 with self.assertRaises(ValueError):
513 self._RunControl('-b', 'badbranch')
514
515 def testBadToolchain(self):
516 """Test that missing toolchains are detected"""
517 self.setupToolchains();
Tom Rinie95eddc2019-10-07 17:17:36 -0400518 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass02811582022-01-29 14:14:18 -0700519 lines = terminal.get_print_test_lines()
Simon Glass8e959562014-09-05 19:00:20 -0600520
521 # Buildman always builds the upstream commit as well
522 self.assertIn('Building %d commits for %d boards' %
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600523 (self._commits, len(BOARDS)), lines[0].text)
Simon Glass8e959562014-09-05 19:00:20 -0600524 self.assertEqual(self._builder.count, self._total_builds)
525
526 # Only sandbox should succeed, the others don't have toolchains
527 self.assertEqual(self._builder.fail,
528 self._total_builds - self._commits)
Simon Glasse4cd5062020-04-09 10:49:45 -0600529 self.assertEqual(ret_code, 100)
Simon Glass8e959562014-09-05 19:00:20 -0600530
531 for commit in range(self._commits):
Simon Glass127a2392022-07-11 19:04:02 -0600532 for brd in self._boards.get_list():
Simon Glass8132f982022-07-11 19:03:57 -0600533 if brd.arch != 'sandbox':
Simon Glassbc74d942023-07-19 17:49:06 -0600534 errfile = self._builder.get_err_file(commit, brd.target)
Simon Glass8e959562014-09-05 19:00:20 -0600535 fd = open(errfile)
Simon Glassbf353b82023-07-19 17:49:25 -0600536 self.assertEqual(
537 fd.readlines(),
538 [f'Tool chain error for {brd.arch}: '
539 f"No tool chain found for arch '{brd.arch}'"])
Simon Glass8e959562014-09-05 19:00:20 -0600540 fd.close()
541
542 def testBranch(self):
543 """Test building a branch with all toolchains present"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400544 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600545 self.assertEqual(self._builder.count, self._total_builds)
546 self.assertEqual(self._builder.fail, 0)
547
548 def testCount(self):
549 """Test building a specific number of commitst"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400550 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600551 self.assertEqual(self._builder.count, 2 * len(BOARDS))
Simon Glass8e959562014-09-05 19:00:20 -0600552 self.assertEqual(self._builder.fail, 0)
Simon Glass6029af12020-04-09 15:08:51 -0600553 # Each board has a config, and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600554 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
Simon Glass8e959562014-09-05 19:00:20 -0600555
556 def testIncremental(self):
557 """Test building a branch twice - the second time should do nothing"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400558 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600559
560 # Each board has a mrproper, config, and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600561 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glass8e959562014-09-05 19:00:20 -0600562 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400563 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600564 self.assertEqual(self._make_calls, 0)
565 self.assertEqual(self._builder.count, self._total_builds)
566 self.assertEqual(self._builder.fail, 0)
567
568 def testForceBuild(self):
569 """The -f flag should force a rebuild"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400570 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600571 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400572 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
Simon Glass6029af12020-04-09 15:08:51 -0600573 # Each board has a config and one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600574 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glass8e959562014-09-05 19:00:20 -0600575
576 def testForceReconfigure(self):
577 """The -f flag should force a rebuild"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400578 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
Simon Glass6029af12020-04-09 15:08:51 -0600579 # Each commit has a config and make
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600580 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
Simon Glass6029af12020-04-09 15:08:51 -0600581
Simon Glass6029af12020-04-09 15:08:51 -0600582 def testMrproper(self):
583 """The -f flag should force a rebuild"""
584 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
585 # Each board has a mkproper, config and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600586 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
Simon Glass8e959562014-09-05 19:00:20 -0600587
588 def testErrors(self):
589 """Test handling of build errors"""
590 self._error['board2', 1] = 'fred\n'
Tom Rinie95eddc2019-10-07 17:17:36 -0400591 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600592 self.assertEqual(self._builder.count, self._total_builds)
593 self.assertEqual(self._builder.fail, 1)
594
595 # Remove the error. This should have no effect since the commit will
596 # not be rebuilt
597 del self._error['board2', 1]
598 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400599 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600600 self.assertEqual(self._builder.count, self._total_builds)
601 self.assertEqual(self._make_calls, 0)
602 self.assertEqual(self._builder.fail, 1)
603
604 # Now use the -F flag to force rebuild of the bad commit
Tom Rinie95eddc2019-10-07 17:17:36 -0400605 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600606 self.assertEqual(self._builder.count, self._total_builds)
607 self.assertEqual(self._builder.fail, 0)
Simon Glass6029af12020-04-09 15:08:51 -0600608 self.assertEqual(self._make_calls, 2)
Simon Glass4aeceb92014-09-05 19:00:22 -0600609
610 def testBranchWithSlash(self):
611 """Test building a branch with a '/' in the name"""
612 self._test_branch = '/__dev/__testbranch'
Simon Glassbe9b52c2023-07-25 08:13:22 -0600613 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
614 clean_dir=False)
Simon Glass4aeceb92014-09-05 19:00:22 -0600615 self.assertEqual(self._builder.count, self._total_builds)
616 self.assertEqual(self._builder.fail, 0)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600617
Simon Glassff48a212020-04-17 17:51:33 -0600618 def testEnvironment(self):
619 """Test that the done and environment files are written to out-env"""
620 self._RunControl('-o', self._output_dir)
621 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
622 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
623 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
624
Simon Glass93008e22021-04-11 16:27:28 +1200625 def testEnvironmentUnicode(self):
626 """Test there are no unicode errors when the env has non-ASCII chars"""
627 try:
628 varname = b'buildman_test_var'
629 os.environb[varname] = b'strange\x80chars'
630 self.assertEqual(0, self._RunControl('-o', self._output_dir))
631 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
632 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
633 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
634 finally:
635 del os.environb[varname]
636
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600637 def testWorkInOutput(self):
638 """Test the -w option which should write directly to the output dir"""
Simon Glass20751d62022-07-11 19:04:03 -0600639 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600640 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600641 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600642 brds=board_list)
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600643 self.assertTrue(
644 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
Simon Glasse3c85ab2020-04-17 17:51:34 -0600645 self.assertTrue(
646 os.path.exists(os.path.join(self._output_dir, 'done')))
647 self.assertTrue(
648 os.path.exists(os.path.join(self._output_dir, 'out-env')))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600649
650 def testWorkInOutputFail(self):
651 """Test the -w option failures"""
652 with self.assertRaises(SystemExit) as e:
653 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
654 self.assertIn("single board", str(e.exception))
655 self.assertFalse(
656 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
657
Simon Glass20751d62022-07-11 19:04:03 -0600658 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600659 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600660 with self.assertRaises(SystemExit) as e:
661 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600662 '-w', clean_dir=False, brds=board_list)
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600663 self.assertIn("single commit", str(e.exception))
Simon Glassd9c98632020-04-17 17:51:32 -0600664
Simon Glass20751d62022-07-11 19:04:03 -0600665 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600666 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd9c98632020-04-17 17:51:32 -0600667 with self.assertRaises(SystemExit) as e:
668 self._RunControl('-w', clean_dir=False)
669 self.assertIn("specify -o", str(e.exception))
Simon Glass9bf9a722021-04-11 16:27:27 +1200670
671 def testThreadExceptions(self):
672 """Test that exceptions in threads are reported"""
673 with test_util.capture_sys_output() as (stdout, stderr):
674 self.assertEqual(102, self._RunControl('-o', self._output_dir,
675 test_thread_exceptions=True))
Simon Glass9bac1672022-01-22 05:07:32 -0700676 self.assertIn(
677 'Thread exception (use -T0 to run without threads): test exception',
678 stdout.getvalue())
Tom Rini93ebd462022-11-09 19:14:53 -0700679
680 def testBlobs(self):
681 """Test handling of missing blobs"""
682 self._missing = True
683
684 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
685 errfile = os.path.join(board0_dir, 'err')
686 logfile = os.path.join(board0_dir, 'log')
687
688 # We expect failure when there are missing blobs
689 result = self._RunControl('board0', '-o', self._output_dir)
690 self.assertEqual(100, result)
691 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
692 self.assertTrue(os.path.exists(errfile))
693 self.assertIn(b"Filename 'fsp.bin' not found in input path",
694 tools.read_file(errfile))
695
696 def testBlobsAllowMissing(self):
697 """Allow missing blobs - still failure but a different exit code"""
698 self._missing = True
699 result = self._RunControl('board0', '-o', self._output_dir, '-M',
700 clean_dir=True)
701 self.assertEqual(101, result)
702 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
703 errfile = os.path.join(board0_dir, 'err')
704 self.assertTrue(os.path.exists(errfile))
705 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
706
707 def testBlobsWarning(self):
708 """Allow missing blobs and ignore warnings"""
709 self._missing = True
710 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
711 self.assertEqual(0, result)
712 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
713 errfile = os.path.join(board0_dir, 'err')
714 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
715
716 def testBlobSettings(self):
717 """Test with no settings"""
718 self.assertEqual(False,
719 control.get_allow_missing(False, False, 1, False))
720 self.assertEqual(True,
721 control.get_allow_missing(True, False, 1, False))
722 self.assertEqual(False,
723 control.get_allow_missing(True, True, 1, False))
724
725 def testBlobSettingsAlways(self):
726 """Test the 'always' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600727 bsettings.set_item('global', 'allow-missing', 'always')
Tom Rini93ebd462022-11-09 19:14:53 -0700728 self.assertEqual(True,
729 control.get_allow_missing(False, False, 1, False))
730 self.assertEqual(False,
731 control.get_allow_missing(False, True, 1, False))
732
733 def testBlobSettingsBranch(self):
734 """Test the 'branch' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600735 bsettings.set_item('global', 'allow-missing', 'branch')
Tom Rini93ebd462022-11-09 19:14:53 -0700736 self.assertEqual(False,
737 control.get_allow_missing(False, False, 1, False))
738 self.assertEqual(True,
739 control.get_allow_missing(False, False, 1, True))
740 self.assertEqual(False,
741 control.get_allow_missing(False, True, 1, True))
742
743 def testBlobSettingsMultiple(self):
744 """Test the 'multiple' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600745 bsettings.set_item('global', 'allow-missing', 'multiple')
Tom Rini93ebd462022-11-09 19:14:53 -0700746 self.assertEqual(False,
747 control.get_allow_missing(False, False, 1, False))
748 self.assertEqual(True,
749 control.get_allow_missing(False, False, 2, False))
750 self.assertEqual(False,
751 control.get_allow_missing(False, True, 2, False))
752
753 def testBlobSettingsBranchMultiple(self):
754 """Test the 'branch multiple' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600755 bsettings.set_item('global', 'allow-missing', 'branch multiple')
Tom Rini93ebd462022-11-09 19:14:53 -0700756 self.assertEqual(False,
757 control.get_allow_missing(False, False, 1, False))
758 self.assertEqual(True,
759 control.get_allow_missing(False, False, 1, True))
760 self.assertEqual(True,
761 control.get_allow_missing(False, False, 2, False))
762 self.assertEqual(True,
763 control.get_allow_missing(False, False, 2, True))
764 self.assertEqual(False,
765 control.get_allow_missing(False, True, 2, True))
Simon Glass1382b1d2023-02-21 12:40:27 -0700766
Simon Glassf6bfcca2023-02-21 12:40:28 -0700767 def check_command(self, *extra_args):
768 """Run a command with the extra arguments and return the commands used
769
770 Args:
771 extra_args (list of str): List of extra arguments
772
773 Returns:
774 list of str: Lines returned in the out-cmd file
775 """
776 self._RunControl('-o', self._output_dir, *extra_args)
Simon Glass1382b1d2023-02-21 12:40:27 -0700777 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
778 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
779 cmd_fname = os.path.join(board0_dir, 'out-cmd')
780 self.assertTrue(os.path.exists(cmd_fname))
781 data = tools.read_file(cmd_fname)
Simon Glass828d70d2023-02-21 12:40:29 -0700782
783 config_fname = os.path.join(board0_dir, '.config')
784 self.assertTrue(os.path.exists(config_fname))
785 cfg_data = tools.read_file(config_fname)
786
787 return data.splitlines(), cfg_data
Simon Glassf6bfcca2023-02-21 12:40:28 -0700788
789 def testCmdFile(self):
790 """Test that the -cmd-out file is produced"""
Simon Glass828d70d2023-02-21 12:40:29 -0700791 lines = self.check_command()[0]
Simon Glass1382b1d2023-02-21 12:40:27 -0700792 self.assertEqual(2, len(lines))
793 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
794 self.assertRegex(lines[0], b'make O=/.*-s.*')
Simon Glassf6bfcca2023-02-21 12:40:28 -0700795
796 def testNoLto(self):
797 """Test that the --no-lto flag works"""
Simon Glass828d70d2023-02-21 12:40:29 -0700798 lines = self.check_command('-L')[0]
Simon Glassf6bfcca2023-02-21 12:40:28 -0700799 self.assertIn(b'NO_LTO=1', lines[0])
800
Simon Glass828d70d2023-02-21 12:40:29 -0700801 def testReproducible(self):
802 """Test that the -r flag works"""
803 lines, cfg_data = self.check_command('-r')
804 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
805
806 # We should see CONFIG_LOCALVERSION_AUTO unset
807 self.assertEqual(b'''CONFIG_SOMETHING=1
808# CONFIG_LOCALVERSION_AUTO is not set
809''', cfg_data)
810
811 with test_util.capture_sys_output() as (stdout, stderr):
812 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
813 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
814
815 # We should see CONFIG_LOCALVERSION_AUTO unset
816 self.assertEqual(b'''CONFIG_SOMETHING=1
817CONFIG_LOCALVERSION=y
818''', cfg_data)
819 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())
Simon Glasscef26b82023-07-19 17:48:15 -0600820
821 def test_scan_defconfigs(self):
822 """Test scanning the defconfigs to obtain all the boards"""
823 src = self._git_dir
824
825 # Scan the test directory which contains a Kconfig and some *_defconfig
826 # files
Simon Glass07a95d82023-07-19 17:48:21 -0600827 params, warnings = self._boards.scan_defconfigs(src, src)
Simon Glasscef26b82023-07-19 17:48:15 -0600828
829 # We should get two boards
Brandon Maiera657bc62024-06-04 16:16:05 +0000830 self.assertEqual(2, len(params))
Simon Glass07a95d82023-07-19 17:48:21 -0600831 self.assertFalse(warnings)
Simon Glasscef26b82023-07-19 17:48:15 -0600832 first = 0 if params[0]['target'] == 'board0' else 1
833 board0 = params[first]
834 board2 = params[1 - first]
835
Brandon Maiera657bc62024-06-04 16:16:05 +0000836 self.assertEqual('arm', board0['arch'])
837 self.assertEqual('armv7', board0['cpu'])
838 self.assertEqual('-', board0['soc'])
839 self.assertEqual('Tester', board0['vendor'])
840 self.assertEqual('ARM Board 0', board0['board'])
841 self.assertEqual('config0', board0['config'])
842 self.assertEqual('board0', board0['target'])
Simon Glasscef26b82023-07-19 17:48:15 -0600843
Brandon Maiera657bc62024-06-04 16:16:05 +0000844 self.assertEqual('powerpc', board2['arch'])
845 self.assertEqual('ppc', board2['cpu'])
846 self.assertEqual('mpc85xx', board2['soc'])
847 self.assertEqual('Tester', board2['vendor'])
848 self.assertEqual('PowerPC board 1', board2['board'])
849 self.assertEqual('config2', board2['config'])
850 self.assertEqual('board2', board2['target'])
Simon Glasscef26b82023-07-19 17:48:15 -0600851
Simon Glassada78d42023-07-19 17:48:16 -0600852 def test_output_is_new(self):
853 """Test detecting new changes to Kconfig"""
854 base = self._base_dir
855 src = self._git_dir
856 config_dir = os.path.join(src, 'configs')
857 delay = 0.02
858
859 # Create a boards.cfg file
860 boards_cfg = os.path.join(base, 'boards.cfg')
861 content = b'''#
862# List of boards
863# Automatically generated by buildman/boards.py: don't edit
864#
865# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
866
867Active aarch64 armv8 - armltd corstone1000 board0
868Active aarch64 armv8 - armltd total_compute board2
869'''
870 # Check missing file
871 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
872
873 # Check that the board.cfg file is newer
874 time.sleep(delay)
875 tools.write_file(boards_cfg, content)
876 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
877
878 # Touch the Kconfig files after a show delay to avoid a race
879 time.sleep(delay)
880 Path(os.path.join(src, 'Kconfig')).touch()
881 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
882 Path(boards_cfg).touch()
883 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
884
885 # Touch a different Kconfig file
886 time.sleep(delay)
887 Path(os.path.join(src, 'Kconfig.something')).touch()
888 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
889 Path(boards_cfg).touch()
890 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
891
892 # Touch a MAINTAINERS file
893 time.sleep(delay)
894 Path(os.path.join(src, 'MAINTAINERS')).touch()
895 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
896
897 Path(boards_cfg).touch()
898 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
899
900 # Touch a defconfig file
901 time.sleep(delay)
902 Path(os.path.join(config_dir, 'board0_defconfig')).touch()
903 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
904 Path(boards_cfg).touch()
905 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
906
907 # Remove a board and check that the board.cfg file is now older
908 Path(os.path.join(config_dir, 'board0_defconfig')).unlink()
909 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
910
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600911 def test_maintainers(self):
912 """Test detecting boards without a MAINTAINERS entry"""
913 src = self._git_dir
914 main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
915 other = os.path.join(src, 'boards', 'board2', 'MAINTAINERS')
Simon Glass07a95d82023-07-19 17:48:21 -0600916 kc_file = os.path.join(src, 'Kconfig')
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600917 config_dir = os.path.join(src, 'configs')
918 params_list, warnings = self._boards.build_board_list(config_dir, src)
919
920 # There should be two boards no warnings
Brandon Maiera657bc62024-06-04 16:16:05 +0000921 self.assertEqual(2, len(params_list))
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600922 self.assertFalse(warnings)
923
924 # Set an invalid status line in the file
925 orig_data = tools.read_file(main, binary=False)
926 lines = ['S: Other\n' if line.startswith('S:') else line
927 for line in orig_data.splitlines(keepends=True)]
928 tools.write_file(main, ''.join(lines), binary=False)
929 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000930 self.assertEqual(2, len(params_list))
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600931 params = params_list[0]
932 if params['target'] == 'board2':
933 params = params_list[1]
Brandon Maiera657bc62024-06-04 16:16:05 +0000934 self.assertEqual('-', params['status'])
935 self.assertEqual(["WARNING: Other: unknown status for 'board0'"],
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600936 warnings)
937
938 # Remove the status line (S:) from a file
939 lines = [line for line in orig_data.splitlines(keepends=True)
940 if not line.startswith('S:')]
941 tools.write_file(main, ''.join(lines), binary=False)
942 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000943 self.assertEqual(2, len(params_list))
944 self.assertEqual(["WARNING: -: unknown status for 'board0'"], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600945
946 # Remove the configs/ line (F:) from a file - this is the last line
947 data = ''.join(orig_data.splitlines(keepends=True)[:-1])
948 tools.write_file(main, data, binary=False)
949 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000950 self.assertEqual(2, len(params_list))
951 self.assertEqual(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600952
Simon Glasse6acab52023-07-19 17:48:26 -0600953 # Mark a board as orphaned - this should give a warning
954 lines = ['S: Orphaned' if line.startswith('S') else line
955 for line in orig_data.splitlines(keepends=True)]
956 tools.write_file(main, ''.join(lines), binary=False)
957 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000958 self.assertEqual(2, len(params_list))
959 self.assertEqual(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glasse6acab52023-07-19 17:48:26 -0600960
961 # Change the maintainer to '-' - this should give a warning
962 lines = ['M: -' if line.startswith('M') else line
963 for line in orig_data.splitlines(keepends=True)]
964 tools.write_file(main, ''.join(lines), binary=False)
965 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000966 self.assertEqual(2, len(params_list))
967 self.assertEqual(["WARNING: -: unknown status for 'board0'"], warnings)
Simon Glasse6acab52023-07-19 17:48:26 -0600968
969 # Remove the maintainer line (M:) from a file
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600970 lines = [line for line in orig_data.splitlines(keepends=True)
971 if not line.startswith('M:')]
972 tools.write_file(main, ''.join(lines), binary=False)
973 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000974 self.assertEqual(2, len(params_list))
975 self.assertEqual(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600976
977 # Move the contents of the second file into this one, removing the
978 # second file, to check multiple records in a single file.
Simon Glass060ee972023-07-19 17:48:23 -0600979 both_data = orig_data + tools.read_file(other, binary=False)
980 tools.write_file(main, both_data, binary=False)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600981 os.remove(other)
982 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000983 self.assertEqual(2, len(params_list))
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600984 self.assertFalse(warnings)
985
Simon Glass9b828ec2023-07-19 17:48:19 -0600986 # Add another record, this should be ignored with a warning
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600987 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
Simon Glass060ee972023-07-19 17:48:23 -0600988 tools.write_file(main, both_data + extra, binary=False)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600989 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +0000990 self.assertEqual(2, len(params_list))
Simon Glass99cb6b12023-08-03 12:51:37 -0600991 self.assertFalse(warnings)
Simon Glass07a95d82023-07-19 17:48:21 -0600992
993 # Add another TARGET to the Kconfig
Simon Glass060ee972023-07-19 17:48:23 -0600994 tools.write_file(main, both_data, binary=False)
Simon Glass061499b2023-07-19 17:48:22 -0600995 orig_kc_data = tools.read_file(kc_file)
Simon Glass07a95d82023-07-19 17:48:21 -0600996 extra = (b'''
997if TARGET_BOARD2
998config TARGET_OTHER
999\tbool "other"
1000\tdefault y
1001endif
1002''')
Simon Glass061499b2023-07-19 17:48:22 -06001003 tools.write_file(kc_file, orig_kc_data + extra)
Simon Glass5e728d42023-07-19 17:48:27 -06001004 params_list, warnings = self._boards.build_board_list(config_dir, src,
1005 warn_targets=True)
Brandon Maiera657bc62024-06-04 16:16:05 +00001006 self.assertEqual(2, len(params_list))
1007 self.assertEqual(
Simon Glass07a95d82023-07-19 17:48:21 -06001008 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
1009 warnings)
Simon Glass061499b2023-07-19 17:48:22 -06001010
1011 # Remove the TARGET_BOARD0 Kconfig option
1012 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
1013 for line in orig_kc_data.splitlines(keepends=True)]
1014 tools.write_file(kc_file, b''.join(lines))
Simon Glass5e728d42023-07-19 17:48:27 -06001015 params_list, warnings = self._boards.build_board_list(config_dir, src,
1016 warn_targets=True)
Brandon Maiera657bc62024-06-04 16:16:05 +00001017 self.assertEqual(2, len(params_list))
1018 self.assertEqual(
Simon Glass061499b2023-07-19 17:48:22 -06001019 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
1020 warnings)
Simon Glass060ee972023-07-19 17:48:23 -06001021 tools.write_file(kc_file, orig_kc_data)
1022
1023 # Replace the last F: line of board 2 with an N: line
1024 data = ''.join(both_data.splitlines(keepends=True)[:-1])
1025 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
1026 params_list, warnings = self._boards.build_board_list(config_dir, src)
Brandon Maiera657bc62024-06-04 16:16:05 +00001027 self.assertEqual(2, len(params_list))
Simon Glass060ee972023-07-19 17:48:23 -06001028 self.assertFalse(warnings)
1029
Simon Glass09afcb72023-07-19 17:48:28 -06001030 def testRegenBoards(self):
1031 """Test that we can regenerate the boards.cfg file"""
1032 outfile = os.path.join(self._output_dir, 'test-boards.cfg')
1033 if os.path.exists(outfile):
1034 os.remove(outfile)
1035 with test_util.capture_sys_output() as (stdout, stderr):
1036 result = self._RunControl('-R', outfile, brds=None,
1037 get_builder=False)
1038 self.assertTrue(os.path.exists(outfile))
Simon Glass8f317b92023-07-19 17:48:35 -06001039
1040 def test_print_prefix(self):
1041 """Test that we can print the toolchain prefix"""
1042 with test_util.capture_sys_output() as (stdout, stderr):
1043 result = self._RunControl('-A', 'board0')
1044 self.assertEqual('arm-\n', stdout.getvalue())
1045 self.assertEqual('', stderr.getvalue())
Simon Glass20978742023-07-19 17:48:38 -06001046
1047 def test_exclude_one(self):
1048 """Test excluding a single board from an arch"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001049 self._RunControl('arm', '-x', 'board1', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001050 self.assertEqual(['board0'],
1051 [b.target for b in self._boards.get_selected()])
1052
1053 def test_exclude_arch(self):
1054 """Test excluding an arch"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001055 self._RunControl('-x', 'arm', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001056 self.assertEqual(['board2', 'board4'],
1057 [b.target for b in self._boards.get_selected()])
1058
1059 def test_exclude_comma(self):
1060 """Test excluding a comma-separated list of things"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001061 self._RunControl('-x', 'arm,powerpc', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001062 self.assertEqual(['board4'],
1063 [b.target for b in self._boards.get_selected()])
1064
1065 def test_exclude_list(self):
1066 """Test excluding a list of things"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001067 self._RunControl('-x', 'board2', '-x' 'board4', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001068 self.assertEqual(['board0', 'board1'],
1069 [b.target for b in self._boards.get_selected()])
Simon Glassf56cc292023-07-19 17:49:03 -06001070
1071 def test_single_boards(self):
1072 """Test building single boards"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001073 self._RunControl('--boards', 'board1', '-o', self._output_dir)
Simon Glassf56cc292023-07-19 17:49:03 -06001074 self.assertEqual(1, self._builder.count)
1075
Simon Glassbe9b52c2023-07-25 08:13:22 -06001076 self._RunControl('--boards', 'board1', '--boards', 'board2',
1077 '-o', self._output_dir)
Simon Glassf56cc292023-07-19 17:49:03 -06001078 self.assertEqual(2, self._builder.count)
1079
Simon Glassbe9b52c2023-07-25 08:13:22 -06001080 self._RunControl('--boards', 'board1,board2', '--boards', 'board4',
1081 '-o', self._output_dir)
Simon Glassf56cc292023-07-19 17:49:03 -06001082 self.assertEqual(3, self._builder.count)
Simon Glassa8a0ce72023-07-19 17:49:28 -06001083
1084 def test_print_arch(self):
1085 """Test that we can print the board architecture"""
1086 with test_util.capture_sys_output() as (stdout, stderr):
1087 result = self._RunControl('--print-arch', 'board0')
1088 self.assertEqual('arm\n', stdout.getvalue())
1089 self.assertEqual('', stderr.getvalue())
Simon Glass78dca002024-11-08 08:23:42 -07001090
1091 def test_kconfig_scanner(self):
1092 """Test using the kconfig scanner to determine important values
1093
1094 Note that there is already a test_scan_defconfigs() which checks the
1095 higher-level scan_defconfigs() function. This test checks just the
1096 scanner itself
1097 """
1098 src = self._git_dir
1099 scanner = boards.KconfigScanner(src)
1100
1101 # First do a simple sanity check
1102 norm = os.path.join(src, 'board0_defconfig')
1103 tools.write_file(norm, 'CONFIG_TARGET_BOARD0=y', False)
1104 res = scanner.scan(norm, True)
1105 self.assertEqual(({
1106 'arch': 'arm',
1107 'cpu': 'armv7',
1108 'soc': '-',
1109 'vendor': 'Tester',
1110 'board': 'ARM Board 0',
1111 'config': 'config0',
1112 'target': 'board0'}, []), res)
1113
1114 # Check that the SoC cannot be changed and the filename does not affect
1115 # the resulting board
1116 tools.write_file(norm, '''CONFIG_TARGET_BOARD2=y
1117CONFIG_SOC="fred"
1118''', False)
1119 res = scanner.scan(norm, True)
1120 self.assertEqual(({
1121 'arch': 'powerpc',
1122 'cpu': 'ppc',
1123 'soc': 'mpc85xx',
1124 'vendor': 'Tester',
1125 'board': 'PowerPC board 1',
1126 'config': 'config2',
1127 'target': 'board0'}, []), res)
1128
1129 # Check handling of missing information
1130 tools.write_file(norm, '', False)
1131 res = scanner.scan(norm, True)
1132 self.assertEqual(({
1133 'arch': '-',
1134 'cpu': '-',
1135 'soc': '-',
1136 'vendor': '-',
1137 'board': '-',
1138 'config': '-',
1139 'target': 'board0'},
1140 ['WARNING: board0_defconfig: No TARGET_BOARD0 enabled']), res)
Simon Glass62dc0942024-11-08 08:23:44 -07001141
1142 # check handling of #include files; see _HandleCommandCpp()
1143 inc = os.path.join(src, 'common')
1144 tools.write_file(inc, b'CONFIG_TARGET_BOARD0=y\n')
1145 tools.write_file(norm, f'#include <{inc}>', False)
1146 res = scanner.scan(norm, True)
1147 self.assertEqual(({
1148 'arch': 'arm',
1149 'cpu': 'armv7',
1150 'soc': '-',
1151 'vendor': 'Tester',
1152 'board': 'ARM Board 0',
1153 'config': 'config0',
1154 'target': 'board0'}, []), res)