blob: bf6e9ed6c355f3f4dd64e5078654e949ce74c2aa [file] [log] [blame]
Simon Glass57454f42016-11-25 20:15:52 -07001#
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
11from optparse import OptionParser
12import os
13import shutil
14import struct
15import sys
16import tempfile
17import unittest
18
19import binman
20import cmdline
21import command
22import control
23import entry
24import fdt_select
25import fdt_util
26import tools
27import tout
28
29# Contents of test files, corresponding to different entry types
30U_BOOT_DATA = '1234'
31U_BOOT_IMG_DATA = 'img'
32U_BOOT_SPL_DATA = '567'
33BLOB_DATA = '89'
34ME_DATA = '0abcd'
35VGA_DATA = 'vga'
36U_BOOT_DTB_DATA = 'udtb'
37X86_START16_DATA = 'start16'
38U_BOOT_NODTB_DATA = 'nodtb with microcode pointer somewhere in here'
39
40class TestFunctional(unittest.TestCase):
41 """Functional tests for binman
42
43 Most of these use a sample .dts file to build an image and then check
44 that it looks correct. The sample files are in the test/ subdirectory
45 and are numbered.
46
47 For each entry type a very small test file is created using fixed
48 string contents. This makes it easy to test that things look right, and
49 debug problems.
50
51 In some cases a 'real' file must be used - these are also supplied in
52 the test/ diurectory.
53 """
54 @classmethod
55 def setUpClass(self):
56 # Handle the case where argv[0] is 'python'
57 self._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
58 self._binman_pathname = os.path.join(self._binman_dir, 'binman')
59
60 # Create a temporary directory for input files
61 self._indir = tempfile.mkdtemp(prefix='binmant.')
62
63 # Create some test files
64 TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
65 TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
66 TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
67 TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
68 TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
69 TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
70 self._output_setup = False
71
72 @classmethod
73 def tearDownClass(self):
74 """Remove the temporary input directory and its contents"""
75 if self._indir:
76 shutil.rmtree(self._indir)
77 self._indir = None
78
79 def setUp(self):
80 # Enable this to turn on debugging output
81 # tout.Init(tout.DEBUG)
82 command.test_result = None
83
84 def tearDown(self):
85 """Remove the temporary output directory"""
86 tools._FinaliseForTest()
87
88 def _RunBinman(self, *args, **kwargs):
89 """Run binman using the command line
90
91 Args:
92 Arguments to pass, as a list of strings
93 kwargs: Arguments to pass to Command.RunPipe()
94 """
95 result = command.RunPipe([[self._binman_pathname] + list(args)],
96 capture=True, capture_stderr=True, raise_on_error=False)
97 if result.return_code and kwargs.get('raise_on_error', True):
98 raise Exception("Error running '%s': %s" % (' '.join(args),
99 result.stdout + result.stderr))
100 return result
101
102 def _DoBinman(self, *args):
103 """Run binman using directly (in the same process)
104
105 Args:
106 Arguments to pass, as a list of strings
107 Returns:
108 Return value (0 for success)
109 """
110 (options, args) = cmdline.ParseArgs(list(args))
111 options.pager = 'binman-invalid-pager'
112 options.build_dir = self._indir
113
114 # For testing, you can force an increase in verbosity here
115 # options.verbosity = tout.DEBUG
116 return control.Binman(options, args)
117
118 def _DoTestFile(self, fname):
119 """Run binman with a given test file
120
121 Args:
122 fname: Device tree source filename to use (e.g. 05_simple.dts)
123 """
124 return self._DoBinman('-p', '-I', self._indir,
125 '-d', self.TestFile(fname))
126
127 def _SetupDtb(self, fname, outfile='u-boot.dtb'):
128 if not self._output_setup:
129 tools.PrepareOutputDir(self._indir, True)
130 self._output_setup = True
131 dtb = fdt_util.EnsureCompiled(self.TestFile(fname))
132 with open(dtb) as fd:
133 data = fd.read()
134 TestFunctional._MakeInputFile(outfile, data)
135
136 def _DoReadFile(self, fname, use_real_dtb=False):
137 """Run binman and return the resulting image
138
139 This runs binman with a given test file and then reads the resulting
140 output file. It is a shortcut function since most tests need to do
141 these steps.
142
143 Raises an assertion failure if binman returns a non-zero exit code.
144
145 Args:
146 fname: Device tree source filename to use (e.g. 05_simple.dts)
147 use_real_dtb: True to use the test file as the contents of
148 the u-boot-dtb entry. Normally this is not needed and the
149 test contents (the U_BOOT_DTB_DATA string) can be used.
150 But in some test we need the real contents.
151 """
152 # Use the compiled test file as the u-boot-dtb input
153 if use_real_dtb:
154 self._SetupDtb(fname)
155
156 try:
157 retcode = self._DoTestFile(fname)
158 self.assertEqual(0, retcode)
159
160 # Find the (only) image, read it and return its contents
161 image = control.images['image']
162 fname = tools.GetOutputFilename('image.bin')
163 self.assertTrue(os.path.exists(fname))
164 with open(fname) as fd:
165 return fd.read()
166 finally:
167 # Put the test file back
168 if use_real_dtb:
169 TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
170
171 @classmethod
172 def _MakeInputFile(self, fname, contents):
173 """Create a new test input file, creating directories as needed
174
175 Args:
176 fname: Filenaem to create
177 contents: File contents to write in to the file
178 Returns:
179 Full pathname of file created
180 """
181 pathname = os.path.join(self._indir, fname)
182 dirname = os.path.dirname(pathname)
183 if dirname and not os.path.exists(dirname):
184 os.makedirs(dirname)
185 with open(pathname, 'wb') as fd:
186 fd.write(contents)
187 return pathname
188
189 @classmethod
190 def TestFile(self, fname):
191 return os.path.join(self._binman_dir, 'test', fname)
192
193 def AssertInList(self, grep_list, target):
194 """Assert that at least one of a list of things is in a target
195
196 Args:
197 grep_list: List of strings to check
198 target: Target string
199 """
200 for grep in grep_list:
201 if grep in target:
202 return
203 self.fail("Error: '%' not found in '%s'" % (grep_list, target))
204
205 def CheckNoGaps(self, entries):
206 """Check that all entries fit together without gaps
207
208 Args:
209 entries: List of entries to check
210 """
211 pos = 0
212 for entry in entries.values():
213 self.assertEqual(pos, entry.pos)
214 pos += entry.size
215
216 def testRun(self):
217 """Test a basic run with valid args"""
218 result = self._RunBinman('-h')
219
220 def testFullHelp(self):
221 """Test that the full help is displayed with -H"""
222 result = self._RunBinman('-H')
223 help_file = os.path.join(self._binman_dir, 'README')
224 self.assertEqual(len(result.stdout), os.path.getsize(help_file))
225 self.assertEqual(0, len(result.stderr))
226 self.assertEqual(0, result.return_code)
227
228 def testFullHelpInternal(self):
229 """Test that the full help is displayed with -H"""
230 try:
231 command.test_result = command.CommandResult()
232 result = self._DoBinman('-H')
233 help_file = os.path.join(self._binman_dir, 'README')
234 finally:
235 command.test_result = None
236
237 def testHelp(self):
238 """Test that the basic help is displayed with -h"""
239 result = self._RunBinman('-h')
240 self.assertTrue(len(result.stdout) > 200)
241 self.assertEqual(0, len(result.stderr))
242 self.assertEqual(0, result.return_code)
243
244 # Not yet available.
245 def testBoard(self):
246 """Test that we can run it with a specific board"""
247 self._SetupDtb('05_simple.dts', 'sandbox/u-boot.dtb')
248 TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
249 result = self._DoBinman('-b', 'sandbox')
250 self.assertEqual(0, result)
251
252 def testNeedBoard(self):
253 """Test that we get an error when no board ius supplied"""
254 with self.assertRaises(ValueError) as e:
255 result = self._DoBinman()
256 self.assertIn("Must provide a board to process (use -b <board>)",
257 str(e.exception))
258
259 def testMissingDt(self):
260 """Test that an invalid device tree file generates an error"""
261 with self.assertRaises(Exception) as e:
262 self._RunBinman('-d', 'missing_file')
263 # We get one error from libfdt, and a different one from fdtget.
264 self.AssertInList(["Couldn't open blob from 'missing_file'",
265 'No such file or directory'], str(e.exception))
266
267 def testBrokenDt(self):
268 """Test that an invalid device tree source file generates an error
269
270 Since this is a source file it should be compiled and the error
271 will come from the device-tree compiler (dtc).
272 """
273 with self.assertRaises(Exception) as e:
274 self._RunBinman('-d', self.TestFile('01_invalid.dts'))
275 self.assertIn("FATAL ERROR: Unable to parse input tree",
276 str(e.exception))
277
278 def testMissingNode(self):
279 """Test that a device tree without a 'binman' node generates an error"""
280 with self.assertRaises(Exception) as e:
281 self._DoBinman('-d', self.TestFile('02_missing_node.dts'))
282 self.assertIn("does not have a 'binman' node", str(e.exception))
283
284 def testEmpty(self):
285 """Test that an empty binman node works OK (i.e. does nothing)"""
286 result = self._RunBinman('-d', self.TestFile('03_empty.dts'))
287 self.assertEqual(0, len(result.stderr))
288 self.assertEqual(0, result.return_code)
289
290 def testInvalidEntry(self):
291 """Test that an invalid entry is flagged"""
292 with self.assertRaises(Exception) as e:
293 result = self._RunBinman('-d',
294 self.TestFile('04_invalid_entry.dts'))
295 #print e.exception
296 self.assertIn("Unknown entry type 'not-a-valid-type' in node "
297 "'/binman/not-a-valid-type'", str(e.exception))
298
299 def testSimple(self):
300 """Test a simple binman with a single file"""
301 data = self._DoReadFile('05_simple.dts')
302 self.assertEqual(U_BOOT_DATA, data)
303
304 def testDual(self):
305 """Test that we can handle creating two images
306
307 This also tests image padding.
308 """
309 retcode = self._DoTestFile('06_dual_image.dts')
310 self.assertEqual(0, retcode)
311
312 image = control.images['image1']
313 self.assertEqual(len(U_BOOT_DATA), image._size)
314 fname = tools.GetOutputFilename('image1.bin')
315 self.assertTrue(os.path.exists(fname))
316 with open(fname) as fd:
317 data = fd.read()
318 self.assertEqual(U_BOOT_DATA, data)
319
320 image = control.images['image2']
321 self.assertEqual(3 + len(U_BOOT_DATA) + 5, image._size)
322 fname = tools.GetOutputFilename('image2.bin')
323 self.assertTrue(os.path.exists(fname))
324 with open(fname) as fd:
325 data = fd.read()
326 self.assertEqual(U_BOOT_DATA, data[3:7])
327 self.assertEqual(chr(0) * 3, data[:3])
328 self.assertEqual(chr(0) * 5, data[7:])
329
330 def testBadAlign(self):
331 """Test that an invalid alignment value is detected"""
332 with self.assertRaises(ValueError) as e:
333 self._DoTestFile('07_bad_align.dts')
334 self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
335 "of two", str(e.exception))
336
337 def testPackSimple(self):
338 """Test that packing works as expected"""
339 retcode = self._DoTestFile('08_pack.dts')
340 self.assertEqual(0, retcode)
341 self.assertIn('image', control.images)
342 image = control.images['image']
343 entries = image._entries
344 self.assertEqual(5, len(entries))
345
346 # First u-boot
347 self.assertIn('u-boot', entries)
348 entry = entries['u-boot']
349 self.assertEqual(0, entry.pos)
350 self.assertEqual(len(U_BOOT_DATA), entry.size)
351
352 # Second u-boot, aligned to 16-byte boundary
353 self.assertIn('u-boot-align', entries)
354 entry = entries['u-boot-align']
355 self.assertEqual(16, entry.pos)
356 self.assertEqual(len(U_BOOT_DATA), entry.size)
357
358 # Third u-boot, size 23 bytes
359 self.assertIn('u-boot-size', entries)
360 entry = entries['u-boot-size']
361 self.assertEqual(20, entry.pos)
362 self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
363 self.assertEqual(23, entry.size)
364
365 # Fourth u-boot, placed immediate after the above
366 self.assertIn('u-boot-next', entries)
367 entry = entries['u-boot-next']
368 self.assertEqual(43, entry.pos)
369 self.assertEqual(len(U_BOOT_DATA), entry.size)
370
371 # Fifth u-boot, placed at a fixed position
372 self.assertIn('u-boot-fixed', entries)
373 entry = entries['u-boot-fixed']
374 self.assertEqual(61, entry.pos)
375 self.assertEqual(len(U_BOOT_DATA), entry.size)
376
377 self.assertEqual(65, image._size)
378
379 def testPackExtra(self):
380 """Test that extra packing feature works as expected"""
381 retcode = self._DoTestFile('09_pack_extra.dts')
382
383 self.assertEqual(0, retcode)
384 self.assertIn('image', control.images)
385 image = control.images['image']
386 entries = image._entries
387 self.assertEqual(5, len(entries))
388
389 # First u-boot with padding before and after
390 self.assertIn('u-boot', entries)
391 entry = entries['u-boot']
392 self.assertEqual(0, entry.pos)
393 self.assertEqual(3, entry.pad_before)
394 self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
395
396 # Second u-boot has an aligned size, but it has no effect
397 self.assertIn('u-boot-align-size-nop', entries)
398 entry = entries['u-boot-align-size-nop']
399 self.assertEqual(12, entry.pos)
400 self.assertEqual(4, entry.size)
401
402 # Third u-boot has an aligned size too
403 self.assertIn('u-boot-align-size', entries)
404 entry = entries['u-boot-align-size']
405 self.assertEqual(16, entry.pos)
406 self.assertEqual(32, entry.size)
407
408 # Fourth u-boot has an aligned end
409 self.assertIn('u-boot-align-end', entries)
410 entry = entries['u-boot-align-end']
411 self.assertEqual(48, entry.pos)
412 self.assertEqual(16, entry.size)
413
414 # Fifth u-boot immediately afterwards
415 self.assertIn('u-boot-align-both', entries)
416 entry = entries['u-boot-align-both']
417 self.assertEqual(64, entry.pos)
418 self.assertEqual(64, entry.size)
419
420 self.CheckNoGaps(entries)
421 self.assertEqual(128, image._size)
422
423 def testPackAlignPowerOf2(self):
424 """Test that invalid entry alignment is detected"""
425 with self.assertRaises(ValueError) as e:
426 self._DoTestFile('10_pack_align_power2.dts')
427 self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
428 "of two", str(e.exception))
429
430 def testPackAlignSizePowerOf2(self):
431 """Test that invalid entry size alignment is detected"""
432 with self.assertRaises(ValueError) as e:
433 self._DoTestFile('11_pack_align_size_power2.dts')
434 self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
435 "power of two", str(e.exception))
436
437 def testPackInvalidAlign(self):
438 """Test detection of an position that does not match its alignment"""
439 with self.assertRaises(ValueError) as e:
440 self._DoTestFile('12_pack_inv_align.dts')
441 self.assertIn("Node '/binman/u-boot': Position 0x5 (5) does not match "
442 "align 0x4 (4)", str(e.exception))
443
444 def testPackInvalidSizeAlign(self):
445 """Test that invalid entry size alignment is detected"""
446 with self.assertRaises(ValueError) as e:
447 self._DoTestFile('13_pack_inv_size_align.dts')
448 self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
449 "align-size 0x4 (4)", str(e.exception))
450
451 def testPackOverlap(self):
452 """Test that overlapping regions are detected"""
453 with self.assertRaises(ValueError) as e:
454 self._DoTestFile('14_pack_overlap.dts')
455 self.assertIn("Node '/binman/u-boot-align': Position 0x3 (3) overlaps "
456 "with previous entry '/binman/u-boot' ending at 0x4 (4)",
457 str(e.exception))
458
459 def testPackEntryOverflow(self):
460 """Test that entries that overflow their size are detected"""
461 with self.assertRaises(ValueError) as e:
462 self._DoTestFile('15_pack_overflow.dts')
463 self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
464 "but entry size is 0x3 (3)", str(e.exception))
465
466 def testPackImageOverflow(self):
467 """Test that entries which overflow the image size are detected"""
468 with self.assertRaises(ValueError) as e:
469 self._DoTestFile('16_pack_image_overflow.dts')
470 self.assertIn("Image '/binman': contents size 0x4 (4) exceeds image "
471 "size 0x3 (3)", str(e.exception))
472
473 def testPackImageSize(self):
474 """Test that the image size can be set"""
475 retcode = self._DoTestFile('17_pack_image_size.dts')
476 self.assertEqual(0, retcode)
477 self.assertIn('image', control.images)
478 image = control.images['image']
479 self.assertEqual(7, image._size)
480
481 def testPackImageSizeAlign(self):
482 """Test that image size alignemnt works as expected"""
483 retcode = self._DoTestFile('18_pack_image_align.dts')
484 self.assertEqual(0, retcode)
485 self.assertIn('image', control.images)
486 image = control.images['image']
487 self.assertEqual(16, image._size)
488
489 def testPackInvalidImageAlign(self):
490 """Test that invalid image alignment is detected"""
491 with self.assertRaises(ValueError) as e:
492 self._DoTestFile('19_pack_inv_image_align.dts')
493 self.assertIn("Image '/binman': Size 0x7 (7) does not match "
494 "align-size 0x8 (8)", str(e.exception))
495
496 def testPackAlignPowerOf2(self):
497 """Test that invalid image alignment is detected"""
498 with self.assertRaises(ValueError) as e:
499 self._DoTestFile('20_pack_inv_image_align_power2.dts')
500 self.assertIn("Image '/binman': Alignment size 131 must be a power of "
501 "two", str(e.exception))
502
503 def testImagePadByte(self):
504 """Test that the image pad byte can be specified"""
505 data = self._DoReadFile('21_image_pad.dts')
506 self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 9) + U_BOOT_DATA, data)
507
508 def testImageName(self):
509 """Test that image files can be named"""
510 retcode = self._DoTestFile('22_image_name.dts')
511 self.assertEqual(0, retcode)
512 image = control.images['image1']
513 fname = tools.GetOutputFilename('test-name')
514 self.assertTrue(os.path.exists(fname))
515
516 image = control.images['image2']
517 fname = tools.GetOutputFilename('test-name.xx')
518 self.assertTrue(os.path.exists(fname))
519
520 def testBlobFilename(self):
521 """Test that generic blobs can be provided by filename"""
522 data = self._DoReadFile('23_blob.dts')
523 self.assertEqual(BLOB_DATA, data)
524
525 def testPackSorted(self):
526 """Test that entries can be sorted"""
527 data = self._DoReadFile('24_sorted.dts')
528 self.assertEqual(chr(0) * 5 + U_BOOT_SPL_DATA + chr(0) * 2 +
529 U_BOOT_DATA, data)
530
531 def testPackZeroPosition(self):
532 """Test that an entry at position 0 is not given a new position"""
533 with self.assertRaises(ValueError) as e:
534 self._DoTestFile('25_pack_zero_size.dts')
535 self.assertIn("Node '/binman/u-boot-spl': Position 0x0 (0) overlaps "
536 "with previous entry '/binman/u-boot' ending at 0x4 (4)",
537 str(e.exception))
538
539 def testPackUbootDtb(self):
540 """Test that a device tree can be added to U-Boot"""
541 data = self._DoReadFile('26_pack_u_boot_dtb.dts')
542 self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)