blob: 164dd7c620a2cef9cff07f35cacdd5014f3611c3 [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 Glass938fa372022-07-11 19:03:58 -0600236 def _RunControl(self, *args, brds=None, clean_dir=False,
Simon Glass8116c782021-04-11 16:27:27 +1200237 test_thread_exceptions=False):
Simon Glass24993312021-04-11 16:27:25 +1200238 """Run buildman
239
240 Args:
241 args: List of arguments to pass
Simon Glass938fa372022-07-11 19:03:58 -0600242 brds: Boards object
Simon Glass24993312021-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 Glass8116c782021-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 Glass24993312021-04-11 16:27:25 +1200248
249 Returns:
250 result code from buildman
251 """
Simon Glassd4144e42014-09-05 19:00:13 -0600252 sys.argv = [sys.argv[0]] + list(args)
253 options, args = cmdline.ParseArgs()
Simon Glassdfb7e932014-09-05 19:00:20 -0600254 result = control.DoBuildman(options, args, toolchains=self._toolchains,
Simon Glasscc2c0d12022-07-11 19:04:00 -0600255 make_func=self._HandleMake, brds=brds or self._boards,
Simon Glass8116c782021-04-11 16:27:27 +1200256 clean_dir=clean_dir,
257 test_thread_exceptions=test_thread_exceptions)
Simon Glassdfb7e932014-09-05 19:00:20 -0600258 self._builder = control.builder
259 return result
Simon Glassd4144e42014-09-05 19:00:13 -0600260
261 def testFullHelp(self):
262 command.test_result = None
263 result = self._RunBuildman('-H')
Simon Glass74df4912022-11-09 19:14:43 -0700264 help_file = os.path.join(self._buildman_dir, 'README.rst')
Tom Rini3759df02018-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 Glassd4144e42014-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 Glass74df4912022-11-09 19:14:43 -0700275 help_file = os.path.join(self._buildman_dir, 'README.rst')
Simon Glassd4144e42014-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 Glass0157b182022-01-29 14:14:11 -0700283 gitutil.setup()
Simon Glassd4144e42014-09-05 19:00:13 -0600284 self.assertEqual(gitutil.use_no_decorate, False)
285
286 command.test_result = command.CommandResult(return_code=0)
Simon Glass0157b182022-01-29 14:14:11 -0700287 gitutil.setup()
Simon Glassd4144e42014-09-05 19:00:13 -0600288 self.assertEqual(gitutil.use_no_decorate, True)
289
290 def _HandleCommandGitLog(self, args):
Simon Glassd4c85722016-03-12 18:50:31 -0700291 if args[-1] == '--':
292 args = args[:-1]
Simon Glassd4144e42014-09-05 19:00:13 -0600293 if '-n0' in args:
294 return command.CommandResult(return_code=0)
Simon Glassf7582ce2014-09-05 19:00:22 -0600295 elif args[-1] == 'upstream/master..%s' % self._test_branch:
Simon Glassdfb7e932014-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 Glassf7582ce2014-09-05 19:00:22 -0600298 if args[-1] == self._test_branch:
Simon Glassdfb7e932014-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 Glassd4144e42014-09-05 19:00:13 -0600302
303 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600304 print('git log', args)
Simon Glassd4144e42014-09-05 19:00:13 -0600305 sys.exit(1)
306
Simon Glassdfb7e932014-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 Glassf7582ce2014-09-05 19:00:22 -0600313 elif config == 'branch.%s.remote' % self._test_branch:
Simon Glassdfb7e932014-09-05 19:00:20 -0600314 return command.CommandResult(return_code=0, stdout='upstream\n')
Simon Glassf7582ce2014-09-05 19:00:22 -0600315 elif config == 'branch.%s.merge' % self._test_branch:
Simon Glassdfb7e932014-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 Glassc05aa032019-10-31 07:42:53 -0600320 print('git config', args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600321 sys.exit(1)
322
Simon Glassd4144e42014-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 Glassdfb7e932014-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 Glassd4144e42014-09-05 19:00:13 -0600344 if sub_cmd == 'config':
Simon Glassdfb7e932014-09-05 19:00:20 -0600345 return self._HandleCommandGitConfig(args)
Simon Glassd4144e42014-09-05 19:00:13 -0600346 elif sub_cmd == 'log':
347 return self._HandleCommandGitLog(args)
Simon Glassdfb7e932014-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 Yasak76de29f2020-09-03 15:51:03 +0300352 elif sub_cmd == 'worktree':
353 return command.CommandResult(return_code=0)
Simon Glassd4144e42014-09-05 19:00:13 -0600354
355 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600356 print('git', git_args, sub_cmd, args)
Simon Glassd4144e42014-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 Kiernan0ddc5102018-05-31 04:48:33 +0000365 def _HandleCommandObjcopy(self, args):
366 return command.CommandResult(return_code=0)
367
Simon Glassd4144e42014-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 Glassdfb7e932014-09-05 19:00:20 -0600382 wc = False
Simon Glassd4144e42014-09-05 19:00:13 -0600383 if len(pipe_list) != 1:
Simon Glassdfb7e932014-09-05 19:00:20 -0600384 if pipe_list[1] == ['wc', '-l']:
385 wc = True
386 else:
Simon Glassc05aa032019-10-31 07:42:53 -0600387 print('invalid pipe', kwargs)
Simon Glassdfb7e932014-09-05 19:00:20 -0600388 sys.exit(1)
Simon Glassd4144e42014-09-05 19:00:13 -0600389 cmd = pipe_list[0][0]
390 args = pipe_list[0][1:]
Simon Glassdfb7e932014-09-05 19:00:20 -0600391 result = None
Simon Glassd4144e42014-09-05 19:00:13 -0600392 if cmd == 'git':
Simon Glassdfb7e932014-09-05 19:00:20 -0600393 result = self._HandleCommandGit(args)
Simon Glassd4144e42014-09-05 19:00:13 -0600394 elif cmd == './scripts/show-gnu-make':
395 return command.CommandResult(return_code=0, stdout='make')
Simon Glassdfb7e932014-09-05 19:00:20 -0600396 elif cmd.endswith('nm'):
Simon Glassd4144e42014-09-05 19:00:13 -0600397 return self._HandleCommandNm(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600398 elif cmd.endswith('objdump'):
Simon Glassd4144e42014-09-05 19:00:13 -0600399 return self._HandleCommandObjdump(args)
Alex Kiernan0ddc5102018-05-31 04:48:33 +0000400 elif cmd.endswith('objcopy'):
401 return self._HandleCommandObjcopy(args)
Simon Glassdfb7e932014-09-05 19:00:20 -0600402 elif cmd.endswith( 'size'):
Simon Glassd4144e42014-09-05 19:00:13 -0600403 return self._HandleCommandSize(args)
404
Simon Glassdfb7e932014-09-05 19:00:20 -0600405 if not result:
406 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600407 print('unknown command', kwargs)
Simon Glassdfb7e932014-09-05 19:00:20 -0600408 sys.exit(1)
409
410 if wc:
411 result.stdout = len(result.stdout.splitlines())
412 return result
Simon Glassd4144e42014-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 Glassd9800692022-01-29 14:14:05 -0700423 kwargs: Arguments to pass to command.run_pipe()
Simon Glassd4144e42014-09-05 19:00:13 -0600424 """
Simon Glassdfb7e932014-09-05 19:00:20 -0600425 self._make_calls += 1
Simon Glassbfb708a2023-02-21 12:40:29 -0700426 out_dir = ''
427 for arg in args:
428 if arg.startswith('O='):
429 out_dir = arg[2:]
Simon Glassd4144e42014-09-05 19:00:13 -0600430 if stage == 'mrproper':
431 return command.CommandResult(return_code=0)
432 elif stage == 'config':
Simon Glassbfb708a2023-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 Glassd4144e42014-09-05 19:00:13 -0600435 return command.CommandResult(return_code=0,
436 combined='Test configuration complete')
437 elif stage == 'build':
Simon Glassdfb7e932014-09-05 19:00:20 -0600438 stderr = ''
Simon Glassd829f122020-03-18 09:42:42 -0600439 fname = os.path.join(cwd or '', out_dir, 'u-boot')
Simon Glassc1aa66e2022-01-29 14:14:04 -0700440 tools.write_file(fname, b'U-Boot')
Tom Rinid7713ad2022-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 Glassdfb7e932014-09-05 19:00:20 -0600452 stderr = self._error.get((brd.target, commit.sequence))
Tom Rinid7713ad2022-11-09 19:14:53 -0700453
Simon Glassdfb7e932014-09-05 19:00:20 -0600454 if stderr:
Tom Rinid7713ad2022-11-09 19:14:53 -0700455 return command.CommandResult(return_code=2, stderr=stderr)
Simon Glassd4144e42014-09-05 19:00:13 -0600456 return command.CommandResult(return_code=0)
457
458 # Not handled, so abort
Simon Glassc05aa032019-10-31 07:42:53 -0600459 print('make', stage)
Simon Glassd4144e42014-09-05 19:00:13 -0600460 sys.exit(1)
461
Simon Glassdfb7e932014-09-05 19:00:20 -0600462 # Example function to print output lines
463 def print_lines(self, lines):
Simon Glassc05aa032019-10-31 07:42:53 -0600464 print(len(lines))
Simon Glassdfb7e932014-09-05 19:00:20 -0600465 for line in lines:
Simon Glassc05aa032019-10-31 07:42:53 -0600466 print(line)
Simon Glass098b10f2022-01-29 14:14:18 -0700467 #self.print_lines(terminal.get_print_test_lines())
Simon Glassdfb7e932014-09-05 19:00:20 -0600468
Simon Glass823e60b2014-09-05 19:00:16 -0600469 def testNoBoards(self):
470 """Test that buildman aborts when there are no boards"""
Simon Glassc52bd222022-07-11 19:04:03 -0600471 self._boards = boards.Boards()
Simon Glass823e60b2014-09-05 19:00:16 -0600472 with self.assertRaises(SystemExit):
473 self._RunControl()
474
Simon Glassd4144e42014-09-05 19:00:13 -0600475 def testCurrentSource(self):
476 """Very simple test to invoke buildman on the current source"""
Simon Glassdfb7e932014-09-05 19:00:20 -0600477 self.setupToolchains();
Tom Riniaae62582019-10-07 17:17:36 -0400478 self._RunControl('-o', self._output_dir)
Simon Glass098b10f2022-01-29 14:14:18 -0700479 lines = terminal.get_print_test_lines()
Simon Glass938fa372022-07-11 19:03:58 -0600480 self.assertIn('Building current source for %d boards' % len(BOARDS),
Simon Glassdfb7e932014-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 Riniaae62582019-10-07 17:17:36 -0400491 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glass098b10f2022-01-29 14:14:18 -0700492 lines = terminal.get_print_test_lines()
Simon Glassdfb7e932014-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 Glass938fa372022-07-11 19:03:58 -0600496 (self._commits, len(BOARDS)), lines[0].text)
Simon Glassdfb7e932014-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 Glassb1e5e6d2020-04-09 10:49:45 -0600502 self.assertEqual(ret_code, 100)
Simon Glassdfb7e932014-09-05 19:00:20 -0600503
504 for commit in range(self._commits):
Simon Glass6014db62022-07-11 19:04:02 -0600505 for brd in self._boards.get_list():
Simon Glassf4ed4702022-07-11 19:03:57 -0600506 if brd.arch != 'sandbox':
507 errfile = self._builder.GetErrFile(commit, brd.target)
Simon Glassdfb7e932014-09-05 19:00:20 -0600508 fd = open(errfile)
509 self.assertEqual(fd.readlines(),
Simon Glassf4ed4702022-07-11 19:03:57 -0600510 ['No tool chain for %s\n' % brd.arch])
Simon Glassdfb7e932014-09-05 19:00:20 -0600511 fd.close()
512
513 def testBranch(self):
514 """Test building a branch with all toolchains present"""
Tom Riniaae62582019-10-07 17:17:36 -0400515 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-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 Riniaae62582019-10-07 17:17:36 -0400521 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
Simon Glass938fa372022-07-11 19:03:58 -0600522 self.assertEqual(self._builder.count, 2 * len(BOARDS))
Simon Glassdfb7e932014-09-05 19:00:20 -0600523 self.assertEqual(self._builder.fail, 0)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600524 # Each board has a config, and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600525 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
Simon Glassdfb7e932014-09-05 19:00:20 -0600526
527 def testIncremental(self):
528 """Test building a branch twice - the second time should do nothing"""
Tom Riniaae62582019-10-07 17:17:36 -0400529 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600530
531 # Each board has a mrproper, config, and then one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600532 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glassdfb7e932014-09-05 19:00:20 -0600533 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400534 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glassdfb7e932014-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 Riniaae62582019-10-07 17:17:36 -0400541 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-09-05 19:00:20 -0600542 self._make_calls = 0
Tom Riniaae62582019-10-07 17:17:36 -0400543 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600544 # Each board has a config and one make per commit
Simon Glass938fa372022-07-11 19:03:58 -0600545 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
Simon Glassdfb7e932014-09-05 19:00:20 -0600546
547 def testForceReconfigure(self):
548 """The -f flag should force a rebuild"""
Tom Riniaae62582019-10-07 17:17:36 -0400549 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600550 # Each commit has a config and make
Simon Glass938fa372022-07-11 19:03:58 -0600551 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600552
Simon Glasseb70a2c2020-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 Glass938fa372022-07-11 19:03:58 -0600557 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
Simon Glassdfb7e932014-09-05 19:00:20 -0600558
559 def testErrors(self):
560 """Test handling of build errors"""
561 self._error['board2', 1] = 'fred\n'
Tom Riniaae62582019-10-07 17:17:36 -0400562 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
Simon Glassdfb7e932014-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 Riniaae62582019-10-07 17:17:36 -0400570 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
Simon Glassdfb7e932014-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 Riniaae62582019-10-07 17:17:36 -0400576 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
Simon Glassdfb7e932014-09-05 19:00:20 -0600577 self.assertEqual(self._builder.count, self._total_builds)
578 self.assertEqual(self._builder.fail, 0)
Simon Glasseb70a2c2020-04-09 15:08:51 -0600579 self.assertEqual(self._make_calls, 2)
Simon Glassf7582ce2014-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ßmann409fc022018-04-08 05:14:11 -0600587
Simon Glass166a98a2020-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 Glassf1a83ab2021-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 Glassd829f122020-03-18 09:42:42 -0600607 def testWorkInOutput(self):
608 """Test the -w option which should write directly to the output dir"""
Simon Glassc52bd222022-07-11 19:04:03 -0600609 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600610 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd829f122020-03-18 09:42:42 -0600611 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
Simon Glass938fa372022-07-11 19:03:58 -0600612 brds=board_list)
Simon Glassd829f122020-03-18 09:42:42 -0600613 self.assertTrue(
614 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
Simon Glass60b285f2020-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 Glassd829f122020-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 Glassc52bd222022-07-11 19:04:03 -0600628 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600629 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glassd829f122020-03-18 09:42:42 -0600630 with self.assertRaises(SystemExit) as e:
631 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
Simon Glass938fa372022-07-11 19:03:58 -0600632 '-w', clean_dir=False, brds=board_list)
Simon Glassd829f122020-03-18 09:42:42 -0600633 self.assertIn("single commit", str(e.exception))
Simon Glass88daaef2020-04-17 17:51:32 -0600634
Simon Glassc52bd222022-07-11 19:04:03 -0600635 board_list = boards.Boards()
Simon Glass6014db62022-07-11 19:04:02 -0600636 board_list.add_board(board.Board(*BOARDS[0]))
Simon Glass88daaef2020-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 Glass8116c782021-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 Glass8ca09312022-01-22 05:07:32 -0700646 self.assertIn(
647 'Thread exception (use -T0 to run without threads): test exception',
648 stdout.getvalue())
Tom Rinid7713ad2022-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 Glasscd37d5b2023-02-21 12:40:27 -0700736
Simon Glass93202d72023-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 Glasscd37d5b2023-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 Glassbfb708a2023-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 Glass93202d72023-02-21 12:40:28 -0700758
759 def testCmdFile(self):
760 """Test that the -cmd-out file is produced"""
Simon Glassbfb708a2023-02-21 12:40:29 -0700761 lines = self.check_command()[0]
Simon Glasscd37d5b2023-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 Glass93202d72023-02-21 12:40:28 -0700765
766 def testNoLto(self):
767 """Test that the --no-lto flag works"""
Simon Glassbfb708a2023-02-21 12:40:29 -0700768 lines = self.check_command('-L')[0]
Simon Glass93202d72023-02-21 12:40:28 -0700769 self.assertIn(b'NO_LTO=1', lines[0])
770
Simon Glassbfb708a2023-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 Glass3350d342023-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 Glassbec06ed2023-07-19 17:48:21 -0600797 params, warnings = self._boards.scan_defconfigs(src, src)
Simon Glass3350d342023-07-19 17:48:15 -0600798
799 # We should get two boards
800 self.assertEquals(2, len(params))
Simon Glassbec06ed2023-07-19 17:48:21 -0600801 self.assertFalse(warnings)
Simon Glass3350d342023-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 Glassbd4ed9f2023-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 Glass5df95cf2023-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 Glassbec06ed2023-07-19 17:48:21 -0600886 kc_file = os.path.join(src, 'Kconfig')
Simon Glass5df95cf2023-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 Glassbc12d032023-07-19 17:48:19 -0600922 ['WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 4',
923 "WARNING: no status info for 'board0'",
Simon Glass5df95cf2023-07-19 17:48:17 -0600924 "WARNING: no maintainers for 'board0'"], warnings)
925
926 # Remove the maintainer line (M:) from a file (this should be fine)
927 lines = [line for line in orig_data.splitlines(keepends=True)
928 if not line.startswith('M:')]
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.assertFalse(warnings)
933
934 # Move the contents of the second file into this one, removing the
935 # second file, to check multiple records in a single file.
Simon Glassc6491532023-07-19 17:48:23 -0600936 both_data = orig_data + tools.read_file(other, binary=False)
937 tools.write_file(main, both_data, binary=False)
Simon Glass5df95cf2023-07-19 17:48:17 -0600938 os.remove(other)
939 params_list, warnings = self._boards.build_board_list(config_dir, src)
940 self.assertEquals(2, len(params_list))
941 self.assertFalse(warnings)
942
Simon Glassbc12d032023-07-19 17:48:19 -0600943 # Add another record, this should be ignored with a warning
Simon Glass5df95cf2023-07-19 17:48:17 -0600944 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
Simon Glassc6491532023-07-19 17:48:23 -0600945 tools.write_file(main, both_data + extra, binary=False)
Simon Glass5df95cf2023-07-19 17:48:17 -0600946 params_list, warnings = self._boards.build_board_list(config_dir, src)
947 self.assertEquals(2, len(params_list))
Simon Glassbc12d032023-07-19 17:48:19 -0600948 self.assertEquals(
949 ['WARNING: orphaned defconfig in boards/board0/MAINTAINERS ending at line 16'],
950 warnings)
Simon Glassbec06ed2023-07-19 17:48:21 -0600951
952 # Add another TARGET to the Kconfig
Simon Glassc6491532023-07-19 17:48:23 -0600953 tools.write_file(main, both_data, binary=False)
Simon Glassad995992023-07-19 17:48:22 -0600954 orig_kc_data = tools.read_file(kc_file)
Simon Glassbec06ed2023-07-19 17:48:21 -0600955 extra = (b'''
956if TARGET_BOARD2
957config TARGET_OTHER
958\tbool "other"
959\tdefault y
960endif
961''')
Simon Glassad995992023-07-19 17:48:22 -0600962 tools.write_file(kc_file, orig_kc_data + extra)
Simon Glassbec06ed2023-07-19 17:48:21 -0600963 params_list, warnings = self._boards.build_board_list(config_dir, src)
964 self.assertEquals(2, len(params_list))
965 self.assertEquals(
966 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
967 warnings)
Simon Glassad995992023-07-19 17:48:22 -0600968
969 # Remove the TARGET_BOARD0 Kconfig option
970 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
971 for line in orig_kc_data.splitlines(keepends=True)]
972 tools.write_file(kc_file, b''.join(lines))
973 params_list, warnings = self._boards.build_board_list(config_dir, src)
974 self.assertEquals(2, len(params_list))
975 self.assertEquals(
976 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
977 warnings)
Simon Glassc6491532023-07-19 17:48:23 -0600978 tools.write_file(kc_file, orig_kc_data)
979
980 # Replace the last F: line of board 2 with an N: line
981 data = ''.join(both_data.splitlines(keepends=True)[:-1])
982 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
983 params_list, warnings = self._boards.build_board_list(config_dir, src)
984 self.assertEquals(2, len(params_list))
985 self.assertFalse(warnings)
986