]> git.sur5r.net Git - u-boot/blob - tools/binman/ftest.py
tools: Update python "help" tests to cope with "more" oddities
[u-boot] / tools / binman / ftest.py
1 #
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # SPDX-License-Identifier:      GPL-2.0+
6 #
7 # To run a single test, change to this directory, and:
8 #
9 #    python -m unittest func_test.TestFunctional.testHelp
10
11 from optparse import OptionParser
12 import os
13 import shutil
14 import struct
15 import sys
16 import tempfile
17 import unittest
18
19 import binman
20 import cmdline
21 import command
22 import control
23 import elf
24 import fdt
25 import fdt_util
26 import tools
27 import tout
28
29 # Contents of test files, corresponding to different entry types
30 U_BOOT_DATA           = '1234'
31 U_BOOT_IMG_DATA       = 'img'
32 U_BOOT_SPL_DATA       = '56780123456789abcde'
33 BLOB_DATA             = '89'
34 ME_DATA               = '0abcd'
35 VGA_DATA              = 'vga'
36 U_BOOT_DTB_DATA       = 'udtb'
37 U_BOOT_SPL_DTB_DATA   = 'spldtb'
38 X86_START16_DATA      = 'start16'
39 X86_START16_SPL_DATA  = 'start16spl'
40 U_BOOT_NODTB_DATA     = 'nodtb with microcode pointer somewhere in here'
41 U_BOOT_SPL_NODTB_DATA = 'splnodtb with microcode pointer somewhere in here'
42 FSP_DATA              = 'fsp'
43 CMC_DATA              = 'cmc'
44 VBT_DATA              = 'vbt'
45 MRC_DATA              = 'mrc'
46
47 class TestFunctional(unittest.TestCase):
48     """Functional tests for binman
49
50     Most of these use a sample .dts file to build an image and then check
51     that it looks correct. The sample files are in the test/ subdirectory
52     and are numbered.
53
54     For each entry type a very small test file is created using fixed
55     string contents. This makes it easy to test that things look right, and
56     debug problems.
57
58     In some cases a 'real' file must be used - these are also supplied in
59     the test/ diurectory.
60     """
61     @classmethod
62     def setUpClass(self):
63         global entry
64         import entry
65
66         # Handle the case where argv[0] is 'python'
67         self._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
68         self._binman_pathname = os.path.join(self._binman_dir, 'binman')
69
70         # Create a temporary directory for input files
71         self._indir = tempfile.mkdtemp(prefix='binmant.')
72
73         # Create some test files
74         TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
75         TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
76         TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
77         TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
78         TestFunctional._MakeInputFile('me.bin', ME_DATA)
79         TestFunctional._MakeInputFile('vga.bin', VGA_DATA)
80         TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
81         TestFunctional._MakeInputFile('spl/u-boot-spl.dtb', U_BOOT_SPL_DTB_DATA)
82         TestFunctional._MakeInputFile('u-boot-x86-16bit.bin', X86_START16_DATA)
83         TestFunctional._MakeInputFile('spl/u-boot-x86-16bit-spl.bin',
84                                       X86_START16_SPL_DATA)
85         TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
86         TestFunctional._MakeInputFile('spl/u-boot-spl-nodtb.bin',
87                                       U_BOOT_SPL_NODTB_DATA)
88         TestFunctional._MakeInputFile('fsp.bin', FSP_DATA)
89         TestFunctional._MakeInputFile('cmc.bin', CMC_DATA)
90         TestFunctional._MakeInputFile('vbt.bin', VBT_DATA)
91         TestFunctional._MakeInputFile('mrc.bin', MRC_DATA)
92         self._output_setup = False
93
94         # ELF file with a '_dt_ucode_base_size' symbol
95         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
96             TestFunctional._MakeInputFile('u-boot', fd.read())
97
98         # Intel flash descriptor file
99         with open(self.TestFile('descriptor.bin')) as fd:
100             TestFunctional._MakeInputFile('descriptor.bin', fd.read())
101
102     @classmethod
103     def tearDownClass(self):
104         """Remove the temporary input directory and its contents"""
105         if self._indir:
106             shutil.rmtree(self._indir)
107         self._indir = None
108
109     def setUp(self):
110         # Enable this to turn on debugging output
111         # tout.Init(tout.DEBUG)
112         command.test_result = None
113
114     def tearDown(self):
115         """Remove the temporary output directory"""
116         tools._FinaliseForTest()
117
118     def _RunBinman(self, *args, **kwargs):
119         """Run binman using the command line
120
121         Args:
122             Arguments to pass, as a list of strings
123             kwargs: Arguments to pass to Command.RunPipe()
124         """
125         result = command.RunPipe([[self._binman_pathname] + list(args)],
126                 capture=True, capture_stderr=True, raise_on_error=False)
127         if result.return_code and kwargs.get('raise_on_error', True):
128             raise Exception("Error running '%s': %s" % (' '.join(args),
129                             result.stdout + result.stderr))
130         return result
131
132     def _DoBinman(self, *args):
133         """Run binman using directly (in the same process)
134
135         Args:
136             Arguments to pass, as a list of strings
137         Returns:
138             Return value (0 for success)
139         """
140         args = list(args)
141         if '-D' in sys.argv:
142             args = args + ['-D']
143         (options, args) = cmdline.ParseArgs(args)
144         options.pager = 'binman-invalid-pager'
145         options.build_dir = self._indir
146
147         # For testing, you can force an increase in verbosity here
148         # options.verbosity = tout.DEBUG
149         return control.Binman(options, args)
150
151     def _DoTestFile(self, fname, debug=False):
152         """Run binman with a given test file
153
154         Args:
155             fname: Device tree source filename to use (e.g. 05_simple.dts)
156         """
157         args = ['-p', '-I', self._indir, '-d', self.TestFile(fname)]
158         if debug:
159             args.append('-D')
160         return self._DoBinman(*args)
161
162     def _SetupDtb(self, fname, outfile='u-boot.dtb'):
163         """Set up a new test device-tree file
164
165         The given file is compiled and set up as the device tree to be used
166         for ths test.
167
168         Args:
169             fname: Filename of .dts file to read
170             outfile: Output filename for compiled device tree binary
171
172         Returns:
173             Contents of device tree binary
174         """
175         if not self._output_setup:
176             tools.PrepareOutputDir(self._indir, True)
177             self._output_setup = True
178         dtb = fdt_util.EnsureCompiled(self.TestFile(fname))
179         with open(dtb) as fd:
180             data = fd.read()
181             TestFunctional._MakeInputFile(outfile, data)
182             return data
183
184     def _DoReadFileDtb(self, fname, use_real_dtb=False):
185         """Run binman and return the resulting image
186
187         This runs binman with a given test file and then reads the resulting
188         output file. It is a shortcut function since most tests need to do
189         these steps.
190
191         Raises an assertion failure if binman returns a non-zero exit code.
192
193         Args:
194             fname: Device tree source filename to use (e.g. 05_simple.dts)
195             use_real_dtb: True to use the test file as the contents of
196                 the u-boot-dtb entry. Normally this is not needed and the
197                 test contents (the U_BOOT_DTB_DATA string) can be used.
198                 But in some test we need the real contents.
199
200         Returns:
201             Tuple:
202                 Resulting image contents
203                 Device tree contents
204         """
205         dtb_data = None
206         # Use the compiled test file as the u-boot-dtb input
207         if use_real_dtb:
208             dtb_data = self._SetupDtb(fname)
209
210         try:
211             retcode = self._DoTestFile(fname)
212             self.assertEqual(0, retcode)
213
214             # Find the (only) image, read it and return its contents
215             image = control.images['image']
216             fname = tools.GetOutputFilename('image.bin')
217             self.assertTrue(os.path.exists(fname))
218             with open(fname) as fd:
219                 return fd.read(), dtb_data
220         finally:
221             # Put the test file back
222             if use_real_dtb:
223                 TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
224
225     def _DoReadFile(self, fname, use_real_dtb=False):
226         """Helper function which discards the device-tree binary"""
227         return self._DoReadFileDtb(fname, use_real_dtb)[0]
228
229     @classmethod
230     def _MakeInputFile(self, fname, contents):
231         """Create a new test input file, creating directories as needed
232
233         Args:
234             fname: Filenaem to create
235             contents: File contents to write in to the file
236         Returns:
237             Full pathname of file created
238         """
239         pathname = os.path.join(self._indir, fname)
240         dirname = os.path.dirname(pathname)
241         if dirname and not os.path.exists(dirname):
242             os.makedirs(dirname)
243         with open(pathname, 'wb') as fd:
244             fd.write(contents)
245         return pathname
246
247     @classmethod
248     def TestFile(self, fname):
249         return os.path.join(self._binman_dir, 'test', fname)
250
251     def AssertInList(self, grep_list, target):
252         """Assert that at least one of a list of things is in a target
253
254         Args:
255             grep_list: List of strings to check
256             target: Target string
257         """
258         for grep in grep_list:
259             if grep in target:
260                 return
261         self.fail("Error: '%' not found in '%s'" % (grep_list, target))
262
263     def CheckNoGaps(self, entries):
264         """Check that all entries fit together without gaps
265
266         Args:
267             entries: List of entries to check
268         """
269         pos = 0
270         for entry in entries.values():
271             self.assertEqual(pos, entry.pos)
272             pos += entry.size
273
274     def GetFdtLen(self, dtb):
275         """Get the totalsize field from a device tree binary
276
277         Args:
278             dtb: Device tree binary contents
279
280         Returns:
281             Total size of device tree binary, from the header
282         """
283         return struct.unpack('>L', dtb[4:8])[0]
284
285     def testRun(self):
286         """Test a basic run with valid args"""
287         result = self._RunBinman('-h')
288
289     def testFullHelp(self):
290         """Test that the full help is displayed with -H"""
291         result = self._RunBinman('-H')
292         help_file = os.path.join(self._binman_dir, 'README')
293         # Remove possible extraneous strings
294         extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
295         gothelp = result.stdout.replace(extra, '')
296         self.assertEqual(len(gothelp), os.path.getsize(help_file))
297         self.assertEqual(0, len(result.stderr))
298         self.assertEqual(0, result.return_code)
299
300     def testFullHelpInternal(self):
301         """Test that the full help is displayed with -H"""
302         try:
303             command.test_result = command.CommandResult()
304             result = self._DoBinman('-H')
305             help_file = os.path.join(self._binman_dir, 'README')
306         finally:
307             command.test_result = None
308
309     def testHelp(self):
310         """Test that the basic help is displayed with -h"""
311         result = self._RunBinman('-h')
312         self.assertTrue(len(result.stdout) > 200)
313         self.assertEqual(0, len(result.stderr))
314         self.assertEqual(0, result.return_code)
315
316     def testBoard(self):
317         """Test that we can run it with a specific board"""
318         self._SetupDtb('05_simple.dts', 'sandbox/u-boot.dtb')
319         TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
320         result = self._DoBinman('-b', 'sandbox')
321         self.assertEqual(0, result)
322
323     def testNeedBoard(self):
324         """Test that we get an error when no board ius supplied"""
325         with self.assertRaises(ValueError) as e:
326             result = self._DoBinman()
327         self.assertIn("Must provide a board to process (use -b <board>)",
328                 str(e.exception))
329
330     def testMissingDt(self):
331         """Test that an invalid device tree file generates an error"""
332         with self.assertRaises(Exception) as e:
333             self._RunBinman('-d', 'missing_file')
334         # We get one error from libfdt, and a different one from fdtget.
335         self.AssertInList(["Couldn't open blob from 'missing_file'",
336                            'No such file or directory'], str(e.exception))
337
338     def testBrokenDt(self):
339         """Test that an invalid device tree source file generates an error
340
341         Since this is a source file it should be compiled and the error
342         will come from the device-tree compiler (dtc).
343         """
344         with self.assertRaises(Exception) as e:
345             self._RunBinman('-d', self.TestFile('01_invalid.dts'))
346         self.assertIn("FATAL ERROR: Unable to parse input tree",
347                 str(e.exception))
348
349     def testMissingNode(self):
350         """Test that a device tree without a 'binman' node generates an error"""
351         with self.assertRaises(Exception) as e:
352             self._DoBinman('-d', self.TestFile('02_missing_node.dts'))
353         self.assertIn("does not have a 'binman' node", str(e.exception))
354
355     def testEmpty(self):
356         """Test that an empty binman node works OK (i.e. does nothing)"""
357         result = self._RunBinman('-d', self.TestFile('03_empty.dts'))
358         self.assertEqual(0, len(result.stderr))
359         self.assertEqual(0, result.return_code)
360
361     def testInvalidEntry(self):
362         """Test that an invalid entry is flagged"""
363         with self.assertRaises(Exception) as e:
364             result = self._RunBinman('-d',
365                                      self.TestFile('04_invalid_entry.dts'))
366         #print e.exception
367         self.assertIn("Unknown entry type 'not-a-valid-type' in node "
368                 "'/binman/not-a-valid-type'", str(e.exception))
369
370     def testSimple(self):
371         """Test a simple binman with a single file"""
372         data = self._DoReadFile('05_simple.dts')
373         self.assertEqual(U_BOOT_DATA, data)
374
375     def testSimpleDebug(self):
376         """Test a simple binman run with debugging enabled"""
377         data = self._DoTestFile('05_simple.dts', debug=True)
378
379     def testDual(self):
380         """Test that we can handle creating two images
381
382         This also tests image padding.
383         """
384         retcode = self._DoTestFile('06_dual_image.dts')
385         self.assertEqual(0, retcode)
386
387         image = control.images['image1']
388         self.assertEqual(len(U_BOOT_DATA), image._size)
389         fname = tools.GetOutputFilename('image1.bin')
390         self.assertTrue(os.path.exists(fname))
391         with open(fname) as fd:
392             data = fd.read()
393             self.assertEqual(U_BOOT_DATA, data)
394
395         image = control.images['image2']
396         self.assertEqual(3 + len(U_BOOT_DATA) + 5, image._size)
397         fname = tools.GetOutputFilename('image2.bin')
398         self.assertTrue(os.path.exists(fname))
399         with open(fname) as fd:
400             data = fd.read()
401             self.assertEqual(U_BOOT_DATA, data[3:7])
402             self.assertEqual(chr(0) * 3, data[:3])
403             self.assertEqual(chr(0) * 5, data[7:])
404
405     def testBadAlign(self):
406         """Test that an invalid alignment value is detected"""
407         with self.assertRaises(ValueError) as e:
408             self._DoTestFile('07_bad_align.dts')
409         self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
410                       "of two", str(e.exception))
411
412     def testPackSimple(self):
413         """Test that packing works as expected"""
414         retcode = self._DoTestFile('08_pack.dts')
415         self.assertEqual(0, retcode)
416         self.assertIn('image', control.images)
417         image = control.images['image']
418         entries = image._entries
419         self.assertEqual(5, len(entries))
420
421         # First u-boot
422         self.assertIn('u-boot', entries)
423         entry = entries['u-boot']
424         self.assertEqual(0, entry.pos)
425         self.assertEqual(len(U_BOOT_DATA), entry.size)
426
427         # Second u-boot, aligned to 16-byte boundary
428         self.assertIn('u-boot-align', entries)
429         entry = entries['u-boot-align']
430         self.assertEqual(16, entry.pos)
431         self.assertEqual(len(U_BOOT_DATA), entry.size)
432
433         # Third u-boot, size 23 bytes
434         self.assertIn('u-boot-size', entries)
435         entry = entries['u-boot-size']
436         self.assertEqual(20, entry.pos)
437         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
438         self.assertEqual(23, entry.size)
439
440         # Fourth u-boot, placed immediate after the above
441         self.assertIn('u-boot-next', entries)
442         entry = entries['u-boot-next']
443         self.assertEqual(43, entry.pos)
444         self.assertEqual(len(U_BOOT_DATA), entry.size)
445
446         # Fifth u-boot, placed at a fixed position
447         self.assertIn('u-boot-fixed', entries)
448         entry = entries['u-boot-fixed']
449         self.assertEqual(61, entry.pos)
450         self.assertEqual(len(U_BOOT_DATA), entry.size)
451
452         self.assertEqual(65, image._size)
453
454     def testPackExtra(self):
455         """Test that extra packing feature works as expected"""
456         retcode = self._DoTestFile('09_pack_extra.dts')
457
458         self.assertEqual(0, retcode)
459         self.assertIn('image', control.images)
460         image = control.images['image']
461         entries = image._entries
462         self.assertEqual(5, len(entries))
463
464         # First u-boot with padding before and after
465         self.assertIn('u-boot', entries)
466         entry = entries['u-boot']
467         self.assertEqual(0, entry.pos)
468         self.assertEqual(3, entry.pad_before)
469         self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
470
471         # Second u-boot has an aligned size, but it has no effect
472         self.assertIn('u-boot-align-size-nop', entries)
473         entry = entries['u-boot-align-size-nop']
474         self.assertEqual(12, entry.pos)
475         self.assertEqual(4, entry.size)
476
477         # Third u-boot has an aligned size too
478         self.assertIn('u-boot-align-size', entries)
479         entry = entries['u-boot-align-size']
480         self.assertEqual(16, entry.pos)
481         self.assertEqual(32, entry.size)
482
483         # Fourth u-boot has an aligned end
484         self.assertIn('u-boot-align-end', entries)
485         entry = entries['u-boot-align-end']
486         self.assertEqual(48, entry.pos)
487         self.assertEqual(16, entry.size)
488
489         # Fifth u-boot immediately afterwards
490         self.assertIn('u-boot-align-both', entries)
491         entry = entries['u-boot-align-both']
492         self.assertEqual(64, entry.pos)
493         self.assertEqual(64, entry.size)
494
495         self.CheckNoGaps(entries)
496         self.assertEqual(128, image._size)
497
498     def testPackAlignPowerOf2(self):
499         """Test that invalid entry alignment is detected"""
500         with self.assertRaises(ValueError) as e:
501             self._DoTestFile('10_pack_align_power2.dts')
502         self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
503                       "of two", str(e.exception))
504
505     def testPackAlignSizePowerOf2(self):
506         """Test that invalid entry size alignment is detected"""
507         with self.assertRaises(ValueError) as e:
508             self._DoTestFile('11_pack_align_size_power2.dts')
509         self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
510                       "power of two", str(e.exception))
511
512     def testPackInvalidAlign(self):
513         """Test detection of an position that does not match its alignment"""
514         with self.assertRaises(ValueError) as e:
515             self._DoTestFile('12_pack_inv_align.dts')
516         self.assertIn("Node '/binman/u-boot': Position 0x5 (5) does not match "
517                       "align 0x4 (4)", str(e.exception))
518
519     def testPackInvalidSizeAlign(self):
520         """Test that invalid entry size alignment is detected"""
521         with self.assertRaises(ValueError) as e:
522             self._DoTestFile('13_pack_inv_size_align.dts')
523         self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
524                       "align-size 0x4 (4)", str(e.exception))
525
526     def testPackOverlap(self):
527         """Test that overlapping regions are detected"""
528         with self.assertRaises(ValueError) as e:
529             self._DoTestFile('14_pack_overlap.dts')
530         self.assertIn("Node '/binman/u-boot-align': Position 0x3 (3) overlaps "
531                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
532                       str(e.exception))
533
534     def testPackEntryOverflow(self):
535         """Test that entries that overflow their size are detected"""
536         with self.assertRaises(ValueError) as e:
537             self._DoTestFile('15_pack_overflow.dts')
538         self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
539                       "but entry size is 0x3 (3)", str(e.exception))
540
541     def testPackImageOverflow(self):
542         """Test that entries which overflow the image size are detected"""
543         with self.assertRaises(ValueError) as e:
544             self._DoTestFile('16_pack_image_overflow.dts')
545         self.assertIn("Image '/binman': contents size 0x4 (4) exceeds image "
546                       "size 0x3 (3)", str(e.exception))
547
548     def testPackImageSize(self):
549         """Test that the image size can be set"""
550         retcode = self._DoTestFile('17_pack_image_size.dts')
551         self.assertEqual(0, retcode)
552         self.assertIn('image', control.images)
553         image = control.images['image']
554         self.assertEqual(7, image._size)
555
556     def testPackImageSizeAlign(self):
557         """Test that image size alignemnt works as expected"""
558         retcode = self._DoTestFile('18_pack_image_align.dts')
559         self.assertEqual(0, retcode)
560         self.assertIn('image', control.images)
561         image = control.images['image']
562         self.assertEqual(16, image._size)
563
564     def testPackInvalidImageAlign(self):
565         """Test that invalid image alignment is detected"""
566         with self.assertRaises(ValueError) as e:
567             self._DoTestFile('19_pack_inv_image_align.dts')
568         self.assertIn("Image '/binman': Size 0x7 (7) does not match "
569                       "align-size 0x8 (8)", str(e.exception))
570
571     def testPackAlignPowerOf2(self):
572         """Test that invalid image alignment is detected"""
573         with self.assertRaises(ValueError) as e:
574             self._DoTestFile('20_pack_inv_image_align_power2.dts')
575         self.assertIn("Image '/binman': Alignment size 131 must be a power of "
576                       "two", str(e.exception))
577
578     def testImagePadByte(self):
579         """Test that the image pad byte can be specified"""
580         with open(self.TestFile('bss_data')) as fd:
581             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
582         data = self._DoReadFile('21_image_pad.dts')
583         self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 1) + U_BOOT_DATA, data)
584
585     def testImageName(self):
586         """Test that image files can be named"""
587         retcode = self._DoTestFile('22_image_name.dts')
588         self.assertEqual(0, retcode)
589         image = control.images['image1']
590         fname = tools.GetOutputFilename('test-name')
591         self.assertTrue(os.path.exists(fname))
592
593         image = control.images['image2']
594         fname = tools.GetOutputFilename('test-name.xx')
595         self.assertTrue(os.path.exists(fname))
596
597     def testBlobFilename(self):
598         """Test that generic blobs can be provided by filename"""
599         data = self._DoReadFile('23_blob.dts')
600         self.assertEqual(BLOB_DATA, data)
601
602     def testPackSorted(self):
603         """Test that entries can be sorted"""
604         data = self._DoReadFile('24_sorted.dts')
605         self.assertEqual(chr(0) * 1 + U_BOOT_SPL_DATA + chr(0) * 2 +
606                          U_BOOT_DATA, data)
607
608     def testPackZeroPosition(self):
609         """Test that an entry at position 0 is not given a new position"""
610         with self.assertRaises(ValueError) as e:
611             self._DoTestFile('25_pack_zero_size.dts')
612         self.assertIn("Node '/binman/u-boot-spl': Position 0x0 (0) overlaps "
613                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
614                       str(e.exception))
615
616     def testPackUbootDtb(self):
617         """Test that a device tree can be added to U-Boot"""
618         data = self._DoReadFile('26_pack_u_boot_dtb.dts')
619         self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)
620
621     def testPackX86RomNoSize(self):
622         """Test that the end-at-4gb property requires a size property"""
623         with self.assertRaises(ValueError) as e:
624             self._DoTestFile('27_pack_4gb_no_size.dts')
625         self.assertIn("Image '/binman': Image size must be provided when "
626                       "using end-at-4gb", str(e.exception))
627
628     def testPackX86RomOutside(self):
629         """Test that the end-at-4gb property checks for position boundaries"""
630         with self.assertRaises(ValueError) as e:
631             self._DoTestFile('28_pack_4gb_outside.dts')
632         self.assertIn("Node '/binman/u-boot': Position 0x0 (0) is outside "
633                       "the image starting at 0xffffffe0 (4294967264)",
634                       str(e.exception))
635
636     def testPackX86Rom(self):
637         """Test that a basic x86 ROM can be created"""
638         data = self._DoReadFile('29_x86-rom.dts')
639         self.assertEqual(U_BOOT_DATA + chr(0) * 7 + U_BOOT_SPL_DATA +
640                          chr(0) * 2, data)
641
642     def testPackX86RomMeNoDesc(self):
643         """Test that an invalid Intel descriptor entry is detected"""
644         TestFunctional._MakeInputFile('descriptor.bin', '')
645         with self.assertRaises(ValueError) as e:
646             self._DoTestFile('31_x86-rom-me.dts')
647         self.assertIn("Node '/binman/intel-descriptor': Cannot find FD "
648                       "signature", str(e.exception))
649
650     def testPackX86RomBadDesc(self):
651         """Test that the Intel requires a descriptor entry"""
652         with self.assertRaises(ValueError) as e:
653             self._DoTestFile('30_x86-rom-me-no-desc.dts')
654         self.assertIn("Node '/binman/intel-me': No position set with "
655                       "pos-unset: should another entry provide this correct "
656                       "position?", str(e.exception))
657
658     def testPackX86RomMe(self):
659         """Test that an x86 ROM with an ME region can be created"""
660         data = self._DoReadFile('31_x86-rom-me.dts')
661         self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
662
663     def testPackVga(self):
664         """Test that an image with a VGA binary can be created"""
665         data = self._DoReadFile('32_intel-vga.dts')
666         self.assertEqual(VGA_DATA, data[:len(VGA_DATA)])
667
668     def testPackStart16(self):
669         """Test that an image with an x86 start16 region can be created"""
670         data = self._DoReadFile('33_x86-start16.dts')
671         self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)])
672
673     def _RunMicrocodeTest(self, dts_fname, nodtb_data):
674         data = self._DoReadFile(dts_fname, True)
675
676         # Now check the device tree has no microcode
677         second = data[len(nodtb_data):]
678         fname = tools.GetOutputFilename('test.dtb')
679         with open(fname, 'wb') as fd:
680             fd.write(second)
681         dtb = fdt.FdtScan(fname)
682         ucode = dtb.GetNode('/microcode')
683         self.assertTrue(ucode)
684         for node in ucode.subnodes:
685             self.assertFalse(node.props.get('data'))
686
687         fdt_len = self.GetFdtLen(second)
688         third = second[fdt_len:]
689
690         # Check that the microcode appears immediately after the Fdt
691         # This matches the concatenation of the data properties in
692         # the /microcode/update@xxx nodes in 34_x86_ucode.dts.
693         ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000,
694                                  0x78235609)
695         self.assertEqual(ucode_data, third[:len(ucode_data)])
696         ucode_pos = len(nodtb_data) + fdt_len
697
698         # Check that the microcode pointer was inserted. It should match the
699         # expected position and size
700         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
701                                    len(ucode_data))
702         first = data[:len(nodtb_data)]
703         return first, pos_and_size
704
705     def testPackUbootMicrocode(self):
706         """Test that x86 microcode can be handled correctly
707
708         We expect to see the following in the image, in order:
709             u-boot-nodtb.bin with a microcode pointer inserted at the correct
710                 place
711             u-boot.dtb with the microcode removed
712             the microcode
713         """
714         first, pos_and_size = self._RunMicrocodeTest('34_x86_ucode.dts',
715                                                      U_BOOT_NODTB_DATA)
716         self.assertEqual('nodtb with microcode' + pos_and_size +
717                          ' somewhere in here', first)
718
719     def _RunPackUbootSingleMicrocode(self):
720         """Test that x86 microcode can be handled correctly
721
722         We expect to see the following in the image, in order:
723             u-boot-nodtb.bin with a microcode pointer inserted at the correct
724                 place
725             u-boot.dtb with the microcode
726             an empty microcode region
727         """
728         # We need the libfdt library to run this test since only that allows
729         # finding the offset of a property. This is required by
730         # Entry_u_boot_dtb_with_ucode.ObtainContents().
731         data = self._DoReadFile('35_x86_single_ucode.dts', True)
732
733         second = data[len(U_BOOT_NODTB_DATA):]
734
735         fdt_len = self.GetFdtLen(second)
736         third = second[fdt_len:]
737         second = second[:fdt_len]
738
739         ucode_data = struct.pack('>2L', 0x12345678, 0x12345679)
740         self.assertIn(ucode_data, second)
741         ucode_pos = second.find(ucode_data) + len(U_BOOT_NODTB_DATA)
742
743         # Check that the microcode pointer was inserted. It should match the
744         # expected position and size
745         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
746                                    len(ucode_data))
747         first = data[:len(U_BOOT_NODTB_DATA)]
748         self.assertEqual('nodtb with microcode' + pos_and_size +
749                          ' somewhere in here', first)
750
751     def testPackUbootSingleMicrocode(self):
752         """Test that x86 microcode can be handled correctly with fdt_normal.
753         """
754         self._RunPackUbootSingleMicrocode()
755
756     def testUBootImg(self):
757         """Test that u-boot.img can be put in a file"""
758         data = self._DoReadFile('36_u_boot_img.dts')
759         self.assertEqual(U_BOOT_IMG_DATA, data)
760
761     def testNoMicrocode(self):
762         """Test that a missing microcode region is detected"""
763         with self.assertRaises(ValueError) as e:
764             self._DoReadFile('37_x86_no_ucode.dts', True)
765         self.assertIn("Node '/binman/u-boot-dtb-with-ucode': No /microcode "
766                       "node found in ", str(e.exception))
767
768     def testMicrocodeWithoutNode(self):
769         """Test that a missing u-boot-dtb-with-ucode node is detected"""
770         with self.assertRaises(ValueError) as e:
771             self._DoReadFile('38_x86_ucode_missing_node.dts', True)
772         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
773                 "microcode region u-boot-dtb-with-ucode", str(e.exception))
774
775     def testMicrocodeWithoutNode2(self):
776         """Test that a missing u-boot-ucode node is detected"""
777         with self.assertRaises(ValueError) as e:
778             self._DoReadFile('39_x86_ucode_missing_node2.dts', True)
779         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
780             "microcode region u-boot-ucode", str(e.exception))
781
782     def testMicrocodeWithoutPtrInElf(self):
783         """Test that a U-Boot binary without the microcode symbol is detected"""
784         # ELF file without a '_dt_ucode_base_size' symbol
785         try:
786             with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
787                 TestFunctional._MakeInputFile('u-boot', fd.read())
788
789             with self.assertRaises(ValueError) as e:
790                 self._RunPackUbootSingleMicrocode()
791             self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot locate "
792                     "_dt_ucode_base_size symbol in u-boot", str(e.exception))
793
794         finally:
795             # Put the original file back
796             with open(self.TestFile('u_boot_ucode_ptr')) as fd:
797                 TestFunctional._MakeInputFile('u-boot', fd.read())
798
799     def testMicrocodeNotInImage(self):
800         """Test that microcode must be placed within the image"""
801         with self.assertRaises(ValueError) as e:
802             self._DoReadFile('40_x86_ucode_not_in_image.dts', True)
803         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Microcode "
804                 "pointer _dt_ucode_base_size at fffffe14 is outside the "
805                 "image ranging from 00000000 to 0000002e", str(e.exception))
806
807     def testWithoutMicrocode(self):
808         """Test that we can cope with an image without microcode (e.g. qemu)"""
809         with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
810             TestFunctional._MakeInputFile('u-boot', fd.read())
811         data, dtb = self._DoReadFileDtb('44_x86_optional_ucode.dts', True)
812
813         # Now check the device tree has no microcode
814         self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)])
815         second = data[len(U_BOOT_NODTB_DATA):]
816
817         fdt_len = self.GetFdtLen(second)
818         self.assertEqual(dtb, second[:fdt_len])
819
820         used_len = len(U_BOOT_NODTB_DATA) + fdt_len
821         third = data[used_len:]
822         self.assertEqual(chr(0) * (0x200 - used_len), third)
823
824     def testUnknownPosSize(self):
825         """Test that microcode must be placed within the image"""
826         with self.assertRaises(ValueError) as e:
827             self._DoReadFile('41_unknown_pos_size.dts', True)
828         self.assertIn("Image '/binman': Unable to set pos/size for unknown "
829                 "entry 'invalid-entry'", str(e.exception))
830
831     def testPackFsp(self):
832         """Test that an image with a FSP binary can be created"""
833         data = self._DoReadFile('42_intel-fsp.dts')
834         self.assertEqual(FSP_DATA, data[:len(FSP_DATA)])
835
836     def testPackCmc(self):
837         """Test that an image with a CMC binary can be created"""
838         data = self._DoReadFile('43_intel-cmc.dts')
839         self.assertEqual(CMC_DATA, data[:len(CMC_DATA)])
840
841     def testPackVbt(self):
842         """Test that an image with a VBT binary can be created"""
843         data = self._DoReadFile('46_intel-vbt.dts')
844         self.assertEqual(VBT_DATA, data[:len(VBT_DATA)])
845
846     def testSplBssPad(self):
847         """Test that we can pad SPL's BSS with zeros"""
848         # ELF file with a '__bss_size' symbol
849         with open(self.TestFile('bss_data')) as fd:
850             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
851         data = self._DoReadFile('47_spl_bss_pad.dts')
852         self.assertEqual(U_BOOT_SPL_DATA + (chr(0) * 10) + U_BOOT_DATA, data)
853
854         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
855             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
856         with self.assertRaises(ValueError) as e:
857             data = self._DoReadFile('47_spl_bss_pad.dts')
858         self.assertIn('Expected __bss_size symbol in spl/u-boot-spl',
859                       str(e.exception))
860
861     def testPackStart16Spl(self):
862         """Test that an image with an x86 start16 region can be created"""
863         data = self._DoReadFile('48_x86-start16-spl.dts')
864         self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)])
865
866     def testPackUbootSplMicrocode(self):
867         """Test that x86 microcode can be handled correctly in SPL
868
869         We expect to see the following in the image, in order:
870             u-boot-spl-nodtb.bin with a microcode pointer inserted at the
871                 correct place
872             u-boot.dtb with the microcode removed
873             the microcode
874         """
875         # ELF file with a '_dt_ucode_base_size' symbol
876         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
877             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
878         first, pos_and_size = self._RunMicrocodeTest('49_x86_ucode_spl.dts',
879                                                      U_BOOT_SPL_NODTB_DATA)
880         self.assertEqual('splnodtb with microc' + pos_and_size +
881                          'ter somewhere in here', first)
882
883     def testPackMrc(self):
884         """Test that an image with an MRC binary can be created"""
885         data = self._DoReadFile('50_intel_mrc.dts')
886         self.assertEqual(MRC_DATA, data[:len(MRC_DATA)])
887
888     def testSplDtb(self):
889         """Test that an image with spl/u-boot-spl.dtb can be created"""
890         data = self._DoReadFile('51_u_boot_spl_dtb.dts')
891         self.assertEqual(U_BOOT_SPL_DTB_DATA, data[:len(U_BOOT_SPL_DTB_DATA)])
892
893     def testSplNoDtb(self):
894         """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
895         data = self._DoReadFile('52_u_boot_spl_nodtb.dts')
896         self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
897
898     def testSymbols(self):
899         """Test binman can assign symbols embedded in U-Boot"""
900         elf_fname = self.TestFile('u_boot_binman_syms')
901         syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
902         addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
903         self.assertEqual(syms['_binman_u_boot_spl_prop_pos'].address, addr)
904
905         with open(self.TestFile('u_boot_binman_syms')) as fd:
906             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
907         data = self._DoReadFile('53_symbols.dts')
908         sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
909         expected = (sym_values + U_BOOT_SPL_DATA[16:] + chr(0xff) +
910                     U_BOOT_DATA +
911                     sym_values + U_BOOT_SPL_DATA[16:])
912         self.assertEqual(expected, data)
913
914
915 if __name__ == "__main__":
916     unittest.main()