⟵ All articles

On-device continuous integration tests with Lager and Github Actions

November 23, 2020

Image credit: Vac Expert

In this blog post we'll show you how you can use Lager and Github Actions to easily set up cloud-based builds that run on-device continuous integration tests.  For this example we'll be using the https://github.com/lagerdata/demo-nrf52840-dk repo, which is a small sample project for the nRF52840-DK evaluation board.

To create a Github action, we simply need to create a directory in the top-level of the repo called .github/workflows, and place a YAML file there describing our workflow. For this example, we'll be using a workflow called build-flash-and-run-simple.yml which demonstrates how to set up a simple, straightforward CI pipeline.

name: Simple pipeline to build firmware, flash DUT, and run tests
on: [push]
env:
  LAGER_GATEWAY: helpful-horse

jobs:
  build_flash_and_run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: 'recursive'

      - name: Build
        uses: docker://lagerdata/devenv-cortexm@sha256:2742d3fc1d3794d84afbe5aee79a1854a9079a2468d26d083f5d67c98435db3a
        with:
          entrypoint: /usr/local/bin/lager
          args: exec make

      - name: Connect to debugger
        uses: docker://lagerdata/lager-cli:v0.1.51
        env:
          LAGER_SECRET_TOKEN: ${{ secrets.AKBAR_SECRET_TOKEN }}
        with:
          entrypoint: /usr/local/bin/lager
          args: connect --device nrf52 --force

      - name: Flash and run
        uses: docker://lagerdata/lager-cli:v0.1.51
        env:
          LAGER_SECRET_TOKEN: ${{ secrets.AKBAR_SECRET_TOKEN }}
        with:
          entrypoint: /usr/local/bin/lager
          args: testrun --serial-device /dev/ttyACM0 --hexfile _build/unit-tests/test-suites/test-example/test-example.hex

Up top we have the name of the workflow which will be displayed in Github's UI, the events that should trigger it (in this example, a push to the repo), and finally some global environment variables we want to set. In this case we set LAGER_GATEWAY to the value helpful-horse. This will be used by the lager-cli tool to determine which of your gateways to talk to. If you only have one you can omit this variable.

Next we have our jobs definition. In this case we have a single job with four steps. More sophisticated workflows can have multiple jobs which run in parallel and/or have ordering dependencies upon each other, but in this case we want to keep it as simple as possible so we just combine everything into one job with four steps that run serially.

The steps:
  1. Check out the code. We use the built-in checkout@v2 action, and recursively check out submodules to make sure we get all dependencies
  2. Build the image. For this step, we are using a public docker image called lagerdata/devenv-cortexm. We specify the exact SHA-256 hash because we want to ensure that whenever this workflow runs, it uses a consistent image. If we'd simply said docker://lagerdata/devenv-cortexm, then we would always get the latest version of the image, which would not necessarily be compatible with this particular commit in the future (imagine if a future version of the image contains an incompatible compiler). In general your CI pipeline should always specify the exact version of each tool to enable consistent repeatable builds..
  3. Connect to the debugger. For this step we're using the lagerdata/lager-cli docker image. This is a minimal image that only contains the lager-cli tool. In this step we're setting an environment variable LAGER_SECRET_TOKEN to the value of secrets.AKBAR_SECRET_TOKEN, which is a repo-level secret. We're using a repo secret since storing secrets directly in your source control is considered a bad practice - it would mean anyone with access to your repo would be able to view your secrets. For help setting up Github repo secrets for use with Lager, see our docs The lager-cli tool will use this secret to authenticate with Lager's API before attempting to talk to the gateway.
  4. Finally, flash the DUT and run it, and read serial output from /dev/ttyACM0, which is the USB serial interface for the dev board. This will flash the board with the output of the build step - _build/unit-tests/test-suites/test-example/test-example.hex - and run it. Assuming everything went well, you'll see output that looks something like this:
Github Actions screenshot