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