blob: e6cdebdbd194f9de0bcaee3adddf3c0024e2f396 [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 Glass19133b72022-01-22 05:07:31 -0700189 bsettings.Setup(None)
190 bsettings.AddFile(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)
255 options, args = cmdline.ParseArgs()
Simon Glassa1431e62023-07-19 17:48:28 -0600256 if brds == False:
257 brds = self._boards
Simon Glassdfb7e932014-09-05 19:00:20 -0600258 result = control.DoBuildman(options, args, toolchains=self._toolchains,
Simon Glassa1431e62023-07-19 17:48:28 -0600259 make_func=self._HandleMake, brds=brds, clean_dir=clean_dir,
Simon Glass8116c782021-04-11 16:27:27 +1200260 test_thread_exceptions=test_thread_exceptions)
Simon Glassa1431e62023-07-19 17:48:28 -0600261 if get_builder:
262 self._builder = control.builder
Simon Glassdfb7e932014-09-05 19:00:20 -0600263 return result
Simon Glassd4144e42014-09-05 19:00:13 -0600264
265 def testFullHelp(self):
266 command.test_result = None
267 result = self._RunBuildman('-H')
Simon Glass74df4912022-11-09 19:14:43 -0700268 help_file = os.path.join(self._buildman_dir, 'README.rst')
Tom Rini3759df02018-01-16 15:29:50 -0500269 # Remove possible extraneous strings
270 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
271 gothelp = result.stdout.replace(extra, '')
272 self.assertEqual(len(gothelp), os.path.getsize(help_file))
Simon Glassd4144e42014-09-05 19:00:13 -0600273 self.assertEqual(0, len(result.stderr))
274 self.assertEqual(0, result.return_code)
275
276 def testHelp(self):
277 command.test_result = None
278 result = self._RunBuildman('-h')
Simon Glass74df4912022-11-09 19:14:43 -0700279 help_file = os.path.join(self._buildman_dir, 'README.rst')
Simon Glassd4144e42014-09-05 19:00:13 -0600280 self.assertTrue(len(result.stdout) > 1000)
281 self.assertEqual(0, len(result.stderr))
282 self.assertEqual(0, result.return_code)
283
284 def testGitSetup(self):
285 """Test gitutils.Setup(), from outside the module itself"""
286 command.test_result = command.CommandResult(return_code=1)
Simon Glass0157b182022-01-29 14:14:11 -0700287 gitutil.setup()
Simon Glassd4144e42014-09-05 19:00:13 -0600288 self.assertEqual(gitutil.use_no_decorate, False)
289
290 command.test_result = command.CommandResult(return_code=0)
Simon Glass0157b182022-01-29 14:14:11 -0700291 gitutil.setup()
Simon Glassd4144e42014-09-05 19:00:13 -0600292 self.assertEqual(gitutil.use_no_decorate, True)
293
294 def _HandleCommandGitLog(self, args):
Simon Glassd4c85722016-03-12 18:50:31 -0700295 if args[-1] == '--':
296 args = args[:-1]
Simon Glassd4144e42014-09-05 19:00:13 -0600297 if '-n0' in args:
298 return command.CommandResult(return_code=0)
Simon Glassf7582ce2014-09-05 19:00:22 -0600299 elif args[-1] == 'upstream/master..%s' % self._test_branch:
Simon Glassdfb7e932014-09-05 19:00:20 -0600300 return command.CommandResult(return_code=0, stdout=commit_shortlog)
301 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
Simon Glassf7582ce2014-09-05 19:00:22 -0600302 if args[-1] == self._test_branch:
Simon Glassdfb7e932014-09-05 19:00:20 -0600303 count = int(args[3][2:])
304 return command.CommandResult(return_code=0,
305 stdout=''.join(commit_log[:count]))
Simon Glassd4144e42014-09-05 19:00:13 -0600306
307 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600308 print('git log', args)
Simon Glassd4144e42014-09-05 19:00:13 -0600309 sys.exit(1)
310
Simon Glassdfb7e932014-09-05 19:00:20 -0600311 def _HandleCommandGitConfig(self, args):
312 config = args[0]
313 if config == 'sendemail.aliasesfile':
314 return command.CommandResult(return_code=0)
315 elif config.startswith('branch.badbranch'):
316 return command.CommandResult(return_code=1)
Simon Glassf7582ce2014-09-05 19:00:22 -0600317 elif config == 'branch.%s.remote' % self._test_branch:
Simon Glassdfb7e932014-09-05 19:00:20 -0600318 return command.CommandResult(return_code=0, stdout='upstream\n')
Simon Glassf7582ce2014-09-05 19:00:22 -0600319 elif config == 'branch.%s.merge' % self._test_branch:
Simon Glassdfb7e932014-09-05 19:00:20 -0600320 return command.CommandResult(return_code=0,
321 stdout='refs/heads/master\n')
322
323 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600324 print('git config', args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600325 sys.exit(1)
326
Simon Glassd4144e42014-09-05 19:00:13 -0600327 def _HandleCommandGit(self, in_args):
328 """Handle execution of a git command
329
330 This uses a hacked-up parser.
331
332 Args:
333 in_args: Arguments after 'git' from the command line
334 """
335 git_args = [] # Top-level arguments to git itself
336 sub_cmd = None # Git sub-command selected
337 args = [] # Arguments to the git sub-command
338 for arg in in_args:
339 if sub_cmd:
340 args.append(arg)
341 elif arg[0] == '-':
342 git_args.append(arg)
343 else:
Simon Glassdfb7e932014-09-05 19:00:20 -0600344 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
345 git_args.append(arg)
346 else:
347 sub_cmd = arg
Simon Glassd4144e42014-09-05 19:00:13 -0600348 if sub_cmd == 'config':
Simon Glassdfb7e932014-09-05 19:00:20 -0600349 return self._HandleCommandGitConfig(args)
Simon Glassd4144e42014-09-05 19:00:13 -0600350 elif sub_cmd == 'log':
351 return self._HandleCommandGitLog(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600352 elif sub_cmd == 'clone':
353 return command.CommandResult(return_code=0)
354 elif sub_cmd == 'checkout':
355 return command.CommandResult(return_code=0)
Alper Nebi Yasak76de29f2020-09-03 15:51:03 +0300356 elif sub_cmd == 'worktree':
357 return command.CommandResult(return_code=0)
Simon Glassd4144e42014-09-05 19:00:13 -0600358
359 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600360 print('git', git_args, sub_cmd, args)
Simon Glassd4144e42014-09-05 19:00:13 -0600361 sys.exit(1)
362
363 def _HandleCommandNm(self, args):
364 return command.CommandResult(return_code=0)
365
366 def _HandleCommandObjdump(self, args):
367 return command.CommandResult(return_code=0)
368
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000369 def _HandleCommandObjcopy(self, args):
370 return command.CommandResult(return_code=0)
371
Simon Glassd4144e42014-09-05 19:00:13 -0600372 def _HandleCommandSize(self, args):
373 return command.CommandResult(return_code=0)
374
375 def _HandleCommand(self, **kwargs):
376 """Handle a command execution.
377
378 The command is in kwargs['pipe-list'], as a list of pipes, each a
379 list of commands. The command should be emulated as required for
380 testing purposes.
381
382 Returns:
383 A CommandResult object
384 """
385 pipe_list = kwargs['pipe_list']
Simon Glassdfb7e932014-09-05 19:00:20 -0600386 wc = False
Simon Glassd4144e42014-09-05 19:00:13 -0600387 if len(pipe_list) != 1:
Simon Glassdfb7e932014-09-05 19:00:20 -0600388 if pipe_list[1] == ['wc', '-l']:
389 wc = True
390 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600391 print('invalid pipe', kwargs)
Simon Glassdfb7e932014-09-05 19:00:20 -0600392 sys.exit(1)
Simon Glassd4144e42014-09-05 19:00:13 -0600393 cmd = pipe_list[0][0]
394 args = pipe_list[0][1:]
Simon Glassdfb7e932014-09-05 19:00:20 -0600395 result = None
Simon Glassd4144e42014-09-05 19:00:13 -0600396 if cmd == 'git':
Simon Glassdfb7e932014-09-05 19:00:20 -0600397 result = self._HandleCommandGit(args)
Simon Glassd4144e42014-09-05 19:00:13 -0600398 elif cmd == './scripts/show-gnu-make':
399 return command.CommandResult(return_code=0, stdout='make')
Simon Glassdfb7e932014-09-05 19:00:20 -0600400 elif cmd.endswith('nm'):
Simon Glassd4144e42014-09-05 19:00:13 -0600401 return self._HandleCommandNm(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600402 elif cmd.endswith('objdump'):
Simon Glassd4144e42014-09-05 19:00:13 -0600403 return self._HandleCommandObjdump(args)
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000404 elif cmd.endswith('objcopy'):
405 return self._HandleCommandObjcopy(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600406 elif cmd.endswith( 'size'):
Simon Glassd4144e42014-09-05 19:00:13 -0600407 return self._HandleCommandSize(args)
408
Simon Glassdfb7e932014-09-05 19:00:20 -0600409 if not result:
410 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600411 print('unknown command', kwargs)
Simon Glassdfb7e932014-09-05 19:00:20 -0600412 sys.exit(1)
413
414 if wc:
415 result.stdout = len(result.stdout.splitlines())
416 return result
Simon Glassd4144e42014-09-05 19:00:13 -0600417
418 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
419 """Handle execution of 'make'
420
421 Args:
422 commit: Commit object that is being built
423 brd: Board object that is being built
424 stage: Stage that we are at (mrproper, config, build)
425 cwd: Directory where make should be run
426 args: Arguments to pass to make
Simon Glassd9800692022-01-29 14:14:05 -0700427 kwargs: Arguments to pass to command.run_pipe()
Simon Glassd4144e42014-09-05 19:00:13 -0600428 """
Simon Glassdfb7e932014-09-05 19:00:20 -0600429 self._make_calls += 1
Simon Glassbfb708a2023-02-21 12:40:29 -0700430 out_dir = ''
431 for arg in args:
432 if arg.startswith('O='):
433 out_dir = arg[2:]
Simon Glassd4144e42014-09-05 19:00:13 -0600434 if stage == 'mrproper':
435 return command.CommandResult(return_code=0)
436 elif stage == 'config':
Simon Glassbfb708a2023-02-21 12:40:29 -0700437 fname = os.path.join(cwd or '', out_dir, '.config')
438 tools.write_file(fname, b'CONFIG_SOMETHING=1')
Simon Glassd4144e42014-09-05 19:00:13 -0600439 return command.CommandResult(return_code=0,
440 combined='Test configuration complete')
441 elif stage == 'build':
Simon Glassdfb7e932014-09-05 19:00:20 -0600442 stderr = ''
Simon Glassd829f122020-03-18 09:42:42 -0600443 fname = os.path.join(cwd or '', out_dir, 'u-boot')
Simon Glassc1aa66e2022-01-29 14:14:04 -0700444 tools.write_file(fname, b'U-Boot')
Tom Rinid7713ad2022-11-09 19:14:53 -0700445
446 # Handle missing blobs
447 if self._missing:
448 if 'BINMAN_ALLOW_MISSING=1' in args:
449 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
450Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
451
452Some images are invalid'''
453 else:
454 stderr = "binman: Filename 'fsp.bin' not found in input path"
455 elif type(commit) is not str:
Simon Glassdfb7e932014-09-05 19:00:20 -0600456 stderr = self._error.get((brd.target, commit.sequence))
Tom Rinid7713ad2022-11-09 19:14:53 -0700457
Simon Glassdfb7e932014-09-05 19:00:20 -0600458 if stderr:
Tom Rinid7713ad2022-11-09 19:14:53 -0700459 return command.CommandResult(return_code=2, stderr=stderr)
Simon Glassd4144e42014-09-05 19:00:13 -0600460 return command.CommandResult(return_code=0)
461
462 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600463 print('make', stage)
Simon Glassd4144e42014-09-05 19:00:13 -0600464 sys.exit(1)
465
Simon Glassdfb7e932014-09-05 19:00:20 -0600466 # Example function to print output lines
467 def print_lines(self, lines):
Simon Glassc05aa032019-10-31 07:42:53 -0600468 print(len(lines))
Simon Glassdfb7e932014-09-05 19:00:20 -0600469 for line in lines:
Simon Glassc05aa032019-10-31 07:42:53 -0600470 print(line)
Simon Glass098b10f2022-01-29 14:14:18 -0700471 #self.print_lines(terminal.get_print_test_lines())
Simon Glassdfb7e932014-09-05 19:00:20 -0600472
Simon Glass823e60b2014-09-05 19:00:16 -0600473 def testNoBoards(self):
474 """Test that buildman aborts when there are no boards"""
Simon Glassc52bd222022-07-11 19:04:03 -0600475 self._boards = boards.Boards()
Simon Glass823e60b2014-09-05 19:00:16 -0600476 with self.assertRaises(SystemExit):
477 self._RunControl()
478
Simon Glassd4144e42014-09-05 19:00:13 -0600479 def testCurrentSource(self):
480 """Very simple test to invoke buildman on the current source"""
Simon Glassdfb7e932014-09-05 19:00:20 -0600481 self.setupToolchains();
Tom Riniaae62582019-10-07 17:17:36 -0400482 self._RunControl('-o', self._output_dir)
Simon Glass098b10f2022-01-29 14:14:18 -0700483 lines = terminal.get_print_test_lines()
Simon Glass938fa372022-07-11 19:03:58 -0600484 self.assertIn('Building current source for %d boards' % len(BOARDS),
Simon Glassdfb7e932014-09-05 19:00:20 -0600485 lines[0].text)
486
487 def testBadBranch(self):
488 """Test that we can detect an invalid branch"""
489 with self.assertRaises(ValueError):
490 self._RunControl('-b', 'badbranch')
491
492 def testBadToolchain(self):
493 """Test that missing toolchains are detected"""
494 self.setupToolchains();
Tom Riniaae62582019-10-07 17:17:36 -0400495 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass098b10f2022-01-29 14:14:18 -0700496 lines = terminal.get_print_test_lines()
Simon Glassdfb7e932014-09-05 19:00:20 -0600497
498 # Buildman always builds the upstream commit as well
499 self.assertIn('Building %d commits for %d boards' %
Simon Glass938fa372022-07-11 19:03:58 -0600500 (self._commits, len(BOARDS)), lines[0].text)
Simon Glassdfb7e932014-09-05 19:00:20 -0600501 self.assertEqual(self._builder.count, self._total_builds)
502
503 # Only sandbox should succeed, the others don't have toolchains
504 self.assertEqual(self._builder.fail,
505 self._total_builds - self._commits)
Simon Glassb1e5e6d2020-04-09 10:49:45 -0600506 self.assertEqual(ret_code, 100)
Simon Glassdfb7e932014-09-05 19:00:20 -0600507
508 for commit in range(self._commits):
Simon Glass6014db62022-07-11 19:04:02 -0600509 for brd in self._boards.get_list():
Simon Glassf4ed4702022-07-11 19:03:57 -0600510 if brd.arch != 'sandbox':
511 errfile = self._builder.GetErrFile(commit, brd.target)
Simon Glassdfb7e932014-09-05 19:00:20 -0600512 fd = open(errfile)
513 self.assertEqual(fd.readlines(),
Simon Glassf4ed4702022-07-11 19:03:57 -0600514 ['No tool chain for %s\n' % brd.arch])
Simon Glassdfb7e932014-09-05 19:00:20 -0600515 fd.close()
516
517 def testBranch(self):
518 """Test building a branch with all toolchains present"""
Tom Riniaae62582019-10-07 17:17:36 -0400519 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600520 self.assertEqual(self._builder.count, self._total_builds)
521 self.assertEqual(self._builder.fail, 0)
522
523 def testCount(self):
524 """Test building a specific number of commitst"""
Tom Riniaae62582019-10-07 17:17:36 -0400525 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
Simon Glass938fa372022-07-11 19:03:58 -0600526 self.assertEqual(self._builder.count, 2 * len(BOARDS))
Simon Glassdfb7e932014-09-05 19:00:20 -0600527 self.assertEqual(self._builder.fail, 0)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600528 # Each board has a config, and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600529 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
Simon Glassdfb7e932014-09-05 19:00:20 -0600530
531 def testIncremental(self):
532 """Test building a branch twice - the second time should do nothing"""
Tom Riniaae62582019-10-07 17:17:36 -0400533 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600534
535 # Each board has a mrproper, config, and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600536 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glassdfb7e932014-09-05 19:00:20 -0600537 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400538 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600539 self.assertEqual(self._make_calls, 0)
540 self.assertEqual(self._builder.count, self._total_builds)
541 self.assertEqual(self._builder.fail, 0)
542
543 def testForceBuild(self):
544 """The -f flag should force a rebuild"""
Tom Riniaae62582019-10-07 17:17:36 -0400545 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600546 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400547 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600548 # Each board has a config and one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600549 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glassdfb7e932014-09-05 19:00:20 -0600550
551 def testForceReconfigure(self):
552 """The -f flag should force a rebuild"""
Tom Riniaae62582019-10-07 17:17:36 -0400553 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600554 # Each commit has a config and make
Simon Glass938fa372022-07-11 19:03:58 -0600555 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600556
Simon Glasseb70a2c2020-04-09 15:08:51 -0600557 def testMrproper(self):
558 """The -f flag should force a rebuild"""
559 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
560 # Each board has a mkproper, config and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600561 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
Simon Glassdfb7e932014-09-05 19:00:20 -0600562
563 def testErrors(self):
564 """Test handling of build errors"""
565 self._error['board2', 1] = 'fred\n'
Tom Riniaae62582019-10-07 17:17:36 -0400566 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600567 self.assertEqual(self._builder.count, self._total_builds)
568 self.assertEqual(self._builder.fail, 1)
569
570 # Remove the error. This should have no effect since the commit will
571 # not be rebuilt
572 del self._error['board2', 1]
573 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400574 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600575 self.assertEqual(self._builder.count, self._total_builds)
576 self.assertEqual(self._make_calls, 0)
577 self.assertEqual(self._builder.fail, 1)
578
579 # Now use the -F flag to force rebuild of the bad commit
Tom Riniaae62582019-10-07 17:17:36 -0400580 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600581 self.assertEqual(self._builder.count, self._total_builds)
582 self.assertEqual(self._builder.fail, 0)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600583 self.assertEqual(self._make_calls, 2)
Simon Glassf7582ce2014-09-05 19:00:22 -0600584
585 def testBranchWithSlash(self):
586 """Test building a branch with a '/' in the name"""
587 self._test_branch = '/__dev/__testbranch'
588 self._RunControl('-b', self._test_branch, clean_dir=False)
589 self.assertEqual(self._builder.count, self._total_builds)
590 self.assertEqual(self._builder.fail, 0)
Lothar Waßmann409fc022018-04-08 05:14:11 -0600591
Simon Glass166a98a2020-04-17 17:51:33 -0600592 def testEnvironment(self):
593 """Test that the done and environment files are written to out-env"""
594 self._RunControl('-o', self._output_dir)
595 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
596 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
597 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
598
Simon Glassf1a83ab2021-04-11 16:27:28 +1200599 def testEnvironmentUnicode(self):
600 """Test there are no unicode errors when the env has non-ASCII chars"""
601 try:
602 varname = b'buildman_test_var'
603 os.environb[varname] = b'strange\x80chars'
604 self.assertEqual(0, self._RunControl('-o', self._output_dir))
605 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
606 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
607 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
608 finally:
609 del os.environb[varname]
610
Simon Glassd829f122020-03-18 09:42:42 -0600611 def testWorkInOutput(self):
612 """Test the -w option which should write directly to the output dir"""
Simon Glassc52bd222022-07-11 19:04:03 -0600613 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600614 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd829f122020-03-18 09:42:42 -0600615 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
Simon Glass938fa372022-07-11 19:03:58 -0600616 brds=board_list)
Simon Glassd829f122020-03-18 09:42:42 -0600617 self.assertTrue(
618 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
Simon Glass60b285f2020-04-17 17:51:34 -0600619 self.assertTrue(
620 os.path.exists(os.path.join(self._output_dir, 'done')))
621 self.assertTrue(
622 os.path.exists(os.path.join(self._output_dir, 'out-env')))
Simon Glassd829f122020-03-18 09:42:42 -0600623
624 def testWorkInOutputFail(self):
625 """Test the -w option failures"""
626 with self.assertRaises(SystemExit) as e:
627 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
628 self.assertIn("single board", str(e.exception))
629 self.assertFalse(
630 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
631
Simon Glassc52bd222022-07-11 19:04:03 -0600632 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600633 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd829f122020-03-18 09:42:42 -0600634 with self.assertRaises(SystemExit) as e:
635 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
Simon Glass938fa372022-07-11 19:03:58 -0600636 '-w', clean_dir=False, brds=board_list)
Simon Glassd829f122020-03-18 09:42:42 -0600637 self.assertIn("single commit", str(e.exception))
Simon Glass88daaef2020-04-17 17:51:32 -0600638
Simon Glassc52bd222022-07-11 19:04:03 -0600639 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600640 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glass88daaef2020-04-17 17:51:32 -0600641 with self.assertRaises(SystemExit) as e:
642 self._RunControl('-w', clean_dir=False)
643 self.assertIn("specify -o", str(e.exception))
Simon Glass8116c782021-04-11 16:27:27 +1200644
645 def testThreadExceptions(self):
646 """Test that exceptions in threads are reported"""
647 with test_util.capture_sys_output() as (stdout, stderr):
648 self.assertEqual(102, self._RunControl('-o', self._output_dir,
649 test_thread_exceptions=True))
Simon Glass8ca09312022-01-22 05:07:32 -0700650 self.assertIn(
651 'Thread exception (use -T0 to run without threads): test exception',
652 stdout.getvalue())
Tom Rinid7713ad2022-11-09 19:14:53 -0700653
654 def testBlobs(self):
655 """Test handling of missing blobs"""
656 self._missing = True
657
658 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
659 errfile = os.path.join(board0_dir, 'err')
660 logfile = os.path.join(board0_dir, 'log')
661
662 # We expect failure when there are missing blobs
663 result = self._RunControl('board0', '-o', self._output_dir)
664 self.assertEqual(100, result)
665 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
666 self.assertTrue(os.path.exists(errfile))
667 self.assertIn(b"Filename 'fsp.bin' not found in input path",
668 tools.read_file(errfile))
669
670 def testBlobsAllowMissing(self):
671 """Allow missing blobs - still failure but a different exit code"""
672 self._missing = True
673 result = self._RunControl('board0', '-o', self._output_dir, '-M',
674 clean_dir=True)
675 self.assertEqual(101, result)
676 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
677 errfile = os.path.join(board0_dir, 'err')
678 self.assertTrue(os.path.exists(errfile))
679 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
680
681 def testBlobsWarning(self):
682 """Allow missing blobs and ignore warnings"""
683 self._missing = True
684 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
685 self.assertEqual(0, result)
686 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
687 errfile = os.path.join(board0_dir, 'err')
688 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
689
690 def testBlobSettings(self):
691 """Test with no settings"""
692 self.assertEqual(False,
693 control.get_allow_missing(False, False, 1, False))
694 self.assertEqual(True,
695 control.get_allow_missing(True, False, 1, False))
696 self.assertEqual(False,
697 control.get_allow_missing(True, True, 1, False))
698
699 def testBlobSettingsAlways(self):
700 """Test the 'always' policy"""
701 bsettings.SetItem('global', 'allow-missing', 'always')
702 self.assertEqual(True,
703 control.get_allow_missing(False, False, 1, False))
704 self.assertEqual(False,
705 control.get_allow_missing(False, True, 1, False))
706
707 def testBlobSettingsBranch(self):
708 """Test the 'branch' policy"""
709 bsettings.SetItem('global', 'allow-missing', 'branch')
710 self.assertEqual(False,
711 control.get_allow_missing(False, False, 1, False))
712 self.assertEqual(True,
713 control.get_allow_missing(False, False, 1, True))
714 self.assertEqual(False,
715 control.get_allow_missing(False, True, 1, True))
716
717 def testBlobSettingsMultiple(self):
718 """Test the 'multiple' policy"""
719 bsettings.SetItem('global', 'allow-missing', 'multiple')
720 self.assertEqual(False,
721 control.get_allow_missing(False, False, 1, False))
722 self.assertEqual(True,
723 control.get_allow_missing(False, False, 2, False))
724 self.assertEqual(False,
725 control.get_allow_missing(False, True, 2, False))
726
727 def testBlobSettingsBranchMultiple(self):
728 """Test the 'branch multiple' policy"""
729 bsettings.SetItem('global', 'allow-missing', 'branch multiple')
730 self.assertEqual(False,
731 control.get_allow_missing(False, False, 1, False))
732 self.assertEqual(True,
733 control.get_allow_missing(False, False, 1, True))
734 self.assertEqual(True,
735 control.get_allow_missing(False, False, 2, False))
736 self.assertEqual(True,
737 control.get_allow_missing(False, False, 2, True))
738 self.assertEqual(False,
739 control.get_allow_missing(False, True, 2, True))
Simon Glasscd37d5b2023-02-21 12:40:27 -0700740
Simon Glass93202d72023-02-21 12:40:28 -0700741 def check_command(self, *extra_args):
742 """Run a command with the extra arguments and return the commands used
743
744 Args:
745 extra_args (list of str): List of extra arguments
746
747 Returns:
748 list of str: Lines returned in the out-cmd file
749 """
750 self._RunControl('-o', self._output_dir, *extra_args)
Simon Glasscd37d5b2023-02-21 12:40:27 -0700751 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
752 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
753 cmd_fname = os.path.join(board0_dir, 'out-cmd')
754 self.assertTrue(os.path.exists(cmd_fname))
755 data = tools.read_file(cmd_fname)
Simon Glassbfb708a2023-02-21 12:40:29 -0700756
757 config_fname = os.path.join(board0_dir, '.config')
758 self.assertTrue(os.path.exists(config_fname))
759 cfg_data = tools.read_file(config_fname)
760
761 return data.splitlines(), cfg_data
Simon Glass93202d72023-02-21 12:40:28 -0700762
763 def testCmdFile(self):
764 """Test that the -cmd-out file is produced"""
Simon Glassbfb708a2023-02-21 12:40:29 -0700765 lines = self.check_command()[0]
Simon Glasscd37d5b2023-02-21 12:40:27 -0700766 self.assertEqual(2, len(lines))
767 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
768 self.assertRegex(lines[0], b'make O=/.*-s.*')
Simon Glass93202d72023-02-21 12:40:28 -0700769
770 def testNoLto(self):
771 """Test that the --no-lto flag works"""
Simon Glassbfb708a2023-02-21 12:40:29 -0700772 lines = self.check_command('-L')[0]
Simon Glass93202d72023-02-21 12:40:28 -0700773 self.assertIn(b'NO_LTO=1', lines[0])
774
Simon Glassbfb708a2023-02-21 12:40:29 -0700775 def testReproducible(self):
776 """Test that the -r flag works"""
777 lines, cfg_data = self.check_command('-r')
778 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
779
780 # We should see CONFIG_LOCALVERSION_AUTO unset
781 self.assertEqual(b'''CONFIG_SOMETHING=1
782# CONFIG_LOCALVERSION_AUTO is not set
783''', cfg_data)
784
785 with test_util.capture_sys_output() as (stdout, stderr):
786 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
787 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
788
789 # We should see CONFIG_LOCALVERSION_AUTO unset
790 self.assertEqual(b'''CONFIG_SOMETHING=1
791CONFIG_LOCALVERSION=y
792''', cfg_data)
793 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())
Simon Glass3350d342023-07-19 17:48:15 -0600794
795 def test_scan_defconfigs(self):
796 """Test scanning the defconfigs to obtain all the boards"""
797 src = self._git_dir
798
799 # Scan the test directory which contains a Kconfig and some *_defconfig
800 # files
Simon Glassbec06ed2023-07-19 17:48:21 -0600801 params, warnings = self._boards.scan_defconfigs(src, src)
Simon Glass3350d342023-07-19 17:48:15 -0600802
803 # We should get two boards
804 self.assertEquals(2, len(params))
Simon Glassbec06ed2023-07-19 17:48:21 -0600805 self.assertFalse(warnings)
Simon Glass3350d342023-07-19 17:48:15 -0600806 first = 0 if params[0]['target'] == 'board0' else 1
807 board0 = params[first]
808 board2 = params[1 - first]
809
810 self.assertEquals('arm', board0['arch'])
811 self.assertEquals('armv7', board0['cpu'])
812 self.assertEquals('-', board0['soc'])
813 self.assertEquals('Tester', board0['vendor'])
814 self.assertEquals('ARM Board 0', board0['board'])
815 self.assertEquals('config0', board0['config'])
816 self.assertEquals('board0', board0['target'])
817
818 self.assertEquals('powerpc', board2['arch'])
819 self.assertEquals('ppc', board2['cpu'])
820 self.assertEquals('mpc85xx', board2['soc'])
821 self.assertEquals('Tester', board2['vendor'])
822 self.assertEquals('PowerPC board 1', board2['board'])
823 self.assertEquals('config2', board2['config'])
824 self.assertEquals('board2', board2['target'])
825
Simon Glassbd4ed9f2023-07-19 17:48:16 -0600826 def test_output_is_new(self):
827 """Test detecting new changes to Kconfig"""
828 base = self._base_dir
829 src = self._git_dir
830 config_dir = os.path.join(src, 'configs')
831 delay = 0.02
832
833 # Create a boards.cfg file
834 boards_cfg = os.path.join(base, 'boards.cfg')
835 content = b'''#
836# List of boards
837# Automatically generated by buildman/boards.py: don't edit
838#
839# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
840
841Active aarch64 armv8 - armltd corstone1000 board0
842Active aarch64 armv8 - armltd total_compute board2
843'''
844 # Check missing file
845 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
846
847 # Check that the board.cfg file is newer
848 time.sleep(delay)
849 tools.write_file(boards_cfg, content)
850 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
851
852 # Touch the Kconfig files after a show delay to avoid a race
853 time.sleep(delay)
854 Path(os.path.join(src, 'Kconfig')).touch()
855 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
856 Path(boards_cfg).touch()
857 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
858
859 # Touch a different Kconfig file
860 time.sleep(delay)
861 Path(os.path.join(src, 'Kconfig.something')).touch()
862 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
863 Path(boards_cfg).touch()
864 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
865
866 # Touch a MAINTAINERS file
867 time.sleep(delay)
868 Path(os.path.join(src, 'MAINTAINERS')).touch()
869 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
870
871 Path(boards_cfg).touch()
872 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
873
874 # Touch a defconfig file
875 time.sleep(delay)
876 Path(os.path.join(config_dir, 'board0_defconfig')).touch()
877 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
878 Path(boards_cfg).touch()
879 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
880
881 # Remove a board and check that the board.cfg file is now older
882 Path(os.path.join(config_dir, 'board0_defconfig')).unlink()
883 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
884
Simon Glass5df95cf2023-07-19 17:48:17 -0600885 def test_maintainers(self):
886 """Test detecting boards without a MAINTAINERS entry"""
887 src = self._git_dir
888 main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
889 other = os.path.join(src, 'boards', 'board2', 'MAINTAINERS')
Simon Glassbec06ed2023-07-19 17:48:21 -0600890 kc_file = os.path.join(src, 'Kconfig')
Simon Glass5df95cf2023-07-19 17:48:17 -0600891 config_dir = os.path.join(src, 'configs')
892 params_list, warnings = self._boards.build_board_list(config_dir, src)
893
894 # There should be two boards no warnings
895 self.assertEquals(2, len(params_list))
896 self.assertFalse(warnings)
897
898 # Set an invalid status line in the file
899 orig_data = tools.read_file(main, binary=False)
900 lines = ['S: Other\n' if line.startswith('S:') else line
901 for line in orig_data.splitlines(keepends=True)]
902 tools.write_file(main, ''.join(lines), binary=False)
903 params_list, warnings = self._boards.build_board_list(config_dir, src)
904 self.assertEquals(2, len(params_list))
905 params = params_list[0]
906 if params['target'] == 'board2':
907 params = params_list[1]
908 self.assertEquals('-', params['status'])
909 self.assertEquals(["WARNING: Other: unknown status for 'board0'"],
910 warnings)
911
912 # Remove the status line (S:) from a file
913 lines = [line for line in orig_data.splitlines(keepends=True)
914 if not line.startswith('S:')]
915 tools.write_file(main, ''.join(lines), binary=False)
916 params_list, warnings = self._boards.build_board_list(config_dir, src)
917 self.assertEquals(2, len(params_list))
918 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
919
920 # Remove the configs/ line (F:) from a file - this is the last line
921 data = ''.join(orig_data.splitlines(keepends=True)[:-1])
922 tools.write_file(main, data, binary=False)
923 params_list, warnings = self._boards.build_board_list(config_dir, src)
924 self.assertEquals(2, len(params_list))
925 self.assertEquals(
Simon Glass1aaaafa2023-07-19 17:48:25 -0600926 ["WARNING: no maintainers for 'board0'",
927 'WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 4',
928 ], warnings)
Simon Glass5df95cf2023-07-19 17:48:17 -0600929
Simon Glass9a7cc812023-07-19 17:48:26 -0600930 # Mark a board as orphaned - this should give a warning
931 lines = ['S: Orphaned' if line.startswith('S') else line
932 for line in orig_data.splitlines(keepends=True)]
933 tools.write_file(main, ''.join(lines), binary=False)
934 params_list, warnings = self._boards.build_board_list(config_dir, src)
935 self.assertEquals(2, len(params_list))
936 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
937
938 # Change the maintainer to '-' - this should give a warning
939 lines = ['M: -' if line.startswith('M') else line
940 for line in orig_data.splitlines(keepends=True)]
941 tools.write_file(main, ''.join(lines), binary=False)
942 params_list, warnings = self._boards.build_board_list(config_dir, src)
943 self.assertEquals(2, len(params_list))
944 self.assertEquals(["WARNING: -: unknown status for 'board0'"], warnings)
945
946 # Remove the maintainer line (M:) from a file
Simon Glass5df95cf2023-07-19 17:48:17 -0600947 lines = [line for line in orig_data.splitlines(keepends=True)
948 if not line.startswith('M:')]
949 tools.write_file(main, ''.join(lines), binary=False)
950 params_list, warnings = self._boards.build_board_list(config_dir, src)
951 self.assertEquals(2, len(params_list))
Simon Glass9a7cc812023-07-19 17:48:26 -0600952 self.assertEquals(["WARNING: no maintainers for 'board0'"], warnings)
Simon Glass5df95cf2023-07-19 17:48:17 -0600953
954 # Move the contents of the second file into this one, removing the
955 # second file, to check multiple records in a single file.
Simon Glassc6491532023-07-19 17:48:23 -0600956 both_data = orig_data + tools.read_file(other, binary=False)
957 tools.write_file(main, both_data, binary=False)
Simon Glass5df95cf2023-07-19 17:48:17 -0600958 os.remove(other)
959 params_list, warnings = self._boards.build_board_list(config_dir, src)
960 self.assertEquals(2, len(params_list))
961 self.assertFalse(warnings)
962
Simon Glassbc12d032023-07-19 17:48:19 -0600963 # Add another record, this should be ignored with a warning
Simon Glass5df95cf2023-07-19 17:48:17 -0600964 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
Simon Glassc6491532023-07-19 17:48:23 -0600965 tools.write_file(main, both_data + extra, binary=False)
Simon Glass5df95cf2023-07-19 17:48:17 -0600966 params_list, warnings = self._boards.build_board_list(config_dir, src)
967 self.assertEquals(2, len(params_list))
Simon Glassbc12d032023-07-19 17:48:19 -0600968 self.assertEquals(
969 ['WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 16'],
970 warnings)
Simon Glassbec06ed2023-07-19 17:48:21 -0600971
972 # Add another TARGET to the Kconfig
Simon Glassc6491532023-07-19 17:48:23 -0600973 tools.write_file(main, both_data, binary=False)
Simon Glassad995992023-07-19 17:48:22 -0600974 orig_kc_data = tools.read_file(kc_file)
Simon Glassbec06ed2023-07-19 17:48:21 -0600975 extra = (b'''
976if TARGET_BOARD2
977config TARGET_OTHER
978\tbool "other"
979\tdefault y
980endif
981''')
Simon Glassad995992023-07-19 17:48:22 -0600982 tools.write_file(kc_file, orig_kc_data + extra)
Simon Glass1b218422023-07-19 17:48:27 -0600983 params_list, warnings = self._boards.build_board_list(config_dir, src,
984 warn_targets=True)
Simon Glassbec06ed2023-07-19 17:48:21 -0600985 self.assertEquals(2, len(params_list))
986 self.assertEquals(
987 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
988 warnings)
Simon Glassad995992023-07-19 17:48:22 -0600989
990 # Remove the TARGET_BOARD0 Kconfig option
991 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
992 for line in orig_kc_data.splitlines(keepends=True)]
993 tools.write_file(kc_file, b''.join(lines))
Simon Glass1b218422023-07-19 17:48:27 -0600994 params_list, warnings = self._boards.build_board_list(config_dir, src,
995 warn_targets=True)
Simon Glassad995992023-07-19 17:48:22 -0600996 self.assertEquals(2, len(params_list))
997 self.assertEquals(
998 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
999 warnings)
Simon Glassc6491532023-07-19 17:48:23 -06001000 tools.write_file(kc_file, orig_kc_data)
1001
1002 # Replace the last F: line of board 2 with an N: line
1003 data = ''.join(both_data.splitlines(keepends=True)[:-1])
1004 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
1005 params_list, warnings = self._boards.build_board_list(config_dir, src)
1006 self.assertEquals(2, len(params_list))
1007 self.assertFalse(warnings)
1008
Simon Glassa1431e62023-07-19 17:48:28 -06001009 def testRegenBoards(self):
1010 """Test that we can regenerate the boards.cfg file"""
1011 outfile = os.path.join(self._output_dir, 'test-boards.cfg')
1012 if os.path.exists(outfile):
1013 os.remove(outfile)
1014 with test_util.capture_sys_output() as (stdout, stderr):
1015 result = self._RunControl('-R', outfile, brds=None,
1016 get_builder=False)
1017 self.assertTrue(os.path.exists(outfile))