Septeni Engineer's Blog

セプテーニ・オリジナルのエンジニアが綴る技術ブログ

GitLab CI で Scala のテストを雑に速くする方法

こんにちは。 .gitlab-ci.yml を書くのが好きな河内です。

社内では GitLab をホスティングして使っています。 GitLab の機能追加速度はすごいですね。 毎月22日にリリースされることになっており、毎回それなりに機能追加されています。

さて、11月22日にリリースされた GitLab 11.5 では CI ジョブの設定に parallel 属性が追加されました。

about.gitlab.com

parallel 属性を指定すると、その数のジョブが並列に実行されます。 各ジョブには CI_NODE_TOTAL (並列ジョブの総数)と CI_NODE_INDEX (自身のジョブのインデックス)が設定されます。

例えば parallel: 4 と指定した場合、4 つのジョブが並列実行されます。 それら全てのジョブで CI_NODE_TOTAL 環境変数は 4 に設定されます。 CI_NODE_INDEX は各ジョブで 1 から 4 までの数字が設定されます。

各ジョブはこれら2つの環境変数を参照して、作業を分担することが想定されています。

ここでは sbt を使って Scala プログラムをテストするときに、作業を分担する方法を紹介します。 testOptionsTests.Filter を指定すると、条件を満たしたテストのみを実行できるので、これを利用します。

build.sbt に次のように記述します。

import scala.util.Try

testOptions in Test ++=
  (for {
    indexString <- sys.env.get("CI_NODE_INDEX")
    index       <- Try(indexString.toInt).toOption
    totalString <- sys.env.get("CI_NODE_TOTAL")
    total       <- Try(totalString.toInt).toOption
    if index >= 1 && total >= 1 && index <= total
  } yield Tests.Filter(testCls => math.abs(testCls.hashCode % total) == index - 1)).toSeq

Tests.Filter は、テストクラス名を受け取ってテスト対象とするか否かの関数 (String => Boolean) を受け取ります。 ここでは雑にテストクラス名の hashCode を CI_NODE_TOTAL で割って、余りが CI_NODE_INDEX となるテストのみを実行対象としています。

これだけで、テストの分担は完了です。 sbt 便利ですね!

あとは .gitlab-ci.yml のジョブ設定で parallel: n と設定すれば n 並列でテストが実行されます。 sbt 起動やコンパイルなどのオーバヘッドがかかるため、単純に n 倍速にはなりませんが、テストの時間だけを取り出して考えると n 倍速が期待できます。 テストが長くて困っている人は是非お試しあれ。