標記の件について、ここ2日間取り組んでいます。もちろん、ボランティアです。
元ネタはじょーたにさんに教えてもらったこのツイート。
JUnitの実行を各クラス毎に並列で実行して、かつそれぞれをDockerで実行してくれるようなものは無いかな..
— たむたむ (@tamtam180) 2015, 10月 16
@johtani 複数台だと嬉しいけど単一マシンでもok。外部依存があるからdockerで隔離しないと並列実行ができないのです。
— たむたむ (@tamtam180) 2015, 10月 16
これ、最初は簡単だろうと思ってたけど、とてつもなく難しいテーマです(未解決)。あまりに思い通りに行かないので、自殺したいレベル。
実装方針
- 分散してテストが実行できるように適度に振り分けたテストタスクを準備します。
- テストクラスまでのコンパイルを行います。
project-root/build.gradle
はCOPY
で、project-root/build/
はVOLUME
で載せられるようにdockerイメージを作成します。project-root/build
などをボリューム指定して起動するdocker-compose.yml
ファイルをテストタスクの数だけ作ります(註1)。ExecutorService
でテストタスクの数+1でdocker-compose
でコンテナを起動、それぞれのテストタスクを実行させます。project-root/build
は共有されているので、それぞれのテストタスクの実行結果をTestReport
タスクでまとめます。
(註1) … ひとつのdocker-compose.yml
だけで起動した場合、一つのコンテナでビルドが終わると、まだビルドの終わっていないコンテナを終了してしまうので、テストタスク個別にdockerを起動しないといけません。これの回避方法を探したのですけど、どうやらない模様。
ドハマリその1
project-root/.gradle/
ディレクトリーも共有してしまった結果、ここにあるロックファイルで競合が発生して、ロック取得を待ってテストを行うか、ロック取得できなくてビルドが落ちます。その結果、ビルドが不安定かつロックファイルの待ちが発生してビルドが並列で実行できないという本来の目的が達成できなくなる。
したがって、project-root/.gradle/
ディレクトリーは共有の対象外にしないといけないようです。
ドハマリその2
project-root/.gradle/
ディレクトリーを共有対象外にしてしまった結果、project-root/build
ディレクトリーを共有していても、タスクのUP-TO-DATE
の管理ができない(project-root/.gradle/
ディレクトリーにクラスファイルのハッシュなどの情報が格納されており、そのハッシュと実際のクラスのハッシュを比較しているが、その比較ができない)ために、再度Javaファイルのコンパイルを実行しようとする。
その結果、各コンテナでproject-root/build/dependency-cache/
ディレクトリーを作成しようとして、競合が発生して、ディレクトリーの作成に失敗、その結果ビルドが失敗する
したがって、project-root/.gradle/
ディレクトリーはやはり共有する必要があるという結論になり、ドハマリその1に戻る
結論
Gradle万能派な僕ですけど、本件に関しては、おとなしく、JenkinsなどのCIでmaster/slave構成でテストした方がいいでしょう。
ちなみに、見るも無残な結果だけギッハブにあげてあります。
ここのコードでも見て、笑うなり何なりしてください(自暴自棄)