]> git.ipfire.org Git - people/ms/u-boot.git/blame - tools/buildman/func_test.py
nvme: Add NVM Express driver support
[people/ms/u-boot.git] / tools / buildman / func_test.py
CommitLineData
d4144e45
SG
1#
2# Copyright (c) 2014 Google, Inc
3#
4# SPDX-License-Identifier: GPL-2.0+
5#
6
7import os
8import shutil
9import sys
10import tempfile
11import unittest
12
823e60b6 13import board
8b985eeb 14import bsettings
d4144e45
SG
15import cmdline
16import command
17import control
18import gitutil
19import terminal
20import toolchain
21
8b985eeb
SG
22settings_data = '''
23# Buildman settings file
24
25[toolchain]
26
27[toolchain-alias]
28
29[make-flags]
30src=/home/sjg/c/src
31chroot=/home/sjg/c/chroot
32vboot=USE_STDINT=1 VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
33chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
34chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
35chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
36'''
37
823e60b6
SG
38boards = [
39 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0', ''],
40 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''],
41 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
823e60b6
SG
42 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
43]
44
dfb7e932
SG
45commit_shortlog = """4aca821 patman: Avoid changing the order of tags
4639403bb patman: Use --no-pager' to stop git from forking a pager
47db6e6f2 patman: Remove the -a option
48f2ccf03 patman: Correct unit tests to run correctly
491d097f9 patman: Fix indentation in terminal.py
50d073747 patman: Support the 'reverse' option for 'git log
51"""
52
53commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
54Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
55Date: Fri Aug 22 19:12:41 2014 +0900
56
57 buildman: refactor help message
58
59 "buildman [options]" is displayed by default.
60
61 Append the rest of help messages to parser.usage
62 instead of replacing it.
63
64 Besides, "-b <branch>" is not mandatory since commit fea5858e.
65 Drop it from the usage.
66
67 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
68""",
69"""commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
70Author: Simon Glass <sjg@chromium.org>
71Date: Thu Aug 14 16:48:25 2014 -0600
72
73 patman: Support the 'reverse' option for 'git log'
74
75 This option is currently not supported, but needs to be, for buildman to
76 operate as expected.
77
78 Series-changes: 7
79 - Add new patch to fix the 'reverse' bug
80
950a2313 81 Series-version: 8
dfb7e932
SG
82
83 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
84 Reported-by: York Sun <yorksun@freescale.com>
85 Signed-off-by: Simon Glass <sjg@chromium.org>
86
87""",
88"""commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
89Author: Simon Glass <sjg@chromium.org>
90Date: Sat Aug 9 11:44:32 2014 -0600
91
92 patman: Fix indentation in terminal.py
93
94 This code came from a different project with 2-character indentation. Fix
95 it for U-Boot.
96
97 Series-changes: 6
98 - Add new patch to fix indentation in teminal.py
99
100 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
101 Signed-off-by: Simon Glass <sjg@chromium.org>
102
103""",
104"""commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
105Author: Simon Glass <sjg@chromium.org>
106Date: Sat Aug 9 11:08:24 2014 -0600
107
108 patman: Correct unit tests to run correctly
109
110 It seems that doctest behaves differently now, and some of the unit tests
111 do not run. Adjust the tests to work correctly.
112
113 ./tools/patman/patman --test
114 <unittest.result.TestResult run=10 errors=0 failures=0>
115
116 Series-changes: 6
117 - Add new patch to fix patman unit tests
118
119 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
120
121""",
122"""commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
123Author: Simon Glass <sjg@chromium.org>
124Date: Sat Aug 9 12:06:02 2014 -0600
125
126 patman: Remove the -a option
127
128 It seems that this is no longer needed, since checkpatch.pl will catch
129 whitespace problems in patches. Also the option is not widely used, so
130 it seems safe to just remove it.
131
132 Series-changes: 6
133 - Add new patch to remove patman's -a option
134
135 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
136 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
137
138""",
139"""commit 39403bb4f838153028a6f21ca30bf100f3791133
140Author: Simon Glass <sjg@chromium.org>
141Date: Thu Aug 14 21:50:52 2014 -0600
142
143 patman: Use --no-pager' to stop git from forking a pager
144
145""",
146"""commit 4aca821e27e97925c039e69fd37375b09c6f129c
147Author: Simon Glass <sjg@chromium.org>
148Date: Fri Aug 22 15:57:39 2014 -0600
149
150 patman: Avoid changing the order of tags
151
152 patman collects tags that it sees in the commit and places them nicely
153 sorted at the end of the patch. However, this is not really necessary and
154 in fact is apparently not desirable.
155
156 Series-changes: 9
157 - Add new patch to avoid changing the order of tags
158
950a2313
SG
159 Series-version: 9
160
dfb7e932
SG
161 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
162 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
163"""]
164
165TEST_BRANCH = '__testbranch'
166
d4144e45
SG
167class TestFunctional(unittest.TestCase):
168 """Functional test for buildman.
169
170 This aims to test from just below the invocation of buildman (parsing
171 of arguments) to 'make' and 'git' invocation. It is not a true
172 emd-to-end test, as it mocks git, make and the tool chain. But this
173 makes it easier to detect when the builder is doing the wrong thing,
174 since in many cases this test code will fail. For example, only a
175 very limited subset of 'git' arguments is supported - anything
176 unexpected will fail.
177 """
178 def setUp(self):
179 self._base_dir = tempfile.mkdtemp()
180 self._git_dir = os.path.join(self._base_dir, 'src')
181 self._buildman_pathname = sys.argv[0]
bd6f5d98 182 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
d4144e45 183 command.test_result = self._HandleCommand
dfb7e932
SG
184 self.setupToolchains()
185 self._toolchains.Add('arm-gcc', test=False)
186 self._toolchains.Add('powerpc-gcc', test=False)
8b985eeb
SG
187 bsettings.Setup(None)
188 bsettings.AddFile(settings_data)
823e60b6
SG
189 self._boards = board.Boards()
190 for brd in boards:
191 self._boards.AddBoard(board.Board(*brd))
d4144e45 192
dfb7e932
SG
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)
197
198 # Number of calls to make
199 self._make_calls = 0
200
201 # Map of [board, commit] to error messages
202 self._error = {}
203
f7582ce8
SG
204 self._test_branch = TEST_BRANCH
205
dfb7e932
SG
206 # Avoid sending any output and clear all terminal output
207 terminal.SetPrintTestMode()
208 terminal.GetPrintTestLines()
209
d4144e45
SG
210 def tearDown(self):
211 shutil.rmtree(self._base_dir)
212
dfb7e932
SG
213 def setupToolchains(self):
214 self._toolchains = toolchain.Toolchains()
215 self._toolchains.Add('gcc', test=False)
216
d4144e45
SG
217 def _RunBuildman(self, *args):
218 return command.RunPipe([[self._buildman_pathname] + list(args)],
219 capture=True, capture_stderr=True)
220
dfb7e932 221 def _RunControl(self, *args, **kwargs):
d4144e45
SG
222 sys.argv = [sys.argv[0]] + list(args)
223 options, args = cmdline.ParseArgs()
dfb7e932
SG
224 result = control.DoBuildman(options, args, toolchains=self._toolchains,
225 make_func=self._HandleMake, boards=self._boards,
226 clean_dir=kwargs.get('clean_dir', True))
227 self._builder = control.builder
228 return result
d4144e45
SG
229
230 def testFullHelp(self):
231 command.test_result = None
232 result = self._RunBuildman('-H')
233 help_file = os.path.join(self._buildman_dir, 'README')
234 self.assertEqual(len(result.stdout), os.path.getsize(help_file))
235 self.assertEqual(0, len(result.stderr))
236 self.assertEqual(0, result.return_code)
237
238 def testHelp(self):
239 command.test_result = None
240 result = self._RunBuildman('-h')
241 help_file = os.path.join(self._buildman_dir, 'README')
242 self.assertTrue(len(result.stdout) > 1000)
243 self.assertEqual(0, len(result.stderr))
244 self.assertEqual(0, result.return_code)
245
246 def testGitSetup(self):
247 """Test gitutils.Setup(), from outside the module itself"""
248 command.test_result = command.CommandResult(return_code=1)
249 gitutil.Setup()
250 self.assertEqual(gitutil.use_no_decorate, False)
251
252 command.test_result = command.CommandResult(return_code=0)
253 gitutil.Setup()
254 self.assertEqual(gitutil.use_no_decorate, True)
255
256 def _HandleCommandGitLog(self, args):
d4c8572b
SG
257 if args[-1] == '--':
258 args = args[:-1]
d4144e45
SG
259 if '-n0' in args:
260 return command.CommandResult(return_code=0)
f7582ce8 261 elif args[-1] == 'upstream/master..%s' % self._test_branch:
dfb7e932
SG
262 return command.CommandResult(return_code=0, stdout=commit_shortlog)
263 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
f7582ce8 264 if args[-1] == self._test_branch:
dfb7e932
SG
265 count = int(args[3][2:])
266 return command.CommandResult(return_code=0,
267 stdout=''.join(commit_log[:count]))
d4144e45
SG
268
269 # Not handled, so abort
270 print 'git log', args
271 sys.exit(1)
272
dfb7e932
SG
273 def _HandleCommandGitConfig(self, args):
274 config = args[0]
275 if config == 'sendemail.aliasesfile':
276 return command.CommandResult(return_code=0)
277 elif config.startswith('branch.badbranch'):
278 return command.CommandResult(return_code=1)
f7582ce8 279 elif config == 'branch.%s.remote' % self._test_branch:
dfb7e932 280 return command.CommandResult(return_code=0, stdout='upstream\n')
f7582ce8 281 elif config == 'branch.%s.merge' % self._test_branch:
dfb7e932
SG
282 return command.CommandResult(return_code=0,
283 stdout='refs/heads/master\n')
284
285 # Not handled, so abort
286 print 'git config', args
287 sys.exit(1)
288
d4144e45
SG
289 def _HandleCommandGit(self, in_args):
290 """Handle execution of a git command
291
292 This uses a hacked-up parser.
293
294 Args:
295 in_args: Arguments after 'git' from the command line
296 """
297 git_args = [] # Top-level arguments to git itself
298 sub_cmd = None # Git sub-command selected
299 args = [] # Arguments to the git sub-command
300 for arg in in_args:
301 if sub_cmd:
302 args.append(arg)
303 elif arg[0] == '-':
304 git_args.append(arg)
305 else:
dfb7e932
SG
306 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
307 git_args.append(arg)
308 else:
309 sub_cmd = arg
d4144e45 310 if sub_cmd == 'config':
dfb7e932 311 return self._HandleCommandGitConfig(args)
d4144e45
SG
312 elif sub_cmd == 'log':
313 return self._HandleCommandGitLog(args)
dfb7e932
SG
314 elif sub_cmd == 'clone':
315 return command.CommandResult(return_code=0)
316 elif sub_cmd == 'checkout':
317 return command.CommandResult(return_code=0)
d4144e45
SG
318
319 # Not handled, so abort
320 print 'git', git_args, sub_cmd, args
321 sys.exit(1)
322
323 def _HandleCommandNm(self, args):
324 return command.CommandResult(return_code=0)
325
326 def _HandleCommandObjdump(self, args):
327 return command.CommandResult(return_code=0)
328
329 def _HandleCommandSize(self, args):
330 return command.CommandResult(return_code=0)
331
332 def _HandleCommand(self, **kwargs):
333 """Handle a command execution.
334
335 The command is in kwargs['pipe-list'], as a list of pipes, each a
336 list of commands. The command should be emulated as required for
337 testing purposes.
338
339 Returns:
340 A CommandResult object
341 """
342 pipe_list = kwargs['pipe_list']
dfb7e932 343 wc = False
d4144e45 344 if len(pipe_list) != 1:
dfb7e932
SG
345 if pipe_list[1] == ['wc', '-l']:
346 wc = True
347 else:
348 print 'invalid pipe', kwargs
349 sys.exit(1)
d4144e45
SG
350 cmd = pipe_list[0][0]
351 args = pipe_list[0][1:]
dfb7e932 352 result = None
d4144e45 353 if cmd == 'git':
dfb7e932 354 result = self._HandleCommandGit(args)
d4144e45
SG
355 elif cmd == './scripts/show-gnu-make':
356 return command.CommandResult(return_code=0, stdout='make')
dfb7e932 357 elif cmd.endswith('nm'):
d4144e45 358 return self._HandleCommandNm(args)
dfb7e932 359 elif cmd.endswith('objdump'):
d4144e45 360 return self._HandleCommandObjdump(args)
dfb7e932 361 elif cmd.endswith( 'size'):
d4144e45
SG
362 return self._HandleCommandSize(args)
363
dfb7e932
SG
364 if not result:
365 # Not handled, so abort
366 print 'unknown command', kwargs
367 sys.exit(1)
368
369 if wc:
370 result.stdout = len(result.stdout.splitlines())
371 return result
d4144e45
SG
372
373 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
374 """Handle execution of 'make'
375
376 Args:
377 commit: Commit object that is being built
378 brd: Board object that is being built
379 stage: Stage that we are at (mrproper, config, build)
380 cwd: Directory where make should be run
381 args: Arguments to pass to make
382 kwargs: Arguments to pass to command.RunPipe()
383 """
dfb7e932 384 self._make_calls += 1
d4144e45
SG
385 if stage == 'mrproper':
386 return command.CommandResult(return_code=0)
387 elif stage == 'config':
388 return command.CommandResult(return_code=0,
389 combined='Test configuration complete')
390 elif stage == 'build':
dfb7e932
SG
391 stderr = ''
392 if type(commit) is not str:
393 stderr = self._error.get((brd.target, commit.sequence))
394 if stderr:
395 return command.CommandResult(return_code=1, stderr=stderr)
d4144e45
SG
396 return command.CommandResult(return_code=0)
397
398 # Not handled, so abort
399 print 'make', stage
400 sys.exit(1)
401
dfb7e932
SG
402 # Example function to print output lines
403 def print_lines(self, lines):
404 print len(lines)
405 for line in lines:
406 print line
407 #self.print_lines(terminal.GetPrintTestLines())
408
823e60b6
SG
409 def testNoBoards(self):
410 """Test that buildman aborts when there are no boards"""
411 self._boards = board.Boards()
412 with self.assertRaises(SystemExit):
413 self._RunControl()
414
d4144e45
SG
415 def testCurrentSource(self):
416 """Very simple test to invoke buildman on the current source"""
dfb7e932 417 self.setupToolchains();
d4144e45
SG
418 self._RunControl()
419 lines = terminal.GetPrintTestLines()
dfb7e932
SG
420 self.assertIn('Building current source for %d boards' % len(boards),
421 lines[0].text)
422
423 def testBadBranch(self):
424 """Test that we can detect an invalid branch"""
425 with self.assertRaises(ValueError):
426 self._RunControl('-b', 'badbranch')
427
428 def testBadToolchain(self):
429 """Test that missing toolchains are detected"""
430 self.setupToolchains();
431 ret_code = self._RunControl('-b', TEST_BRANCH)
432 lines = terminal.GetPrintTestLines()
433
434 # Buildman always builds the upstream commit as well
435 self.assertIn('Building %d commits for %d boards' %
436 (self._commits, len(boards)), lines[0].text)
437 self.assertEqual(self._builder.count, self._total_builds)
438
439 # Only sandbox should succeed, the others don't have toolchains
440 self.assertEqual(self._builder.fail,
441 self._total_builds - self._commits)
442 self.assertEqual(ret_code, 128)
443
444 for commit in range(self._commits):
445 for board in self._boards.GetList():
446 if board.arch != 'sandbox':
447 errfile = self._builder.GetErrFile(commit, board.target)
448 fd = open(errfile)
449 self.assertEqual(fd.readlines(),
450 ['No tool chain for %s\n' % board.arch])
451 fd.close()
452
453 def testBranch(self):
454 """Test building a branch with all toolchains present"""
455 self._RunControl('-b', TEST_BRANCH)
456 self.assertEqual(self._builder.count, self._total_builds)
457 self.assertEqual(self._builder.fail, 0)
458
459 def testCount(self):
460 """Test building a specific number of commitst"""
461 self._RunControl('-b', TEST_BRANCH, '-c2')
462 self.assertEqual(self._builder.count, 2 * len(boards))
463 self.assertEqual(self._builder.fail, 0)
464 # Each board has a mrproper, config, and then one make per commit
465 self.assertEqual(self._make_calls, len(boards) * (2 + 2))
466
467 def testIncremental(self):
468 """Test building a branch twice - the second time should do nothing"""
469 self._RunControl('-b', TEST_BRANCH)
470
471 # Each board has a mrproper, config, and then one make per commit
472 self.assertEqual(self._make_calls, len(boards) * (self._commits + 2))
473 self._make_calls = 0
474 self._RunControl('-b', TEST_BRANCH, clean_dir=False)
475 self.assertEqual(self._make_calls, 0)
476 self.assertEqual(self._builder.count, self._total_builds)
477 self.assertEqual(self._builder.fail, 0)
478
479 def testForceBuild(self):
480 """The -f flag should force a rebuild"""
481 self._RunControl('-b', TEST_BRANCH)
482 self._make_calls = 0
483 self._RunControl('-b', TEST_BRANCH, '-f', clean_dir=False)
484 # Each board has a mrproper, config, and then one make per commit
485 self.assertEqual(self._make_calls, len(boards) * (self._commits + 2))
486
487 def testForceReconfigure(self):
488 """The -f flag should force a rebuild"""
489 self._RunControl('-b', TEST_BRANCH, '-C')
490 # Each commit has a mrproper, config and make
491 self.assertEqual(self._make_calls, len(boards) * self._commits * 3)
492
493 def testErrors(self):
494 """Test handling of build errors"""
495 self._error['board2', 1] = 'fred\n'
496 self._RunControl('-b', TEST_BRANCH)
497 self.assertEqual(self._builder.count, self._total_builds)
498 self.assertEqual(self._builder.fail, 1)
499
500 # Remove the error. This should have no effect since the commit will
501 # not be rebuilt
502 del self._error['board2', 1]
503 self._make_calls = 0
504 self._RunControl('-b', TEST_BRANCH, clean_dir=False)
505 self.assertEqual(self._builder.count, self._total_builds)
506 self.assertEqual(self._make_calls, 0)
507 self.assertEqual(self._builder.fail, 1)
508
509 # Now use the -F flag to force rebuild of the bad commit
510 self._RunControl('-b', TEST_BRANCH, '-F', clean_dir=False)
511 self.assertEqual(self._builder.count, self._total_builds)
512 self.assertEqual(self._builder.fail, 0)
513 self.assertEqual(self._make_calls, 3)
f7582ce8
SG
514
515 def testBranchWithSlash(self):
516 """Test building a branch with a '/' in the name"""
517 self._test_branch = '/__dev/__testbranch'
518 self._RunControl('-b', self._test_branch, clean_dir=False)
519 self.assertEqual(self._builder.count, self._total_builds)
520 self.assertEqual(self._builder.fail, 0)