diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 4bbe95c5a..981c714cf 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -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) diff --git a/sbt/src/sbt-test/tests/setup-cleanup/base.sbt b/sbt/src/sbt-test/tests/setup-cleanup/base.sbt new file mode 100644 index 000000000..afd509cbc --- /dev/null +++ b/sbt/src/sbt-test/tests/setup-cleanup/base.sbt @@ -0,0 +1 @@ +libraryDependencies += "org.scalatest" %% "scalatest" % "1.9.1" \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/setup-cleanup/changes/fork.sbt b/sbt/src/sbt-test/tests/setup-cleanup/changes/fork.sbt new file mode 100644 index 000000000..dfdf7b740 --- /dev/null +++ b/sbt/src/sbt-test/tests/setup-cleanup/changes/fork.sbt @@ -0,0 +1 @@ +fork in Test := true \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt b/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt new file mode 100644 index 000000000..d9abc7285 --- /dev/null +++ b/sbt/src/sbt-test/tests/setup-cleanup/changes/setup.sbt @@ -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) + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/setup-cleanup/src/test/scala/CheckSetupCleanup.scala b/sbt/src/sbt-test/tests/setup-cleanup/src/test/scala/CheckSetupCleanup.scala new file mode 100644 index 000000000..054d6019b --- /dev/null +++ b/sbt/src/sbt-test/tests/setup-cleanup/src/test/scala/CheckSetupCleanup.scala @@ -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() + } +} diff --git a/sbt/src/sbt-test/tests/setup-cleanup/test b/sbt/src/sbt-test/tests/setup-cleanup/test new file mode 100644 index 000000000..b2bc42423 --- /dev/null +++ b/sbt/src/sbt-test/tests/setup-cleanup/test @@ -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 \ No newline at end of file