Simon Glass | 6a0f481 | 2024-09-30 12:51:38 -0600 | [diff] [blame] | 1 | .. SPDX-License-Identifier: GPL-2.0+ |
| 2 | |
| 3 | .. toctree:: |
| 4 | :maxdepth: 1 |
| 5 | |
| 6 | Binman Tests |
| 7 | ============ |
| 8 | |
| 9 | .. contents:: |
| 10 | :depth: 2 |
| 11 | :local: |
| 12 | |
| 13 | There is some material on writing tests in the main Binman documentation |
| 14 | (see :doc:`package/index`). This short guide is separate so people don't |
| 15 | feel they have to read as much. |
| 16 | |
| 17 | Code and output is mostly included verbatim, which makes the doc longer, but |
| 18 | avoids its becoming confusing when the output or referenced code changes in the |
| 19 | future. |
| 20 | |
| 21 | Purpose |
| 22 | ------- |
| 23 | |
| 24 | The main purpose of tests in Binman is to make sure that Binman actually does |
| 25 | what it is supposed to. Various people contribute code, refactoring is done |
| 26 | over time, but U-Boot users (developers, SoC vendors, board vendors) rely on |
| 27 | Binman producing images which function correctly. Without tests, a one-line |
| 28 | change could unintentionally break a corner-case and the problem might not be |
| 29 | noticed for months. Debugging an image-generation problem with a board you |
| 30 | don't have can be very hard. |
| 31 | |
| 32 | A secondary purpose is productivity. U-Boot contributors are busy and often |
| 33 | have too much on their plate. Trying to figure out why their patch broke |
| 34 | some other vendor's workflow can be very time-consuming and frustrating. By |
| 35 | building in tests from the start, this is largely avoided. If your change has |
| 36 | full test coverage and doesn't break any test, all is well and no one can |
| 37 | complain. |
| 38 | |
| 39 | A lessor purpose is to document what Binman actually does. If a test covers a |
| 40 | feature, it works. If there is no test coverage, no one can say for sure |
| 41 | whether it works in all expected situations, certainly not wihout manual |
| 42 | effort. |
| 43 | |
| 44 | In fact, strictly speaking it isn't completely clear what 'works' even means in |
| 45 | the case where these is no test to cover the code. We are often left guessing |
| 46 | as to what the documentation means, what was actually intended, etc. |
| 47 | |
| 48 | Finally, code-coverage helps to remove 'zombie code', copied from elsewhere |
| 49 | because it looks reasonable, but not actually needed. The same situation arises |
| 50 | in silicon-chip design, where a part of the chip is not validated. If it isn't |
| 51 | validated, it can be assumed not to work, either now or later, so it is best to |
| 52 | remove that logic to avoid it causing problems. |
| 53 | |
| 54 | Setting up |
| 55 | ---------- |
| 56 | |
| 57 | Binman tests use various utility programs. Most of these are documented in |
| 58 | :doc:`../build/gcc`. But some are SoC-specific. To fetch these, tell Binman to |
| 59 | fetch or build any missing tools: |
| 60 | |
| 61 | .. code-block:: bash |
| 62 | |
| 63 | $ binman tool -f missing |
| 64 | |
| 65 | When this completes successfully, you can list the tools. You should see |
| 66 | something like this: |
| 67 | |
| 68 | .. code-block:: bash |
| 69 | |
| 70 | $ binman tool -l |
| 71 | Name Version Description Path |
| 72 | --------------- ----------- ------------------------- ------------------------------ |
| 73 | bootgen ****** Bootg Xilinx Bootgen /home/sglass/.binman-tools/bootgen |
| 74 | bzip2 1.0.8 bzip2 compression /usr/bin/bzip2 |
| 75 | cbfstool unknown Manipulate CBFS files /home/sglass/bin/cbfstool |
| 76 | fdt_add_pubkey unknown Generate image for U-Boot /home/sglass/bin/fdt_add_pubkey |
| 77 | fdtgrep unknown Grep devicetree files /home/sglass/bin/fdtgrep |
| 78 | fiptool v2.11.0(rele Manipulate ATF FIP files /home/sglass/.binman-tools/fiptool |
| 79 | futility v0.0.1-9f2e9 Chromium OS firmware utili /home/sglass/.binman-tools/futility |
| 80 | gzip 1.12 gzip compression /usr/bin/gzip |
| 81 | ifwitool unknown Manipulate Intel IFWI file /home/sglass/.binman-tools/ifwitool |
| 82 | lz4 v1.9.4 lz4 compression /usr/bin/lz4 |
| 83 | lzma_alone 9.22 beta lzma_alone compression /usr/bin/lzma_alone |
| 84 | lzop v1.04 lzo compression /usr/bin/lzop |
| 85 | mkeficapsule 2024.10-rc5- mkeficapsule tool for gene /home/sglass/bin/mkeficapsule |
| 86 | mkimage 2024.10-rc5- Generate image for U-Boot /home/sglass/bin/mkimage |
| 87 | openssl 3.0.13 30 Ja openssl cryptography toolk /usr/bin/openssl |
| 88 | xz 5.4.5 xz compression /usr/bin/xz |
| 89 | zstd v1.5.5 zstd compression /usr/bin/zstd |
| 90 | |
| 91 | The tools are written to ``~/.binman-tools`` so add that to your ``PATH``. |
| 92 | It's fine to have some of the tools elsewhere (e.g. ``~/bin``) so long as they |
| 93 | are up-to-date. This allows you use the version of the tools intended for |
| 94 | running tests. |
| 95 | |
| 96 | Now you should be able to actually run the tests: |
| 97 | |
| 98 | .. code-block:: bash |
| 99 | |
| 100 | $ binman test |
| 101 | ======================== Running binman tests ======================== |
| 102 | ...................................................................... |
| 103 | ...................................................................... |
| 104 | ...................................................................... |
| 105 | ...................................................................... |
| 106 | ...................................................................... |
| 107 | ...................................................................... |
| 108 | ...................................................................... |
| 109 | ...................................................................... |
| 110 | ........ |
| 111 | ---------------------------------------------------------------------- |
| 112 | Ran 568 tests in 2.578s |
| 113 | |
| 114 | OK |
| 115 | |
| 116 | If this doesn't work, see if you can have some missing tools. Check that the |
| 117 | dependencies are all there as above. If it is very slow, try installing |
| 118 | concurrencytest so that the tests run in parallel. |
| 119 | |
| 120 | The next thing to set up is code coverage, using the -T flag: |
| 121 | |
| 122 | .. code-block:: bash |
| 123 | |
| 124 | $ binman test -T |
| 125 | ======================== Running binman tests ======================== |
| 126 | ...................................................................... |
| 127 | ...................................................................... |
| 128 | ...................................................................... |
| 129 | ...................................................................... |
| 130 | ...................................................................... |
| 131 | ...................................................................... |
| 132 | ...................................................................... |
| 133 | ...................................................................... |
| 134 | ........ |
| 135 | ---------------------------------------------------------------------- |
| 136 | Ran 568 tests in 17.367s |
| 137 | |
| 138 | OK |
| 139 | |
| 140 | 99% |
| 141 | Name Stmts Miss Cover |
| 142 | --------------------------------------------------------------------------- |
| 143 | tools/binman/__init__.py 0 0 100% |
| 144 | tools/binman/bintool.py 263 0 100% |
| 145 | tools/binman/btool/bootgen.py 21 0 100% |
| 146 | tools/binman/btool/btool_gzip.py 5 0 100% |
| 147 | tools/binman/btool/bzip2.py 5 0 100% |
| 148 | tools/binman/btool/cbfstool.py 24 0 100% |
| 149 | tools/binman/btool/cst.py 15 4 73% |
| 150 | tools/binman/btool/fdt_add_pubkey.py 21 0 100% |
| 151 | tools/binman/btool/fdtgrep.py 26 0 100% |
| 152 | tools/binman/btool/fiptool.py 19 0 100% |
| 153 | tools/binman/btool/futility.py 19 0 100% |
| 154 | tools/binman/btool/ifwitool.py 22 0 100% |
| 155 | tools/binman/btool/lz4.py 22 0 100% |
| 156 | tools/binman/btool/lzma_alone.py 34 0 100% |
| 157 | tools/binman/btool/lzop.py 5 0 100% |
| 158 | tools/binman/btool/mkeficapsule.py 27 0 100% |
| 159 | tools/binman/btool/mkimage.py 23 0 100% |
| 160 | tools/binman/btool/openssl.py 42 0 100% |
| 161 | tools/binman/btool/xz.py 5 0 100% |
| 162 | tools/binman/btool/zstd.py 5 0 100% |
| 163 | tools/binman/cbfs_util.py 376 0 100% |
| 164 | tools/binman/cmdline.py 90 0 100% |
| 165 | tools/binman/control.py 409 0 100% |
| 166 | tools/binman/elf.py 241 0 100% |
| 167 | tools/binman/entry.py 548 0 100% |
| 168 | tools/binman/etype/alternates_fdt.py 58 0 100% |
| 169 | tools/binman/etype/atf_bl31.py 5 0 100% |
| 170 | tools/binman/etype/atf_fip.py 67 0 100% |
| 171 | tools/binman/etype/blob.py 49 0 100% |
| 172 | tools/binman/etype/blob_dtb.py 46 0 100% |
| 173 | tools/binman/etype/blob_ext.py 9 0 100% |
| 174 | tools/binman/etype/blob_ext_list.py 32 0 100% |
| 175 | tools/binman/etype/blob_named_by_arg.py 9 0 100% |
| 176 | tools/binman/etype/blob_phase.py 22 0 100% |
| 177 | tools/binman/etype/cbfs.py 101 0 100% |
| 178 | tools/binman/etype/collection.py 30 0 100% |
| 179 | tools/binman/etype/cros_ec_rw.py 5 0 100% |
| 180 | tools/binman/etype/efi_capsule.py 59 0 100% |
| 181 | tools/binman/etype/efi_empty_capsule.py 33 0 100% |
| 182 | tools/binman/etype/encrypted.py 34 0 100% |
| 183 | tools/binman/etype/fdtmap.py 62 0 100% |
| 184 | tools/binman/etype/files.py 35 0 100% |
| 185 | tools/binman/etype/fill.py 13 0 100% |
| 186 | tools/binman/etype/fit.py 311 0 100% |
| 187 | tools/binman/etype/fmap.py 37 0 100% |
| 188 | tools/binman/etype/gbb.py 37 0 100% |
| 189 | tools/binman/etype/image_header.py 53 0 100% |
| 190 | tools/binman/etype/intel_cmc.py 4 0 100% |
| 191 | tools/binman/etype/intel_descriptor.py 39 0 100% |
| 192 | tools/binman/etype/intel_fit.py 12 0 100% |
| 193 | tools/binman/etype/intel_fit_ptr.py 17 0 100% |
| 194 | tools/binman/etype/intel_fsp.py 4 0 100% |
| 195 | tools/binman/etype/intel_fsp_m.py 4 0 100% |
| 196 | tools/binman/etype/intel_fsp_s.py 4 0 100% |
| 197 | tools/binman/etype/intel_fsp_t.py 4 0 100% |
| 198 | tools/binman/etype/intel_ifwi.py 67 0 100% |
| 199 | tools/binman/etype/intel_me.py 4 0 100% |
| 200 | tools/binman/etype/intel_mrc.py 6 0 100% |
| 201 | tools/binman/etype/intel_refcode.py 6 0 100% |
| 202 | tools/binman/etype/intel_vbt.py 4 0 100% |
| 203 | tools/binman/etype/intel_vga.py 4 0 100% |
| 204 | tools/binman/etype/mkimage.py 84 0 100% |
| 205 | tools/binman/etype/null.py 9 0 100% |
| 206 | tools/binman/etype/nxp_imx8mcst.py 78 59 24% |
| 207 | tools/binman/etype/nxp_imx8mimage.py 38 6 84% |
| 208 | tools/binman/etype/opensbi.py 5 0 100% |
| 209 | tools/binman/etype/powerpc_mpc85xx_bootpg_resetvec.py 6 0 100% |
| 210 | tools/binman/etype/pre_load.py 76 0 100% |
| 211 | tools/binman/etype/rockchip_tpl.py 5 0 100% |
| 212 | tools/binman/etype/scp.py 5 0 100% |
| 213 | tools/binman/etype/section.py 418 0 100% |
| 214 | tools/binman/etype/tee_os.py 31 0 100% |
| 215 | tools/binman/etype/text.py 21 0 100% |
| 216 | tools/binman/etype/ti_board_config.py 139 0 100% |
| 217 | tools/binman/etype/ti_dm.py 5 0 100% |
| 218 | tools/binman/etype/ti_secure.py 65 0 100% |
| 219 | tools/binman/etype/ti_secure_rom.py 117 0 100% |
| 220 | tools/binman/etype/u_boot.py 7 0 100% |
| 221 | tools/binman/etype/u_boot_dtb.py 9 0 100% |
| 222 | tools/binman/etype/u_boot_dtb_with_ucode.py 51 0 100% |
| 223 | tools/binman/etype/u_boot_elf.py 19 0 100% |
| 224 | tools/binman/etype/u_boot_env.py 27 0 100% |
| 225 | tools/binman/etype/u_boot_expanded.py 4 0 100% |
| 226 | tools/binman/etype/u_boot_img.py 7 0 100% |
| 227 | tools/binman/etype/u_boot_nodtb.py 7 0 100% |
| 228 | tools/binman/etype/u_boot_spl.py 8 0 100% |
| 229 | tools/binman/etype/u_boot_spl_bss_pad.py 14 0 100% |
| 230 | tools/binman/etype/u_boot_spl_dtb.py 9 0 100% |
| 231 | tools/binman/etype/u_boot_spl_elf.py 8 0 100% |
| 232 | tools/binman/etype/u_boot_spl_expanded.py 12 0 100% |
| 233 | tools/binman/etype/u_boot_spl_nodtb.py 8 0 100% |
| 234 | tools/binman/etype/u_boot_spl_pubkey_dtb.py 32 0 100% |
| 235 | tools/binman/etype/u_boot_spl_with_ucode_ptr.py 8 0 100% |
| 236 | tools/binman/etype/u_boot_tpl.py 8 0 100% |
| 237 | tools/binman/etype/u_boot_tpl_bss_pad.py 14 0 100% |
| 238 | tools/binman/etype/u_boot_tpl_dtb.py 9 0 100% |
| 239 | tools/binman/etype/u_boot_tpl_dtb_with_ucode.py 8 0 100% |
| 240 | tools/binman/etype/u_boot_tpl_elf.py 8 0 100% |
| 241 | tools/binman/etype/u_boot_tpl_expanded.py 12 0 100% |
| 242 | tools/binman/etype/u_boot_tpl_nodtb.py 8 0 100% |
| 243 | tools/binman/etype/u_boot_tpl_with_ucode_ptr.py 12 0 100% |
| 244 | tools/binman/etype/u_boot_ucode.py 33 0 100% |
| 245 | tools/binman/etype/u_boot_vpl.py 8 0 100% |
| 246 | tools/binman/etype/u_boot_vpl_bss_pad.py 14 0 100% |
| 247 | tools/binman/etype/u_boot_vpl_dtb.py 9 0 100% |
| 248 | tools/binman/etype/u_boot_vpl_elf.py 8 0 100% |
| 249 | tools/binman/etype/u_boot_vpl_expanded.py 12 0 100% |
| 250 | tools/binman/etype/u_boot_vpl_nodtb.py 8 0 100% |
| 251 | tools/binman/etype/u_boot_with_ucode_ptr.py 42 0 100% |
| 252 | tools/binman/etype/vblock.py 38 0 100% |
| 253 | tools/binman/etype/x86_reset16.py 7 0 100% |
| 254 | tools/binman/etype/x86_reset16_spl.py 7 0 100% |
| 255 | tools/binman/etype/x86_reset16_tpl.py 7 0 100% |
| 256 | tools/binman/etype/x86_start16.py 7 0 100% |
| 257 | tools/binman/etype/x86_start16_spl.py 7 0 100% |
| 258 | tools/binman/etype/x86_start16_tpl.py 7 0 100% |
| 259 | tools/binman/etype/x509_cert.py 71 0 100% |
| 260 | tools/binman/etype/xilinx_bootgen.py 72 0 100% |
| 261 | tools/binman/fip_util.py 202 0 100% |
| 262 | tools/binman/fmap_util.py 49 0 100% |
| 263 | tools/binman/image.py 181 0 100% |
| 264 | tools/binman/state.py 201 0 100% |
| 265 | --------------------------------------------------------------------------- |
| 266 | TOTAL 5954 69 99% |
| 267 | |
| 268 | To get a report in 'htmlcov/index.html', type: python3-coverage html |
| 269 | Coverage error: 99%, but should be 100% |
| 270 | ValueError: Test coverage failure |
| 271 | |
| 272 | Unfortunately the run failed. As it suggests, create a report: |
| 273 | |
| 274 | .. code-block:: bash |
| 275 | |
| 276 | $ python3-coverage html |
| 277 | Wrote HTML report to htmlcov/index.html |
| 278 | |
| 279 | If you open that file in the browser, you can see which files are not reaching |
| 280 | 100% and click on them. Here is ``nxp_imx8mimage.py``, for example: |
| 281 | |
| 282 | .. code-block:: python |
| 283 | |
| 284 | 43 # Generate mkimage configuration file similar to imx8mimage.cfg |
| 285 | 44 # and pass it to mkimage to generate SPL image for us here. |
| 286 | 45 cfg_fname = tools.get_output_filename('nxp.imx8mimage.cfg.%s' % uniq) |
| 287 | 46 with open(cfg_fname, 'w') as outf: |
| 288 | 47 print('ROM_VERSION v%d' % self.rom_version, file=outf) |
| 289 | 48 print('BOOT_FROM %s' % self.boot_from, file=outf) |
| 290 | 49 print('LOADER %s %#x' % (input_fname, self.loader_address), file=outf) |
| 291 | 50 |
| 292 | 51 output_fname = tools.get_output_filename(f'cfg-out.{uniq}') |
| 293 | 52 args = ['-d', input_fname, '-n', cfg_fname, '-T', 'imx8mimage', |
| 294 | 53 output_fname] |
| 295 | 54 if self.mkimage.run_cmd(*args) is not None: |
| 296 | 55 return tools.read_file(output_fname) |
| 297 | 56 else: |
| 298 | 57 # Bintool is missing; just use the input data as the output |
| 299 | 58 x self.record_missing_bintool(self.mkimage) |
| 300 | 59 x return data |
| 301 | 60 |
| 302 | 61 def SetImagePos(self, image_pos): |
| 303 | 62 # Customized SoC specific SetImagePos which skips the mkimage etype |
| 304 | 63 # implementation and removes the 0x48 offset introduced there. That |
| 305 | 64 # offset is only used for uImage/fitImage, which is not the case in |
| 306 | 65 # here. |
| 307 | 66 upto = 0x00 |
| 308 | 67 for entry in super().GetEntries().values(): |
| 309 | 68 x entry.SetOffsetSize(upto, None) |
| 310 | 69 |
| 311 | 70 # Give up if any entries lack a size |
| 312 | 71 x if entry.size is None: |
| 313 | 72 x return |
| 314 | 73 x upto += entry.size |
| 315 | 74 |
| 316 | 75 Entry_section.SetImagePos(self, image_pos) |
| 317 | |
| 318 | Most of the file is covered, but the lines marked with ``x`` indicate missing |
| 319 | coverage. The will show up red in your browser. |
| 320 | |
| 321 | What is a test? |
| 322 | --------------- |
| 323 | |
| 324 | A test is a function in ``ftest.py`` which uses an image description in |
| 325 | ``tools/binman/test`` to perform some operations and exercise the code. Some |
| 326 | tests are just a few lines; some are more complicated. |
| 327 | |
| 328 | Here is a simple test: |
| 329 | |
| 330 | .. code-block:: python |
| 331 | |
| 332 | def testSimple(self): |
| 333 | """Test a simple binman with a single file""" |
| 334 | data = self._DoReadFile('005_simple.dts') |
| 335 | self.assertEqual(U_BOOT_DATA, data) |
| 336 | |
| 337 | This test tells Binman to build an image using the description. Then it checks |
| 338 | that the resulting image looks correct. The image description is: |
| 339 | |
| 340 | .. code-block:: devicetree |
| 341 | |
| 342 | /dts-v1/; |
| 343 | |
| 344 | / { |
| 345 | #address-cells = <1>; |
| 346 | #size-cells = <1>; |
| 347 | |
| 348 | binman { |
| 349 | u-boot { |
| 350 | }; |
| 351 | }; |
| 352 | }; |
| 353 | |
| 354 | As you will know from the Binman documentation, this says that there is |
| 355 | one image and it contains the U-Boot binary. So this test builds an image |
| 356 | consisting of a U-Boot binary, then checks that it does indeed have just a |
| 357 | U-Boot binary in it. |
| 358 | |
| 359 | Test data |
| 360 | --------- |
| 361 | |
| 362 | Using real binaries (like ``u-boot.bin``) to test Binman would be quite tedious. |
| 363 | Every output file would be large and it would be hard to tell by looking at the |
| 364 | output (e.g. with a hex dump) if a particular entry contains ``u-boot.bin`` or |
| 365 | ``u-boot-spl.bin`` or something else. |
| 366 | |
| 367 | Binman gets around this by using simple placeholders. Here is the placeholder |
| 368 | for u-boot.bin: |
| 369 | |
| 370 | .. code-block:: python |
| 371 | |
| 372 | U_BOOT_DATA = b'1234' |
| 373 | |
| 374 | This is just bytes. So the test above checks that the output image contains |
| 375 | these four bytes. This makes verification fast for Binman and very easy for |
| 376 | humans. |
| 377 | |
| 378 | Even the devicetree is a placeholder: |
| 379 | |
| 380 | .. code-block:: python |
| 381 | |
| 382 | U_BOOT_DTB_DATA = b'udtb' |
| 383 | |
| 384 | But for some tests you need to use the real devicetree. In that case you can |
| 385 | use ``_DoReadFileRealDtb()``. See ``testUpdateFdtAll()`` for an example of how |
| 386 | to check the devicetree updated by Binman. |
| 387 | |
| 388 | Test structure |
| 389 | -------------- |
| 390 | |
| 391 | Each test is designed to test just one thing. Binman tests are named according |
| 392 | to what they are testing. Individually they don't do very much, but as a whole |
| 393 | they test every line of code in Binman. |
| 394 | |
| 395 | So ``testSimple()`` is designed to check that Binman can build the |
| 396 | simplest-possible image that isn't completely empty. |
| 397 | |
| 398 | Another type of test is one which checks error-handling, for example: |
| 399 | |
| 400 | .. code-block:: python |
| 401 | |
| 402 | def testFillNoSize(self): |
| 403 | """Test for an fill entry type with no size""" |
| 404 | with self.assertRaises(ValueError) as e: |
| 405 | self._DoReadFile('070_fill_no_size.dts') |
| 406 | self.assertIn("'fill' entry is missing properties: size", |
| 407 | str(e.exception)) |
| 408 | |
| 409 | This test deliberately tries to provoke an error. The image description is: |
| 410 | |
| 411 | .. code-block:: devicetree |
| 412 | |
| 413 | // SPDX-License-Identifier: GPL-2.0+ |
| 414 | /dts-v1/; |
| 415 | |
| 416 | / { |
| 417 | #address-cells = <1>; |
| 418 | #size-cells = <1>; |
| 419 | |
| 420 | binman { |
| 421 | size = <16>; |
| 422 | fill { |
| 423 | fill-byte = [ff]; |
| 424 | }; |
| 425 | }; |
| 426 | }; |
| 427 | |
| 428 | You can see that there is no size for the 'fill' entry, so we would expect |
| 429 | Binman to complain. The test checks that it actually does. It also checks the |
| 430 | error message produced by Binman. Sometimes you need to add several tests, each |
| 431 | with their own broken image description, in order to check all the error cases. |
| 432 | |
| 433 | Sometimes you need to capture the console output of Binman, to check it is |
| 434 | correct. You can to this with ``test_util.capture_sys_output()``, for example: |
| 435 | |
| 436 | .. code-block:: python |
| 437 | |
| 438 | with test_util.capture_sys_output() as (_, stderr): |
| 439 | self._DoTestFile('071_gbb.dts', force_missing_bintools='futility', |
| 440 | entry_args=entry_args) |
| 441 | err = stderr.getvalue() |
| 442 | self.assertRegex(err, "Image 'image'.*missing bintools.*: futility") |
| 443 | |
| 444 | The test collects the output and checks it with a regular expression. If you |
| 445 | need to see the test output (e.g. to debug it), you will have to remove that |
| 446 | capture line. |
| 447 | |
| 448 | How to add a new test |
| 449 | --------------------- |
| 450 | |
| 451 | This section explains the process of writing a new test. It uses an example to |
| 452 | help with this, but your code will be different. |
| 453 | |
| 454 | Generally you are adding a test because you are adding a new entry type |
| 455 | ('etype'). So start by creating the shortest and simplest image-description you |
| 456 | can, which contains the new etype. Put it in a numbered file in |
| 457 | ``tool/binman/test`` so that it comes last. All the numbers are unique and there |
| 458 | are no gaps. |
| 459 | |
| 460 | Example from ``tools/binman/test/339_nxp_imx8.dts``: |
| 461 | |
| 462 | .. code-block:: devicetree |
| 463 | |
| 464 | // SPDX-License-Identifier: GPL-2.0+ |
| 465 | |
| 466 | /dts-v1/; |
| 467 | |
| 468 | / { |
| 469 | #address-cells = <1>; |
| 470 | #size-cells = <1>; |
| 471 | |
| 472 | binman { |
| 473 | nxp-imx8mimage { |
| 474 | args; /* TODO: Needed by mkimage etype superclass */ |
| 475 | nxp,boot-from = "sd"; |
| 476 | nxp,rom-version = <1>; |
| 477 | nxp,loader-address = <0x10>; |
| 478 | }; |
| 479 | }; |
| 480 | }; |
| 481 | |
| 482 | Note that you should use tabs in the file, not spaces. You can see that this has |
| 483 | been cut down to the bare minimum, just enough to include the etype and the |
| 484 | arguments it needs. This is of course not a real image. It will not boot on |
| 485 | anything. But that's fine; we are just trying to test this one etype. Try not |
| 486 | to add any other sections and etypes unless they are absolutely essential for |
| 487 | your test to work. This helps others too: they don't need to understand the full |
| 488 | complexity of your etype just to read your test. |
| 489 | |
| 490 | Then create your test by adding a new function at the end of ``ftest.py``: |
| 491 | |
| 492 | .. code-block:: python |
| 493 | |
| 494 | def testNxpImx8Image(self): |
| 495 | """Test that binman can produce an iMX8 image""" |
| 496 | self._DoTestFile('339_nxp_imx8.dts') |
| 497 | |
| 498 | This uses the test file that you created. It doesn't check anything, it just |
| 499 | runs the image description through binman. |
| 500 | |
| 501 | Let's run it: |
| 502 | |
| 503 | .. code-block:: bash |
| 504 | |
| 505 | $ binman test testNxpImx8Image |
| 506 | ======================== Running binman tests ======================== |
| 507 | . |
| 508 | ---------------------------------------------------------------------- |
| 509 | Ran 1 test in 0.242s |
| 510 | |
| 511 | OK |
| 512 | |
| 513 | So the test passes. It doesn't really do a lot, but it does exercise the etype. |
| 514 | The next step is to update it to actually check the output: |
| 515 | |
| 516 | .. code-block:: python |
| 517 | |
| 518 | def testNxpImx8Image(self): |
| 519 | """Test that binman can produce an iMX8 image""" |
| 520 | data = self._DoReadFile('339_nxp_imx8.dts') |
| 521 | print('data', len(data)) |
| 522 | |
| 523 | The ``_DoReadFile()`` function is documented in the code. It returns the image |
| 524 | contents as the first part of a tuple. |
| 525 | |
| 526 | Running this we see: |
| 527 | |
| 528 | .. code-block:: bash |
| 529 | |
| 530 | data 2200 |
| 531 | |
| 532 | So it is producing a little over 8K of data. Your etype will be different, but |
| 533 | in any case you can add Python code to check that this data is actually correct, |
| 534 | based on your knowledge of your etype. Note that you should not be checking |
| 535 | whether the external tools (called 'bintools' in Binman) are actually working, |
| 536 | since presumably they have their own tests. You just need to check that the |
| 537 | image seems reasonable, e.g. is not empty, contains the expected sections, etc. |
| 538 | |
| 539 | When your etype does use a bintool, it also needs tests, but generally it will |
| 540 | be tested by virtue of the etype test. This is because your etype must call the |
| 541 | bintool to create the image. Sometimes you might need to add a test for a |
| 542 | bintool error-condition, though. |
| 543 | |
| 544 | Finishing code coverage |
| 545 | ----------------------- |
| 546 | |
| 547 | The objective is to have test-coverage for every line of code that you add to |
| 548 | Binman. So how can you tell? First, get a coverage report as described above. |
| 549 | Look through the output for any files which are not at 100%. Add more test cases |
| 550 | (image descriptions and new functions in ``ftest.py``) until you have covered |
| 551 | each line. |
| 552 | |
| 553 | In the above example, here are some possible steps: |
| 554 | |
| 555 | #. The first red bit is where the ``mkimage`` call returns None. This can be |
| 556 | traced to ``Bintoolmkimage.mkimage()`` which calls |
| 557 | ``Bintool.run_cmd_result()`` and ``None`` means that ``mkimage`` is missing. |
| 558 | So the etype has code to handle that case, but it is never used. You can |
| 559 | look for other examples of ``self.mkimage`` returning ``None`` - e.g. |
| 560 | ``Entry_mkimage.BuildSectionData()`` does this. The clue for finding this is |
| 561 | that the ``nxp-imx8mimage`` etype is based on ``Entry_mkimage``: |
| 562 | |
| 563 | .. code-block:: python |
| 564 | |
| 565 | class Entry_nxp_imx8mimage(Entry_mkimage): |
| 566 | |
| 567 | It must be tested somewhere...in this case ``testMkimage()`` doesn't do it, |
| 568 | but ``testMkimageMissing()`` immediately below that does. So you can create a |
| 569 | similar test, e.g.: |
| 570 | |
| 571 | .. code-block:: python |
| 572 | |
| 573 | def testNxpImx8ImageMkimageMissing(self): |
| 574 | """Test that binman can produce an iMX8 image""" |
| 575 | with test_util.capture_sys_output() as (_, stderr): |
| 576 | self._DoTestFile('339_nxp_imx8.dts', |
| 577 | force_missing_bintools='mkimage') |
| 578 | err = stderr.getvalue() |
| 579 | self.assertRegex(err, "Image 'image'.*missing bintools.*: mkimage") |
| 580 | |
| 581 | Note that this uses exactly the same image description as the first test. |
| 582 | It just checks what happens when the tool is missing. Checking the coverage |
| 583 | again, you will see that the first red bit has gone: |
| 584 | |
| 585 | .. code-block:: bash |
| 586 | |
| 587 | $ binman test -T |
| 588 | $ python3-coverage html |
| 589 | |
| 590 | #. The second red bit is for ``SetImagePos()``. You can see that it is iterating |
| 591 | through the sub-entries inside the ``nxp-imx8mimage`` entry. In the case of |
| 592 | the 339 file, there are no such entries, so this code inside the for() loop |
| 593 | isn't used: |
| 594 | |
| 595 | .. code-block:: python |
| 596 | |
| 597 | def SetImagePos(self, image_pos): |
| 598 | # Customized SoC specific SetImagePos which skips the mkimage etype |
| 599 | # implementation and removes the 0x48 offset introduced there. That |
| 600 | # offset is only used for uImage/fitImage, which is not the case in |
| 601 | # here. |
| 602 | upto = 0x00 |
| 603 | for entry in super().GetEntries().values(): |
| 604 | entry.SetOffsetSize(upto, None) |
| 605 | |
| 606 | # Give up if any entries lack a size |
| 607 | if entry.size is None: |
| 608 | return |
| 609 | upto += entry.size |
| 610 | |
| 611 | Entry_section.SetImagePos(self, image_pos) |
| 612 | |
| 613 | The solution is to add an entry, e.g. in ``340_nxp_imx8_non_empty.dts``: |
| 614 | |
| 615 | .. code-block:: devicetree |
| 616 | |
| 617 | // SPDX-License-Identifier: GPL-2.0+ |
| 618 | |
| 619 | /dts-v1/; |
| 620 | |
| 621 | / { |
| 622 | #address-cells = <1>; |
| 623 | #size-cells = <1>; |
| 624 | |
| 625 | binman { |
| 626 | nxp-imx8mimage { |
| 627 | args; /* TODO: Needed by mkimage etype superclass */ |
| 628 | nxp,boot-from = "sd"; |
| 629 | nxp,rom-version = <1>; |
| 630 | nxp,loader-address = <0x10>; |
| 631 | |
| 632 | u-boot { |
| 633 | }; |
| 634 | }; |
| 635 | }; |
| 636 | }; |
| 637 | |
| 638 | Now write a little test to use it: |
| 639 | |
| 640 | .. code-block:: python |
| 641 | |
| 642 | def testNxpImx8ImageNonEmpty(self): |
| 643 | """Test that binman can produce an iMX8 image with something in it""" |
| 644 | data = self._DoReadFile('340_nxp_imx8_non_empty.dts') |
| 645 | # check data here |
| 646 | |
| 647 | With that, the second red bit goes away, because the for() loop is now used. |
| 648 | |
| 649 | #. There is one more red bit left, the ``return`` in ``SetImagePos()``. The |
| 650 | above effort got the for() loop to be executed, but it doesn't cover the |
| 651 | ``return``. It might have been copied from some other etype, e.g. the mkimage |
| 652 | one. See ``Entry_mkimage.SetImagePos()`` which contains the code: |
| 653 | |
| 654 | .. code-block:: python |
| 655 | |
| 656 | for entry in self.GetEntries().values(): |
| 657 | entry.SetOffsetSize(upto, None) |
| 658 | |
| 659 | # Give up if any entries lack a size |
| 660 | if entry.size is None: |
| 661 | return |
| 662 | upto += entry.size |
| 663 | |
| 664 | But which test covers that code for mkimage? By figuring that out, you could |
| 665 | use a similar technique. One way to find out is to delete the two lines in |
| 666 | ``Entry_mkimage`` which check for entry.size being None and returning, then |
| 667 | see what breaks with ``binman test``: |
| 668 | |
| 669 | .. code-block:: bash |
| 670 | |
| 671 | ERROR: binman.ftest.TestFunctional.testMkimageCollection (subunit.RemotedTestCase) |
| 672 | binman.ftest.TestFunctional.testMkimageCollection |
| 673 | ---------------------------------------------------------------------- |
| 674 | testtools.testresult.real._StringException: Traceback (most recent call last): |
| 675 | TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType' |
| 676 | |
| 677 | ====================================================================== |
| 678 | ERROR: binman.ftest.TestFunctional.testMkimageImage (subunit.RemotedTestCase) |
| 679 | binman.ftest.TestFunctional.testMkimageImage |
| 680 | ---------------------------------------------------------------------- |
| 681 | testtools.testresult.real._StringException: Traceback (most recent call last): |
| 682 | TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType' |
| 683 | |
| 684 | ====================================================================== |
| 685 | ERROR: binman.ftest.TestFunctional.testMkimageSpecial (subunit.RemotedTestCase) |
| 686 | binman.ftest.TestFunctional.testMkimageSpecial |
| 687 | ---------------------------------------------------------------------- |
| 688 | testtools.testresult.real._StringException: Traceback (most recent call last): |
| 689 | TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType' |
| 690 | |
| 691 | We can verify that you got the right test, by putting the lines back in and |
| 692 | getting coverage for just that test: |
| 693 | |
| 694 | .. code-block:: bash |
| 695 | |
| 696 | binman test -T testMkimageCollection |
| 697 | python3-coverage html |
| 698 | |
| 699 | You will see a lot of red since we are seeing test coverage just for one |
| 700 | test, but if you look in ``mkimage.py`` at ``SetImagePos()`` you will see |
| 701 | that the ``return`` is covered (i.e. it is marked green). |
| 702 | |
| 703 | Looking at the ``.dts`` files for each of these tests, none jumps out as |
| 704 | being relevant to our case. It seems that this code just isn't needed, so the |
| 705 | best solution is to delete those two lines from the function: |
| 706 | |
| 707 | .. code-block:: python |
| 708 | |
| 709 | def SetImagePos(self, image_pos): |
| 710 | # Customized SoC specific SetImagePos which skips the mkimage etype |
| 711 | # implementation and removes the 0x48 offset introduced there. That |
| 712 | # offset is only used for uImage/fitImage, which is not the case in |
| 713 | # here. |
| 714 | upto = 0x00 |
| 715 | for entry in super().GetEntries().values(): |
| 716 | entry.SetOffsetSize(upto, None) |
| 717 | upto += entry.size |
| 718 | |
| 719 | Entry_section.SetImagePos(self, image_pos) |
| 720 | |
| 721 | We should check the updated code on a real build, to make sure it really |
| 722 | isn't needed, of course. |
| 723 | |
| 724 | Now, the test coverage is complete! |
| 725 | |
| 726 | If we later discover a case where those lines are needed, we can add the |
| 727 | lines back, along with a test for this case. |
| 728 | |
| 729 | Getting help |
| 730 | ------------ |
| 731 | |
| 732 | If you are stuck and cannot work out how to add test coverage for your entry |
| 733 | type, ask on the U-Boot mailing list, cc ``Simon Glass <sjg@chromium.org>`` or |
| 734 | on irc ``sjg1`` |