TT Lab
はじめる
学ぶ 学習パス コース

CI/CDパイプライン

マージを止めるゲートを作る

TT Labで続きを見る

目標

テストランナー、カバレッジゲート、リトライ、隔離をシェルで作り、これらのシグナルを1つのマージ判定にまとめます。

なぜ重要なのか

ゲートの価値は、通す能力ではなく止める能力から生まれます。だからランナーは失敗を正確に数えなければならず、カバレッジの比較は境界値で揺らいではならず、リトライには上限が必要です。無限リトライは失敗を隠すことであり、隔離は、不安定なテストがチーム全体を止めないようにしつつ、その事実をレポートに残すための妥協です。最後のレポートが重要なのは、人ではなく、ブランチ保護のrequired status checksのような自動の仕組みがこの判定を読んでマージを止めるからです。数値と判定が食い違ったレポートは、ゲートを無意味にします。

ステップ

  1. /root/ci2/run-tests.sh <디렉터리>(プレースホルダーはディレクトリです)を作り、実行権限を付けてください。引数で受け取ったディレクトリの*.shファイルを1つずつ実行し、最後にpassed=<수> failed=<수> skipped=<수>(プレースホルダーは件数です)の要約を1行で出力してください。すべて通ったら終了コードは0です。テストファイルには実行権限がない場合があるので、bash "$f"の形で実行してください。
  2. 失敗するテストが混ざっていても残りを最後まで実行し、最後に0以外のコードで終了してください。成功が2つ、失敗が1つなら、要約はpassed=2 failed=1になっている必要があります。
  3. 空のディレクトリを受け取ったら、passed=0 failed=0と終了コード0を出してください。このとき、対象ディレクトリの中にログなどのファイルを作らないでください。
  4. /root/ci2/gate-coverage.sh <커버리지파일> <임계값>(プレースホルダーはカバレッジファイルとしきい値です)を作ってください。カバレッジファイルはcovered=80 total=100形式の1行です。パーセントはcovered * 100 / totalで、しきい値以上なら終了コード0、未満なら0以外のコードを出してください(同じなら通過です)。失敗するときは実際のパーセント値を出力してください。
  5. /root/ci2/retry.sh <최대시도횟수> <명령...>(プレースホルダーは最大試行回数とコマンドです)を作ってください。コマンドが成功したらすぐに止めて0を出し、最大回数まですべて失敗したら0以外のコードを出してください。実際の試行回数は、最大回数を決して超えてはいけません。
  6. 対象ディレクトリにquarantine.txtがあれば、そこに書かれたファイル名(1行に1つ)は実行せず、skippedとして数えてください。隔離されたテストはfailedとして数えず、残りがすべて通れば終了コードは0です。
  7. /root/ci2/gate.sh <테스트디렉터리> <커버리지파일> <임계값>(プレースホルダーはテストディレクトリとカバレッジファイルとしきい値です)を作ってください。テストとカバレッジの2つの条件がどちらも通過のときだけ終了コード0を出し、1つでも失敗なら0以外のコードを出してください。
  8. /root/ci2/gate-report.jsonを作ってください。フィールドはtests.passed、tests.failed、tests.skipped、coverage.pct、coverage.threshold、coverage.ok(真偽値のtrue/false)、decision(文字列mergeまたはblock)です。failedが1以上、またはpctがthreshold未満なら、decisionはblock、それ以外はmergeでなければなりません。

参考

テストランナーを作る

/root/ci2/run-tests.sh <디렉터리>(プレースホルダーはディレクトリです)を作り、実行権限を付けてください。引数で受け取ったディレクトリの*.shファイルを1つずつ実行し、最後にpassed=<수> failed=<수> skipped=<수>(プレースホルダーは件数です)の要約を1行で出力してください。すべて通ったら終了コードは0です。テストファイルには実行権限がない場合があるので、bash "$f"の形で実行してください。

/root/ci2/run-tests.sh <디렉터리>(プレースホルダーはディレクトリです)の形で呼び出されます。ディレクトリの*.shを1つずつ実行し、最後に要約を1行だけ出力してください。採点が作るテストファイルには実行権限がないので、必ずbash "$f"で実行する必要があります。

