同じソースツリーでテストが通っていたらテストをスキップする
tl;dr
git rev-parse HEAD^{tree}
でツリーオブジェクトのハッシュ値が取れるので、ブランチが異なる場合でも同じソースツリーであるかどうかを判定できます。
これを利用して、すでにテストを通ったtreeのハッシュ値をどこかに記録しておいて、同一のソースツリーに対するテストをスキップできます。
本題
よく使われている、develop/mainブランチ運用をしている場合に、ちょっとした修正を本番に入れたい場合には以下のようなフローを踏むことになるでしょう。
- featureブランチをdevelopブランチの先頭から切って修正を作ってテストが通るのを待つ
- developブランチにfeatureブランチにマージしてテストが通るのを待つ
- mainブランチにdevelopブランチをマージしてテストが通ったらdeployする
さて、この時、他の作業が混ざらない限りにおいては1,2,3のソースツリーの内容は実は全く同じです。それなのにテストが3回も回ってしまいます。これは無駄ですし、テスト時間が長い場合にはストレスです。
ソースツリーの内容が同じであればテストをスキップしたい
なので、ソースツリーの内容が同じであればテストをスキップすれば良いでしょう。幸いGitでツリーオブジェクトのハッシュ値を git rev-parse HEAD^{tree}
で取得でき、これをソースツリーの内容の高速な同一判定に利用できます。
ref. https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#_tree_objects
テストが通ったツリーのハッシュ値を記録する
後はテストが通った後に、テストをパスしたハッシュ値をどこかに保存しておいて、テスト実行前にそれを確認し、すでに通っていればテストをスキップすればよいでしょう。
Nature社では、CIにはCircleCIを使っているため、少し大げさな気がしますがハッシュ値の保存にはS3を使うことにしました。他に良いアイデアがあれば教えて欲しい!
ハッシュ値の保存
ハッシュ値をそのままファイル名として0byteのファイルをS3にputしています。
store-success-hash:
docker:
- image: cimg/base:stable
steps:
- checkout
- run:
name: install awscli v2
command: |
curl -s https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip -o aws.zip
unzip aws.zip
sudo ./aws/install
- run:
name: stores a hash of the tree object that has been successfully tested
command: |
set +e
repotree_hash=$(git rev-parse HEAD^{tree})
touch ${repotree_hash}
aws s3 mv ${repotree_hash} s3://${treehash_bucket}/${repotree_hash}
true # This step is always considered a success because it doesn't matter if it fails.
テスト前の確認
S3のpublic accessを開けておき、curlで取得して200が返ってきたらテストをスキップします。
test:
docker:
- image: golang:1.16.0
- checkout
- run:
name: go test
command: |
repotree_hash=$(git rev-parse HEAD^{tree})
set +e
curl -f https://${treehash_bucket}.s3.amazonaws.com/${repotree_hash} 2>/dev/null
status=$?
set -e
if [ $status = "0" ]; then
echo 'skip testing because the test for the same source tree has already passed'
exit 0
fi
go test ./...
public accessを空けておくのは乱暴ですが、bucket上にはハッシュ値ファイル名の空ファイルしか存在しないので大きな問題はなかろうという判断です。
またbucket上のファイルは3日でexpireする設定にしています。
まとめ
ちょっとした修正をリリースしたい時の待ち時間が減って最高です。ただ、store-success-hashの時間が地味にかかるので、短縮したいのですが、これも良いアイデアあれば募集です。
ちなみに、gitの状態に依存したテストを実施している場合等、この方法を避けたほうが良いケースもあるでしょう。hackishなテクニックでもあるので、自分の環境に適しているかどうかを確認してからご活用ください。
今日のCTO に言いたいこと: いきなり CI にハック入れないで欲しいし、酔っ払っい勢いツイートを控えてほしい
— soh335 (@soh335) October 30, 2019
Thanks to
git rev-parse HEAD^{tree}
でtreeのハッシュが取れるというのは @k_hanazuki さんに教えてもらいました!ありがとうございます!
そもそもgitのtree自体がファイル内容のハッシュを並べたもののハッシュで管理されているので, git rev-parse HEAD^{tree} を使うとどうでしょう (ファイル内容に加えてモードの情報も含まれますが)
— 花月かすみΛ__Λ (@k_hanazuki) October 28, 2019
関係ないボヤキ
- develop/mainブランチじゃなくてmainブランチ一本にしてタグを打ったらリリースとかにしたい
- でもpull requestをmainにマージしたら自動deployというワークフローを組んでいるので変更が難しい
- ref. https://songmu.jp/riji/entry/2019-07-28-circleci-git-pr-release.html
- tag request的な機能がGitHubに欲しいね
- でもpull requestをmainにマージしたら自動deployというワークフローを組んでいるので変更が難しい
- Gitでトピックブランチをfast forwardした場合でもコミットハッシュが変わるのは何なのか
- fast forwardする運用にはしていないのですが
採用情報
Natureでは一緒に高速にソフトウェア開発をするエンジニアを募集しています!