項目32:CIシステムを設定しよう
https://effective-rust.com/ci.html
LT;DR
すべてのテストは CI 上で実行しよう
CI は素早く、決定的に動作し、偽陽性 がゼロでなければならない
Protocol Buffers などからコードを自動生成する場合、CI 上でも自動生成して元のコードベースと整合するかチェックしよう
コードベースに秘密情報が含まれていないことのチェックは、CI 上ではなく Pre-commit 時にチェックしよう
チェックは実行頻度によって分割しよう
偽陽性 であっても CI の失敗は許容しないようにしよう
プロセスの問題を修正する前に、問題を検出する CI ステップを追加しよう
Rust
rust-toolchain.toml で CI 上の Rust のバージョンを固定しよう
radish-miyazaki.icon が個人的に API 開発で CI 上で Must で実行したいツール群
cargo test
cargo clippy
cargo tarpaulin
cargo-deny / cargo-udeps
Rust は特に依存関係が肥大化しがちなので
以下は任意または一部で実行する
cargo doc
cargo bench
hr.icon
CI の各ステップ
大まかには以下の 2 つ
1. コードを ビルド する
クレート に フィーチャ がある場合は「CI でフィーチャのすべての有効な組み合わせ(2N)に対してビルドしよう」
項目26:忍び寄るフィーチャに注意しよう
no_std 互換の場合は「CI で no_std であることをテストしよう」
項目33:ライブラリコードをno_std互換にすることを検討しよう
e.g. クロスコンパイル 機能を用いて、明らかに no_std な ビルドターゲット(e.g. thumbv6m-none-eabi)
MSRV を設定する場合は「MSRV をCI でチェックしよう」
項目22:可視範囲を最小化しよう
2. コードに対してテストを実行する
「すべてのテストは CI で実行しよう」
項目30:ユニットテスト以上のものを書こう
cargo test で実行できるテスト(単体テスト と 結合テスト、ドキュメンテーションテスト)+ 明示的に実行する必要があるテスト(examples 内のサンプルプログラム)
どちらのステップも、素早く 決定的 に動作 し、偽陽性 がゼロ でなければならない
決定的にするには、「rust-toolchain.toml を用いて、CI ビルドで用いるバージョンを固定しよう」
具体的には、rust-toolchain.toml の [channel] テーブルに以下を指定する
バージョン(e.g. 1.70)
チャンネル(stable、beta、nightly)
nightly の場合は、日付を付けることもできる(e.g. nightly-2023-09-19)
上記以外にも、コードの品質を改善するステップを追加することもできる
静的解析: cargo clippy
項目29:Clippyに耳を傾けよう
「CI で Clippy(cargo clippy --Dwarnings)を実行しよう」
--Dwarnings オプションを付けると、警告が見つかったときにビルドが失敗するようになる
ドキュメント生成: cargo doc
項目27:パブリックインターフェイスのドキュメントを書こう
CI で cargo doc を実行して、ドキュメントが生成されることと ハイパーリンク がリンク切れしていないことを確認しよう
問題のあるクレートの検出: cargo-deny / cargo-udeps
項目25:依存グラフを管理しよう
「CI で cargo-deny や cargo-udeps を実行して、リグレッション を防止しよう」
フォーマット: rustfmt / cargo fmt
項目31:ツールのエコシステムを活用しよう
「CI で rustfmt や cargo-fmt を実行して、スタイルガイドライン を遵守していないコードを検出しよう」
--check を用いると問題点が分かりやすくなる
また、コードのある性質を測定するステップも追加できる
コードカバレッジ 測定: cargo tarpaulin
ベンチマーク 測定(ベンチマークテスト): cargo bench
外部要因に大きく依存ため、信頼性 の高い結果を得るには専用の環境が必要
warning.icon
これら 2 つの測定は 前回の結果と比較する必要 がある
∵ テストが不十分だったり、パフォーマンスに悪影響が出ることを検出できるようにするため
これを実現するには、外部の追跡システム(e.g. Codecov や Grafana)と統合する必要がある
プロジェクトによっては入れる必要のあるステップ
クレートライブラリの場合
Cargo.lock を使わずにビルドする
ユーザはライブラリの Cargo.lock を必要としないため、Cargo.toml の依存ライブラリのバージョン指定でうまく機能するか確認する
自動生成するリソース(e.g. Protocol Buffers)が含まれる場合
リソースを再生成して、既存のものと整合するか確認する
プラットフォーム固有(#[cfg(target_arch = "arm"])の処理がある場合
コードがビルドできることと(可能なら)そのプラットフォーム上で動作することを確認する
アクセストークン や 暗号鍵 などの秘密情報を扱う場合
秘密情報が誤ってチェックインされていないことを確認する
warning.icon このステップは CI 上ではなく、 Git フック を用いてコミット前に行うこと
CI チェックは Cargo や Rust のツールチェーンを使わずに、シェルスクリプトでも良い
特にコードベースが独自の慣習に従っている場合
e.g.
panic を起こしうるメソッド呼び出しには、特別な マーカトレイト を付ける
TODO コメントには所有者を付ける
公開されている Rust のプロジェクトのCI を参考にしよう
e.g. Cargo の CI(https://github.com/rust-lang/cargo/blob/master/.github/workflows/main.yml)
CI の原則
CI の最も基本的な原則は「人間の時間を無駄使いするな」
CI が不必要にエンジニアの時間を浪費すると、回避する方法を探し始める
よくある時間を無駄にするパターン
Flaky test
完了までに時間がかかる(レビュー依頼後も走る) CI
CI で検出できたはずの問題を Reviewer が指摘することで時間が無駄になる
解決策
Flaky test
初期投資 として、原因を調査して修正する時間をとる
完了までに時間がかかる(レビュー依頼後も走る) CI
1. CI 上で動かしているチェックを手動で実行できるようにする
ローカル環境や IDE に組み込む
2. チェックを頻度に応じて分割する
一般的には、以下のように分割する
各エンジニアの開発環境に統合されたチェック(e.g. rustfmt)
PR 作成時に実行され、手動でも簡単に実行できるチェック(e.g. cargo clippy、cargo build)
メインブランチを変更するたびに行うチェック(e.g. すべてのサポート環境を対象とした cargo test)
スケジュールされた間隔(e.g. 毎日 or 毎週)で実行され、リグレッション を後から検出するためのチェック(e.g. 統合テスト や ベンチマークテスト)
現在のコードに常時実行されるチェック(e.g. ファズテスト)
また、偽陽性 であっても CI の失敗を認めてはいけない
認めてしまうと、新しいリグレッションエラーを見つけるのが困難になる
「プロセスの問題を修正する前に、問題を検出する CI ステップを追加しよう」
バグを修正する前にバグを再現するテストを書く(項目30:ユニットテスト以上のものを書こう)のと同じ
e.g.
なんらかの自動生成されるコードが元のコード整合していない場合、整合性をチェックする CI ステップを追加すべきである
パブリックな CI システム
OSS として公開しているコードベースの場合、いくつか知っておくべきことがある
OSS の場合、CI システムを構築するサービスが無料のことが多い(e.g. GitHub Actions)
コードが他のものに依存する場合、CI システムは事前条件 のセットアップ方法を示すガイドとして機能する
e.g. データベースや FFI コード用のツールチェイン、設定
これをスクリプトとしてまとめておくと、ユーザと CI の両方が簡単にセットアップできるようになる
悪用や攻撃の可能性がある
e.g.
CI システムを利用した マイニング
コードベースへのアクセストークンの窃盗
サプライチェーン攻撃
これを防ぐために、以下のガイドラインを検討すること
既知のコラボレータに対してのみ CI が自動的に実行するようにアクセスを制限し、新しい コントリビュータ については手動で実行する
すべての外部スクリプトのバージョン(ハッシュ)を固定する
コードベースへの Read 権限以上を必要とするステップについては、厳重に監視する
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目