おそらくはそれさえも平凡な日々

同じソースツリーでテストが通っていたらテストをスキップする

tl;dr

git rev-parse HEAD^{tree} でツリーオブジェクトのハッシュ値が取れるので、ブランチが異なる場合でも同じソースツリーであるかどうかを判定できます。

これを利用して、すでにテストを通ったtreeのハッシュ値をどこかに記録しておいて、同一のソースツリーに対するテストをスキップできます。

本題

よく使われている、develop/mainブランチ運用をしている場合に、ちょっとした修正を本番に入れたい場合には以下のようなフローを踏むことになるでしょう。

  1. featureブランチをdevelopブランチの先頭から切って修正を作ってテストが通るのを待つ
  2. developブランチにfeatureブランチにマージしてテストが通るのを待つ
  3. 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なテクニックでもあるので、自分の環境に適しているかどうかを確認してからご活用ください。

Thanks to

git rev-parse HEAD^{tree} でtreeのハッシュが取れるというのは @k_hanazuki さんに教えてもらいました!ありがとうございます!

関係ないボヤキ

  • develop/mainブランチじゃなくてmainブランチ一本にしてタグを打ったらリリースとかにしたい
    • でもpull requestをmainにマージしたら自動deployというワークフローを組んでいるので変更が難しい
      • ref. https://songmu.jp/riji/entry/2019-07-28-circleci-git-pr-release.html
    • tag request的な機能がGitHubに欲しいね
  • Gitでトピックブランチをfast forwardした場合でもコミットハッシュが変わるのは何なのか
    • fast forwardする運用にはしていないのですが

採用情報

Natureでは一緒に高速にソフトウェア開発をするエンジニアを募集しています!

https://nature.global/careers/

created at
last modified at

2021-03-08T01:05:50+0900

comments powered by Disqus