Testing and Quality Assurance

How Yocto build QA, package tests, runtime image tests, and product validation fit together

The image built successfully. What does that actually prove?

It proves that BitBake could parse the metadata, run the required tasks, and produce the requested artifacts. That is necessary. It is not the same as proving that the generated product is correct, that the package metadata is clean, that the services start, or that the hardware behaves as expected.

That distinction matters on real projects. A build can complete while hiding a bad packaging decision, a disabled service, an image that only works under QEMU, or an application that starts but cannot do anything useful. Yocto gives us several places to catch those problems, but each one answers a different question.

Recipe
  -> Build
  -> Package
  -> Package QA
  -> Image
  -> Runtime tests
  -> Product validation

This lesson is about that chain. It is not a full testing course, and it is not a catalogue of every QA check OpenEmbedded can run. The useful skill is knowing what kind of evidence you have, what it proves, and where to look when it fails.

Build-Time QA

OpenEmbedded performs package QA checks as part of the normal build. These checks are implemented through insane.bbclass, which adds quality checks to the package generation process. In practice, you will often see the results around do_package or do_package_qa.

The name is memorable, if not especially gentle. The checks are not there to annoy you. They catch common problems in the packaged output before those problems become runtime failures or strange release artifacts.

Representative QA problems include:

  • files installed under ${D} but not included in any package
  • files installed into unexpected locations
  • incorrect ownership or permissions
  • runtime dependency problems
  • shared library packaging issues
  • target binaries with the wrong architecture
  • binaries that have already been stripped
  • patch warnings where source changes are not being applied cleanly

You do not need to memorise every check name. The important habit is to read the message as a clue about the metadata. A QA warning is usually telling you that the files produced by the recipe do not match what the recipe metadata says should happen.

For example, an installed-but-not-shipped error is not really about the error text. It means a file exists in the recipe’s staged install tree, but no package claimed it through FILES. The debugging path is:

  1. find the package and check name in the QA message
  2. inspect the path under the recipe work directory
  3. decide whether the file should exist
  4. fix the install path, FILES, PACKAGES, or dependency metadata as needed

That connects directly to the packaging lesson: Recipes, Packages and Package Splitting. For general task logs and work directories, use the workflow from Debugging Yocto Builds.

INSANE_SKIP Is Not a Fix

INSANE_SKIP tells Yocto not to run a particular QA check for a particular package. It does not fix the underlying issue.

That distinction is important enough to be slightly blunt about. If a QA check reports a real packaging problem and you add INSANE_SKIP, you have not made the package correct. You have made Yocto stop telling you that it is suspicious.

There are legitimate uses. Some products include third-party binary-only software where, for example, an unstripped binary is not available. In that case, skipping a specific check for a specific package may be a deliberate product decision. It should still be documented in the recipe or in the review history.

It should not normally be the first response to a QA failure.

A Small QA Example

Suppose a recipe installs a helper script like this:

do_install() {
    install -d ${D}${libexecdir}/myproduct
    install -m 0755 ${S}/factory-reset ${D}${libexecdir}/myproduct/factory-reset
}

The build then fails during packaging with an installed-but-not-shipped error for /usr/libexec/myproduct/factory-reset.

The tempting response is to hide the check:

# Bad response: this hides the symptom rather than fixing ownership.
INSANE_SKIP:${PN} += "installed-vs-shipped"

That is the wrong fix. The file has been installed, and the package metadata does not say which package owns it. If the file belongs in the main package, say so:

FILES:${PN} += "${libexecdir}/myproduct/factory-reset"

Now the metadata matches the installed output. The package QA check can keep running, and the package ownership is clear.

The same reasoning applies to other QA messages. If a shared library is in the wrong package, fix the package split. If a runtime dependency is missing, fix the runtime dependency. If a file has the wrong mode, fix the install command. Only skip the check after you understand why the check is not appropriate for that specific package.

Package Tests with ptest

Package QA checks the shape of the generated packages. It does not run the software and decide whether it behaves correctly. For that, some components can ship target-side tests using ptest.

A ptest packages tests with the component that they test. A recipe that supports ptest normally inherits ptest.bbclass:

