sbtでテストの並列実行を制御する設定

環境:
* sbt 1.10.0 
* Oracle Corporation Java 17.0.11
* MacOS

Sbtでテストを実行する時に並列実行するかどうかを制御するためのパラメータがいくつかあります。
それらのパラメータの意味と実際の設定値ごとの動作をまとめます。

# 設定項目

* `Test / parallelExecution`
  * １プロジェクト内でテストクラスごとに並列で実行するかどうか
* `Test / fork`
  * １プロジェクトごとにJVMをforkして独立したプロセスで実行するかどうか
* `Test / testForkedParallel`
  * forkする場合にテストクラスごとに並列で実行するかどうか
* `concurrentRestrictions`の`Tags.limitAll(num)`
  * １プロジェクト内で並列実行する時の並列数
* `concurrentRestrictions`の`Tags.limit(Tags.ForkedTestGroup, num)`
  * forkで実行するときにプロジェクトごとの並列実行数(= 並列でforkする数)

# 設定ごとの実際の動作

検証用のbuiid.sbtにて各設定項目の影響を確認しました。

* fork 〜 testForkedParallelまでが設定項目の値、Projects〜JVM processesまでが動作の結果
  * maxは並列数の値をCPUコア数を指定したことを意味する
* Projects: プロジェクト(hello, alpha, beta)ごとに並列で実行されるかどうか
* Test classes: 1つのプロジェクト内でテストクラスごとに並列実行されるかどうか
* Test cases: １つのテストクラス内のテストケースごとに並列実行されるかどうか
* JVM processes： JVMのプロセスがforkされるかどうか。どの単位でforkされるか
* １行目のパターンが何も指定していない時のデフォルトでの設定

(凡例） P: Parallel, S: Sequential

| fork  | parallelExecution | Tags.limitAll | testForkedParallel | Tags.ForkedTestGroup | Projects | Test classes | Test cases | JVM processes     |
| ---   | ---               | ---   | ---                | ---             | ---      | ---          | ---        | ---               |
| false | false             | max   | false              | 1               | P        | S            | S          | not fork            |
| false | false             | 1     | false              | 1               | S        | S            | S          | not fork            |
| false | true              | max   | false              | 1               | P        | P            | S          | not fork            |
| false | true              | max   | true               | 1               | P        | P            | S          | not fork            |
| true  | false             | max   | false              | 1               | S        | S            | S          | fork each sub projects |
| true  | false             | max   | true               | 1               | S        | S            | S          | fork each sub projects |
| true  | true              | max   | false              | 1               | S        | S            | S          | fork each sub projects |
| true  | true              | max   | true               | 1               | S        | P            | S          | fork each sub projects |
| true  | true              | 1     | true               | 1               | S        | P            | S          | fork each sub projects |
| true  | true              | 1     | true               | max             | S        | P            | S          | fork each sub projects |
| true  | false             | max   | false              | max             | P        | S            | S          | fork each sub projects |
| true  | false             | max   | true               | max             | P        | S            | S          | fork each sub projects |
| true  | true              | max   | true               | max             | P        | P            | S          | fork each sub projects |

## まとめ

* forkするかどうかで、並列に実行されるかが変わる
* デフォルト状態でプロジェクトごとでは並列実行される
* forkしない状態
  * parallelExecutionの値に応じて、１プロジェクト内でテストクラスごとの並列実行するかどうかが変わる
  * 基本的に、プロジェクト単位では並列実行される
      * Tags.ForkedTestGroupで設定するは影響しない
  * limitAllの値で、プロジェクト単位／テストクラス単位のトータルでの並列実行数を指定できる
  * limitAllを１にすると、全体的に直列実行される
* forkする場合
    * プロジェクト単位での並列実行数をTags.ForkedTestGroupで設定する
    * プロジェクト内でテストクラスごとに並列実行するかどうかは、parallelExecutionとtestForkedParallelの両方がtrueの場合のみ
    * Tags.limitAllの値は影響しない
* テストケースごとで並列実行されることはない


# 動作確認用コード

```scala:build.sbt
import scala.sys.process._
import com.typesafe.sbt.packager.archetypes.JavaAppPackaging._

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

def createTestSettings(
  _parallelExecution: Boolean,
  _limit: Int,
  _fork: Boolean,
  _testForkedParallel: Boolean,
  _forkedTestGroup: Int
): Seq[Def.Setting[_ >: Boolean with Task[Seq[TestOption]] with Seq[Tags.Rule]]] =
  Seq(
    // テストの並列実行に関連するパラメータ
    Test / fork := _fork,
    Test / parallelExecution := _parallelExecution,
    Test / testForkedParallel := _testForkedParallel,
    concurrentRestrictions := Seq(
       Tags.limitAll(_limit),
       Tags.limit(Tags.ForkedTestGroup, _forkedTestGroup),
       Tags.exclusiveGroup(Tags.Clean)
    )
  )

val max = EvaluateTask.SystemProcessors
val testSettings = createTestSettings(false, max, true, true, 1)

val testLibs = Seq(
  libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.16" % "test",
)

val showSettings = taskKey[Unit]("show test settings")

lazy val hello = (project in file("."))
  .settings(testLibs)
  .settings(testSettings)
  .settings(
    // 設定の内容を表示させる
    showSettings := {
      println(
        s"""
           |- parallelExecution: ${(Test / parallelExecution).value},
           |- concurrentRestrictions: ${(Test / concurrentRestrictions).value},
           |- fork: ${(Test / fork).value},
           |- testForkedParallel: ${(Test / testForkedParallel).value},
           |""".stripMargin.trim)
    }
  )
  .aggregate(alpha, beta)

lazy val alpha = (project in file("./alpha"))
  .settings(testLibs)
  .settings(testSettings)

lazy val beta = (project in file("./beta"))
  .settings(testLibs)
  .settings(testSettings)
```

# 参考にした資料

* sbtにおけるテストの並列実行の設定詳細解説 - xuwei-k's blog
  * https://xuwei-k.hatenablog.com/entry/2020/03/14/234758
* sbt Reference Manual
  * https://www.scala-sbt.org/1.x/docs/offline/Testing.html#Disable+Parallel+Execution+of+Tests