mirror of https://github.com/sbt/sbt.git
Apply setup and cleanup hooks to forked tests.
The actual class loader for test classes cannot be provided because it is in another jvm.
This commit is contained in:
parent
c2df1e8d68
commit
c0e06a14fe
|
|
@ -10,80 +10,93 @@ import java.io._
|
|||
import Tests.{Output => TestOutput, _}
|
||||
import ForkMain._
|
||||
|
||||
private[sbt] object ForkTests {
|
||||
private[sbt] object ForkTests
|
||||
{
|
||||
def apply(runners: Map[TestFramework, Runner], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger): Task[TestOutput] = {
|
||||
val opts = processOptions(config, tests, log)
|
||||
val testListeners = opts.testListeners flatMap {
|
||||
case tl: TestsListener => Some(tl)
|
||||
case _ => None
|
||||
|
||||
import std.TaskExtra._
|
||||
val dummyLoader = this.getClass.getClassLoader // can't provide the loader for test classes, which is in another jvm
|
||||
def all(work: Seq[ClassLoader => Unit]) = work.fork(f => f(dummyLoader))
|
||||
|
||||
val main =
|
||||
if(opts.tests.isEmpty)
|
||||
constant( TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty) )
|
||||
else
|
||||
mainTestTask(runners, opts, classpath, fork, log).tagw(config.tags: _*)
|
||||
main.dependsOn( all(opts.setup) : _*) flatMap { results =>
|
||||
all(opts.cleanup).join.map( _ => results)
|
||||
}
|
||||
}
|
||||
|
||||
std.TaskExtra.task {
|
||||
if (!tests.isEmpty) {
|
||||
val server = new ServerSocket(0)
|
||||
object Acceptor extends Runnable {
|
||||
val resultsAcc = mutable.Map.empty[String, SuiteResult]
|
||||
lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty)
|
||||
def run: Unit = {
|
||||
val socket =
|
||||
try {
|
||||
server.accept()
|
||||
} catch {
|
||||
case _: java.net.SocketException => return
|
||||
}
|
||||
val os = new ObjectOutputStream(socket.getOutputStream)
|
||||
val is = new ObjectInputStream(socket.getInputStream)
|
||||
private[this] def mainTestTask(runners: Map[TestFramework, Runner], opts: ProcessedOptions, classpath: Seq[File], fork: ForkOptions, log: Logger): Task[TestOutput] =
|
||||
std.TaskExtra.task
|
||||
{
|
||||
val server = new ServerSocket(0)
|
||||
val testListeners = opts.testListeners flatMap {
|
||||
case tl: TestsListener => Some(tl)
|
||||
case _ => None
|
||||
}
|
||||
|
||||
object Acceptor extends Runnable {
|
||||
val resultsAcc = mutable.Map.empty[String, SuiteResult]
|
||||
lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty)
|
||||
def run: Unit = {
|
||||
val socket =
|
||||
try {
|
||||
os.writeBoolean(log.ansiCodesSupported)
|
||||
|
||||
val testsFiltered = opts.tests.map{
|
||||
t => new TaskDef(t.name, forkFingerprint(t.fingerprint), t.explicitlySpecified, t.selectors)
|
||||
}.toArray
|
||||
os.writeObject(testsFiltered)
|
||||
|
||||
os.writeInt(runners.size)
|
||||
for ((testFramework, mainRunner) <- runners) {
|
||||
val remoteArgs = mainRunner.remoteArgs()
|
||||
os.writeObject(testFramework.implClassNames.toArray)
|
||||
os.writeObject(mainRunner.args)
|
||||
os.writeObject(remoteArgs)
|
||||
}
|
||||
os.flush()
|
||||
|
||||
(new React(is, os, log, opts.testListeners, resultsAcc)).react()
|
||||
} finally {
|
||||
is.close(); os.close(); socket.close()
|
||||
server.accept()
|
||||
} catch {
|
||||
case _: java.net.SocketException => return
|
||||
}
|
||||
val os = new ObjectOutputStream(socket.getOutputStream)
|
||||
val is = new ObjectInputStream(socket.getInputStream)
|
||||
|
||||
try {
|
||||
os.writeBoolean(log.ansiCodesSupported)
|
||||
|
||||
val taskdefs = opts.tests.map(t => new TaskDef(t.name, forkFingerprint(t.fingerprint), t.explicitlySpecified, t.selectors))
|
||||
os.writeObject(taskdefs.toArray)
|
||||
|
||||
os.writeInt(runners.size)
|
||||
for ((testFramework, mainRunner) <- runners) {
|
||||
val remoteArgs = mainRunner.remoteArgs()
|
||||
os.writeObject(testFramework.implClassNames.toArray)
|
||||
os.writeObject(mainRunner.args)
|
||||
os.writeObject(remoteArgs)
|
||||
}
|
||||
os.flush()
|
||||
|
||||
(new React(is, os, log, opts.testListeners, resultsAcc)).react()
|
||||
} finally {
|
||||
is.close(); os.close(); socket.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
testListeners.foreach(_.doInit())
|
||||
val acceptorThread = new Thread(Acceptor)
|
||||
acceptorThread.start()
|
||||
try {
|
||||
testListeners.foreach(_.doInit())
|
||||
val acceptorThread = new Thread(Acceptor)
|
||||
acceptorThread.start()
|
||||
|
||||
val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework])
|
||||
val options = Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString)
|
||||
val ec = Fork.java(fork, options)
|
||||
val result =
|
||||
if (ec != 0)
|
||||
TestOutput(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), Iterable.empty)
|
||||
else {
|
||||
// Need to wait acceptor thread to finish its business
|
||||
acceptorThread.join()
|
||||
Acceptor.result
|
||||
}
|
||||
|
||||
testListeners.foreach(_.doComplete(result.overall))
|
||||
result
|
||||
} finally {
|
||||
server.close()
|
||||
}
|
||||
}
|
||||
|
||||
val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework])
|
||||
val options = Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString)
|
||||
val ec = Fork.java(fork, options)
|
||||
val result =
|
||||
if (ec != 0)
|
||||
TestOutput(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), Iterable.empty)
|
||||
else {
|
||||
// Need to wait acceptor thread to finish its business
|
||||
acceptorThread.join()
|
||||
Acceptor.result
|
||||
}
|
||||
|
||||
testListeners.foreach(_.doComplete(result.overall))
|
||||
result
|
||||
} finally {
|
||||
server.close()
|
||||
}
|
||||
} else
|
||||
TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty)
|
||||
} tagw (config.tags: _*)
|
||||
}
|
||||
private[this] def forkFingerprint(f: Fingerprint): Fingerprint with Serializable =
|
||||
f match {
|
||||
case s: SubclassFingerprint => new ForkMain.SubclassFingerscan(s)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1"
|
||||
|
|
@ -0,0 +1 @@
|
|||
fork in Test := true
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
testOptions in Test +=
|
||||
Tests.Setup { () =>
|
||||
IO.touch(baseDirectory.value / "setup")
|
||||
}
|
||||
|
||||
testOptions in Test += {
|
||||
val t = baseDirectory.value / "tested"
|
||||
val c = baseDirectory.value / "cleanup"
|
||||
Tests.Cleanup { () =>
|
||||
assert(t.exists, "Didn't exist: " + t.getAbsolutePath)
|
||||
IO.delete(t)
|
||||
IO.touch(c)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import org.scalatest.FlatSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import java.io.File
|
||||
|
||||
class CheckSetupCleanup extends FlatSpec with MustMatchers
|
||||
{
|
||||
val f = new File("setup")
|
||||
"setup file" must "exist" in {
|
||||
assert(f.exists, "Setup file didn't exist: " + f.getAbsolutePath)
|
||||
f.delete()
|
||||
}
|
||||
|
||||
val t = new File("tested")
|
||||
"cleanup file" must "not exist" in {
|
||||
assert(!t.exists, "Cleanup file already existed: " + t.getAbsolutePath)
|
||||
t.createNewFile()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# check that we are starting clean
|
||||
$ absent setup
|
||||
$ absent tested
|
||||
$ absent cleanup
|
||||
|
||||
# without Setup configured, the setup file won't exist and the test will fail
|
||||
-> test
|
||||
|
||||
# check that we are starting clean
|
||||
$ absent setup
|
||||
$ exists tested
|
||||
$ delete tested
|
||||
$ absent cleanup
|
||||
|
||||
|
||||
$ copy-file changes/setup.sbt setup.sbt
|
||||
> reload
|
||||
> test
|
||||
|
||||
# setup should have been deleted by the test
|
||||
$ absent setup
|
||||
# tested should have been deleted by the cleanup
|
||||
$ absent tested
|
||||
# cleanup should have been created by cleanup
|
||||
$ exists cleanup
|
||||
$ delete cleanup
|
||||
|
||||
|
||||
# Same test for forking
|
||||
|
||||
|
||||
$ delete setup.sbt
|
||||
$ copy-file changes/fork.sbt fork.sbt
|
||||
> reload
|
||||
|
||||
# check that we are starting clean
|
||||
$ absent setup
|
||||
$ absent tested
|
||||
$ absent cleanup
|
||||
|
||||
# without Setup configured, the setup file won't exist and the test will fail
|
||||
-> test
|
||||
|
||||
# check that we are starting clean
|
||||
$ absent setup
|
||||
$ exists tested
|
||||
$ delete tested
|
||||
$ absent cleanup
|
||||
|
||||
|
||||
$ copy-file changes/setup.sbt setup.sbt
|
||||
> reload
|
||||
> test
|
||||
|
||||
# setup should have been deleted by the test
|
||||
$ absent setup
|
||||
# tested should have been deleted by the cleanup
|
||||
$ absent tested
|
||||
# cleanup should have been created by cleanup
|
||||
$ exists cleanup
|
||||
Loading…
Reference in New Issue