]> git.ipfire.org Git - thirdparty/u-boot.git/blame - tools/buildman/func_test.py
Merge tag 'efi-2025-07-rc6' of https://source.denx.de/u-boot/custodians/u-boot-efi
[thirdparty/u-boot.git] / tools / buildman / func_test.py
CommitLineData
83d290c5 1# SPDX-License-Identifier: GPL-2.0+
d4144e45
SG
2# Copyright (c) 2014 Google, Inc
3#
d4144e45 4
6bf74a2e 5import io
d4144e45 6import os
bd4ed9f7 7from pathlib import Path
6bf74a2e 8import re
d4144e45
SG
9import shutil
10import sys
11import tempfile
bd4ed9f7 12import time
d4144e45
SG
13import unittest
14
0ede00fd 15from buildman import board
c52bd225 16from buildman import boards
0ede00fd
SG
17from buildman import bsettings
18from buildman import cmdline
19from buildman import control
20from buildman import toolchain
4583c002 21from u_boot_pylib import command
5c33fb02 22from u_boot_pylib import gitutil
4583c002
SG
23from u_boot_pylib import terminal
24from u_boot_pylib import test_util
25from u_boot_pylib import tools
d4144e45 26
8b985eeb
SG
27settings_data = '''
28# Buildman settings file
d7713ad3 29[global]
8b985eeb
SG
30
31[toolchain]
32
33[toolchain-alias]
34
35[make-flags]
36src=/home/sjg/c/src
37chroot=/home/sjg/c/chroot
9865543a 38vboot=VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
8b985eeb
SG
39chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
40chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
41chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
42'''
43
938fa37c 44BOARDS = [
2ef88d63
SG
45 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''],
46 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''],
823e60b6 47 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
823e60b6
SG
48 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
49]
50
dfb7e932
SG
51commit_shortlog = """4aca821 patman: Avoid changing the order of tags
5239403bb patman: Use --no-pager' to stop git from forking a pager
53db6e6f2 patman: Remove the -a option
54f2ccf03 patman: Correct unit tests to run correctly
551d097f9 patman: Fix indentation in terminal.py
56d073747 patman: Support the 'reverse' option for 'git log
57"""
58
59commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
60Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
61Date: Fri Aug 22 19:12:41 2014 +0900
62
63 buildman: refactor help message
64
65 "buildman [options]" is displayed by default.
66
67 Append the rest of help messages to parser.usage
68 instead of replacing it.
69
70 Besides, "-b <branch>" is not mandatory since commit fea5858e.
71 Drop it from the usage.
72
73 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
74""",
75"""commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
76Author: Simon Glass <sjg@chromium.org>
77Date: Thu Aug 14 16:48:25 2014 -0600
78
79 patman: Support the 'reverse' option for 'git log'
80
81 This option is currently not supported, but needs to be, for buildman to
82 operate as expected.
83
84 Series-changes: 7
85 - Add new patch to fix the 'reverse' bug
86
950a2313 87 Series-version: 8
dfb7e932
SG
88
89 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
90 Reported-by: York Sun <yorksun@freescale.com>
91 Signed-off-by: Simon Glass <sjg@chromium.org>
92
93""",
94"""commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
95Author: Simon Glass <sjg@chromium.org>
96Date: Sat Aug 9 11:44:32 2014 -0600
97
98 patman: Fix indentation in terminal.py
99
100 This code came from a different project with 2-character indentation. Fix
101 it for U-Boot.
102
103 Series-changes: 6
104 - Add new patch to fix indentation in teminal.py
105
106 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
107 Signed-off-by: Simon Glass <sjg@chromium.org>
108
109""",
110"""commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
111Author: Simon Glass <sjg@chromium.org>
112Date: Sat Aug 9 11:08:24 2014 -0600
113
114 patman: Correct unit tests to run correctly
115
116 It seems that doctest behaves differently now, and some of the unit tests
117 do not run. Adjust the tests to work correctly.
118
119 ./tools/patman/patman --test
120 <unittest.result.TestResult run=10 errors=0 failures=0>
121
122 Series-changes: 6
123 - Add new patch to fix patman unit tests
124
125 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
126
127""",
128"""commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
129Author: Simon Glass <sjg@chromium.org>
130Date: Sat Aug 9 12:06:02 2014 -0600
131
132 patman: Remove the -a option
133
134 It seems that this is no longer needed, since checkpatch.pl will catch
135 whitespace problems in patches. Also the option is not widely used, so
136 it seems safe to just remove it.
137
138 Series-changes: 6
139 - Add new patch to remove patman's -a option
140
141 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
142 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
143
144""",
145"""commit 39403bb4f838153028a6f21ca30bf100f3791133
146Author: Simon Glass <sjg@chromium.org>
147Date: Thu Aug 14 21:50:52 2014 -0600
148
149 patman: Use --no-pager' to stop git from forking a pager
150
151""",
152"""commit 4aca821e27e97925c039e69fd37375b09c6f129c
153Author: Simon Glass <sjg@chromium.org>
154Date: Fri Aug 22 15:57:39 2014 -0600
155
156 patman: Avoid changing the order of tags
157
158 patman collects tags that it sees in the commit and places them nicely
159 sorted at the end of the patch. However, this is not really necessary and
160 in fact is apparently not desirable.
161
162 Series-changes: 9
163 - Add new patch to avoid changing the order of tags
164
950a2313
SG
165 Series-version: 9
166
dfb7e932
SG
167 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
168 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
169"""]
170
171TEST_BRANCH = '__testbranch'
172
d4144e45
SG
173class TestFunctional(unittest.TestCase):
174 """Functional test for buildman.
175
176 This aims to test from just below the invocation of buildman (parsing
177 of arguments) to 'make' and 'git' invocation. It is not a true
178 emd-to-end test, as it mocks git, make and the tool chain. But this
179 makes it easier to detect when the builder is doing the wrong thing,
180 since in many cases this test code will fail. For example, only a
181 very limited subset of 'git' arguments is supported - anything
182 unexpected will fail.
183 """
184 def setUp(self):
185 self._base_dir = tempfile.mkdtemp()
aae62584 186 self._output_dir = tempfile.mkdtemp()
d4144e45
SG
187 self._git_dir = os.path.join(self._base_dir, 'src')
188 self._buildman_pathname = sys.argv[0]
bd6f5d98 189 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
d6900a77 190 command.TEST_RESULT = self._HandleCommand
42d42cf1
SG
191 bsettings.setup(None)
192 bsettings.add_file(settings_data)
dfb7e932
SG
193 self.setupToolchains()
194 self._toolchains.Add('arm-gcc', test=False)
195 self._toolchains.Add('powerpc-gcc', test=False)
c52bd225 196 self._boards = boards.Boards()
938fa37c 197 for brd in BOARDS:
6014db68 198 self._boards.add_board(board.Board(*brd))
d4144e45 199
dfb7e932
SG
200 # Directories where the source been cloned
201 self._clone_dirs = []
202 self._commits = len(commit_shortlog.splitlines()) + 1
938fa37c 203 self._total_builds = self._commits * len(BOARDS)
dfb7e932
SG
204
205 # Number of calls to make
206 self._make_calls = 0
207
208 # Map of [board, commit] to error messages
209 self._error = {}
210
f7582ce8
SG
211 self._test_branch = TEST_BRANCH
212
d7713ad3
TR
213 # Set to True to report missing blobs
214 self._missing = False
215
3350d34f
SG
216 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
217 self._test_dir = os.path.join(self._buildman_dir, 'test')
218
219 # Set up some fake source files
220 shutil.copytree(self._test_dir, self._git_dir)
221
dfb7e932 222 # Avoid sending any output and clear all terminal output
098b10fb
SG
223 terminal.set_print_test_mode()
224 terminal.get_print_test_lines()
dfb7e932 225
d4144e45
SG
226 def tearDown(self):
227 shutil.rmtree(self._base_dir)
5f319fa7 228 shutil.rmtree(self._output_dir)
d4144e45 229
dfb7e932
SG
230 def setupToolchains(self):
231 self._toolchains = toolchain.Toolchains()
232 self._toolchains.Add('gcc', test=False)
233
d4144e45 234 def _RunBuildman(self, *args):
3d094ce2
SG
235 all_args = [self._buildman_pathname] + list(args)
236 return command.run_one(*all_args, capture=True, capture_stderr=True)
d4144e45 237
a1431e6c
SG
238 def _RunControl(self, *args, brds=False, clean_dir=False,
239 test_thread_exceptions=False, get_builder=True):
24993313
SG
240 """Run buildman
241
242 Args:
243 args: List of arguments to pass
a1431e6c
SG
244 brds: Boards object, or False to pass self._boards, or None to pass
245 None
24993313
SG
246 clean_dir: Used for tests only, indicates that the existing output_dir
247 should be removed before starting the build
8116c78f
SG
248 test_thread_exceptions: Uses for tests only, True to make the threads
249 raise an exception instead of reporting their result. This simulates
250 a failure in the code somewhere
a1431e6c 251 get_builder (bool): Set self._builder to the resulting builder
24993313
SG
252
253 Returns:
254 result code from buildman
255 """
d4144e45 256 sys.argv = [sys.argv[0]] + list(args)
529957c3 257 args = cmdline.parse_args()
a1431e6c
SG
258 if brds == False:
259 brds = self._boards
9ef05b95 260 result = control.do_buildman(
529957c3
SG
261 args, toolchains=self._toolchains, make_func=self._HandleMake,
262 brds=brds, clean_dir=clean_dir,
9ef05b95 263 test_thread_exceptions=test_thread_exceptions)
a1431e6c 264 if get_builder:
b8be2bd8 265 self._builder = control.TEST_BUILDER
dfb7e932 266 return result
d4144e45
SG
267
268 def testFullHelp(self):
d6900a77 269 command.TEST_RESULT = None
d4144e45 270 result = self._RunBuildman('-H')
74df4910 271 help_file = os.path.join(self._buildman_dir, 'README.rst')
3759df0c
TR
272 # Remove possible extraneous strings
273 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
274 gothelp = result.stdout.replace(extra, '')
275 self.assertEqual(len(gothelp), os.path.getsize(help_file))
d4144e45
SG
276 self.assertEqual(0, len(result.stderr))
277 self.assertEqual(0, result.return_code)
278
279 def testHelp(self):
d6900a77 280 command.TEST_RESULT = None
d4144e45 281 result = self._RunBuildman('-h')
74df4910 282 help_file = os.path.join(self._buildman_dir, 'README.rst')
d4144e45
SG
283 self.assertTrue(len(result.stdout) > 1000)
284 self.assertEqual(0, len(result.stderr))
285 self.assertEqual(0, result.return_code)
286
287 def testGitSetup(self):
288 """Test gitutils.Setup(), from outside the module itself"""
d6900a77 289 command.TEST_RESULT = command.CommandResult(return_code=1)
0157b187 290 gitutil.setup()
3703298e 291 self.assertEqual(gitutil.USE_NO_DECORATE, False)
d4144e45 292
d6900a77 293 command.TEST_RESULT = command.CommandResult(return_code=0)
0157b187 294 gitutil.setup()
3703298e 295 self.assertEqual(gitutil.USE_NO_DECORATE, True)
d4144e45
SG
296
297 def _HandleCommandGitLog(self, args):
d4c8572b
SG
298 if args[-1] == '--':
299 args = args[:-1]
d4144e45
SG
300 if '-n0' in args:
301 return command.CommandResult(return_code=0)
f7582ce8 302 elif args[-1] == 'upstream/master..%s' % self._test_branch:
dfb7e932
SG
303 return command.CommandResult(return_code=0, stdout=commit_shortlog)
304 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
f7582ce8 305 if args[-1] == self._test_branch:
dfb7e932
SG
306 count = int(args[3][2:])
307 return command.CommandResult(return_code=0,
308 stdout=''.join(commit_log[:count]))
d4144e45
SG
309
310 # Not handled, so abort
c05aa036 311 print('git log', args)
d4144e45
SG
312 sys.exit(1)
313
dfb7e932
SG
314 def _HandleCommandGitConfig(self, args):
315 config = args[0]
316 if config == 'sendemail.aliasesfile':
317 return command.CommandResult(return_code=0)
318 elif config.startswith('branch.badbranch'):
319 return command.CommandResult(return_code=1)
f7582ce8 320 elif config == 'branch.%s.remote' % self._test_branch:
dfb7e932 321 return command.CommandResult(return_code=0, stdout='upstream\n')
f7582ce8 322 elif config == 'branch.%s.merge' % self._test_branch:
dfb7e932
SG
323 return command.CommandResult(return_code=0,
324 stdout='refs/heads/master\n')
325
326 # Not handled, so abort
c05aa036 327 print('git config', args)
dfb7e932
SG
328 sys.exit(1)
329
d4144e45
SG
330 def _HandleCommandGit(self, in_args):
331 """Handle execution of a git command
332
333 This uses a hacked-up parser.
334
335 Args:
336 in_args: Arguments after 'git' from the command line
337 """
338 git_args = [] # Top-level arguments to git itself
339 sub_cmd = None # Git sub-command selected
340 args = [] # Arguments to the git sub-command
341 for arg in in_args:
342 if sub_cmd:
343 args.append(arg)
344 elif arg[0] == '-':
345 git_args.append(arg)
346 else:
dfb7e932
SG
347 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
348 git_args.append(arg)
349 else:
350 sub_cmd = arg
d4144e45 351 if sub_cmd == 'config':
dfb7e932 352 return self._HandleCommandGitConfig(args)
d4144e45
SG
353 elif sub_cmd == 'log':
354 return self._HandleCommandGitLog(args)
dfb7e932
SG
355 elif sub_cmd == 'clone':
356 return command.CommandResult(return_code=0)
357 elif sub_cmd == 'checkout':
358 return command.CommandResult(return_code=0)
76de29fc
ANY
359 elif sub_cmd == 'worktree':
360 return command.CommandResult(return_code=0)
d4144e45
SG
361
362 # Not handled, so abort
c05aa036 363 print('git', git_args, sub_cmd, args)
d4144e45
SG
364 sys.exit(1)
365
366 def _HandleCommandNm(self, args):
367 return command.CommandResult(return_code=0)
368
369 def _HandleCommandObjdump(self, args):
370 return command.CommandResult(return_code=0)
371
0ddc510e
AK
372 def _HandleCommandObjcopy(self, args):
373 return command.CommandResult(return_code=0)
374
d4144e45
SG
375 def _HandleCommandSize(self, args):
376 return command.CommandResult(return_code=0)
377
6bf74a2e
SG
378 def _HandleCommandCpp(self, args):
379 # args ['-nostdinc', '-P', '-I', '/tmp/tmp7f17xk_o/src', '-undef',
380 # '-x', 'assembler-with-cpp', fname]
381 fname = args[7]
382 buf = io.StringIO()
383 for line in tools.read_file(fname, False).splitlines():
384 if line.startswith('#include'):
385 # Example: #include <configs/renesas_rcar2.config>
386 m_incfname = re.match('#include <(.*)>', line)
387 data = tools.read_file(m_incfname.group(1), False)
388 for line in data.splitlines():
389 print(line, file=buf)
390 else:
391 print(line, file=buf)
392 return command.CommandResult(stdout=buf.getvalue(), return_code=0)
393
d4144e45
SG
394 def _HandleCommand(self, **kwargs):
395 """Handle a command execution.
396
397 The command is in kwargs['pipe-list'], as a list of pipes, each a
398 list of commands. The command should be emulated as required for
399 testing purposes.
400
401 Returns:
402 A CommandResult object
403 """
404 pipe_list = kwargs['pipe_list']
dfb7e932 405 wc = False
d4144e45 406 if len(pipe_list) != 1:
dfb7e932
SG
407 if pipe_list[1] == ['wc', '-l']:
408 wc = True
409 else:
c05aa036 410 print('invalid pipe', kwargs)
dfb7e932 411 sys.exit(1)
d4144e45
SG
412 cmd = pipe_list[0][0]
413 args = pipe_list[0][1:]
dfb7e932 414 result = None
d4144e45 415 if cmd == 'git':
dfb7e932 416 result = self._HandleCommandGit(args)
d4144e45
SG
417 elif cmd == './scripts/show-gnu-make':
418 return command.CommandResult(return_code=0, stdout='make')
dfb7e932 419 elif cmd.endswith('nm'):
d4144e45 420 return self._HandleCommandNm(args)
dfb7e932 421 elif cmd.endswith('objdump'):
d4144e45 422 return self._HandleCommandObjdump(args)
0ddc510e
AK
423 elif cmd.endswith('objcopy'):
424 return self._HandleCommandObjcopy(args)
dfb7e932 425 elif cmd.endswith( 'size'):
d4144e45 426 return self._HandleCommandSize(args)
6bf74a2e
SG
427 elif cmd.endswith( 'cpp'):
428 return self._HandleCommandCpp(args)
d4144e45 429
dfb7e932
SG
430 if not result:
431 # Not handled, so abort
c05aa036 432 print('unknown command', kwargs)
dfb7e932
SG
433 sys.exit(1)
434
435 if wc:
436 result.stdout = len(result.stdout.splitlines())
437 return result
d4144e45
SG
438
439 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
440 """Handle execution of 'make'
441
442 Args:
443 commit: Commit object that is being built
444 brd: Board object that is being built
445 stage: Stage that we are at (mrproper, config, build)
446 cwd: Directory where make should be run
447 args: Arguments to pass to make
3d094ce2 448 kwargs: Arguments to pass to command.run_one()
d4144e45 449 """
dfb7e932 450 self._make_calls += 1
bfb708ad
SG
451 out_dir = ''
452 for arg in args:
453 if arg.startswith('O='):
454 out_dir = arg[2:]
d4144e45
SG
455 if stage == 'mrproper':
456 return command.CommandResult(return_code=0)
457 elif stage == 'config':
bfb708ad
SG
458 fname = os.path.join(cwd or '', out_dir, '.config')
459 tools.write_file(fname, b'CONFIG_SOMETHING=1')
d4144e45
SG
460 return command.CommandResult(return_code=0,
461 combined='Test configuration complete')
dc314185
SG
462 elif stage == 'oldconfig':
463 return command.CommandResult(return_code=0)
d4144e45 464 elif stage == 'build':
dfb7e932 465 stderr = ''
d829f121 466 fname = os.path.join(cwd or '', out_dir, 'u-boot')
c1aa66e7 467 tools.write_file(fname, b'U-Boot')
d7713ad3
TR
468
469 # Handle missing blobs
470 if self._missing:
471 if 'BINMAN_ALLOW_MISSING=1' in args:
472 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
473Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
474
475Some images are invalid'''
476 else:
477 stderr = "binman: Filename 'fsp.bin' not found in input path"
478 elif type(commit) is not str:
dfb7e932 479 stderr = self._error.get((brd.target, commit.sequence))
d7713ad3 480
dfb7e932 481 if stderr:
d7713ad3 482 return command.CommandResult(return_code=2, stderr=stderr)
d4144e45
SG
483 return command.CommandResult(return_code=0)
484
485 # Not handled, so abort
dc314185 486 print('_HandleMake failure: make', stage)
d4144e45
SG
487 sys.exit(1)
488
dfb7e932
SG
489 # Example function to print output lines
490 def print_lines(self, lines):
c05aa036 491 print(len(lines))
dfb7e932 492 for line in lines:
c05aa036 493 print(line)
098b10fb 494 #self.print_lines(terminal.get_print_test_lines())
dfb7e932 495
823e60b6
SG
496 def testNoBoards(self):
497 """Test that buildman aborts when there are no boards"""
c52bd225 498 self._boards = boards.Boards()
823e60b6
SG
499 with self.assertRaises(SystemExit):
500 self._RunControl()
501
d4144e45
SG
502 def testCurrentSource(self):
503 """Very simple test to invoke buildman on the current source"""
dfb7e932 504 self.setupToolchains();
aae62584 505 self._RunControl('-o', self._output_dir)
098b10fb 506 lines = terminal.get_print_test_lines()
938fa37c 507 self.assertIn('Building current source for %d boards' % len(BOARDS),
dfb7e932
SG
508 lines[0].text)
509
510 def testBadBranch(self):
511 """Test that we can detect an invalid branch"""
512 with self.assertRaises(ValueError):
513 self._RunControl('-b', 'badbranch')
514
515 def testBadToolchain(self):
516 """Test that missing toolchains are detected"""
517 self.setupToolchains();
aae62584 518 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
098b10fb 519 lines = terminal.get_print_test_lines()
dfb7e932
SG
520
521 # Buildman always builds the upstream commit as well
522 self.assertIn('Building %d commits for %d boards' %
938fa37c 523 (self._commits, len(BOARDS)), lines[0].text)
dfb7e932
SG
524 self.assertEqual(self._builder.count, self._total_builds)
525
526 # Only sandbox should succeed, the others don't have toolchains
527 self.assertEqual(self._builder.fail,
528 self._total_builds - self._commits)
b1e5e6d2 529 self.assertEqual(ret_code, 100)
dfb7e932
SG
530
531 for commit in range(self._commits):
6014db68 532 for brd in self._boards.get_list():
f4ed4706 533 if brd.arch != 'sandbox':
37edf5fc 534 errfile = self._builder.get_err_file(commit, brd.target)
dfb7e932 535 fd = open(errfile)
57686d33
SG
536 self.assertEqual(
537 fd.readlines(),
538 [f'Tool chain error for {brd.arch}: '
539 f"No tool chain found for arch '{brd.arch}'"])
dfb7e932
SG
540 fd.close()
541
542 def testBranch(self):
543 """Test building a branch with all toolchains present"""
aae62584 544 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932
SG
545 self.assertEqual(self._builder.count, self._total_builds)
546 self.assertEqual(self._builder.fail, 0)
547
548 def testCount(self):
549 """Test building a specific number of commitst"""
aae62584 550 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
938fa37c 551 self.assertEqual(self._builder.count, 2 * len(BOARDS))
dfb7e932 552 self.assertEqual(self._builder.fail, 0)
eb70a2c0 553 # Each board has a config, and then one make per commit
938fa37c 554 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
dfb7e932
SG
555
556 def testIncremental(self):
557 """Test building a branch twice - the second time should do nothing"""
aae62584 558 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932
SG
559
560 # Each board has a mrproper, config, and then one make per commit
938fa37c 561 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
dfb7e932 562 self._make_calls = 0
aae62584 563 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
dfb7e932
SG
564 self.assertEqual(self._make_calls, 0)
565 self.assertEqual(self._builder.count, self._total_builds)
566 self.assertEqual(self._builder.fail, 0)
567
568 def testForceBuild(self):
569 """The -f flag should force a rebuild"""
aae62584 570 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932 571 self._make_calls = 0
aae62584 572 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
eb70a2c0 573 # Each board has a config and one make per commit
938fa37c 574 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
dfb7e932
SG
575
576 def testForceReconfigure(self):
577 """The -f flag should force a rebuild"""
aae62584 578 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
eb70a2c0 579 # Each commit has a config and make
938fa37c 580 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
eb70a2c0 581
eb70a2c0
SG
582 def testMrproper(self):
583 """The -f flag should force a rebuild"""
584 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
585 # Each board has a mkproper, config and then one make per commit
938fa37c 586 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
dfb7e932
SG
587
588 def testErrors(self):
589 """Test handling of build errors"""
590 self._error['board2', 1] = 'fred\n'
aae62584 591 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932
SG
592 self.assertEqual(self._builder.count, self._total_builds)
593 self.assertEqual(self._builder.fail, 1)
594
595 # Remove the error. This should have no effect since the commit will
596 # not be rebuilt
597 del self._error['board2', 1]
598 self._make_calls = 0
aae62584 599 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
dfb7e932
SG
600 self.assertEqual(self._builder.count, self._total_builds)
601 self.assertEqual(self._make_calls, 0)
602 self.assertEqual(self._builder.fail, 1)
603
604 # Now use the -F flag to force rebuild of the bad commit
aae62584 605 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
dfb7e932
SG
606 self.assertEqual(self._builder.count, self._total_builds)
607 self.assertEqual(self._builder.fail, 0)
eb70a2c0 608 self.assertEqual(self._make_calls, 2)
f7582ce8
SG
609
610 def testBranchWithSlash(self):
611 """Test building a branch with a '/' in the name"""
612 self._test_branch = '/__dev/__testbranch'
ad1c9b26
SG
613 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
614 clean_dir=False)
f7582ce8
SG
615 self.assertEqual(self._builder.count, self._total_builds)
616 self.assertEqual(self._builder.fail, 0)
409fc029 617
166a98a4
SG
618 def testEnvironment(self):
619 """Test that the done and environment files are written to out-env"""
620 self._RunControl('-o', self._output_dir)
621 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
622 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
623 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
624
f1a83abe
SG
625 def testEnvironmentUnicode(self):
626 """Test there are no unicode errors when the env has non-ASCII chars"""
627 try:
628 varname = b'buildman_test_var'
629 os.environb[varname] = b'strange\x80chars'
630 self.assertEqual(0, self._RunControl('-o', self._output_dir))
631 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
632 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
633 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
634 finally:
635 del os.environb[varname]
636
d829f121
SG
637 def testWorkInOutput(self):
638 """Test the -w option which should write directly to the output dir"""
c52bd225 639 board_list = boards.Boards()
6014db68 640 board_list.add_board(board.Board(*BOARDS[0]))
d829f121 641 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
938fa37c 642 brds=board_list)
d829f121
SG
643 self.assertTrue(
644 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
60b285f8
SG
645 self.assertTrue(
646 os.path.exists(os.path.join(self._output_dir, 'done')))
647 self.assertTrue(
648 os.path.exists(os.path.join(self._output_dir, 'out-env')))
d829f121
SG
649
650 def testWorkInOutputFail(self):
651 """Test the -w option failures"""
652 with self.assertRaises(SystemExit) as e:
653 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
654 self.assertIn("single board", str(e.exception))
655 self.assertFalse(
656 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
657
c52bd225 658 board_list = boards.Boards()
6014db68 659 board_list.add_board(board.Board(*BOARDS[0]))
d829f121
SG
660 with self.assertRaises(SystemExit) as e:
661 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
938fa37c 662 '-w', clean_dir=False, brds=board_list)
d829f121 663 self.assertIn("single commit", str(e.exception))
88daaef1 664
c52bd225 665 board_list = boards.Boards()
6014db68 666 board_list.add_board(board.Board(*BOARDS[0]))
88daaef1
SG
667 with self.assertRaises(SystemExit) as e:
668 self._RunControl('-w', clean_dir=False)
669 self.assertIn("specify -o", str(e.exception))
8116c78f
SG
670
671 def testThreadExceptions(self):
672 """Test that exceptions in threads are reported"""
673 with test_util.capture_sys_output() as (stdout, stderr):
674 self.assertEqual(102, self._RunControl('-o', self._output_dir,
675 test_thread_exceptions=True))
8ca0931a
SG
676 self.assertIn(
677 'Thread exception (use -T0 to run without threads): test exception',
678 stdout.getvalue())
d7713ad3
TR
679
680 def testBlobs(self):
681 """Test handling of missing blobs"""
682 self._missing = True
683
684 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
685 errfile = os.path.join(board0_dir, 'err')
686 logfile = os.path.join(board0_dir, 'log')
687
688 # We expect failure when there are missing blobs
689 result = self._RunControl('board0', '-o', self._output_dir)
690 self.assertEqual(100, result)
691 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
692 self.assertTrue(os.path.exists(errfile))
693 self.assertIn(b"Filename 'fsp.bin' not found in input path",
694 tools.read_file(errfile))
695
696 def testBlobsAllowMissing(self):
697 """Allow missing blobs - still failure but a different exit code"""
698 self._missing = True
699 result = self._RunControl('board0', '-o', self._output_dir, '-M',
700 clean_dir=True)
701 self.assertEqual(101, result)
702 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
703 errfile = os.path.join(board0_dir, 'err')
704 self.assertTrue(os.path.exists(errfile))
705 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
706
707 def testBlobsWarning(self):
708 """Allow missing blobs and ignore warnings"""
709 self._missing = True
710 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
711 self.assertEqual(0, result)
712 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
713 errfile = os.path.join(board0_dir, 'err')
714 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
715
716 def testBlobSettings(self):
717 """Test with no settings"""
718 self.assertEqual(False,
719 control.get_allow_missing(False, False, 1, False))
720 self.assertEqual(True,
721 control.get_allow_missing(True, False, 1, False))
722 self.assertEqual(False,
723 control.get_allow_missing(True, True, 1, False))
724
725 def testBlobSettingsAlways(self):
726 """Test the 'always' policy"""
42d42cf1 727 bsettings.set_item('global', 'allow-missing', 'always')
d7713ad3
TR
728 self.assertEqual(True,
729 control.get_allow_missing(False, False, 1, False))
730 self.assertEqual(False,
731 control.get_allow_missing(False, True, 1, False))
732
733 def testBlobSettingsBranch(self):
734 """Test the 'branch' policy"""
42d42cf1 735 bsettings.set_item('global', 'allow-missing', 'branch')
d7713ad3
TR
736 self.assertEqual(False,
737 control.get_allow_missing(False, False, 1, False))
738 self.assertEqual(True,
739 control.get_allow_missing(False, False, 1, True))
740 self.assertEqual(False,
741 control.get_allow_missing(False, True, 1, True))
742
743 def testBlobSettingsMultiple(self):
744 """Test the 'multiple' policy"""
42d42cf1 745 bsettings.set_item('global', 'allow-missing', 'multiple')
d7713ad3
TR
746 self.assertEqual(False,
747 control.get_allow_missing(False, False, 1, False))
748 self.assertEqual(True,
749 control.get_allow_missing(False, False, 2, False))
750 self.assertEqual(False,
751 control.get_allow_missing(False, True, 2, False))
752
753 def testBlobSettingsBranchMultiple(self):
754 """Test the 'branch multiple' policy"""
42d42cf1 755 bsettings.set_item('global', 'allow-missing', 'branch multiple')
d7713ad3
TR
756 self.assertEqual(False,
757 control.get_allow_missing(False, False, 1, False))
758 self.assertEqual(True,
759 control.get_allow_missing(False, False, 1, True))
760 self.assertEqual(True,
761 control.get_allow_missing(False, False, 2, False))
762 self.assertEqual(True,
763 control.get_allow_missing(False, False, 2, True))
764 self.assertEqual(False,
765 control.get_allow_missing(False, True, 2, True))
cd37d5bc 766
93202d72
SG
767 def check_command(self, *extra_args):
768 """Run a command with the extra arguments and return the commands used
769
770 Args:
771 extra_args (list of str): List of extra arguments
772
773 Returns:
774 list of str: Lines returned in the out-cmd file
775 """
776 self._RunControl('-o', self._output_dir, *extra_args)
cd37d5bc
SG
777 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
778 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
779 cmd_fname = os.path.join(board0_dir, 'out-cmd')
780 self.assertTrue(os.path.exists(cmd_fname))
781 data = tools.read_file(cmd_fname)
bfb708ad
SG
782
783 config_fname = os.path.join(board0_dir, '.config')
784 self.assertTrue(os.path.exists(config_fname))
785 cfg_data = tools.read_file(config_fname)
786
787 return data.splitlines(), cfg_data
93202d72
SG
788
789 def testCmdFile(self):
790 """Test that the -cmd-out file is produced"""
bfb708ad 791 lines = self.check_command()[0]
cd37d5bc
SG
792 self.assertEqual(2, len(lines))
793 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
794 self.assertRegex(lines[0], b'make O=/.*-s.*')
93202d72
SG
795
796 def testNoLto(self):
797 """Test that the --no-lto flag works"""
bfb708ad 798 lines = self.check_command('-L')[0]
93202d72
SG
799 self.assertIn(b'NO_LTO=1', lines[0])
800
bfb708ad
SG
801 def testReproducible(self):
802 """Test that the -r flag works"""
803 lines, cfg_data = self.check_command('-r')
804 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
805
806 # We should see CONFIG_LOCALVERSION_AUTO unset
807 self.assertEqual(b'''CONFIG_SOMETHING=1
808# CONFIG_LOCALVERSION_AUTO is not set
809''', cfg_data)
810
811 with test_util.capture_sys_output() as (stdout, stderr):
812 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
813 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
814
815 # We should see CONFIG_LOCALVERSION_AUTO unset
816 self.assertEqual(b'''CONFIG_SOMETHING=1
817CONFIG_LOCALVERSION=y
818''', cfg_data)
819 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())
3350d34f
SG
820
821 def test_scan_defconfigs(self):
822 """Test scanning the defconfigs to obtain all the boards"""
823 src = self._git_dir
824
825 # Scan the test directory which contains a Kconfig and some *_defconfig
826 # files
bec06ed8 827 params, warnings = self._boards.scan_defconfigs(src, src)
3350d34f
SG
828
829 # We should get two boards
357bfca5 830 self.assertEqual(2, len(params))
bec06ed8 831 self.assertFalse(warnings)
3350d34f
SG
832 first = 0 if params[0]['target'] == 'board0' else 1
833 board0 = params[first]
834 board2 = params[1 - first]
835
357bfca5
BM
836 self.assertEqual('arm', board0['arch'])
837 self.assertEqual('armv7', board0['cpu'])
838 self.assertEqual('-', board0['soc'])
839 self.assertEqual('Tester', board0['vendor'])
840 self.assertEqual('ARM Board 0', board0['board'])
841 self.assertEqual('config0', board0['config'])
842 self.assertEqual('board0', board0['target'])
843
844 self.assertEqual('powerpc', board2['arch'])
845 self.assertEqual('ppc', board2['cpu'])
846 self.assertEqual('mpc85xx', board2['soc'])
847 self.assertEqual('Tester', board2['vendor'])
848 self.assertEqual('PowerPC board 1', board2['board'])
849 self.assertEqual('config2', board2['config'])
850 self.assertEqual('board2', board2['target'])
3350d34f 851
bd4ed9f7
SG
852 def test_output_is_new(self):
853 """Test detecting new changes to Kconfig"""
854 base = self._base_dir
855 src = self._git_dir
856 config_dir = os.path.join(src, 'configs')
857 delay = 0.02
858
859 # Create a boards.cfg file
860 boards_cfg = os.path.join(base, 'boards.cfg')
861 content = b'''#
862# List of boards
863# Automatically generated by buildman/boards.py: don't edit
864#
865# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
866
867Active aarch64 armv8 - armltd corstone1000 board0
868Active aarch64 armv8 - armltd total_compute board2
869'''
870 # Check missing file
871 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
872
873 # Check that the board.cfg file is newer
874 time.sleep(delay)
875 tools.write_file(boards_cfg, content)
876 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
877
878 # Touch the Kconfig files after a show delay to avoid a race
879 time.sleep(delay)
880 Path(os.path.join(src, 'Kconfig')).touch()
881 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
882 Path(boards_cfg).touch()
883 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
884
885 # Touch a different Kconfig file
886 time.sleep(delay)
887 Path(os.path.join(src, 'Kconfig.something')).touch()
888 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
889 Path(boards_cfg).touch()
890 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
891
892 # Touch a MAINTAINERS file
893 time.sleep(delay)
894 Path(os.path.join(src, 'MAINTAINERS')).touch()
895 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
896
897 Path(boards_cfg).touch()
898 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
899
900 # Touch a defconfig file
901 time.sleep(delay)
902 Path(os.path.join(config_dir, 'board0_defconfig')).touch()
903 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
904 Path(boards_cfg).touch()
905 self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src))
906
907 # Remove a board and check that the board.cfg file is now older
908 Path(os.path.join(config_dir, 'board0_defconfig')).unlink()
909 self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src))
910
5df95cf1
SG
911 def test_maintainers(self):
912 """Test detecting boards without a MAINTAINERS entry"""
913 src = self._git_dir
914 main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS')
915 other = os.path.join(src, 'boards', 'board2', 'MAINTAINERS')
bec06ed8 916 kc_file = os.path.join(src, 'Kconfig')
5df95cf1
SG
917 config_dir = os.path.join(src, 'configs')
918 params_list, warnings = self._boards.build_board_list(config_dir, src)
919
920 # There should be two boards no warnings
357bfca5 921 self.assertEqual(2, len(params_list))
5df95cf1
SG
922 self.assertFalse(warnings)
923
924 # Set an invalid status line in the file
925 orig_data = tools.read_file(main, binary=False)
926 lines = ['S: Other\n' if line.startswith('S:') else line
927 for line in orig_data.splitlines(keepends=True)]
928 tools.write_file(main, ''.join(lines), binary=False)
929 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5 930 self.assertEqual(2, len(params_list))
5df95cf1
SG
931 params = params_list[0]
932 if params['target'] == 'board2':
933 params = params_list[1]
357bfca5
BM
934 self.assertEqual('-', params['status'])
935 self.assertEqual(["WARNING: Other: unknown status for 'board0'"],
5df95cf1
SG
936 warnings)
937
938 # Remove the status line (S:) from a file
939 lines = [line for line in orig_data.splitlines(keepends=True)
940 if not line.startswith('S:')]
941 tools.write_file(main, ''.join(lines), binary=False)
942 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5
BM
943 self.assertEqual(2, len(params_list))
944 self.assertEqual(["WARNING: -: unknown status for 'board0'"], warnings)
5df95cf1
SG
945
946 # Remove the configs/ line (F:) from a file - this is the last line
947 data = ''.join(orig_data.splitlines(keepends=True)[:-1])
948 tools.write_file(main, data, binary=False)
949 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5
BM
950 self.assertEqual(2, len(params_list))
951 self.assertEqual(["WARNING: no maintainers for 'board0'"], warnings)
5df95cf1 952
9a7cc812
SG
953 # Mark a board as orphaned - this should give a warning
954 lines = ['S: Orphaned' if line.startswith('S') else line
955 for line in orig_data.splitlines(keepends=True)]
956 tools.write_file(main, ''.join(lines), binary=False)
957 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5
BM
958 self.assertEqual(2, len(params_list))
959 self.assertEqual(["WARNING: no maintainers for 'board0'"], warnings)
9a7cc812
SG
960
961 # Change the maintainer to '-' - this should give a warning
962 lines = ['M: -' if line.startswith('M') else line
963 for line in orig_data.splitlines(keepends=True)]
964 tools.write_file(main, ''.join(lines), binary=False)
965 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5
BM
966 self.assertEqual(2, len(params_list))
967 self.assertEqual(["WARNING: -: unknown status for 'board0'"], warnings)
9a7cc812
SG
968
969 # Remove the maintainer line (M:) from a file
5df95cf1
SG
970 lines = [line for line in orig_data.splitlines(keepends=True)
971 if not line.startswith('M:')]
972 tools.write_file(main, ''.join(lines), binary=False)
973 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5
BM
974 self.assertEqual(2, len(params_list))
975 self.assertEqual(["WARNING: no maintainers for 'board0'"], warnings)
5df95cf1
SG
976
977 # Move the contents of the second file into this one, removing the
978 # second file, to check multiple records in a single file.
c649153b
SG
979 both_data = orig_data + tools.read_file(other, binary=False)
980 tools.write_file(main, both_data, binary=False)
5df95cf1
SG
981 os.remove(other)
982 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5 983 self.assertEqual(2, len(params_list))
5df95cf1
SG
984 self.assertFalse(warnings)
985
bc12d034 986 # Add another record, this should be ignored with a warning
5df95cf1 987 extra = '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
c649153b 988 tools.write_file(main, both_data + extra, binary=False)
5df95cf1 989 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5 990 self.assertEqual(2, len(params_list))
48d4c0a8 991 self.assertFalse(warnings)
bec06ed8
SG
992
993 # Add another TARGET to the Kconfig
c649153b 994 tools.write_file(main, both_data, binary=False)
ad99599c 995 orig_kc_data = tools.read_file(kc_file)
bec06ed8
SG
996 extra = (b'''
997if TARGET_BOARD2
998config TARGET_OTHER
999\tbool "other"
1000\tdefault y
1001endif
1002''')
ad99599c 1003 tools.write_file(kc_file, orig_kc_data + extra)
1b21842e
SG
1004 params_list, warnings = self._boards.build_board_list(config_dir, src,
1005 warn_targets=True)
357bfca5
BM
1006 self.assertEqual(2, len(params_list))
1007 self.assertEqual(
bec06ed8
SG
1008 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
1009 warnings)
ad99599c
SG
1010
1011 # Remove the TARGET_BOARD0 Kconfig option
1012 lines = [b'' if line == b'config TARGET_BOARD2\n' else line
1013 for line in orig_kc_data.splitlines(keepends=True)]
1014 tools.write_file(kc_file, b''.join(lines))
1b21842e
SG
1015 params_list, warnings = self._boards.build_board_list(config_dir, src,
1016 warn_targets=True)
357bfca5
BM
1017 self.assertEqual(2, len(params_list))
1018 self.assertEqual(
ad99599c
SG
1019 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
1020 warnings)
c649153b
SG
1021 tools.write_file(kc_file, orig_kc_data)
1022
1023 # Replace the last F: line of board 2 with an N: line
1024 data = ''.join(both_data.splitlines(keepends=True)[:-1])
1025 tools.write_file(main, data + 'N: oa.*2\n', binary=False)
1026 params_list, warnings = self._boards.build_board_list(config_dir, src)
357bfca5 1027 self.assertEqual(2, len(params_list))
c649153b
SG
1028 self.assertFalse(warnings)
1029
a1431e6c
SG
1030 def testRegenBoards(self):
1031 """Test that we can regenerate the boards.cfg file"""
1032 outfile = os.path.join(self._output_dir, 'test-boards.cfg')
1033 if os.path.exists(outfile):
1034 os.remove(outfile)
1035 with test_util.capture_sys_output() as (stdout, stderr):
1036 result = self._RunControl('-R', outfile, brds=None,
1037 get_builder=False)
1038 self.assertTrue(os.path.exists(outfile))
d4366b11
SG
1039
1040 def test_print_prefix(self):
1041 """Test that we can print the toolchain prefix"""
1042 with test_util.capture_sys_output() as (stdout, stderr):
1043 result = self._RunControl('-A', 'board0')
1044 self.assertEqual('arm-\n', stdout.getvalue())
1045 self.assertEqual('', stderr.getvalue())
26d9077c
SG
1046
1047 def test_exclude_one(self):
1048 """Test excluding a single board from an arch"""
ad1c9b26 1049 self._RunControl('arm', '-x', 'board1', '-o', self._output_dir)
26d9077c
SG
1050 self.assertEqual(['board0'],
1051 [b.target for b in self._boards.get_selected()])
1052
1053 def test_exclude_arch(self):
1054 """Test excluding an arch"""
ad1c9b26 1055 self._RunControl('-x', 'arm', '-o', self._output_dir)
26d9077c
SG
1056 self.assertEqual(['board2', 'board4'],
1057 [b.target for b in self._boards.get_selected()])
1058
1059 def test_exclude_comma(self):
1060 """Test excluding a comma-separated list of things"""
ad1c9b26 1061 self._RunControl('-x', 'arm,powerpc', '-o', self._output_dir)
26d9077c
SG
1062 self.assertEqual(['board4'],
1063 [b.target for b in self._boards.get_selected()])
1064
1065 def test_exclude_list(self):
1066 """Test excluding a list of things"""
ad1c9b26 1067 self._RunControl('-x', 'board2', '-x' 'board4', '-o', self._output_dir)
26d9077c
SG
1068 self.assertEqual(['board0', 'board1'],
1069 [b.target for b in self._boards.get_selected()])
6a0c7b4a
SG
1070
1071 def test_single_boards(self):
1072 """Test building single boards"""
ad1c9b26 1073 self._RunControl('--boards', 'board1', '-o', self._output_dir)
6a0c7b4a
SG
1074 self.assertEqual(1, self._builder.count)
1075
ad1c9b26
SG
1076 self._RunControl('--boards', 'board1', '--boards', 'board2',
1077 '-o', self._output_dir)
6a0c7b4a
SG
1078 self.assertEqual(2, self._builder.count)
1079
ad1c9b26
SG
1080 self._RunControl('--boards', 'board1,board2', '--boards', 'board4',
1081 '-o', self._output_dir)
6a0c7b4a 1082 self.assertEqual(3, self._builder.count)
ad037874
SG
1083
1084 def test_print_arch(self):
1085 """Test that we can print the board architecture"""
1086 with test_util.capture_sys_output() as (stdout, stderr):
1087 result = self._RunControl('--print-arch', 'board0')
1088 self.assertEqual('arm\n', stdout.getvalue())
1089 self.assertEqual('', stderr.getvalue())
a63fcdb9
SG
1090
1091 def test_kconfig_scanner(self):
1092 """Test using the kconfig scanner to determine important values
1093
1094 Note that there is already a test_scan_defconfigs() which checks the
1095 higher-level scan_defconfigs() function. This test checks just the
1096 scanner itself
1097 """
1098 src = self._git_dir
1099 scanner = boards.KconfigScanner(src)
1100
1101 # First do a simple sanity check
1102 norm = os.path.join(src, 'board0_defconfig')
1103 tools.write_file(norm, 'CONFIG_TARGET_BOARD0=y', False)
1104 res = scanner.scan(norm, True)
1105 self.assertEqual(({
1106 'arch': 'arm',
1107 'cpu': 'armv7',
1108 'soc': '-',
1109 'vendor': 'Tester',
1110 'board': 'ARM Board 0',
1111 'config': 'config0',
1112 'target': 'board0'}, []), res)
1113
1114 # Check that the SoC cannot be changed and the filename does not affect
1115 # the resulting board
1116 tools.write_file(norm, '''CONFIG_TARGET_BOARD2=y
1117CONFIG_SOC="fred"
1118''', False)
1119 res = scanner.scan(norm, True)
1120 self.assertEqual(({
1121 'arch': 'powerpc',
1122 'cpu': 'ppc',
1123 'soc': 'mpc85xx',
1124 'vendor': 'Tester',
1125 'board': 'PowerPC board 1',
1126 'config': 'config2',
1127 'target': 'board0'}, []), res)
1128
1129 # Check handling of missing information
1130 tools.write_file(norm, '', False)
1131 res = scanner.scan(norm, True)
1132 self.assertEqual(({
1133 'arch': '-',
1134 'cpu': '-',
1135 'soc': '-',
1136 'vendor': '-',
1137 'board': '-',
1138 'config': '-',
1139 'target': 'board0'},
1140 ['WARNING: board0_defconfig: No TARGET_BOARD0 enabled']), res)
6bf74a2e
SG
1141
1142 # check handling of #include files; see _HandleCommandCpp()
1143 inc = os.path.join(src, 'common')
1144 tools.write_file(inc, b'CONFIG_TARGET_BOARD0=y\n')
1145 tools.write_file(norm, f'#include <{inc}>', False)
1146 res = scanner.scan(norm, True)
1147 self.assertEqual(({
1148 'arch': 'arm',
1149 'cpu': 'armv7',
1150 'soc': '-',
1151 'vendor': 'Tester',
1152 'board': 'ARM Board 0',
1153 'config': 'config0',
1154 'target': 'board0'}, []), res)