xref: /openbmc/u-boot/tools/buildman/test.py (revision 63e22517)
1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2012 The Chromium OS Authors.
3#
4
5import os
6import shutil
7import sys
8import tempfile
9import time
10import unittest
11
12# Bring in the patman libraries
13our_path = os.path.dirname(os.path.realpath(__file__))
14sys.path.append(os.path.join(our_path, '../patman'))
15
16import board
17import bsettings
18import builder
19import control
20import command
21import commit
22import terminal
23import toolchain
24
25use_network = True
26
27settings_data = '''
28# Buildman settings file
29
30[toolchain]
31main: /usr/sbin
32
33[toolchain-alias]
34x86: i386 x86_64
35'''
36
37errors = [
38    '''main.c: In function 'main_loop':
39main.c:260:6: warning: unused variable 'joe' [-Wunused-variable]
40''',
41    '''main.c: In function 'main_loop2':
42main.c:295:2: error: 'fred' undeclared (first use in this function)
43main.c:295:2: note: each undeclared identifier is reported only once for each function it appears in
44make[1]: *** [main.o] Error 1
45make: *** [common/libcommon.o] Error 2
46Make failed
47''',
48    '''main.c: In function 'main_loop3':
49main.c:280:6: warning: unused variable 'mary' [-Wunused-variable]
50''',
51    '''powerpc-linux-ld: warning: dot moved backwards before `.bss'
52powerpc-linux-ld: warning: dot moved backwards before `.bss'
53powerpc-linux-ld: u-boot: section .text lma 0xfffc0000 overlaps previous sections
54powerpc-linux-ld: u-boot: section .rodata lma 0xfffef3ec overlaps previous sections
55powerpc-linux-ld: u-boot: section .reloc lma 0xffffa400 overlaps previous sections
56powerpc-linux-ld: u-boot: section .data lma 0xffffcd38 overlaps previous sections
57powerpc-linux-ld: u-boot: section .u_boot_cmd lma 0xffffeb40 overlaps previous sections
58powerpc-linux-ld: u-boot: section .bootpg lma 0xfffff198 overlaps previous sections
59''',
60   '''In file included from %(basedir)sarch/sandbox/cpu/cpu.c:9:0:
61%(basedir)sarch/sandbox/include/asm/state.h:44:0: warning: "xxxx" redefined [enabled by default]
62%(basedir)sarch/sandbox/include/asm/state.h:43:0: note: this is the location of the previous definition
63%(basedir)sarch/sandbox/cpu/cpu.c: In function 'do_reset':
64%(basedir)sarch/sandbox/cpu/cpu.c:27:1: error: unknown type name 'blah'
65%(basedir)sarch/sandbox/cpu/cpu.c:28:12: error: expected declaration specifiers or '...' before numeric constant
66make[2]: *** [arch/sandbox/cpu/cpu.o] Error 1
67make[1]: *** [arch/sandbox/cpu] Error 2
68make[1]: *** Waiting for unfinished jobs....
69In file included from %(basedir)scommon/board_f.c:55:0:
70%(basedir)sarch/sandbox/include/asm/state.h:44:0: warning: "xxxx" redefined [enabled by default]
71%(basedir)sarch/sandbox/include/asm/state.h:43:0: note: this is the location of the previous definition
72make: *** [sub-make] Error 2
73'''
74]
75
76
77# hash, subject, return code, list of errors/warnings
78commits = [
79    ['1234', 'upstream/master, ok', 0, []],
80    ['5678', 'Second commit, a warning', 0, errors[0:1]],
81    ['9012', 'Third commit, error', 1, errors[0:2]],
82    ['3456', 'Fourth commit, warning', 0, [errors[0], errors[2]]],
83    ['7890', 'Fifth commit, link errors', 1, [errors[0], errors[3]]],
84    ['abcd', 'Sixth commit, fixes all errors', 0, []],
85    ['ef01', 'Seventh commit, check directory suppression', 1, [errors[4]]],
86]
87
88boards = [
89    ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board0',  ''],
90    ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 2', 'board1', ''],
91    ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''],
92    ['Active', 'powerpc', 'mpc83xx', '', 'Tester', 'PowerPC board 2', 'board3', ''],
93    ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
94]
95
96BASE_DIR = 'base'
97
98class Options:
99    """Class that holds build options"""
100    pass
101
102class TestBuild(unittest.TestCase):
103    """Test buildman
104
105    TODO: Write tests for the rest of the functionality
106    """
107    def setUp(self):
108        # Set up commits to build
109        self.commits = []
110        sequence = 0
111        for commit_info in commits:
112            comm = commit.Commit(commit_info[0])
113            comm.subject = commit_info[1]
114            comm.return_code = commit_info[2]
115            comm.error_list = commit_info[3]
116            comm.sequence = sequence
117            sequence += 1
118            self.commits.append(comm)
119
120        # Set up boards to build
121        self.boards = board.Boards()
122        for brd in boards:
123            self.boards.AddBoard(board.Board(*brd))
124        self.boards.SelectBoards([])
125
126        # Add some test settings
127        bsettings.Setup(None)
128        bsettings.AddFile(settings_data)
129
130        # Set up the toolchains
131        self.toolchains = toolchain.Toolchains()
132        self.toolchains.Add('arm-linux-gcc', test=False)
133        self.toolchains.Add('sparc-linux-gcc', test=False)
134        self.toolchains.Add('powerpc-linux-gcc', test=False)
135        self.toolchains.Add('gcc', test=False)
136
137        # Avoid sending any output
138        terminal.SetPrintTestMode()
139        self._col = terminal.Color()
140
141    def Make(self, commit, brd, stage, *args, **kwargs):
142        global base_dir
143
144        result = command.CommandResult()
145        boardnum = int(brd.target[-1])
146        result.return_code = 0
147        result.stderr = ''
148        result.stdout = ('This is the test output for board %s, commit %s' %
149                (brd.target, commit.hash))
150        if ((boardnum >= 1 and boardnum >= commit.sequence) or
151                boardnum == 4 and commit.sequence == 6):
152            result.return_code = commit.return_code
153            result.stderr = (''.join(commit.error_list)
154                % {'basedir' : base_dir + '/.bm-work/00/'})
155        if stage == 'build':
156            target_dir = None
157            for arg in args:
158                if arg.startswith('O='):
159                    target_dir = arg[2:]
160
161            if not os.path.isdir(target_dir):
162                os.mkdir(target_dir)
163
164        result.combined = result.stdout + result.stderr
165        return result
166
167    def assertSummary(self, text, arch, plus, boards, ok=False):
168        col = self._col
169        expected_colour = col.GREEN if ok else col.RED
170        expect = '%10s: ' % arch
171        # TODO(sjg@chromium.org): If plus is '', we shouldn't need this
172        expect += ' ' + col.Color(expected_colour, plus)
173        expect += '  '
174        for board in boards:
175            expect += col.Color(expected_colour, ' %s' % board)
176        self.assertEqual(text, expect)
177
178    def testOutput(self):
179        """Test basic builder operation and output
180
181        This does a line-by-line verification of the summary output.
182        """
183        global base_dir
184
185        base_dir = tempfile.mkdtemp()
186        if not os.path.isdir(base_dir):
187            os.mkdir(base_dir)
188        build = builder.Builder(self.toolchains, base_dir, None, 1, 2,
189                                checkout=False, show_unknown=False)
190        build.do_make = self.Make
191        board_selected = self.boards.GetSelectedDict()
192
193        build.BuildBoards(self.commits, board_selected, keep_outputs=False,
194                          verbose=False)
195        lines = terminal.GetPrintTestLines()
196        count = 0
197        for line in lines:
198            if line.text.strip():
199                count += 1
200
201        # We should get two starting messages, then an update for every commit
202        # built.
203        self.assertEqual(count, len(commits) * len(boards) + 2)
204        build.SetDisplayOptions(show_errors=True);
205        build.ShowSummary(self.commits, board_selected)
206        #terminal.EchoPrintTestLines()
207        lines = terminal.GetPrintTestLines()
208        self.assertEqual(lines[0].text, '01: %s' % commits[0][1])
209        self.assertEqual(lines[1].text, '02: %s' % commits[1][1])
210
211        # We expect all archs to fail
212        col = terminal.Color()
213        self.assertSummary(lines[2].text, 'sandbox', '+', ['board4'])
214        self.assertSummary(lines[3].text, 'arm', '+', ['board1'])
215        self.assertSummary(lines[4].text, 'powerpc', '+', ['board2', 'board3'])
216
217        # Now we should have the compiler warning
218        self.assertEqual(lines[5].text, 'w+%s' %
219                errors[0].rstrip().replace('\n', '\nw+'))
220        self.assertEqual(lines[5].colour, col.MAGENTA)
221
222        self.assertEqual(lines[6].text, '03: %s' % commits[2][1])
223        self.assertSummary(lines[7].text, 'sandbox', '+', ['board4'])
224        self.assertSummary(lines[8].text, 'arm', '', ['board1'], ok=True)
225        self.assertSummary(lines[9].text, 'powerpc', '+', ['board2', 'board3'])
226
227        # Compiler error
228        self.assertEqual(lines[10].text, '+%s' %
229                errors[1].rstrip().replace('\n', '\n+'))
230
231        self.assertEqual(lines[11].text, '04: %s' % commits[3][1])
232        self.assertSummary(lines[12].text, 'sandbox', '', ['board4'], ok=True)
233        self.assertSummary(lines[13].text, 'powerpc', '', ['board2', 'board3'],
234                ok=True)
235
236        # Compile error fixed
237        self.assertEqual(lines[14].text, '-%s' %
238                errors[1].rstrip().replace('\n', '\n-'))
239        self.assertEqual(lines[14].colour, col.GREEN)
240
241        self.assertEqual(lines[15].text, 'w+%s' %
242                errors[2].rstrip().replace('\n', '\nw+'))
243        self.assertEqual(lines[15].colour, col.MAGENTA)
244
245        self.assertEqual(lines[16].text, '05: %s' % commits[4][1])
246        self.assertSummary(lines[17].text, 'sandbox', '+', ['board4'])
247        self.assertSummary(lines[18].text, 'powerpc', '', ['board3'], ok=True)
248
249        # The second line of errors[3] is a duplicate, so buildman will drop it
250        expect = errors[3].rstrip().split('\n')
251        expect = [expect[0]] + expect[2:]
252        self.assertEqual(lines[19].text, '+%s' %
253                '\n'.join(expect).replace('\n', '\n+'))
254
255        self.assertEqual(lines[20].text, 'w-%s' %
256                errors[2].rstrip().replace('\n', '\nw-'))
257
258        self.assertEqual(lines[21].text, '06: %s' % commits[5][1])
259        self.assertSummary(lines[22].text, 'sandbox', '', ['board4'], ok=True)
260
261        # The second line of errors[3] is a duplicate, so buildman will drop it
262        expect = errors[3].rstrip().split('\n')
263        expect = [expect[0]] + expect[2:]
264        self.assertEqual(lines[23].text, '-%s' %
265                '\n'.join(expect).replace('\n', '\n-'))
266
267        self.assertEqual(lines[24].text, 'w-%s' %
268                errors[0].rstrip().replace('\n', '\nw-'))
269
270        self.assertEqual(lines[25].text, '07: %s' % commits[6][1])
271        self.assertSummary(lines[26].text, 'sandbox', '+', ['board4'])
272
273        # Pick out the correct error lines
274        expect_str = errors[4].rstrip().replace('%(basedir)s', '').split('\n')
275        expect = expect_str[3:8] + [expect_str[-1]]
276        self.assertEqual(lines[27].text, '+%s' %
277                '\n'.join(expect).replace('\n', '\n+'))
278
279        # Now the warnings lines
280        expect = [expect_str[0]] + expect_str[10:12] + [expect_str[9]]
281        self.assertEqual(lines[28].text, 'w+%s' %
282                '\n'.join(expect).replace('\n', '\nw+'))
283
284        self.assertEqual(len(lines), 29)
285        shutil.rmtree(base_dir)
286
287    def _testGit(self):
288        """Test basic builder operation by building a branch"""
289        base_dir = tempfile.mkdtemp()
290        if not os.path.isdir(base_dir):
291            os.mkdir(base_dir)
292        options = Options()
293        options.git = os.getcwd()
294        options.summary = False
295        options.jobs = None
296        options.dry_run = False
297        #options.git = os.path.join(base_dir, 'repo')
298        options.branch = 'test-buildman'
299        options.force_build = False
300        options.list_tool_chains = False
301        options.count = -1
302        options.git_dir = None
303        options.threads = None
304        options.show_unknown = False
305        options.quick = False
306        options.show_errors = False
307        options.keep_outputs = False
308        args = ['tegra20']
309        control.DoBuildman(options, args)
310        shutil.rmtree(base_dir)
311
312    def testBoardSingle(self):
313        """Test single board selection"""
314        self.assertEqual(self.boards.SelectBoards(['sandbox']),
315                         {'all': ['board4'], 'sandbox': ['board4']})
316
317    def testBoardArch(self):
318        """Test single board selection"""
319        self.assertEqual(self.boards.SelectBoards(['arm']),
320                         {'all': ['board0', 'board1'],
321                          'arm': ['board0', 'board1']})
322
323    def testBoardArchSingle(self):
324        """Test single board selection"""
325        self.assertEqual(self.boards.SelectBoards(['arm sandbox']),
326                         {'sandbox': ['board4'],
327                          'all': ['board0', 'board1', 'board4'],
328                          'arm': ['board0', 'board1']})
329
330
331    def testBoardArchSingleMultiWord(self):
332        """Test single board selection"""
333        self.assertEqual(self.boards.SelectBoards(['arm', 'sandbox']),
334                         {'sandbox': ['board4'], 'all': ['board0', 'board1', 'board4'], 'arm': ['board0', 'board1']})
335
336    def testBoardSingleAnd(self):
337        """Test single board selection"""
338        self.assertEqual(self.boards.SelectBoards(['Tester & arm']),
339                         {'Tester&arm': ['board0', 'board1'], 'all': ['board0', 'board1']})
340
341    def testBoardTwoAnd(self):
342        """Test single board selection"""
343        self.assertEqual(self.boards.SelectBoards(['Tester', '&', 'arm',
344                                                   'Tester' '&', 'powerpc',
345                                                   'sandbox']),
346                         {'sandbox': ['board4'],
347                          'all': ['board0', 'board1', 'board2', 'board3',
348                                  'board4'],
349                          'Tester&powerpc': ['board2', 'board3'],
350                          'Tester&arm': ['board0', 'board1']})
351
352    def testBoardAll(self):
353        """Test single board selection"""
354        self.assertEqual(self.boards.SelectBoards([]),
355                         {'all': ['board0', 'board1', 'board2', 'board3',
356                                  'board4']})
357
358    def testBoardRegularExpression(self):
359        """Test single board selection"""
360        self.assertEqual(self.boards.SelectBoards(['T.*r&^Po']),
361                         {'all': ['board2', 'board3'],
362                          'T.*r&^Po': ['board2', 'board3']})
363
364    def testBoardDuplicate(self):
365        """Test single board selection"""
366        self.assertEqual(self.boards.SelectBoards(['sandbox sandbox',
367                                                   'sandbox']),
368                         {'all': ['board4'], 'sandbox': ['board4']})
369    def CheckDirs(self, build, dirname):
370        self.assertEqual('base%s' % dirname, build._GetOutputDir(1))
371        self.assertEqual('base%s/fred' % dirname,
372                         build.GetBuildDir(1, 'fred'))
373        self.assertEqual('base%s/fred/done' % dirname,
374                         build.GetDoneFile(1, 'fred'))
375        self.assertEqual('base%s/fred/u-boot.sizes' % dirname,
376                         build.GetFuncSizesFile(1, 'fred', 'u-boot'))
377        self.assertEqual('base%s/fred/u-boot.objdump' % dirname,
378                         build.GetObjdumpFile(1, 'fred', 'u-boot'))
379        self.assertEqual('base%s/fred/err' % dirname,
380                         build.GetErrFile(1, 'fred'))
381
382    def testOutputDir(self):
383        build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
384                                checkout=False, show_unknown=False)
385        build.commits = self.commits
386        build.commit_count = len(self.commits)
387        subject = self.commits[1].subject.translate(builder.trans_valid_chars)
388        dirname ='/%02d_of_%02d_g%s_%s' % (2, build.commit_count, commits[1][0],
389                                           subject[:20])
390        self.CheckDirs(build, dirname)
391
392    def testOutputDirCurrent(self):
393        build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
394                                checkout=False, show_unknown=False)
395        build.commits = None
396        build.commit_count = 0
397        self.CheckDirs(build, '/current')
398
399    def testOutputDirNoSubdirs(self):
400        build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
401                                checkout=False, show_unknown=False,
402                                no_subdirs=True)
403        build.commits = None
404        build.commit_count = 0
405        self.CheckDirs(build, '')
406
407    def testToolchainAliases(self):
408        self.assertTrue(self.toolchains.Select('arm') != None)
409        with self.assertRaises(ValueError):
410            self.toolchains.Select('no-arch')
411        with self.assertRaises(ValueError):
412            self.toolchains.Select('x86')
413
414        self.toolchains = toolchain.Toolchains()
415        self.toolchains.Add('x86_64-linux-gcc', test=False)
416        self.assertTrue(self.toolchains.Select('x86') != None)
417
418        self.toolchains = toolchain.Toolchains()
419        self.toolchains.Add('i386-linux-gcc', test=False)
420        self.assertTrue(self.toolchains.Select('x86') != None)
421
422    def testToolchainDownload(self):
423        """Test that we can download toolchains"""
424        if use_network:
425            self.assertEqual('https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.9.0/x86_64-gcc-4.9.0-nolibc_arm-unknown-linux-gnueabi.tar.xz',
426                self.toolchains.LocateArchUrl('arm'))
427
428
429if __name__ == "__main__":
430    unittest.main()
431