123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 |
- #
- # Copyright (c) 2014 Google, Inc
- #
- # SPDX-License-Identifier: GPL-2.0+
- #
- import os
- import shutil
- import sys
- import tempfile
- import unittest
- import board
- import bsettings
- import cmdline
- import command
- import control
- import gitutil
- import terminal
- import toolchain
- settings_data = '''
- # Buildman settings file
- [toolchain]
- [toolchain-alias]
- [make-flags]
- src=/home/sjg/c/src
- chroot=/home/sjg/c/chroot
- vboot=USE_STDINT=1 VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
- chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
- chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
- chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
- '''
- boards = [
- ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0', ''],
- ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''],
- ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
- ['Active', 'powerpc', 'mpc5xx', '', 'Tester', 'PowerPC board 2', 'board3', ''],
- ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
- ]
- commit_shortlog = """4aca821 patman: Avoid changing the order of tags
- 39403bb patman: Use --no-pager' to stop git from forking a pager
- db6e6f2 patman: Remove the -a option
- f2ccf03 patman: Correct unit tests to run correctly
- 1d097f9 patman: Fix indentation in terminal.py
- d073747 patman: Support the 'reverse' option for 'git log
- """
- commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
- Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
- Date: Fri Aug 22 19:12:41 2014 +0900
- buildman: refactor help message
- "buildman [options]" is displayed by default.
- Append the rest of help messages to parser.usage
- instead of replacing it.
- Besides, "-b <branch>" is not mandatory since commit fea5858e.
- Drop it from the usage.
- Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
- """,
- """commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
- Author: Simon Glass <sjg@chromium.org>
- Date: Thu Aug 14 16:48:25 2014 -0600
- patman: Support the 'reverse' option for 'git log'
- This option is currently not supported, but needs to be, for buildman to
- operate as expected.
- Series-changes: 7
- - Add new patch to fix the 'reverse' bug
- Series-version: 8
- Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
- Reported-by: York Sun <yorksun@freescale.com>
- Signed-off-by: Simon Glass <sjg@chromium.org>
- """,
- """commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
- Author: Simon Glass <sjg@chromium.org>
- Date: Sat Aug 9 11:44:32 2014 -0600
- patman: Fix indentation in terminal.py
- This code came from a different project with 2-character indentation. Fix
- it for U-Boot.
- Series-changes: 6
- - Add new patch to fix indentation in teminal.py
- Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
- Signed-off-by: Simon Glass <sjg@chromium.org>
- """,
- """commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
- Author: Simon Glass <sjg@chromium.org>
- Date: Sat Aug 9 11:08:24 2014 -0600
- patman: Correct unit tests to run correctly
- It seems that doctest behaves differently now, and some of the unit tests
- do not run. Adjust the tests to work correctly.
- ./tools/patman/patman --test
- <unittest.result.TestResult run=10 errors=0 failures=0>
- Series-changes: 6
- - Add new patch to fix patman unit tests
- Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
- """,
- """commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
- Author: Simon Glass <sjg@chromium.org>
- Date: Sat Aug 9 12:06:02 2014 -0600
- patman: Remove the -a option
- It seems that this is no longer needed, since checkpatch.pl will catch
- whitespace problems in patches. Also the option is not widely used, so
- it seems safe to just remove it.
- Series-changes: 6
- - Add new patch to remove patman's -a option
- Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
- Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
- """,
- """commit 39403bb4f838153028a6f21ca30bf100f3791133
- Author: Simon Glass <sjg@chromium.org>
- Date: Thu Aug 14 21:50:52 2014 -0600
- patman: Use --no-pager' to stop git from forking a pager
- """,
- """commit 4aca821e27e97925c039e69fd37375b09c6f129c
- Author: Simon Glass <sjg@chromium.org>
- Date: Fri Aug 22 15:57:39 2014 -0600
- patman: Avoid changing the order of tags
- patman collects tags that it sees in the commit and places them nicely
- sorted at the end of the patch. However, this is not really necessary and
- in fact is apparently not desirable.
- Series-changes: 9
- - Add new patch to avoid changing the order of tags
- Series-version: 9
- Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
- Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
- """]
- TEST_BRANCH = '__testbranch'
- class TestFunctional(unittest.TestCase):
- """Functional test for buildman.
- This aims to test from just below the invocation of buildman (parsing
- of arguments) to 'make' and 'git' invocation. It is not a true
- emd-to-end test, as it mocks git, make and the tool chain. But this
- makes it easier to detect when the builder is doing the wrong thing,
- since in many cases this test code will fail. For example, only a
- very limited subset of 'git' arguments is supported - anything
- unexpected will fail.
- """
- def setUp(self):
- self._base_dir = tempfile.mkdtemp()
- self._git_dir = os.path.join(self._base_dir, 'src')
- self._buildman_pathname = sys.argv[0]
- self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
- command.test_result = self._HandleCommand
- self.setupToolchains()
- self._toolchains.Add('arm-gcc', test=False)
- self._toolchains.Add('powerpc-gcc', test=False)
- bsettings.Setup(None)
- bsettings.AddFile(settings_data)
- self._boards = board.Boards()
- for brd in boards:
- self._boards.AddBoard(board.Board(*brd))
- # Directories where the source been cloned
- self._clone_dirs = []
- self._commits = len(commit_shortlog.splitlines()) + 1
- self._total_builds = self._commits * len(boards)
- # Number of calls to make
- self._make_calls = 0
- # Map of [board, commit] to error messages
- self._error = {}
- self._test_branch = TEST_BRANCH
- # Avoid sending any output and clear all terminal output
- terminal.SetPrintTestMode()
- terminal.GetPrintTestLines()
- def tearDown(self):
- shutil.rmtree(self._base_dir)
- def setupToolchains(self):
- self._toolchains = toolchain.Toolchains()
- self._toolchains.Add('gcc', test=False)
- def _RunBuildman(self, *args):
- return command.RunPipe([[self._buildman_pathname] + list(args)],
- capture=True, capture_stderr=True)
- def _RunControl(self, *args, **kwargs):
- sys.argv = [sys.argv[0]] + list(args)
- options, args = cmdline.ParseArgs()
- result = control.DoBuildman(options, args, toolchains=self._toolchains,
- make_func=self._HandleMake, boards=self._boards,
- clean_dir=kwargs.get('clean_dir', True))
- self._builder = control.builder
- return result
- def testFullHelp(self):
- command.test_result = None
- result = self._RunBuildman('-H')
- help_file = os.path.join(self._buildman_dir, 'README')
- self.assertEqual(len(result.stdout), os.path.getsize(help_file))
- self.assertEqual(0, len(result.stderr))
- self.assertEqual(0, result.return_code)
- def testHelp(self):
- command.test_result = None
- result = self._RunBuildman('-h')
- help_file = os.path.join(self._buildman_dir, 'README')
- self.assertTrue(len(result.stdout) > 1000)
- self.assertEqual(0, len(result.stderr))
- self.assertEqual(0, result.return_code)
- def testGitSetup(self):
- """Test gitutils.Setup(), from outside the module itself"""
- command.test_result = command.CommandResult(return_code=1)
- gitutil.Setup()
- self.assertEqual(gitutil.use_no_decorate, False)
- command.test_result = command.CommandResult(return_code=0)
- gitutil.Setup()
- self.assertEqual(gitutil.use_no_decorate, True)
- def _HandleCommandGitLog(self, args):
- if args[-1] == '--':
- args = args[:-1]
- if '-n0' in args:
- return command.CommandResult(return_code=0)
- elif args[-1] == 'upstream/master..%s' % self._test_branch:
- return command.CommandResult(return_code=0, stdout=commit_shortlog)
- elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
- if args[-1] == self._test_branch:
- count = int(args[3][2:])
- return command.CommandResult(return_code=0,
- stdout=''.join(commit_log[:count]))
- # Not handled, so abort
- print 'git log', args
- sys.exit(1)
- def _HandleCommandGitConfig(self, args):
- config = args[0]
- if config == 'sendemail.aliasesfile':
- return command.CommandResult(return_code=0)
- elif config.startswith('branch.badbranch'):
- return command.CommandResult(return_code=1)
- elif config == 'branch.%s.remote' % self._test_branch:
- return command.CommandResult(return_code=0, stdout='upstream\n')
- elif config == 'branch.%s.merge' % self._test_branch:
- return command.CommandResult(return_code=0,
- stdout='refs/heads/master\n')
- # Not handled, so abort
- print 'git config', args
- sys.exit(1)
- def _HandleCommandGit(self, in_args):
- """Handle execution of a git command
- This uses a hacked-up parser.
- Args:
- in_args: Arguments after 'git' from the command line
- """
- git_args = [] # Top-level arguments to git itself
- sub_cmd = None # Git sub-command selected
- args = [] # Arguments to the git sub-command
- for arg in in_args:
- if sub_cmd:
- args.append(arg)
- elif arg[0] == '-':
- git_args.append(arg)
- else:
- if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
- git_args.append(arg)
- else:
- sub_cmd = arg
- if sub_cmd == 'config':
- return self._HandleCommandGitConfig(args)
- elif sub_cmd == 'log':
- return self._HandleCommandGitLog(args)
- elif sub_cmd == 'clone':
- return command.CommandResult(return_code=0)
- elif sub_cmd == 'checkout':
- return command.CommandResult(return_code=0)
- # Not handled, so abort
- print 'git', git_args, sub_cmd, args
- sys.exit(1)
- def _HandleCommandNm(self, args):
- return command.CommandResult(return_code=0)
- def _HandleCommandObjdump(self, args):
- return command.CommandResult(return_code=0)
- def _HandleCommandSize(self, args):
- return command.CommandResult(return_code=0)
- def _HandleCommand(self, **kwargs):
- """Handle a command execution.
- The command is in kwargs['pipe-list'], as a list of pipes, each a
- list of commands. The command should be emulated as required for
- testing purposes.
- Returns:
- A CommandResult object
- """
- pipe_list = kwargs['pipe_list']
- wc = False
- if len(pipe_list) != 1:
- if pipe_list[1] == ['wc', '-l']:
- wc = True
- else:
- print 'invalid pipe', kwargs
- sys.exit(1)
- cmd = pipe_list[0][0]
- args = pipe_list[0][1:]
- result = None
- if cmd == 'git':
- result = self._HandleCommandGit(args)
- elif cmd == './scripts/show-gnu-make':
- return command.CommandResult(return_code=0, stdout='make')
- elif cmd.endswith('nm'):
- return self._HandleCommandNm(args)
- elif cmd.endswith('objdump'):
- return self._HandleCommandObjdump(args)
- elif cmd.endswith( 'size'):
- return self._HandleCommandSize(args)
- if not result:
- # Not handled, so abort
- print 'unknown command', kwargs
- sys.exit(1)
- if wc:
- result.stdout = len(result.stdout.splitlines())
- return result
- def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
- """Handle execution of 'make'
- Args:
- commit: Commit object that is being built
- brd: Board object that is being built
- stage: Stage that we are at (mrproper, config, build)
- cwd: Directory where make should be run
- args: Arguments to pass to make
- kwargs: Arguments to pass to command.RunPipe()
- """
- self._make_calls += 1
- if stage == 'mrproper':
- return command.CommandResult(return_code=0)
- elif stage == 'config':
- return command.CommandResult(return_code=0,
- combined='Test configuration complete')
- elif stage == 'build':
- stderr = ''
- if type(commit) is not str:
- stderr = self._error.get((brd.target, commit.sequence))
- if stderr:
- return command.CommandResult(return_code=1, stderr=stderr)
- return command.CommandResult(return_code=0)
- # Not handled, so abort
- print 'make', stage
- sys.exit(1)
- # Example function to print output lines
- def print_lines(self, lines):
- print len(lines)
- for line in lines:
- print line
- #self.print_lines(terminal.GetPrintTestLines())
- def testNoBoards(self):
- """Test that buildman aborts when there are no boards"""
- self._boards = board.Boards()
- with self.assertRaises(SystemExit):
- self._RunControl()
- def testCurrentSource(self):
- """Very simple test to invoke buildman on the current source"""
- self.setupToolchains();
- self._RunControl()
- lines = terminal.GetPrintTestLines()
- self.assertIn('Building current source for %d boards' % len(boards),
- lines[0].text)
- def testBadBranch(self):
- """Test that we can detect an invalid branch"""
- with self.assertRaises(ValueError):
- self._RunControl('-b', 'badbranch')
- def testBadToolchain(self):
- """Test that missing toolchains are detected"""
- self.setupToolchains();
- ret_code = self._RunControl('-b', TEST_BRANCH)
- lines = terminal.GetPrintTestLines()
- # Buildman always builds the upstream commit as well
- self.assertIn('Building %d commits for %d boards' %
- (self._commits, len(boards)), lines[0].text)
- self.assertEqual(self._builder.count, self._total_builds)
- # Only sandbox should succeed, the others don't have toolchains
- self.assertEqual(self._builder.fail,
- self._total_builds - self._commits)
- self.assertEqual(ret_code, 128)
- for commit in range(self._commits):
- for board in self._boards.GetList():
- if board.arch != 'sandbox':
- errfile = self._builder.GetErrFile(commit, board.target)
- fd = open(errfile)
- self.assertEqual(fd.readlines(),
- ['No tool chain for %s\n' % board.arch])
- fd.close()
- def testBranch(self):
- """Test building a branch with all toolchains present"""
- self._RunControl('-b', TEST_BRANCH)
- self.assertEqual(self._builder.count, self._total_builds)
- self.assertEqual(self._builder.fail, 0)
- def testCount(self):
- """Test building a specific number of commitst"""
- self._RunControl('-b', TEST_BRANCH, '-c2')
- self.assertEqual(self._builder.count, 2 * len(boards))
- self.assertEqual(self._builder.fail, 0)
- # Each board has a mrproper, config, and then one make per commit
- self.assertEqual(self._make_calls, len(boards) * (2 + 2))
- def testIncremental(self):
- """Test building a branch twice - the second time should do nothing"""
- self._RunControl('-b', TEST_BRANCH)
- # Each board has a mrproper, config, and then one make per commit
- self.assertEqual(self._make_calls, len(boards) * (self._commits + 2))
- self._make_calls = 0
- self._RunControl('-b', TEST_BRANCH, clean_dir=False)
- self.assertEqual(self._make_calls, 0)
- self.assertEqual(self._builder.count, self._total_builds)
- self.assertEqual(self._builder.fail, 0)
- def testForceBuild(self):
- """The -f flag should force a rebuild"""
- self._RunControl('-b', TEST_BRANCH)
- self._make_calls = 0
- self._RunControl('-b', TEST_BRANCH, '-f', clean_dir=False)
- # Each board has a mrproper, config, and then one make per commit
- self.assertEqual(self._make_calls, len(boards) * (self._commits + 2))
- def testForceReconfigure(self):
- """The -f flag should force a rebuild"""
- self._RunControl('-b', TEST_BRANCH, '-C')
- # Each commit has a mrproper, config and make
- self.assertEqual(self._make_calls, len(boards) * self._commits * 3)
- def testErrors(self):
- """Test handling of build errors"""
- self._error['board2', 1] = 'fred\n'
- self._RunControl('-b', TEST_BRANCH)
- self.assertEqual(self._builder.count, self._total_builds)
- self.assertEqual(self._builder.fail, 1)
- # Remove the error. This should have no effect since the commit will
- # not be rebuilt
- del self._error['board2', 1]
- self._make_calls = 0
- self._RunControl('-b', TEST_BRANCH, clean_dir=False)
- self.assertEqual(self._builder.count, self._total_builds)
- self.assertEqual(self._make_calls, 0)
- self.assertEqual(self._builder.fail, 1)
- # Now use the -F flag to force rebuild of the bad commit
- self._RunControl('-b', TEST_BRANCH, '-F', clean_dir=False)
- self.assertEqual(self._builder.count, self._total_builds)
- self.assertEqual(self._builder.fail, 0)
- self.assertEqual(self._make_calls, 3)
- def testBranchWithSlash(self):
- """Test building a branch with a '/' in the name"""
- self._test_branch = '/__dev/__testbranch'
- self._RunControl('-b', self._test_branch, clean_dir=False)
- self.assertEqual(self._builder.count, self._total_builds)
- self.assertEqual(self._builder.fail, 0)
|