From e9b4b5c767981609751fab4a4e9c61c5e7a7338b Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Thu, 11 Feb 2016 13:44:00 +0100 Subject: [PATCH 1/2] Integrate scala-partest-interface for sbt into scala-partest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s only a few lines of code, it ends up on the same ClassLoader in the Scala build, and we already have a dependency on sbt’s test-interface here anyway. The test framework class name changes from `scala.tools.partest.Framework` to `scala.tools.partest.sbt.Framework`. --- .../scala/tools/partest/nest/SBTRunner.scala | 10 +-- .../scala/tools/partest/sbt/Framework.scala | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 src/main/scala/scala/tools/partest/sbt/Framework.scala diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index acc956f..f250d61 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -9,15 +9,7 @@ package scala.tools.partest package nest -import sbt.testing.EventHandler -import sbt.testing.Logger -import sbt.testing.Event -import sbt.testing.Fingerprint -import sbt.testing.Selector -import sbt.testing.Status -import sbt.testing.OptionalThrowable -import sbt.testing.SuiteSelector -import sbt.testing.TestSelector +import _root_.sbt.testing._ import java.net.URLClassLoader import TestState._ diff --git a/src/main/scala/scala/tools/partest/sbt/Framework.scala b/src/main/scala/scala/tools/partest/sbt/Framework.scala new file mode 100644 index 0000000..4fd8e69 --- /dev/null +++ b/src/main/scala/scala/tools/partest/sbt/Framework.scala @@ -0,0 +1,70 @@ +package scala.tools.partest.sbt + +import scala.language.reflectiveCalls + +import _root_.sbt.testing._ +import java.net.URLClassLoader +import java.io.File + +object Framework { + // as partest is not driven by test classes discovered by sbt, need to add this marker fingerprint to definedTests + val fingerprint = new AnnotatedFingerprint { def isModule = true; def annotationName = "partest" } + + // TODO how can we export `fingerprint` so that a user can just add this to their build.sbt + // definedTests in Test += new sbt.TestDefinition("partest", fingerprint, true, Array()) +} +class Framework extends sbt.testing.Framework { + def fingerprints: Array[Fingerprint] = Array(Framework.fingerprint) + def name: String = "partest" + + def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): sbt.testing.Runner = + new Runner(args, remoteArgs, testClassLoader) +} + +/** Represents one run of a suite of tests. + */ +case class Runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader) extends sbt.testing.Runner { + + def tasks(taskDefs: Array[TaskDef]): Array[sbt.testing.Task] = taskDefs map (SbtPartestTask(_, args): sbt.testing.Task) + + /** Indicates the client is done with this Runner instance. + * + * @return a possibly multi-line summary string, or the empty string if no summary is provided -- TODO + */ + def done(): String = "" +} + +/** Run partest in this VM. Assumes we're running in a forked VM! + */ +case class SbtPartestTask(taskDef: TaskDef, args: Array[String]) extends Task { + /** Executes this task, possibly returning to the client new tasks to execute. */ + def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = { + val forkedCp = scala.util.Properties.javaClassPath + val classLoader = new URLClassLoader(forkedCp.split(java.io.File.pathSeparator).map(new File(_).toURI.toURL)) + val runner = SBTRunner(Framework.fingerprint, eventHandler, loggers, "files", classLoader, null, null, Array.empty[String], args) + + if (Runtime.getRuntime().maxMemory() / (1024*1024) < 800) + loggers foreach (_.warn(s"""Low heap size detected (~ ${Runtime.getRuntime().maxMemory() / (1024*1024)}M). Please add the following to your build.sbt: javaOptions in Test += "-Xmx1G"""")) + + try runner.run + catch { + case ex: ClassNotFoundException => + loggers foreach { l => l.error("Please make sure partest is running in a forked VM by including the following line in build.sbt:\nfork in Test := true") } + throw ex + } + + Array() + } + + type SBTRunner = { def run(): Unit } + + // use reflection to instantiate scala.tools.partest.nest.SBTRunner, + // casting to the structural type SBTRunner above so that method calls on the result will be invoked reflectively as well + private def SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], args: Array[String]): SBTRunner = { + val runnerClass = Class.forName("scala.tools.partest.nest.SBTRunner") + runnerClass.getConstructors()(0).newInstance(partestFingerprint, eventHandler, loggers, srcDir, testClassLoader, javaCmd, javacCmd, scalacArgs, args).asInstanceOf[SBTRunner] + } + + /** A possibly zero-length array of string tags associated with this task. */ + def tags: Array[String] = Array() +} From 695a30c51e56beaffa53dfef3eb292dcf284b8b3 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Thu, 11 Feb 2016 14:11:48 +0100 Subject: [PATCH 2/2] Integrate sbt test framework more closely without reflective calls --- .../scala/tools/partest/nest/SBTRunner.scala | 1 - .../scala/tools/partest/sbt/Framework.scala | 36 ++++++------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index f250d61..771678a 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -13,7 +13,6 @@ import _root_.sbt.testing._ import java.net.URLClassLoader import TestState._ -// called reflectively from scala-partest-test-interface class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], args: Array[String]) extends AbstractRunner(args.filter(a => !a.startsWith("-D")).mkString(" ")) { diff --git a/src/main/scala/scala/tools/partest/sbt/Framework.scala b/src/main/scala/scala/tools/partest/sbt/Framework.scala index 4fd8e69..489dae6 100644 --- a/src/main/scala/scala/tools/partest/sbt/Framework.scala +++ b/src/main/scala/scala/tools/partest/sbt/Framework.scala @@ -1,7 +1,6 @@ package scala.tools.partest.sbt -import scala.language.reflectiveCalls - +import scala.tools.partest.nest.SBTRunner import _root_.sbt.testing._ import java.net.URLClassLoader import java.io.File @@ -17,31 +16,25 @@ class Framework extends sbt.testing.Framework { def fingerprints: Array[Fingerprint] = Array(Framework.fingerprint) def name: String = "partest" - def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): sbt.testing.Runner = - new Runner(args, remoteArgs, testClassLoader) + def runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader): Runner = + new PartestRunner(args, remoteArgs, testClassLoader) } -/** Represents one run of a suite of tests. - */ -case class Runner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader) extends sbt.testing.Runner { - - def tasks(taskDefs: Array[TaskDef]): Array[sbt.testing.Task] = taskDefs map (SbtPartestTask(_, args): sbt.testing.Task) - - /** Indicates the client is done with this Runner instance. - * - * @return a possibly multi-line summary string, or the empty string if no summary is provided -- TODO - */ - def done(): String = "" +// We don't use sbt's Runner or Task abstractions properly. +// sbt runs a single dummy fingerprint match and partest does everything else internally. +case class PartestRunner(args: Array[String], remoteArgs: Array[String], testClassLoader: ClassLoader) extends Runner { + def tasks(taskDefs: Array[TaskDef]): Array[Task] = taskDefs map (PartestTask(_, args): Task) + def done(): String = "" // no summary } /** Run partest in this VM. Assumes we're running in a forked VM! */ -case class SbtPartestTask(taskDef: TaskDef, args: Array[String]) extends Task { +case class PartestTask(taskDef: TaskDef, args: Array[String]) extends Task { /** Executes this task, possibly returning to the client new tasks to execute. */ def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = { val forkedCp = scala.util.Properties.javaClassPath val classLoader = new URLClassLoader(forkedCp.split(java.io.File.pathSeparator).map(new File(_).toURI.toURL)) - val runner = SBTRunner(Framework.fingerprint, eventHandler, loggers, "files", classLoader, null, null, Array.empty[String], args) + val runner = new SBTRunner(taskDef.fingerprint(), eventHandler, loggers, "files", classLoader, null, null, Array.empty[String], args) if (Runtime.getRuntime().maxMemory() / (1024*1024) < 800) loggers foreach (_.warn(s"""Low heap size detected (~ ${Runtime.getRuntime().maxMemory() / (1024*1024)}M). Please add the following to your build.sbt: javaOptions in Test += "-Xmx1G"""")) @@ -56,15 +49,6 @@ case class SbtPartestTask(taskDef: TaskDef, args: Array[String]) extends Task { Array() } - type SBTRunner = { def run(): Unit } - - // use reflection to instantiate scala.tools.partest.nest.SBTRunner, - // casting to the structural type SBTRunner above so that method calls on the result will be invoked reflectively as well - private def SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], args: Array[String]): SBTRunner = { - val runnerClass = Class.forName("scala.tools.partest.nest.SBTRunner") - runnerClass.getConstructors()(0).newInstance(partestFingerprint, eventHandler, loggers, srcDir, testClassLoader, javaCmd, javacCmd, scalacArgs, args).asInstanceOf[SBTRunner] - } - /** A possibly zero-length array of string tags associated with this task. */ def tags: Array[String] = Array() }