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 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) 543