xref: /openbmc/u-boot/tools/binman/ftest.py (revision e5e701c2)
1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# To run a single test, change to this directory, and:
6#
7#    python -m unittest func_test.TestFunctional.testHelp
8
9from optparse import OptionParser
10import os
11import shutil
12import struct
13import sys
14import tempfile
15import unittest
16
17import binman
18import cmdline
19import command
20import control
21import elf
22import fdt
23import fdt_util
24import fmap_util
25import test_util
26import tools
27import tout
28
29# Contents of test files, corresponding to different entry types
30U_BOOT_DATA           = '1234'
31U_BOOT_IMG_DATA       = 'img'
32U_BOOT_SPL_DATA       = '56780123456789abcde'
33U_BOOT_TPL_DATA       = 'tpl'
34BLOB_DATA             = '89'
35ME_DATA               = '0abcd'
36VGA_DATA              = 'vga'
37U_BOOT_DTB_DATA       = 'udtb'
38U_BOOT_SPL_DTB_DATA   = 'spldtb'
39U_BOOT_TPL_DTB_DATA   = 'tpldtb'
40X86_START16_DATA      = 'start16'
41X86_START16_SPL_DATA  = 'start16spl'
42PPC_MPC85XX_BR_DATA   = 'ppcmpc85xxbr'
43U_BOOT_NODTB_DATA     = 'nodtb with microcode pointer somewhere in here'
44U_BOOT_SPL_NODTB_DATA = 'splnodtb with microcode pointer somewhere in here'
45FSP_DATA              = 'fsp'
46CMC_DATA              = 'cmc'
47VBT_DATA              = 'vbt'
48MRC_DATA              = 'mrc'
49TEXT_DATA             = 'text'
50TEXT_DATA2            = 'text2'
51TEXT_DATA3            = 'text3'
52CROS_EC_RW_DATA       = 'ecrw'
53GBB_DATA              = 'gbbd'
54BMPBLK_DATA           = 'bmp'
55VBLOCK_DATA           = 'vblk'
56
57
58class TestFunctional(unittest.TestCase):
59    """Functional tests for binman
60
61    Most of these use a sample .dts file to build an image and then check
62    that it looks correct. The sample files are in the test/ subdirectory
63    and are numbered.
64
65    For each entry type a very small test file is created using fixed
66    string contents. This makes it easy to test that things look right, and
67    debug problems.
68
69    In some cases a 'real' file must be used - these are also supplied in
70    the test/ diurectory.
71    """
72    @classmethod
73    def setUpClass(self):
74        global entry
75        import entry
76
77        # Handle the case where argv[0] is 'python'
78        self._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
79        self._binman_pathname = os.path.join(self._binman_dir, 'binman')
80
81        # Create a temporary directory for input files
82        self._indir = tempfile.mkdtemp(prefix='binmant.')
83
84        # Create some test files
85        TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
86        TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
87        TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
88        TestFunctional._MakeInputFile('tpl/u-boot-tpl.bin', U_BOOT_TPL_DATA)
89        TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
90        TestFunctional._MakeInputFile('me.bin', ME_DATA)
91        TestFunctional._MakeInputFile('vga.bin', VGA_DATA)
92        self._ResetDtbs()
93        TestFunctional._MakeInputFile('u-boot-x86-16bit.bin', X86_START16_DATA)
94        TestFunctional._MakeInputFile('u-boot-br.bin', PPC_MPC85XX_BR_DATA)
95        TestFunctional._MakeInputFile('spl/u-boot-x86-16bit-spl.bin',
96                                      X86_START16_SPL_DATA)
97        TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
98        TestFunctional._MakeInputFile('spl/u-boot-spl-nodtb.bin',
99                                      U_BOOT_SPL_NODTB_DATA)
100        TestFunctional._MakeInputFile('fsp.bin', FSP_DATA)
101        TestFunctional._MakeInputFile('cmc.bin', CMC_DATA)
102        TestFunctional._MakeInputFile('vbt.bin', VBT_DATA)
103        TestFunctional._MakeInputFile('mrc.bin', MRC_DATA)
104        TestFunctional._MakeInputFile('ecrw.bin', CROS_EC_RW_DATA)
105        TestFunctional._MakeInputDir('devkeys')
106        TestFunctional._MakeInputFile('bmpblk.bin', BMPBLK_DATA)
107        self._output_setup = False
108
109        # ELF file with a '_dt_ucode_base_size' symbol
110        with open(self.TestFile('u_boot_ucode_ptr')) as fd:
111            TestFunctional._MakeInputFile('u-boot', fd.read())
112
113        # Intel flash descriptor file
114        with open(self.TestFile('descriptor.bin')) as fd:
115            TestFunctional._MakeInputFile('descriptor.bin', fd.read())
116
117    @classmethod
118    def tearDownClass(self):
119        """Remove the temporary input directory and its contents"""
120        if self._indir:
121            shutil.rmtree(self._indir)
122        self._indir = None
123
124    def setUp(self):
125        # Enable this to turn on debugging output
126        # tout.Init(tout.DEBUG)
127        command.test_result = None
128
129    def tearDown(self):
130        """Remove the temporary output directory"""
131        tools._FinaliseForTest()
132
133    @classmethod
134    def _ResetDtbs(self):
135        TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
136        TestFunctional._MakeInputFile('spl/u-boot-spl.dtb', U_BOOT_SPL_DTB_DATA)
137        TestFunctional._MakeInputFile('tpl/u-boot-tpl.dtb', U_BOOT_TPL_DTB_DATA)
138
139    def _RunBinman(self, *args, **kwargs):
140        """Run binman using the command line
141
142        Args:
143            Arguments to pass, as a list of strings
144            kwargs: Arguments to pass to Command.RunPipe()
145        """
146        result = command.RunPipe([[self._binman_pathname] + list(args)],
147                capture=True, capture_stderr=True, raise_on_error=False)
148        if result.return_code and kwargs.get('raise_on_error', True):
149            raise Exception("Error running '%s': %s" % (' '.join(args),
150                            result.stdout + result.stderr))
151        return result
152
153    def _DoBinman(self, *args):
154        """Run binman using directly (in the same process)
155
156        Args:
157            Arguments to pass, as a list of strings
158        Returns:
159            Return value (0 for success)
160        """
161        args = list(args)
162        if '-D' in sys.argv:
163            args = args + ['-D']
164        (options, args) = cmdline.ParseArgs(args)
165        options.pager = 'binman-invalid-pager'
166        options.build_dir = self._indir
167
168        # For testing, you can force an increase in verbosity here
169        # options.verbosity = tout.DEBUG
170        return control.Binman(options, args)
171
172    def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
173                    entry_args=None):
174        """Run binman with a given test file
175
176        Args:
177            fname: Device-tree source filename to use (e.g. 05_simple.dts)
178            debug: True to enable debugging output
179            map: True to output map files for the images
180            update_dtb: Update the offset and size of each entry in the device
181                tree before packing it into the image
182        """
183        args = ['-p', '-I', self._indir, '-d', self.TestFile(fname)]
184        if debug:
185            args.append('-D')
186        if map:
187            args.append('-m')
188        if update_dtb:
189            args.append('-up')
190        if entry_args:
191            for arg, value in entry_args.iteritems():
192                args.append('-a%s=%s' % (arg, value))
193        return self._DoBinman(*args)
194
195    def _SetupDtb(self, fname, outfile='u-boot.dtb'):
196        """Set up a new test device-tree file
197
198        The given file is compiled and set up as the device tree to be used
199        for ths test.
200
201        Args:
202            fname: Filename of .dts file to read
203            outfile: Output filename for compiled device-tree binary
204
205        Returns:
206            Contents of device-tree binary
207        """
208        if not self._output_setup:
209            tools.PrepareOutputDir(self._indir, True)
210            self._output_setup = True
211        dtb = fdt_util.EnsureCompiled(self.TestFile(fname))
212        with open(dtb) as fd:
213            data = fd.read()
214            TestFunctional._MakeInputFile(outfile, data)
215            return data
216
217    def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False,
218                       update_dtb=False, entry_args=None):
219        """Run binman and return the resulting image
220
221        This runs binman with a given test file and then reads the resulting
222        output file. It is a shortcut function since most tests need to do
223        these steps.
224
225        Raises an assertion failure if binman returns a non-zero exit code.
226
227        Args:
228            fname: Device-tree source filename to use (e.g. 05_simple.dts)
229            use_real_dtb: True to use the test file as the contents of
230                the u-boot-dtb entry. Normally this is not needed and the
231                test contents (the U_BOOT_DTB_DATA string) can be used.
232                But in some test we need the real contents.
233            map: True to output map files for the images
234            update_dtb: Update the offset and size of each entry in the device
235                tree before packing it into the image
236
237        Returns:
238            Tuple:
239                Resulting image contents
240                Device tree contents
241                Map data showing contents of image (or None if none)
242                Output device tree binary filename ('u-boot.dtb' path)
243        """
244        dtb_data = None
245        # Use the compiled test file as the u-boot-dtb input
246        if use_real_dtb:
247            dtb_data = self._SetupDtb(fname)
248
249        try:
250            retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb,
251                                       entry_args=entry_args)
252            self.assertEqual(0, retcode)
253            out_dtb_fname = control.GetFdtPath('u-boot.dtb')
254
255            # Find the (only) image, read it and return its contents
256            image = control.images['image']
257            image_fname = tools.GetOutputFilename('image.bin')
258            self.assertTrue(os.path.exists(image_fname))
259            if map:
260                map_fname = tools.GetOutputFilename('image.map')
261                with open(map_fname) as fd:
262                    map_data = fd.read()
263            else:
264                map_data = None
265            with open(image_fname) as fd:
266                return fd.read(), dtb_data, map_data, out_dtb_fname
267        finally:
268            # Put the test file back
269            if use_real_dtb:
270                self._ResetDtbs()
271
272    def _DoReadFile(self, fname, use_real_dtb=False):
273        """Helper function which discards the device-tree binary
274
275        Args:
276            fname: Device-tree source filename to use (e.g. 05_simple.dts)
277            use_real_dtb: True to use the test file as the contents of
278                the u-boot-dtb entry. Normally this is not needed and the
279                test contents (the U_BOOT_DTB_DATA string) can be used.
280                But in some test we need the real contents.
281
282        Returns:
283            Resulting image contents
284        """
285        return self._DoReadFileDtb(fname, use_real_dtb)[0]
286
287    @classmethod
288    def _MakeInputFile(self, fname, contents):
289        """Create a new test input file, creating directories as needed
290
291        Args:
292            fname: Filename to create
293            contents: File contents to write in to the file
294        Returns:
295            Full pathname of file created
296        """
297        pathname = os.path.join(self._indir, fname)
298        dirname = os.path.dirname(pathname)
299        if dirname and not os.path.exists(dirname):
300            os.makedirs(dirname)
301        with open(pathname, 'wb') as fd:
302            fd.write(contents)
303        return pathname
304
305    @classmethod
306    def _MakeInputDir(self, dirname):
307        """Create a new test input directory, creating directories as needed
308
309        Args:
310            dirname: Directory name to create
311
312        Returns:
313            Full pathname of directory created
314        """
315        pathname = os.path.join(self._indir, dirname)
316        if not os.path.exists(pathname):
317            os.makedirs(pathname)
318        return pathname
319
320    @classmethod
321    def TestFile(self, fname):
322        return os.path.join(self._binman_dir, 'test', fname)
323
324    def AssertInList(self, grep_list, target):
325        """Assert that at least one of a list of things is in a target
326
327        Args:
328            grep_list: List of strings to check
329            target: Target string
330        """
331        for grep in grep_list:
332            if grep in target:
333                return
334        self.fail("Error: '%' not found in '%s'" % (grep_list, target))
335
336    def CheckNoGaps(self, entries):
337        """Check that all entries fit together without gaps
338
339        Args:
340            entries: List of entries to check
341        """
342        offset = 0
343        for entry in entries.values():
344            self.assertEqual(offset, entry.offset)
345            offset += entry.size
346
347    def GetFdtLen(self, dtb):
348        """Get the totalsize field from a device-tree binary
349
350        Args:
351            dtb: Device-tree binary contents
352
353        Returns:
354            Total size of device-tree binary, from the header
355        """
356        return struct.unpack('>L', dtb[4:8])[0]
357
358    def _GetPropTree(self, dtb, prop_names):
359        def AddNode(node, path):
360            if node.name != '/':
361                path += '/' + node.name
362            for subnode in node.subnodes:
363                for prop in subnode.props.values():
364                    if prop.name in prop_names:
365                        prop_path = path + '/' + subnode.name + ':' + prop.name
366                        tree[prop_path[len('/binman/'):]] = fdt_util.fdt32_to_cpu(
367                            prop.value)
368                AddNode(subnode, path)
369
370        tree = {}
371        AddNode(dtb.GetRoot(), '')
372        return tree
373
374    def testRun(self):
375        """Test a basic run with valid args"""
376        result = self._RunBinman('-h')
377
378    def testFullHelp(self):
379        """Test that the full help is displayed with -H"""
380        result = self._RunBinman('-H')
381        help_file = os.path.join(self._binman_dir, 'README')
382        # Remove possible extraneous strings
383        extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
384        gothelp = result.stdout.replace(extra, '')
385        self.assertEqual(len(gothelp), os.path.getsize(help_file))
386        self.assertEqual(0, len(result.stderr))
387        self.assertEqual(0, result.return_code)
388
389    def testFullHelpInternal(self):
390        """Test that the full help is displayed with -H"""
391        try:
392            command.test_result = command.CommandResult()
393            result = self._DoBinman('-H')
394            help_file = os.path.join(self._binman_dir, 'README')
395        finally:
396            command.test_result = None
397
398    def testHelp(self):
399        """Test that the basic help is displayed with -h"""
400        result = self._RunBinman('-h')
401        self.assertTrue(len(result.stdout) > 200)
402        self.assertEqual(0, len(result.stderr))
403        self.assertEqual(0, result.return_code)
404
405    def testBoard(self):
406        """Test that we can run it with a specific board"""
407        self._SetupDtb('05_simple.dts', 'sandbox/u-boot.dtb')
408        TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
409        result = self._DoBinman('-b', 'sandbox')
410        self.assertEqual(0, result)
411
412    def testNeedBoard(self):
413        """Test that we get an error when no board ius supplied"""
414        with self.assertRaises(ValueError) as e:
415            result = self._DoBinman()
416        self.assertIn("Must provide a board to process (use -b <board>)",
417                str(e.exception))
418
419    def testMissingDt(self):
420        """Test that an invalid device-tree file generates an error"""
421        with self.assertRaises(Exception) as e:
422            self._RunBinman('-d', 'missing_file')
423        # We get one error from libfdt, and a different one from fdtget.
424        self.AssertInList(["Couldn't open blob from 'missing_file'",
425                           'No such file or directory'], str(e.exception))
426
427    def testBrokenDt(self):
428        """Test that an invalid device-tree source file generates an error
429
430        Since this is a source file it should be compiled and the error
431        will come from the device-tree compiler (dtc).
432        """
433        with self.assertRaises(Exception) as e:
434            self._RunBinman('-d', self.TestFile('01_invalid.dts'))
435        self.assertIn("FATAL ERROR: Unable to parse input tree",
436                str(e.exception))
437
438    def testMissingNode(self):
439        """Test that a device tree without a 'binman' node generates an error"""
440        with self.assertRaises(Exception) as e:
441            self._DoBinman('-d', self.TestFile('02_missing_node.dts'))
442        self.assertIn("does not have a 'binman' node", str(e.exception))
443
444    def testEmpty(self):
445        """Test that an empty binman node works OK (i.e. does nothing)"""
446        result = self._RunBinman('-d', self.TestFile('03_empty.dts'))
447        self.assertEqual(0, len(result.stderr))
448        self.assertEqual(0, result.return_code)
449
450    def testInvalidEntry(self):
451        """Test that an invalid entry is flagged"""
452        with self.assertRaises(Exception) as e:
453            result = self._RunBinman('-d',
454                                     self.TestFile('04_invalid_entry.dts'))
455        self.assertIn("Unknown entry type 'not-a-valid-type' in node "
456                "'/binman/not-a-valid-type'", str(e.exception))
457
458    def testSimple(self):
459        """Test a simple binman with a single file"""
460        data = self._DoReadFile('05_simple.dts')
461        self.assertEqual(U_BOOT_DATA, data)
462
463    def testSimpleDebug(self):
464        """Test a simple binman run with debugging enabled"""
465        data = self._DoTestFile('05_simple.dts', debug=True)
466
467    def testDual(self):
468        """Test that we can handle creating two images
469
470        This also tests image padding.
471        """
472        retcode = self._DoTestFile('06_dual_image.dts')
473        self.assertEqual(0, retcode)
474
475        image = control.images['image1']
476        self.assertEqual(len(U_BOOT_DATA), image._size)
477        fname = tools.GetOutputFilename('image1.bin')
478        self.assertTrue(os.path.exists(fname))
479        with open(fname) as fd:
480            data = fd.read()
481            self.assertEqual(U_BOOT_DATA, data)
482
483        image = control.images['image2']
484        self.assertEqual(3 + len(U_BOOT_DATA) + 5, image._size)
485        fname = tools.GetOutputFilename('image2.bin')
486        self.assertTrue(os.path.exists(fname))
487        with open(fname) as fd:
488            data = fd.read()
489            self.assertEqual(U_BOOT_DATA, data[3:7])
490            self.assertEqual(chr(0) * 3, data[:3])
491            self.assertEqual(chr(0) * 5, data[7:])
492
493    def testBadAlign(self):
494        """Test that an invalid alignment value is detected"""
495        with self.assertRaises(ValueError) as e:
496            self._DoTestFile('07_bad_align.dts')
497        self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
498                      "of two", str(e.exception))
499
500    def testPackSimple(self):
501        """Test that packing works as expected"""
502        retcode = self._DoTestFile('08_pack.dts')
503        self.assertEqual(0, retcode)
504        self.assertIn('image', control.images)
505        image = control.images['image']
506        entries = image.GetEntries()
507        self.assertEqual(5, len(entries))
508
509        # First u-boot
510        self.assertIn('u-boot', entries)
511        entry = entries['u-boot']
512        self.assertEqual(0, entry.offset)
513        self.assertEqual(len(U_BOOT_DATA), entry.size)
514
515        # Second u-boot, aligned to 16-byte boundary
516        self.assertIn('u-boot-align', entries)
517        entry = entries['u-boot-align']
518        self.assertEqual(16, entry.offset)
519        self.assertEqual(len(U_BOOT_DATA), entry.size)
520
521        # Third u-boot, size 23 bytes
522        self.assertIn('u-boot-size', entries)
523        entry = entries['u-boot-size']
524        self.assertEqual(20, entry.offset)
525        self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
526        self.assertEqual(23, entry.size)
527
528        # Fourth u-boot, placed immediate after the above
529        self.assertIn('u-boot-next', entries)
530        entry = entries['u-boot-next']
531        self.assertEqual(43, entry.offset)
532        self.assertEqual(len(U_BOOT_DATA), entry.size)
533
534        # Fifth u-boot, placed at a fixed offset
535        self.assertIn('u-boot-fixed', entries)
536        entry = entries['u-boot-fixed']
537        self.assertEqual(61, entry.offset)
538        self.assertEqual(len(U_BOOT_DATA), entry.size)
539
540        self.assertEqual(65, image._size)
541
542    def testPackExtra(self):
543        """Test that extra packing feature works as expected"""
544        retcode = self._DoTestFile('09_pack_extra.dts')
545
546        self.assertEqual(0, retcode)
547        self.assertIn('image', control.images)
548        image = control.images['image']
549        entries = image.GetEntries()
550        self.assertEqual(5, len(entries))
551
552        # First u-boot with padding before and after
553        self.assertIn('u-boot', entries)
554        entry = entries['u-boot']
555        self.assertEqual(0, entry.offset)
556        self.assertEqual(3, entry.pad_before)
557        self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
558
559        # Second u-boot has an aligned size, but it has no effect
560        self.assertIn('u-boot-align-size-nop', entries)
561        entry = entries['u-boot-align-size-nop']
562        self.assertEqual(12, entry.offset)
563        self.assertEqual(4, entry.size)
564
565        # Third u-boot has an aligned size too
566        self.assertIn('u-boot-align-size', entries)
567        entry = entries['u-boot-align-size']
568        self.assertEqual(16, entry.offset)
569        self.assertEqual(32, entry.size)
570
571        # Fourth u-boot has an aligned end
572        self.assertIn('u-boot-align-end', entries)
573        entry = entries['u-boot-align-end']
574        self.assertEqual(48, entry.offset)
575        self.assertEqual(16, entry.size)
576
577        # Fifth u-boot immediately afterwards
578        self.assertIn('u-boot-align-both', entries)
579        entry = entries['u-boot-align-both']
580        self.assertEqual(64, entry.offset)
581        self.assertEqual(64, entry.size)
582
583        self.CheckNoGaps(entries)
584        self.assertEqual(128, image._size)
585
586    def testPackAlignPowerOf2(self):
587        """Test that invalid entry alignment is detected"""
588        with self.assertRaises(ValueError) as e:
589            self._DoTestFile('10_pack_align_power2.dts')
590        self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
591                      "of two", str(e.exception))
592
593    def testPackAlignSizePowerOf2(self):
594        """Test that invalid entry size alignment is detected"""
595        with self.assertRaises(ValueError) as e:
596            self._DoTestFile('11_pack_align_size_power2.dts')
597        self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
598                      "power of two", str(e.exception))
599
600    def testPackInvalidAlign(self):
601        """Test detection of an offset that does not match its alignment"""
602        with self.assertRaises(ValueError) as e:
603            self._DoTestFile('12_pack_inv_align.dts')
604        self.assertIn("Node '/binman/u-boot': Offset 0x5 (5) does not match "
605                      "align 0x4 (4)", str(e.exception))
606
607    def testPackInvalidSizeAlign(self):
608        """Test that invalid entry size alignment is detected"""
609        with self.assertRaises(ValueError) as e:
610            self._DoTestFile('13_pack_inv_size_align.dts')
611        self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
612                      "align-size 0x4 (4)", str(e.exception))
613
614    def testPackOverlap(self):
615        """Test that overlapping regions are detected"""
616        with self.assertRaises(ValueError) as e:
617            self._DoTestFile('14_pack_overlap.dts')
618        self.assertIn("Node '/binman/u-boot-align': Offset 0x3 (3) overlaps "
619                      "with previous entry '/binman/u-boot' ending at 0x4 (4)",
620                      str(e.exception))
621
622    def testPackEntryOverflow(self):
623        """Test that entries that overflow their size are detected"""
624        with self.assertRaises(ValueError) as e:
625            self._DoTestFile('15_pack_overflow.dts')
626        self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
627                      "but entry size is 0x3 (3)", str(e.exception))
628
629    def testPackImageOverflow(self):
630        """Test that entries which overflow the image size are detected"""
631        with self.assertRaises(ValueError) as e:
632            self._DoTestFile('16_pack_image_overflow.dts')
633        self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section "
634                      "size 0x3 (3)", str(e.exception))
635
636    def testPackImageSize(self):
637        """Test that the image size can be set"""
638        retcode = self._DoTestFile('17_pack_image_size.dts')
639        self.assertEqual(0, retcode)
640        self.assertIn('image', control.images)
641        image = control.images['image']
642        self.assertEqual(7, image._size)
643
644    def testPackImageSizeAlign(self):
645        """Test that image size alignemnt works as expected"""
646        retcode = self._DoTestFile('18_pack_image_align.dts')
647        self.assertEqual(0, retcode)
648        self.assertIn('image', control.images)
649        image = control.images['image']
650        self.assertEqual(16, image._size)
651
652    def testPackInvalidImageAlign(self):
653        """Test that invalid image alignment is detected"""
654        with self.assertRaises(ValueError) as e:
655            self._DoTestFile('19_pack_inv_image_align.dts')
656        self.assertIn("Section '/binman': Size 0x7 (7) does not match "
657                      "align-size 0x8 (8)", str(e.exception))
658
659    def testPackAlignPowerOf2(self):
660        """Test that invalid image alignment is detected"""
661        with self.assertRaises(ValueError) as e:
662            self._DoTestFile('20_pack_inv_image_align_power2.dts')
663        self.assertIn("Section '/binman': Alignment size 131 must be a power of "
664                      "two", str(e.exception))
665
666    def testImagePadByte(self):
667        """Test that the image pad byte can be specified"""
668        with open(self.TestFile('bss_data')) as fd:
669            TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
670        data = self._DoReadFile('21_image_pad.dts')
671        self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 1) + U_BOOT_DATA, data)
672
673    def testImageName(self):
674        """Test that image files can be named"""
675        retcode = self._DoTestFile('22_image_name.dts')
676        self.assertEqual(0, retcode)
677        image = control.images['image1']
678        fname = tools.GetOutputFilename('test-name')
679        self.assertTrue(os.path.exists(fname))
680
681        image = control.images['image2']
682        fname = tools.GetOutputFilename('test-name.xx')
683        self.assertTrue(os.path.exists(fname))
684
685    def testBlobFilename(self):
686        """Test that generic blobs can be provided by filename"""
687        data = self._DoReadFile('23_blob.dts')
688        self.assertEqual(BLOB_DATA, data)
689
690    def testPackSorted(self):
691        """Test that entries can be sorted"""
692        data = self._DoReadFile('24_sorted.dts')
693        self.assertEqual(chr(0) * 1 + U_BOOT_SPL_DATA + chr(0) * 2 +
694                         U_BOOT_DATA, data)
695
696    def testPackZeroOffset(self):
697        """Test that an entry at offset 0 is not given a new offset"""
698        with self.assertRaises(ValueError) as e:
699            self._DoTestFile('25_pack_zero_size.dts')
700        self.assertIn("Node '/binman/u-boot-spl': Offset 0x0 (0) overlaps "
701                      "with previous entry '/binman/u-boot' ending at 0x4 (4)",
702                      str(e.exception))
703
704    def testPackUbootDtb(self):
705        """Test that a device tree can be added to U-Boot"""
706        data = self._DoReadFile('26_pack_u_boot_dtb.dts')
707        self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)
708
709    def testPackX86RomNoSize(self):
710        """Test that the end-at-4gb property requires a size property"""
711        with self.assertRaises(ValueError) as e:
712            self._DoTestFile('27_pack_4gb_no_size.dts')
713        self.assertIn("Section '/binman': Section size must be provided when "
714                      "using end-at-4gb", str(e.exception))
715
716    def test4gbAndSkipAtStartTogether(self):
717        """Test that the end-at-4gb and skip-at-size property can't be used
718        together"""
719        with self.assertRaises(ValueError) as e:
720            self._DoTestFile('80_4gb_and_skip_at_start_together.dts')
721        self.assertIn("Section '/binman': Provide either 'end-at-4gb' or "
722                      "'skip-at-start'", str(e.exception))
723
724    def testPackX86RomOutside(self):
725        """Test that the end-at-4gb property checks for offset boundaries"""
726        with self.assertRaises(ValueError) as e:
727            self._DoTestFile('28_pack_4gb_outside.dts')
728        self.assertIn("Node '/binman/u-boot': Offset 0x0 (0) is outside "
729                      "the section starting at 0xffffffe0 (4294967264)",
730                      str(e.exception))
731
732    def testPackX86Rom(self):
733        """Test that a basic x86 ROM can be created"""
734        data = self._DoReadFile('29_x86-rom.dts')
735        self.assertEqual(U_BOOT_DATA + chr(0) * 7 + U_BOOT_SPL_DATA +
736                         chr(0) * 2, data)
737
738    def testPackX86RomMeNoDesc(self):
739        """Test that an invalid Intel descriptor entry is detected"""
740        TestFunctional._MakeInputFile('descriptor.bin', '')
741        with self.assertRaises(ValueError) as e:
742            self._DoTestFile('31_x86-rom-me.dts')
743        self.assertIn("Node '/binman/intel-descriptor': Cannot find FD "
744                      "signature", str(e.exception))
745
746    def testPackX86RomBadDesc(self):
747        """Test that the Intel requires a descriptor entry"""
748        with self.assertRaises(ValueError) as e:
749            self._DoTestFile('30_x86-rom-me-no-desc.dts')
750        self.assertIn("Node '/binman/intel-me': No offset set with "
751                      "offset-unset: should another entry provide this correct "
752                      "offset?", str(e.exception))
753
754    def testPackX86RomMe(self):
755        """Test that an x86 ROM with an ME region can be created"""
756        data = self._DoReadFile('31_x86-rom-me.dts')
757        self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
758
759    def testPackVga(self):
760        """Test that an image with a VGA binary can be created"""
761        data = self._DoReadFile('32_intel-vga.dts')
762        self.assertEqual(VGA_DATA, data[:len(VGA_DATA)])
763
764    def testPackStart16(self):
765        """Test that an image with an x86 start16 region can be created"""
766        data = self._DoReadFile('33_x86-start16.dts')
767        self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)])
768
769    def testPackPowerpcMpc85xxBootpgResetvec(self):
770        """Test that an image with powerpc-mpc85xx-bootpg-resetvec can be
771        created"""
772        data = self._DoReadFile('81_powerpc_mpc85xx_bootpg_resetvec.dts')
773        self.assertEqual(PPC_MPC85XX_BR_DATA, data[:len(PPC_MPC85XX_BR_DATA)])
774
775    def _RunMicrocodeTest(self, dts_fname, nodtb_data, ucode_second=False):
776        """Handle running a test for insertion of microcode
777
778        Args:
779            dts_fname: Name of test .dts file
780            nodtb_data: Data that we expect in the first section
781            ucode_second: True if the microsecond entry is second instead of
782                third
783
784        Returns:
785            Tuple:
786                Contents of first region (U-Boot or SPL)
787                Offset and size components of microcode pointer, as inserted
788                    in the above (two 4-byte words)
789        """
790        data = self._DoReadFile(dts_fname, True)
791
792        # Now check the device tree has no microcode
793        if ucode_second:
794            ucode_content = data[len(nodtb_data):]
795            ucode_pos = len(nodtb_data)
796            dtb_with_ucode = ucode_content[16:]
797            fdt_len = self.GetFdtLen(dtb_with_ucode)
798        else:
799            dtb_with_ucode = data[len(nodtb_data):]
800            fdt_len = self.GetFdtLen(dtb_with_ucode)
801            ucode_content = dtb_with_ucode[fdt_len:]
802            ucode_pos = len(nodtb_data) + fdt_len
803        fname = tools.GetOutputFilename('test.dtb')
804        with open(fname, 'wb') as fd:
805            fd.write(dtb_with_ucode)
806        dtb = fdt.FdtScan(fname)
807        ucode = dtb.GetNode('/microcode')
808        self.assertTrue(ucode)
809        for node in ucode.subnodes:
810            self.assertFalse(node.props.get('data'))
811
812        # Check that the microcode appears immediately after the Fdt
813        # This matches the concatenation of the data properties in
814        # the /microcode/update@xxx nodes in 34_x86_ucode.dts.
815        ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000,
816                                 0x78235609)
817        self.assertEqual(ucode_data, ucode_content[:len(ucode_data)])
818
819        # Check that the microcode pointer was inserted. It should match the
820        # expected offset and size
821        pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
822                                   len(ucode_data))
823        u_boot = data[:len(nodtb_data)]
824        return u_boot, pos_and_size
825
826    def testPackUbootMicrocode(self):
827        """Test that x86 microcode can be handled correctly
828
829        We expect to see the following in the image, in order:
830            u-boot-nodtb.bin with a microcode pointer inserted at the correct
831                place
832            u-boot.dtb with the microcode removed
833            the microcode
834        """
835        first, pos_and_size = self._RunMicrocodeTest('34_x86_ucode.dts',
836                                                     U_BOOT_NODTB_DATA)
837        self.assertEqual('nodtb with microcode' + pos_and_size +
838                         ' somewhere in here', first)
839
840    def _RunPackUbootSingleMicrocode(self):
841        """Test that x86 microcode can be handled correctly
842
843        We expect to see the following in the image, in order:
844            u-boot-nodtb.bin with a microcode pointer inserted at the correct
845                place
846            u-boot.dtb with the microcode
847            an empty microcode region
848        """
849        # We need the libfdt library to run this test since only that allows
850        # finding the offset of a property. This is required by
851        # Entry_u_boot_dtb_with_ucode.ObtainContents().
852        data = self._DoReadFile('35_x86_single_ucode.dts', True)
853
854        second = data[len(U_BOOT_NODTB_DATA):]
855
856        fdt_len = self.GetFdtLen(second)
857        third = second[fdt_len:]
858        second = second[:fdt_len]
859
860        ucode_data = struct.pack('>2L', 0x12345678, 0x12345679)
861        self.assertIn(ucode_data, second)
862        ucode_pos = second.find(ucode_data) + len(U_BOOT_NODTB_DATA)
863
864        # Check that the microcode pointer was inserted. It should match the
865        # expected offset and size
866        pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
867                                   len(ucode_data))
868        first = data[:len(U_BOOT_NODTB_DATA)]
869        self.assertEqual('nodtb with microcode' + pos_and_size +
870                         ' somewhere in here', first)
871
872    def testPackUbootSingleMicrocode(self):
873        """Test that x86 microcode can be handled correctly with fdt_normal.
874        """
875        self._RunPackUbootSingleMicrocode()
876
877    def testUBootImg(self):
878        """Test that u-boot.img can be put in a file"""
879        data = self._DoReadFile('36_u_boot_img.dts')
880        self.assertEqual(U_BOOT_IMG_DATA, data)
881
882    def testNoMicrocode(self):
883        """Test that a missing microcode region is detected"""
884        with self.assertRaises(ValueError) as e:
885            self._DoReadFile('37_x86_no_ucode.dts', True)
886        self.assertIn("Node '/binman/u-boot-dtb-with-ucode': No /microcode "
887                      "node found in ", str(e.exception))
888
889    def testMicrocodeWithoutNode(self):
890        """Test that a missing u-boot-dtb-with-ucode node is detected"""
891        with self.assertRaises(ValueError) as e:
892            self._DoReadFile('38_x86_ucode_missing_node.dts', True)
893        self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
894                "microcode region u-boot-dtb-with-ucode", str(e.exception))
895
896    def testMicrocodeWithoutNode2(self):
897        """Test that a missing u-boot-ucode node is detected"""
898        with self.assertRaises(ValueError) as e:
899            self._DoReadFile('39_x86_ucode_missing_node2.dts', True)
900        self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
901            "microcode region u-boot-ucode", str(e.exception))
902
903    def testMicrocodeWithoutPtrInElf(self):
904        """Test that a U-Boot binary without the microcode symbol is detected"""
905        # ELF file without a '_dt_ucode_base_size' symbol
906        try:
907            with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
908                TestFunctional._MakeInputFile('u-boot', fd.read())
909
910            with self.assertRaises(ValueError) as e:
911                self._RunPackUbootSingleMicrocode()
912            self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot locate "
913                    "_dt_ucode_base_size symbol in u-boot", str(e.exception))
914
915        finally:
916            # Put the original file back
917            with open(self.TestFile('u_boot_ucode_ptr')) as fd:
918                TestFunctional._MakeInputFile('u-boot', fd.read())
919
920    def testMicrocodeNotInImage(self):
921        """Test that microcode must be placed within the image"""
922        with self.assertRaises(ValueError) as e:
923            self._DoReadFile('40_x86_ucode_not_in_image.dts', True)
924        self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Microcode "
925                "pointer _dt_ucode_base_size at fffffe14 is outside the "
926                "section ranging from 00000000 to 0000002e", str(e.exception))
927
928    def testWithoutMicrocode(self):
929        """Test that we can cope with an image without microcode (e.g. qemu)"""
930        with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
931            TestFunctional._MakeInputFile('u-boot', fd.read())
932        data, dtb, _, _ = self._DoReadFileDtb('44_x86_optional_ucode.dts', True)
933
934        # Now check the device tree has no microcode
935        self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)])
936        second = data[len(U_BOOT_NODTB_DATA):]
937
938        fdt_len = self.GetFdtLen(second)
939        self.assertEqual(dtb, second[:fdt_len])
940
941        used_len = len(U_BOOT_NODTB_DATA) + fdt_len
942        third = data[used_len:]
943        self.assertEqual(chr(0) * (0x200 - used_len), third)
944
945    def testUnknownPosSize(self):
946        """Test that microcode must be placed within the image"""
947        with self.assertRaises(ValueError) as e:
948            self._DoReadFile('41_unknown_pos_size.dts', True)
949        self.assertIn("Section '/binman': Unable to set offset/size for unknown "
950                "entry 'invalid-entry'", str(e.exception))
951
952    def testPackFsp(self):
953        """Test that an image with a FSP binary can be created"""
954        data = self._DoReadFile('42_intel-fsp.dts')
955        self.assertEqual(FSP_DATA, data[:len(FSP_DATA)])
956
957    def testPackCmc(self):
958        """Test that an image with a CMC binary can be created"""
959        data = self._DoReadFile('43_intel-cmc.dts')
960        self.assertEqual(CMC_DATA, data[:len(CMC_DATA)])
961
962    def testPackVbt(self):
963        """Test that an image with a VBT binary can be created"""
964        data = self._DoReadFile('46_intel-vbt.dts')
965        self.assertEqual(VBT_DATA, data[:len(VBT_DATA)])
966
967    def testSplBssPad(self):
968        """Test that we can pad SPL's BSS with zeros"""
969        # ELF file with a '__bss_size' symbol
970        with open(self.TestFile('bss_data')) as fd:
971            TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
972        data = self._DoReadFile('47_spl_bss_pad.dts')
973        self.assertEqual(U_BOOT_SPL_DATA + (chr(0) * 10) + U_BOOT_DATA, data)
974
975        with open(self.TestFile('u_boot_ucode_ptr')) as fd:
976            TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
977        with self.assertRaises(ValueError) as e:
978            data = self._DoReadFile('47_spl_bss_pad.dts')
979        self.assertIn('Expected __bss_size symbol in spl/u-boot-spl',
980                      str(e.exception))
981
982    def testPackStart16Spl(self):
983        """Test that an image with an x86 start16 region can be created"""
984        data = self._DoReadFile('48_x86-start16-spl.dts')
985        self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)])
986
987    def _PackUbootSplMicrocode(self, dts, ucode_second=False):
988        """Helper function for microcode tests
989
990        We expect to see the following in the image, in order:
991            u-boot-spl-nodtb.bin with a microcode pointer inserted at the
992                correct place
993            u-boot.dtb with the microcode removed
994            the microcode
995
996        Args:
997            dts: Device tree file to use for test
998            ucode_second: True if the microsecond entry is second instead of
999                third
1000        """
1001        # ELF file with a '_dt_ucode_base_size' symbol
1002        with open(self.TestFile('u_boot_ucode_ptr')) as fd:
1003            TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
1004        first, pos_and_size = self._RunMicrocodeTest(dts, U_BOOT_SPL_NODTB_DATA,
1005                                                     ucode_second=ucode_second)
1006        self.assertEqual('splnodtb with microc' + pos_and_size +
1007                         'ter somewhere in here', first)
1008
1009    def testPackUbootSplMicrocode(self):
1010        """Test that x86 microcode can be handled correctly in SPL"""
1011        self._PackUbootSplMicrocode('49_x86_ucode_spl.dts')
1012
1013    def testPackUbootSplMicrocodeReorder(self):
1014        """Test that order doesn't matter for microcode entries
1015
1016        This is the same as testPackUbootSplMicrocode but when we process the
1017        u-boot-ucode entry we have not yet seen the u-boot-dtb-with-ucode
1018        entry, so we reply on binman to try later.
1019        """
1020        self._PackUbootSplMicrocode('58_x86_ucode_spl_needs_retry.dts',
1021                                    ucode_second=True)
1022
1023    def testPackMrc(self):
1024        """Test that an image with an MRC binary can be created"""
1025        data = self._DoReadFile('50_intel_mrc.dts')
1026        self.assertEqual(MRC_DATA, data[:len(MRC_DATA)])
1027
1028    def testSplDtb(self):
1029        """Test that an image with spl/u-boot-spl.dtb can be created"""
1030        data = self._DoReadFile('51_u_boot_spl_dtb.dts')
1031        self.assertEqual(U_BOOT_SPL_DTB_DATA, data[:len(U_BOOT_SPL_DTB_DATA)])
1032
1033    def testSplNoDtb(self):
1034        """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
1035        data = self._DoReadFile('52_u_boot_spl_nodtb.dts')
1036        self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
1037
1038    def testSymbols(self):
1039        """Test binman can assign symbols embedded in U-Boot"""
1040        elf_fname = self.TestFile('u_boot_binman_syms')
1041        syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
1042        addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
1043        self.assertEqual(syms['_binman_u_boot_spl_prop_offset'].address, addr)
1044
1045        with open(self.TestFile('u_boot_binman_syms')) as fd:
1046            TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
1047        data = self._DoReadFile('53_symbols.dts')
1048        sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
1049        expected = (sym_values + U_BOOT_SPL_DATA[16:] + chr(0xff) +
1050                    U_BOOT_DATA +
1051                    sym_values + U_BOOT_SPL_DATA[16:])
1052        self.assertEqual(expected, data)
1053
1054    def testPackUnitAddress(self):
1055        """Test that we support multiple binaries with the same name"""
1056        data = self._DoReadFile('54_unit_address.dts')
1057        self.assertEqual(U_BOOT_DATA + U_BOOT_DATA, data)
1058
1059    def testSections(self):
1060        """Basic test of sections"""
1061        data = self._DoReadFile('55_sections.dts')
1062        expected = (U_BOOT_DATA + '!' * 12 + U_BOOT_DATA + 'a' * 12 +
1063                    U_BOOT_DATA + '&' * 4)
1064        self.assertEqual(expected, data)
1065
1066    def testMap(self):
1067        """Tests outputting a map of the images"""
1068        _, _, map_data, _ = self._DoReadFileDtb('55_sections.dts', map=True)
1069        self.assertEqual('''ImagePos    Offset      Size  Name
107000000000  00000000  00000028  main-section
107100000000   00000000  00000010  section@0
107200000000    00000000  00000004  u-boot
107300000010   00000010  00000010  section@1
107400000010    00000000  00000004  u-boot
107500000020   00000020  00000004  section@2
107600000020    00000000  00000004  u-boot
1077''', map_data)
1078
1079    def testNamePrefix(self):
1080        """Tests that name prefixes are used"""
1081        _, _, map_data, _ = self._DoReadFileDtb('56_name_prefix.dts', map=True)
1082        self.assertEqual('''ImagePos    Offset      Size  Name
108300000000  00000000  00000028  main-section
108400000000   00000000  00000010  section@0
108500000000    00000000  00000004  ro-u-boot
108600000010   00000010  00000010  section@1
108700000010    00000000  00000004  rw-u-boot
1088''', map_data)
1089
1090    def testUnknownContents(self):
1091        """Test that obtaining the contents works as expected"""
1092        with self.assertRaises(ValueError) as e:
1093            self._DoReadFile('57_unknown_contents.dts', True)
1094        self.assertIn("Section '/binman': Internal error: Could not complete "
1095                "processing of contents: remaining [<_testing.Entry__testing ",
1096                str(e.exception))
1097
1098    def testBadChangeSize(self):
1099        """Test that trying to change the size of an entry fails"""
1100        with self.assertRaises(ValueError) as e:
1101            self._DoReadFile('59_change_size.dts', True)
1102        self.assertIn("Node '/binman/_testing': Cannot update entry size from "
1103                      '2 to 1', str(e.exception))
1104
1105    def testUpdateFdt(self):
1106        """Test that we can update the device tree with offset/size info"""
1107        _, _, _, out_dtb_fname = self._DoReadFileDtb('60_fdt_update.dts',
1108                                                     update_dtb=True)
1109        dtb = fdt.Fdt(out_dtb_fname)
1110        dtb.Scan()
1111        props = self._GetPropTree(dtb, ['offset', 'size', 'image-pos'])
1112        self.assertEqual({
1113            'image-pos': 0,
1114            'offset': 0,
1115            '_testing:offset': 32,
1116            '_testing:size': 1,
1117            '_testing:image-pos': 32,
1118            'section@0/u-boot:offset': 0,
1119            'section@0/u-boot:size': len(U_BOOT_DATA),
1120            'section@0/u-boot:image-pos': 0,
1121            'section@0:offset': 0,
1122            'section@0:size': 16,
1123            'section@0:image-pos': 0,
1124
1125            'section@1/u-boot:offset': 0,
1126            'section@1/u-boot:size': len(U_BOOT_DATA),
1127            'section@1/u-boot:image-pos': 16,
1128            'section@1:offset': 16,
1129            'section@1:size': 16,
1130            'section@1:image-pos': 16,
1131            'size': 40
1132        }, props)
1133
1134    def testUpdateFdtBad(self):
1135        """Test that we detect when ProcessFdt never completes"""
1136        with self.assertRaises(ValueError) as e:
1137            self._DoReadFileDtb('61_fdt_update_bad.dts', update_dtb=True)
1138        self.assertIn('Could not complete processing of Fdt: remaining '
1139                      '[<_testing.Entry__testing', str(e.exception))
1140
1141    def testEntryArgs(self):
1142        """Test passing arguments to entries from the command line"""
1143        entry_args = {
1144            'test-str-arg': 'test1',
1145            'test-int-arg': '456',
1146        }
1147        self._DoReadFileDtb('62_entry_args.dts', entry_args=entry_args)
1148        self.assertIn('image', control.images)
1149        entry = control.images['image'].GetEntries()['_testing']
1150        self.assertEqual('test0', entry.test_str_fdt)
1151        self.assertEqual('test1', entry.test_str_arg)
1152        self.assertEqual(123, entry.test_int_fdt)
1153        self.assertEqual(456, entry.test_int_arg)
1154
1155    def testEntryArgsMissing(self):
1156        """Test missing arguments and properties"""
1157        entry_args = {
1158            'test-int-arg': '456',
1159        }
1160        self._DoReadFileDtb('63_entry_args_missing.dts', entry_args=entry_args)
1161        entry = control.images['image'].GetEntries()['_testing']
1162        self.assertEqual('test0', entry.test_str_fdt)
1163        self.assertEqual(None, entry.test_str_arg)
1164        self.assertEqual(None, entry.test_int_fdt)
1165        self.assertEqual(456, entry.test_int_arg)
1166
1167    def testEntryArgsRequired(self):
1168        """Test missing arguments and properties"""
1169        entry_args = {
1170            'test-int-arg': '456',
1171        }
1172        with self.assertRaises(ValueError) as e:
1173            self._DoReadFileDtb('64_entry_args_required.dts')
1174        self.assertIn("Node '/binman/_testing': Missing required "
1175            'properties/entry args: test-str-arg, test-int-fdt, test-int-arg',
1176            str(e.exception))
1177
1178    def testEntryArgsInvalidFormat(self):
1179        """Test that an invalid entry-argument format is detected"""
1180        args = ['-d', self.TestFile('64_entry_args_required.dts'), '-ano-value']
1181        with self.assertRaises(ValueError) as e:
1182            self._DoBinman(*args)
1183        self.assertIn("Invalid entry arguemnt 'no-value'", str(e.exception))
1184
1185    def testEntryArgsInvalidInteger(self):
1186        """Test that an invalid entry-argument integer is detected"""
1187        entry_args = {
1188            'test-int-arg': 'abc',
1189        }
1190        with self.assertRaises(ValueError) as e:
1191            self._DoReadFileDtb('62_entry_args.dts', entry_args=entry_args)
1192        self.assertIn("Node '/binman/_testing': Cannot convert entry arg "
1193                      "'test-int-arg' (value 'abc') to integer",
1194            str(e.exception))
1195
1196    def testEntryArgsInvalidDatatype(self):
1197        """Test that an invalid entry-argument datatype is detected
1198
1199        This test could be written in entry_test.py except that it needs
1200        access to control.entry_args, which seems more than that module should
1201        be able to see.
1202        """
1203        entry_args = {
1204            'test-bad-datatype-arg': '12',
1205        }
1206        with self.assertRaises(ValueError) as e:
1207            self._DoReadFileDtb('65_entry_args_unknown_datatype.dts',
1208                                entry_args=entry_args)
1209        self.assertIn('GetArg() internal error: Unknown data type ',
1210                      str(e.exception))
1211
1212    def testText(self):
1213        """Test for a text entry type"""
1214        entry_args = {
1215            'test-id': TEXT_DATA,
1216            'test-id2': TEXT_DATA2,
1217            'test-id3': TEXT_DATA3,
1218        }
1219        data, _, _, _ = self._DoReadFileDtb('66_text.dts',
1220                                            entry_args=entry_args)
1221        expected = (TEXT_DATA + chr(0) * (8 - len(TEXT_DATA)) + TEXT_DATA2 +
1222                    TEXT_DATA3 + 'some text')
1223        self.assertEqual(expected, data)
1224
1225    def testEntryDocs(self):
1226        """Test for creation of entry documentation"""
1227        with test_util.capture_sys_output() as (stdout, stderr):
1228            control.WriteEntryDocs(binman.GetEntryModules())
1229        self.assertTrue(len(stdout.getvalue()) > 0)
1230
1231    def testEntryDocsMissing(self):
1232        """Test handling of missing entry documentation"""
1233        with self.assertRaises(ValueError) as e:
1234            with test_util.capture_sys_output() as (stdout, stderr):
1235                control.WriteEntryDocs(binman.GetEntryModules(), 'u_boot')
1236        self.assertIn('Documentation is missing for modules: u_boot',
1237                      str(e.exception))
1238
1239    def testFmap(self):
1240        """Basic test of generation of a flashrom fmap"""
1241        data = self._DoReadFile('67_fmap.dts')
1242        fhdr, fentries = fmap_util.DecodeFmap(data[32:])
1243        expected = U_BOOT_DATA + '!' * 12 + U_BOOT_DATA + 'a' * 12
1244        self.assertEqual(expected, data[:32])
1245        self.assertEqual('__FMAP__', fhdr.signature)
1246        self.assertEqual(1, fhdr.ver_major)
1247        self.assertEqual(0, fhdr.ver_minor)
1248        self.assertEqual(0, fhdr.base)
1249        self.assertEqual(16 + 16 +
1250                         fmap_util.FMAP_HEADER_LEN +
1251                         fmap_util.FMAP_AREA_LEN * 3, fhdr.image_size)
1252        self.assertEqual('FMAP', fhdr.name)
1253        self.assertEqual(3, fhdr.nareas)
1254        for fentry in fentries:
1255            self.assertEqual(0, fentry.flags)
1256
1257        self.assertEqual(0, fentries[0].offset)
1258        self.assertEqual(4, fentries[0].size)
1259        self.assertEqual('RO_U_BOOT', fentries[0].name)
1260
1261        self.assertEqual(16, fentries[1].offset)
1262        self.assertEqual(4, fentries[1].size)
1263        self.assertEqual('RW_U_BOOT', fentries[1].name)
1264
1265        self.assertEqual(32, fentries[2].offset)
1266        self.assertEqual(fmap_util.FMAP_HEADER_LEN +
1267                         fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
1268        self.assertEqual('FMAP', fentries[2].name)
1269
1270    def testBlobNamedByArg(self):
1271        """Test we can add a blob with the filename coming from an entry arg"""
1272        entry_args = {
1273            'cros-ec-rw-path': 'ecrw.bin',
1274        }
1275        data, _, _, _ = self._DoReadFileDtb('68_blob_named_by_arg.dts',
1276                                            entry_args=entry_args)
1277
1278    def testFill(self):
1279        """Test for an fill entry type"""
1280        data = self._DoReadFile('69_fill.dts')
1281        expected = 8 * chr(0xff) + 8 * chr(0)
1282        self.assertEqual(expected, data)
1283
1284    def testFillNoSize(self):
1285        """Test for an fill entry type with no size"""
1286        with self.assertRaises(ValueError) as e:
1287            self._DoReadFile('70_fill_no_size.dts')
1288        self.assertIn("'fill' entry must have a size property",
1289                      str(e.exception))
1290
1291    def _HandleGbbCommand(self, pipe_list):
1292        """Fake calls to the futility utility"""
1293        if pipe_list[0][0] == 'futility':
1294            fname = pipe_list[0][-1]
1295            # Append our GBB data to the file, which will happen every time the
1296            # futility command is called.
1297            with open(fname, 'a') as fd:
1298                fd.write(GBB_DATA)
1299            return command.CommandResult()
1300
1301    def testGbb(self):
1302        """Test for the Chromium OS Google Binary Block"""
1303        command.test_result = self._HandleGbbCommand
1304        entry_args = {
1305            'keydir': 'devkeys',
1306            'bmpblk': 'bmpblk.bin',
1307        }
1308        data, _, _, _ = self._DoReadFileDtb('71_gbb.dts', entry_args=entry_args)
1309
1310        # Since futility
1311        expected = GBB_DATA + GBB_DATA + 8 * chr(0) + (0x2180 - 16) * chr(0)
1312        self.assertEqual(expected, data)
1313
1314    def testGbbTooSmall(self):
1315        """Test for the Chromium OS Google Binary Block being large enough"""
1316        with self.assertRaises(ValueError) as e:
1317            self._DoReadFileDtb('72_gbb_too_small.dts')
1318        self.assertIn("Node '/binman/gbb': GBB is too small",
1319                      str(e.exception))
1320
1321    def testGbbNoSize(self):
1322        """Test for the Chromium OS Google Binary Block having a size"""
1323        with self.assertRaises(ValueError) as e:
1324            self._DoReadFileDtb('73_gbb_no_size.dts')
1325        self.assertIn("Node '/binman/gbb': GBB must have a fixed size",
1326                      str(e.exception))
1327
1328    def _HandleVblockCommand(self, pipe_list):
1329        """Fake calls to the futility utility"""
1330        if pipe_list[0][0] == 'futility':
1331            fname = pipe_list[0][3]
1332            with open(fname, 'w') as fd:
1333                fd.write(VBLOCK_DATA)
1334            return command.CommandResult()
1335
1336    def testVblock(self):
1337        """Test for the Chromium OS Verified Boot Block"""
1338        command.test_result = self._HandleVblockCommand
1339        entry_args = {
1340            'keydir': 'devkeys',
1341        }
1342        data, _, _, _ = self._DoReadFileDtb('74_vblock.dts',
1343                                            entry_args=entry_args)
1344        expected = U_BOOT_DATA + VBLOCK_DATA + U_BOOT_DTB_DATA
1345        self.assertEqual(expected, data)
1346
1347    def testVblockNoContent(self):
1348        """Test we detect a vblock which has no content to sign"""
1349        with self.assertRaises(ValueError) as e:
1350            self._DoReadFile('75_vblock_no_content.dts')
1351        self.assertIn("Node '/binman/vblock': Vblock must have a 'content' "
1352                      'property', str(e.exception))
1353
1354    def testVblockBadPhandle(self):
1355        """Test that we detect a vblock with an invalid phandle in contents"""
1356        with self.assertRaises(ValueError) as e:
1357            self._DoReadFile('76_vblock_bad_phandle.dts')
1358        self.assertIn("Node '/binman/vblock': Cannot find node for phandle "
1359                      '1000', str(e.exception))
1360
1361    def testVblockBadEntry(self):
1362        """Test that we detect an entry that points to a non-entry"""
1363        with self.assertRaises(ValueError) as e:
1364            self._DoReadFile('77_vblock_bad_entry.dts')
1365        self.assertIn("Node '/binman/vblock': Cannot find entry for node "
1366                      "'other'", str(e.exception))
1367
1368    def testTpl(self):
1369        """Test that an image with TPL and ots device tree can be created"""
1370        # ELF file with a '__bss_size' symbol
1371        with open(self.TestFile('bss_data')) as fd:
1372            TestFunctional._MakeInputFile('tpl/u-boot-tpl', fd.read())
1373        data = self._DoReadFile('78_u_boot_tpl.dts')
1374        self.assertEqual(U_BOOT_TPL_DATA + U_BOOT_TPL_DTB_DATA, data)
1375
1376    def testUsesPos(self):
1377        """Test that the 'pos' property cannot be used anymore"""
1378        with self.assertRaises(ValueError) as e:
1379           data = self._DoReadFile('79_uses_pos.dts')
1380        self.assertIn("Node '/binman/u-boot': Please use 'offset' instead of "
1381                      "'pos'", str(e.exception))
1382
1383
1384if __name__ == "__main__":
1385    unittest.main()
1386