GitHub Actions と arduino-cli を使って Arduino のライブラリに静的テストを導入する

GitHub Actions と arduino-cli を使って Arduino のライブラリに静的テストを導入する

2024-10/19

ライブラリの規模が大きくなると、機能を追加や変更によって既存機能がエラーとなる事がよく起こります。実際にその機能を使うまでエラーに気づけないことも多く、気付いた時には対応が困難になる場合が多いです。

さらには、マルチプラットフォームなライブラリである場合、対象の環境全て人力でテストするのは無理があります。

このような鬼畜的状況を打破すべく、GitHub Actions と arduino-cli を使って、自動的にテストを行う方法を紹介します。

前提

ライブラリを GitHub で管理していること (プライベートレポジトリでも大丈夫です)

GitHub Actions とは

GitHub Actions 1 はプッシュした時や、プルリクエストを発行した時などに、GitHub の仮想マシン上で任意のスクリプトを動かせる、非常に有用なサービスです。使用上限はありますが無償です。

さっそくテストを作る

仮想マシン上で arduino-cli にテスト用スケッチをコンパイルさせ、コンパイルエラーになったら失敗するようなテストを作ります。

あくまで、コンパイルできるかチェックするテストであり、実行時に分かるようなバグは発見できません。コンパイル時に処理可能なアルゴリズムはチェックできますよ(#^.^#)

※コンパイル時に実行時エラーが起きそうな箇所を見つけられるケースも多いです。

テスト用のディレクトリを構成

コンパイル対象のスケッチを作成します。中、大規模ライブラリの想定なので、ライブラリのサブディレクトリにヘッダーファイルを置いています。こちらの記事でサブディレクトリにファイルを分割する方法を説明してますので、興味がありましたらご覧ください。

ライブラリのサブディレクトリにあるヘッダーファイルをインクルードできるようにする

テスト用ソースファイルを分割する際はsrc 下に追加していきます。src 下はサブディレクトリの中のソースファイルも再帰的にコンパイルしてくれます。

MyLibrary
├─ .github
│  └─ workflows
│     └─ ArduinoLint.yml
├─ src
│  ├─ MyLibrary
│  │  └─ Algorithm
│  │     └─ Math.hpp
│  └─ MyLibrary.hpp
├─ test
│  └─ ArduinoLint
│     ├─ src
│     │  ├─ Algorithm
│     │  │   └─ Math.cpp            <-- テスト用ソースファイル
│     │  └─ Other
│     │      └─ LinkErrorCheck.cpp  <-- テスト用ソースファイル
│     └─ ArduinoLint.ino            <-- テスト用スケッチ
└─ library.properties

src/MyLibrary/Algorithm/Math.hpp

#pragma once

namespace Math
{
    inline int Factorial(int n)
    {
        if (n <= 0)
            return 1;
        else
            return n * Factorial(n - 1);
    }
}

src/MyLibrary.hpp

#pragma once

#include <MyLibrary/Algorithm/Math.hpp>

library.properties

name=MyLibrary
version=1.0.0
author=HogeHoge
maintainer=HogeHoge
sentence=HogeHoge
paragraph=HogeHoge
category=Other
url=http://example.com/
architectures=*

テスト用スケッチを作成

ライブラリで定義している機能を呼び出します。呼び出さないと厳密にコンパイルされないので、すべての機能を呼び出す必要があります。

test/ArduinoLint/src/Algorithm/Math.cpp

#include <MyLibrary/Algorithm/Math.hpp>

// どこからも呼ばない
__attribute__((unused)) static void Test()
{
   (void)Math::Factorial(123);
}

__attribute__((unused)) は関数が呼び出されないことを明示する関数属性です。無いと警告が出ます。

他のテスト用ソースファイルに Test 関数があるとリンクエラーになるので、static 関数にして内部リンケージを持たせています。

戻り値のある関数は、呼び出し側が戻り値を使用しないことを明示するために void 型にキャストします (任意)。


test/ArduinoLint/src/Other/LinkErrorCheck.cpp

#include <MyLibrary.hpp>

多重定義によるリンクエラーをチェックするためのソースファイルです。ArduinoLint.ino + このファイルからヘッダーをインクルードすることで、リンクエラーを誘発させます。

例えばヘッダーファイルにグローバル関数が定義されている場合、多重定義となります。対処方法は関数をインライン化するなどです。


test/ArduinoLint/ArduinoLint.ino

#include <MyLibrary.hpp>

void setup() {}

void loop() {}

サブディレクトリにあるファイルをインクルードするには、サブディレクトリにないファイルをあらかじめインクルードする必要があるので、ここでインクルードしておきます。(Arduino の仕様)

ボードの FQBN を調べる

コンパイルの際、ボードを指定するのに FQBN という文字列を用います。下記の様なものです。

rp2040:rp2040:rpipico

FQBN を調べるには ファイル > 基本設定 を開き、コンパイラの出力を表示するようにチェックします。

オプションにチェック

対象のボードでコンパイルすると、FQBN を得られます。

FQBN

テスト用スクリプトを書く

.github/workflows/ArduinoLint.yml を編集します。並列にジョブを実行する機能(matrix)を用いて、複数のボード環境を同時にテストできます。

ボード環境を追加する場合、先ほど調べた FQBN を他のボードの FQBN のノリで、board: に追加します。不要な環境は削除しちゃってください。

name: Arduino Lint

on: # プッシュ時、プルリクエスト時に実行
  push:
    paths-ignore:
      - "**.md" # マークダウンファイルの変更は無視
  pull_request:
    paths-ignore:
      - "**.md"

concurrency: # 連続プッシュされた場合、前回の実行を止める
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ArduinoLint:
    name: ${{ matrix.board.fqbn }}

    runs-on: ubuntu-latest # Ubuntu 環境で実行

    strategy:
      fail-fast: false # デフォルトでは、ジョブのいずれかが失敗した場合中断するので、中断しないように

      matrix:
        board: # ボード群
          - fqbn: arduino:avr:nano
            platform: arduino:avr
          - fqbn: arduino:avr:uno
            platform: arduino:avr
          - fqbn: teensy:avr:teensy35
            platform: teensy:avr
            url: https://www.pjrc.com/teensy/td_156/package_teensy_index.json # URL からインストールも可
          - fqbn: teensy:avr:teensy40
            platform: teensy:avr
            url: https://www.pjrc.com/teensy/td_156/package_teensy_index.json
          - fqbn: rp2040:rp2040:rpipico
            platform: rp2040:rp2040
            url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
          - fqbn: rp2040:rp2040:rpipicow
            platform: rp2040:rp2040
            url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Arduino CLI
        run: |
          curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
          echo "./bin" >> $GITHUB_PATH          

      - name: Install board
        run: |
          arduino-cli config init
          arduino-cli config set library.enable_unsafe_install true
          if [ -n "${{ matrix.board.url }}" ]; then
            arduino-cli config add board_manager.additional_urls ${{ matrix.board.url }}
          fi
          arduino-cli core update-index
          arduino-cli core install ${{ matrix.board.platform }}          

      # 依存しているライブラリがある場合 (publicレポジトリのみ)
      # - name: Install libraries
      #   run: |
      #     arduino-cli lib install --git-url https://github.com/adafruit/Adafruit_BusIO.git
      #     arduino-cli lib install --git-url https://github.com/adafruit/Adafruit_Sensor.git
      #     arduino-cli lib install --git-url https://github.com/adafruit/Adafruit_BNO055.git

      - name: Run test
        run: arduino-cli compile --library . -b ${{ matrix.board.fqbn }} ./test/ArduinoLint/ArduinoLint.ino
  • arduino-cli config set library.enable_unsafe_install true という怪しげなスクリプトがありますが、リモートレポジトリの URL を基にライブラリを使用する際に必要です。

  • arduino-cli compile --library . として、レポジトリ自身をライブラリとするようにしています。そのため、プライベートレポジトリもテストできます。

arduino/compile-sketches アクションを使う方法もあります。(実行時間が少し長くなります)
name: Arduino Lint

on:
  push:
    paths-ignore:
      - "**.md"
  pull_request:
    paths-ignore:
      - "**.md"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ArduinoLint:
    name: ${{ matrix.board.fqbn }}

    runs-on: ubuntu-latest

    strategy:
      fail-fast: false

      matrix:
        board:
          - fqbn: arduino:avr:nano
          - fqbn: teensy:avr:teensy40
            platforms: |
              - name: teensy:avr
                source-url: https://www.pjrc.com/teensy/td_156/package_teensy_index.json              
          - fqbn: rp2040:rp2040:rpipico
            platforms: |
              - name: rp2040:rp2040
                source-url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json              

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Arduino-cli test
        uses: arduino/compile-sketches@v1

        with:
          fqbn: ${{ matrix.board.fqbn }}
          platforms: ${{ matrix.board.platforms }}
          sketch-paths: |
            - ./test/ArduinoLint            
          libraries: |
            - source-path: ./            
          # - source-url: https://github.com/adafruit/Adafruit_BusIO.git
          # - source-url: https://github.com/adafruit/Adafruit_Sensor.git
          # - source-url: https://github.com/adafruit/Adafruit_BNO055.git

GitHub へプッシュ

変更をプッシュすると、Actions タブでテストが実行されていると思います。

Actions タブ

実行中のワークフローをクリックすると、詳細を確認できます。テストをパスすると、テスト項目に ✅ がつきます。

テストが実行されている様子

実行時に出力されるログも見ることができます。

ログが出力されている様子

ステータスバッジを README.md に記載 (任意)

画面右上からステータスバッチの画像 URL をマークダウン形式で取得できます。

画面右上

バッジのマークダウン

README.md 等のマークダウンファイルに張り付けると、このような感じでステータスを見ることができます。テストに失敗している場合 Falling と表示されます。

Arduino Lint

完成 “(-”"-)"

記事と同じ内容のものを GitHub に公開しております。

CaseyNelson314/MyLibrary

また部活のライブラリにもこのテストを導入しています。

udonrobo/UdonLibrary

ローカル環境で実行する

ArduinoLint.ino を Arduino IDE で開きコンパイルすればテストできます。複数環境での実行はボードを手動で切り替えるしかないです。arduino-cli でコンパイルするようなスクリプトを作れば、ローカルでも並列テストできると思います。

Windows で開発されている方向けの注意点

Linux (Ubuntu) マシンで実行するので、Windows と異なり、ファイルの大文字小文字を区別します。そのため、大文字小文字を厳密に扱う必要があります。例えば Arduino.harduino.h としていると事故ります。

#include <Arduino.h>
// #include <arduino.h>  // NO!!

また Linux はパスの区切り文字に / を用います (Windows 版 Arduino では \ もしくは / )。なのでインクルード文を Ubuntu の方に合わせてあげると楽です。

#include <MyLibrary/Algorithm/Math.hpp>
// #include <MyLibrary\Algorithm\Math.hpp>  // NO!

おわり

テストを導入すると機能変更の際に、テストによって他の機能が生きていることが(おおよそ)保証されるので、精神的に相当楽になります。

少しの労力で導入できますので、是非導入してみてはいかがでしょうか。

おかしな点や、感想等ありましたら、是非コメントお寄せください。ご覧頂き有難うございました。