1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2014 Google, Inc
11 from buildman import board
12 from buildman import bsettings
13 from buildman import cmdline
14 from buildman import control
15 from buildman import toolchain
16 from patman import command
17 from patman import gitutil
18 from patman import terminal
19 from patman import tools
22 # Buildman settings file
30 chroot=/home/sjg/c/chroot
31 vboot=VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
32 chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
33 chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
34 chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
38 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0', ''],
39 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''],
40 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
41 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
44 commit_shortlog = """4aca821 patman: Avoid changing the order of tags
45 39403bb patman: Use --no-pager' to stop git from forking a pager
46 db6e6f2 patman: Remove the -a option
47 f2ccf03 patman: Correct unit tests to run correctly
48 1d097f9 patman: Fix indentation in terminal.py
49 d073747 patman: Support the 'reverse' option for 'git log
52 commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
53 Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
54 Date: Fri Aug 22 19:12:41 2014 +0900
56 buildman: refactor help message
58 "buildman [options]" is displayed by default.
60 Append the rest of help messages to parser.usage
61 instead of replacing it.
63 Besides, "-b <branch>" is not mandatory since commit fea5858e.
64 Drop it from the usage.
66 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
68 """commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
69 Author: Simon Glass <sjg@chromium.org>
70 Date: Thu Aug 14 16:48:25 2014 -0600
72 patman: Support the 'reverse' option for 'git log'
74 This option is currently not supported, but needs to be, for buildman to
78 - Add new patch to fix the 'reverse' bug
82 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
83 Reported-by: York Sun <yorksun@freescale.com>
84 Signed-off-by: Simon Glass <sjg@chromium.org>
87 """commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
88 Author: Simon Glass <sjg@chromium.org>
89 Date: Sat Aug 9 11:44:32 2014 -0600
91 patman: Fix indentation in terminal.py
93 This code came from a different project with 2-character indentation. Fix
97 - Add new patch to fix indentation in teminal.py
99 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
100 Signed-off-by: Simon Glass <sjg@chromium.org>
103 """commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
104 Author: Simon Glass <sjg@chromium.org>
105 Date: Sat Aug 9 11:08:24 2014 -0600
107 patman: Correct unit tests to run correctly
109 It seems that doctest behaves differently now, and some of the unit tests
110 do not run. Adjust the tests to work correctly.
112 ./tools/patman/patman --test
113 <unittest.result.TestResult run=10 errors=0 failures=0>
116 - Add new patch to fix patman unit tests
118 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
121 """commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
122 Author: Simon Glass <sjg@chromium.org>
123 Date: Sat Aug 9 12:06:02 2014 -0600
125 patman: Remove the -a option
127 It seems that this is no longer needed, since checkpatch.pl will catch
128 whitespace problems in patches. Also the option is not widely used, so
129 it seems safe to just remove it.
132 - Add new patch to remove patman's -a option
134 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
135 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
138 """commit 39403bb4f838153028a6f21ca30bf100f3791133
139 Author: Simon Glass <sjg@chromium.org>
140 Date: Thu Aug 14 21:50:52 2014 -0600
142 patman: Use --no-pager' to stop git from forking a pager
145 """commit 4aca821e27e97925c039e69fd37375b09c6f129c
146 Author: Simon Glass <sjg@chromium.org>
147 Date: Fri Aug 22 15:57:39 2014 -0600
149 patman: Avoid changing the order of tags
151 patman collects tags that it sees in the commit and places them nicely
152 sorted at the end of the patch. However, this is not really necessary and
153 in fact is apparently not desirable.
156 - Add new patch to avoid changing the order of tags
160 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
161 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
164 TEST_BRANCH = '__testbranch'
166 class TestFunctional(unittest.TestCase):
167 """Functional test for buildman.
169 This aims to test from just below the invocation of buildman (parsing
170 of arguments) to 'make' and 'git' invocation. It is not a true
171 emd-to-end test, as it mocks git, make and the tool chain. But this
172 makes it easier to detect when the builder is doing the wrong thing,
173 since in many cases this test code will fail. For example, only a
174 very limited subset of 'git' arguments is supported - anything
175 unexpected will fail.
178 self._base_dir = tempfile.mkdtemp()
179 self._output_dir = tempfile.mkdtemp()
180 self._git_dir = os.path.join(self._base_dir, 'src')
181 self._buildman_pathname = sys.argv[0]
182 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
183 command.test_result = self._HandleCommand
184 self.setupToolchains()
185 self._toolchains.Add('arm-gcc', test=False)
186 self._toolchains.Add('powerpc-gcc', test=False)
187 bsettings.Setup(None)
188 bsettings.AddFile(settings_data)
189 self._boards = board.Boards()
191 self._boards.AddBoard(board.Board(*brd))
193 # Directories where the source been cloned
194 self._clone_dirs = []
195 self._commits = len(commit_shortlog.splitlines()) + 1
196 self._total_builds = self._commits * len(boards)
198 # Number of calls to make
201 # Map of [board, commit] to error messages
204 self._test_branch = TEST_BRANCH
206 # Avoid sending any output and clear all terminal output
207 terminal.SetPrintTestMode()
208 terminal.GetPrintTestLines()
211 shutil.rmtree(self._base_dir)
212 #shutil.rmtree(self._output_dir)
214 def setupToolchains(self):
215 self._toolchains = toolchain.Toolchains()
216 self._toolchains.Add('gcc', test=False)
218 def _RunBuildman(self, *args):
219 return command.RunPipe([[self._buildman_pathname] + list(args)],
220 capture=True, capture_stderr=True)
222 def _RunControl(self, *args, clean_dir=False, boards=None):
223 sys.argv = [sys.argv[0]] + list(args)
224 options, args = cmdline.ParseArgs()
225 result = control.DoBuildman(options, args, toolchains=self._toolchains,
226 make_func=self._HandleMake, boards=boards or self._boards,
228 self._builder = control.builder
231 def testFullHelp(self):
232 command.test_result = None
233 result = self._RunBuildman('-H')
234 help_file = os.path.join(self._buildman_dir, 'README')
235 # Remove possible extraneous strings
236 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
237 gothelp = result.stdout.replace(extra, '')
238 self.assertEqual(len(gothelp), os.path.getsize(help_file))
239 self.assertEqual(0, len(result.stderr))
240 self.assertEqual(0, result.return_code)
243 command.test_result = None
244 result = self._RunBuildman('-h')
245 help_file = os.path.join(self._buildman_dir, 'README')
246 self.assertTrue(len(result.stdout) > 1000)
247 self.assertEqual(0, len(result.stderr))
248 self.assertEqual(0, result.return_code)
250 def testGitSetup(self):
251 """Test gitutils.Setup(), from outside the module itself"""
252 command.test_result = command.CommandResult(return_code=1)
254 self.assertEqual(gitutil.use_no_decorate, False)
256 command.test_result = command.CommandResult(return_code=0)
258 self.assertEqual(gitutil.use_no_decorate, True)
260 def _HandleCommandGitLog(self, args):
264 return command.CommandResult(return_code=0)
265 elif args[-1] == 'upstream/master..%s' % self._test_branch:
266 return command.CommandResult(return_code=0, stdout=commit_shortlog)
267 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
268 if args[-1] == self._test_branch:
269 count = int(args[3][2:])
270 return command.CommandResult(return_code=0,
271 stdout=''.join(commit_log[:count]))
273 # Not handled, so abort
274 print('git log', args)
277 def _HandleCommandGitConfig(self, args):
279 if config == 'sendemail.aliasesfile':
280 return command.CommandResult(return_code=0)
281 elif config.startswith('branch.badbranch'):
282 return command.CommandResult(return_code=1)
283 elif config == 'branch.%s.remote' % self._test_branch:
284 return command.CommandResult(return_code=0, stdout='upstream\n')
285 elif config == 'branch.%s.merge' % self._test_branch:
286 return command.CommandResult(return_code=0,
287 stdout='refs/heads/master\n')
289 # Not handled, so abort
290 print('git config', args)
293 def _HandleCommandGit(self, in_args):
294 """Handle execution of a git command
296 This uses a hacked-up parser.
299 in_args: Arguments after 'git' from the command line
301 git_args = [] # Top-level arguments to git itself
302 sub_cmd = None # Git sub-command selected
303 args = [] # Arguments to the git sub-command
310 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
314 if sub_cmd == 'config':
315 return self._HandleCommandGitConfig(args)
316 elif sub_cmd == 'log':
317 return self._HandleCommandGitLog(args)
318 elif sub_cmd == 'clone':
319 return command.CommandResult(return_code=0)
320 elif sub_cmd == 'checkout':
321 return command.CommandResult(return_code=0)
323 # Not handled, so abort
324 print('git', git_args, sub_cmd, args)
327 def _HandleCommandNm(self, args):
328 return command.CommandResult(return_code=0)
330 def _HandleCommandObjdump(self, args):
331 return command.CommandResult(return_code=0)
333 def _HandleCommandObjcopy(self, args):
334 return command.CommandResult(return_code=0)
336 def _HandleCommandSize(self, args):
337 return command.CommandResult(return_code=0)
339 def _HandleCommand(self, **kwargs):
340 """Handle a command execution.
342 The command is in kwargs['pipe-list'], as a list of pipes, each a
343 list of commands. The command should be emulated as required for
347 A CommandResult object
349 pipe_list = kwargs['pipe_list']
351 if len(pipe_list) != 1:
352 if pipe_list[1] == ['wc', '-l']:
355 print('invalid pipe', kwargs)
357 cmd = pipe_list[0][0]
358 args = pipe_list[0][1:]
361 result = self._HandleCommandGit(args)
362 elif cmd == './scripts/show-gnu-make':
363 return command.CommandResult(return_code=0, stdout='make')
364 elif cmd.endswith('nm'):
365 return self._HandleCommandNm(args)
366 elif cmd.endswith('objdump'):
367 return self._HandleCommandObjdump(args)
368 elif cmd.endswith('objcopy'):
369 return self._HandleCommandObjcopy(args)
370 elif cmd.endswith( 'size'):
371 return self._HandleCommandSize(args)
374 # Not handled, so abort
375 print('unknown command', kwargs)
379 result.stdout = len(result.stdout.splitlines())
382 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
383 """Handle execution of 'make'
386 commit: Commit object that is being built
387 brd: Board object that is being built
388 stage: Stage that we are at (mrproper, config, build)
389 cwd: Directory where make should be run
390 args: Arguments to pass to make
391 kwargs: Arguments to pass to command.RunPipe()
393 self._make_calls += 1
394 if stage == 'mrproper':
395 return command.CommandResult(return_code=0)
396 elif stage == 'config':
397 return command.CommandResult(return_code=0,
398 combined='Test configuration complete')
399 elif stage == 'build':
403 if arg.startswith('O='):
405 fname = os.path.join(cwd or '', out_dir, 'u-boot')
406 tools.WriteFile(fname, b'U-Boot')
407 if type(commit) is not str:
408 stderr = self._error.get((brd.target, commit.sequence))
410 return command.CommandResult(return_code=1, stderr=stderr)
411 return command.CommandResult(return_code=0)
413 # Not handled, so abort
417 # Example function to print output lines
418 def print_lines(self, lines):
422 #self.print_lines(terminal.GetPrintTestLines())
424 def testNoBoards(self):
425 """Test that buildman aborts when there are no boards"""
426 self._boards = board.Boards()
427 with self.assertRaises(SystemExit):
430 def testCurrentSource(self):
431 """Very simple test to invoke buildman on the current source"""
432 self.setupToolchains();
433 self._RunControl('-o', self._output_dir)
434 lines = terminal.GetPrintTestLines()
435 self.assertIn('Building current source for %d boards' % len(boards),
438 def testBadBranch(self):
439 """Test that we can detect an invalid branch"""
440 with self.assertRaises(ValueError):
441 self._RunControl('-b', 'badbranch')
443 def testBadToolchain(self):
444 """Test that missing toolchains are detected"""
445 self.setupToolchains();
446 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
447 lines = terminal.GetPrintTestLines()
449 # Buildman always builds the upstream commit as well
450 self.assertIn('Building %d commits for %d boards' %
451 (self._commits, len(boards)), lines[0].text)
452 self.assertEqual(self._builder.count, self._total_builds)
454 # Only sandbox should succeed, the others don't have toolchains
455 self.assertEqual(self._builder.fail,
456 self._total_builds - self._commits)
457 self.assertEqual(ret_code, 100)
459 for commit in range(self._commits):
460 for board in self._boards.GetList():
461 if board.arch != 'sandbox':
462 errfile = self._builder.GetErrFile(commit, board.target)
464 self.assertEqual(fd.readlines(),
465 ['No tool chain for %s\n' % board.arch])
468 def testBranch(self):
469 """Test building a branch with all toolchains present"""
470 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
471 self.assertEqual(self._builder.count, self._total_builds)
472 self.assertEqual(self._builder.fail, 0)
475 """Test building a specific number of commitst"""
476 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
477 self.assertEqual(self._builder.count, 2 * len(boards))
478 self.assertEqual(self._builder.fail, 0)
479 # Each board has a config, and then one make per commit
480 self.assertEqual(self._make_calls, len(boards) * (1 + 2))
482 def testIncremental(self):
483 """Test building a branch twice - the second time should do nothing"""
484 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
486 # Each board has a mrproper, config, and then one make per commit
487 self.assertEqual(self._make_calls, len(boards) * (self._commits + 1))
489 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
490 self.assertEqual(self._make_calls, 0)
491 self.assertEqual(self._builder.count, self._total_builds)
492 self.assertEqual(self._builder.fail, 0)
494 def testForceBuild(self):
495 """The -f flag should force a rebuild"""
496 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
498 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
499 # Each board has a config and one make per commit
500 self.assertEqual(self._make_calls, len(boards) * (self._commits + 1))
502 def testForceReconfigure(self):
503 """The -f flag should force a rebuild"""
504 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
505 # Each commit has a config and make
506 self.assertEqual(self._make_calls, len(boards) * self._commits * 2)
508 def testForceReconfigure(self):
509 """The -f flag should force a rebuild"""
510 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
511 # Each commit has a config and make
512 self.assertEqual(self._make_calls, len(boards) * self._commits * 2)
514 def testMrproper(self):
515 """The -f flag should force a rebuild"""
516 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
517 # Each board has a mkproper, config and then one make per commit
518 self.assertEqual(self._make_calls, len(boards) * (self._commits + 2))
520 def testErrors(self):
521 """Test handling of build errors"""
522 self._error['board2', 1] = 'fred\n'
523 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
524 self.assertEqual(self._builder.count, self._total_builds)
525 self.assertEqual(self._builder.fail, 1)
527 # Remove the error. This should have no effect since the commit will
529 del self._error['board2', 1]
531 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
532 self.assertEqual(self._builder.count, self._total_builds)
533 self.assertEqual(self._make_calls, 0)
534 self.assertEqual(self._builder.fail, 1)
536 # Now use the -F flag to force rebuild of the bad commit
537 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
538 self.assertEqual(self._builder.count, self._total_builds)
539 self.assertEqual(self._builder.fail, 0)
540 self.assertEqual(self._make_calls, 2)
542 def testBranchWithSlash(self):
543 """Test building a branch with a '/' in the name"""
544 self._test_branch = '/__dev/__testbranch'
545 self._RunControl('-b', self._test_branch, clean_dir=False)
546 self.assertEqual(self._builder.count, self._total_builds)
547 self.assertEqual(self._builder.fail, 0)
549 def testEnvironment(self):
550 """Test that the done and environment files are written to out-env"""
551 self._RunControl('-o', self._output_dir)
552 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
553 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
554 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
556 def testWorkInOutput(self):
557 """Test the -w option which should write directly to the output dir"""
558 board_list = board.Boards()
559 board_list.AddBoard(board.Board(*boards[0]))
560 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
563 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
565 os.path.exists(os.path.join(self._output_dir, 'done')))
567 os.path.exists(os.path.join(self._output_dir, 'out-env')))
569 def testWorkInOutputFail(self):
570 """Test the -w option failures"""
571 with self.assertRaises(SystemExit) as e:
572 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
573 self.assertIn("single board", str(e.exception))
575 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
577 board_list = board.Boards()
578 board_list.AddBoard(board.Board(*boards[0]))
579 with self.assertRaises(SystemExit) as e:
580 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
581 '-w', clean_dir=False, boards=board_list)
582 self.assertIn("single commit", str(e.exception))
584 board_list = board.Boards()
585 board_list.AddBoard(board.Board(*boards[0]))
586 with self.assertRaises(SystemExit) as e:
587 self._RunControl('-w', clean_dir=False)
588 self.assertIn("specify -o", str(e.exception))