失敗しても最後まで数える

失敗するテストが混ざっていても残りを最後まで実行し、最後に0以外のコードで終了してください。成功が2つ、失敗が1つなら、要約はpassed=2 failed=1になっている必要があります。

最初の失敗で止まると、全体像が見えません。最後まで実行してから、最後に0以外のコードで終了してください。set -eを有効にしたままテストをそのまま呼ぶと、ランナー自身が終了してしまいます。if bash "$f"; then ... else ... fiで終了コードを自分で扱ってください。

空の入力でも安全に動く

空のディレクトリを受け取ったら、passed=0 failed=0と終了コード0を出してください。このとき、対象ディレクトリの中にログなどのファイルを作らないでください。

テストが1つもないことは失敗ではありません。グロブが何も見つけられなかったときに、*.shという文字列そのものがファイル名として渡されないよう、存在を確認してください。対象ディレクトリの中にログファイルを作ってはいけません。

カバレッジのしきい値ゲート

/root/ci2/gate-coverage.sh <커버리지파일> <임계값>(プレースホルダーはカバレッジファイルとしきい値です)を作ってください。カバレッジファイルはcovered=80 total=100形式の1行です。パーセントはcovered * 100 / totalで、しきい値以上なら終了コード0、未満なら0以外のコードを出してください(同じなら通過です)。失敗するときは実際のパーセント値を出力してください。

gate-coverage.sh <파일> <임계값>(プレースホルダーはファイルとしきい値です)で、ファイルはcovered=80 total=100の1行です。境界値(同じとき)は通過です。整数の割り算で比較すると境界で間違えるので、awk 'BEGIN{exit !(p+0 >= t+0)}'のように実数で比較してください。

上限のあるリトライ

/root/ci2/retry.sh <최대시도횟수> <명령...>(プレースホルダーは最大試行回数とコマンドです)を作ってください。コマンドが成功したらすぐに止めて0を出し、最大回数まですべて失敗したら0以外のコードを出してください。実際の試行回数は、最大回数を決して超えてはいけません。

retry.sh <최대횟수> <명령...>(プレースホルダーは最大回数とコマンドです)です。成功したらすぐに止まり、最大回数を決して超えません。無限リトライは安定化ではなく失敗を隠すことなので、最後まで失敗したら必ず0以外のコードを出す必要があります。

不安定なテストの隔離

対象ディレクトリにquarantine.txtがあれば、そこに書かれたファイル名(1行に1つ)は実行せず、skippedとして数えてください。隔離されたテストはfailedとして数えず、残りがすべて通れば終了コードは0です。

対象ディレクトリのquarantine.txtに、ファイル名が1行に1つずつ書かれています。そのファイルは実行せず、skippedとして数えてください。隔離されたテストはfailedに入れないので、残りが通れば終了コードは0です。

複合ゲート

/root/ci2/gate.sh <테스트디렉터리> <커버리지파일> <임계값>(プレースホルダーはテストディレクトリとカバレッジファイルとしきい値です)を作ってください。テストとカバレッジの2つの条件がどちらも通過のときだけ終了コード0を出し、1つでも失敗なら0以外のコードを出してください。

gate.sh <테스트디렉터리> <커버리지파일> <임계값>(プレースホルダーはテストディレクトリとカバレッジファイルとしきい値です)は、先ほど作った2つのスクリプトを呼び出して判定だけをまとめます。1つでも失敗なら失敗です。各呼び出しの終了コードを変数に入れておくと、どちらが止めたのかも出力できます。

判定と数値が一致するレポート

/root/ci2/gate-report.jsonを作ってください。フィールドはtests.passed、tests.failed、tests.skipped、coverage.pct、coverage.threshold、coverage.ok(真偽値のtrue/false)、decision(文字列mergeまたはblock)です。failedが1以上、またはpctがthreshold未満なら、decisionはblock、それ以外はmergeでなければなりません。

/root/ci2/gate-report.jsonのdecisionは、数値から導かれている必要があります。failedが1以上、またはpctがthreshold未満ならblock、そうでなければmergeです。coverage.okは文字列ではなく、JSONの真偽値(true/false)で入れてください。