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._
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 listeners = opts flatMap {
case Listeners(ls) => ls
@ -75,8 +75,8 @@ private[sbt] object ForkTests {
new Thread(Acceptor).start()
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 ec = Fork.java(javaHome, options, StdoutOutput)
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)
(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
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)
}

View File

@ -70,6 +70,7 @@ object Defaults extends BuildCommon
logBuffered :== false,
connectInput :== false,
cancelable :== false,
envVars :== Map.empty,
sourcesInBase :== true,
autoAPIMappings := false,
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 }
extra.put(name.key, tdef.name).put(isModule, mod)
}
def singleTestGroup(key: Scoped): Initialize[Task[Seq[Tests.Group]]] =
((definedTests in key, fork in key, javaOptions in key) map {
(tests, fork, javaOpts) => Seq(new Tests.Group("<default>", tests, if (fork) Tests.SubProcess(javaOpts) else Tests.InProcess))
})
def singleTestGroup(key: Scoped): Initialize[Task[Seq[Tests.Group]]] = Def.task {
val tests = (definedTests in key).value
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]] =
(testOptions in task, parallelExecution in task, tags in task) map {
@ -445,8 +453,8 @@ object Defaults extends BuildCommon
val groupTasks = groups map {
case Tests.Group(name, tests, runPolicy) =>
runPolicy match {
case Tests.SubProcess(javaOpts) =>
ForkTests(frameworks.keys.toSeq, tests.toList, config, cp.files, javaHome, javaOpts, s.log) tag Tags.ForkedTestGroup
case Tests.SubProcess(opts) =>
ForkTests(frameworks.keys.toSeq, tests.toList, config, cp.files, opts, s.log) tag Tags.ForkedTestGroup
case Tests.InProcess =>
Tests(frameworks, loader, tests, config, s.log)
}
@ -598,14 +606,11 @@ object Defaults extends BuildCommon
}
def runnerTask = runner <<= runnerInit
def runnerInit: Initialize[Task[ScalaRun]] =
(taskTemporaryDirectory, scalaInstance, baseDirectory, javaOptions, outputStrategy, fork, javaHome, trapExit, connectInput) map {
(tmp, si, base, options, strategy, forkRun, javaHomeDir, trap, connectIn) =>
if(forkRun) {
new ForkRun( ForkOptions(scalaJars = si.jars, javaHome = javaHomeDir, connectInput = connectIn, outputStrategy = strategy, runJVMOptions = options, workingDirectory = Some(base)) )
} else
new Run(si, trap, tmp)
}
def runnerInit: Initialize[Task[ScalaRun]] = Def.task {
val tmp = taskTemporaryDirectory.value
val si = scalaInstance.value
if(fork.value) new ForkRun(forkOptions.value) else new Run(si, trapExit.value, tmp)
}
@deprecated("Use `docTaskSettings` instead", "0.12.0")
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 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 envVars = TaskKey[Map[String,String]]("envVars", "Environment variables used when forking a new JVM", BTask)
// Test Keys
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[_]] =
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.
* 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

View File

@ -20,7 +20,7 @@ trait ForkScalaRun extends ForkScala
def workingDirectory: Option[File]
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
case object StdoutOutput extends OutputStrategy
@ -45,16 +45,28 @@ object Fork
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 =
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 =
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 =
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 =
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 =
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 =
@ -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 =
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 =
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 =
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 =
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")
val scalaClasspathString = "-Xbootclasspath/a:" + scalaJars.map(_.getAbsolutePath).mkString(File.pathSeparator)
val mainClass = if(mainClassName.isEmpty) Nil else mainClassName :: Nil
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]
}
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] =
{
log.info("Running " + mainClass + " " + options.mkString(" "))
val scalaOptions = classpathOption(classpath) ::: mainClass :: options.toList
val strategy = config.outputStrategy getOrElse LoggedOutput(log)
val process = Fork.scala.fork(config.javaHome, config.runJVMOptions, config.scalaJars, scalaOptions, config.workingDirectory, config.connectInput, strategy)
val configLogged = if(config.outputStrategy.isDefined) config else config.copy(outputStrategy = Some(LoggedOutput(log)))
val process = Fork.scala.fork(configLogged, scalaOptions)
def cancel() = {
log.warn("Run canceled.")
process.destroy()

View File

@ -1,6 +1,8 @@
object ForkTest {
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.createNewFile()
}

View File

@ -8,4 +8,11 @@ $ mkdir "forked"
> run "forked"
$ 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
class Test extends FlatSpec with MustMatchers {
val v = Option(System.getenv("tests.max.value")) getOrElse Int.MaxValue
"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
> 'set javaOptions += "-Xno-opt"'
-> test
-> test
> session clear
> 'set envVars += ("tests.max.value" -> "0")'
-> test