1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2012 The Chromium OS Authors.
12 from buildman import board
13 from buildman import bsettings
14 from buildman import builder
15 from buildman import control
16 from buildman import toolchain
17 from patman import commit
18 from patman import command
19 from patman import terminal
20 from patman import test_util
21 from patman import tools
26 # Buildman settings file
35 migration = '''===================== WARNING ======================
36 This board does not use CONFIG_DM. CONFIG_DM will be
37 compulsory starting with the v2020.01 release.
38 Failure to update may result in board removal.
39 See doc/driver-model/migration.rst for more info.
40 ====================================================
44 '''main.c: In function 'main_loop':
45 main.c:260:6: warning: unused variable 'joe' [-Wunused-variable]
47 '''main.c: In function 'main_loop2':
48 main.c:295:2: error: 'fred' undeclared (first use in this function)
49 main.c:295:2: note: each undeclared identifier is reported only once for each function it appears in
50 make[1]: *** [main.o] Error 1
51 make: *** [common/libcommon.o] Error 2
54 '''arch/arm/dts/socfpga_arria10_socdk_sdmmc.dtb: Warning \
55 (avoid_unnecessary_addr_size): /clocks: unnecessary #address-cells/#size-cells \
56 without "ranges" or child "reg" property
58 '''powerpc-linux-ld: warning: dot moved backwards before `.bss'
59 powerpc-linux-ld: warning: dot moved backwards before `.bss'
60 powerpc-linux-ld: u-boot: section .text lma 0xfffc0000 overlaps previous sections
61 powerpc-linux-ld: u-boot: section .rodata lma 0xfffef3ec overlaps previous sections
62 powerpc-linux-ld: u-boot: section .reloc lma 0xffffa400 overlaps previous sections
63 powerpc-linux-ld: u-boot: section .data lma 0xffffcd38 overlaps previous sections
64 powerpc-linux-ld: u-boot: section .u_boot_cmd lma 0xffffeb40 overlaps previous sections
65 powerpc-linux-ld: u-boot: section .bootpg lma 0xfffff198 overlaps previous sections
67 '''In file included from %(basedir)sarch/sandbox/cpu/cpu.c:9:0:
68 %(basedir)sarch/sandbox/include/asm/state.h:44:0: warning: "xxxx" redefined [enabled by default]
69 %(basedir)sarch/sandbox/include/asm/state.h:43:0: note: this is the location of the previous definition
70 %(basedir)sarch/sandbox/cpu/cpu.c: In function 'do_reset':
71 %(basedir)sarch/sandbox/cpu/cpu.c:27:1: error: unknown type name 'blah'
72 %(basedir)sarch/sandbox/cpu/cpu.c:28:12: error: expected declaration specifiers or '...' before numeric constant
73 make[2]: *** [arch/sandbox/cpu/cpu.o] Error 1
74 make[1]: *** [arch/sandbox/cpu] Error 2
75 make[1]: *** Waiting for unfinished jobs....
76 In file included from %(basedir)scommon/board_f.c:55:0:
77 %(basedir)sarch/sandbox/include/asm/state.h:44:0: warning: "xxxx" redefined [enabled by default]
78 %(basedir)sarch/sandbox/include/asm/state.h:43:0: note: this is the location of the previous definition
79 make: *** [sub-make] Error 2
84 # hash, subject, return code, list of errors/warnings
86 ['1234', 'upstream/master, migration warning', 0, []],
87 ['5678', 'Second commit, a warning', 0, errors[0:1]],
88 ['9012', 'Third commit, error', 1, errors[0:2]],
89 ['3456', 'Fourth commit, warning', 0, [errors[0], errors[2]]],
90 ['7890', 'Fifth commit, link errors', 1, [errors[0], errors[3]]],
91 ['abcd', 'Sixth commit, fixes all errors', 0, []],
92 ['ef01', 'Seventh commit, fix migration, check directory suppression', 1,
97 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0', ''],
98 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''],
99 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
100 ['Active', 'powerpc', 'mpc83xx', '', 'Tester', 'PowerPC board 2', 'board3', ''],
101 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
106 OUTCOME_OK, OUTCOME_WARN, OUTCOME_ERR = range(3)
109 """Class that holds build options"""
112 class TestBuild(unittest.TestCase):
115 TODO: Write tests for the rest of the functionality
118 # Set up commits to build
121 for commit_info in commits:
122 comm = commit.Commit(commit_info[0])
123 comm.subject = commit_info[1]
124 comm.return_code = commit_info[2]
125 comm.error_list = commit_info[3]
127 comm.error_list += [migration]
128 comm.sequence = sequence
130 self.commits.append(comm)
132 # Set up boards to build
133 self.boards = board.Boards()
135 self.boards.AddBoard(board.Board(*brd))
136 self.boards.SelectBoards([])
138 # Add some test settings
139 bsettings.Setup(None)
140 bsettings.AddFile(settings_data)
142 # Set up the toolchains
143 self.toolchains = toolchain.Toolchains()
144 self.toolchains.Add('arm-linux-gcc', test=False)
145 self.toolchains.Add('sparc-linux-gcc', test=False)
146 self.toolchains.Add('powerpc-linux-gcc', test=False)
147 self.toolchains.Add('gcc', test=False)
149 # Avoid sending any output
150 terminal.SetPrintTestMode()
151 self._col = terminal.Color()
153 self.base_dir = tempfile.mkdtemp()
154 if not os.path.isdir(self.base_dir):
155 os.mkdir(self.base_dir)
158 shutil.rmtree(self.base_dir)
160 def Make(self, commit, brd, stage, *args, **kwargs):
161 result = command.CommandResult()
162 boardnum = int(brd.target[-1])
163 result.return_code = 0
165 result.stdout = ('This is the test output for board %s, commit %s' %
166 (brd.target, commit.hash))
167 if ((boardnum >= 1 and boardnum >= commit.sequence) or
168 boardnum == 4 and commit.sequence == 6):
169 result.return_code = commit.return_code
170 result.stderr = (''.join(commit.error_list)
171 % {'basedir' : self.base_dir + '/.bm-work/00/'})
172 elif commit.sequence < 6:
173 result.stderr = migration
175 result.combined = result.stdout + result.stderr
178 def assertSummary(self, text, arch, plus, boards, outcome=OUTCOME_ERR):
180 expected_colour = (col.GREEN if outcome == OUTCOME_OK else
181 col.YELLOW if outcome == OUTCOME_WARN else col.RED)
182 expect = '%10s: ' % arch
183 # TODO(sjg@chromium.org): If plus is '', we shouldn't need this
184 expect += ' ' + col.Color(expected_colour, plus)
187 expect += col.Color(expected_colour, ' %s' % board)
188 self.assertEqual(text, expect)
190 def _SetupTest(self, echo_lines=False, **kwdisplay_args):
191 """Set up the test by running a build and summary
194 echo_lines: True to echo lines to the terminal to aid test
196 kwdisplay_args: Dict of arguemnts to pass to
197 Builder.SetDisplayOptions()
200 Iterator containing the output lines, each a PrintLine() object
202 build = builder.Builder(self.toolchains, self.base_dir, None, 1, 2,
203 checkout=False, show_unknown=False)
204 build.do_make = self.Make
205 board_selected = self.boards.GetSelectedDict()
207 # Build the boards for the pre-defined commits and warnings/errors
208 # associated with each. This calls our Make() to inject the fake output.
209 build.BuildBoards(self.commits, board_selected, keep_outputs=False,
211 lines = terminal.GetPrintTestLines()
214 if line.text.strip():
217 # We should get two starting messages, an update for every commit built
218 # and a summary message
219 self.assertEqual(count, len(commits) * len(boards) + 3)
220 build.SetDisplayOptions(**kwdisplay_args);
221 build.ShowSummary(self.commits, board_selected)
223 terminal.EchoPrintTestLines()
224 return iter(terminal.GetPrintTestLines())
226 def _CheckOutput(self, lines, list_error_boards=False,
227 filter_dtb_warnings=False,
228 filter_migration_warnings=False):
229 """Check for expected output from the build summary
232 lines: Iterator containing the lines returned from the summary
233 list_error_boards: Adjust the check for output produced with the
234 --list-error-boards flag
235 filter_dtb_warnings: Adjust the check for output produced with the
236 --filter-dtb-warnings flag
238 def add_line_prefix(prefix, boards, error_str, colour):
239 """Add a prefix to each line of a string
241 The training \n in error_str is removed before processing
244 prefix: String prefix to add
245 error_str: Error string containing the lines
246 colour: Expected colour for the line. Note that the board list,
247 if present, always appears in magenta
250 New string where each line has the prefix added
252 lines = error_str.strip().splitlines()
256 expect = self._col.Color(colour, prefix + '(')
257 expect += self._col.Color(self._col.MAGENTA, boards,
259 expect += self._col.Color(colour, ') %s' % line)
261 expect = self._col.Color(colour, prefix + line)
262 new_lines.append(expect)
263 return '\n'.join(new_lines)
265 col = terminal.Color()
266 boards01234 = ('board0 board1 board2 board3 board4'
267 if list_error_boards else '')
268 boards1234 = 'board1 board2 board3 board4' if list_error_boards else ''
269 boards234 = 'board2 board3 board4' if list_error_boards else ''
270 boards34 = 'board3 board4' if list_error_boards else ''
271 boards4 = 'board4' if list_error_boards else ''
273 # Upstream commit: migration warnings only
274 self.assertEqual(next(lines).text, '01: %s' % commits[0][1])
276 if not filter_migration_warnings:
277 self.assertSummary(next(lines).text, 'arm', 'w+',
278 ['board0', 'board1'], outcome=OUTCOME_WARN)
279 self.assertSummary(next(lines).text, 'powerpc', 'w+',
280 ['board2', 'board3'], outcome=OUTCOME_WARN)
281 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'],
282 outcome=OUTCOME_WARN)
284 self.assertEqual(next(lines).text,
285 add_line_prefix('+', boards01234, migration, col.RED))
287 # Second commit: all archs should fail with warnings
288 self.assertEqual(next(lines).text, '02: %s' % commits[1][1])
290 if filter_migration_warnings:
291 self.assertSummary(next(lines).text, 'arm', 'w+',
292 ['board1'], outcome=OUTCOME_WARN)
293 self.assertSummary(next(lines).text, 'powerpc', 'w+',
294 ['board2', 'board3'], outcome=OUTCOME_WARN)
295 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'],
296 outcome=OUTCOME_WARN)
298 # Second commit: The warnings should be listed
299 self.assertEqual(next(lines).text,
300 add_line_prefix('w+', boards1234, errors[0], col.YELLOW))
302 # Third commit: Still fails
303 self.assertEqual(next(lines).text, '03: %s' % commits[2][1])
304 if filter_migration_warnings:
305 self.assertSummary(next(lines).text, 'arm', '',
306 ['board1'], outcome=OUTCOME_OK)
307 self.assertSummary(next(lines).text, 'powerpc', '+',
308 ['board2', 'board3'])
309 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4'])
311 # Expect a compiler error
312 self.assertEqual(next(lines).text,
313 add_line_prefix('+', boards234, errors[1], col.RED))
315 # Fourth commit: Compile errors are fixed, just have warning for board3
316 self.assertEqual(next(lines).text, '04: %s' % commits[3][1])
317 if filter_migration_warnings:
318 expect = '%10s: ' % 'powerpc'
319 expect += ' ' + col.Color(col.GREEN, '')
321 expect += col.Color(col.GREEN, ' %s' % 'board2')
322 expect += ' ' + col.Color(col.YELLOW, 'w+')
324 expect += col.Color(col.YELLOW, ' %s' % 'board3')
325 self.assertEqual(next(lines).text, expect)
327 self.assertSummary(next(lines).text, 'powerpc', 'w+',
328 ['board2', 'board3'], outcome=OUTCOME_WARN)
329 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'],
330 outcome=OUTCOME_WARN)
332 # Compile error fixed
333 self.assertEqual(next(lines).text,
334 add_line_prefix('-', boards234, errors[1], col.GREEN))
336 if not filter_dtb_warnings:
339 add_line_prefix('w+', boards34, errors[2], col.YELLOW))
342 self.assertEqual(next(lines).text, '05: %s' % commits[4][1])
343 if filter_migration_warnings:
344 self.assertSummary(next(lines).text, 'powerpc', '', ['board3'],
346 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4'])
348 # The second line of errors[3] is a duplicate, so buildman will drop it
349 expect = errors[3].rstrip().split('\n')
350 expect = [expect[0]] + expect[2:]
351 expect = '\n'.join(expect)
352 self.assertEqual(next(lines).text,
353 add_line_prefix('+', boards4, expect, col.RED))
355 if not filter_dtb_warnings:
358 add_line_prefix('w-', boards34, errors[2], col.CYAN))
361 self.assertEqual(next(lines).text, '06: %s' % commits[5][1])
362 if filter_migration_warnings:
363 self.assertSummary(next(lines).text, 'sandbox', '', ['board4'],
366 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'],
367 outcome=OUTCOME_WARN)
369 # The second line of errors[3] is a duplicate, so buildman will drop it
370 expect = errors[3].rstrip().split('\n')
371 expect = [expect[0]] + expect[2:]
372 expect = '\n'.join(expect)
373 self.assertEqual(next(lines).text,
374 add_line_prefix('-', boards4, expect, col.GREEN))
375 self.assertEqual(next(lines).text,
376 add_line_prefix('w-', boards4, errors[0], col.CYAN))
379 self.assertEqual(next(lines).text, '07: %s' % commits[6][1])
380 if filter_migration_warnings:
381 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4'])
383 self.assertSummary(next(lines).text, 'arm', '', ['board0', 'board1'],
385 self.assertSummary(next(lines).text, 'powerpc', '',
386 ['board2', 'board3'], outcome=OUTCOME_OK)
387 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4'])
389 # Pick out the correct error lines
390 expect_str = errors[4].rstrip().replace('%(basedir)s', '').split('\n')
391 expect = expect_str[3:8] + [expect_str[-1]]
392 expect = '\n'.join(expect)
393 if not filter_migration_warnings:
396 add_line_prefix('-', boards01234, migration, col.GREEN))
398 self.assertEqual(next(lines).text,
399 add_line_prefix('+', boards4, expect, col.RED))
401 # Now the warnings lines
402 expect = [expect_str[0]] + expect_str[10:12] + [expect_str[9]]
403 expect = '\n'.join(expect)
404 self.assertEqual(next(lines).text,
405 add_line_prefix('w+', boards4, expect, col.YELLOW))
407 def testOutput(self):
408 """Test basic builder operation and output
410 This does a line-by-line verification of the summary output.
412 lines = self._SetupTest(show_errors=True)
413 self._CheckOutput(lines, list_error_boards=False,
414 filter_dtb_warnings=False)
416 def testErrorBoards(self):
417 """Test output with --list-error-boards
419 This does a line-by-line verification of the summary output.
421 lines = self._SetupTest(show_errors=True, list_error_boards=True)
422 self._CheckOutput(lines, list_error_boards=True)
424 def testFilterDtb(self):
425 """Test output with --filter-dtb-warnings
427 This does a line-by-line verification of the summary output.
429 lines = self._SetupTest(show_errors=True, filter_dtb_warnings=True)
430 self._CheckOutput(lines, filter_dtb_warnings=True)
432 def testFilterMigration(self):
433 """Test output with --filter-migration-warnings
435 This does a line-by-line verification of the summary output.
437 lines = self._SetupTest(show_errors=True,
438 filter_migration_warnings=True)
439 self._CheckOutput(lines, filter_migration_warnings=True)
442 """Test basic builder operation by building a branch"""
444 options.git = os.getcwd()
445 options.summary = False
447 options.dry_run = False
448 #options.git = os.path.join(self.base_dir, 'repo')
449 options.branch = 'test-buildman'
450 options.force_build = False
451 options.list_tool_chains = False
453 options.git_dir = None
454 options.threads = None
455 options.show_unknown = False
456 options.quick = False
457 options.show_errors = False
458 options.keep_outputs = False
460 control.DoBuildman(options, args)
462 def testBoardSingle(self):
463 """Test single board selection"""
464 self.assertEqual(self.boards.SelectBoards(['sandbox']),
465 ({'all': ['board4'], 'sandbox': ['board4']}, []))
467 def testBoardArch(self):
468 """Test single board selection"""
469 self.assertEqual(self.boards.SelectBoards(['arm']),
470 ({'all': ['board0', 'board1'],
471 'arm': ['board0', 'board1']}, []))
473 def testBoardArchSingle(self):
474 """Test single board selection"""
475 self.assertEqual(self.boards.SelectBoards(['arm sandbox']),
476 ({'sandbox': ['board4'],
477 'all': ['board0', 'board1', 'board4'],
478 'arm': ['board0', 'board1']}, []))
481 def testBoardArchSingleMultiWord(self):
482 """Test single board selection"""
483 self.assertEqual(self.boards.SelectBoards(['arm', 'sandbox']),
484 ({'sandbox': ['board4'],
485 'all': ['board0', 'board1', 'board4'],
486 'arm': ['board0', 'board1']}, []))
488 def testBoardSingleAnd(self):
489 """Test single board selection"""
490 self.assertEqual(self.boards.SelectBoards(['Tester & arm']),
491 ({'Tester&arm': ['board0', 'board1'],
492 'all': ['board0', 'board1']}, []))
494 def testBoardTwoAnd(self):
495 """Test single board selection"""
496 self.assertEqual(self.boards.SelectBoards(['Tester', '&', 'arm',
497 'Tester' '&', 'powerpc',
499 ({'sandbox': ['board4'],
500 'all': ['board0', 'board1', 'board2', 'board3',
502 'Tester&powerpc': ['board2', 'board3'],
503 'Tester&arm': ['board0', 'board1']}, []))
505 def testBoardAll(self):
506 """Test single board selection"""
507 self.assertEqual(self.boards.SelectBoards([]),
508 ({'all': ['board0', 'board1', 'board2', 'board3',
511 def testBoardRegularExpression(self):
512 """Test single board selection"""
513 self.assertEqual(self.boards.SelectBoards(['T.*r&^Po']),
514 ({'all': ['board2', 'board3'],
515 'T.*r&^Po': ['board2', 'board3']}, []))
517 def testBoardDuplicate(self):
518 """Test single board selection"""
519 self.assertEqual(self.boards.SelectBoards(['sandbox sandbox',
521 ({'all': ['board4'], 'sandbox': ['board4']}, []))
522 def CheckDirs(self, build, dirname):
523 self.assertEqual('base%s' % dirname, build._GetOutputDir(1))
524 self.assertEqual('base%s/fred' % dirname,
525 build.GetBuildDir(1, 'fred'))
526 self.assertEqual('base%s/fred/done' % dirname,
527 build.GetDoneFile(1, 'fred'))
528 self.assertEqual('base%s/fred/u-boot.sizes' % dirname,
529 build.GetFuncSizesFile(1, 'fred', 'u-boot'))
530 self.assertEqual('base%s/fred/u-boot.objdump' % dirname,
531 build.GetObjdumpFile(1, 'fred', 'u-boot'))
532 self.assertEqual('base%s/fred/err' % dirname,
533 build.GetErrFile(1, 'fred'))
535 def testOutputDir(self):
536 build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
537 checkout=False, show_unknown=False)
538 build.commits = self.commits
539 build.commit_count = len(self.commits)
540 subject = self.commits[1].subject.translate(builder.trans_valid_chars)
541 dirname ='/%02d_g%s_%s' % (2, commits[1][0], subject[:20])
542 self.CheckDirs(build, dirname)
544 def testOutputDirCurrent(self):
545 build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
546 checkout=False, show_unknown=False)
548 build.commit_count = 0
549 self.CheckDirs(build, '/current')
551 def testOutputDirNoSubdirs(self):
552 build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
553 checkout=False, show_unknown=False,
556 build.commit_count = 0
557 self.CheckDirs(build, '')
559 def testToolchainAliases(self):
560 self.assertTrue(self.toolchains.Select('arm') != None)
561 with self.assertRaises(ValueError):
562 self.toolchains.Select('no-arch')
563 with self.assertRaises(ValueError):
564 self.toolchains.Select('x86')
566 self.toolchains = toolchain.Toolchains()
567 self.toolchains.Add('x86_64-linux-gcc', test=False)
568 self.assertTrue(self.toolchains.Select('x86') != None)
570 self.toolchains = toolchain.Toolchains()
571 self.toolchains.Add('i386-linux-gcc', test=False)
572 self.assertTrue(self.toolchains.Select('x86') != None)
574 def testToolchainDownload(self):
575 """Test that we can download toolchains"""
577 with test_util.capture_sys_output() as (stdout, stderr):
578 url = self.toolchains.LocateArchUrl('arm')
579 self.assertRegexpMatches(url, 'https://www.kernel.org/pub/tools/'
580 'crosstool/files/bin/x86_64/.*/'
581 'x86_64-gcc-.*-nolibc[-_]arm-.*linux-gnueabi.tar.xz')
583 def testGetEnvArgs(self):
584 """Test the GetEnvArgs() function"""
585 tc = self.toolchains.Select('arm')
586 self.assertEqual('arm-linux-',
587 tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE))
588 self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_PATH))
589 self.assertEqual('arm',
590 tc.GetEnvArgs(toolchain.VAR_ARCH))
591 self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_MAKE_ARGS))
593 self.toolchains.Add('/path/to/x86_64-linux-gcc', test=False)
594 tc = self.toolchains.Select('x86')
595 self.assertEqual('/path/to',
596 tc.GetEnvArgs(toolchain.VAR_PATH))
597 tc.override_toolchain = 'clang'
598 self.assertEqual('HOSTCC=clang CC=clang',
599 tc.GetEnvArgs(toolchain.VAR_MAKE_ARGS))
601 def testPrepareOutputSpace(self):
603 tools.WriteFile(os.path.join(base_dir, fname), b'')
605 base_dir = tempfile.mkdtemp()
607 # Add various files that we want removed and left alone
608 to_remove = ['01_g0982734987_title', '102_g92bf_title',
609 '01_g2938abd8_title']
610 to_leave = ['something_else', '01-something.patch', '01_another']
611 for name in to_remove + to_leave:
614 build = builder.Builder(self.toolchains, base_dir, None, 1, 2)
615 build.commits = self.commits
616 build.commit_count = len(commits)
617 result = set(build._GetOutputSpaceRemovals())
618 expected = set([os.path.join(base_dir, f) for f in to_remove])
619 self.assertEqual(expected, result)
621 if __name__ == "__main__":