GitHub Actions と arduino-cli を使って Arduino のライブラリに静的テストを導入する
ライブラリの規模が大きくなると、機能を追加や変更によって既存機能がエラーとなる事がよく起こります。実際にその機能を使うまでエラーに気づけないことも多く、気付いた時には対応が困難になる場合が多いです。
さらには、マルチプラットフォームなライブラリである場合、対象の環境全て人力でテストするのは無理があります。
このような鬼畜的状況を打破すべく、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 を得られます。
テスト用スクリプトを書く
.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 タブでテストが実行されていると思います。
実行中のワークフローをクリックすると、詳細を確認できます。テストをパスすると、テスト項目に ✅ がつきます。
実行時に出力されるログも見ることができます。
ステータスバッジを README.md に記載 (任意)
画面右上からステータスバッチの画像 URL をマークダウン形式で取得できます。
README.md 等のマークダウンファイルに張り付けると、このような感じでステータスを見ることができます。テストに失敗している場合 Falling
と表示されます。
完成 “(-”"-)"
記事と同じ内容のものを GitHub に公開しております。
また部活のライブラリにもこのテストを導入しています。
ローカル環境で実行する
ArduinoLint.ino
を Arduino IDE で開きコンパイルすればテストできます。複数環境での実行はボードを手動で切り替えるしかないです。arduino-cli でコンパイルするようなスクリプトを作れば、ローカルでも並列テストできると思います。
Windows で開発されている方向けの注意点
Linux (Ubuntu) マシンで実行するので、Windows と異なり、ファイルの大文字小文字を区別します。そのため、大文字小文字を厳密に扱う必要があります。例えば Arduino.h
を arduino.h
としていると事故ります。
#include <Arduino.h>
// #include <arduino.h> // NO!!
また Linux はパスの区切り文字に /
を用います (Windows 版 Arduino では \
もしくは /
)。なのでインクルード文を Ubuntu の方に合わせてあげると楽です。
#include <MyLibrary/Algorithm/Math.hpp>
// #include <MyLibrary\Algorithm\Math.hpp> // NO!
おわり
テストを導入すると機能変更の際に、テストによって他の機能が生きていることが(おおよそ)保証されるので、精神的に相当楽になります。
少しの労力で導入できますので、是非導入してみてはいかがでしょうか。
おかしな点や、感想等ありましたら、是非コメントお寄せください。ご覧頂き有難うございました。