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