Previously I discussed using qemu user mode to run cross-compiled binaries, now let’s put a few things together and run unit tests automatically with automake’s make check. Then we’ll integrate everything into a build system (buildroot) and automatically run cross-compiled tests as part of the build process. This has a number of advantages including:

  • no need to build for both x86 and ARM
  • no need to deploy cross-compiled unit tests to a real target or even a qemu-system-arm machine, they’ll just run on your machine
  • easy integration and automation in your build system without requiring any additional steps or configurations

Many thanks to Andrey Smirnov who explored this further after discussing qemu user mode with me and figured all of this out (I’m mostly just writing up notes on what he did).

Running tests with automake

automake has a feature called LOG_COMPILER as part of its Parallel Test Harness infrastructure. If this variable is set, tests are executed with the LOG_COMPILER as a wrapper. The Parallel Test Harness documentation gives perl as an example wrapper but we can take advantage of this feature to wrap our tests with a shell script calling out to qemu in order to run them directly on our build machine.

We can capture and set this variable from the environment in configure.ac, for instance:

AC_SUBST([LOG_COMPILER], ${LOG_COMPILER})

It could be set like this when invoking configure:

LOG_COMPILER=qemu-wrapper.sh ./configure

Of course we’d also include our cross-compilation options as well. We can then run make and make check and we should see that the tests invoked by make check are now run using qemu-wrapper.sh. That in turn needs to be some shell script that runs its first command-line argument using qemu-arm with appropriate options (such as -L) set.

Running tests with buildroot

Now let’s put everything together into a buildroot recipe. I assume you’re familiar with how buildroot works (if not, please consult the buildroot manual and familiarize yourself with how external.mk and recipes work).

Add a test runner

We need something simple to point LOG_COMPILER to so we can add that as a shell script somewhere in your external/ tree. For example, external/test-runner.sh could look like:

#!bin/sh

exec ${HOST_DIR}/usr/bin/qemu-arm -L ${STAGING_DIR} $1

Keep in mind that HOST_DIR points to your build output’s host directory, containing the host’s toolchain and, in this case, also the host-qemu executible. Meanwhile the STAGING_DIR gives us exactly what we need for qemu’s -L option.

We can then create a variable that points to this script. For instance, add:

TEST_RUNNER := $(BR2_EXTERNAL)/test-runner.sh

to external.mk. Now package recipes have access to the test-runner.sh script via TEST_RUNNER.

Making sure qemu-arm is available

Enable qemu from the Host Utilities menu. Specifically you want to turn on:

  • host qemu
  • Enable Linux user-land emulation

You don’t need “Enable system emulation” unless you want to use qemu’s machine emulator mode.

Packages needing this qemu-based test infrastructure simply need to select BR2_PACKAGE_HOST_QEMU as a dependency in their Config.in. For example,

config BR2_PACKAGE_EXAMPLE_APP
    bool "example-app"
    select BR2_PACKAGE_HOST_QEMU
    help
      This is a our example app.

They also need to call out host-qemu as a dependency in their recipe, for example for in example-app.mk, we could have:

EXAMPLE_APP_DEPENDENCIES = host-qemu

Now when example-app is built, buildroot will build and deploy qemu-arm (to host).

Adding test hooks to a recipe

Given a typical buildroot recipe for our example app, example-app.mk, all we need to do is set LOG_COMPILER for automake and tell buildroot to run make check as a post-build step. Setting LOG_COMPILER is easy by adding to buildroot’s <package>_CONF_ENV variable:

EXAMPLE_APP_CONF_ENV += LOG_COMPILER=$(TEST_RUNNER)

Next, we add a post-build hook:

define example-app-make-check
$(MAKE) -C $(@D) check
endef

EXAMPLE_APP_POST_BUILD_HOOKS += example-app-make-check

Now we should see two things:

  • buildroot will add our LOG_COMPILER variable to the environment when running configure in this package
  • having built the package, buildroot will execute make check in the package build directory and that in turn will run any tests the package provides with qemu-arm

To try this out, build the package:

make example-app

You should hopefully see the familiar make check output after the application is built. Running file on binaries in the build directory should reveal that they are in fact ARM binaries!

A failed unit test in the above example will break the build for that package. You can adjust the implementation slightly if that’s not what you want and you could even introduce a configuration option in your external/Config.in menu to make tests optional or control whether test failures break the build, as usual there is a lot that can be customized with buildroot.

Specifying which tests to run in a recipe

We may not want to run every test in the build system. Generally speaking, we can run most unit tests but we will need to skip shell-based functional tests. automake provides a way to do that with the TESTS environment variable. For example, if we want to run only “test1” and “test2” we can specify them like this:

define example-app-make-check
TESTS="test1 test2" $(MAKE) -C $(@D) -e check
endef

EXAMPLE_APP_POST_BUILD_HOOKS += example-app-make-check

The -e argument tells make to override variables from the environment and we override TESTS (the variable calling out which tests to run) with our own list. Now make check will run just those tests.

qemu and kernel version

The recipe the builds qemu in buildroot, package/qemu/qemu.mk, performs a check comparing the host’s kernel version to the target’s and will only succeed if the host version is greater than or equal to the target’s. You may find that buildroot refuses to build qemu-arm for you. The reasoning is explained in qemu.mk: in user mode, qemu translates system calls, so it’s assumed that the target does not make calls that don’t exist on your host machine.

If this is a problem, you can defeat this check (HOST_QEMU_COMPARE_VERSION) at your own risk or upgrade your host machine if possible. Alternately, you can use your own qemu rather than buildroot’s (for example a distribution package or your own build). Generally speaking, it’s unlikely that a unit test is going to make system calls that don’t exist on your host machine but of course it depends on what you’re testing and anything is possible.