"Das U-Boot" Source Tree
at master 1085 lines 45 kB view raw
1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2012 The Chromium OS Authors. 3# 4 5from filelock import FileLock 6import os 7import shutil 8import sys 9import tempfile 10import time 11import unittest 12from unittest.mock import patch 13 14from buildman import board 15from buildman import boards 16from buildman import bsettings 17from buildman import builder 18from buildman import cfgutil 19from buildman import control 20from buildman import toolchain 21from patman import commit 22from u_boot_pylib import command 23from u_boot_pylib import terminal 24from u_boot_pylib import test_util 25from u_boot_pylib import tools 26 27use_network = True 28 29settings_data = ''' 30# Buildman settings file 31 32[toolchain] 33main: /usr/sbin 34 35[toolchain-alias] 36x86: i386 x86_64 37''' 38 39settings_data_wrapper = ''' 40# Buildman settings file 41 42[toolchain] 43main: /usr/sbin 44 45[toolchain-wrapper] 46wrapper = ccache 47''' 48 49settings_data_homedir = ''' 50# Buildman settings file 51 52[toolchain] 53main = ~/mypath 54 55[toolchain-prefix] 56x86 = ~/mypath-x86- 57''' 58 59migration = '''===================== WARNING ====================== 60This board does not use CONFIG_DM. CONFIG_DM will be 61compulsory starting with the v2020.01 release. 62Failure to update may result in board removal. 63See doc/develop/driver-model/migration.rst for more info. 64==================================================== 65''' 66 67errors = [ 68 '''main.c: In function 'main_loop': 69main.c:260:6: warning: unused variable 'joe' [-Wunused-variable] 70''', 71 '''main.c: In function 'main_loop2': 72main.c:295:2: error: 'fred' undeclared (first use in this function) 73main.c:295:2: note: each undeclared identifier is reported only once for each function it appears in 74make[1]: *** [main.o] Error 1 75make: *** [common/libcommon.o] Error 2 76Make failed 77''', 78 '''arch/arm/dts/socfpga_arria10_socdk_sdmmc.dtb: Warning \ 79(avoid_unnecessary_addr_size): /clocks: unnecessary #address-cells/#size-cells \ 80without "ranges" or child "reg" property 81''', 82 '''powerpc-linux-ld: warning: dot moved backwards before `.bss' 83powerpc-linux-ld: warning: dot moved backwards before `.bss' 84powerpc-linux-ld: u-boot: section .text lma 0xfffc0000 overlaps previous sections 85powerpc-linux-ld: u-boot: section .rodata lma 0xfffef3ec overlaps previous sections 86powerpc-linux-ld: u-boot: section .reloc lma 0xffffa400 overlaps previous sections 87powerpc-linux-ld: u-boot: section .data lma 0xffffcd38 overlaps previous sections 88powerpc-linux-ld: u-boot: section .u_boot_cmd lma 0xffffeb40 overlaps previous sections 89powerpc-linux-ld: u-boot: section .bootpg lma 0xfffff198 overlaps previous sections 90''', 91 '''In file included from %(basedir)sarch/sandbox/cpu/cpu.c:9:0: 92%(basedir)sarch/sandbox/include/asm/state.h:44:0: warning: "xxxx" redefined [enabled by default] 93%(basedir)sarch/sandbox/include/asm/state.h:43:0: note: this is the location of the previous definition 94%(basedir)sarch/sandbox/cpu/cpu.c: In function 'do_reset': 95%(basedir)sarch/sandbox/cpu/cpu.c:27:1: error: unknown type name 'blah' 96%(basedir)sarch/sandbox/cpu/cpu.c:28:12: error: expected declaration specifiers or '...' before numeric constant 97make[2]: *** [arch/sandbox/cpu/cpu.o] Error 1 98make[1]: *** [arch/sandbox/cpu] Error 2 99make[1]: *** Waiting for unfinished jobs.... 100In file included from %(basedir)scommon/board_f.c:55:0: 101%(basedir)sarch/sandbox/include/asm/state.h:44:0: warning: "xxxx" redefined [enabled by default] 102%(basedir)sarch/sandbox/include/asm/state.h:43:0: note: this is the location of the previous definition 103make: *** [sub-make] Error 2 104''' 105] 106 107 108# hash, subject, return code, list of errors/warnings 109commits = [ 110 ['1234', 'upstream/master, migration warning', 0, []], 111 ['5678', 'Second commit, a warning', 0, errors[0:1]], 112 ['9012', 'Third commit, error', 1, errors[0:2]], 113 ['3456', 'Fourth commit, warning', 0, [errors[0], errors[2]]], 114 ['7890', 'Fifth commit, link errors', 1, [errors[0], errors[3]]], 115 ['abcd', 'Sixth commit, fixes all errors', 0, []], 116 ['ef01', 'Seventh commit, fix migration, check directory suppression', 1, 117 [errors[4]]], 118] 119 120BOARDS = [ 121 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0', ''], 122 ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''], 123 ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''], 124 ['Active', 'powerpc', 'mpc83xx', '', 'Tester', 'PowerPC board 2', 'board3', ''], 125 ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''], 126] 127 128BASE_DIR = 'base' 129 130OUTCOME_OK, OUTCOME_WARN, OUTCOME_ERR = range(3) 131 132class Options: 133 """Class that holds build options""" 134 pass 135 136class TestBuild(unittest.TestCase): 137 """Test buildman 138 139 TODO: Write tests for the rest of the functionality 140 """ 141 def setUp(self): 142 # Set up commits to build 143 self.commits = [] 144 sequence = 0 145 for commit_info in commits: 146 comm = commit.Commit(commit_info[0]) 147 comm.subject = commit_info[1] 148 comm.return_code = commit_info[2] 149 comm.error_list = commit_info[3] 150 if sequence < 6: 151 comm.error_list += [migration] 152 comm.sequence = sequence 153 sequence += 1 154 self.commits.append(comm) 155 156 # Set up boards to build 157 self.brds = boards.Boards() 158 for brd in BOARDS: 159 self.brds.add_board(board.Board(*brd)) 160 self.brds.select_boards([]) 161 162 # Add some test settings 163 bsettings.setup(None) 164 bsettings.add_file(settings_data) 165 166 # Set up the toolchains 167 self.toolchains = toolchain.Toolchains() 168 self.toolchains.Add('arm-linux-gcc', test=False) 169 self.toolchains.Add('sparc-linux-gcc', test=False) 170 self.toolchains.Add('powerpc-linux-gcc', test=False) 171 self.toolchains.Add('/path/to/aarch64-linux-gcc', test=False) 172 self.toolchains.Add('gcc', test=False) 173 174 # Avoid sending any output 175 terminal.set_print_test_mode() 176 self._col = terminal.Color() 177 178 self.base_dir = tempfile.mkdtemp() 179 if not os.path.isdir(self.base_dir): 180 os.mkdir(self.base_dir) 181 182 self.cur_time = 0 183 self.valid_pids = [] 184 self.finish_time = None 185 self.finish_pid = None 186 187 def tearDown(self): 188 shutil.rmtree(self.base_dir) 189 190 def Make(self, commit, brd, stage, *args, **kwargs): 191 result = command.CommandResult() 192 boardnum = int(brd.target[-1]) 193 result.return_code = 0 194 result.stderr = '' 195 result.stdout = ('This is the test output for board %s, commit %s' % 196 (brd.target, commit.hash)) 197 if ((boardnum >= 1 and boardnum >= commit.sequence) or 198 boardnum == 4 and commit.sequence == 6): 199 result.return_code = commit.return_code 200 result.stderr = (''.join(commit.error_list) 201 % {'basedir' : self.base_dir + '/.bm-work/00/'}) 202 elif commit.sequence < 6: 203 result.stderr = migration 204 205 result.combined = result.stdout + result.stderr 206 return result 207 208 def assertSummary(self, text, arch, plus, brds, outcome=OUTCOME_ERR): 209 col = self._col 210 expected_colour = (col.GREEN if outcome == OUTCOME_OK else 211 col.YELLOW if outcome == OUTCOME_WARN else col.RED) 212 expect = '%10s: ' % arch 213 # TODO(sjg@chromium.org): If plus is '', we shouldn't need this 214 expect += ' ' + col.build(expected_colour, plus) 215 expect += ' ' 216 for brd in brds: 217 expect += col.build(expected_colour, ' %s' % brd) 218 self.assertEqual(text, expect) 219 220 def _SetupTest(self, echo_lines=False, threads=1, **kwdisplay_args): 221 """Set up the test by running a build and summary 222 223 Args: 224 echo_lines: True to echo lines to the terminal to aid test 225 development 226 kwdisplay_args: Dict of arguments to pass to 227 Builder.SetDisplayOptions() 228 229 Returns: 230 Iterator containing the output lines, each a PrintLine() object 231 """ 232 build = builder.Builder(self.toolchains, self.base_dir, None, threads, 233 2, checkout=False, show_unknown=False) 234 build.do_make = self.Make 235 board_selected = self.brds.get_selected_dict() 236 237 # Build the boards for the pre-defined commits and warnings/errors 238 # associated with each. This calls our Make() to inject the fake output. 239 build.build_boards(self.commits, board_selected, keep_outputs=False, 240 verbose=False) 241 lines = terminal.get_print_test_lines() 242 count = 0 243 for line in lines: 244 if line.text.strip(): 245 count += 1 246 247 # We should get two starting messages, an update for every commit built 248 # and a summary message 249 self.assertEqual(count, len(commits) * len(BOARDS) + 3) 250 build.set_display_options(**kwdisplay_args); 251 build.show_summary(self.commits, board_selected) 252 if echo_lines: 253 terminal.echo_print_test_lines() 254 return iter(terminal.get_print_test_lines()) 255 256 def _CheckOutput(self, lines, list_error_boards=False, 257 filter_dtb_warnings=False, 258 filter_migration_warnings=False): 259 """Check for expected output from the build summary 260 261 Args: 262 lines: Iterator containing the lines returned from the summary 263 list_error_boards: Adjust the check for output produced with the 264 --list-error-boards flag 265 filter_dtb_warnings: Adjust the check for output produced with the 266 --filter-dtb-warnings flag 267 """ 268 def add_line_prefix(prefix, brds, error_str, colour): 269 """Add a prefix to each line of a string 270 271 The training \n in error_str is removed before processing 272 273 Args: 274 prefix: String prefix to add 275 error_str: Error string containing the lines 276 colour: Expected colour for the line. Note that the board list, 277 if present, always appears in magenta 278 279 Returns: 280 New string where each line has the prefix added 281 """ 282 lines = error_str.strip().splitlines() 283 new_lines = [] 284 for line in lines: 285 if brds: 286 expect = self._col.build(colour, prefix + '(') 287 expect += self._col.build(self._col.MAGENTA, brds, 288 bright=False) 289 expect += self._col.build(colour, ') %s' % line) 290 else: 291 expect = self._col.build(colour, prefix + line) 292 new_lines.append(expect) 293 return '\n'.join(new_lines) 294 295 col = terminal.Color() 296 boards01234 = ('board0 board1 board2 board3 board4' 297 if list_error_boards else '') 298 boards1234 = 'board1 board2 board3 board4' if list_error_boards else '' 299 boards234 = 'board2 board3 board4' if list_error_boards else '' 300 boards34 = 'board3 board4' if list_error_boards else '' 301 boards4 = 'board4' if list_error_boards else '' 302 303 # Upstream commit: migration warnings only 304 self.assertEqual(next(lines).text, '01: %s' % commits[0][1]) 305 306 if not filter_migration_warnings: 307 self.assertSummary(next(lines).text, 'arm', 'w+', 308 ['board0', 'board1'], outcome=OUTCOME_WARN) 309 self.assertSummary(next(lines).text, 'powerpc', 'w+', 310 ['board2', 'board3'], outcome=OUTCOME_WARN) 311 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], 312 outcome=OUTCOME_WARN) 313 314 self.assertEqual(next(lines).text, 315 add_line_prefix('+', boards01234, migration, col.RED)) 316 317 # Second commit: all archs should fail with warnings 318 self.assertEqual(next(lines).text, '02: %s' % commits[1][1]) 319 320 if filter_migration_warnings: 321 self.assertSummary(next(lines).text, 'arm', 'w+', 322 ['board1'], outcome=OUTCOME_WARN) 323 self.assertSummary(next(lines).text, 'powerpc', 'w+', 324 ['board2', 'board3'], outcome=OUTCOME_WARN) 325 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], 326 outcome=OUTCOME_WARN) 327 328 # Second commit: The warnings should be listed 329 self.assertEqual(next(lines).text, 330 add_line_prefix('w+', boards1234, errors[0], col.YELLOW)) 331 332 # Third commit: Still fails 333 self.assertEqual(next(lines).text, '03: %s' % commits[2][1]) 334 if filter_migration_warnings: 335 self.assertSummary(next(lines).text, 'arm', '', 336 ['board1'], outcome=OUTCOME_OK) 337 self.assertSummary(next(lines).text, 'powerpc', '+', 338 ['board2', 'board3']) 339 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) 340 341 # Expect a compiler error 342 self.assertEqual(next(lines).text, 343 add_line_prefix('+', boards234, errors[1], col.RED)) 344 345 # Fourth commit: Compile errors are fixed, just have warning for board3 346 self.assertEqual(next(lines).text, '04: %s' % commits[3][1]) 347 if filter_migration_warnings: 348 expect = '%10s: ' % 'powerpc' 349 expect += ' ' + col.build(col.GREEN, '') 350 expect += ' ' 351 expect += col.build(col.GREEN, ' %s' % 'board2') 352 expect += ' ' + col.build(col.YELLOW, 'w+') 353 expect += ' ' 354 expect += col.build(col.YELLOW, ' %s' % 'board3') 355 self.assertEqual(next(lines).text, expect) 356 else: 357 self.assertSummary(next(lines).text, 'powerpc', 'w+', 358 ['board2', 'board3'], outcome=OUTCOME_WARN) 359 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], 360 outcome=OUTCOME_WARN) 361 362 # Compile error fixed 363 self.assertEqual(next(lines).text, 364 add_line_prefix('-', boards234, errors[1], col.GREEN)) 365 366 if not filter_dtb_warnings: 367 self.assertEqual( 368 next(lines).text, 369 add_line_prefix('w+', boards34, errors[2], col.YELLOW)) 370 371 # Fifth commit 372 self.assertEqual(next(lines).text, '05: %s' % commits[4][1]) 373 if filter_migration_warnings: 374 self.assertSummary(next(lines).text, 'powerpc', '', ['board3'], 375 outcome=OUTCOME_OK) 376 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) 377 378 # The second line of errors[3] is a duplicate, so buildman will drop it 379 expect = errors[3].rstrip().split('\n') 380 expect = [expect[0]] + expect[2:] 381 expect = '\n'.join(expect) 382 self.assertEqual(next(lines).text, 383 add_line_prefix('+', boards4, expect, col.RED)) 384 385 if not filter_dtb_warnings: 386 self.assertEqual( 387 next(lines).text, 388 add_line_prefix('w-', boards34, errors[2], col.CYAN)) 389 390 # Sixth commit 391 self.assertEqual(next(lines).text, '06: %s' % commits[5][1]) 392 if filter_migration_warnings: 393 self.assertSummary(next(lines).text, 'sandbox', '', ['board4'], 394 outcome=OUTCOME_OK) 395 else: 396 self.assertSummary(next(lines).text, 'sandbox', 'w+', ['board4'], 397 outcome=OUTCOME_WARN) 398 399 # The second line of errors[3] is a duplicate, so buildman will drop it 400 expect = errors[3].rstrip().split('\n') 401 expect = [expect[0]] + expect[2:] 402 expect = '\n'.join(expect) 403 self.assertEqual(next(lines).text, 404 add_line_prefix('-', boards4, expect, col.GREEN)) 405 self.assertEqual(next(lines).text, 406 add_line_prefix('w-', boards4, errors[0], col.CYAN)) 407 408 # Seventh commit 409 self.assertEqual(next(lines).text, '07: %s' % commits[6][1]) 410 if filter_migration_warnings: 411 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) 412 else: 413 self.assertSummary(next(lines).text, 'arm', '', ['board0', 'board1'], 414 outcome=OUTCOME_OK) 415 self.assertSummary(next(lines).text, 'powerpc', '', 416 ['board2', 'board3'], outcome=OUTCOME_OK) 417 self.assertSummary(next(lines).text, 'sandbox', '+', ['board4']) 418 419 # Pick out the correct error lines 420 expect_str = errors[4].rstrip().replace('%(basedir)s', '').split('\n') 421 expect = expect_str[3:8] + [expect_str[-1]] 422 expect = '\n'.join(expect) 423 if not filter_migration_warnings: 424 self.assertEqual( 425 next(lines).text, 426 add_line_prefix('-', boards01234, migration, col.GREEN)) 427 428 self.assertEqual(next(lines).text, 429 add_line_prefix('+', boards4, expect, col.RED)) 430 431 # Now the warnings lines 432 expect = [expect_str[0]] + expect_str[10:12] + [expect_str[9]] 433 expect = '\n'.join(expect) 434 self.assertEqual(next(lines).text, 435 add_line_prefix('w+', boards4, expect, col.YELLOW)) 436 437 def testOutput(self): 438 """Test basic builder operation and output 439 440 This does a line-by-line verification of the summary output. 441 """ 442 lines = self._SetupTest(show_errors=True) 443 self._CheckOutput(lines, list_error_boards=False, 444 filter_dtb_warnings=False) 445 446 def testErrorBoards(self): 447 """Test output with --list-error-boards 448 449 This does a line-by-line verification of the summary output. 450 """ 451 lines = self._SetupTest(show_errors=True, list_error_boards=True) 452 self._CheckOutput(lines, list_error_boards=True) 453 454 def testFilterDtb(self): 455 """Test output with --filter-dtb-warnings 456 457 This does a line-by-line verification of the summary output. 458 """ 459 lines = self._SetupTest(show_errors=True, filter_dtb_warnings=True) 460 self._CheckOutput(lines, filter_dtb_warnings=True) 461 462 def testFilterMigration(self): 463 """Test output with --filter-migration-warnings 464 465 This does a line-by-line verification of the summary output. 466 """ 467 lines = self._SetupTest(show_errors=True, 468 filter_migration_warnings=True) 469 self._CheckOutput(lines, filter_migration_warnings=True) 470 471 def testSingleThread(self): 472 """Test operation without threading""" 473 lines = self._SetupTest(show_errors=True, threads=0) 474 self._CheckOutput(lines, list_error_boards=False, 475 filter_dtb_warnings=False) 476 477 def _testGit(self): 478 """Test basic builder operation by building a branch""" 479 options = Options() 480 options.git = os.getcwd() 481 options.summary = False 482 options.jobs = None 483 options.dry_run = False 484 #options.git = os.path.join(self.base_dir, 'repo') 485 options.branch = 'test-buildman' 486 options.force_build = False 487 options.list_tool_chains = False 488 options.count = -1 489 options.git_dir = None 490 options.threads = None 491 options.show_unknown = False 492 options.quick = False 493 options.show_errors = False 494 options.keep_outputs = False 495 args = ['tegra20'] 496 control.do_buildman(options, args) 497 498 def testBoardSingle(self): 499 """Test single board selection""" 500 self.assertEqual(self.brds.select_boards(['sandbox']), 501 ({'all': ['board4'], 'sandbox': ['board4']}, [])) 502 503 def testBoardArch(self): 504 """Test single board selection""" 505 self.assertEqual(self.brds.select_boards(['arm']), 506 ({'all': ['board0', 'board1'], 507 'arm': ['board0', 'board1']}, [])) 508 509 def testBoardArchSingle(self): 510 """Test single board selection""" 511 self.assertEqual(self.brds.select_boards(['arm sandbox']), 512 ({'sandbox': ['board4'], 513 'all': ['board0', 'board1', 'board4'], 514 'arm': ['board0', 'board1']}, [])) 515 516 517 def testBoardArchSingleMultiWord(self): 518 """Test single board selection""" 519 self.assertEqual(self.brds.select_boards(['arm', 'sandbox']), 520 ({'sandbox': ['board4'], 521 'all': ['board0', 'board1', 'board4'], 522 'arm': ['board0', 'board1']}, [])) 523 524 def testBoardSingleAnd(self): 525 """Test single board selection""" 526 self.assertEqual(self.brds.select_boards(['Tester & arm']), 527 ({'Tester&arm': ['board0', 'board1'], 528 'all': ['board0', 'board1']}, [])) 529 530 def testBoardTwoAnd(self): 531 """Test single board selection""" 532 self.assertEqual(self.brds.select_boards(['Tester', '&', 'arm', 533 'Tester' '&', 'powerpc', 534 'sandbox']), 535 ({'sandbox': ['board4'], 536 'all': ['board0', 'board1', 'board2', 'board3', 537 'board4'], 538 'Tester&powerpc': ['board2', 'board3'], 539 'Tester&arm': ['board0', 'board1']}, [])) 540 541 def testBoardAll(self): 542 """Test single board selection""" 543 self.assertEqual(self.brds.select_boards([]), 544 ({'all': ['board0', 'board1', 'board2', 'board3', 545 'board4']}, [])) 546 547 def testBoardRegularExpression(self): 548 """Test single board selection""" 549 self.assertEqual(self.brds.select_boards(['T.*r&^Po']), 550 ({'all': ['board2', 'board3'], 551 'T.*r&^Po': ['board2', 'board3']}, [])) 552 553 def testBoardDuplicate(self): 554 """Test single board selection""" 555 self.assertEqual(self.brds.select_boards(['sandbox sandbox', 556 'sandbox']), 557 ({'all': ['board4'], 'sandbox': ['board4']}, [])) 558 def CheckDirs(self, build, dirname): 559 self.assertEqual('base%s' % dirname, build.get_output_dir(1)) 560 self.assertEqual('base%s/fred' % dirname, 561 build.get_build_dir(1, 'fred')) 562 self.assertEqual('base%s/fred/done' % dirname, 563 build.get_done_file(1, 'fred')) 564 self.assertEqual('base%s/fred/u-boot.sizes' % dirname, 565 build.get_func_sizes_file(1, 'fred', 'u-boot')) 566 self.assertEqual('base%s/fred/u-boot.objdump' % dirname, 567 build.get_objdump_file(1, 'fred', 'u-boot')) 568 self.assertEqual('base%s/fred/err' % dirname, 569 build.get_err_file(1, 'fred')) 570 571 def testOutputDir(self): 572 build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2, 573 checkout=False, show_unknown=False) 574 build.commits = self.commits 575 build.commit_count = len(self.commits) 576 subject = self.commits[1].subject.translate(builder.trans_valid_chars) 577 dirname ='/%02d_g%s_%s' % (2, commits[1][0], subject[:20]) 578 self.CheckDirs(build, dirname) 579 580 def testOutputDirCurrent(self): 581 build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2, 582 checkout=False, show_unknown=False) 583 build.commits = None 584 build.commit_count = 0 585 self.CheckDirs(build, '/current') 586 587 def testOutputDirNoSubdirs(self): 588 build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2, 589 checkout=False, show_unknown=False, 590 no_subdirs=True) 591 build.commits = None 592 build.commit_count = 0 593 self.CheckDirs(build, '') 594 595 def testToolchainAliases(self): 596 self.assertTrue(self.toolchains.Select('arm') != None) 597 with self.assertRaises(ValueError): 598 self.toolchains.Select('no-arch') 599 with self.assertRaises(ValueError): 600 self.toolchains.Select('x86') 601 602 self.toolchains = toolchain.Toolchains() 603 self.toolchains.Add('x86_64-linux-gcc', test=False) 604 self.assertTrue(self.toolchains.Select('x86') != None) 605 606 self.toolchains = toolchain.Toolchains() 607 self.toolchains.Add('i386-linux-gcc', test=False) 608 self.assertTrue(self.toolchains.Select('x86') != None) 609 610 def testToolchainDownload(self): 611 """Test that we can download toolchains""" 612 if use_network: 613 with test_util.capture_sys_output() as (stdout, stderr): 614 url = self.toolchains.LocateArchUrl('arm') 615 self.assertRegex(url, 'https://www.kernel.org/pub/tools/' 616 'crosstool/files/bin/x86_64/.*/' 617 'x86_64-gcc-.*-nolibc[-_]arm-.*linux-gnueabi.tar.xz') 618 619 def testGetEnvArgs(self): 620 """Test the GetEnvArgs() function""" 621 tc = self.toolchains.Select('arm') 622 self.assertEqual('arm-linux-', 623 tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) 624 self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_PATH)) 625 self.assertEqual('arm', 626 tc.GetEnvArgs(toolchain.VAR_ARCH)) 627 self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_MAKE_ARGS)) 628 629 tc = self.toolchains.Select('sandbox') 630 self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) 631 632 self.toolchains.Add('/path/to/x86_64-linux-gcc', test=False) 633 tc = self.toolchains.Select('x86') 634 self.assertEqual('/path/to', 635 tc.GetEnvArgs(toolchain.VAR_PATH)) 636 tc.override_toolchain = 'clang' 637 self.assertEqual('HOSTCC=clang CC=clang', 638 tc.GetEnvArgs(toolchain.VAR_MAKE_ARGS)) 639 640 # Test config with ccache wrapper 641 bsettings.setup(None) 642 bsettings.add_file(settings_data_wrapper) 643 644 tc = self.toolchains.Select('arm') 645 self.assertEqual('ccache arm-linux-', 646 tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) 647 648 tc = self.toolchains.Select('sandbox') 649 self.assertEqual('', tc.GetEnvArgs(toolchain.VAR_CROSS_COMPILE)) 650 651 def testMakeEnvironment(self): 652 """Test the MakeEnvironment function""" 653 tc = self.toolchains.Select('arm') 654 env = tc.MakeEnvironment(False) 655 self.assertEqual(env[b'CROSS_COMPILE'], b'arm-linux-') 656 657 tc = self.toolchains.Select('sandbox') 658 env = tc.MakeEnvironment(False) 659 self.assertTrue(b'CROSS_COMPILE' not in env) 660 661 # Test config with ccache wrapper 662 bsettings.setup(None) 663 bsettings.add_file(settings_data_wrapper) 664 665 tc = self.toolchains.Select('arm') 666 env = tc.MakeEnvironment(False) 667 self.assertEqual(env[b'CROSS_COMPILE'], b'ccache arm-linux-') 668 669 tc = self.toolchains.Select('sandbox') 670 env = tc.MakeEnvironment(False) 671 self.assertTrue(b'CROSS_COMPILE' not in env) 672 673 def testPrepareOutputSpace(self): 674 def _Touch(fname): 675 tools.write_file(os.path.join(base_dir, fname), b'') 676 677 base_dir = tempfile.mkdtemp() 678 679 # Add various files that we want removed and left alone 680 to_remove = ['01_g0982734987_title', '102_g92bf_title', 681 '01_g2938abd8_title'] 682 to_leave = ['something_else', '01-something.patch', '01_another'] 683 for name in to_remove + to_leave: 684 _Touch(name) 685 686 build = builder.Builder(self.toolchains, base_dir, None, 1, 2) 687 build.commits = self.commits 688 build.commit_count = len(commits) 689 result = set(build._get_output_space_removals()) 690 expected = set([os.path.join(base_dir, f) for f in to_remove]) 691 self.assertEqual(expected, result) 692 693 def test_adjust_cfg_nop(self): 694 """check various adjustments of config that are nops""" 695 # enable an enabled CONFIG 696 self.assertEqual( 697 'CONFIG_FRED=y', 698 cfgutil.adjust_cfg_line('CONFIG_FRED=y', {'FRED':'FRED'})[0]) 699 700 # disable a disabled CONFIG 701 self.assertEqual( 702 '# CONFIG_FRED is not set', 703 cfgutil.adjust_cfg_line( 704 '# CONFIG_FRED is not set', {'FRED':'~FRED'})[0]) 705 706 # use the adjust_cfg_lines() function 707 self.assertEqual( 708 ['CONFIG_FRED=y'], 709 cfgutil.adjust_cfg_lines(['CONFIG_FRED=y'], {'FRED':'FRED'})) 710 self.assertEqual( 711 ['# CONFIG_FRED is not set'], 712 cfgutil.adjust_cfg_lines(['CONFIG_FRED=y'], {'FRED':'~FRED'})) 713 714 # handling an empty line 715 self.assertEqual('#', cfgutil.adjust_cfg_line('#', {'FRED':'~FRED'})[0]) 716 717 def test_adjust_cfg(self): 718 """check various adjustments of config""" 719 # disable a CONFIG 720 self.assertEqual( 721 '# CONFIG_FRED is not set', 722 cfgutil.adjust_cfg_line('CONFIG_FRED=1' , {'FRED':'~FRED'})[0]) 723 724 # enable a disabled CONFIG 725 self.assertEqual( 726 'CONFIG_FRED=y', 727 cfgutil.adjust_cfg_line( 728 '# CONFIG_FRED is not set', {'FRED':'FRED'})[0]) 729 730 # enable a CONFIG that doesn't exist 731 self.assertEqual( 732 ['CONFIG_FRED=y'], 733 cfgutil.adjust_cfg_lines([], {'FRED':'FRED'})) 734 735 # disable a CONFIG that doesn't exist 736 self.assertEqual( 737 ['# CONFIG_FRED is not set'], 738 cfgutil.adjust_cfg_lines([], {'FRED':'~FRED'})) 739 740 # disable a value CONFIG 741 self.assertEqual( 742 '# CONFIG_FRED is not set', 743 cfgutil.adjust_cfg_line('CONFIG_FRED="fred"' , {'FRED':'~FRED'})[0]) 744 745 # setting a value CONFIG 746 self.assertEqual( 747 'CONFIG_FRED="fred"', 748 cfgutil.adjust_cfg_line('# CONFIG_FRED is not set' , 749 {'FRED':'FRED="fred"'})[0]) 750 751 # changing a value CONFIG 752 self.assertEqual( 753 'CONFIG_FRED="fred"', 754 cfgutil.adjust_cfg_line('CONFIG_FRED="ernie"' , 755 {'FRED':'FRED="fred"'})[0]) 756 757 # setting a value for a CONFIG that doesn't exist 758 self.assertEqual( 759 ['CONFIG_FRED="fred"'], 760 cfgutil.adjust_cfg_lines([], {'FRED':'FRED="fred"'})) 761 762 def test_convert_adjust_cfg_list(self): 763 """Check conversion of the list of changes into a dict""" 764 self.assertEqual({}, cfgutil.convert_list_to_dict(None)) 765 766 expect = { 767 'FRED':'FRED', 768 'MARY':'~MARY', 769 'JOHN':'JOHN=0x123', 770 'ALICE':'ALICE="alice"', 771 'AMY':'AMY', 772 'ABE':'~ABE', 773 'MARK':'MARK=0x456', 774 'ANNA':'ANNA="anna"', 775 } 776 actual = cfgutil.convert_list_to_dict( 777 ['FRED', '~MARY', 'JOHN=0x123', 'ALICE="alice"', 778 'CONFIG_AMY', '~CONFIG_ABE', 'CONFIG_MARK=0x456', 779 'CONFIG_ANNA="anna"']) 780 self.assertEqual(expect, actual) 781 782 def test_check_cfg_file(self): 783 """Test check_cfg_file detects conflicts as expected""" 784 # Check failure to disable CONFIG 785 result = cfgutil.check_cfg_lines(['CONFIG_FRED=1'], {'FRED':'~FRED'}) 786 self.assertEqual([['~FRED', 'CONFIG_FRED=1']], result) 787 788 result = cfgutil.check_cfg_lines( 789 ['CONFIG_FRED=1', 'CONFIG_MARY="mary"'], {'FRED':'~FRED'}) 790 self.assertEqual([['~FRED', 'CONFIG_FRED=1']], result) 791 792 result = cfgutil.check_cfg_lines( 793 ['CONFIG_FRED=1', 'CONFIG_MARY="mary"'], {'MARY':'~MARY'}) 794 self.assertEqual([['~MARY', 'CONFIG_MARY="mary"']], result) 795 796 # Check failure to enable CONFIG 797 result = cfgutil.check_cfg_lines( 798 ['# CONFIG_FRED is not set'], {'FRED':'FRED'}) 799 self.assertEqual([['FRED', '# CONFIG_FRED is not set']], result) 800 801 # Check failure to set CONFIG value 802 result = cfgutil.check_cfg_lines( 803 ['# CONFIG_FRED is not set', 'CONFIG_MARY="not"'], 804 {'MARY':'MARY="mary"', 'FRED':'FRED'}) 805 self.assertEqual([ 806 ['FRED', '# CONFIG_FRED is not set'], 807 ['MARY="mary"', 'CONFIG_MARY="not"']], result) 808 809 # Check failure to add CONFIG value 810 result = cfgutil.check_cfg_lines([], {'MARY':'MARY="mary"'}) 811 self.assertEqual([ 812 ['MARY="mary"', 'Missing expected line: CONFIG_MARY="mary"']], result) 813 814 def get_procs(self): 815 running_fname = os.path.join(self.base_dir, control.RUNNING_FNAME) 816 items = tools.read_file(running_fname, binary=False).split() 817 return [int(x) for x in items] 818 819 def get_time(self): 820 return self.cur_time 821 822 def inc_time(self, amount): 823 self.cur_time += amount 824 825 # Handle a process exiting 826 if self.finish_time == self.cur_time: 827 self.valid_pids = [pid for pid in self.valid_pids 828 if pid != self.finish_pid] 829 830 def kill(self, pid, signal): 831 if pid not in self.valid_pids: 832 raise OSError('Invalid PID') 833 834 def test_process_limit(self): 835 """Test wait_for_process_limit() function""" 836 tmpdir = self.base_dir 837 838 with (patch('time.time', side_effect=self.get_time), 839 patch('time.monotonic', side_effect=self.get_time), 840 patch('time.sleep', side_effect=self.inc_time), 841 patch('os.kill', side_effect=self.kill)): 842 # Grab the process. Since there is no other profcess, this should 843 # immediately succeed 844 control.wait_for_process_limit(1, tmpdir=tmpdir, pid=1) 845 lines = terminal.get_print_test_lines() 846 self.assertEqual(0, self.cur_time) 847 self.assertEqual('Waiting for other buildman processes...', 848 lines[0].text) 849 self.assertEqual(self._col.RED, lines[0].colour) 850 self.assertEqual(False, lines[0].newline) 851 self.assertEqual(True, lines[0].bright) 852 853 self.assertEqual('done...', lines[1].text) 854 self.assertEqual(None, lines[1].colour) 855 self.assertEqual(False, lines[1].newline) 856 self.assertEqual(True, lines[1].bright) 857 858 self.assertEqual('starting build', lines[2].text) 859 self.assertEqual([1], control.read_procs(tmpdir)) 860 self.assertEqual(None, lines[2].colour) 861 self.assertEqual(False, lines[2].newline) 862 self.assertEqual(True, lines[2].bright) 863 864 # Try again, with a different PID...this should eventually timeout 865 # and start the build anyway 866 self.cur_time = 0 867 self.valid_pids = [1] 868 control.wait_for_process_limit(1, tmpdir=tmpdir, pid=2) 869 lines = terminal.get_print_test_lines() 870 self.assertEqual('Waiting for other buildman processes...', 871 lines[0].text) 872 self.assertEqual('timeout...', lines[1].text) 873 self.assertEqual(None, lines[1].colour) 874 self.assertEqual(False, lines[1].newline) 875 self.assertEqual(True, lines[1].bright) 876 self.assertEqual('starting build', lines[2].text) 877 self.assertEqual([1, 2], control.read_procs(tmpdir)) 878 self.assertEqual(control.RUN_WAIT_S, self.cur_time) 879 880 # Check lock-busting 881 self.cur_time = 0 882 self.valid_pids = [1, 2] 883 lock_fname = os.path.join(tmpdir, control.LOCK_FNAME) 884 lock = FileLock(lock_fname) 885 lock.acquire(timeout=1) 886 control.wait_for_process_limit(1, tmpdir=tmpdir, pid=3) 887 lines = terminal.get_print_test_lines() 888 self.assertEqual('Waiting for other buildman processes...', 889 lines[0].text) 890 self.assertEqual('failed to get lock: busting...', lines[1].text) 891 self.assertEqual(None, lines[1].colour) 892 self.assertEqual(False, lines[1].newline) 893 self.assertEqual(True, lines[1].bright) 894 self.assertEqual('timeout...', lines[2].text) 895 self.assertEqual('starting build', lines[3].text) 896 self.assertEqual([1, 2, 3], control.read_procs(tmpdir)) 897 self.assertEqual(control.RUN_WAIT_S, self.cur_time) 898 lock.release() 899 900 # Check handling of dead processes. Here we have PID 2 as a running 901 # process, even though the PID file contains 1, 2 and 3. So we can 902 # add one more PID, to make 2 and 4 903 self.cur_time = 0 904 self.valid_pids = [2] 905 control.wait_for_process_limit(2, tmpdir=tmpdir, pid=4) 906 lines = terminal.get_print_test_lines() 907 self.assertEqual('Waiting for other buildman processes...', 908 lines[0].text) 909 self.assertEqual('done...', lines[1].text) 910 self.assertEqual('starting build', lines[2].text) 911 self.assertEqual([2, 4], control.read_procs(tmpdir)) 912 self.assertEqual(0, self.cur_time) 913 914 # Try again, with PID 2 quitting at time 50. This allows the new 915 # build to start 916 self.cur_time = 0 917 self.valid_pids = [2, 4] 918 self.finish_pid = 2 919 self.finish_time = 50 920 control.wait_for_process_limit(2, tmpdir=tmpdir, pid=5) 921 lines = terminal.get_print_test_lines() 922 self.assertEqual('Waiting for other buildman processes...', 923 lines[0].text) 924 self.assertEqual('done...', lines[1].text) 925 self.assertEqual('starting build', lines[2].text) 926 self.assertEqual([4, 5], control.read_procs(tmpdir)) 927 self.assertEqual(self.finish_time, self.cur_time) 928 929 def call_make_environment(self, tchn, full_path, in_env=None): 930 """Call Toolchain.MakeEnvironment() and process the result 931 932 Args: 933 tchn (Toolchain): Toolchain to use 934 full_path (bool): True to return the full path in CROSS_COMPILE 935 rather than adding it to the PATH variable 936 in_env (dict): Input environment to use, None to use current env 937 938 Returns: 939 tuple: 940 dict: Changes that MakeEnvironment has made to the environment 941 key: Environment variable that was changed 942 value: New value (for PATH this only includes components 943 which were added) 944 str: Full value of the new PATH variable 945 """ 946 env = tchn.MakeEnvironment(full_path, env=in_env) 947 948 # Get the original environment 949 orig_env = dict(os.environb if in_env is None else in_env) 950 orig_path = orig_env[b'PATH'].split(b':') 951 952 # Find new variables 953 diff = dict((k, env[k]) for k in env if orig_env.get(k) != env[k]) 954 955 # Find new / different path components 956 diff_path = None 957 new_path = None 958 if b'PATH' in diff: 959 new_path = diff[b'PATH'].split(b':') 960 diff_paths = [p for p in new_path if p not in orig_path] 961 diff_path = b':'.join(p for p in new_path if p not in orig_path) 962 if diff_path: 963 diff[b'PATH'] = diff_path 964 else: 965 del diff[b'PATH'] 966 return diff, new_path 967 968 def test_toolchain_env(self): 969 """Test PATH and other environment settings for toolchains""" 970 # Use a toolchain which has a path, so that full_path makes a difference 971 tchn = self.toolchains.Select('aarch64') 972 973 # Normal cases 974 diff = self.call_make_environment(tchn, full_path=False)[0] 975 self.assertEqual( 976 {b'CROSS_COMPILE': b'aarch64-linux-', b'LC_ALL': b'C', 977 b'PATH': b'/path/to'}, diff) 978 979 diff = self.call_make_environment(tchn, full_path=True)[0] 980 self.assertEqual( 981 {b'CROSS_COMPILE': b'/path/to/aarch64-linux-', b'LC_ALL': b'C'}, 982 diff) 983 984 # When overriding the toolchain, only LC_ALL should be set 985 tchn.override_toolchain = True 986 diff = self.call_make_environment(tchn, full_path=True)[0] 987 self.assertEqual({b'LC_ALL': b'C'}, diff) 988 989 # Test that virtualenv is handled correctly 990 tchn.override_toolchain = False 991 sys.prefix = '/some/venv' 992 env = dict(os.environb) 993 env[b'PATH'] = b'/some/venv/bin:other/things' 994 tchn.path = '/my/path' 995 diff, diff_path = self.call_make_environment(tchn, False, env) 996 997 self.assertIn(b'PATH', diff) 998 self.assertEqual([b'/some/venv/bin', b'/my/path', b'other/things'], 999 diff_path) 1000 self.assertEqual( 1001 {b'CROSS_COMPILE': b'aarch64-linux-', b'LC_ALL': b'C', 1002 b'PATH': b'/my/path'}, diff) 1003 1004 # Handle a toolchain wrapper 1005 tchn.path = '' 1006 bsettings.add_section('toolchain-wrapper') 1007 bsettings.set_item('toolchain-wrapper', 'my-wrapper', 'fred') 1008 diff = self.call_make_environment(tchn, full_path=True)[0] 1009 self.assertEqual( 1010 {b'CROSS_COMPILE': b'fred aarch64-linux-', b'LC_ALL': b'C'}, diff) 1011 1012 def test_skip_dtc(self): 1013 """Test skipping building the dtc tool""" 1014 old_path = os.getenv('PATH') 1015 try: 1016 os.environ['PATH'] = self.base_dir 1017 1018 # Check a missing tool 1019 with self.assertRaises(ValueError) as exc: 1020 builder.Builder(self.toolchains, self.base_dir, None, 0, 2, 1021 dtc_skip=True) 1022 self.assertIn('Cannot find dtc', str(exc.exception)) 1023 1024 # Create a fake tool to use 1025 dtc = os.path.join(self.base_dir, 'dtc') 1026 tools.write_file(dtc, b'xx') 1027 os.chmod(dtc, 0o777) 1028 1029 build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2, 1030 dtc_skip=True) 1031 toolchain = self.toolchains.Select('arm') 1032 env = build.make_environment(toolchain) 1033 self.assertIn(b'DTC', env) 1034 1035 # Try the normal case, i.e. not skipping the dtc build 1036 build = builder.Builder(self.toolchains, self.base_dir, None, 0, 2) 1037 toolchain = self.toolchains.Select('arm') 1038 env = build.make_environment(toolchain) 1039 self.assertNotIn(b'DTC', env) 1040 finally: 1041 os.environ['PATH'] = old_path 1042 1043 def testHomedir(self): 1044 """Test using ~ in a toolchain or toolchain-prefix section""" 1045 # Add some test settings 1046 bsettings.setup(None) 1047 bsettings.add_file(settings_data_homedir) 1048 1049 # Set up the toolchains 1050 home = os.path.expanduser('~') 1051 toolchains = toolchain.Toolchains() 1052 toolchains.GetSettings() 1053 self.assertEqual([f'{home}/mypath'], toolchains.paths) 1054 1055 # Check scanning 1056 with test_util.capture_sys_output() as (stdout, _): 1057 toolchains.Scan(verbose=True, raise_on_error=False) 1058 lines = iter(stdout.getvalue().splitlines() + ['##done']) 1059 self.assertEqual('Scanning for tool chains', next(lines)) 1060 self.assertEqual(f" - scanning prefix '{home}/mypath-x86-'", 1061 next(lines)) 1062 self.assertEqual( 1063 f"Error: No tool chain found for prefix '{home}/mypath-x86-gcc'", 1064 next(lines)) 1065 self.assertEqual(f" - scanning path '{home}/mypath'", next(lines)) 1066 self.assertEqual(f" - looking in '{home}/mypath/.'", next(lines)) 1067 self.assertEqual(f" - looking in '{home}/mypath/bin'", next(lines)) 1068 self.assertEqual(f" - looking in '{home}/mypath/usr/bin'", 1069 next(lines)) 1070 self.assertEqual('##done', next(lines)) 1071 1072 # Check adding a toolchain 1073 with test_util.capture_sys_output() as (stdout, _): 1074 toolchains.Add('~/aarch64-linux-gcc', test=True, verbose=True) 1075 lines = iter(stdout.getvalue().splitlines() + ['##done']) 1076 self.assertEqual('Tool chain test: BAD', next(lines)) 1077 self.assertEqual(f'Command: {home}/aarch64-linux-gcc --version', 1078 next(lines)) 1079 self.assertEqual('', next(lines)) 1080 self.assertEqual('', next(lines)) 1081 self.assertEqual('##done', next(lines)) 1082 1083 1084if __name__ == "__main__": 1085 unittest.main()