mirror of https://github.com/sbt/sbt.git
Merge pull request #5527 from frosforever/tag-fork-groups
Add user defined tags to Tests.Group to allow for better concurrency control
This commit is contained in:
commit
5d97d643b4
|
|
@ -28,7 +28,7 @@ private[sbt] object ForkTests {
|
|||
classpath: Seq[File],
|
||||
fork: ForkOptions,
|
||||
log: Logger,
|
||||
tag: Tag
|
||||
tags: (Tag, Int)*
|
||||
): Task[TestOutput] = {
|
||||
val opts = processOptions(config, tests, log)
|
||||
|
||||
|
|
@ -41,11 +41,23 @@ private[sbt] object ForkTests {
|
|||
constant(TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty))
|
||||
else
|
||||
mainTestTask(runners, opts, classpath, fork, log, config.parallel).tagw(config.tags: _*)
|
||||
main.tag(tag).dependsOn(all(opts.setup): _*) flatMap { results =>
|
||||
main.tagw(tags: _*).dependsOn(all(opts.setup): _*) flatMap { results =>
|
||||
all(opts.cleanup).join.map(_ => results)
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
runners: Map[TestFramework, Runner],
|
||||
tests: Vector[TestDefinition],
|
||||
config: Execution,
|
||||
classpath: Seq[File],
|
||||
fork: ForkOptions,
|
||||
log: Logger,
|
||||
tag: Tag
|
||||
): Task[TestOutput] = {
|
||||
apply(runners, tests, config, classpath, fork, log, tag -> 1)
|
||||
}
|
||||
|
||||
private[this] def mainTestTask(
|
||||
runners: Map[TestFramework, Runner],
|
||||
opts: ProcessedOptions,
|
||||
|
|
|
|||
|
|
@ -16,24 +16,25 @@ import xsbti.api.Definition
|
|||
import xsbti.api.ClassLike
|
||||
import xsbti.compile.CompileAnalysis
|
||||
import ConcurrentRestrictions.Tag
|
||||
|
||||
import testing.{
|
||||
AnnotatedFingerprint,
|
||||
Fingerprint,
|
||||
Framework,
|
||||
SubclassFingerprint,
|
||||
Runner,
|
||||
TaskDef,
|
||||
Selector,
|
||||
SubclassFingerprint,
|
||||
SuiteSelector,
|
||||
TaskDef,
|
||||
Task => TestTask
|
||||
}
|
||||
import scala.annotation.tailrec
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import sbt.internal.util.ManagedLogger
|
||||
import sbt.util.Logger
|
||||
import sbt.protocol.testing.TestResult
|
||||
|
||||
import scala.runtime.AbstractFunction3
|
||||
|
||||
sealed trait TestOption
|
||||
|
||||
object Tests {
|
||||
|
|
@ -133,7 +134,113 @@ object Tests {
|
|||
final case class SubProcess(config: ForkOptions) extends TestRunPolicy
|
||||
|
||||
/** A named group of tests configured to run in the same JVM or be forked. */
|
||||
final case class Group(name: String, tests: Seq[TestDefinition], runPolicy: TestRunPolicy)
|
||||
final class Group(
|
||||
val name: String,
|
||||
val tests: Seq[TestDefinition],
|
||||
val runPolicy: TestRunPolicy,
|
||||
val tags: Seq[(Tag, Int)]
|
||||
) extends Product
|
||||
with Serializable {
|
||||
|
||||
def this(name: String, tests: Seq[TestDefinition], runPolicy: TestRunPolicy) = {
|
||||
this(name, tests, runPolicy, Seq.empty)
|
||||
}
|
||||
|
||||
def withName(name: String): Group = {
|
||||
new Group(name, tests, runPolicy, tags)
|
||||
}
|
||||
|
||||
def withTests(tests: Seq[TestDefinition]): Group = {
|
||||
new Group(name, tests, runPolicy, tags)
|
||||
}
|
||||
|
||||
def withRunPolicy(runPolicy: TestRunPolicy): Group = {
|
||||
new Group(name, tests, runPolicy, tags)
|
||||
}
|
||||
|
||||
def withTags(tags: Seq[(Tag, Int)]): Group = {
|
||||
new Group(name, tests, runPolicy, tags)
|
||||
}
|
||||
|
||||
//- EXPANDED CASE CLASS METHOD BEGIN -//
|
||||
@deprecated("Methods generated for case class will be removed in the future.", "1.4.0")
|
||||
def copy(
|
||||
name: String = this.name,
|
||||
tests: Seq[TestDefinition] = this.tests,
|
||||
runPolicy: TestRunPolicy = this.runPolicy
|
||||
): Group = {
|
||||
new Group(name, tests, runPolicy, this.tags)
|
||||
}
|
||||
|
||||
@deprecated("Methods generated for case class will be removed in the future.", "1.4.0")
|
||||
override def productElement(x$1: Int): Any = x$1 match {
|
||||
case 0 => Group.this.name
|
||||
case 1 => Group.this.tests
|
||||
case 2 => Group.this.runPolicy
|
||||
case 3 => Group.this.tags
|
||||
}
|
||||
|
||||
@deprecated("Methods generated for case class will be removed in the future.", "1.4.0")
|
||||
override def productArity: Int = 4
|
||||
|
||||
@deprecated("Methods generated for case class will be removed in the future.", "1.4.0")
|
||||
def canEqual(x$1: Any): Boolean = x$1.isInstanceOf[Group]
|
||||
|
||||
override def hashCode(): Int = {
|
||||
scala.runtime.ScalaRunTime._hashCode(Group.this)
|
||||
}
|
||||
|
||||
override def toString(): String = scala.runtime.ScalaRunTime._toString(Group.this)
|
||||
|
||||
override def equals(x$1: Any): Boolean = {
|
||||
this.eq(x$1.asInstanceOf[Object]) || (x$1.isInstanceOf[Group] && ({
|
||||
val Group$1: Group = x$1.asInstanceOf[Group]
|
||||
name == Group$1.name && tests == Group$1.tests &&
|
||||
runPolicy == Group$1.runPolicy && tags == Group$1.tags
|
||||
}))
|
||||
}
|
||||
//- EXPANDED CASE CLASS METHOD END -//
|
||||
}
|
||||
|
||||
object Group
|
||||
extends AbstractFunction3[String, Seq[TestDefinition], TestRunPolicy, Group]
|
||||
with Serializable {
|
||||
//- EXPANDED CASE CLASS METHOD BEGIN -//
|
||||
final override def toString(): String = "Group"
|
||||
def apply(
|
||||
name: String,
|
||||
tests: Seq[TestDefinition],
|
||||
runPolicy: TestRunPolicy
|
||||
): Group = {
|
||||
new Group(name, tests, runPolicy, Seq.empty)
|
||||
}
|
||||
|
||||
def apply(
|
||||
name: String,
|
||||
tests: Seq[TestDefinition],
|
||||
runPolicy: TestRunPolicy,
|
||||
tags: Seq[(Tag, Int)]
|
||||
): Group = {
|
||||
new Group(name, tests, runPolicy, tags)
|
||||
}
|
||||
|
||||
@deprecated("Methods generated for case class will be removed in the future.", "1.4.0")
|
||||
def unapply(
|
||||
x$0: Group
|
||||
): Option[(String, Seq[TestDefinition], TestRunPolicy)] = {
|
||||
if (x$0 == null) None
|
||||
else
|
||||
Some.apply[(String, Seq[TestDefinition], TestRunPolicy)](
|
||||
Tuple3.apply[String, Seq[TestDefinition], TestRunPolicy](
|
||||
x$0.name,
|
||||
x$0.tests,
|
||||
x$0.runPolicy
|
||||
)
|
||||
)
|
||||
}
|
||||
private def readResolve(): Object = Group
|
||||
//- EXPANDED CASE CLASS METHOD END -//
|
||||
}
|
||||
|
||||
private[sbt] final class ProcessedOptions(
|
||||
val tests: Vector[TestDefinition],
|
||||
|
|
|
|||
|
|
@ -1030,7 +1030,14 @@ object Defaults extends BuildCommon {
|
|||
val tests = definedTests.value
|
||||
val fk = fork.value
|
||||
val opts = forkOptions.value
|
||||
Seq(new Tests.Group("<default>", tests, if (fk) Tests.SubProcess(opts) else Tests.InProcess))
|
||||
Seq(
|
||||
new Tests.Group(
|
||||
"<default>",
|
||||
tests,
|
||||
if (fk) Tests.SubProcess(opts) else Tests.InProcess,
|
||||
Seq.empty
|
||||
)
|
||||
)
|
||||
}
|
||||
def forkOptionsTask: Initialize[Task[ForkOptions]] =
|
||||
Def.task {
|
||||
|
|
@ -1207,28 +1214,34 @@ object Defaults extends BuildCommon {
|
|||
projectId: String
|
||||
): Initialize[Task[Tests.Output]] = {
|
||||
val runners = createTestRunners(frameworks, loader, config)
|
||||
val groupTasks = groups map {
|
||||
case Tests.Group(_, tests, runPolicy) =>
|
||||
runPolicy match {
|
||||
case Tests.SubProcess(opts) =>
|
||||
s.log.debug(s"javaOptions: ${opts.runJVMOptions}")
|
||||
val forkedConfig = config.copy(parallel = config.parallel && forkedParallelExecution)
|
||||
s.log.debug(s"Forking tests - parallelism = ${forkedConfig.parallel}")
|
||||
ForkTests(
|
||||
runners,
|
||||
tests.toVector,
|
||||
forkedConfig,
|
||||
cp.files,
|
||||
opts,
|
||||
s.log,
|
||||
Tags.ForkedTestGroup
|
||||
)
|
||||
case Tests.InProcess =>
|
||||
if (javaOptions.nonEmpty) {
|
||||
s.log.warn("javaOptions will be ignored, fork is set to false")
|
||||
}
|
||||
Tests(frameworks, loader, runners, tests.toVector, config, s.log)
|
||||
}
|
||||
val groupTasks = groups map { group =>
|
||||
group.runPolicy match {
|
||||
case Tests.SubProcess(opts) =>
|
||||
s.log.debug(s"javaOptions: ${opts.runJVMOptions}")
|
||||
val forkedConfig = config.copy(parallel = config.parallel && forkedParallelExecution)
|
||||
s.log.debug(s"Forking tests - parallelism = ${forkedConfig.parallel}")
|
||||
ForkTests(
|
||||
runners,
|
||||
group.tests.toVector,
|
||||
forkedConfig,
|
||||
cp.files,
|
||||
opts,
|
||||
s.log,
|
||||
(Tags.ForkedTestGroup, 1) +: group.tags: _*
|
||||
)
|
||||
case Tests.InProcess =>
|
||||
if (javaOptions.nonEmpty) {
|
||||
s.log.warn("javaOptions will be ignored, fork is set to false")
|
||||
}
|
||||
Tests(
|
||||
frameworks,
|
||||
loader,
|
||||
runners,
|
||||
group.tests.toVector,
|
||||
config.copy(tags = config.tags ++ group.tags),
|
||||
s.log
|
||||
)
|
||||
}
|
||||
}
|
||||
val output = Tests.foldTasks(groupTasks, config.parallel)
|
||||
val result = output map { out =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
val specs = "org.specs2" %% "specs2-core" % "4.3.4"
|
||||
ThisBuild / scalaVersion := "2.12.11"
|
||||
|
||||
val TestATypeTag = Tags.Tag("TestA")
|
||||
val TestBTypeTag = Tags.Tag("TestB")
|
||||
|
||||
Global / concurrentRestrictions := Seq(Tags.limit(TestATypeTag, 1), Tags.limit(TestBTypeTag, 1))
|
||||
|
||||
libraryDependencies += specs % Test
|
||||
inConfig(Test)(Seq(
|
||||
testGrouping := {
|
||||
val home = javaHome.value
|
||||
val strategy = outputStrategy.value
|
||||
val baseDir = baseDirectory.value
|
||||
val options = javaOptions.value
|
||||
val connect = connectInput.value
|
||||
val vars = envVars.value
|
||||
|
||||
definedTests.value.map { test => new Tests.Group(test.name, Seq(test), Tests.SubProcess(
|
||||
ForkOptions(
|
||||
javaHome = home,
|
||||
outputStrategy = strategy,
|
||||
bootJars = Vector(),
|
||||
workingDirectory = Some(baseDir),
|
||||
runJVMOptions = options.toVector,
|
||||
connectInput = connect,
|
||||
envVars = vars
|
||||
)
|
||||
), Seq((if (test.name.contains("TestA")) TestATypeTag else TestBTypeTag) -> 1))
|
||||
}
|
||||
},
|
||||
TaskKey[Unit]("test-failure") := test.failure.value
|
||||
))
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package example
|
||||
|
||||
import java.io.File
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
trait Test extends Specification {
|
||||
def testType: String
|
||||
"spec" should {
|
||||
"be run one at time" in {
|
||||
val lock = new File(s"lock${testType}")
|
||||
println(lock)
|
||||
lock.mkdir() should beTrue
|
||||
Thread.sleep(2000)
|
||||
lock.delete() should beTrue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestA0 extends Test {
|
||||
override def testType: String = "A"
|
||||
}
|
||||
|
||||
class TestA1 extends Test {
|
||||
override def testType: String = "A"
|
||||
}
|
||||
|
||||
class TestB0 extends Test {
|
||||
override def testType: String = "B"
|
||||
}
|
||||
|
||||
class TestB1 extends Test {
|
||||
override def testType: String = "B"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
> test
|
||||
> set concurrentRestrictions in Global := Seq(Tags.limitAll(4))
|
||||
> testFailure
|
||||
Loading…
Reference in New Issue