blob: 7f3d54cc762462043205933552da4ffd64f0698c [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 Glass22901f92022-01-22 05:07:31 -0700189 bsettings.Setup(None)
190 bsettings.AddFile(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 Glassd4c6c8a2022-07-11 19:03:58 -0600236 def _RunControl(self, *args, brds=None, clean_dir=False,
Simon Glass9bf9a722021-04-11 16:27:27 +1200237 test_thread_exceptions=False):
Simon Glassa29b3ea2021-04-11 16:27:25 +1200238 """Run buildman
239
240 Args:
241 args: List of arguments to pass
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600242 brds: Boards object
Simon Glassa29b3ea2021-04-11 16:27:25 +1200243 clean_dir: Used for tests only, indicates that the existing output_dir
244 should be removed before starting the build
Simon Glass9bf9a722021-04-11 16:27:27 +1200245 test_thread_exceptions: Uses for tests only, True to make the threads
246 raise an exception instead of reporting their result. This simulates
247 a failure in the code somewhere
Simon Glassa29b3ea2021-04-11 16:27:25 +1200248
249 Returns:
250 result code from buildman
251 """
Simon Glassed098bb2014-09-05 19:00:13 -0600252 sys.argv = [sys.argv[0]] + list(args)
253 options, args = cmdline.ParseArgs()
Simon Glass8e959562014-09-05 19:00:20 -0600254 result = control.DoBuildman(options, args, toolchains=self._toolchains,
Simon Glass5df45222022-07-11 19:04:00 -0600255 make_func=self._HandleMake, brds=brds or self._boards,
Simon Glass9bf9a722021-04-11 16:27:27 +1200256 clean_dir=clean_dir,
257 test_thread_exceptions=test_thread_exceptions)
Simon Glass8e959562014-09-05 19:00:20 -0600258 self._builder = control.builder
259 return result
Simon Glassed098bb2014-09-05 19:00:13 -0600260
261 def testFullHelp(self):
262 command.test_result = None
263 result = self._RunBuildman('-H')
Simon Glass79cc9be2022-11-09 19:14:43 -0700264 help_file = os.path.join(self._buildman_dir, 'README.rst')
Tom Rinic3c0b6d2018-01-16 15:29:50 -0500265 # Remove possible extraneous strings
266 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
267 gothelp = result.stdout.replace(extra, '')
268 self.assertEqual(len(gothelp), os.path.getsize(help_file))
Simon Glassed098bb2014-09-05 19:00:13 -0600269 self.assertEqual(0, len(result.stderr))
270 self.assertEqual(0, result.return_code)
271
272 def testHelp(self):
273 command.test_result = None
274 result = self._RunBuildman('-h')
Simon Glass79cc9be2022-11-09 19:14:43 -0700275 help_file = os.path.join(self._buildman_dir, 'README.rst')
Simon Glassed098bb2014-09-05 19:00:13 -0600276 self.assertTrue(len(result.stdout) > 1000)
277 self.assertEqual(0, len(result.stderr))
278 self.assertEqual(0, result.return_code)
279
280 def testGitSetup(self):
281 """Test gitutils.Setup(), from outside the module itself"""
282 command.test_result = command.CommandResult(return_code=1)
Simon Glass761648b2022-01-29 14:14:11 -0700283 gitutil.setup()
Simon Glassed098bb2014-09-05 19:00:13 -0600284 self.assertEqual(gitutil.use_no_decorate, False)
285
286 command.test_result = command.CommandResult(return_code=0)
Simon Glass761648b2022-01-29 14:14:11 -0700287 gitutil.setup()
Simon Glassed098bb2014-09-05 19:00:13 -0600288 self.assertEqual(gitutil.use_no_decorate, True)
289
290 def _HandleCommandGitLog(self, args):
Simon Glass642e9a62016-03-12 18:50:31 -0700291 if args[-1] == '--':
292 args = args[:-1]
Simon Glassed098bb2014-09-05 19:00:13 -0600293 if '-n0' in args:
294 return command.CommandResult(return_code=0)
Simon Glass4aeceb92014-09-05 19:00:22 -0600295 elif args[-1] == 'upstream/master..%s' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600296 return command.CommandResult(return_code=0, stdout=commit_shortlog)
297 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
Simon Glass4aeceb92014-09-05 19:00:22 -0600298 if args[-1] == self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600299 count = int(args[3][2:])
300 return command.CommandResult(return_code=0,
301 stdout=''.join(commit_log[:count]))
Simon Glassed098bb2014-09-05 19:00:13 -0600302
303 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600304 print('git log', args)
Simon Glassed098bb2014-09-05 19:00:13 -0600305 sys.exit(1)
306
Simon Glass8e959562014-09-05 19:00:20 -0600307 def _HandleCommandGitConfig(self, args):
308 config = args[0]
309 if config == 'sendemail.aliasesfile':
310 return command.CommandResult(return_code=0)
311 elif config.startswith('branch.badbranch'):
312 return command.CommandResult(return_code=1)
Simon Glass4aeceb92014-09-05 19:00:22 -0600313 elif config == 'branch.%s.remote' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600314 return command.CommandResult(return_code=0, stdout='upstream\n')
Simon Glass4aeceb92014-09-05 19:00:22 -0600315 elif config == 'branch.%s.merge' % self._test_branch:
Simon Glass8e959562014-09-05 19:00:20 -0600316 return command.CommandResult(return_code=0,
317 stdout='refs/heads/master\n')
318
319 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600320 print('git config', args)
Simon Glass8e959562014-09-05 19:00:20 -0600321 sys.exit(1)
322
Simon Glassed098bb2014-09-05 19:00:13 -0600323 def _HandleCommandGit(self, in_args):
324 """Handle execution of a git command
325
326 This uses a hacked-up parser.
327
328 Args:
329 in_args: Arguments after 'git' from the command line
330 """
331 git_args = [] # Top-level arguments to git itself
332 sub_cmd = None # Git sub-command selected
333 args = [] # Arguments to the git sub-command
334 for arg in in_args:
335 if sub_cmd:
336 args.append(arg)
337 elif arg[0] == '-':
338 git_args.append(arg)
339 else:
Simon Glass8e959562014-09-05 19:00:20 -0600340 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
341 git_args.append(arg)
342 else:
343 sub_cmd = arg
Simon Glassed098bb2014-09-05 19:00:13 -0600344 if sub_cmd == 'config':
Simon Glass8e959562014-09-05 19:00:20 -0600345 return self._HandleCommandGitConfig(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600346 elif sub_cmd == 'log':
347 return self._HandleCommandGitLog(args)
Simon Glass8e959562014-09-05 19:00:20 -0600348 elif sub_cmd == 'clone':
349 return command.CommandResult(return_code=0)
350 elif sub_cmd == 'checkout':
351 return command.CommandResult(return_code=0)
Alper Nebi Yasakfede44a2020-09-03 15:51:03 +0300352 elif sub_cmd == 'worktree':
353 return command.CommandResult(return_code=0)
Simon Glassed098bb2014-09-05 19:00:13 -0600354
355 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600356 print('git', git_args, sub_cmd, args)
Simon Glassed098bb2014-09-05 19:00:13 -0600357 sys.exit(1)
358
359 def _HandleCommandNm(self, args):
360 return command.CommandResult(return_code=0)
361
362 def _HandleCommandObjdump(self, args):
363 return command.CommandResult(return_code=0)
364
Alex Kiernanf07ed232018-05-31 04:48:33 +0000365 def _HandleCommandObjcopy(self, args):
366 return command.CommandResult(return_code=0)
367
Simon Glassed098bb2014-09-05 19:00:13 -0600368 def _HandleCommandSize(self, args):
369 return command.CommandResult(return_code=0)
370
371 def _HandleCommand(self, **kwargs):
372 """Handle a command execution.
373
374 The command is in kwargs['pipe-list'], as a list of pipes, each a
375 list of commands. The command should be emulated as required for
376 testing purposes.
377
378 Returns:
379 A CommandResult object
380 """
381 pipe_list = kwargs['pipe_list']
Simon Glass8e959562014-09-05 19:00:20 -0600382 wc = False
Simon Glassed098bb2014-09-05 19:00:13 -0600383 if len(pipe_list) != 1:
Simon Glass8e959562014-09-05 19:00:20 -0600384 if pipe_list[1] == ['wc', '-l']:
385 wc = True
386 else:
Simon Glassc78ed662019-10-31 07:42:53 -0600387 print('invalid pipe', kwargs)
Simon Glass8e959562014-09-05 19:00:20 -0600388 sys.exit(1)
Simon Glassed098bb2014-09-05 19:00:13 -0600389 cmd = pipe_list[0][0]
390 args = pipe_list[0][1:]
Simon Glass8e959562014-09-05 19:00:20 -0600391 result = None
Simon Glassed098bb2014-09-05 19:00:13 -0600392 if cmd == 'git':
Simon Glass8e959562014-09-05 19:00:20 -0600393 result = self._HandleCommandGit(args)
Simon Glassed098bb2014-09-05 19:00:13 -0600394 elif cmd == './scripts/show-gnu-make':
395 return command.CommandResult(return_code=0, stdout='make')
Simon Glass8e959562014-09-05 19:00:20 -0600396 elif cmd.endswith('nm'):
Simon Glassed098bb2014-09-05 19:00:13 -0600397 return self._HandleCommandNm(args)
Simon Glass8e959562014-09-05 19:00:20 -0600398 elif cmd.endswith('objdump'):
Simon Glassed098bb2014-09-05 19:00:13 -0600399 return self._HandleCommandObjdump(args)
Alex Kiernanf07ed232018-05-31 04:48:33 +0000400 elif cmd.endswith('objcopy'):
401 return self._HandleCommandObjcopy(args)
Simon Glass8e959562014-09-05 19:00:20 -0600402 elif cmd.endswith( 'size'):
Simon Glassed098bb2014-09-05 19:00:13 -0600403 return self._HandleCommandSize(args)
404
Simon Glass8e959562014-09-05 19:00:20 -0600405 if not result:
406 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600407 print('unknown command', kwargs)
Simon Glass8e959562014-09-05 19:00:20 -0600408 sys.exit(1)
409
410 if wc:
411 result.stdout = len(result.stdout.splitlines())
412 return result
Simon Glassed098bb2014-09-05 19:00:13 -0600413
414 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
415 """Handle execution of 'make'
416
417 Args:
418 commit: Commit object that is being built
419 brd: Board object that is being built
420 stage: Stage that we are at (mrproper, config, build)
421 cwd: Directory where make should be run
422 args: Arguments to pass to make
Simon Glass840be732022-01-29 14:14:05 -0700423 kwargs: Arguments to pass to command.run_pipe()
Simon Glassed098bb2014-09-05 19:00:13 -0600424 """
Simon Glass8e959562014-09-05 19:00:20 -0600425 self._make_calls += 1
Simon Glass828d70d2023-02-21 12:40:29 -0700426 out_dir = ''
427 for arg in args:
428 if arg.startswith('O='):
429 out_dir = arg[2:]
Simon Glassed098bb2014-09-05 19:00:13 -0600430 if stage == 'mrproper':
431 return command.CommandResult(return_code=0)
432 elif stage == 'config':
Simon Glass828d70d2023-02-21 12:40:29 -0700433 fname = os.path.join(cwd or '', out_dir, '.config')
434 tools.write_file(fname, b'CONFIG_SOMETHING=1')
Simon Glassed098bb2014-09-05 19:00:13 -0600435 return command.CommandResult(return_code=0,
436 combined='Test configuration complete')
437 elif stage == 'build':
Simon Glass8e959562014-09-05 19:00:20 -0600438 stderr = ''
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600439 fname = os.path.join(cwd or '', out_dir, 'u-boot')
Simon Glass80025522022-01-29 14:14:04 -0700440 tools.write_file(fname, b'U-Boot')
Tom Rini93ebd462022-11-09 19:14:53 -0700441
442 # Handle missing blobs
443 if self._missing:
444 if 'BINMAN_ALLOW_MISSING=1' in args:
445 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
446Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
447
448Some images are invalid'''
449 else:
450 stderr = "binman: Filename 'fsp.bin' not found in input path"
451 elif type(commit) is not str:
Simon Glass8e959562014-09-05 19:00:20 -0600452 stderr = self._error.get((brd.target, commit.sequence))
Tom Rini93ebd462022-11-09 19:14:53 -0700453
Simon Glass8e959562014-09-05 19:00:20 -0600454 if stderr:
Tom Rini93ebd462022-11-09 19:14:53 -0700455 return command.CommandResult(return_code=2, stderr=stderr)
Simon Glassed098bb2014-09-05 19:00:13 -0600456 return command.CommandResult(return_code=0)
457
458 # Not handled, so abort
Simon Glassc78ed662019-10-31 07:42:53 -0600459 print('make', stage)
Simon Glassed098bb2014-09-05 19:00:13 -0600460 sys.exit(1)
461
Simon Glass8e959562014-09-05 19:00:20 -0600462 # Example function to print output lines
463 def print_lines(self, lines):
Simon Glassc78ed662019-10-31 07:42:53 -0600464 print(len(lines))
Simon Glass8e959562014-09-05 19:00:20 -0600465 for line in lines:
Simon Glassc78ed662019-10-31 07:42:53 -0600466 print(line)
Simon Glass02811582022-01-29 14:14:18 -0700467 #self.print_lines(terminal.get_print_test_lines())
Simon Glass8e959562014-09-05 19:00:20 -0600468
Simon Glasscbd36582014-09-05 19:00:16 -0600469 def testNoBoards(self):
470 """Test that buildman aborts when there are no boards"""
Simon Glass20751d62022-07-11 19:04:03 -0600471 self._boards = boards.Boards()
Simon Glasscbd36582014-09-05 19:00:16 -0600472 with self.assertRaises(SystemExit):
473 self._RunControl()
474
Simon Glassed098bb2014-09-05 19:00:13 -0600475 def testCurrentSource(self):
476 """Very simple test to invoke buildman on the current source"""
Simon Glass8e959562014-09-05 19:00:20 -0600477 self.setupToolchains();
Tom Rinie95eddc2019-10-07 17:17:36 -0400478 self._RunControl('-o', self._output_dir)
Simon Glass02811582022-01-29 14:14:18 -0700479 lines = terminal.get_print_test_lines()
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600480 self.assertIn('Building current source for %d boards' % len(BOARDS),
Simon Glass8e959562014-09-05 19:00:20 -0600481 lines[0].text)
482
483 def testBadBranch(self):
484 """Test that we can detect an invalid branch"""
485 with self.assertRaises(ValueError):
486 self._RunControl('-b', 'badbranch')
487
488 def testBadToolchain(self):
489 """Test that missing toolchains are detected"""
490 self.setupToolchains();
Tom Rinie95eddc2019-10-07 17:17:36 -0400491 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass02811582022-01-29 14:14:18 -0700492 lines = terminal.get_print_test_lines()
Simon Glass8e959562014-09-05 19:00:20 -0600493
494 # Buildman always builds the upstream commit as well
495 self.assertIn('Building %d commits for %d boards' %
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600496 (self._commits, len(BOARDS)), lines[0].text)
Simon Glass8e959562014-09-05 19:00:20 -0600497 self.assertEqual(self._builder.count, self._total_builds)
498
499 # Only sandbox should succeed, the others don't have toolchains
500 self.assertEqual(self._builder.fail,
501 self._total_builds - self._commits)
Simon Glasse4cd5062020-04-09 10:49:45 -0600502 self.assertEqual(ret_code, 100)
Simon Glass8e959562014-09-05 19:00:20 -0600503
504 for commit in range(self._commits):
Simon Glass127a2392022-07-11 19:04:02 -0600505 for brd in self._boards.get_list():
Simon Glass8132f982022-07-11 19:03:57 -0600506 if brd.arch != 'sandbox':
507 errfile = self._builder.GetErrFile(commit, brd.target)
Simon Glass8e959562014-09-05 19:00:20 -0600508 fd = open(errfile)
509 self.assertEqual(fd.readlines(),
Simon Glass8132f982022-07-11 19:03:57 -0600510 ['No tool chain for %s\n' % brd.arch])
Simon Glass8e959562014-09-05 19:00:20 -0600511 fd.close()
512
513 def testBranch(self):
514 """Test building a branch with all toolchains present"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400515 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600516 self.assertEqual(self._builder.count, self._total_builds)
517 self.assertEqual(self._builder.fail, 0)
518
519 def testCount(self):
520 """Test building a specific number of commitst"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400521 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600522 self.assertEqual(self._builder.count, 2 * len(BOARDS))
Simon Glass8e959562014-09-05 19:00:20 -0600523 self.assertEqual(self._builder.fail, 0)
Simon Glass6029af12020-04-09 15:08:51 -0600524 # Each board has a config, and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600525 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
Simon Glass8e959562014-09-05 19:00:20 -0600526
527 def testIncremental(self):
528 """Test building a branch twice - the second time should do nothing"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400529 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600530
531 # Each board has a mrproper, config, and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600532 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glass8e959562014-09-05 19:00:20 -0600533 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400534 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600535 self.assertEqual(self._make_calls, 0)
536 self.assertEqual(self._builder.count, self._total_builds)
537 self.assertEqual(self._builder.fail, 0)
538
539 def testForceBuild(self):
540 """The -f flag should force a rebuild"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400541 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
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, '-f', '-o', self._output_dir, clean_dir=False)
Simon Glass6029af12020-04-09 15:08:51 -0600544 # Each board has a config and one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600545 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glass8e959562014-09-05 19:00:20 -0600546
547 def testForceReconfigure(self):
548 """The -f flag should force a rebuild"""
Tom Rinie95eddc2019-10-07 17:17:36 -0400549 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
Simon Glass6029af12020-04-09 15:08:51 -0600550 # Each commit has a config and make
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600551 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
Simon Glass6029af12020-04-09 15:08:51 -0600552
Simon Glass6029af12020-04-09 15:08:51 -0600553 def testMrproper(self):
554 """The -f flag should force a rebuild"""
555 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
556 # Each board has a mkproper, config and then one make per commit
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600557 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
Simon Glass8e959562014-09-05 19:00:20 -0600558
559 def testErrors(self):
560 """Test handling of build errors"""
561 self._error['board2', 1] = 'fred\n'
Tom Rinie95eddc2019-10-07 17:17:36 -0400562 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass8e959562014-09-05 19:00:20 -0600563 self.assertEqual(self._builder.count, self._total_builds)
564 self.assertEqual(self._builder.fail, 1)
565
566 # Remove the error. This should have no effect since the commit will
567 # not be rebuilt
568 del self._error['board2', 1]
569 self._make_calls = 0
Tom Rinie95eddc2019-10-07 17:17:36 -0400570 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600571 self.assertEqual(self._builder.count, self._total_builds)
572 self.assertEqual(self._make_calls, 0)
573 self.assertEqual(self._builder.fail, 1)
574
575 # Now use the -F flag to force rebuild of the bad commit
Tom Rinie95eddc2019-10-07 17:17:36 -0400576 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
Simon Glass8e959562014-09-05 19:00:20 -0600577 self.assertEqual(self._builder.count, self._total_builds)
578 self.assertEqual(self._builder.fail, 0)
Simon Glass6029af12020-04-09 15:08:51 -0600579 self.assertEqual(self._make_calls, 2)
Simon Glass4aeceb92014-09-05 19:00:22 -0600580
581 def testBranchWithSlash(self):
582 """Test building a branch with a '/' in the name"""
583 self._test_branch = '/__dev/__testbranch'
584 self._RunControl('-b', self._test_branch, clean_dir=False)
585 self.assertEqual(self._builder.count, self._total_builds)
586 self.assertEqual(self._builder.fail, 0)
Lothar Waßmannce6df922018-04-08 05:14:11 -0600587
Simon Glassff48a212020-04-17 17:51:33 -0600588 def testEnvironment(self):
589 """Test that the done and environment files are written to out-env"""
590 self._RunControl('-o', self._output_dir)
591 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
592 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
593 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
594
Simon Glass93008e22021-04-11 16:27:28 +1200595 def testEnvironmentUnicode(self):
596 """Test there are no unicode errors when the env has non-ASCII chars"""
597 try:
598 varname = b'buildman_test_var'
599 os.environb[varname] = b'strange\x80chars'
600 self.assertEqual(0, 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 finally:
605 del os.environb[varname]
606
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600607 def testWorkInOutput(self):
608 """Test the -w option which should write directly to the output dir"""
Simon Glass20751d62022-07-11 19:04:03 -0600609 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600610 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600611 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600612 brds=board_list)
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600613 self.assertTrue(
614 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
Simon Glasse3c85ab2020-04-17 17:51:34 -0600615 self.assertTrue(
616 os.path.exists(os.path.join(self._output_dir, 'done')))
617 self.assertTrue(
618 os.path.exists(os.path.join(self._output_dir, 'out-env')))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600619
620 def testWorkInOutputFail(self):
621 """Test the -w option failures"""
622 with self.assertRaises(SystemExit) as e:
623 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
624 self.assertIn("single board", str(e.exception))
625 self.assertFalse(
626 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
627
Simon Glass20751d62022-07-11 19:04:03 -0600628 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600629 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600630 with self.assertRaises(SystemExit) as e:
631 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
Simon Glassd4c6c8a2022-07-11 19:03:58 -0600632 '-w', clean_dir=False, brds=board_list)
Simon Glassb6eb8cf2020-03-18 09:42:42 -0600633 self.assertIn("single commit", str(e.exception))
Simon Glassd9c98632020-04-17 17:51:32 -0600634
Simon Glass20751d62022-07-11 19:04:03 -0600635 board_list = boards.Boards()
Simon Glass127a2392022-07-11 19:04:02 -0600636 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd9c98632020-04-17 17:51:32 -0600637 with self.assertRaises(SystemExit) as e:
638 self._RunControl('-w', clean_dir=False)
639 self.assertIn("specify -o", str(e.exception))
Simon Glass9bf9a722021-04-11 16:27:27 +1200640
641 def testThreadExceptions(self):
642 """Test that exceptions in threads are reported"""
643 with test_util.capture_sys_output() as (stdout, stderr):
644 self.assertEqual(102, self._RunControl('-o', self._output_dir,
645 test_thread_exceptions=True))
Simon Glass9bac1672022-01-22 05:07:32 -0700646 self.assertIn(
647 'Thread exception (use -T0 to run without threads): test exception',
648 stdout.getvalue())
Tom Rini93ebd462022-11-09 19:14:53 -0700649
650 def testBlobs(self):
651 """Test handling of missing blobs"""
652 self._missing = True
653
654 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
655 errfile = os.path.join(board0_dir, 'err')
656 logfile = os.path.join(board0_dir, 'log')
657
658 # We expect failure when there are missing blobs
659 result = self._RunControl('board0', '-o', self._output_dir)
660 self.assertEqual(100, result)
661 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
662 self.assertTrue(os.path.exists(errfile))
663 self.assertIn(b"Filename 'fsp.bin' not found in input path",
664 tools.read_file(errfile))
665
666 def testBlobsAllowMissing(self):
667 """Allow missing blobs - still failure but a different exit code"""
668 self._missing = True
669 result = self._RunControl('board0', '-o', self._output_dir, '-M',
670 clean_dir=True)
671 self.assertEqual(101, result)
672 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
673 errfile = os.path.join(board0_dir, 'err')
674 self.assertTrue(os.path.exists(errfile))
675 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
676
677 def testBlobsWarning(self):
678 """Allow missing blobs and ignore warnings"""
679 self._missing = True
680 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
681 self.assertEqual(0, result)
682 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
683 errfile = os.path.join(board0_dir, 'err')
684 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
685
686 def testBlobSettings(self):
687 """Test with no settings"""
688 self.assertEqual(False,
689 control.get_allow_missing(False, False, 1, False))
690 self.assertEqual(True,
691 control.get_allow_missing(True, False, 1, False))
692 self.assertEqual(False,
693 control.get_allow_missing(True, True, 1, False))
694
695 def testBlobSettingsAlways(self):
696 """Test the 'always' policy"""
697 bsettings.SetItem('global', 'allow-missing', 'always')
698 self.assertEqual(True,
699 control.get_allow_missing(False, False, 1, False))
700 self.assertEqual(False,
701 control.get_allow_missing(False, True, 1, False))
702
703 def testBlobSettingsBranch(self):
704 """Test the 'branch' policy"""
705 bsettings.SetItem('global', 'allow-missing', 'branch')
706 self.assertEqual(False,
707 control.get_allow_missing(False, False, 1, False))
708 self.assertEqual(True,
709 control.get_allow_missing(False, False, 1, True))
710 self.assertEqual(False,
711 control.get_allow_missing(False, True, 1, True))
712
713 def testBlobSettingsMultiple(self):
714 """Test the 'multiple' policy"""
715 bsettings.SetItem('global', 'allow-missing', 'multiple')
716 self.assertEqual(False,
717 control.get_allow_missing(False, False, 1, False))
718 self.assertEqual(True,
719 control.get_allow_missing(False, False, 2, False))
720 self.assertEqual(False,
721 control.get_allow_missing(False, True, 2, False))
722
723 def testBlobSettingsBranchMultiple(self):
724 """Test the 'branch multiple' policy"""
725 bsettings.SetItem('global', 'allow-missing', 'branch multiple')
726 self.assertEqual(False,
727 control.get_allow_missing(False, False, 1, False))
728 self.assertEqual(True,
729 control.get_allow_missing(False, False, 1, True))
730 self.assertEqual(True,
731 control.get_allow_missing(False, False, 2, False))
732 self.assertEqual(True,
733 control.get_allow_missing(False, False, 2, True))
734 self.assertEqual(False,
735 control.get_allow_missing(False, True, 2, True))
Simon Glass1382b1d2023-02-21 12:40:27 -0700736
Simon Glassf6bfcca2023-02-21 12:40:28 -0700737 def check_command(self, *extra_args):
738 """Run a command with the extra arguments and return the commands used
739
740 Args:
741 extra_args (list of str): List of extra arguments
742
743 Returns:
744 list of str: Lines returned in the out-cmd file
745 """
746 self._RunControl('-o', self._output_dir, *extra_args)
Simon Glass1382b1d2023-02-21 12:40:27 -0700747 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
748 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
749 cmd_fname = os.path.join(board0_dir, 'out-cmd')
750 self.assertTrue(os.path.exists(cmd_fname))
751 data = tools.read_file(cmd_fname)
Simon Glass828d70d2023-02-21 12:40:29 -0700752
753 config_fname = os.path.join(board0_dir, '.config')
754 self.assertTrue(os.path.exists(config_fname))
755 cfg_data = tools.read_file(config_fname)
756
757 return data.splitlines(), cfg_data
Simon Glassf6bfcca2023-02-21 12:40:28 -0700758
759 def testCmdFile(self):
760 """Test that the -cmd-out file is produced"""
Simon Glass828d70d2023-02-21 12:40:29 -0700761 lines = self.check_command()[0]
Simon Glass1382b1d2023-02-21 12:40:27 -0700762 self.assertEqual(2, len(lines))
763 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
764 self.assertRegex(lines[0], b'make O=/.*-s.*')
Simon Glassf6bfcca2023-02-21 12:40:28 -0700765
766 def testNoLto(self):
767 """Test that the --no-lto flag works"""
Simon Glass828d70d2023-02-21 12:40:29 -0700768 lines = self.check_command('-L')[0]
Simon Glassf6bfcca2023-02-21 12:40:28 -0700769 self.assertIn(b'NO_LTO=1', lines[0])
770
Simon Glass828d70d2023-02-21 12:40:29 -0700771 def testReproducible(self):
772 """Test that the -r flag works"""
773 lines, cfg_data = self.check_command('-r')
774 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
775
776 # We should see CONFIG_LOCALVERSION_AUTO unset
777 self.assertEqual(b'''CONFIG_SOMETHING=1
778# CONFIG_LOCALVERSION_AUTO is not set
779''', cfg_data)
780
781 with test_util.capture_sys_output() as (stdout, stderr):
782 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
783 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
784
785 # We should see CONFIG_LOCALVERSION_AUTO unset
786 self.assertEqual(b'''CONFIG_SOMETHING=1
787CONFIG_LOCALVERSION=y
788''', cfg_data)
789 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())
Simon Glasscef26b82023-07-19 17:48:15 -0600790
791 def test_scan_defconfigs(self):
792 """Test scanning the defconfigs to obtain all the boards"""
793 src = self._git_dir
794
795 # Scan the test directory which contains a Kconfig and some *_defconfig
796 # files
Simon Glass07a95d82023-07-19 17:48:21 -0600797 params, warnings = self._boards.scan_defconfigs(src, src)
Simon Glasscef26b82023-07-19 17:48:15 -0600798
799 # We should get two boards
800 self.assertEquals(2, len(params))
Simon Glass07a95d82023-07-19 17:48:21 -0600801 self.assertFalse(warnings)
Simon Glasscef26b82023-07-19 17:48:15 -0600802 first = 0 if params[0]['target'] == 'board0' else 1
803 board0 = params[first]
804 board2 = params[1 - first]
805
806 self.assertEquals('arm', board0['arch'])
807 self.assertEquals('armv7', board0['cpu'])
808 self.assertEquals('-', board0['soc'])
809 self.assertEquals('Tester', board0['vendor'])
810 self.assertEquals('ARM Board 0', board0['board'])
811 self.assertEquals('config0', board0['config'])
812 self.assertEquals('board0', board0['target'])
813
814 self.assertEquals('powerpc', board2['arch'])
815 self.assertEquals('ppc', board2['cpu'])
816 self.assertEquals('mpc85xx', board2['soc'])
817 self.assertEquals('Tester', board2['vendor'])
818 self.assertEquals('PowerPC board 1', board2['board'])
819 self.assertEquals('config2', board2['config'])
820 self.assertEquals('board2', board2['target'])
821
Simon Glassada78d42023-07-19 17:48:16 -0600822 def test_output_is_new(self):
823 """Test detecting new changes to Kconfig"""
824 base = self._base_dir
825 src = self._git_dir
826 config_dir = os.path.join(src, 'configs')
827 delay = 0.02
828
829 # Create a boards.cfg file
830 boards_cfg = os.path.join(base, 'boards.cfg')
831 content = b'''#
832# List of boards
833# Automatically generated by buildman/boards.py: don't edit
834#
835# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
836
837Active aarch64 armv8 - armltd corstone1000 board0
838Active aarch64 armv8 - armltd total_compute board2
839'''
840 # Check missing file
841 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
842
843 # Check that the board.cfg file is newer
844 time.sleep(delay)
845 tools.write_file(boards_cfg, content)
846 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
847
848 # Touch the Kconfig files after a show delay to avoid a race
849 time.sleep(delay)
850 Path(os.path.join(src, 'Kconfig')).touch()
851 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
852 Path(boards_cfg).touch()
853 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
854
855 # Touch a different Kconfig file
856 time.sleep(delay)
857 Path(os.path.join(src, 'Kconfig.something')).touch()
858 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
859 Path(boards_cfg).touch()
860 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
861
862 # Touch a MAINTAINERS file
863 time.sleep(delay)
864 Path(os.path.join(src, 'MAINTAINERS')).touch()
865 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
866
867 Path(boards_cfg).touch()
868 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
869
870 # Touch a defconfig file
871 time.sleep(delay)
872 Path(os.path.join(config_dir, 'board0_defconfig')).touch()
873 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
874 Path(boards_cfg).touch()
875 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
876
877 # Remove a board and check that the board.cfg file is now older
878 Path(os.path.join(config_dir, 'board0_defconfig')).unlink()
879 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
880
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600881 def test_maintainers(self):
882 """Test detecting boards without a MAINTAINERS entry"""
883 src = self._git_dir
884 main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
885 other = os.path.join(src, 'boards', 'board2', 'MAINTAINERS')
Simon Glass07a95d82023-07-19 17:48:21 -0600886 kc_file = os.path.join(src, 'Kconfig')
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600887 config_dir = os.path.join(src, 'configs')
888 params_list, warnings = self._boards.build_board_list(config_dir, src)
889
890 # There should be two boards no warnings
891 self.assertEquals(2, len(params_list))
892 self.assertFalse(warnings)
893
894 # Set an invalid status line in the file
895 orig_data = tools.read_file(main, binary=False)
896 lines = ['S: Other\n' if line.startswith('S:') else line
897 for line in orig_data.splitlines(keepends=True)]
898 tools.write_file(main, ''.join(lines), binary=False)
899 params_list, warnings = self._boards.build_board_list(config_dir, src)
900 self.assertEquals(2, len(params_list))
901 params = params_list[0]
902 if params['target'] == 'board2':
903 params = params_list[1]
904 self.assertEquals('-', params['status'])
905 self.assertEquals(["WARNING: Other: unknown status for 'board0'"],
906 warnings)
907
908 # Remove the status line (S:) from a file
909 lines = [line for line in orig_data.splitlines(keepends=True)
910 if not line.startswith('S:')]
911 tools.write_file(main, ''.join(lines), binary=False)
912 params_list, warnings = self._boards.build_board_list(config_dir, src)
913 self.assertEquals(2, len(params_list))
914 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
915
916 # Remove the configs/ line (F:) from a file - this is the last line
917 data = ''.join(orig_data.splitlines(keepends=True)[:-1])
918 tools.write_file(main, data, binary=False)
919 params_list, warnings = self._boards.build_board_list(config_dir, src)
920 self.assertEquals(2, len(params_list))
921 self.assertEquals(
Simon Glass263b5982023-07-19 17:48:25 -0600922 ["WARNING: no maintainers for 'board0'",
923 'WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 4',
924 ], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600925
Simon Glasse6acab52023-07-19 17:48:26 -0600926 # Mark a board as orphaned - this should give a warning
927 lines = ['S: Orphaned' if line.startswith('S') else line
928 for line in orig_data.splitlines(keepends=True)]
929 tools.write_file(main, ''.join(lines), binary=False)
930 params_list, warnings = self._boards.build_board_list(config_dir, src)
931 self.assertEquals(2, len(params_list))
932 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
933
934 # Change the maintainer to '-' - this should give a warning
935 lines = ['M: -' if line.startswith('M') else line
936 for line in orig_data.splitlines(keepends=True)]
937 tools.write_file(main, ''.join(lines), binary=False)
938 params_list, warnings = self._boards.build_board_list(config_dir, src)
939 self.assertEquals(2, len(params_list))
940 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
941
942 # Remove the maintainer line (M:) from a file
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600943 lines = [line for line in orig_data.splitlines(keepends=True)
944 if not line.startswith('M:')]
945 tools.write_file(main, ''.join(lines), binary=False)
946 params_list, warnings = self._boards.build_board_list(config_dir, src)
947 self.assertEquals(2, len(params_list))
Simon Glasse6acab52023-07-19 17:48:26 -0600948 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600949
950 # Move the contents of the second file into this one, removing the
951 # second file, to check multiple records in a single file.
Simon Glass060ee972023-07-19 17:48:23 -0600952 both_data = orig_data + tools.read_file(other, binary=False)
953 tools.write_file(main, both_data, binary=False)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600954 os.remove(other)
955 params_list, warnings = self._boards.build_board_list(config_dir, src)
956 self.assertEquals(2, len(params_list))
957 self.assertFalse(warnings)
958
Simon Glass9b828ec2023-07-19 17:48:19 -0600959 # Add another record, this should be ignored with a warning
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600960 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
Simon Glass060ee972023-07-19 17:48:23 -0600961 tools.write_file(main, both_data + extra, binary=False)
Simon Glassc0b6fcc2023-07-19 17:48:17 -0600962 params_list, warnings = self._boards.build_board_list(config_dir, src)
963 self.assertEquals(2, len(params_list))
Simon Glass9b828ec2023-07-19 17:48:19 -0600964 self.assertEquals(
965 ['WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 16'],
966 warnings)
Simon Glass07a95d82023-07-19 17:48:21 -0600967
968 # Add another TARGET to the Kconfig
Simon Glass060ee972023-07-19 17:48:23 -0600969 tools.write_file(main, both_data, binary=False)
Simon Glass061499b2023-07-19 17:48:22 -0600970 orig_kc_data = tools.read_file(kc_file)
Simon Glass07a95d82023-07-19 17:48:21 -0600971 extra = (b'''
972if TARGET_BOARD2
973config TARGET_OTHER
974\tbool "other"
975\tdefault y
976endif
977''')
Simon Glass061499b2023-07-19 17:48:22 -0600978 tools.write_file(kc_file, orig_kc_data + extra)
Simon Glass5e728d42023-07-19 17:48:27 -0600979 params_list, warnings = self._boards.build_board_list(config_dir, src,
980 warn_targets=True)
Simon Glass07a95d82023-07-19 17:48:21 -0600981 self.assertEquals(2, len(params_list))
982 self.assertEquals(
983 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
984 warnings)
Simon Glass061499b2023-07-19 17:48:22 -0600985
986 # Remove the TARGET_BOARD0 Kconfig option
987 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
988 for line in orig_kc_data.splitlines(keepends=True)]
989 tools.write_file(kc_file, b''.join(lines))
Simon Glass5e728d42023-07-19 17:48:27 -0600990 params_list, warnings = self._boards.build_board_list(config_dir, src,
991 warn_targets=True)
Simon Glass061499b2023-07-19 17:48:22 -0600992 self.assertEquals(2, len(params_list))
993 self.assertEquals(
994 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
995 warnings)
Simon Glass060ee972023-07-19 17:48:23 -0600996 tools.write_file(kc_file, orig_kc_data)
997
998 # Replace the last F: line of board 2 with an N: line
999 data = ''.join(both_data.splitlines(keepends=True)[:-1])
1000 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
1001 params_list, warnings = self._boards.build_board_list(config_dir, src)
1002 self.assertEquals(2, len(params_list))
1003 self.assertFalse(warnings)
1004