]> git.ipfire.org Git - thirdparty/u-boot.git/blame - tools/buildman/func_test.py
Merge branch 'next'
[thirdparty/u-boot.git] / tools / buildman / func_test.py
CommitLineData
83d290c5 1# SPDX-License-Identifier: GPL-2.0+
d4144e45
SG
2# Copyright (c) 2014 Google, Inc
3#
d4144e45
SG
4
5import os
6import shutil
7import sys
8import tempfile
9import unittest
10
0ede00fd 11from buildman import board
c52bd225 12from buildman import boards
0ede00fd
SG
13from buildman import bsettings
14from buildman import cmdline
15from buildman import control
16from buildman import toolchain
bf776679 17from patman import gitutil
4583c002
SG
18from u_boot_pylib import command
19from u_boot_pylib import terminal
20from u_boot_pylib import test_util
21from u_boot_pylib import tools
d4144e45 22
8b985eeb
SG
23settings_data = '''
24# Buildman settings file
d7713ad3 25[global]
8b985eeb
SG
26
27[toolchain]
28
29[toolchain-alias]
30
31[make-flags]
32src=/home/sjg/c/src
33chroot=/home/sjg/c/chroot
9865543a 34vboot=VBOOT_DEBUG=1 MAKEFLAGS_VBOOT=DEBUG=1 CFLAGS_EXTRA_VBOOT=-DUNROLL_LOOPS VBOOT_SOURCE=${src}/platform/vboot_reference
8b985eeb
SG
35chromeos_coreboot=VBOOT=${chroot}/build/link/usr ${vboot}
36chromeos_daisy=VBOOT=${chroot}/build/daisy/usr ${vboot}
37chromeos_peach=VBOOT=${chroot}/build/peach_pit/usr ${vboot}
38'''
39
938fa37c 40BOARDS = [
823e60b6
SG
41 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0', ''],
42 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''],
43 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
823e60b6
SG
44 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
45]
46
dfb7e932
SG
47commit_shortlog = """4aca821 patman: Avoid changing the order of tags
4839403bb patman: Use --no-pager' to stop git from forking a pager
49db6e6f2 patman: Remove the -a option
50f2ccf03 patman: Correct unit tests to run correctly
511d097f9 patman: Fix indentation in terminal.py
52d073747 patman: Support the 'reverse' option for 'git log
53"""
54
55commit_log = ["""commit 7f6b8315d18f683c5181d0c3694818c1b2a20dcd
56Author: Masahiro Yamada <yamada.m@jp.panasonic.com>
57Date: Fri Aug 22 19:12:41 2014 +0900
58
59 buildman: refactor help message
60
61 "buildman [options]" is displayed by default.
62
63 Append the rest of help messages to parser.usage
64 instead of replacing it.
65
66 Besides, "-b <branch>" is not mandatory since commit fea5858e.
67 Drop it from the usage.
68
69 Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
70""",
71"""commit d0737479be6baf4db5e2cdbee123e96bc5ed0ba8
72Author: Simon Glass <sjg@chromium.org>
73Date: Thu Aug 14 16:48:25 2014 -0600
74
75 patman: Support the 'reverse' option for 'git log'
76
77 This option is currently not supported, but needs to be, for buildman to
78 operate as expected.
79
80 Series-changes: 7
81 - Add new patch to fix the 'reverse' bug
82
950a2313 83 Series-version: 8
dfb7e932
SG
84
85 Change-Id: I79078f792e8b390b8a1272a8023537821d45feda
86 Reported-by: York Sun <yorksun@freescale.com>
87 Signed-off-by: Simon Glass <sjg@chromium.org>
88
89""",
90"""commit 1d097f9ab487c5019152fd47bda126839f3bf9fc
91Author: Simon Glass <sjg@chromium.org>
92Date: Sat Aug 9 11:44:32 2014 -0600
93
94 patman: Fix indentation in terminal.py
95
96 This code came from a different project with 2-character indentation. Fix
97 it for U-Boot.
98
99 Series-changes: 6
100 - Add new patch to fix indentation in teminal.py
101
102 Change-Id: I5a74d2ebbb3cc12a665f5c725064009ac96e8a34
103 Signed-off-by: Simon Glass <sjg@chromium.org>
104
105""",
106"""commit f2ccf03869d1e152c836515a3ceb83cdfe04a105
107Author: Simon Glass <sjg@chromium.org>
108Date: Sat Aug 9 11:08:24 2014 -0600
109
110 patman: Correct unit tests to run correctly
111
112 It seems that doctest behaves differently now, and some of the unit tests
113 do not run. Adjust the tests to work correctly.
114
115 ./tools/patman/patman --test
116 <unittest.result.TestResult run=10 errors=0 failures=0>
117
118 Series-changes: 6
119 - Add new patch to fix patman unit tests
120
121 Change-Id: I3d2ca588f4933e1f9d6b1665a00e4ae58269ff3b
122
123""",
124"""commit db6e6f2f9331c5a37647d6668768d4a40b8b0d1c
125Author: Simon Glass <sjg@chromium.org>
126Date: Sat Aug 9 12:06:02 2014 -0600
127
128 patman: Remove the -a option
129
130 It seems that this is no longer needed, since checkpatch.pl will catch
131 whitespace problems in patches. Also the option is not widely used, so
132 it seems safe to just remove it.
133
134 Series-changes: 6
135 - Add new patch to remove patman's -a option
136
137 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
138 Change-Id: I5821a1c75154e532c46513486ca40b808de7e2cc
139
140""",
141"""commit 39403bb4f838153028a6f21ca30bf100f3791133
142Author: Simon Glass <sjg@chromium.org>
143Date: Thu Aug 14 21:50:52 2014 -0600
144
145 patman: Use --no-pager' to stop git from forking a pager
146
147""",
148"""commit 4aca821e27e97925c039e69fd37375b09c6f129c
149Author: Simon Glass <sjg@chromium.org>
150Date: Fri Aug 22 15:57:39 2014 -0600
151
152 patman: Avoid changing the order of tags
153
154 patman collects tags that it sees in the commit and places them nicely
155 sorted at the end of the patch. However, this is not really necessary and
156 in fact is apparently not desirable.
157
158 Series-changes: 9
159 - Add new patch to avoid changing the order of tags
160
950a2313
SG
161 Series-version: 9
162
dfb7e932
SG
163 Suggested-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
164 Change-Id: Ib1518588c1a189ad5c3198aae76f8654aed8d0db
165"""]
166
167TEST_BRANCH = '__testbranch'
168
d4144e45
SG
169class TestFunctional(unittest.TestCase):
170 """Functional test for buildman.
171
172 This aims to test from just below the invocation of buildman (parsing
173 of arguments) to 'make' and 'git' invocation. It is not a true
174 emd-to-end test, as it mocks git, make and the tool chain. But this
175 makes it easier to detect when the builder is doing the wrong thing,
176 since in many cases this test code will fail. For example, only a
177 very limited subset of 'git' arguments is supported - anything
178 unexpected will fail.
179 """
180 def setUp(self):
181 self._base_dir = tempfile.mkdtemp()
aae62584 182 self._output_dir = tempfile.mkdtemp()
d4144e45
SG
183 self._git_dir = os.path.join(self._base_dir, 'src')
184 self._buildman_pathname = sys.argv[0]
bd6f5d98 185 self._buildman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
d4144e45 186 command.test_result = self._HandleCommand
19133b71
SG
187 bsettings.Setup(None)
188 bsettings.AddFile(settings_data)
dfb7e932
SG
189 self.setupToolchains()
190 self._toolchains.Add('arm-gcc', test=False)
191 self._toolchains.Add('powerpc-gcc', test=False)
c52bd225 192 self._boards = boards.Boards()
938fa37c 193 for brd in BOARDS:
6014db68 194 self._boards.add_board(board.Board(*brd))
d4144e45 195
dfb7e932
SG
196 # Directories where the source been cloned
197 self._clone_dirs = []
198 self._commits = len(commit_shortlog.splitlines()) + 1
938fa37c 199 self._total_builds = self._commits * len(BOARDS)
dfb7e932
SG
200
201 # Number of calls to make
202 self._make_calls = 0
203
204 # Map of [board, commit] to error messages
205 self._error = {}
206
f7582ce8
SG
207 self._test_branch = TEST_BRANCH
208
d7713ad3
TR
209 # Set to True to report missing blobs
210 self._missing = False
211
dfb7e932 212 # Avoid sending any output and clear all terminal output
098b10fb
SG
213 terminal.set_print_test_mode()
214 terminal.get_print_test_lines()
dfb7e932 215
d4144e45
SG
216 def tearDown(self):
217 shutil.rmtree(self._base_dir)
5f319fa7 218 shutil.rmtree(self._output_dir)
d4144e45 219
dfb7e932
SG
220 def setupToolchains(self):
221 self._toolchains = toolchain.Toolchains()
222 self._toolchains.Add('gcc', test=False)
223
d4144e45 224 def _RunBuildman(self, *args):
d9800699 225 return command.run_pipe([[self._buildman_pathname] + list(args)],
d4144e45
SG
226 capture=True, capture_stderr=True)
227
938fa37c 228 def _RunControl(self, *args, brds=None, clean_dir=False,
8116c78f 229 test_thread_exceptions=False):
24993313
SG
230 """Run buildman
231
232 Args:
233 args: List of arguments to pass
938fa37c 234 brds: Boards object
24993313
SG
235 clean_dir: Used for tests only, indicates that the existing output_dir
236 should be removed before starting the build
8116c78f
SG
237 test_thread_exceptions: Uses for tests only, True to make the threads
238 raise an exception instead of reporting their result. This simulates
239 a failure in the code somewhere
24993313
SG
240
241 Returns:
242 result code from buildman
243 """
d4144e45
SG
244 sys.argv = [sys.argv[0]] + list(args)
245 options, args = cmdline.ParseArgs()
dfb7e932 246 result = control.DoBuildman(options, args, toolchains=self._toolchains,
cc2c0d18 247 make_func=self._HandleMake, brds=brds or self._boards,
8116c78f
SG
248 clean_dir=clean_dir,
249 test_thread_exceptions=test_thread_exceptions)
dfb7e932
SG
250 self._builder = control.builder
251 return result
d4144e45
SG
252
253 def testFullHelp(self):
254 command.test_result = None
255 result = self._RunBuildman('-H')
74df4910 256 help_file = os.path.join(self._buildman_dir, 'README.rst')
3759df0c
TR
257 # Remove possible extraneous strings
258 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
259 gothelp = result.stdout.replace(extra, '')
260 self.assertEqual(len(gothelp), os.path.getsize(help_file))
d4144e45
SG
261 self.assertEqual(0, len(result.stderr))
262 self.assertEqual(0, result.return_code)
263
264 def testHelp(self):
265 command.test_result = None
266 result = self._RunBuildman('-h')
74df4910 267 help_file = os.path.join(self._buildman_dir, 'README.rst')
d4144e45
SG
268 self.assertTrue(len(result.stdout) > 1000)
269 self.assertEqual(0, len(result.stderr))
270 self.assertEqual(0, result.return_code)
271
272 def testGitSetup(self):
273 """Test gitutils.Setup(), from outside the module itself"""
274 command.test_result = command.CommandResult(return_code=1)
0157b187 275 gitutil.setup()
d4144e45
SG
276 self.assertEqual(gitutil.use_no_decorate, False)
277
278 command.test_result = command.CommandResult(return_code=0)
0157b187 279 gitutil.setup()
d4144e45
SG
280 self.assertEqual(gitutil.use_no_decorate, True)
281
282 def _HandleCommandGitLog(self, args):
d4c8572b
SG
283 if args[-1] == '--':
284 args = args[:-1]
d4144e45
SG
285 if '-n0' in args:
286 return command.CommandResult(return_code=0)
f7582ce8 287 elif args[-1] == 'upstream/master..%s' % self._test_branch:
dfb7e932
SG
288 return command.CommandResult(return_code=0, stdout=commit_shortlog)
289 elif args[:3] == ['--no-color', '--no-decorate', '--reverse']:
f7582ce8 290 if args[-1] == self._test_branch:
dfb7e932
SG
291 count = int(args[3][2:])
292 return command.CommandResult(return_code=0,
293 stdout=''.join(commit_log[:count]))
d4144e45
SG
294
295 # Not handled, so abort
c05aa036 296 print('git log', args)
d4144e45
SG
297 sys.exit(1)
298
dfb7e932
SG
299 def _HandleCommandGitConfig(self, args):
300 config = args[0]
301 if config == 'sendemail.aliasesfile':
302 return command.CommandResult(return_code=0)
303 elif config.startswith('branch.badbranch'):
304 return command.CommandResult(return_code=1)
f7582ce8 305 elif config == 'branch.%s.remote' % self._test_branch:
dfb7e932 306 return command.CommandResult(return_code=0, stdout='upstream\n')
f7582ce8 307 elif config == 'branch.%s.merge' % self._test_branch:
dfb7e932
SG
308 return command.CommandResult(return_code=0,
309 stdout='refs/heads/master\n')
310
311 # Not handled, so abort
c05aa036 312 print('git config', args)
dfb7e932
SG
313 sys.exit(1)
314
d4144e45
SG
315 def _HandleCommandGit(self, in_args):
316 """Handle execution of a git command
317
318 This uses a hacked-up parser.
319
320 Args:
321 in_args: Arguments after 'git' from the command line
322 """
323 git_args = [] # Top-level arguments to git itself
324 sub_cmd = None # Git sub-command selected
325 args = [] # Arguments to the git sub-command
326 for arg in in_args:
327 if sub_cmd:
328 args.append(arg)
329 elif arg[0] == '-':
330 git_args.append(arg)
331 else:
dfb7e932
SG
332 if git_args and git_args[-1] in ['--git-dir', '--work-tree']:
333 git_args.append(arg)
334 else:
335 sub_cmd = arg
d4144e45 336 if sub_cmd == 'config':
dfb7e932 337 return self._HandleCommandGitConfig(args)
d4144e45
SG
338 elif sub_cmd == 'log':
339 return self._HandleCommandGitLog(args)
dfb7e932
SG
340 elif sub_cmd == 'clone':
341 return command.CommandResult(return_code=0)
342 elif sub_cmd == 'checkout':
343 return command.CommandResult(return_code=0)
76de29fc
ANY
344 elif sub_cmd == 'worktree':
345 return command.CommandResult(return_code=0)
d4144e45
SG
346
347 # Not handled, so abort
c05aa036 348 print('git', git_args, sub_cmd, args)
d4144e45
SG
349 sys.exit(1)
350
351 def _HandleCommandNm(self, args):
352 return command.CommandResult(return_code=0)
353
354 def _HandleCommandObjdump(self, args):
355 return command.CommandResult(return_code=0)
356
0ddc510e
AK
357 def _HandleCommandObjcopy(self, args):
358 return command.CommandResult(return_code=0)
359
d4144e45
SG
360 def _HandleCommandSize(self, args):
361 return command.CommandResult(return_code=0)
362
363 def _HandleCommand(self, **kwargs):
364 """Handle a command execution.
365
366 The command is in kwargs['pipe-list'], as a list of pipes, each a
367 list of commands. The command should be emulated as required for
368 testing purposes.
369
370 Returns:
371 A CommandResult object
372 """
373 pipe_list = kwargs['pipe_list']
dfb7e932 374 wc = False
d4144e45 375 if len(pipe_list) != 1:
dfb7e932
SG
376 if pipe_list[1] == ['wc', '-l']:
377 wc = True
378 else:
c05aa036 379 print('invalid pipe', kwargs)
dfb7e932 380 sys.exit(1)
d4144e45
SG
381 cmd = pipe_list[0][0]
382 args = pipe_list[0][1:]
dfb7e932 383 result = None
d4144e45 384 if cmd == 'git':
dfb7e932 385 result = self._HandleCommandGit(args)
d4144e45
SG
386 elif cmd == './scripts/show-gnu-make':
387 return command.CommandResult(return_code=0, stdout='make')
dfb7e932 388 elif cmd.endswith('nm'):
d4144e45 389 return self._HandleCommandNm(args)
dfb7e932 390 elif cmd.endswith('objdump'):
d4144e45 391 return self._HandleCommandObjdump(args)
0ddc510e
AK
392 elif cmd.endswith('objcopy'):
393 return self._HandleCommandObjcopy(args)
dfb7e932 394 elif cmd.endswith( 'size'):
d4144e45
SG
395 return self._HandleCommandSize(args)
396
dfb7e932
SG
397 if not result:
398 # Not handled, so abort
c05aa036 399 print('unknown command', kwargs)
dfb7e932
SG
400 sys.exit(1)
401
402 if wc:
403 result.stdout = len(result.stdout.splitlines())
404 return result
d4144e45
SG
405
406 def _HandleMake(self, commit, brd, stage, cwd, *args, **kwargs):
407 """Handle execution of 'make'
408
409 Args:
410 commit: Commit object that is being built
411 brd: Board object that is being built
412 stage: Stage that we are at (mrproper, config, build)
413 cwd: Directory where make should be run
414 args: Arguments to pass to make
d9800699 415 kwargs: Arguments to pass to command.run_pipe()
d4144e45 416 """
dfb7e932 417 self._make_calls += 1
bfb708ad
SG
418 out_dir = ''
419 for arg in args:
420 if arg.startswith('O='):
421 out_dir = arg[2:]
d4144e45
SG
422 if stage == 'mrproper':
423 return command.CommandResult(return_code=0)
424 elif stage == 'config':
bfb708ad
SG
425 fname = os.path.join(cwd or '', out_dir, '.config')
426 tools.write_file(fname, b'CONFIG_SOMETHING=1')
d4144e45
SG
427 return command.CommandResult(return_code=0,
428 combined='Test configuration complete')
429 elif stage == 'build':
dfb7e932 430 stderr = ''
d829f121 431 fname = os.path.join(cwd or '', out_dir, 'u-boot')
c1aa66e7 432 tools.write_file(fname, b'U-Boot')
d7713ad3
TR
433
434 # Handle missing blobs
435 if self._missing:
436 if 'BINMAN_ALLOW_MISSING=1' in args:
437 stderr = '''+Image 'main-section' is missing external blobs and is non-functional: intel-descriptor intel-ifwi intel-fsp-m intel-fsp-s intel-vbt
438Image 'main-section' has faked external blobs and is non-functional: descriptor.bin fsp_m.bin fsp_s.bin vbt.bin
439
440Some images are invalid'''
441 else:
442 stderr = "binman: Filename 'fsp.bin' not found in input path"
443 elif type(commit) is not str:
dfb7e932 444 stderr = self._error.get((brd.target, commit.sequence))
d7713ad3 445
dfb7e932 446 if stderr:
d7713ad3 447 return command.CommandResult(return_code=2, stderr=stderr)
d4144e45
SG
448 return command.CommandResult(return_code=0)
449
450 # Not handled, so abort
c05aa036 451 print('make', stage)
d4144e45
SG
452 sys.exit(1)
453
dfb7e932
SG
454 # Example function to print output lines
455 def print_lines(self, lines):
c05aa036 456 print(len(lines))
dfb7e932 457 for line in lines:
c05aa036 458 print(line)
098b10fb 459 #self.print_lines(terminal.get_print_test_lines())
dfb7e932 460
823e60b6
SG
461 def testNoBoards(self):
462 """Test that buildman aborts when there are no boards"""
c52bd225 463 self._boards = boards.Boards()
823e60b6
SG
464 with self.assertRaises(SystemExit):
465 self._RunControl()
466
d4144e45
SG
467 def testCurrentSource(self):
468 """Very simple test to invoke buildman on the current source"""
dfb7e932 469 self.setupToolchains();
aae62584 470 self._RunControl('-o', self._output_dir)
098b10fb 471 lines = terminal.get_print_test_lines()
938fa37c 472 self.assertIn('Building current source for %d boards' % len(BOARDS),
dfb7e932
SG
473 lines[0].text)
474
475 def testBadBranch(self):
476 """Test that we can detect an invalid branch"""
477 with self.assertRaises(ValueError):
478 self._RunControl('-b', 'badbranch')
479
480 def testBadToolchain(self):
481 """Test that missing toolchains are detected"""
482 self.setupToolchains();
aae62584 483 ret_code = self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
098b10fb 484 lines = terminal.get_print_test_lines()
dfb7e932
SG
485
486 # Buildman always builds the upstream commit as well
487 self.assertIn('Building %d commits for %d boards' %
938fa37c 488 (self._commits, len(BOARDS)), lines[0].text)
dfb7e932
SG
489 self.assertEqual(self._builder.count, self._total_builds)
490
491 # Only sandbox should succeed, the others don't have toolchains
492 self.assertEqual(self._builder.fail,
493 self._total_builds - self._commits)
b1e5e6d2 494 self.assertEqual(ret_code, 100)
dfb7e932
SG
495
496 for commit in range(self._commits):
6014db68 497 for brd in self._boards.get_list():
f4ed4706
SG
498 if brd.arch != 'sandbox':
499 errfile = self._builder.GetErrFile(commit, brd.target)
dfb7e932
SG
500 fd = open(errfile)
501 self.assertEqual(fd.readlines(),
f4ed4706 502 ['No tool chain for %s\n' % brd.arch])
dfb7e932
SG
503 fd.close()
504
505 def testBranch(self):
506 """Test building a branch with all toolchains present"""
aae62584 507 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932
SG
508 self.assertEqual(self._builder.count, self._total_builds)
509 self.assertEqual(self._builder.fail, 0)
510
511 def testCount(self):
512 """Test building a specific number of commitst"""
aae62584 513 self._RunControl('-b', TEST_BRANCH, '-c2', '-o', self._output_dir)
938fa37c 514 self.assertEqual(self._builder.count, 2 * len(BOARDS))
dfb7e932 515 self.assertEqual(self._builder.fail, 0)
eb70a2c0 516 # Each board has a config, and then one make per commit
938fa37c 517 self.assertEqual(self._make_calls, len(BOARDS) * (1 + 2))
dfb7e932
SG
518
519 def testIncremental(self):
520 """Test building a branch twice - the second time should do nothing"""
aae62584 521 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932
SG
522
523 # Each board has a mrproper, config, and then one make per commit
938fa37c 524 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
dfb7e932 525 self._make_calls = 0
aae62584 526 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
dfb7e932
SG
527 self.assertEqual(self._make_calls, 0)
528 self.assertEqual(self._builder.count, self._total_builds)
529 self.assertEqual(self._builder.fail, 0)
530
531 def testForceBuild(self):
532 """The -f flag should force a rebuild"""
aae62584 533 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932 534 self._make_calls = 0
aae62584 535 self._RunControl('-b', TEST_BRANCH, '-f', '-o', self._output_dir, clean_dir=False)
eb70a2c0 536 # Each board has a config and one make per commit
938fa37c 537 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 1))
dfb7e932
SG
538
539 def testForceReconfigure(self):
540 """The -f flag should force a rebuild"""
aae62584 541 self._RunControl('-b', TEST_BRANCH, '-C', '-o', self._output_dir)
eb70a2c0 542 # Each commit has a config and make
938fa37c 543 self.assertEqual(self._make_calls, len(BOARDS) * self._commits * 2)
eb70a2c0 544
eb70a2c0
SG
545 def testMrproper(self):
546 """The -f flag should force a rebuild"""
547 self._RunControl('-b', TEST_BRANCH, '-m', '-o', self._output_dir)
548 # Each board has a mkproper, config and then one make per commit
938fa37c 549 self.assertEqual(self._make_calls, len(BOARDS) * (self._commits + 2))
dfb7e932
SG
550
551 def testErrors(self):
552 """Test handling of build errors"""
553 self._error['board2', 1] = 'fred\n'
aae62584 554 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir)
dfb7e932
SG
555 self.assertEqual(self._builder.count, self._total_builds)
556 self.assertEqual(self._builder.fail, 1)
557
558 # Remove the error. This should have no effect since the commit will
559 # not be rebuilt
560 del self._error['board2', 1]
561 self._make_calls = 0
aae62584 562 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, clean_dir=False)
dfb7e932
SG
563 self.assertEqual(self._builder.count, self._total_builds)
564 self.assertEqual(self._make_calls, 0)
565 self.assertEqual(self._builder.fail, 1)
566
567 # Now use the -F flag to force rebuild of the bad commit
aae62584 568 self._RunControl('-b', TEST_BRANCH, '-o', self._output_dir, '-F', clean_dir=False)
dfb7e932
SG
569 self.assertEqual(self._builder.count, self._total_builds)
570 self.assertEqual(self._builder.fail, 0)
eb70a2c0 571 self.assertEqual(self._make_calls, 2)
f7582ce8
SG
572
573 def testBranchWithSlash(self):
574 """Test building a branch with a '/' in the name"""
575 self._test_branch = '/__dev/__testbranch'
576 self._RunControl('-b', self._test_branch, clean_dir=False)
577 self.assertEqual(self._builder.count, self._total_builds)
578 self.assertEqual(self._builder.fail, 0)
409fc029 579
166a98a4
SG
580 def testEnvironment(self):
581 """Test that the done and environment files are written to out-env"""
582 self._RunControl('-o', self._output_dir)
583 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
584 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
585 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
586
f1a83abe
SG
587 def testEnvironmentUnicode(self):
588 """Test there are no unicode errors when the env has non-ASCII chars"""
589 try:
590 varname = b'buildman_test_var'
591 os.environb[varname] = b'strange\x80chars'
592 self.assertEqual(0, self._RunControl('-o', self._output_dir))
593 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
594 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
595 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'out-env')))
596 finally:
597 del os.environb[varname]
598
d829f121
SG
599 def testWorkInOutput(self):
600 """Test the -w option which should write directly to the output dir"""
c52bd225 601 board_list = boards.Boards()
6014db68 602 board_list.add_board(board.Board(*BOARDS[0]))
d829f121 603 self._RunControl('-o', self._output_dir, '-w', clean_dir=False,
938fa37c 604 brds=board_list)
d829f121
SG
605 self.assertTrue(
606 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
60b285f8
SG
607 self.assertTrue(
608 os.path.exists(os.path.join(self._output_dir, 'done')))
609 self.assertTrue(
610 os.path.exists(os.path.join(self._output_dir, 'out-env')))
d829f121
SG
611
612 def testWorkInOutputFail(self):
613 """Test the -w option failures"""
614 with self.assertRaises(SystemExit) as e:
615 self._RunControl('-o', self._output_dir, '-w', clean_dir=False)
616 self.assertIn("single board", str(e.exception))
617 self.assertFalse(
618 os.path.exists(os.path.join(self._output_dir, 'u-boot')))
619
c52bd225 620 board_list = boards.Boards()
6014db68 621 board_list.add_board(board.Board(*BOARDS[0]))
d829f121
SG
622 with self.assertRaises(SystemExit) as e:
623 self._RunControl('-b', self._test_branch, '-o', self._output_dir,
938fa37c 624 '-w', clean_dir=False, brds=board_list)
d829f121 625 self.assertIn("single commit", str(e.exception))
88daaef1 626
c52bd225 627 board_list = boards.Boards()
6014db68 628 board_list.add_board(board.Board(*BOARDS[0]))
88daaef1
SG
629 with self.assertRaises(SystemExit) as e:
630 self._RunControl('-w', clean_dir=False)
631 self.assertIn("specify -o", str(e.exception))
8116c78f
SG
632
633 def testThreadExceptions(self):
634 """Test that exceptions in threads are reported"""
635 with test_util.capture_sys_output() as (stdout, stderr):
636 self.assertEqual(102, self._RunControl('-o', self._output_dir,
637 test_thread_exceptions=True))
8ca0931a
SG
638 self.assertIn(
639 'Thread exception (use -T0 to run without threads): test exception',
640 stdout.getvalue())
d7713ad3
TR
641
642 def testBlobs(self):
643 """Test handling of missing blobs"""
644 self._missing = True
645
646 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
647 errfile = os.path.join(board0_dir, 'err')
648 logfile = os.path.join(board0_dir, 'log')
649
650 # We expect failure when there are missing blobs
651 result = self._RunControl('board0', '-o', self._output_dir)
652 self.assertEqual(100, result)
653 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
654 self.assertTrue(os.path.exists(errfile))
655 self.assertIn(b"Filename 'fsp.bin' not found in input path",
656 tools.read_file(errfile))
657
658 def testBlobsAllowMissing(self):
659 """Allow missing blobs - still failure but a different exit code"""
660 self._missing = True
661 result = self._RunControl('board0', '-o', self._output_dir, '-M',
662 clean_dir=True)
663 self.assertEqual(101, result)
664 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
665 errfile = os.path.join(board0_dir, 'err')
666 self.assertTrue(os.path.exists(errfile))
667 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
668
669 def testBlobsWarning(self):
670 """Allow missing blobs and ignore warnings"""
671 self._missing = True
672 result = self._RunControl('board0', '-o', self._output_dir, '-MW')
673 self.assertEqual(0, result)
674 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
675 errfile = os.path.join(board0_dir, 'err')
676 self.assertIn(b'Some images are invalid', tools.read_file(errfile))
677
678 def testBlobSettings(self):
679 """Test with no settings"""
680 self.assertEqual(False,
681 control.get_allow_missing(False, False, 1, False))
682 self.assertEqual(True,
683 control.get_allow_missing(True, False, 1, False))
684 self.assertEqual(False,
685 control.get_allow_missing(True, True, 1, False))
686
687 def testBlobSettingsAlways(self):
688 """Test the 'always' policy"""
689 bsettings.SetItem('global', 'allow-missing', 'always')
690 self.assertEqual(True,
691 control.get_allow_missing(False, False, 1, False))
692 self.assertEqual(False,
693 control.get_allow_missing(False, True, 1, False))
694
695 def testBlobSettingsBranch(self):
696 """Test the 'branch' policy"""
697 bsettings.SetItem('global', 'allow-missing', 'branch')
698 self.assertEqual(False,
699 control.get_allow_missing(False, False, 1, False))
700 self.assertEqual(True,
701 control.get_allow_missing(False, False, 1, True))
702 self.assertEqual(False,
703 control.get_allow_missing(False, True, 1, True))
704
705 def testBlobSettingsMultiple(self):
706 """Test the 'multiple' policy"""
707 bsettings.SetItem('global', 'allow-missing', 'multiple')
708 self.assertEqual(False,
709 control.get_allow_missing(False, False, 1, False))
710 self.assertEqual(True,
711 control.get_allow_missing(False, False, 2, False))
712 self.assertEqual(False,
713 control.get_allow_missing(False, True, 2, False))
714
715 def testBlobSettingsBranchMultiple(self):
716 """Test the 'branch multiple' policy"""
717 bsettings.SetItem('global', 'allow-missing', 'branch multiple')
718 self.assertEqual(False,
719 control.get_allow_missing(False, False, 1, False))
720 self.assertEqual(True,
721 control.get_allow_missing(False, False, 1, True))
722 self.assertEqual(True,
723 control.get_allow_missing(False, False, 2, False))
724 self.assertEqual(True,
725 control.get_allow_missing(False, False, 2, True))
726 self.assertEqual(False,
727 control.get_allow_missing(False, True, 2, True))
cd37d5bc 728
93202d72
SG
729 def check_command(self, *extra_args):
730 """Run a command with the extra arguments and return the commands used
731
732 Args:
733 extra_args (list of str): List of extra arguments
734
735 Returns:
736 list of str: Lines returned in the out-cmd file
737 """
738 self._RunControl('-o', self._output_dir, *extra_args)
cd37d5bc
SG
739 board0_dir = os.path.join(self._output_dir, 'current', 'board0')
740 self.assertTrue(os.path.exists(os.path.join(board0_dir, 'done')))
741 cmd_fname = os.path.join(board0_dir, 'out-cmd')
742 self.assertTrue(os.path.exists(cmd_fname))
743 data = tools.read_file(cmd_fname)
bfb708ad
SG
744
745 config_fname = os.path.join(board0_dir, '.config')
746 self.assertTrue(os.path.exists(config_fname))
747 cfg_data = tools.read_file(config_fname)
748
749 return data.splitlines(), cfg_data
93202d72
SG
750
751 def testCmdFile(self):
752 """Test that the -cmd-out file is produced"""
bfb708ad 753 lines = self.check_command()[0]
cd37d5bc
SG
754 self.assertEqual(2, len(lines))
755 self.assertRegex(lines[0], b'make O=/.*board0_defconfig')
756 self.assertRegex(lines[0], b'make O=/.*-s.*')
93202d72
SG
757
758 def testNoLto(self):
759 """Test that the --no-lto flag works"""
bfb708ad 760 lines = self.check_command('-L')[0]
93202d72
SG
761 self.assertIn(b'NO_LTO=1', lines[0])
762
bfb708ad
SG
763 def testReproducible(self):
764 """Test that the -r flag works"""
765 lines, cfg_data = self.check_command('-r')
766 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
767
768 # We should see CONFIG_LOCALVERSION_AUTO unset
769 self.assertEqual(b'''CONFIG_SOMETHING=1
770# CONFIG_LOCALVERSION_AUTO is not set
771''', cfg_data)
772
773 with test_util.capture_sys_output() as (stdout, stderr):
774 lines, cfg_data = self.check_command('-r', '-a', 'LOCALVERSION')
775 self.assertIn(b'SOURCE_DATE_EPOCH=0', lines[0])
776
777 # We should see CONFIG_LOCALVERSION_AUTO unset
778 self.assertEqual(b'''CONFIG_SOMETHING=1
779CONFIG_LOCALVERSION=y
780''', cfg_data)
781 self.assertIn('Not dropping LOCALVERSION_AUTO', stdout.getvalue())