blob: 55dd494fe8ee36d02c45702420e57fd73ac377eb [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassd4144e42014-09-05 19:00:13 -06002# Copyright (c) 2014 Google, Inc
3#
Simon Glassd4144e42014-09-05 19:00:13 -06004
5import os
Simon Glassbd4ed9f2023-07-19 17:48:16 -06006from pathlib import Path
Simon Glassd4144e42014-09-05 19:00:13 -06007import shutil
8import sys
9import tempfile
Simon Glassbd4ed9f2023-07-19 17:48:16 -060010import time
Simon Glassd4144e42014-09-05 19:00:13 -060011import unittest
12
Simon Glass0ede00f2020-04-17 18:09:02 -060013from buildman import board
Simon Glassc52bd222022-07-11 19:04:03 -060014from buildman import boards
Simon Glass0ede00f2020-04-17 18:09:02 -060015from buildman import bsettings
16from buildman import cmdline
17from buildman import control
18from buildman import toolchain
Simon Glassbf776672020-04-17 18:09:04 -060019from patman import gitutil
Simon Glass4583c002023-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 Glassd4144e42014-09-05 19:00:13 -060024
Simon Glass8b985ee2014-09-05 19:00:15 -060025settings_data = '''
26# Buildman settings file
Tom Rinid7713ad2022-11-09 19:14:53 -070027[global]
Simon Glass8b985ee2014-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 Yamada98655432018-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 Glass8b985ee2014-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 Glass938fa372022-07-11 19:03:58 -060042BOARDS = [
Simon Glass2ef88d62023-07-19 17:48:12 -060043 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''],
44 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''],
Simon Glass823e60b2014-09-05 19:00:16 -060045 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
Simon Glass823e60b2014-09-05 19:00:16 -060046 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
47]
48
Simon Glassdfb7e932014-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 Glass950a2312014-09-05 19:00:23 -060085 Series-version: 8
Simon Glassdfb7e932014-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 Glass950a2312014-09-05 19:00:23 -0600163 Series-version: 9
164
Simon Glassdfb7e932014-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 Glassd4144e42014-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 Riniaae62582019-10-07 17:17:36 -0400184 self._output_dir = tempfile.mkdtemp()
Simon Glassd4144e42014-09-05 19:00:13 -0600185 self._git_dir = os.path.join(self._base_dir, 'src')
186 self._buildman_pathname = sys.argv[0]
Simon Glassbd6f5d92016-07-27 20:33:00 -0600187 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
Simon Glassd4144e42014-09-05 19:00:13 -0600188 command.test_result = self._HandleCommand
Simon Glass42d42cf2023-07-19 17:49:05 -0600189 bsettings.setup(None)
190 bsettings.add_file(settings_data)
Simon Glassdfb7e932014-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 Glassc52bd222022-07-11 19:04:03 -0600194 self._boards = boards.Boards()
Simon Glass938fa372022-07-11 19:03:58 -0600195 for brd in BOARDS:
Simon Glass6014db62022-07-11 19:04:02 -0600196 self._boards.add_board(board.Board(*brd))
Simon Glassd4144e42014-09-05 19:00:13 -0600197
Simon Glassdfb7e932014-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 Glass938fa372022-07-11 19:03:58 -0600201 self._total_builds = self._commits * len(BOARDS)
Simon Glassdfb7e932014-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 Glassf7582ce2014-09-05 19:00:22 -0600209 self._test_branch = TEST_BRANCH
210
Tom Rinid7713ad2022-11-09 19:14:53 -0700211 # Set to True to report missing blobs
212 self._missing = False
213
Simon Glass3350d342023-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 Glassdfb7e932014-09-05 19:00:20 -0600220 # Avoid sending any output and clear all terminal output
Simon Glass098b10f2022-01-29 14:14:18 -0700221 terminal.set_print_test_mode()
222 terminal.get_print_test_lines()
Simon Glassdfb7e932014-09-05 19:00:20 -0600223
Simon Glassd4144e42014-09-05 19:00:13 -0600224 def tearDown(self):
225 shutil.rmtree(self._base_dir)
Simon Glass5f319fa2022-11-09 19:14:52 -0700226 shutil.rmtree(self._output_dir)
Simon Glassd4144e42014-09-05 19:00:13 -0600227
Simon Glassdfb7e932014-09-05 19:00:20 -0600228 def setupToolchains(self):
229 self._toolchains = toolchain.Toolchains()
230 self._toolchains.Add('gcc', test=False)
231
Simon Glassd4144e42014-09-05 19:00:13 -0600232 def _RunBuildman(self, *args):
Simon Glassd9800692022-01-29 14:14:05 -0700233 return command.run_pipe([[self._buildman_pathname] + list(args)],
Simon Glassd4144e42014-09-05 19:00:13 -0600234 capture=True, capture_stderr=True)
235
Simon Glassa1431e62023-07-19 17:48:28 -0600236 def _RunControl(self, *args, brds=False, clean_dir=False,
237 test_thread_exceptions=False, get_builder=True):
Simon Glass24993312021-04-11 16:27:25 +1200238 """Run buildman
239
240 Args:
241 args: List of arguments to pass
Simon Glassa1431e62023-07-19 17:48:28 -0600242 brds: Boards object, or False to pass self._boards, or None to pass
243 None
Simon Glass24993312021-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 Glass8116c782021-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 Glassa1431e62023-07-19 17:48:28 -0600249 get_builder (bool): Set self._builder to the resulting builder
Simon Glass24993312021-04-11 16:27:25 +1200250
251 Returns:
252 result code from buildman
253 """
Simon Glassd4144e42014-09-05 19:00:13 -0600254 sys.argv = [sys.argv[0]] + list(args)
Simon Glass529957c2023-07-19 17:49:04 -0600255 args = cmdline.parse_args()
Simon Glassa1431e62023-07-19 17:48:28 -0600256 if brds == False:
257 brds = self._boards
Simon Glass9ef05b92023-07-19 17:48:30 -0600258 result = control.do_buildman(
Simon Glass529957c2023-07-19 17:49:04 -0600259 args, toolchains=self._toolchains, make_func=self._HandleMake,
260 brds=brds, clean_dir=clean_dir,
Simon Glass9ef05b92023-07-19 17:48:30 -0600261 test_thread_exceptions=test_thread_exceptions)
Simon Glassa1431e62023-07-19 17:48:28 -0600262 if get_builder:
Simon Glassb8be2bd2023-07-19 17:48:31 -0600263 self._builder = control.TEST_BUILDER
Simon Glassdfb7e932014-09-05 19:00:20 -0600264 return result
Simon Glassd4144e42014-09-05 19:00:13 -0600265
266 def testFullHelp(self):
267 command.test_result = None
268 result = self._RunBuildman('-H')
Simon Glass74df4912022-11-09 19:14:43 -0700269 help_file = os.path.join(self._buildman_dir, 'README.rst')
Tom Rini3759df02018-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 Glassd4144e42014-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 Glass74df4912022-11-09 19:14:43 -0700280 help_file = os.path.join(self._buildman_dir, 'README.rst')
Simon Glassd4144e42014-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 Glass0157b182022-01-29 14:14:11 -0700288 gitutil.setup()
Simon Glassd4144e42014-09-05 19:00:13 -0600289 self.assertEqual(gitutil.use_no_decorate, False)
290
291 command.test_result = command.CommandResult(return_code=0)
Simon Glass0157b182022-01-29 14:14:11 -0700292 gitutil.setup()
Simon Glassd4144e42014-09-05 19:00:13 -0600293 self.assertEqual(gitutil.use_no_decorate, True)
294
295 def _HandleCommandGitLog(self, args):
Simon Glassd4c85722016-03-12 18:50:31 -0700296 if args[-1] == '--':
297 args = args[:-1]
Simon Glassd4144e42014-09-05 19:00:13 -0600298 if '-n0' in args:
299 return command.CommandResult(return_code=0)
Simon Glassf7582ce2014-09-05 19:00:22 -0600300 elif args[-1] == 'upstream/master..%s' % self._test_branch:
Simon Glassdfb7e932014-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 Glassf7582ce2014-09-05 19:00:22 -0600303 if args[-1] == self._test_branch:
Simon Glassdfb7e932014-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 Glassd4144e42014-09-05 19:00:13 -0600307
308 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600309 print('git log', args)
Simon Glassd4144e42014-09-05 19:00:13 -0600310 sys.exit(1)
311
Simon Glassdfb7e932014-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 Glassf7582ce2014-09-05 19:00:22 -0600318 elif config == 'branch.%s.remote' % self._test_branch:
Simon Glassdfb7e932014-09-05 19:00:20 -0600319 return command.CommandResult(return_code=0, stdout='upstream\n')
Simon Glassf7582ce2014-09-05 19:00:22 -0600320 elif config == 'branch.%s.merge' % self._test_branch:
Simon Glassdfb7e932014-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 Glassc05aa032019-10-31 07:42:53 -0600325 print('git config', args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600326 sys.exit(1)
327
Simon Glassd4144e42014-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 Glassdfb7e932014-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 Glassd4144e42014-09-05 19:00:13 -0600349 if sub_cmd == 'config':
Simon Glassdfb7e932014-09-05 19:00:20 -0600350 return self._HandleCommandGitConfig(args)
Simon Glassd4144e42014-09-05 19:00:13 -0600351 elif sub_cmd == 'log':
352 return self._HandleCommandGitLog(args)
Simon Glassdfb7e932014-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 Yasak76de29f2020-09-03 15:51:03 +0300357 elif sub_cmd == 'worktree':
358 return command.CommandResult(return_code=0)
Simon Glassd4144e42014-09-05 19:00:13 -0600359
360 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600361 print('git', git_args, sub_cmd, args)
Simon Glassd4144e42014-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 Kiernan0ddc5102018-05-31 04:48:33 +0000370 def _HandleCommandObjcopy(self, args):
371 return command.CommandResult(return_code=0)
372
Simon Glassd4144e42014-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 Glassdfb7e932014-09-05 19:00:20 -0600387 wc = False
Simon Glassd4144e42014-09-05 19:00:13 -0600388 if len(pipe_list) != 1:
Simon Glassdfb7e932014-09-05 19:00:20 -0600389 if pipe_list[1] == ['wc', '-l']:
390 wc = True
391 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600392 print('invalid pipe', kwargs)
Simon Glassdfb7e932014-09-05 19:00:20 -0600393 sys.exit(1)
Simon Glassd4144e42014-09-05 19:00:13 -0600394 cmd = pipe_list[0][0]
395 args = pipe_list[0][1:]
Simon Glassdfb7e932014-09-05 19:00:20 -0600396 result = None
Simon Glassd4144e42014-09-05 19:00:13 -0600397 if cmd == 'git':
Simon Glassdfb7e932014-09-05 19:00:20 -0600398 result = self._HandleCommandGit(args)
Simon Glassd4144e42014-09-05 19:00:13 -0600399 elif cmd == './scripts/show-gnu-make':
400 return command.CommandResult(return_code=0, stdout='make')
Simon Glassdfb7e932014-09-05 19:00:20 -0600401 elif cmd.endswith('nm'):
Simon Glassd4144e42014-09-05 19:00:13 -0600402 return self._HandleCommandNm(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600403 elif cmd.endswith('objdump'):
Simon Glassd4144e42014-09-05 19:00:13 -0600404 return self._HandleCommandObjdump(args)
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000405 elif cmd.endswith('objcopy'):
406 return self._HandleCommandObjcopy(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600407 elif cmd.endswith( 'size'):
Simon Glassd4144e42014-09-05 19:00:13 -0600408 return self._HandleCommandSize(args)
409
Simon Glassdfb7e932014-09-05 19:00:20 -0600410 if not result:
411 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600412 print('unknown command', kwargs)
Simon Glassdfb7e932014-09-05 19:00:20 -0600413 sys.exit(1)
414
415 if wc:
416 result.stdout = len(result.stdout.splitlines())
417 return result
Simon Glassd4144e42014-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 Glassd9800692022-01-29 14:14:05 -0700428 kwargs: Arguments to pass to command.run_pipe()
Simon Glassd4144e42014-09-05 19:00:13 -0600429 """
Simon Glassdfb7e932014-09-05 19:00:20 -0600430 self._make_calls += 1
Simon Glassbfb708a2023-02-21 12:40:29 -0700431 out_dir = ''
432 for arg in args:
433 if arg.startswith('O='):
434 out_dir = arg[2:]
Simon Glassd4144e42014-09-05 19:00:13 -0600435 if stage == 'mrproper':
436 return command.CommandResult(return_code=0)
437 elif stage == 'config':
Simon Glassbfb708a2023-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 Glassd4144e42014-09-05 19:00:13 -0600440 return command.CommandResult(return_code=0,
441 combined='Test configuration complete')
442 elif stage == 'build':
Simon Glassdfb7e932014-09-05 19:00:20 -0600443 stderr = ''
Simon Glassd829f122020-03-18 09:42:42 -0600444 fname = os.path.join(cwd or '', out_dir, 'u-boot')
Simon Glassc1aa66e2022-01-29 14:14:04 -0700445 tools.write_file(fname, b'U-Boot')
Tom Rinid7713ad2022-11-09 19:14:53 -0700446
447 # Handle missing blobs
448 if self._missing:
449 if 'BINMAN_ALLOW_MISSING=1' in args:
450 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
451Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
452
453Some images are invalid'''
454 else:
455 stderr = "binman: Filename 'fsp.bin' not found in input path"
456 elif type(commit) is not str:
Simon Glassdfb7e932014-09-05 19:00:20 -0600457 stderr = self._error.get((brd.target, commit.sequence))
Tom Rinid7713ad2022-11-09 19:14:53 -0700458
Simon Glassdfb7e932014-09-05 19:00:20 -0600459 if stderr:
Tom Rinid7713ad2022-11-09 19:14:53 -0700460 return command.CommandResult(return_code=2, stderr=stderr)
Simon Glassd4144e42014-09-05 19:00:13 -0600461 return command.CommandResult(return_code=0)
462
463 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600464 print('make', stage)
Simon Glassd4144e42014-09-05 19:00:13 -0600465 sys.exit(1)
466
Simon Glassdfb7e932014-09-05 19:00:20 -0600467 # Example function to print output lines
468 def print_lines(self, lines):
Simon Glassc05aa032019-10-31 07:42:53 -0600469 print(len(lines))
Simon Glassdfb7e932014-09-05 19:00:20 -0600470 for line in lines:
Simon Glassc05aa032019-10-31 07:42:53 -0600471 print(line)
Simon Glass098b10f2022-01-29 14:14:18 -0700472 #self.print_lines(terminal.get_print_test_lines())
Simon Glassdfb7e932014-09-05 19:00:20 -0600473
Simon Glass823e60b2014-09-05 19:00:16 -0600474 def testNoBoards(self):
475 """Test that buildman aborts when there are no boards"""
Simon Glassc52bd222022-07-11 19:04:03 -0600476 self._boards = boards.Boards()
Simon Glass823e60b2014-09-05 19:00:16 -0600477 with self.assertRaises(SystemExit):
478 self._RunControl()
479
Simon Glassd4144e42014-09-05 19:00:13 -0600480 def testCurrentSource(self):
481 """Very simple test to invoke buildman on the current source"""
Simon Glassdfb7e932014-09-05 19:00:20 -0600482 self.setupToolchains();
Tom Riniaae62582019-10-07 17:17:36 -0400483 self._RunControl('-o', self._output_dir)
Simon Glass098b10f2022-01-29 14:14:18 -0700484 lines = terminal.get_print_test_lines()
Simon Glass938fa372022-07-11 19:03:58 -0600485 self.assertIn('Building current source for %d boards' % len(BOARDS),
Simon Glassdfb7e932014-09-05 19:00:20 -0600486 lines[0].text)
487
488 def testBadBranch(self):
489 """Test that we can detect an invalid branch"""
490 with self.assertRaises(ValueError):
491 self._RunControl('-b', 'badbranch')
492
493 def testBadToolchain(self):
494 """Test that missing toolchains are detected"""
495 self.setupToolchains();
Tom Riniaae62582019-10-07 17:17:36 -0400496 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass098b10f2022-01-29 14:14:18 -0700497 lines = terminal.get_print_test_lines()
Simon Glassdfb7e932014-09-05 19:00:20 -0600498
499 # Buildman always builds the upstream commit as well
500 self.assertIn('Building %d commits for %d boards' %
Simon Glass938fa372022-07-11 19:03:58 -0600501 (self._commits, len(BOARDS)), lines[0].text)
Simon Glassdfb7e932014-09-05 19:00:20 -0600502 self.assertEqual(self._builder.count, self._total_builds)
503
504 # Only sandbox should succeed, the others don't have toolchains
505 self.assertEqual(self._builder.fail,
506 self._total_builds - self._commits)
Simon Glassb1e5e6d2020-04-09 10:49:45 -0600507 self.assertEqual(ret_code, 100)
Simon Glassdfb7e932014-09-05 19:00:20 -0600508
509 for commit in range(self._commits):
Simon Glass6014db62022-07-11 19:04:02 -0600510 for brd in self._boards.get_list():
Simon Glassf4ed4702022-07-11 19:03:57 -0600511 if brd.arch != 'sandbox':
Simon Glass37edf5f2023-07-19 17:49:06 -0600512 errfile = self._builder.get_err_file(commit, brd.target)
Simon Glassdfb7e932014-09-05 19:00:20 -0600513 fd = open(errfile)
Simon Glass57686d32023-07-19 17:49:25 -0600514 self.assertEqual(
515 fd.readlines(),
516 [f'Tool chain error for {brd.arch}: '
517 f"No tool chain found for arch '{brd.arch}'"])
Simon Glassdfb7e932014-09-05 19:00:20 -0600518 fd.close()
519
520 def testBranch(self):
521 """Test building a branch with all toolchains present"""
Tom Riniaae62582019-10-07 17:17:36 -0400522 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600523 self.assertEqual(self._builder.count, self._total_builds)
524 self.assertEqual(self._builder.fail, 0)
525
526 def testCount(self):
527 """Test building a specific number of commitst"""
Tom Riniaae62582019-10-07 17:17:36 -0400528 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
Simon Glass938fa372022-07-11 19:03:58 -0600529 self.assertEqual(self._builder.count, 2 * len(BOARDS))
Simon Glassdfb7e932014-09-05 19:00:20 -0600530 self.assertEqual(self._builder.fail, 0)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600531 # Each board has a config, and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600532 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
Simon Glassdfb7e932014-09-05 19:00:20 -0600533
534 def testIncremental(self):
535 """Test building a branch twice - the second time should do nothing"""
Tom Riniaae62582019-10-07 17:17:36 -0400536 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600537
538 # Each board has a mrproper, config, and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600539 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glassdfb7e932014-09-05 19:00:20 -0600540 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400541 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600542 self.assertEqual(self._make_calls, 0)
543 self.assertEqual(self._builder.count, self._total_builds)
544 self.assertEqual(self._builder.fail, 0)
545
546 def testForceBuild(self):
547 """The -f flag should force a rebuild"""
Tom Riniaae62582019-10-07 17:17:36 -0400548 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600549 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400550 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600551 # Each board has a config and one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600552 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glassdfb7e932014-09-05 19:00:20 -0600553
554 def testForceReconfigure(self):
555 """The -f flag should force a rebuild"""
Tom Riniaae62582019-10-07 17:17:36 -0400556 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600557 # Each commit has a config and make
Simon Glass938fa372022-07-11 19:03:58 -0600558 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600559
Simon Glasseb70a2c2020-04-09 15:08:51 -0600560 def testMrproper(self):
561 """The -f flag should force a rebuild"""
562 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
563 # Each board has a mkproper, config and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600564 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
Simon Glassdfb7e932014-09-05 19:00:20 -0600565
566 def testErrors(self):
567 """Test handling of build errors"""
568 self._error['board2', 1] = 'fred\n'
Tom Riniaae62582019-10-07 17:17:36 -0400569 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600570 self.assertEqual(self._builder.count, self._total_builds)
571 self.assertEqual(self._builder.fail, 1)
572
573 # Remove the error. This should have no effect since the commit will
574 # not be rebuilt
575 del self._error['board2', 1]
576 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400577 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600578 self.assertEqual(self._builder.count, self._total_builds)
579 self.assertEqual(self._make_calls, 0)
580 self.assertEqual(self._builder.fail, 1)
581
582 # Now use the -F flag to force rebuild of the bad commit
Tom Riniaae62582019-10-07 17:17:36 -0400583 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600584 self.assertEqual(self._builder.count, self._total_builds)
585 self.assertEqual(self._builder.fail, 0)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600586 self.assertEqual(self._make_calls, 2)
Simon Glassf7582ce2014-09-05 19:00:22 -0600587
588 def testBranchWithSlash(self):
589 """Test building a branch with a '/' in the name"""
590 self._test_branch = '/__dev/__testbranch'
Simon Glassad1c9b22023-07-25 08:13:22 -0600591 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
592 clean_dir=False)
Simon Glassf7582ce2014-09-05 19:00:22 -0600593 self.assertEqual(self._builder.count, self._total_builds)
594 self.assertEqual(self._builder.fail, 0)
Lothar Waßmann409fc022018-04-08 05:14:11 -0600595
Simon Glass166a98a2020-04-17 17:51:33 -0600596 def testEnvironment(self):
597 """Test that the done and environment files are written to out-env"""
598 self._RunControl('-o', self._output_dir)
599 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
600 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
601 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
602
Simon Glassf1a83ab2021-04-11 16:27:28 +1200603 def testEnvironmentUnicode(self):
604 """Test there are no unicode errors when the env has non-ASCII chars"""
605 try:
606 varname = b'buildman_test_var'
607 os.environb[varname] = b'strange\x80chars'
608 self.assertEqual(0, self._RunControl('-o', self._output_dir))
609 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
610 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
611 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
612 finally:
613 del os.environb[varname]
614
Simon Glassd829f122020-03-18 09:42:42 -0600615 def testWorkInOutput(self):
616 """Test the -w option which should write directly to the output dir"""
Simon Glassc52bd222022-07-11 19:04:03 -0600617 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600618 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd829f122020-03-18 09:42:42 -0600619 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
Simon Glass938fa372022-07-11 19:03:58 -0600620 brds=board_list)
Simon Glassd829f122020-03-18 09:42:42 -0600621 self.assertTrue(
622 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
Simon Glass60b285f2020-04-17 17:51:34 -0600623 self.assertTrue(
624 os.path.exists(os.path.join(self._output_dir, 'done')))
625 self.assertTrue(
626 os.path.exists(os.path.join(self._output_dir, 'out-env')))
Simon Glassd829f122020-03-18 09:42:42 -0600627
628 def testWorkInOutputFail(self):
629 """Test the -w option failures"""
630 with self.assertRaises(SystemExit) as e:
631 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
632 self.assertIn("single board", str(e.exception))
633 self.assertFalse(
634 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
635
Simon Glassc52bd222022-07-11 19:04:03 -0600636 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600637 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd829f122020-03-18 09:42:42 -0600638 with self.assertRaises(SystemExit) as e:
639 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
Simon Glass938fa372022-07-11 19:03:58 -0600640 '-w', clean_dir=False, brds=board_list)
Simon Glassd829f122020-03-18 09:42:42 -0600641 self.assertIn("single commit", str(e.exception))
Simon Glass88daaef2020-04-17 17:51:32 -0600642
Simon Glassc52bd222022-07-11 19:04:03 -0600643 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600644 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glass88daaef2020-04-17 17:51:32 -0600645 with self.assertRaises(SystemExit) as e:
646 self._RunControl('-w', clean_dir=False)
647 self.assertIn("specify -o", str(e.exception))
Simon Glass8116c782021-04-11 16:27:27 +1200648
649 def testThreadExceptions(self):
650 """Test that exceptions in threads are reported"""
651 with test_util.capture_sys_output() as (stdout, stderr):
652 self.assertEqual(102, self._RunControl('-o', self._output_dir,
653 test_thread_exceptions=True))
Simon Glass8ca09312022-01-22 05:07:32 -0700654 self.assertIn(
655 'Thread exception (use -T0 to run without threads): test exception',
656 stdout.getvalue())
Tom Rinid7713ad2022-11-09 19:14:53 -0700657
658 def testBlobs(self):
659 """Test handling of missing blobs"""
660 self._missing = True
661
662 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
663 errfile = os.path.join(board0_dir, 'err')
664 logfile = os.path.join(board0_dir, 'log')
665
666 # We expect failure when there are missing blobs
667 result = self._RunControl('board0', '-o', self._output_dir)
668 self.assertEqual(100, result)
669 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
670 self.assertTrue(os.path.exists(errfile))
671 self.assertIn(b"Filename 'fsp.bin' not found in input path",
672 tools.read_file(errfile))
673
674 def testBlobsAllowMissing(self):
675 """Allow missing blobs - still failure but a different exit code"""
676 self._missing = True
677 result = self._RunControl('board0', '-o', self._output_dir, '-M',
678 clean_dir=True)
679 self.assertEqual(101, result)
680 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
681 errfile = os.path.join(board0_dir, 'err')
682 self.assertTrue(os.path.exists(errfile))
683 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
684
685 def testBlobsWarning(self):
686 """Allow missing blobs and ignore warnings"""
687 self._missing = True
688 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
689 self.assertEqual(0, result)
690 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
691 errfile = os.path.join(board0_dir, 'err')
692 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
693
694 def testBlobSettings(self):
695 """Test with no settings"""
696 self.assertEqual(False,
697 control.get_allow_missing(False, False, 1, False))
698 self.assertEqual(True,
699 control.get_allow_missing(True, False, 1, False))
700 self.assertEqual(False,
701 control.get_allow_missing(True, True, 1, False))
702
703 def testBlobSettingsAlways(self):
704 """Test the 'always' policy"""
Simon Glass42d42cf2023-07-19 17:49:05 -0600705 bsettings.set_item('global', 'allow-missing', 'always')
Tom Rinid7713ad2022-11-09 19:14:53 -0700706 self.assertEqual(True,
707 control.get_allow_missing(False, False, 1, False))
708 self.assertEqual(False,
709 control.get_allow_missing(False, True, 1, False))
710
711 def testBlobSettingsBranch(self):
712 """Test the 'branch' policy"""
Simon Glass42d42cf2023-07-19 17:49:05 -0600713 bsettings.set_item('global', 'allow-missing', 'branch')
Tom Rinid7713ad2022-11-09 19:14:53 -0700714 self.assertEqual(False,
715 control.get_allow_missing(False, False, 1, False))
716 self.assertEqual(True,
717 control.get_allow_missing(False, False, 1, True))
718 self.assertEqual(False,
719 control.get_allow_missing(False, True, 1, True))
720
721 def testBlobSettingsMultiple(self):
722 """Test the 'multiple' policy"""
Simon Glass42d42cf2023-07-19 17:49:05 -0600723 bsettings.set_item('global', 'allow-missing', 'multiple')
Tom Rinid7713ad2022-11-09 19:14:53 -0700724 self.assertEqual(False,
725 control.get_allow_missing(False, False, 1, False))
726 self.assertEqual(True,
727 control.get_allow_missing(False, False, 2, False))
728 self.assertEqual(False,
729 control.get_allow_missing(False, True, 2, False))
730
731 def testBlobSettingsBranchMultiple(self):
732 """Test the 'branch multiple' policy"""
Simon Glass42d42cf2023-07-19 17:49:05 -0600733 bsettings.set_item('global', 'allow-missing', 'branch multiple')
Tom Rinid7713ad2022-11-09 19:14:53 -0700734 self.assertEqual(False,
735 control.get_allow_missing(False, False, 1, False))
736 self.assertEqual(True,
737 control.get_allow_missing(False, False, 1, True))
738 self.assertEqual(True,
739 control.get_allow_missing(False, False, 2, False))
740 self.assertEqual(True,
741 control.get_allow_missing(False, False, 2, True))
742 self.assertEqual(False,
743 control.get_allow_missing(False, True, 2, True))
Simon Glasscd37d5b2023-02-21 12:40:27 -0700744
Simon Glass93202d72023-02-21 12:40:28 -0700745 def check_command(self, *extra_args):
746 """Run a command with the extra arguments and return the commands used
747
748 Args:
749 extra_args (list of str): List of extra arguments
750
751 Returns:
752 list of str: Lines returned in the out-cmd file
753 """
754 self._RunControl('-o', self._output_dir, *extra_args)
Simon Glasscd37d5b2023-02-21 12:40:27 -0700755 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
756 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
757 cmd_fname = os.path.join(board0_dir, 'out-cmd')
758 self.assertTrue(os.path.exists(cmd_fname))
759 data = tools.read_file(cmd_fname)
Simon Glassbfb708a2023-02-21 12:40:29 -0700760
761 config_fname = os.path.join(board0_dir, '.config')
762 self.assertTrue(os.path.exists(config_fname))
763 cfg_data = tools.read_file(config_fname)
764
765 return data.splitlines(), cfg_data
Simon Glass93202d72023-02-21 12:40:28 -0700766
767 def testCmdFile(self):
768 """Test that the -cmd-out file is produced"""
Simon Glassbfb708a2023-02-21 12:40:29 -0700769 lines = self.check_command()[0]
Simon Glasscd37d5b2023-02-21 12:40:27 -0700770 self.assertEqual(2, len(lines))
771 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
772 self.assertRegex(lines[0], b'make O=/.*-s.*')
Simon Glass93202d72023-02-21 12:40:28 -0700773
774 def testNoLto(self):
775 """Test that the --no-lto flag works"""
Simon Glassbfb708a2023-02-21 12:40:29 -0700776 lines = self.check_command('-L')[0]
Simon Glass93202d72023-02-21 12:40:28 -0700777 self.assertIn(b'NO_LTO=1', lines[0])
778
Simon Glassbfb708a2023-02-21 12:40:29 -0700779 def testReproducible(self):
780 """Test that the -r flag works"""
781 lines, cfg_data = self.check_command('-r')
782 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
783
784 # We should see CONFIG_LOCALVERSION_AUTO unset
785 self.assertEqual(b'''CONFIG_SOMETHING=1
786# CONFIG_LOCALVERSION_AUTO is not set
787''', cfg_data)
788
789 with test_util.capture_sys_output() as (stdout, stderr):
790 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
791 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
792
793 # We should see CONFIG_LOCALVERSION_AUTO unset
794 self.assertEqual(b'''CONFIG_SOMETHING=1
795CONFIG_LOCALVERSION=y
796''', cfg_data)
797 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())
Simon Glass3350d342023-07-19 17:48:15 -0600798
799 def test_scan_defconfigs(self):
800 """Test scanning the defconfigs to obtain all the boards"""
801 src = self._git_dir
802
803 # Scan the test directory which contains a Kconfig and some *_defconfig
804 # files
Simon Glassbec06ed2023-07-19 17:48:21 -0600805 params, warnings = self._boards.scan_defconfigs(src, src)
Simon Glass3350d342023-07-19 17:48:15 -0600806
807 # We should get two boards
808 self.assertEquals(2, len(params))
Simon Glassbec06ed2023-07-19 17:48:21 -0600809 self.assertFalse(warnings)
Simon Glass3350d342023-07-19 17:48:15 -0600810 first = 0 if params[0]['target'] == 'board0' else 1
811 board0 = params[first]
812 board2 = params[1 - first]
813
814 self.assertEquals('arm', board0['arch'])
815 self.assertEquals('armv7', board0['cpu'])
816 self.assertEquals('-', board0['soc'])
817 self.assertEquals('Tester', board0['vendor'])
818 self.assertEquals('ARM Board 0', board0['board'])
819 self.assertEquals('config0', board0['config'])
820 self.assertEquals('board0', board0['target'])
821
822 self.assertEquals('powerpc', board2['arch'])
823 self.assertEquals('ppc', board2['cpu'])
824 self.assertEquals('mpc85xx', board2['soc'])
825 self.assertEquals('Tester', board2['vendor'])
826 self.assertEquals('PowerPC board 1', board2['board'])
827 self.assertEquals('config2', board2['config'])
828 self.assertEquals('board2', board2['target'])
829
Simon Glassbd4ed9f2023-07-19 17:48:16 -0600830 def test_output_is_new(self):
831 """Test detecting new changes to Kconfig"""
832 base = self._base_dir
833 src = self._git_dir
834 config_dir = os.path.join(src, 'configs')
835 delay = 0.02
836
837 # Create a boards.cfg file
838 boards_cfg = os.path.join(base, 'boards.cfg')
839 content = b'''#
840# List of boards
841# Automatically generated by buildman/boards.py: don't edit
842#
843# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
844
845Active aarch64 armv8 - armltd corstone1000 board0
846Active aarch64 armv8 - armltd total_compute board2
847'''
848 # Check missing file
849 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
850
851 # Check that the board.cfg file is newer
852 time.sleep(delay)
853 tools.write_file(boards_cfg, content)
854 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
855
856 # Touch the Kconfig files after a show delay to avoid a race
857 time.sleep(delay)
858 Path(os.path.join(src, 'Kconfig')).touch()
859 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
860 Path(boards_cfg).touch()
861 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
862
863 # Touch a different Kconfig file
864 time.sleep(delay)
865 Path(os.path.join(src, 'Kconfig.something')).touch()
866 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
867 Path(boards_cfg).touch()
868 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
869
870 # Touch a MAINTAINERS file
871 time.sleep(delay)
872 Path(os.path.join(src, 'MAINTAINERS')).touch()
873 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
874
875 Path(boards_cfg).touch()
876 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
877
878 # Touch a defconfig file
879 time.sleep(delay)
880 Path(os.path.join(config_dir, 'board0_defconfig')).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 # Remove a board and check that the board.cfg file is now older
886 Path(os.path.join(config_dir, 'board0_defconfig')).unlink()
887 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
888
Simon Glass5df95cf2023-07-19 17:48:17 -0600889 def test_maintainers(self):
890 """Test detecting boards without a MAINTAINERS entry"""
891 src = self._git_dir
892 main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
893 other = os.path.join(src, 'boards', 'board2', 'MAINTAINERS')
Simon Glassbec06ed2023-07-19 17:48:21 -0600894 kc_file = os.path.join(src, 'Kconfig')
Simon Glass5df95cf2023-07-19 17:48:17 -0600895 config_dir = os.path.join(src, 'configs')
896 params_list, warnings = self._boards.build_board_list(config_dir, src)
897
898 # There should be two boards no warnings
899 self.assertEquals(2, len(params_list))
900 self.assertFalse(warnings)
901
902 # Set an invalid status line in the file
903 orig_data = tools.read_file(main, binary=False)
904 lines = ['S: Other\n' if line.startswith('S:') else line
905 for line in orig_data.splitlines(keepends=True)]
906 tools.write_file(main, ''.join(lines), binary=False)
907 params_list, warnings = self._boards.build_board_list(config_dir, src)
908 self.assertEquals(2, len(params_list))
909 params = params_list[0]
910 if params['target'] == 'board2':
911 params = params_list[1]
912 self.assertEquals('-', params['status'])
913 self.assertEquals(["WARNING: Other: unknown status for 'board0'"],
914 warnings)
915
916 # Remove the status line (S:) from a file
917 lines = [line for line in orig_data.splitlines(keepends=True)
918 if not line.startswith('S:')]
919 tools.write_file(main, ''.join(lines), binary=False)
920 params_list, warnings = self._boards.build_board_list(config_dir, src)
921 self.assertEquals(2, len(params_list))
922 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
923
924 # Remove the configs/ line (F:) from a file - this is the last line
925 data = ''.join(orig_data.splitlines(keepends=True)[:-1])
926 tools.write_file(main, data, binary=False)
927 params_list, warnings = self._boards.build_board_list(config_dir, src)
928 self.assertEquals(2, len(params_list))
Simon Glass48d4c0a2023-08-03 12:51:37 -0600929 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glass5df95cf2023-07-19 17:48:17 -0600930
Simon Glass9a7cc812023-07-19 17:48:26 -0600931 # Mark a board as orphaned - this should give a warning
932 lines = ['S: Orphaned' if line.startswith('S') else line
933 for line in orig_data.splitlines(keepends=True)]
934 tools.write_file(main, ''.join(lines), binary=False)
935 params_list, warnings = self._boards.build_board_list(config_dir, src)
936 self.assertEquals(2, len(params_list))
937 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
938
939 # Change the maintainer to '-' - this should give a warning
940 lines = ['M: -' if line.startswith('M') else line
941 for line in orig_data.splitlines(keepends=True)]
942 tools.write_file(main, ''.join(lines), binary=False)
943 params_list, warnings = self._boards.build_board_list(config_dir, src)
944 self.assertEquals(2, len(params_list))
945 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
946
947 # Remove the maintainer line (M:) from a file
Simon Glass5df95cf2023-07-19 17:48:17 -0600948 lines = [line for line in orig_data.splitlines(keepends=True)
949 if not line.startswith('M:')]
950 tools.write_file(main, ''.join(lines), binary=False)
951 params_list, warnings = self._boards.build_board_list(config_dir, src)
952 self.assertEquals(2, len(params_list))
Simon Glass9a7cc812023-07-19 17:48:26 -0600953 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glass5df95cf2023-07-19 17:48:17 -0600954
955 # Move the contents of the second file into this one, removing the
956 # second file, to check multiple records in a single file.
Simon Glassc6491532023-07-19 17:48:23 -0600957 both_data = orig_data + tools.read_file(other, binary=False)
958 tools.write_file(main, both_data, binary=False)
Simon Glass5df95cf2023-07-19 17:48:17 -0600959 os.remove(other)
960 params_list, warnings = self._boards.build_board_list(config_dir, src)
961 self.assertEquals(2, len(params_list))
962 self.assertFalse(warnings)
963
Simon Glassbc12d032023-07-19 17:48:19 -0600964 # Add another record, this should be ignored with a warning
Simon Glass5df95cf2023-07-19 17:48:17 -0600965 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
Simon Glassc6491532023-07-19 17:48:23 -0600966 tools.write_file(main, both_data + extra, binary=False)
Simon Glass5df95cf2023-07-19 17:48:17 -0600967 params_list, warnings = self._boards.build_board_list(config_dir, src)
968 self.assertEquals(2, len(params_list))
Simon Glass48d4c0a2023-08-03 12:51:37 -0600969 self.assertFalse(warnings)
Simon Glassbec06ed2023-07-19 17:48:21 -0600970
971 # Add another TARGET to the Kconfig
Simon Glassc6491532023-07-19 17:48:23 -0600972 tools.write_file(main, both_data, binary=False)
Simon Glassad995992023-07-19 17:48:22 -0600973 orig_kc_data = tools.read_file(kc_file)
Simon Glassbec06ed2023-07-19 17:48:21 -0600974 extra = (b'''
975if TARGET_BOARD2
976config TARGET_OTHER
977\tbool "other"
978\tdefault y
979endif
980''')
Simon Glassad995992023-07-19 17:48:22 -0600981 tools.write_file(kc_file, orig_kc_data + extra)
Simon Glass1b218422023-07-19 17:48:27 -0600982 params_list, warnings = self._boards.build_board_list(config_dir, src,
983 warn_targets=True)
Simon Glassbec06ed2023-07-19 17:48:21 -0600984 self.assertEquals(2, len(params_list))
985 self.assertEquals(
986 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
987 warnings)
Simon Glassad995992023-07-19 17:48:22 -0600988
989 # Remove the TARGET_BOARD0 Kconfig option
990 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
991 for line in orig_kc_data.splitlines(keepends=True)]
992 tools.write_file(kc_file, b''.join(lines))
Simon Glass1b218422023-07-19 17:48:27 -0600993 params_list, warnings = self._boards.build_board_list(config_dir, src,
994 warn_targets=True)
Simon Glassad995992023-07-19 17:48:22 -0600995 self.assertEquals(2, len(params_list))
996 self.assertEquals(
997 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
998 warnings)
Simon Glassc6491532023-07-19 17:48:23 -0600999 tools.write_file(kc_file, orig_kc_data)
1000
1001 # Replace the last F: line of board 2 with an N: line
1002 data = ''.join(both_data.splitlines(keepends=True)[:-1])
1003 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
1004 params_list, warnings = self._boards.build_board_list(config_dir, src)
1005 self.assertEquals(2, len(params_list))
1006 self.assertFalse(warnings)
1007
Simon Glassa1431e62023-07-19 17:48:28 -06001008 def testRegenBoards(self):
1009 """Test that we can regenerate the boards.cfg file"""
1010 outfile = os.path.join(self._output_dir, 'test-boards.cfg')
1011 if os.path.exists(outfile):
1012 os.remove(outfile)
1013 with test_util.capture_sys_output() as (stdout, stderr):
1014 result = self._RunControl('-R', outfile, brds=None,
1015 get_builder=False)
1016 self.assertTrue(os.path.exists(outfile))
Simon Glassd4366b12023-07-19 17:48:35 -06001017
1018 def test_print_prefix(self):
1019 """Test that we can print the toolchain prefix"""
1020 with test_util.capture_sys_output() as (stdout, stderr):
1021 result = self._RunControl('-A', 'board0')
1022 self.assertEqual('arm-\n', stdout.getvalue())
1023 self.assertEqual('', stderr.getvalue())
Simon Glass26d90772023-07-19 17:48:38 -06001024
1025 def test_exclude_one(self):
1026 """Test excluding a single board from an arch"""
Simon Glassad1c9b22023-07-25 08:13:22 -06001027 self._RunControl('arm', '-x', 'board1', '-o', self._output_dir)
Simon Glass26d90772023-07-19 17:48:38 -06001028 self.assertEqual(['board0'],
1029 [b.target for b in self._boards.get_selected()])
1030
1031 def test_exclude_arch(self):
1032 """Test excluding an arch"""
Simon Glassad1c9b22023-07-25 08:13:22 -06001033 self._RunControl('-x', 'arm', '-o', self._output_dir)
Simon Glass26d90772023-07-19 17:48:38 -06001034 self.assertEqual(['board2', 'board4'],
1035 [b.target for b in self._boards.get_selected()])
1036
1037 def test_exclude_comma(self):
1038 """Test excluding a comma-separated list of things"""
Simon Glassad1c9b22023-07-25 08:13:22 -06001039 self._RunControl('-x', 'arm,powerpc', '-o', self._output_dir)
Simon Glass26d90772023-07-19 17:48:38 -06001040 self.assertEqual(['board4'],
1041 [b.target for b in self._boards.get_selected()])
1042
1043 def test_exclude_list(self):
1044 """Test excluding a list of things"""
Simon Glassad1c9b22023-07-25 08:13:22 -06001045 self._RunControl('-x', 'board2', '-x' 'board4', '-o', self._output_dir)
Simon Glass26d90772023-07-19 17:48:38 -06001046 self.assertEqual(['board0', 'board1'],
1047 [b.target for b in self._boards.get_selected()])
Simon Glass6a0c7b42023-07-19 17:49:03 -06001048
1049 def test_single_boards(self):
1050 """Test building single boards"""
Simon Glassad1c9b22023-07-25 08:13:22 -06001051 self._RunControl('--boards', 'board1', '-o', self._output_dir)
Simon Glass6a0c7b42023-07-19 17:49:03 -06001052 self.assertEqual(1, self._builder.count)
1053
Simon Glassad1c9b22023-07-25 08:13:22 -06001054 self._RunControl('--boards', 'board1', '--boards', 'board2',
1055 '-o', self._output_dir)
Simon Glass6a0c7b42023-07-19 17:49:03 -06001056 self.assertEqual(2, self._builder.count)
1057
Simon Glassad1c9b22023-07-25 08:13:22 -06001058 self._RunControl('--boards', 'board1,board2', '--boards', 'board4',
1059 '-o', self._output_dir)
Simon Glass6a0c7b42023-07-19 17:49:03 -06001060 self.assertEqual(3, self._builder.count)
Simon Glassad037872023-07-19 17:49:28 -06001061
1062 def test_print_arch(self):
1063 """Test that we can print the board architecture"""
1064 with test_util.capture_sys_output() as (stdout, stderr):
1065 result = self._RunControl('--print-arch', 'board0')
1066 self.assertEqual('arm\n', stdout.getvalue())
1067 self.assertEqual('', stderr.getvalue())