Last active
November 8, 2019 09:00
-
-
Save tototoshi/638505c2fead8e4193c3621b321b5eaa to your computer and use it in GitHub Desktop.
テストを並列実行するsbtプラグイン
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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