Skip to content

Instantly share code, notes, and snippets.

@tototoshi
Last active November 8, 2019 09:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tototoshi/638505c2fead8e4193c3621b321b5eaa to your computer and use it in GitHub Desktop.
Save tototoshi/638505c2fead8e4193c3621b321b5eaa to your computer and use it in GitHub Desktop.
テストを並列実行するsbtプラグイン
import sbt._
import Keys._
/**
* テストをいい感じに分割して実行します。
* 分割にはテストクラス名のCRC32チェックサムを使っています。
* 例えば3分割したい時は
*
* > sbt 'splitTest 3 1'
* > sbt 'splitTest 3 2'
* > sbt 'splitTest 3 3'
*
* をそれぞれ別のワーカーで実行してください。
*
*
* 作ってみたものの、kawachiさんが似たようなことをすでにやっていたのでgistに貼って供養します。
* → GitLab CI で Scala のテストを雑に速くする方法 http://labs.septeni.co.jp/entry/2018/11/23/170627
*
* ハッシュの剰余を使うというアプローチは同じですが、使うハッシュの種類が違います。
* このプラグインはCRC32を使っていますが、kawachiさんはhashCodeメソッドを使っていて、
* 確かにそれで十分ですよね。
* このプラグインではインプットタスクの仕組みを使っているけど、環境変数使う方がシンプルで良いですね。
*/
object SplitTestPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
/**
* 分割実行用のconfigurationを作る
*
* configurationを使って設定を変えたテストを実行する方法については下記リンクあたりを見ると良い
* https://www.scala-sbt.org/1.x/docs/Testing.html#Additional+test+configurations
*/
lazy val SplitTest = config("splitTest").extend(Test)
object autoImport {
lazy val splitTest = InputKey[Unit]("splitTest", "Run tests")
lazy val splitTestFailure = TaskKey[Unit]("splitTestFailure", "splitTest failure")
}
import autoImport._
/** テストを何分割するか */
private var numOfChunk = 1
/** 分割したテストの何番目を実行するか */
private var index = 1
override def projectConfigurations: Seq[Configuration] = Seq(SplitTest)
override def projectSettings: Seq[Def.Setting[_]] = {
inConfig(SplitTest)(Defaults.testTasks) ++
Seq(
testOptions in SplitTest := Seq(Tests.Filter(splitTestFilter)),
splitTestFailure := {},
// Def.inputTaskDyn を用いた動的インプットタスクの定義
// https://www.scala-sbt.org/1.x/docs/ja/Howto-Dynamic-Input-Task.html
splitTest := Def.inputTaskDyn {
import complete.DefaultParsers._
val args: Seq[String] = spaceDelimited("<arg>").parsed
if (args.size != 2) {
val message = """|
|Usage:
|> splitTest <numOfChunk> <index>
|
|Example:
|> splitTest 3 1
|> splitTest 3 2
|> splitTest 3 3
|""".stripMargin
Def.taskDyn {
splitTestFailure
}
} else {
Def.taskDyn {
numOfChunk = args.head.toInt
index = args(1).toInt
(test in SplitTest)
}
}
}.evaluated
)
}
private def splitTestFilter(name: String): Boolean = {
import java.util.zip.CRC32
val crc = new CRC32
crc.update(name.getBytes())
val value = crc.getValue
value % numOfChunk == (index - 1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment