Make full set of ForkOptions configurable for both run and test. Fixes #665.

Environment variables may be passed by configuring the envVars task.
This commit is contained in:
Mark Harrah 2013-02-21 20:44:26 -05:00
parent 5119ea2574
commit 98e2662bc4
11 changed files with 89 additions and 29 deletions

View File

@ -11,7 +11,7 @@ import Tests.{Output => TestOutput, _}
import ForkMain._ import ForkMain._
private[sbt] object ForkTests { private[sbt] object ForkTests {
def apply(frameworks: Seq[TestFramework], tests: List[TestDefinition], config: Execution, classpath: Seq[File], javaHome: Option[File], javaOpts: Seq[String], log: Logger): Task[TestOutput] = { def apply(frameworks: Seq[TestFramework], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger): Task[TestOutput] = {
val opts = config.options.toList val opts = config.options.toList
val listeners = opts flatMap { val listeners = opts flatMap {
case Listeners(ls) => ls case Listeners(ls) => ls
@ -75,8 +75,8 @@ private[sbt] object ForkTests {
new Thread(Acceptor).start() new Thread(Acceptor).start()
val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework]) val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework])
val options = javaOpts ++: Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString) val options = Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString)
val ec = Fork.java(javaHome, options, StdoutOutput) val ec = Fork.java(fork, options)
val result = val result =
if (ec != 0) if (ec != 0)
(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> TestResult.Error)) (TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> TestResult.Error))

View File

@ -200,7 +200,11 @@ object Tests
sealed trait TestRunPolicy sealed trait TestRunPolicy
case object InProcess extends TestRunPolicy case object InProcess extends TestRunPolicy
final case class SubProcess(javaOptions: Seq[String]) extends TestRunPolicy final case class SubProcess(config: ForkOptions) extends TestRunPolicy
object SubProcess {
@deprecated("Construct SubProcess with a ForkOptions argument.", "0.13.0")
def apply(javaOptions: Seq[String]): SubProcess = SubProcess(ForkOptions(runJVMOptions = javaOptions))
}
final case class Group(name: String, tests: Seq[TestDefinition], runPolicy: TestRunPolicy) final case class Group(name: String, tests: Seq[TestDefinition], runPolicy: TestRunPolicy)
} }

View File

@ -70,6 +70,7 @@ object Defaults extends BuildCommon
logBuffered :== false, logBuffered :== false,
connectInput :== false, connectInput :== false,
cancelable :== false, cancelable :== false,
envVars :== Map.empty,
sourcesInBase :== true, sourcesInBase :== true,
autoAPIMappings := false, autoAPIMappings := false,
apiMappings := Map.empty, apiMappings := Map.empty,
@ -378,10 +379,17 @@ object Defaults extends BuildCommon
val mod = tdef.fingerprint match { case f: SubclassFingerprint => f.isModule; case f: AnnotatedFingerprint => f.isModule; case _ => false } val mod = tdef.fingerprint match { case f: SubclassFingerprint => f.isModule; case f: AnnotatedFingerprint => f.isModule; case _ => false }
extra.put(name.key, tdef.name).put(isModule, mod) extra.put(name.key, tdef.name).put(isModule, mod)
} }
def singleTestGroup(key: Scoped): Initialize[Task[Seq[Tests.Group]]] = def singleTestGroup(key: Scoped): Initialize[Task[Seq[Tests.Group]]] = Def.task {
((definedTests in key, fork in key, javaOptions in key) map { val tests = (definedTests in key).value
(tests, fork, javaOpts) => Seq(new Tests.Group("<default>", tests, if (fork) Tests.SubProcess(javaOpts) else Tests.InProcess)) val fk = (fork in key).value
}) val opts = inTask(key, forkOptions).value
Seq(new Tests.Group("<default>", tests, if(fk) Tests.SubProcess(opts) else Tests.InProcess))
}
private[this] def forkOptions: Initialize[Task[ForkOptions]] =
(baseDirectory, scalaInstance, javaOptions, outputStrategy, envVars, javaHome, connectInput) map {
(base, si, options, strategy, env, javaHomeDir, connectIn) =>
ForkOptions(scalaJars = si.jars, javaHome = javaHomeDir, connectInput = connectIn, outputStrategy = strategy, runJVMOptions = options, workingDirectory = Some(base), envVars = env)
}
def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] = def testExecutionTask(task: Scoped): Initialize[Task[Tests.Execution]] =
(testOptions in task, parallelExecution in task, tags in task) map { (testOptions in task, parallelExecution in task, tags in task) map {
@ -445,8 +453,8 @@ object Defaults extends BuildCommon
val groupTasks = groups map { val groupTasks = groups map {
case Tests.Group(name, tests, runPolicy) => case Tests.Group(name, tests, runPolicy) =>
runPolicy match { runPolicy match {
case Tests.SubProcess(javaOpts) => case Tests.SubProcess(opts) =>
ForkTests(frameworks.keys.toSeq, tests.toList, config, cp.files, javaHome, javaOpts, s.log) tag Tags.ForkedTestGroup ForkTests(frameworks.keys.toSeq, tests.toList, config, cp.files, opts, s.log) tag Tags.ForkedTestGroup
case Tests.InProcess => case Tests.InProcess =>
Tests(frameworks, loader, tests, config, s.log) Tests(frameworks, loader, tests, config, s.log)
} }
@ -598,14 +606,11 @@ object Defaults extends BuildCommon
} }
def runnerTask = runner <<= runnerInit def runnerTask = runner <<= runnerInit
def runnerInit: Initialize[Task[ScalaRun]] = def runnerInit: Initialize[Task[ScalaRun]] = Def.task {
(taskTemporaryDirectory, scalaInstance, baseDirectory, javaOptions, outputStrategy, fork, javaHome, trapExit, connectInput) map { val tmp = taskTemporaryDirectory.value
(tmp, si, base, options, strategy, forkRun, javaHomeDir, trap, connectIn) => val si = scalaInstance.value
if(forkRun) { if(fork.value) new ForkRun(forkOptions.value) else new Run(si, trapExit.value, tmp)
new ForkRun( ForkOptions(scalaJars = si.jars, javaHome = javaHomeDir, connectInput = connectIn, outputStrategy = strategy, runJVMOptions = options, workingDirectory = Some(base)) ) }
} else
new Run(si, trap, tmp)
}
@deprecated("Use `docTaskSettings` instead", "0.12.0") @deprecated("Use `docTaskSettings` instead", "0.12.0")
def docSetting(key: TaskKey[File]) = docTaskSettings(key) def docSetting(key: TaskKey[File]) = docTaskSettings(key)

View File

@ -187,6 +187,7 @@ object Keys
val connectInput = SettingKey[Boolean]("connect-input", "If true, connects standard input when running a main class forked.", CSetting) val connectInput = SettingKey[Boolean]("connect-input", "If true, connects standard input when running a main class forked.", CSetting)
val javaHome = SettingKey[Option[File]]("java-home", "Selects the Java installation used for compiling and forking. If None, uses the Java installation running the build.", ASetting) val javaHome = SettingKey[Option[File]]("java-home", "Selects the Java installation used for compiling and forking. If None, uses the Java installation running the build.", ASetting)
val javaOptions = TaskKey[Seq[String]]("java-options", "Options passed to a new JVM when forking.", BPlusTask) val javaOptions = TaskKey[Seq[String]]("java-options", "Options passed to a new JVM when forking.", BPlusTask)
val envVars = TaskKey[Map[String,String]]("envVars", "Environment variables used when forking a new JVM", BTask)
// Test Keys // Test Keys
val testLoader = TaskKey[ClassLoader]("test-loader", "Provides the class loader used for testing.", DTask) val testLoader = TaskKey[ClassLoader]("test-loader", "Provides the class loader used for testing.", DTask)

View File

@ -435,6 +435,13 @@ trait ProjectExtra
def inScope(scope: Scope)(ss: Seq[Setting[_]]): Seq[Setting[_]] = def inScope(scope: Scope)(ss: Seq[Setting[_]]): Seq[Setting[_]] =
Project.transform(Scope.replaceThis(scope), ss) Project.transform(Scope.replaceThis(scope), ss)
private[sbt] def inConfig[T](conf: Configuration, i: Initialize[T]): Initialize[T] =
inScope(ThisScope.copy(config = Select(conf)), i)
private[sbt] def inTask[T](t: Scoped, i: Initialize[T]): Initialize[T] =
inScope(ThisScope.copy(task = Select(t.key)), i)
private[sbt] def inScope[T](scope: Scope, i: Initialize[T]): Initialize[T] =
i mapReferenced Project.mapScope(Scope.replaceThis(scope))
/** Creates a new Project. This is a macro that expects to be assigned directly to a val. /** Creates a new Project. This is a macro that expects to be assigned directly to a val.
* The name of the val is used as the project ID and the name of the base directory of the project. */ * The name of the val is used as the project ID and the name of the base directory of the project. */
def project: Project = macro Project.projectMacroImpl def project: Project = macro Project.projectMacroImpl

View File

@ -20,7 +20,7 @@ trait ForkScalaRun extends ForkScala
def workingDirectory: Option[File] def workingDirectory: Option[File]
def runJVMOptions: Seq[String] def runJVMOptions: Seq[String]
} }
final case class ForkOptions(javaHome: Option[File] = None, outputStrategy: Option[OutputStrategy] = None, scalaJars: Iterable[File] = Nil, workingDirectory: Option[File] = None, runJVMOptions: Seq[String] = Nil, connectInput: Boolean = false) extends ForkScalaRun final case class ForkOptions(javaHome: Option[File] = None, outputStrategy: Option[OutputStrategy] = None, scalaJars: Iterable[File] = Nil, workingDirectory: Option[File] = None, runJVMOptions: Seq[String] = Nil, connectInput: Boolean = false, envVars: Map[String,String] = Map.empty) extends ForkScalaRun
sealed abstract class OutputStrategy extends NotNull sealed abstract class OutputStrategy extends NotNull
case object StdoutOutput extends OutputStrategy case object StdoutOutput extends OutputStrategy
@ -45,16 +45,28 @@ object Fork
new File(new File(home, "bin"), name) new File(new File(home, "bin"), name)
} }
final class ForkJava(commandName: String) extends NotNull final class ForkJava(commandName: String)
{ {
def apply(config: ForkOptions, arguments: Seq[String]): Int = fork(config, arguments).exitValue()
def fork(config: ForkOptions, arguments: Seq[String]): Process =
fork(config.javaHome, config.runJVMOptions ++ arguments, config.workingDirectory, config.envVars, config.connectInput, config.outputStrategy getOrElse StdoutOutput)
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], options: Seq[String], log: Logger): Int = def apply(javaHome: Option[File], options: Seq[String], log: Logger): Int =
apply(javaHome, options, BufferedOutput(log)) apply(javaHome, options, BufferedOutput(log))
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], options: Seq[String], outputStrategy: OutputStrategy): Int = def apply(javaHome: Option[File], options: Seq[String], outputStrategy: OutputStrategy): Int =
apply(javaHome, options, None, outputStrategy) apply(javaHome, options, None, outputStrategy)
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], log: Logger): Int = def apply(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], log: Logger): Int =
apply(javaHome, options, workingDirectory, BufferedOutput(log)) apply(javaHome, options, workingDirectory, BufferedOutput(log))
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], outputStrategy: OutputStrategy): Int = def apply(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], outputStrategy: OutputStrategy): Int =
apply(javaHome, options, workingDirectory, Map.empty, outputStrategy) apply(javaHome, options, workingDirectory, Map.empty, outputStrategy)
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], env: Map[String, String], outputStrategy: OutputStrategy): Int = def apply(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], env: Map[String, String], outputStrategy: OutputStrategy): Int =
fork(javaHome, options, workingDirectory, env, false, outputStrategy).exitValue fork(javaHome, options, workingDirectory, env, false, outputStrategy).exitValue
def fork(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], env: Map[String, String], connectInput: Boolean, outputStrategy: OutputStrategy): Process = def fork(javaHome: Option[File], options: Seq[String], workingDirectory: Option[File], env: Map[String, String], connectInput: Boolean, outputStrategy: OutputStrategy): Process =
@ -75,21 +87,35 @@ object Fork
} }
} }
final class ForkScala(mainClassName: String) extends NotNull final class ForkScala(mainClassName: String)
{ {
def apply(options: ForkOptions, arguments: Seq[String]): Int = fork(options, arguments).exitValue()
def fork(options: ForkOptions, arguments: Seq[String]): Process =
fork(options.javaHome, options.runJVMOptions, options.scalaJars, arguments, options.workingDirectory, options.envVars, options.connectInput, options.outputStrategy getOrElse StdoutOutput)
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], log: Logger): Int = def apply(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], log: Logger): Int =
apply(javaHome, jvmOptions, scalaJars, arguments, None, BufferedOutput(log)) apply(javaHome, jvmOptions, scalaJars, arguments, None, BufferedOutput(log))
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], log: Logger): Int = def apply(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], log: Logger): Int =
apply(javaHome, jvmOptions, scalaJars, arguments, workingDirectory, BufferedOutput(log)) apply(javaHome, jvmOptions, scalaJars, arguments, workingDirectory, BufferedOutput(log))
@deprecated("Use apply(ForkOptions, Seq[String])", "0.13.0")
def apply(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], outputStrategy: OutputStrategy): Int = def apply(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], outputStrategy: OutputStrategy): Int =
fork(javaHome, jvmOptions, scalaJars, arguments, workingDirectory, false, outputStrategy).exitValue() fork(javaHome, jvmOptions, scalaJars, arguments, workingDirectory, false, outputStrategy).exitValue()
@deprecated("Use fork(ForkOptions, Seq[String])", "0.13.0")
def fork(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], connectInput: Boolean, outputStrategy: OutputStrategy): Process = def fork(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], connectInput: Boolean, outputStrategy: OutputStrategy): Process =
fork(javaHome, jvmOptions, scalaJars, arguments, workingDirectory, connectInput, outputStrategy)
def fork(javaHome: Option[File], jvmOptions: Seq[String], scalaJars: Iterable[File], arguments: Seq[String], workingDirectory: Option[File], env: Map[String,String], connectInput: Boolean, outputStrategy: OutputStrategy): Process =
{ {
if(scalaJars.isEmpty) error("Scala jars not specified") if(scalaJars.isEmpty) error("Scala jars not specified")
val scalaClasspathString = "-Xbootclasspath/a:" + scalaJars.map(_.getAbsolutePath).mkString(File.pathSeparator) val scalaClasspathString = "-Xbootclasspath/a:" + scalaJars.map(_.getAbsolutePath).mkString(File.pathSeparator)
val mainClass = if(mainClassName.isEmpty) Nil else mainClassName :: Nil val mainClass = if(mainClassName.isEmpty) Nil else mainClassName :: Nil
val options = jvmOptions ++ (scalaClasspathString :: mainClass ::: arguments.toList) val options = jvmOptions ++ (scalaClasspathString :: mainClass ::: arguments.toList)
Fork.java.fork(javaHome, options, workingDirectory, Map.empty, connectInput, outputStrategy) Fork.java.fork(javaHome, options, workingDirectory, env, connectInput, outputStrategy)
} }
} }
} }

View File

@ -13,15 +13,18 @@ trait ScalaRun
{ {
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String] def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String]
} }
class ForkRun(config: ForkScalaRun) extends ScalaRun class ForkRun(config: ForkOptions) extends ScalaRun
{ {
@deprecated("Use the `ForkRun(ForkOptions) constructor`", "0.13.0")
def this(options: ForkScalaRun) = this(ForkOptions(options.javaHome, options.outputStrategy, options.scalaJars, options.workingDirectory, options.runJVMOptions, options.connectInput))
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String] = def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String] =
{ {
log.info("Running " + mainClass + " " + options.mkString(" ")) log.info("Running " + mainClass + " " + options.mkString(" "))
val scalaOptions = classpathOption(classpath) ::: mainClass :: options.toList val scalaOptions = classpathOption(classpath) ::: mainClass :: options.toList
val strategy = config.outputStrategy getOrElse LoggedOutput(log) val configLogged = if(config.outputStrategy.isDefined) config else config.copy(outputStrategy = Some(LoggedOutput(log)))
val process = Fork.scala.fork(config.javaHome, config.runJVMOptions, config.scalaJars, scalaOptions, config.workingDirectory, config.connectInput, strategy) val process = Fork.scala.fork(configLogged, scalaOptions)
def cancel() = { def cancel() = {
log.warn("Run canceled.") log.warn("Run canceled.")
process.destroy() process.destroy()

View File

@ -1,6 +1,8 @@
object ForkTest { object ForkTest {
def main(args:Array[String]) { def main(args:Array[String]) {
val cwd = (new java.io.File("flag")).getAbsoluteFile val name = Option(System.getenv("flag.name")) getOrElse("flag")
println("Name: " + name)
val cwd = (new java.io.File(name)).getAbsoluteFile
cwd.getParentFile.mkdirs() cwd.getParentFile.mkdirs()
cwd.createNewFile() cwd.createNewFile()
} }

View File

@ -8,4 +8,11 @@ $ mkdir "forked"
> run "forked" > run "forked"
$ exists forked/flag $ exists forked/flag
$ absent flag $ absent flag
$ delete forked/flag
> 'set envVars += ("flag.name" -> "env.flag")'
> run "forked"
$ exists forked/env.flag
$ absent flag
$ absent forked/flag

View File

@ -2,7 +2,8 @@ import org.scalatest.FlatSpec
import org.scalatest.matchers.MustMatchers import org.scalatest.matchers.MustMatchers
class Test extends FlatSpec with MustMatchers { class Test extends FlatSpec with MustMatchers {
val v = Option(System.getenv("tests.max.value")) getOrElse Int.MaxValue
"A simple equation" must "hold" in { "A simple equation" must "hold" in {
Int.MaxValue must equal (Int.MaxValue) Int.MaxValue must equal (v)
} }
} }

View File

@ -5,4 +5,8 @@ $ copy-file changes/Test.scala src/test/scala/Test.scala
> test > test
> 'set javaOptions += "-Xno-opt"' > 'set javaOptions += "-Xno-opt"'
-> test -> test
> session clear
> 'set envVars += ("tests.max.value" -> "0")'
-> test