Buildkite で実行される RSpec のカバレッジを SimpleCov で計測してレポートを見る
今回やったこと
- SimpleCov による RSpec カバレッジの計測
- Buildkite で並列にテストを実行してレポートをまとめる
- (PR への push の場合)レポートページのリンクを PR にコメント
実装
カバレッジ計測
SimpleCov を Gemfile に追加し spec_helper.rb に以下のコードを追加します。
SimpleCov.start に 'rails' 引数を渡さないと後々並列テストのレポートをマージする時にうまくいきませんでした。
require 'simplecov'
SimpleCov.start 'rails'
テスト実行
並列テスト
テストの並列実行は builkite の parallelism 設定と knapsack を利用しています。
knapsack すでには knapsack pro というサービスに移行していてレポートに応じて最適な時間配分でテストを割り振ることはできなくなってしまっていますが、実行時間を考慮しないで単純にテストを割り振るのは knapsack でも可能なので、今回は knapsack で単純な分割のみを行なっています。
knapsack による並列テストは spec_helper.rb に以下のコードを追加して knapsack コマンドから実行します。
require 'knapsack'
Knapsack::Adapters::RSpecAdapter.bind
bundle exec rails knapsack:rspec
カバレッジレポートのマージ
SimpleCov では SimpleCov.collate という複数のレポートをマージする機能が提供されています。
この機能を使うには並列テストを実行したコンテナごとに生成されたカバレッジレポートを一箇所に集める必要があり、今回は各並列テストのレポートを S3 にアップロードする法方をとりました。
今回はテストの実行やレポートのマージといったステップごとの処理をシェルにして pipeline.yml から呼び出す構成にしています。
.buildkite
├ pipeline.yml
└ script
├ rspec.sh
└ coverage.sh
lib
└ tasks
└ coverage.rake
pipeline.yml
env:
BUILDKITE_PLUGIN_DOCKER_COMPOSE_CONFIG: docker-compose.yml
BUILDKITE_PLUGIN_DOCKER_COMPOSE_IMAGE_REPOSITORY: 265438772420.dkr.ecr.us-west-2.amazonaws.com/buildkite-prebuild
steps:
- label: "Build"
plugins:
- docker-compose#v3.9.0:
build: web
- wait
- label: "rspec"
commands: . /script/rspec.sh
parallelism: 5
plugins: &plugins
- docker-compose#v3.9.0:
run: web
- label: "coverage"
commands: . /script/coverage.sh
plugins: *plugins
rspec.sh
knapsack を利用した rspec の実行とカバレッジレポートの S3 へのアップロード。
カバレッジレポートは SimpleCov を入れて rspec を実行すると自動で出力される。
#!/bin/bash
bundle exec rails knapsack:rspec
base_url=s3://buildkite/${BUILDKITE_PIPELINE_SLUG}/tmp_${BUILDKITE_BUILD_NUMBER}/coverage_report
filename=${BUILDKITE_PARALLEL_JOB}.json
aws s3 cp ./coverage/.resultset.json ${base_url}/${filename};
coverage.sh
各テストコンテナから S3 にアップしたレポートのダウンロードとマージ。
マージ後は coverage ディレクトリにマージされたレポートが出力される。
#!/bin/bash
tmp_dir=s3://buildkite/${BUILDKITE_PIPELINE_SLUG}/tmp_${BUILDKITE_BUILD_NUMBER}/coverage_report
# download reports from S3
mkdir -p tmp/coverage
aws s3 cp ${tmp_dir}/ tmp/coverage --recursive
# aggregate reports
result=$(bundle exec rails coverage:report)
echo $result
# clear temp files
rm -f ${filename}*.json
rm -f tmp.json
aws s3 rm ${tmp_dir}/ --recursive
lib/tasks/coverage.rake
namespace :coverage do
desc "Collates all result sets generated by the different test runners"
task report: :environment do
require 'simplecov'
SimpleCov.start
dir = Rails.root.join('tmp/coverage/*.json')
SimpleCov.collate Dir[dir], 'rails'
end
end
PR へのレポートコメント
今のままだと buildkite の coverage ステップのコンテナ内でレポートがマージされて終了になっているので、これを参照できるようにします。
今回は SimpleCov が出力するカバレッジ可視化用の HTML を buildkite の artifact としてアップロードすることにしました。
また、毎回 buildkite のレポートページから artifact を開くのは少し面倒なので、GitHub の PR にレポート結果とレポートページへのリンクをコメントするようにもします。
artifact アップロード
artifact のアップロードは buildkite-agent artifact upload
を利用します。
buildkite-agent を利用するためには buildkite 上で実行されるコンテナが buildkite-agent コマンドを利用できるようにする必要があるので、pipeline.yml で docker-compose プラグインに mount-buildkite-agent を追加します。
- label: "rspec"
commands: . /script/rspec.sh
parallelism: 5
plugins: &plugins
- docker-compose#v3.9.0:
run: web
mount-buildkite-agent: true # New
次に buildkite の artifact としてカバレッジレポートをアップロードするために coverage.sh を編集します。
マージ後に出力される HTML はローカルの assets を参照しており HTML 単品では見れたものじゃありませんが、SimpleCov の HTML 用のリポジトリが GitHub で公開されているので、github-cdn-converter という GitHub 上の CDN のように HTML から読み込めるようにしてくれるツールを使って GitHub の assets を参照するようにします。
# aggregate reports
result=$(bundle exec rails coverage:report)
echo $result
sed -i 's#\./assets/0.12.3#https://cdn.jsdelivr.net/gh/simplecov-ruby/simplecov-html@main/public#g' coverage/index.html # assets の参照先をローカルから GitHub に差し替え
# upload reports as buildkite artifact
buildkite-agent artifact upload coverage/.resultset.json
buildkite-agent artifact upload coverage/index.html
こうすることで buildkite の coverage ステップの artifact にレポートが表示されるようになります。
PR へのコメント
PR へのコメントは以下のようなフォーマットにします。
Coverage of [${commit}](${commit_url}) is ${coverage} [Report](${artifact_url})
commit = short commit hash
commit_url = GitHub のコミットページへの URL
coverage = レポートマージ時に出力されるメッセージに含まれる 'N / M LOC (XX.X%).'
artifact_url = artifact としてアップロードした HTML の URL
最終的に以下のコードを coverage.sh に追加しました。
if [ -n "${BUILDKITE_PULL_REQUEST}" ]; then
coverage=$(echo "$result" | cut -c134-)
commit=$(echo "$BUILDKITE_COMMIT" | cut -c1-7)
commit_url=https://github.com/git-hub-user/repository/pull/${BUILDKITE_PULL_REQUEST}/commits/${commit}
buildkite_url=https://api.buildkite.com/v2/organizations/git-hub-user/pipelines/repository/builds/${BUILDKITE_BUILD_NUMBER}/jobs/${BUILDKITE_JOB_ID}/artifacts
artifacts=$(curl ${buildkite_url} -X GET -H "Authorization: Bearer ${BUILDKITE_API_TOKEN}")
artifact_id=$(echo "$artifacts" | jq -r '.[] | select(.path == "coverage/index.html") | .id')
artifact_url=https://buildkite.com/organizations/git-hub-user/pipelines/repository/builds/${BUILDKITE_BUILD_NUMBER}/jobs/${BUILDKITE_JOB_ID}/artifacts/${artifact_id}
message="Coverage of [${commit}](${commit_url}) is ${coverage} [Report](${artifact_url})"
clean_message=$(echo "$message" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g')
body=$(jq -n --arg message "$clean_message" '{body: $message}')
curl -X POST \
-H "Authorization: Bearer ${GITHUB_API_TOKEN}" \
-H "application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/git-hub-user/repository/issues/${BUILDKITE_PULL_REQUEST}/comments" \
-d "$body"
fi
PR かどうかの判定
PR への push ではない場合はコメント先がないので PR への push の場合のみ処理をするようにしています。
BUILDKITE_PULL_REQUEST で PR の ID が取得できるので、これの有無で判定しています。
artifact へのリンク取得
artifact にアップロードした HTML へのリンクは buildkite が提供している Artifact API を使って取得しています。
URL には artifact の ID が必要なため JOB(ステップ)の artifact を全て取得して path で判定して HTML artifact の ID を取得しています。
ENECHANGEグループは、「エネルギー革命」を技術革新により推進し、より良い世界を創出することをミッションとするエネルギーベンチャー企業です。 enechange.co.jp/
Discussion