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