]>
git.ipfire.org Git - thirdparty/u-boot.git/blob - tools/buildman/func_test.py
b45eb95a1e6aafdf7da0e1f2e2ace36db794776d
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2014 Google, Inc
7 from pathlib
import Path
15 from buildman
import board
16 from buildman
import boards
17 from buildman
import bsettings
18 from buildman
import cmdline
19 from buildman
import control
20 from buildman
import toolchain
21 from u_boot_pylib
import command
22 from u_boot_pylib
import gitutil
23 from u_boot_pylib
import terminal
24 from u_boot_pylib
import test_util
25 from u_boot_pylib
import tools
28 # Buildman settings file
37 chroot=/home/sjg/c/chroot
38 vboot=VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
39 chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
40 chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
41 chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
45 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''],
46 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''],
47 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
48 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
51 commit_shortlog
= """4aca821 patman: Avoid changing the order of tags
52 39403bb patman: Use --no-pager' to stop git from forking a pager
53 db6e6f2 patman: Remove the -a option
54 f2ccf03 patman: Correct unit tests to run correctly
55 1d097f9 patman: Fix indentation in terminal.py
56 d073747 patman: Support the 'reverse' option for 'git log
59 commit_log
= ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
60 Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
61 Date: Fri Aug 22 19:12:41 2014 +0900
63 buildman: refactor help message
65 "buildman [options]" is displayed by default.
67 Append the rest of help messages to parser.usage
68 instead of replacing it.
70 Besides, "-b <branch>" is not mandatory since commit fea5858e.
71 Drop it from the usage.
73 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
75 """commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
76 Author: Simon Glass <sjg@chromium.org>
77 Date: Thu Aug 14 16:48:25 2014 -0600
79 patman: Support the 'reverse' option for 'git log'
81 This option is currently not supported, but needs to be, for buildman to
85 - Add new patch to fix the 'reverse' bug
89 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
90 Reported-by: York Sun <yorksun@freescale.com>
91 Signed-off-by: Simon Glass <sjg@chromium.org>
94 """commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
95 Author: Simon Glass <sjg@chromium.org>
96 Date: Sat Aug 9 11:44:32 2014 -0600
98 patman: Fix indentation in terminal.py
100 This code came from a different project with 2-character indentation. Fix
104 - Add new patch to fix indentation in teminal.py
106 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
107 Signed-off-by: Simon Glass <sjg@chromium.org>
110 """commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
111 Author: Simon Glass <sjg@chromium.org>
112 Date: Sat Aug 9 11:08:24 2014 -0600
114 patman: Correct unit tests to run correctly
116 It seems that doctest behaves differently now, and some of the unit tests
117 do not run. Adjust the tests to work correctly.
119 ./tools/patman/patman --test
120 <unittest.result.TestResult run=10 errors=0 failures=0>
123 - Add new patch to fix patman unit tests
125 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
128 """commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
129 Author: Simon Glass <sjg@chromium.org>
130 Date: Sat Aug 9 12:06:02 2014 -0600
132 patman: Remove the -a option
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.
139 - Add new patch to remove patman's -a option
141 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
142 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
145 """commit 39403bb4f838153028a6f21ca30bf100f3791133
146 Author: Simon Glass <sjg@chromium.org>
147 Date: Thu Aug 14 21:50:52 2014 -0600
149 patman: Use --no-pager' to stop git from forking a pager
152 """commit 4aca821e27e97925c039e69fd37375b09c6f129c
153 Author: Simon Glass <sjg@chromium.org>
154 Date: Fri Aug 22 15:57:39 2014 -0600
156 patman: Avoid changing the order of tags
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.
163 - Add new patch to avoid changing the order of tags
167 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
168 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
171 TEST_BRANCH
= '__testbranch'
173 class TestFunctional(unittest
.TestCase
):
174 """Functional test for buildman.
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.
185 self
._base
_dir
= tempfile
.mkdtemp()
186 self
._output
_dir
= tempfile
.mkdtemp()
187 self
._git
_dir
= os
.path
.join(self
._base
_dir
, 'src')
188 self
._buildman
_pathname
= sys
.argv
[0]
189 self
._buildman
_dir
= os
.path
.dirname(os
.path
.realpath(sys
.argv
[0]))
190 command
.TEST_RESULT
= self
._HandleCommand
191 bsettings
.setup(None)
192 bsettings
.add_file(settings_data
)
193 self
.setupToolchains()
194 self
._toolchains
.Add('arm-gcc', test
=False)
195 self
._toolchains
.Add('powerpc-gcc', test
=False)
196 self
._boards
= boards
.Boards()
198 self
._boards
.add_board(board
.Board(*brd
))
200 # Directories where the source been cloned
201 self
._clone
_dirs
= []
202 self
._commits
= len(commit_shortlog
.splitlines()) + 1
203 self
._total
_builds
= self
._commits
* len(BOARDS
)
205 # Number of calls to make
208 # Map of [board, commit] to error messages
211 self
._test
_branch
= TEST_BRANCH
213 # Set to True to report missing blobs
214 self
._missing
= False
216 self
._buildman
_dir
= os
.path
.dirname(os
.path
.realpath(sys
.argv
[0]))
217 self
._test
_dir
= os
.path
.join(self
._buildman
_dir
, 'test')
219 # Set up some fake source files
220 shutil
.copytree(self
._test
_dir
, self
._git
_dir
)
222 # Avoid sending any output and clear all terminal output
223 terminal
.set_print_test_mode()
224 terminal
.get_print_test_lines()
227 shutil
.rmtree(self
._base
_dir
)
228 shutil
.rmtree(self
._output
_dir
)
230 def setupToolchains(self
):
231 self
._toolchains
= toolchain
.Toolchains()
232 self
._toolchains
.Add('gcc', test
=False)
234 def _RunBuildman(self
, *args
):
235 all_args
= [self
._buildman
_pathname
] + list(args
)
236 return command
.run_one(*all_args
, capture
=True, capture_stderr
=True)
238 def _RunControl(self
, *args
, brds
=False, clean_dir
=False,
239 test_thread_exceptions
=False, get_builder
=True):
243 args: List of arguments to pass
244 brds: Boards object, or False to pass self._boards, or None to pass
246 clean_dir: Used for tests only, indicates that the existing output_dir
247 should be removed before starting the build
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
251 get_builder (bool): Set self._builder to the resulting builder
254 result code from buildman
256 sys
.argv
= [sys
.argv
[0]] + list(args
)
257 args
= cmdline
.parse_args()
260 result
= control
.do_buildman(
261 args
, toolchains
=self
._toolchains
, make_func
=self
._HandleMake
,
262 brds
=brds
, clean_dir
=clean_dir
,
263 test_thread_exceptions
=test_thread_exceptions
)
265 self
._builder
= control
.TEST_BUILDER
268 def testFullHelp(self
):
269 command
.TEST_RESULT
= None
270 result
= self
._RunBuildman
('-H')
271 help_file
= os
.path
.join(self
._buildman
_dir
, 'README.rst')
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
))
276 self
.assertEqual(0, len(result
.stderr
))
277 self
.assertEqual(0, result
.return_code
)
280 command
.TEST_RESULT
= None
281 result
= self
._RunBuildman
('-h')
282 help_file
= os
.path
.join(self
._buildman
_dir
, 'README.rst')
283 self
.assertTrue(len(result
.stdout
) > 1000)
284 self
.assertEqual(0, len(result
.stderr
))
285 self
.assertEqual(0, result
.return_code
)
287 def testGitSetup(self
):
288 """Test gitutils.Setup(), from outside the module itself"""
289 command
.TEST_RESULT
= command
.CommandResult(return_code
=1)
291 self
.assertEqual(gitutil
.USE_NO_DECORATE
, False)
293 command
.TEST_RESULT
= command
.CommandResult(return_code
=0)
295 self
.assertEqual(gitutil
.USE_NO_DECORATE
, True)
297 def _HandleCommandGitLog(self
, args
):
301 return command
.CommandResult(return_code
=0)
302 elif args
[-1] == 'upstream/master..%s' % self
._test
_branch
:
303 return command
.CommandResult(return_code
=0, stdout
=commit_shortlog
)
304 elif args
[:3] == ['--no-color', '--no-decorate', '--reverse']:
305 if args
[-1] == self
._test
_branch
:
306 count
= int(args
[3][2:])
307 return command
.CommandResult(return_code
=0,
308 stdout
=''.join(commit_log
[:count
]))
310 # Not handled, so abort
311 print('git log', args
)
314 def _HandleCommandGitConfig(self
, args
):
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)
320 elif config
== 'branch.%s.remote' % self
._test
_branch
:
321 return command
.CommandResult(return_code
=0, stdout
='upstream\n')
322 elif config
== 'branch.%s.merge' % self
._test
_branch
:
323 return command
.CommandResult(return_code
=0,
324 stdout
='refs/heads/master\n')
326 # Not handled, so abort
327 print('git config', args
)
330 def _HandleCommandGit(self
, in_args
):
331 """Handle execution of a git command
333 This uses a hacked-up parser.
336 in_args: Arguments after 'git' from the command line
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
347 if git_args
and git_args
[-1] in ['--git-dir', '--work-tree']:
351 if sub_cmd
== 'config':
352 return self
._HandleCommandGitConfig
(args
)
353 elif sub_cmd
== 'log':
354 return self
._HandleCommandGitLog
(args
)
355 elif sub_cmd
== 'clone':
356 return command
.CommandResult(return_code
=0)
357 elif sub_cmd
== 'checkout':
358 return command
.CommandResult(return_code
=0)
359 elif sub_cmd
== 'worktree':
360 return command
.CommandResult(return_code
=0)
362 # Not handled, so abort
363 print('git', git_args
, sub_cmd
, args
)
366 def _HandleCommandNm(self
, args
):
367 return command
.CommandResult(return_code
=0)
369 def _HandleCommandObjdump(self
, args
):
370 return command
.CommandResult(return_code
=0)
372 def _HandleCommandObjcopy(self
, args
):
373 return command
.CommandResult(return_code
=0)
375 def _HandleCommandSize(self
, args
):
376 return command
.CommandResult(return_code
=0)
378 def _HandleCommandCpp(self
, args
):
379 # args ['-nostdinc', '-P', '-I', '/tmp/tmp7f17xk_o/src', '-undef',
380 # '-x', 'assembler-with-cpp', fname]
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
)
391 print(line
, file=buf
)
392 return command
.CommandResult(stdout
=buf
.getvalue(), return_code
=0)
394 def _HandleCommand(self
, **kwargs
):
395 """Handle a command execution.
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
402 A CommandResult object
404 pipe_list
= kwargs
['pipe_list']
406 if len(pipe_list
) != 1:
407 if pipe_list
[1] == ['wc', '-l']:
410 print('invalid pipe', kwargs
)
412 cmd
= pipe_list
[0][0]
413 args
= pipe_list
[0][1:]
416 result
= self
._HandleCommandGit
(args
)
417 elif cmd
== './scripts/show-gnu-make':
418 return command
.CommandResult(return_code
=0, stdout
='make')
419 elif cmd
.endswith('nm'):
420 return self
._HandleCommandNm
(args
)
421 elif cmd
.endswith('objdump'):
422 return self
._HandleCommandObjdump
(args
)
423 elif cmd
.endswith('objcopy'):
424 return self
._HandleCommandObjcopy
(args
)
425 elif cmd
.endswith( 'size'):
426 return self
._HandleCommandSize
(args
)
427 elif cmd
.endswith( 'cpp'):
428 return self
._HandleCommandCpp
(args
)
431 # Not handled, so abort
432 print('unknown command', kwargs
)
436 result
.stdout
= len(result
.stdout
.splitlines())
439 def _HandleMake(self
, commit
, brd
, stage
, cwd
, *args
, **kwargs
):
440 """Handle execution of 'make'
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
448 kwargs: Arguments to pass to command.run_one()
450 self
._make
_calls
+= 1
453 if arg
.startswith('O='):
455 if stage
== 'mrproper':
456 return command
.CommandResult(return_code
=0)
457 elif stage
== 'config':
458 fname
= os
.path
.join(cwd
or '', out_dir
, '.config')
459 tools
.write_file(fname
, b
'CONFIG_SOMETHING=1')
460 return command
.CommandResult(return_code
=0,
461 combined
='Test configuration complete')
462 elif stage
== 'oldconfig':
463 return command
.CommandResult(return_code
=0)
464 elif stage
== 'build':
466 fname
= os
.path
.join(cwd
or '', out_dir
, 'u-boot')
467 tools
.write_file(fname
, b
'U-Boot')
469 # Handle missing blobs
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
473 Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
475 Some images are invalid'''
477 stderr
= "binman: Filename 'fsp.bin' not found in input path"
478 elif type(commit
) is not str:
479 stderr
= self
._error
.get((brd
.target
, commit
.sequence
))
482 return command
.CommandResult(return_code
=2, stderr
=stderr
)
483 return command
.CommandResult(return_code
=0)
485 # Not handled, so abort
486 print('_HandleMake failure: make', stage
)
489 # Example function to print output lines
490 def print_lines(self
, lines
):
494 #self.print_lines(terminal.get_print_test_lines())
496 def testNoBoards(self
):
497 """Test that buildman aborts when there are no boards"""
498 self
._boards
= boards
.Boards()
499 with self
.assertRaises(SystemExit):
502 def testCurrentSource(self
):
503 """Very simple test to invoke buildman on the current source"""
504 self
.setupToolchains();
505 self
._RunControl
('-o', self
._output
_dir
)
506 lines
= terminal
.get_print_test_lines()
507 self
.assertIn('Building current source for %d boards' % len(BOARDS
),
510 def testBadBranch(self
):
511 """Test that we can detect an invalid branch"""
512 with self
.assertRaises(ValueError):
513 self
._RunControl
('-b', 'badbranch')
515 def testBadToolchain(self
):
516 """Test that missing toolchains are detected"""
517 self
.setupToolchains();
518 ret_code
= self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
)
519 lines
= terminal
.get_print_test_lines()
521 # Buildman always builds the upstream commit as well
522 self
.assertIn('Building %d commits for %d boards' %
523 (self
._commits
, len(BOARDS
)), lines
[0].text
)
524 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
526 # Only sandbox should succeed, the others don't have toolchains
527 self
.assertEqual(self
._builder
.fail
,
528 self
._total
_builds
- self
._commits
)
529 self
.assertEqual(ret_code
, 100)
531 for commit
in range(self
._commits
):
532 for brd
in self
._boards
.get_list():
533 if brd
.arch
!= 'sandbox':
534 errfile
= self
._builder
.get_err_file(commit
, brd
.target
)
538 [f
'Tool chain error for {brd.arch}: '
539 f
"No tool chain found for arch '{brd.arch}'"])
542 def testBranch(self
):
543 """Test building a branch with all toolchains present"""
544 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
)
545 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
546 self
.assertEqual(self
._builder
.fail
, 0)
549 """Test building a specific number of commitst"""
550 self
._RunControl
('-b', TEST_BRANCH
, '-c2', '-o', self
._output
_dir
)
551 self
.assertEqual(self
._builder
.count
, 2 * len(BOARDS
))
552 self
.assertEqual(self
._builder
.fail
, 0)
553 # Each board has a config, and then one make per commit
554 self
.assertEqual(self
._make
_calls
, len(BOARDS
) * (1 + 2))
556 def testIncremental(self
):
557 """Test building a branch twice - the second time should do nothing"""
558 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
)
560 # Each board has a mrproper, config, and then one make per commit
561 self
.assertEqual(self
._make
_calls
, len(BOARDS
) * (self
._commits
+ 1))
563 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
, clean_dir
=False)
564 self
.assertEqual(self
._make
_calls
, 0)
565 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
566 self
.assertEqual(self
._builder
.fail
, 0)
568 def testForceBuild(self
):
569 """The -f flag should force a rebuild"""
570 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
)
572 self
._RunControl
('-b', TEST_BRANCH
, '-f', '-o', self
._output
_dir
, clean_dir
=False)
573 # Each board has a config and one make per commit
574 self
.assertEqual(self
._make
_calls
, len(BOARDS
) * (self
._commits
+ 1))
576 def testForceReconfigure(self
):
577 """The -f flag should force a rebuild"""
578 self
._RunControl
('-b', TEST_BRANCH
, '-C', '-o', self
._output
_dir
)
579 # Each commit has a config and make
580 self
.assertEqual(self
._make
_calls
, len(BOARDS
) * self
._commits
* 2)
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
586 self
.assertEqual(self
._make
_calls
, len(BOARDS
) * (self
._commits
+ 2))
588 def testErrors(self
):
589 """Test handling of build errors"""
590 self
._error
['board2', 1] = 'fred\n'
591 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
)
592 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
593 self
.assertEqual(self
._builder
.fail
, 1)
595 # Remove the error. This should have no effect since the commit will
597 del self
._error
['board2', 1]
599 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
, clean_dir
=False)
600 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
601 self
.assertEqual(self
._make
_calls
, 0)
602 self
.assertEqual(self
._builder
.fail
, 1)
604 # Now use the -F flag to force rebuild of the bad commit
605 self
._RunControl
('-b', TEST_BRANCH
, '-o', self
._output
_dir
, '-F', clean_dir
=False)
606 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
607 self
.assertEqual(self
._builder
.fail
, 0)
608 self
.assertEqual(self
._make
_calls
, 2)
610 def testBranchWithSlash(self
):
611 """Test building a branch with a '/' in the name"""
612 self
._test
_branch
= '/__dev/__testbranch'
613 self
._RunControl
('-b', self
._test
_branch
, '-o', self
._output
_dir
,
615 self
.assertEqual(self
._builder
.count
, self
._total
_builds
)
616 self
.assertEqual(self
._builder
.fail
, 0)
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')))
625 def testEnvironmentUnicode(self
):
626 """Test there are no unicode errors when the env has non-ASCII chars"""
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')))
635 del os
.environb
[varname
]
637 def testWorkInOutput(self
):
638 """Test the -w option which should write directly to the output dir"""
639 board_list
= boards
.Boards()
640 board_list
.add_board(board
.Board(*BOARDS
[0]))
641 self
._RunControl
('-o', self
._output
_dir
, '-w', clean_dir
=False,
644 os
.path
.exists(os
.path
.join(self
._output
_dir
, 'u-boot')))
646 os
.path
.exists(os
.path
.join(self
._output
_dir
, 'done')))
648 os
.path
.exists(os
.path
.join(self
._output
_dir
, 'out-env')))
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
))
656 os
.path
.exists(os
.path
.join(self
._output
_dir
, 'u-boot')))
658 board_list
= boards
.Boards()
659 board_list
.add_board(board
.Board(*BOARDS
[0]))
660 with self
.assertRaises(SystemExit) as e
:
661 self
._RunControl
('-b', self
._test
_branch
, '-o', self
._output
_dir
,
662 '-w', clean_dir
=False, brds
=board_list
)
663 self
.assertIn("single commit", str(e
.exception
))
665 board_list
= boards
.Boards()
666 board_list
.add_board(board
.Board(*BOARDS
[0]))
667 with self
.assertRaises(SystemExit) as e
:
668 self
._RunControl
('-w', clean_dir
=False)
669 self
.assertIn("specify -o", str(e
.exception
))
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))
677 'Thread exception (use -T0 to run without threads): test exception',
681 """Test handling of missing blobs"""
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')
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
))
696 def testBlobsAllowMissing(self
):
697 """Allow missing blobs - still failure but a different exit code"""
699 result
= self
._RunControl
('board0', '-o', self
._output
_dir
, '-M',
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
))
707 def testBlobsWarning(self
):
708 """Allow missing blobs and ignore warnings"""
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
))
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))
725 def testBlobSettingsAlways(self
):
726 """Test the 'always' policy"""
727 bsettings
.set_item('global', 'allow-missing', 'always')
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))
733 def testBlobSettingsBranch(self
):
734 """Test the 'branch' policy"""
735 bsettings
.set_item('global', 'allow-missing', 'branch')
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))
743 def testBlobSettingsMultiple(self
):
744 """Test the 'multiple' policy"""
745 bsettings
.set_item('global', 'allow-missing', 'multiple')
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))
753 def testBlobSettingsBranchMultiple(self
):
754 """Test the 'branch multiple' policy"""
755 bsettings
.set_item('global', 'allow-missing', 'branch multiple')
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))
767 def check_command(self
, *extra_args
):
768 """Run a command with the extra arguments and return the commands used
771 extra_args (list of str): List of extra arguments
774 list of str: Lines returned in the out-cmd file
776 self
._RunControl
('-o', self
._output
_dir
, *extra_args
)
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
)
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
)
787 return data
.splitlines(), cfg_data
789 def testCmdFile(self
):
790 """Test that the -cmd-out file is produced"""
791 lines
= self
.check_command()[0]
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.*')
797 """Test that the --no-lto flag works"""
798 lines
= self
.check_command('-L')[0]
799 self
.assertIn(b
'NO_LTO=1', lines
[0])
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])
806 # We should see CONFIG_LOCALVERSION_AUTO unset
807 self
.assertEqual(b
'''CONFIG_SOMETHING=1
808 # CONFIG_LOCALVERSION_AUTO is not set
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])
815 # We should see CONFIG_LOCALVERSION_AUTO unset
816 self
.assertEqual(b
'''CONFIG_SOMETHING=1
817 CONFIG_LOCALVERSION=y
819 self
.assertIn('Not dropping LOCALVERSION_AUTO', stdout
.getvalue())
821 def test_scan_defconfigs(self
):
822 """Test scanning the defconfigs to obtain all the boards"""
825 # Scan the test directory which contains a Kconfig and some *_defconfig
827 params
, warnings
= self
._boards
.scan_defconfigs(src
, src
)
829 # We should get two boards
830 self
.assertEqual(2, len(params
))
831 self
.assertFalse(warnings
)
832 first
= 0 if params
[0]['target'] == 'board0' else 1
833 board0
= params
[first
]
834 board2
= params
[1 - first
]
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'])
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'])
852 def test_output_is_new(self
):
853 """Test detecting new changes to Kconfig"""
854 base
= self
._base
_dir
856 config_dir
= os
.path
.join(src
, 'configs')
859 # Create a boards.cfg file
860 boards_cfg
= os
.path
.join(base
, 'boards.cfg')
863 # Automatically generated by buildman/boards.py: don't edit
865 # Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers
867 Active aarch64 armv8 - armltd corstone1000 board0
868 Active aarch64 armv8 - armltd total_compute board2
871 self
.assertFalse(boards
.output_is_new(boards_cfg
, config_dir
, src
))
873 # Check that the board.cfg file is newer
875 tools
.write_file(boards_cfg
, content
)
876 self
.assertTrue(boards
.output_is_new(boards_cfg
, config_dir
, src
))
878 # Touch the Kconfig files after a show delay to avoid a race
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
))
885 # Touch a different Kconfig file
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
))
892 # Touch a MAINTAINERS file
894 Path(os
.path
.join(src
, 'MAINTAINERS')).touch()
895 self
.assertFalse(boards
.output_is_new(boards_cfg
, config_dir
, src
))
897 Path(boards_cfg
).touch()
898 self
.assertTrue(boards
.output_is_new(boards_cfg
, config_dir
, src
))
900 # Touch a defconfig file
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
))
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
))
911 def test_maintainers(self
):
912 """Test detecting boards without a MAINTAINERS entry"""
914 main
= os
.path
.join(src
, 'boards', 'board0', 'MAINTAINERS')
915 other
= os
.path
.join(src
, 'boards', 'board2', 'MAINTAINERS')
916 kc_file
= os
.path
.join(src
, 'Kconfig')
917 config_dir
= os
.path
.join(src
, 'configs')
918 params_list
, warnings
= self
._boards
.build_board_list(config_dir
, src
)
920 # There should be two boards no warnings
921 self
.assertEqual(2, len(params_list
))
922 self
.assertFalse(warnings
)
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
)
930 self
.assertEqual(2, len(params_list
))
931 params
= params_list
[0]
932 if params
['target'] == 'board2':
933 params
= params_list
[1]
934 self
.assertEqual('-', params
['status'])
935 self
.assertEqual(["WARNING: Other: unknown status for 'board0'"],
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
)
943 self
.assertEqual(2, len(params_list
))
944 self
.assertEqual(["WARNING: -: unknown status for 'board0'"], warnings
)
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
)
950 self
.assertEqual(2, len(params_list
))
951 self
.assertEqual(["WARNING: no maintainers for 'board0'"], warnings
)
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
)
958 self
.assertEqual(2, len(params_list
))
959 self
.assertEqual(["WARNING: no maintainers for 'board0'"], warnings
)
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
)
966 self
.assertEqual(2, len(params_list
))
967 self
.assertEqual(["WARNING: -: unknown status for 'board0'"], warnings
)
969 # Remove the maintainer line (M:) from a file
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
)
974 self
.assertEqual(2, len(params_list
))
975 self
.assertEqual(["WARNING: no maintainers for 'board0'"], warnings
)
977 # Move the contents of the second file into this one, removing the
978 # second file, to check multiple records in a single file.
979 both_data
= orig_data
+ tools
.read_file(other
, binary
=False)
980 tools
.write_file(main
, both_data
, binary
=False)
982 params_list
, warnings
= self
._boards
.build_board_list(config_dir
, src
)
983 self
.assertEqual(2, len(params_list
))
984 self
.assertFalse(warnings
)
986 # Add another record, this should be ignored with a warning
987 extra
= '\n\nAnother\nM: Fred\nF: configs/board9_defconfig\nS: other\n'
988 tools
.write_file(main
, both_data
+ extra
, binary
=False)
989 params_list
, warnings
= self
._boards
.build_board_list(config_dir
, src
)
990 self
.assertEqual(2, len(params_list
))
991 self
.assertFalse(warnings
)
993 # Add another TARGET to the Kconfig
994 tools
.write_file(main
, both_data
, binary
=False)
995 orig_kc_data
= tools
.read_file(kc_file
)
1003 tools
.write_file(kc_file
, orig_kc_data
+ extra
)
1004 params_list
, warnings
= self
._boards
.build_board_list(config_dir
, src
,
1006 self
.assertEqual(2, len(params_list
))
1008 ['WARNING: board2_defconfig: Duplicate TARGET_xxx: board2 and other'],
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
))
1015 params_list
, warnings
= self
._boards
.build_board_list(config_dir
, src
,
1017 self
.assertEqual(2, len(params_list
))
1019 ['WARNING: board2_defconfig: No TARGET_BOARD2 enabled'],
1021 tools
.write_file(kc_file
, orig_kc_data
)
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
)
1027 self
.assertEqual(2, len(params_list
))
1028 self
.assertFalse(warnings
)
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
):
1035 with test_util
.capture_sys_output() as (stdout
, stderr
):
1036 result
= self
._RunControl
('-R', outfile
, brds
=None,
1038 self
.assertTrue(os
.path
.exists(outfile
))
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())
1047 def test_exclude_one(self
):
1048 """Test excluding a single board from an arch"""
1049 self
._RunControl
('arm', '-x', 'board1', '-o', self
._output
_dir
)
1050 self
.assertEqual(['board0'],
1051 [b
.target
for b
in self
._boards
.get_selected()])
1053 def test_exclude_arch(self
):
1054 """Test excluding an arch"""
1055 self
._RunControl
('-x', 'arm', '-o', self
._output
_dir
)
1056 self
.assertEqual(['board2', 'board4'],
1057 [b
.target
for b
in self
._boards
.get_selected()])
1059 def test_exclude_comma(self
):
1060 """Test excluding a comma-separated list of things"""
1061 self
._RunControl
('-x', 'arm,powerpc', '-o', self
._output
_dir
)
1062 self
.assertEqual(['board4'],
1063 [b
.target
for b
in self
._boards
.get_selected()])
1065 def test_exclude_list(self
):
1066 """Test excluding a list of things"""
1067 self
._RunControl
('-x', 'board2', '-x' 'board4', '-o', self
._output
_dir
)
1068 self
.assertEqual(['board0', 'board1'],
1069 [b
.target
for b
in self
._boards
.get_selected()])
1071 def test_single_boards(self
):
1072 """Test building single boards"""
1073 self
._RunControl
('--boards', 'board1', '-o', self
._output
_dir
)
1074 self
.assertEqual(1, self
._builder
.count
)
1076 self
._RunControl
('--boards', 'board1', '--boards', 'board2',
1077 '-o', self
._output
_dir
)
1078 self
.assertEqual(2, self
._builder
.count
)
1080 self
._RunControl
('--boards', 'board1,board2', '--boards', 'board4',
1081 '-o', self
._output
_dir
)
1082 self
.assertEqual(3, self
._builder
.count
)
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())
1091 def test_kconfig_scanner(self
):
1092 """Test using the kconfig scanner to determine important values
1094 Note that there is already a test_scan_defconfigs() which checks the
1095 higher-level scan_defconfigs() function. This test checks just the
1099 scanner
= boards
.KconfigScanner(src
)
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)
1110 'board': 'ARM Board 0',
1111 'config': 'config0',
1112 'target': 'board0'}, []), res
)
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
1119 res
= scanner
.scan(norm
, True)
1125 'board': 'PowerPC board 1',
1126 'config': 'config2',
1127 'target': 'board0'}, []), res
)
1129 # Check handling of missing information
1130 tools
.write_file(norm
, '', False)
1131 res
= scanner
.scan(norm
, True)
1139 'target': 'board0'},
1140 ['WARNING: board0_defconfig: No TARGET_BOARD0 enabled']), res
)
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)
1152 'board': 'ARM Board 0',
1153 'config': 'config0',
1154 'target': 'board0'}, []), res
)