inherit ptest

The recipe may also need to build test binaries, install test data, or provide a run-ptest script. Once included in an image, ptest files are normally installed under a directory such as:

/usr/lib/<package>/ptest

The ptest-runner package then runs the installed ptest suites on the target. For an image used for package testing, you might see:

DISTRO_FEATURES:append = " ptest"
EXTRA_IMAGE_FEATURES += "ptest-pkgs"
IMAGE_INSTALL:append = " ptest-runner"

That belongs in a test or CI image, not usually in the production image you ship. ptest packages add size, test dependencies, and extra files that a deployed product normally does not need.

ptest is useful when a library, command-line tool, or service already has a reasonable target-side test suite. It is less useful when the component only works with real peripherals, requires complex external systems, or has tests that are unreliable in an embedded environment. Flaky ptests are still flaky tests; putting them in Yocto does not make them noble.

Image and Runtime Testing

There are several different levels of testing in a Yocto product, and it is worth keeping them separate.

Recipe and package QA

Checks whether the recipe output and generated packages obey expected packaging rules.

Package-level runtime tests

Runs tests that were packaged with a component, commonly through ptest.

Image tests

Boots or accesses an assembled image and checks basic runtime behaviour.

Product and system tests

Checks whether the actual product satisfies its requirements on representative hardware.

The testimage class is Yocto’s standard entry point for automated runtime image testing. For QEMU-capable machines, it can boot the image and run tests through the OpenEmbedded test framework. The tests run against the target system, often over SSH, which means they can check behaviour that packaging QA cannot see.

A common manual setup is:

IMAGE_CLASSES += "testimage"

Then run:

bitbake -c testimage my-image

In CI you may instead see:

TESTIMAGE_AUTO = "1"

That asks the tests to run automatically after the image has been created.

Image tests are good for smoke testing. They can check that the image boots, that networking works well enough for the test environment, that expected services are running, that an application responds, or that the package manager behaves as expected in images where runtime package management is enabled.

QEMU is particularly useful here because it gives fast feedback. It is also not a real board. Anything involving device trees, bootloader interaction, firmware, storage timing, field buses, radios, sensors, power behaviour, or product enclosure assumptions still needs hardware validation.

What This Looks Like in a Real Project

A production project usually ends up with several images or build modes.

A development image might contain SSH access, debug tools, package manager support, ptest packages, test users, and logging that would be inappropriate in a released product. That image is useful for engineering work, but it is not the same artifact as the production image.

A CI pipeline might build both:

  • a test image, used for QEMU boot checks and ptests
  • a production image, used for release artifact generation and final validation

The important point is to record which one was tested. “CI passed” is much less useful than:

image: my-product-image
machine: myboard
distro: mydistro
version: 2.3.0
tests: package QA, QEMU smoke tests, hardware boot test

That record does not have to be elaborate. It just needs to be good enough that six months later you can tell what was built, what was tested, and where the gaps were.

What Different Tests Prove

Different tests prove different things.

  • “BitBake completed” means the build completed.
  • Package QA checks whether generated packages obey expected rules.
  • Runtime tests check behaviour.
  • Product testing determines whether the actual system satisfies its requirements.

Confusing these is how projects accidentally treat a successful build as a validated product. Yocto can give you useful evidence at several points in the chain, but it cannot decide what your product requirements are.

What to Check in a Real Project

When reviewing a Yocto product, I would normally check:

  • whether QA warnings have been fixed or merely suppressed with INSANE_SKIP
  • whether any suppressed QA checks are narrow and documented
  • whether important components have package or runtime tests
  • which tests run in CI for normal changes
  • which checks run against QEMU images
  • which validation steps require actual hardware
  • whether the release record says exactly which image was tested

That is the engineering message of this lesson. A successful build is the start of the evidence, not the end of the argument.

Quick quiz: testing and QA

Check what different Yocto checks and tests actually prove.

Question 1The image built successfully. What has that proved?
Question 2A package QA check reports an installed-but-not-shipped file. What is the better first response?
Question 3What does `INSANE_SKIP` do?
Question 4Which distinction best matches the lesson's testing model?