blob: 6b88ed815d65162e2f1a28cae780c5dcdd26b37c [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
5import os
Simon Glassada78d42023-07-19 17:48:16 -06006from pathlib import Path
Simon Glassed098bb2014-09-05 19:00:13 -06007import shutil
8import sys
9import tempfile
Simon Glassada78d42023-07-19 17:48:16 -060010import time
Simon Glassed098bb2014-09-05 19:00:13 -060011import unittest
12
Simon Glassf0d9c102020-04-17 18:09:02 -060013from buildman import board
Simon Glass20751d62022-07-11 19:04:03 -060014from buildman import boards
Simon Glassf0d9c102020-04-17 18:09:02 -060015from buildman import bsettings
16from buildman import cmdline
17from buildman import control
18from buildman import toolchain
Simon Glassa997ea52020-04-17 18:09:04 -060019from patman import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070020from u_boot_pylib import command
21from u_boot_pylib import terminal
22from u_boot_pylib import test_util
23from u_boot_pylib import tools
Simon Glassed098bb2014-09-05 19:00:13 -060024
Simon Glass5e0441d2014-09-05 19:00:15 -060025settings_data = '''
26# Buildman settings file
Tom Rini93ebd462022-11-09 19:14:53 -070027[global]
Simon Glass5e0441d2014-09-05 19:00:15 -060028
29[toolchain]
30
31[toolchain-alias]
32
33[make-flags]
34src=/home/sjg/c/src
35chroot=/home/sjg/c/chroot
Masahiro Yamada72e545a2018-08-06 20:47:38 +090036vboot=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 -060037chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
38chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
39chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
40'''
41
Simon Glassd4c6c8a2022-07-11 19:03:58 -060042BOARDS = [
Simon Glass560bd4a2023-07-19 17:48:12 -060043 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''],
44 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''],
Simon Glasscbd36582014-09-05 19:00:16 -060045 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
Simon Glasscbd36582014-09-05 19:00:16 -060046 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
47]
48
Simon Glass8e959562014-09-05 19:00:20 -060049commit_shortlog = """4aca821 patman: Avoid changing the order of tags
5039403bb patman: Use --no-pager' to stop git from forking a pager
51db6e6f2 patman: Remove the -a option
52f2ccf03 patman: Correct unit tests to run correctly
531d097f9 patman: Fix indentation in terminal.py
54d073747 patman: Support the 'reverse' option for 'git log
55"""
56
57commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
58Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
59Date: Fri Aug 22 19:12:41 2014 +0900
60
61 buildman: refactor help message
62
63 "buildman [options]" is displayed by default.
64
65 Append the rest of help messages to parser.usage
66 instead of replacing it.
67
68 Besides, "-b <branch>" is not mandatory since commit fea5858e.
69 Drop it from the usage.
70
71 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
72""",
73"""commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
74Author: Simon Glass <sjg@chromium.org>
75Date: Thu Aug 14 16:48:25 2014 -0600
76
77 patman: Support the 'reverse' option for 'git log'
78
79 This option is currently not supported, but needs to be, for buildman to
80 operate as expected.
81
82 Series-changes: 7
83 - Add new patch to fix the 'reverse' bug
84
Simon Glass359b55a62014-09-05 19:00:23 -060085 Series-version: 8
Simon Glass8e959562014-09-05 19:00:20 -060086
87 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
88 Reported-by: York Sun <yorksun@freescale.com>
89 Signed-off-by: Simon Glass <sjg@chromium.org>
90
91""",
92"""commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
93Author: Simon Glass <sjg@chromium.org>
94Date: Sat Aug 9 11:44:32 2014 -0600
95
96 patman: Fix indentation in terminal.py
97
98 This code came from a different project with 2-character indentation. Fix
99 it for U-Boot.
100
101 Series-changes: 6
102 - Add new patch to fix indentation in teminal.py
103
104 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
105 Signed-off-by: Simon Glass <sjg@chromium.org>
106
107""",
108"""commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
109Author: Simon Glass <sjg@chromium.org>
110Date: Sat Aug 9 11:08:24 2014 -0600
111
112 patman: Correct unit tests to run correctly
113
114 It seems that doctest behaves differently now, and some of the unit tests
115 do not run. Adjust the tests to work correctly.
116
117 ./tools/patman/patman --test
118 <unittest.result.TestResult run=10 errors=0 failures=0>
119
120 Series-changes: 6
121 - Add new patch to fix patman unit tests
122
123 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
124
125""",
126"""commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
127Author: Simon Glass <sjg@chromium.org>
128Date: Sat Aug 9 12:06:02 2014 -0600
129
130 patman: Remove the -a option
131
132 It seems that this is no longer needed, since checkpatch.pl will catch
133 whitespace problems in patches. Also the option is not widely used, so
134 it seems safe to just remove it.
135
136 Series-changes: 6
137 - Add new patch to remove patman's -a option
138
139 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
140 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
141
142""",
143"""commit 39403bb4f838153028a6f21ca30bf100f3791133
144Author: Simon Glass <sjg@chromium.org>
145Date: Thu Aug 14 21:50:52 2014 -0600
146
147 patman: Use --no-pager' to stop git from forking a pager
148
149""",
150"""commit 4aca821e27e97925c039e69fd37375b09c6f129c
151Author: Simon Glass <sjg@chromium.org>
152Date: Fri Aug 22 15:57:39 2014 -0600
153
154 patman: Avoid changing the order of tags
155
156 patman collects tags that it sees in the commit and places them nicely
157 sorted at the end of the patch. However, this is not really necessary and
158 in fact is apparently not desirable.
159
160 Series-changes: 9
161 - Add new patch to avoid changing the order of tags
162
Simon Glass359b55a62014-09-05 19:00:23 -0600163 Series-version: 9
164
Simon Glass8e959562014-09-05 19:00:20 -0600165 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
166 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
167"""]
168
169TEST_BRANCH = '__testbranch'
170
Simon Glassed098bb2014-09-05 19:00:13 -0600171class TestFunctional(unittest.TestCase):
172 """Functional test for buildman.
173
174 This aims to test from just below the invocation of buildman (parsing
175 of arguments) to 'make' and 'git' invocation. It is not a true
176 emd-to-end test, as it mocks git, make and the tool chain. But this
177 makes it easier to detect when the builder is doing the wrong thing,
178 since in many cases this test code will fail. For example, only a
179 very limited subset of 'git' arguments is supported - anything
180 unexpected will fail.
181 """
182 def setUp(self):
183 self._base_dir = tempfile.mkdtemp()
Tom Rinie95eddc2019-10-07 17:17:36 -0400184 self._output_dir = tempfile.mkdtemp()
Simon Glassed098bb2014-09-05 19:00:13 -0600185 self._git_dir = os.path.join(self._base_dir, 'src')
186 self._buildman_pathname = sys.argv[0]
Simon Glass5d4a7872016-07-27 20:33:00 -0600187 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
Simon Glassed098bb2014-09-05 19:00:13 -0600188 command.test_result = self._HandleCommand
Simon Glass06b83a52023-07-19 17:49:05 -0600189 bsettings.setup(None)
190 bsettings.add_file(settings_data)
Simon Glass8e959562014-09-05 19:00:20 -0600191 self.setupToolchains()
192 self._toolchains.Add('arm-gcc', test=False)
193 self._toolchains.Add('powerpc-gcc', test=False)
Simon Glass20751d62022-07-11 19:04:03 -0600194 self._boards = boards.Boards()
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600195 for brd in BOARDS:
Simon Glass127a2392022-07-11 19:04:02 -0600196 self._boards.add_board(board.Board(*brd))
Simon Glassed098bb2014-09-05 19:00:13 -0600197
Simon Glass8e959562014-09-05 19:00:20 -0600198 # Directories where the source been cloned
199 self._clone_dirs = []
200 self._commits = len(commit_shortlog.splitlines()) + 1
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600201 self._total_builds = self._commits * len(BOARDS)
Simon Glass8e959562014-09-05 19:00:20 -0600202
203 # Number of calls to make
204 self._make_calls = 0
205
206 # Map of [board, commit] to error messages
207 self._error = {}
208
Simon Glass4aeceb92014-09-05 19:00:22 -0600209 self._test_branch = TEST_BRANCH
210
Tom Rini93ebd462022-11-09 19:14:53 -0700211 # Set to True to report missing blobs
212 self._missing = False
213
Simon Glasscef26b82023-07-19 17:48:15 -0600214 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
215 self._test_dir = os.path.join(self._buildman_dir, 'test')
216
217 # Set up some fake source files
218 shutil.copytree(self._test_dir, self._git_dir)
219
Simon Glass8e959562014-09-05 19:00:20 -0600220 # Avoid sending any output and clear all terminal output
Simon Glass02811582022-01-29 14:14:18 -0700221 terminal.set_print_test_mode()
222 terminal.get_print_test_lines()
Simon Glass8e959562014-09-05 19:00:20 -0600223
Simon Glassed098bb2014-09-05 19:00:13 -0600224 def tearDown(self):
225 shutil.rmtree(self._base_dir)
Simon Glass211c74b2022-11-09 19:14:52 -0700226 shutil.rmtree(self._output_dir)
Simon Glassed098bb2014-09-05 19:00:13 -0600227
Simon Glass8e959562014-09-05 19:00:20 -0600228 def setupToolchains(self):
229 self._toolchains = toolchain.Toolchains()
230 self._toolchains.Add('gcc', test=False)
231
Simon Glassed098bb2014-09-05 19:00:13 -0600232 def _RunBuildman(self, *args):
Simon Glass840be732022-01-29 14:14:05 -0700233 return command.run_pipe([[self._buildman_pathname] + list(args)],
Simon Glassed098bb2014-09-05 19:00:13 -0600234 capture=True, capture_stderr=True)
235
Simon Glass09afcb72023-07-19 17:48:28 -0600236 def _RunControl(self, *args, brds=False, clean_dir=False,
237 test_thread_exceptions=False, get_builder=True):
Simon Glassa29b3ea2021-04-11 16:27:25 +1200238 """Run buildman
239
240 Args:
241 args: List of arguments to pass
Simon Glass09afcb72023-07-19 17:48:28 -0600242 brds: Boards object, or False to pass self._boards, or None to pass
243 None
Simon Glassa29b3ea2021-04-11 16:27:25 +1200244 clean_dir: Used for tests only, indicates that the existing output_dir
245 should be removed before starting the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200246 test_thread_exceptions: Uses for tests only, True to make the threads
247 raise an exception instead of reporting their result. This simulates
248 a failure in the code somewhere
Simon Glass09afcb72023-07-19 17:48:28 -0600249 get_builder (bool): Set self._builder to the resulting builder
Simon Glassa29b3ea2021-04-11 16:27:25 +1200250
251 Returns:
252 result code from buildman
253 """
Simon Glassed098bb2014-09-05 19:00:13 -0600254 sys.argv = [sys.argv[0]] + list(args)
Simon Glassa56ac992023-07-19 17:49:04 -0600255 args = cmdline.parse_args()
Simon Glass09afcb72023-07-19 17:48:28 -0600256 if brds == False:
257 brds = self._boards
Simon Glassc1e1e1d2023-07-19 17:48:30 -0600258 result = control.do_buildman(
Simon Glassa56ac992023-07-19 17:49:04 -0600259 args, toolchains=self._toolchains, make_func=self._HandleMake,
260 brds=brds, clean_dir=clean_dir,
Simon Glassc1e1e1d2023-07-19 17:48:30 -0600261 test_thread_exceptions=test_thread_exceptions)
Simon Glass09afcb72023-07-19 17:48:28 -0600262 if get_builder:
Simon Glassaf0e29f2023-07-19 17:48:31 -0600263 self._builder = control.TEST_BUILDER
Simon Glass8e959562014-09-05 19:00:20 -0600264 return result
Simon Glassed098bb2014-09-05 19:00:13 -0600265
266 def testFullHelp(self):
267 command.test_result = None
268 result = self._RunBuildman('-H')
Simon Glass79cc9be2022-11-09 19:14:43 -0700269 help_file = os.path.join(self._buildman_dir, 'README.rst')
Tom Rinic3c0b6d2018-01-16 15:29:50 -0500270 # Remove possible extraneous strings
271 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
272 gothelp = result.stdout.replace(extra, '')
273 self.assertEqual(len(gothelp), os.path.getsize(help_file))
Simon Glassed098bb2014-09-05 19:00:13 -0600274 self.assertEqual(0, len(result.stderr))
275 self.assertEqual(0, result.return_code)
276
277 def testHelp(self):
278 command.test_result = None
279 result = self._RunBuildman('-h')
Simon Glass79cc9be2022-11-09 19:14:43 -0700280 help_file = os.path.join(self._buildman_dir, 'README.rst')
Simon Glassed098bb2014-09-05 19:00:13 -0600281 self.assertTrue(len(result.stdout) > 1000)
282 self.assertEqual(0, len(result.stderr))
283 self.assertEqual(0, result.return_code)
284
285 def testGitSetup(self):
286 """Test gitutils.Setup(), from outside the module itself"""
287 command.test_result = command.CommandResult(return_code=1)
Simon Glass761648b2022-01-29 14:14:11 -0700288 gitutil.setup()
Simon Glassed098bb2014-09-05 19:00:13 -0600289 self.assertEqual(gitutil.use_no_decorate, False)
290
291 command.test_result = command.CommandResult(return_code=0)
Simon Glass761648b2022-01-29 14:14:11 -0700292 gitutil.setup()
Simon Glassed098bb2014-09-05 19:00:13 -0600293 self.assertEqual(gitutil.use_no_decorate, True)
294
295 def _HandleCommandGitLog(self, args):
Simon Glass642e9a62016-03-12 18:50:31 -0700296 if args[-1] == '--':
297 args = args[:-1]
Simon Glassed098bb2014-09-05 19:00:13 -0600298 if '-n0' in args:
299 return command.CommandResult(return_code=0)
Simon Glass4aeceb92014-09-05 19:00:22 -0600300 elif args[-1] == 'upstream/master..%s' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600301 return command.CommandResult(return_code=0, stdout=commit_shortlog)
302 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
Simon Glass4aeceb92014-09-05 19:00:22 -0600303 if args[-1] == self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600304 count = int(args[3][2:])
305 return command.CommandResult(return_code=0,
306 stdout=''.join(commit_log[:count]))
Simon Glassed098bb2014-09-05 19:00:13 -0600307
308 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600309 print('git log', args)
Simon Glassed098bb2014-09-05 19:00:13 -0600310 sys.exit(1)
311
Simon Glass8e959562014-09-05 19:00:20 -0600312 def _HandleCommandGitConfig(self, args):
313 config = args[0]
314 if config == 'sendemail.aliasesfile':
315 return command.CommandResult(return_code=0)
316 elif config.startswith('branch.badbranch'):
317 return command.CommandResult(return_code=1)
Simon Glass4aeceb92014-09-05 19:00:22 -0600318 elif config == 'branch.%s.remote' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600319 return command.CommandResult(return_code=0, stdout='upstream\n')
Simon Glass4aeceb92014-09-05 19:00:22 -0600320 elif config == 'branch.%s.merge' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600321 return command.CommandResult(return_code=0,
322 stdout='refs/heads/master\n')
323
324 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600325 print('git config', args)
Simon Glass8e959562014-09-05 19:00:20 -0600326 sys.exit(1)
327
Simon Glassed098bb2014-09-05 19:00:13 -0600328 def _HandleCommandGit(self, in_args):
329 """Handle execution of a git command
330
331 This uses a hacked-up parser.
332
333 Args:
334 in_args: Arguments after 'git' from the command line
335 """
336 git_args = [] # Top-level arguments to git itself
337 sub_cmd = None # Git sub-command selected
338 args = [] # Arguments to the git sub-command
339 for arg in in_args:
340 if sub_cmd:
341 args.append(arg)
342 elif arg[0] == '-':
343 git_args.append(arg)
344 else:
Simon Glass8e959562014-09-05 19:00:20 -0600345 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
346 git_args.append(arg)
347 else:
348 sub_cmd = arg
Simon Glassed098bb2014-09-05 19:00:13 -0600349 if sub_cmd == 'config':
Simon Glass8e959562014-09-05 19:00:20 -0600350 return self._HandleCommandGitConfig(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600351 elif sub_cmd == 'log':
352 return self._HandleCommandGitLog(args)
Simon Glass8e959562014-09-05 19:00:20 -0600353 elif sub_cmd == 'clone':
354 return command.CommandResult(return_code=0)
355 elif sub_cmd == 'checkout':
356 return command.CommandResult(return_code=0)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300357 elif sub_cmd == 'worktree':
358 return command.CommandResult(return_code=0)
Simon Glassed098bb2014-09-05 19:00:13 -0600359
360 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600361 print('git', git_args, sub_cmd, args)
Simon Glassed098bb2014-09-05 19:00:13 -0600362 sys.exit(1)
363
364 def _HandleCommandNm(self, args):
365 return command.CommandResult(return_code=0)
366
367 def _HandleCommandObjdump(self, args):
368 return command.CommandResult(return_code=0)
369
Alex Kiernanf07ed232018-05-31 04:48:33 +0000370 def _HandleCommandObjcopy(self, args):
371 return command.CommandResult(return_code=0)
372
Simon Glassed098bb2014-09-05 19:00:13 -0600373 def _HandleCommandSize(self, args):
374 return command.CommandResult(return_code=0)
375
376 def _HandleCommand(self, **kwargs):
377 """Handle a command execution.
378
379 The command is in kwargs['pipe-list'], as a list of pipes, each a
380 list of commands. The command should be emulated as required for
381 testing purposes.
382
383 Returns:
384 A CommandResult object
385 """
386 pipe_list = kwargs['pipe_list']
Simon Glass8e959562014-09-05 19:00:20 -0600387 wc = False
Simon Glassed098bb2014-09-05 19:00:13 -0600388 if len(pipe_list) != 1:
Simon Glass8e959562014-09-05 19:00:20 -0600389 if pipe_list[1] == ['wc', '-l']:
390 wc = True
391 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600392 print('invalid pipe', kwargs)
Simon Glass8e959562014-09-05 19:00:20 -0600393 sys.exit(1)
Simon Glassed098bb2014-09-05 19:00:13 -0600394 cmd = pipe_list[0][0]
395 args = pipe_list[0][1:]
Simon Glass8e959562014-09-05 19:00:20 -0600396 result = None
Simon Glassed098bb2014-09-05 19:00:13 -0600397 if cmd == 'git':
Simon Glass8e959562014-09-05 19:00:20 -0600398 result = self._HandleCommandGit(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600399 elif cmd == './scripts/show-gnu-make':
400 return command.CommandResult(return_code=0, stdout='make')
Simon Glass8e959562014-09-05 19:00:20 -0600401 elif cmd.endswith('nm'):
Simon Glassed098bb2014-09-05 19:00:13 -0600402 return self._HandleCommandNm(args)
Simon Glass8e959562014-09-05 19:00:20 -0600403 elif cmd.endswith('objdump'):
Simon Glassed098bb2014-09-05 19:00:13 -0600404 return self._HandleCommandObjdump(args)
Alex Kiernanf07ed232018-05-31 04:48:33 +0000405 elif cmd.endswith('objcopy'):
406 return self._HandleCommandObjcopy(args)
Simon Glass8e959562014-09-05 19:00:20 -0600407 elif cmd.endswith( 'size'):
Simon Glassed098bb2014-09-05 19:00:13 -0600408 return self._HandleCommandSize(args)
409
Simon Glass8e959562014-09-05 19:00:20 -0600410 if not result:
411 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600412 print('unknown command', kwargs)
Simon Glass8e959562014-09-05 19:00:20 -0600413 sys.exit(1)
414
415 if wc:
416 result.stdout = len(result.stdout.splitlines())
417 return result
Simon Glassed098bb2014-09-05 19:00:13 -0600418
419 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
420 """Handle execution of 'make'
421
422 Args:
423 commit: Commit object that is being built
424 brd: Board object that is being built
425 stage: Stage that we are at (mrproper, config, build)
426 cwd: Directory where make should be run
427 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700428 kwargs: Arguments to pass to command.run_pipe()
Simon Glassed098bb2014-09-05 19:00:13 -0600429 """
Simon Glass8e959562014-09-05 19:00:20 -0600430 self._make_calls += 1
Simon Glass828d70d2023-02-21 12:40:29 -0700431 out_dir = ''
432 for arg in args:
433 if arg.startswith('O='):
434 out_dir = arg[2:]
Simon Glassed098bb2014-09-05 19:00:13 -0600435 if stage == 'mrproper':
436 return command.CommandResult(return_code=0)
437 elif stage == 'config':
Simon Glass828d70d2023-02-21 12:40:29 -0700438 fname = os.path.join(cwd or '', out_dir, '.config')
439 tools.write_file(fname, b'CONFIG_SOMETHING=1')
Simon Glassed098bb2014-09-05 19:00:13 -0600440 return command.CommandResult(return_code=0,
441 combined='Test configuration complete')
Simon Glassd6c1ec82023-10-26 14:31:10 -0400442 elif stage == 'oldconfig':
443 return command.CommandResult(return_code=0)
Simon Glassed098bb2014-09-05 19:00:13 -0600444 elif stage == 'build':
Simon Glass8e959562014-09-05 19:00:20 -0600445 stderr = ''
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600446 fname = os.path.join(cwd or '', out_dir, 'u-boot')
Simon Glass80025522022-01-29 14:14:04 -0700447 tools.write_file(fname, b'U-Boot')
Tom Rini93ebd462022-11-09 19:14:53 -0700448
449 # Handle missing blobs
450 if self._missing:
451 if 'BINMAN_ALLOW_MISSING=1' in args:
452 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
453Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
454
455Some images are invalid'''
456 else:
457 stderr = "binman: Filename 'fsp.bin' not found in input path"
458 elif type(commit) is not str:
Simon Glass8e959562014-09-05 19:00:20 -0600459 stderr = self._error.get((brd.target, commit.sequence))
Tom Rini93ebd462022-11-09 19:14:53 -0700460
Simon Glass8e959562014-09-05 19:00:20 -0600461 if stderr:
Tom Rini93ebd462022-11-09 19:14:53 -0700462 return command.CommandResult(return_code=2, stderr=stderr)
Simon Glassed098bb2014-09-05 19:00:13 -0600463 return command.CommandResult(return_code=0)
464
465 # Not handled, so abort
Simon Glassd6c1ec82023-10-26 14:31:10 -0400466 print('_HandleMake failure: make', stage)
Simon Glassed098bb2014-09-05 19:00:13 -0600467 sys.exit(1)
468
Simon Glass8e959562014-09-05 19:00:20 -0600469 # Example function to print output lines
470 def print_lines(self, lines):
Simon Glassc78ed662019-10-31 07:42:53 -0600471 print(len(lines))
Simon Glass8e959562014-09-05 19:00:20 -0600472 for line in lines:
Simon Glassc78ed662019-10-31 07:42:53 -0600473 print(line)
Simon Glass02811582022-01-29 14:14:18 -0700474 #self.print_lines(terminal.get_print_test_lines())
Simon Glass8e959562014-09-05 19:00:20 -0600475
Simon Glasscbd36582014-09-05 19:00:16 -0600476 def testNoBoards(self):
477 """Test that buildman aborts when there are no boards"""
Simon Glass20751d62022-07-11 19:04:03 -0600478 self._boards = boards.Boards()
Simon Glasscbd36582014-09-05 19:00:16 -0600479 with self.assertRaises(SystemExit):
480 self._RunControl()
481
Simon Glassed098bb2014-09-05 19:00:13 -0600482 def testCurrentSource(self):
483 """Very simple test to invoke buildman on the current source"""
Simon Glass8e959562014-09-05 19:00:20 -0600484 self.setupToolchains();
Tom Rinie95eddc2019-10-07 17:17:36 -0400485 self._RunControl('-o', self._output_dir)
Simon Glass02811582022-01-29 14:14:18 -0700486 lines = terminal.get_print_test_lines()
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600487 self.assertIn('Building current source for %d boards' % len(BOARDS),
Simon Glass8e959562014-09-05 19:00:20 -0600488 lines[0].text)
489
490 def testBadBranch(self):
491 """Test that we can detect an invalid branch"""
492 with self.assertRaises(ValueError):
493 self._RunControl('-b', 'badbranch')
494
495 def testBadToolchain(self):
496 """Test that missing toolchains are detected"""
497 self.setupToolchains();
Tom Rinie95eddc2019-10-07 17:17:36 -0400498 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass02811582022-01-29 14:14:18 -0700499 lines = terminal.get_print_test_lines()
Simon Glass8e959562014-09-05 19:00:20 -0600500
501 # Buildman always builds the upstream commit as well
502 self.assertIn('Building %d commits for %d boards' %
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600503 (self._commits, len(BOARDS)), lines[0].text)
Simon Glass8e959562014-09-05 19:00:20 -0600504 self.assertEqual(self._builder.count, self._total_builds)
505
506 # Only sandbox should succeed, the others don't have toolchains
507 self.assertEqual(self._builder.fail,
508 self._total_builds - self._commits)
Simon Glasse4cd5062020-04-09 10:49:45 -0600509 self.assertEqual(ret_code, 100)
Simon Glass8e959562014-09-05 19:00:20 -0600510
511 for commit in range(self._commits):
Simon Glass127a2392022-07-11 19:04:02 -0600512 for brd in self._boards.get_list():
Simon Glass8132f982022-07-11 19:03:57 -0600513 if brd.arch != 'sandbox':
Simon Glassbc74d942023-07-19 17:49:06 -0600514 errfile = self._builder.get_err_file(commit, brd.target)
Simon Glass8e959562014-09-05 19:00:20 -0600515 fd = open(errfile)
Simon Glassbf353b82023-07-19 17:49:25 -0600516 self.assertEqual(
517 fd.readlines(),
518 [f'Tool chain error for {brd.arch}: '
519 f"No tool chain found for arch '{brd.arch}'"])
Simon Glass8e959562014-09-05 19:00:20 -0600520 fd.close()
521
522 def testBranch(self):
523 """Test building a branch with all toolchains present"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400524 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600525 self.assertEqual(self._builder.count, self._total_builds)
526 self.assertEqual(self._builder.fail, 0)
527
528 def testCount(self):
529 """Test building a specific number of commitst"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400530 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600531 self.assertEqual(self._builder.count, 2 * len(BOARDS))
Simon Glass8e959562014-09-05 19:00:20 -0600532 self.assertEqual(self._builder.fail, 0)
Simon Glass6029af12020-04-09 15:08:51 -0600533 # Each board has a config, and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600534 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
Simon Glass8e959562014-09-05 19:00:20 -0600535
536 def testIncremental(self):
537 """Test building a branch twice - the second time should do nothing"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400538 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600539
540 # Each board has a mrproper, config, and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600541 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glass8e959562014-09-05 19:00:20 -0600542 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400543 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600544 self.assertEqual(self._make_calls, 0)
545 self.assertEqual(self._builder.count, self._total_builds)
546 self.assertEqual(self._builder.fail, 0)
547
548 def testForceBuild(self):
549 """The -f flag should force a rebuild"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400550 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600551 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400552 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
Simon Glass6029af12020-04-09 15:08:51 -0600553 # Each board has a config and one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600554 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glass8e959562014-09-05 19:00:20 -0600555
556 def testForceReconfigure(self):
557 """The -f flag should force a rebuild"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400558 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
Simon Glass6029af12020-04-09 15:08:51 -0600559 # Each commit has a config and make
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600560 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
Simon Glass6029af12020-04-09 15:08:51 -0600561
Simon Glass6029af12020-04-09 15:08:51 -0600562 def testMrproper(self):
563 """The -f flag should force a rebuild"""
564 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
565 # Each board has a mkproper, config and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600566 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
Simon Glass8e959562014-09-05 19:00:20 -0600567
568 def testErrors(self):
569 """Test handling of build errors"""
570 self._error['board2', 1] = 'fred\n'
Tom Rinie95eddc2019-10-07 17:17:36 -0400571 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600572 self.assertEqual(self._builder.count, self._total_builds)
573 self.assertEqual(self._builder.fail, 1)
574
575 # Remove the error. This should have no effect since the commit will
576 # not be rebuilt
577 del self._error['board2', 1]
578 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400579 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600580 self.assertEqual(self._builder.count, self._total_builds)
581 self.assertEqual(self._make_calls, 0)
582 self.assertEqual(self._builder.fail, 1)
583
584 # Now use the -F flag to force rebuild of the bad commit
Tom Rinie95eddc2019-10-07 17:17:36 -0400585 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600586 self.assertEqual(self._builder.count, self._total_builds)
587 self.assertEqual(self._builder.fail, 0)
Simon Glass6029af12020-04-09 15:08:51 -0600588 self.assertEqual(self._make_calls, 2)
Simon Glass4aeceb92014-09-05 19:00:22 -0600589
590 def testBranchWithSlash(self):
591 """Test building a branch with a '/' in the name"""
592 self._test_branch = '/__dev/__testbranch'
Simon Glassbe9b52c2023-07-25 08:13:22 -0600593 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
594 clean_dir=False)
Simon Glass4aeceb92014-09-05 19:00:22 -0600595 self.assertEqual(self._builder.count, self._total_builds)
596 self.assertEqual(self._builder.fail, 0)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600597
Simon Glassff48a212020-04-17 17:51:33 -0600598 def testEnvironment(self):
599 """Test that the done and environment files are written to out-env"""
600 self._RunControl('-o', self._output_dir)
601 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
602 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
603 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
604
Simon Glass93008e22021-04-11 16:27:28 +1200605 def testEnvironmentUnicode(self):
606 """Test there are no unicode errors when the env has non-ASCII chars"""
607 try:
608 varname = b'buildman_test_var'
609 os.environb[varname] = b'strange\x80chars'
610 self.assertEqual(0, self._RunControl('-o', self._output_dir))
611 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
612 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
613 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
614 finally:
615 del os.environb[varname]
616
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600617 def testWorkInOutput(self):
618 """Test the -w option which should write directly to the output dir"""
Simon Glass20751d62022-07-11 19:04:03 -0600619 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600620 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600621 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600622 brds=board_list)
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600623 self.assertTrue(
624 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
Simon Glasse3c85ab2020-04-17 17:51:34 -0600625 self.assertTrue(
626 os.path.exists(os.path.join(self._output_dir, 'done')))
627 self.assertTrue(
628 os.path.exists(os.path.join(self._output_dir, 'out-env')))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600629
630 def testWorkInOutputFail(self):
631 """Test the -w option failures"""
632 with self.assertRaises(SystemExit) as e:
633 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
634 self.assertIn("single board", str(e.exception))
635 self.assertFalse(
636 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
637
Simon Glass20751d62022-07-11 19:04:03 -0600638 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600639 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600640 with self.assertRaises(SystemExit) as e:
641 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600642 '-w', clean_dir=False, brds=board_list)
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600643 self.assertIn("single commit", str(e.exception))
Simon Glassd9c98632020-04-17 17:51:32 -0600644
Simon Glass20751d62022-07-11 19:04:03 -0600645 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600646 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd9c98632020-04-17 17:51:32 -0600647 with self.assertRaises(SystemExit) as e:
648 self._RunControl('-w', clean_dir=False)
649 self.assertIn("specify -o", str(e.exception))
Simon Glass9bf9a722021-04-11 16:27:27 +1200650
651 def testThreadExceptions(self):
652 """Test that exceptions in threads are reported"""
653 with test_util.capture_sys_output() as (stdout, stderr):
654 self.assertEqual(102, self._RunControl('-o', self._output_dir,
655 test_thread_exceptions=True))
Simon Glass9bac1672022-01-22 05:07:32 -0700656 self.assertIn(
657 'Thread exception (use -T0 to run without threads): test exception',
658 stdout.getvalue())
Tom Rini93ebd462022-11-09 19:14:53 -0700659
660 def testBlobs(self):
661 """Test handling of missing blobs"""
662 self._missing = True
663
664 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
665 errfile = os.path.join(board0_dir, 'err')
666 logfile = os.path.join(board0_dir, 'log')
667
668 # We expect failure when there are missing blobs
669 result = self._RunControl('board0', '-o', self._output_dir)
670 self.assertEqual(100, result)
671 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
672 self.assertTrue(os.path.exists(errfile))
673 self.assertIn(b"Filename 'fsp.bin' not found in input path",
674 tools.read_file(errfile))
675
676 def testBlobsAllowMissing(self):
677 """Allow missing blobs - still failure but a different exit code"""
678 self._missing = True
679 result = self._RunControl('board0', '-o', self._output_dir, '-M',
680 clean_dir=True)
681 self.assertEqual(101, result)
682 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
683 errfile = os.path.join(board0_dir, 'err')
684 self.assertTrue(os.path.exists(errfile))
685 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
686
687 def testBlobsWarning(self):
688 """Allow missing blobs and ignore warnings"""
689 self._missing = True
690 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
691 self.assertEqual(0, result)
692 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
693 errfile = os.path.join(board0_dir, 'err')
694 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
695
696 def testBlobSettings(self):
697 """Test with no settings"""
698 self.assertEqual(False,
699 control.get_allow_missing(False, False, 1, False))
700 self.assertEqual(True,
701 control.get_allow_missing(True, False, 1, False))
702 self.assertEqual(False,
703 control.get_allow_missing(True, True, 1, False))
704
705 def testBlobSettingsAlways(self):
706 """Test the 'always' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600707 bsettings.set_item('global', 'allow-missing', 'always')
Tom Rini93ebd462022-11-09 19:14:53 -0700708 self.assertEqual(True,
709 control.get_allow_missing(False, False, 1, False))
710 self.assertEqual(False,
711 control.get_allow_missing(False, True, 1, False))
712
713 def testBlobSettingsBranch(self):
714 """Test the 'branch' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600715 bsettings.set_item('global', 'allow-missing', 'branch')
Tom Rini93ebd462022-11-09 19:14:53 -0700716 self.assertEqual(False,
717 control.get_allow_missing(False, False, 1, False))
718 self.assertEqual(True,
719 control.get_allow_missing(False, False, 1, True))
720 self.assertEqual(False,
721 control.get_allow_missing(False, True, 1, True))
722
723 def testBlobSettingsMultiple(self):
724 """Test the 'multiple' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600725 bsettings.set_item('global', 'allow-missing', 'multiple')
Tom Rini93ebd462022-11-09 19:14:53 -0700726 self.assertEqual(False,
727 control.get_allow_missing(False, False, 1, False))
728 self.assertEqual(True,
729 control.get_allow_missing(False, False, 2, False))
730 self.assertEqual(False,
731 control.get_allow_missing(False, True, 2, False))
732
733 def testBlobSettingsBranchMultiple(self):
734 """Test the 'branch multiple' policy"""
Simon Glass06b83a52023-07-19 17:49:05 -0600735 bsettings.set_item('global', 'allow-missing', 'branch multiple')
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(True,
741 control.get_allow_missing(False, False, 2, False))
742 self.assertEqual(True,
743 control.get_allow_missing(False, False, 2, True))
744 self.assertEqual(False,
745 control.get_allow_missing(False, True, 2, True))
Simon Glass1382b1d2023-02-21 12:40:27 -0700746
Simon Glassf6bfcca2023-02-21 12:40:28 -0700747 def check_command(self, *extra_args):
748 """Run a command with the extra arguments and return the commands used
749
750 Args:
751 extra_args (list of str): List of extra arguments
752
753 Returns:
754 list of str: Lines returned in the out-cmd file
755 """
756 self._RunControl('-o', self._output_dir, *extra_args)
Simon Glass1382b1d2023-02-21 12:40:27 -0700757 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
758 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
759 cmd_fname = os.path.join(board0_dir, 'out-cmd')
760 self.assertTrue(os.path.exists(cmd_fname))
761 data = tools.read_file(cmd_fname)
Simon Glass828d70d2023-02-21 12:40:29 -0700762
763 config_fname = os.path.join(board0_dir, '.config')
764 self.assertTrue(os.path.exists(config_fname))
765 cfg_data = tools.read_file(config_fname)
766
767 return data.splitlines(), cfg_data
Simon Glassf6bfcca2023-02-21 12:40:28 -0700768
769 def testCmdFile(self):
770 """Test that the -cmd-out file is produced"""
Simon Glass828d70d2023-02-21 12:40:29 -0700771 lines = self.check_command()[0]
Simon Glass1382b1d2023-02-21 12:40:27 -0700772 self.assertEqual(2, len(lines))
773 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
774 self.assertRegex(lines[0], b'make O=/.*-s.*')
Simon Glassf6bfcca2023-02-21 12:40:28 -0700775
776 def testNoLto(self):
777 """Test that the --no-lto flag works"""
Simon Glass828d70d2023-02-21 12:40:29 -0700778 lines = self.check_command('-L')[0]
Simon Glassf6bfcca2023-02-21 12:40:28 -0700779 self.assertIn(b'NO_LTO=1', lines[0])
780
Simon Glass828d70d2023-02-21 12:40:29 -0700781 def testReproducible(self):
782 """Test that the -r flag works"""
783 lines, cfg_data = self.check_command('-r')
784 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
785
786 # We should see CONFIG_LOCALVERSION_AUTO unset
787 self.assertEqual(b'''CONFIG_SOMETHING=1
788# CONFIG_LOCALVERSION_AUTO is not set
789''', cfg_data)
790
791 with test_util.capture_sys_output() as (stdout, stderr):
792 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
793 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
794
795 # We should see CONFIG_LOCALVERSION_AUTO unset
796 self.assertEqual(b'''CONFIG_SOMETHING=1
797CONFIG_LOCALVERSION=y
798''', cfg_data)
799 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())
Simon Glasscef26b82023-07-19 17:48:15 -0600800
801 def test_scan_defconfigs(self):
802 """Test scanning the defconfigs to obtain all the boards"""
803 src = self._git_dir
804
805 # Scan the test directory which contains a Kconfig and some *_defconfig
806 # files
Simon Glass07a95d82023-07-19 17:48:21 -0600807 params, warnings = self._boards.scan_defconfigs(src, src)
Simon Glasscef26b82023-07-19 17:48:15 -0600808
809 # We should get two boards
810 self.assertEquals(2, len(params))
Simon Glass07a95d82023-07-19 17:48:21 -0600811 self.assertFalse(warnings)
Simon Glasscef26b82023-07-19 17:48:15 -0600812 first = 0 if params[0]['target'] == 'board0' else 1
813 board0 = params[first]
814 board2 = params[1 - first]
815
816 self.assertEquals('arm', board0['arch'])
817 self.assertEquals('armv7', board0['cpu'])
818 self.assertEquals('-', board0['soc'])
819 self.assertEquals('Tester', board0['vendor'])
820 self.assertEquals('ARM Board 0', board0['board'])
821 self.assertEquals('config0', board0['config'])
822 self.assertEquals('board0', board0['target'])
823
824 self.assertEquals('powerpc', board2['arch'])
825 self.assertEquals('ppc', board2['cpu'])
826 self.assertEquals('mpc85xx', board2['soc'])
827 self.assertEquals('Tester', board2['vendor'])
828 self.assertEquals('PowerPC board 1', board2['board'])
829 self.assertEquals('config2', board2['config'])
830 self.assertEquals('board2', board2['target'])
831
Simon Glassada78d42023-07-19 17:48:16 -0600832 def test_output_is_new(self):
833 """Test detecting new changes to Kconfig"""
834 base = self._base_dir
835 src = self._git_dir
836 config_dir = os.path.join(src, 'configs')
837 delay = 0.02
838
839 # Create a boards.cfg file
840 boards_cfg = os.path.join(base, 'boards.cfg')
841 content = b'''#
842# List of boards
843# Automatically generated by buildman/boards.py: don't edit
844#
845# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
846
847Active aarch64 armv8 - armltd corstone1000 board0
848Active aarch64 armv8 - armltd total_compute board2
849'''
850 # Check missing file
851 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
852
853 # Check that the board.cfg file is newer
854 time.sleep(delay)
855 tools.write_file(boards_cfg, content)
856 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
857
858 # Touch the Kconfig files after a show delay to avoid a race
859 time.sleep(delay)
860 Path(os.path.join(src, 'Kconfig')).touch()
861 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
862 Path(boards_cfg).touch()
863 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
864
865 # Touch a different Kconfig file
866 time.sleep(delay)
867 Path(os.path.join(src, 'Kconfig.something')).touch()
868 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
869 Path(boards_cfg).touch()
870 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
871
872 # Touch a MAINTAINERS file
873 time.sleep(delay)
874 Path(os.path.join(src, 'MAINTAINERS')).touch()
875 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
876
877 Path(boards_cfg).touch()
878 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
879
880 # Touch a defconfig file
881 time.sleep(delay)
882 Path(os.path.join(config_dir, 'board0_defconfig')).touch()
883 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
884 Path(boards_cfg).touch()
885 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
886
887 # Remove a board and check that the board.cfg file is now older
888 Path(os.path.join(config_dir, 'board0_defconfig')).unlink()
889 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
890
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600891 def test_maintainers(self):
892 """Test detecting boards without a MAINTAINERS entry"""
893 src = self._git_dir
894 main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
895 other = os.path.join(src, 'boards', 'board2', 'MAINTAINERS')
Simon Glass07a95d82023-07-19 17:48:21 -0600896 kc_file = os.path.join(src, 'Kconfig')
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600897 config_dir = os.path.join(src, 'configs')
898 params_list, warnings = self._boards.build_board_list(config_dir, src)
899
900 # There should be two boards no warnings
901 self.assertEquals(2, len(params_list))
902 self.assertFalse(warnings)
903
904 # Set an invalid status line in the file
905 orig_data = tools.read_file(main, binary=False)
906 lines = ['S: Other\n' if line.startswith('S:') else line
907 for line in orig_data.splitlines(keepends=True)]
908 tools.write_file(main, ''.join(lines), binary=False)
909 params_list, warnings = self._boards.build_board_list(config_dir, src)
910 self.assertEquals(2, len(params_list))
911 params = params_list[0]
912 if params['target'] == 'board2':
913 params = params_list[1]
914 self.assertEquals('-', params['status'])
915 self.assertEquals(["WARNING: Other: unknown status for 'board0'"],
916 warnings)
917
918 # Remove the status line (S:) from a file
919 lines = [line for line in orig_data.splitlines(keepends=True)
920 if not line.startswith('S:')]
921 tools.write_file(main, ''.join(lines), binary=False)
922 params_list, warnings = self._boards.build_board_list(config_dir, src)
923 self.assertEquals(2, len(params_list))
924 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
925
926 # Remove the configs/ line (F:) from a file - this is the last line
927 data = ''.join(orig_data.splitlines(keepends=True)[:-1])
928 tools.write_file(main, data, binary=False)
929 params_list, warnings = self._boards.build_board_list(config_dir, src)
930 self.assertEquals(2, len(params_list))
Simon Glass99cb6b12023-08-03 12:51:37 -0600931 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600932
Simon Glasse6acab52023-07-19 17:48:26 -0600933 # Mark a board as orphaned - this should give a warning
934 lines = ['S: Orphaned' if line.startswith('S') else line
935 for line in orig_data.splitlines(keepends=True)]
936 tools.write_file(main, ''.join(lines), binary=False)
937 params_list, warnings = self._boards.build_board_list(config_dir, src)
938 self.assertEquals(2, len(params_list))
939 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
940
941 # Change the maintainer to '-' - this should give a warning
942 lines = ['M: -' if line.startswith('M') else line
943 for line in orig_data.splitlines(keepends=True)]
944 tools.write_file(main, ''.join(lines), binary=False)
945 params_list, warnings = self._boards.build_board_list(config_dir, src)
946 self.assertEquals(2, len(params_list))
947 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
948
949 # Remove the maintainer line (M:) from a file
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600950 lines = [line for line in orig_data.splitlines(keepends=True)
951 if not line.startswith('M:')]
952 tools.write_file(main, ''.join(lines), binary=False)
953 params_list, warnings = self._boards.build_board_list(config_dir, src)
954 self.assertEquals(2, len(params_list))
Simon Glasse6acab52023-07-19 17:48:26 -0600955 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600956
957 # Move the contents of the second file into this one, removing the
958 # second file, to check multiple records in a single file.
Simon Glass060ee972023-07-19 17:48:23 -0600959 both_data = orig_data + tools.read_file(other, binary=False)
960 tools.write_file(main, both_data, binary=False)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600961 os.remove(other)
962 params_list, warnings = self._boards.build_board_list(config_dir, src)
963 self.assertEquals(2, len(params_list))
964 self.assertFalse(warnings)
965
Simon Glass9b828ec2023-07-19 17:48:19 -0600966 # Add another record, this should be ignored with a warning
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600967 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
Simon Glass060ee972023-07-19 17:48:23 -0600968 tools.write_file(main, both_data + extra, binary=False)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600969 params_list, warnings = self._boards.build_board_list(config_dir, src)
970 self.assertEquals(2, len(params_list))
Simon Glass99cb6b12023-08-03 12:51:37 -0600971 self.assertFalse(warnings)
Simon Glass07a95d82023-07-19 17:48:21 -0600972
973 # Add another TARGET to the Kconfig
Simon Glass060ee972023-07-19 17:48:23 -0600974 tools.write_file(main, both_data, binary=False)
Simon Glass061499b2023-07-19 17:48:22 -0600975 orig_kc_data = tools.read_file(kc_file)
Simon Glass07a95d82023-07-19 17:48:21 -0600976 extra = (b'''
977if TARGET_BOARD2
978config TARGET_OTHER
979\tbool "other"
980\tdefault y
981endif
982''')
Simon Glass061499b2023-07-19 17:48:22 -0600983 tools.write_file(kc_file, orig_kc_data + extra)
Simon Glass5e728d42023-07-19 17:48:27 -0600984 params_list, warnings = self._boards.build_board_list(config_dir, src,
985 warn_targets=True)
Simon Glass07a95d82023-07-19 17:48:21 -0600986 self.assertEquals(2, len(params_list))
987 self.assertEquals(
988 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
989 warnings)
Simon Glass061499b2023-07-19 17:48:22 -0600990
991 # Remove the TARGET_BOARD0 Kconfig option
992 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
993 for line in orig_kc_data.splitlines(keepends=True)]
994 tools.write_file(kc_file, b''.join(lines))
Simon Glass5e728d42023-07-19 17:48:27 -0600995 params_list, warnings = self._boards.build_board_list(config_dir, src,
996 warn_targets=True)
Simon Glass061499b2023-07-19 17:48:22 -0600997 self.assertEquals(2, len(params_list))
998 self.assertEquals(
999 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
1000 warnings)
Simon Glass060ee972023-07-19 17:48:23 -06001001 tools.write_file(kc_file, orig_kc_data)
1002
1003 # Replace the last F: line of board 2 with an N: line
1004 data = ''.join(both_data.splitlines(keepends=True)[:-1])
1005 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
1006 params_list, warnings = self._boards.build_board_list(config_dir, src)
1007 self.assertEquals(2, len(params_list))
1008 self.assertFalse(warnings)
1009
Simon Glass09afcb72023-07-19 17:48:28 -06001010 def testRegenBoards(self):
1011 """Test that we can regenerate the boards.cfg file"""
1012 outfile = os.path.join(self._output_dir, 'test-boards.cfg')
1013 if os.path.exists(outfile):
1014 os.remove(outfile)
1015 with test_util.capture_sys_output() as (stdout, stderr):
1016 result = self._RunControl('-R', outfile, brds=None,
1017 get_builder=False)
1018 self.assertTrue(os.path.exists(outfile))
Simon Glass8f317b92023-07-19 17:48:35 -06001019
1020 def test_print_prefix(self):
1021 """Test that we can print the toolchain prefix"""
1022 with test_util.capture_sys_output() as (stdout, stderr):
1023 result = self._RunControl('-A', 'board0')
1024 self.assertEqual('arm-\n', stdout.getvalue())
1025 self.assertEqual('', stderr.getvalue())
Simon Glass20978742023-07-19 17:48:38 -06001026
1027 def test_exclude_one(self):
1028 """Test excluding a single board from an arch"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001029 self._RunControl('arm', '-x', 'board1', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001030 self.assertEqual(['board0'],
1031 [b.target for b in self._boards.get_selected()])
1032
1033 def test_exclude_arch(self):
1034 """Test excluding an arch"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001035 self._RunControl('-x', 'arm', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001036 self.assertEqual(['board2', 'board4'],
1037 [b.target for b in self._boards.get_selected()])
1038
1039 def test_exclude_comma(self):
1040 """Test excluding a comma-separated list of things"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001041 self._RunControl('-x', 'arm,powerpc', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001042 self.assertEqual(['board4'],
1043 [b.target for b in self._boards.get_selected()])
1044
1045 def test_exclude_list(self):
1046 """Test excluding a list of things"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001047 self._RunControl('-x', 'board2', '-x' 'board4', '-o', self._output_dir)
Simon Glass20978742023-07-19 17:48:38 -06001048 self.assertEqual(['board0', 'board1'],
1049 [b.target for b in self._boards.get_selected()])
Simon Glassf56cc292023-07-19 17:49:03 -06001050
1051 def test_single_boards(self):
1052 """Test building single boards"""
Simon Glassbe9b52c2023-07-25 08:13:22 -06001053 self._RunControl('--boards', 'board1', '-o', self._output_dir)
Simon Glassf56cc292023-07-19 17:49:03 -06001054 self.assertEqual(1, self._builder.count)
1055
Simon Glassbe9b52c2023-07-25 08:13:22 -06001056 self._RunControl('--boards', 'board1', '--boards', 'board2',
1057 '-o', self._output_dir)
Simon Glassf56cc292023-07-19 17:49:03 -06001058 self.assertEqual(2, self._builder.count)
1059
Simon Glassbe9b52c2023-07-25 08:13:22 -06001060 self._RunControl('--boards', 'board1,board2', '--boards', 'board4',
1061 '-o', self._output_dir)
Simon Glassf56cc292023-07-19 17:49:03 -06001062 self.assertEqual(3, self._builder.count)
Simon Glassa8a0ce72023-07-19 17:49:28 -06001063
1064 def test_print_arch(self):
1065 """Test that we can print the board architecture"""
1066 with test_util.capture_sys_output() as (stdout, stderr):
1067 result = self._RunControl('--print-arch', 'board0')
1068 self.assertEqual('arm\n', stdout.getvalue())
1069 self.assertEqual('', stderr.getvalue())