From 7c9a535bc3b531777f577c07adf823292b9d859a Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Mon, 20 Oct 2014 15:17:44 +0200 Subject: [PATCH 1/4] extract internal partest behavior from ConsoleRunner This splits ConsoleRunner in two: AbstractRunner contains most of the behavior but doesn't assume (or at least less that before...) that we are running from the command line. ConsoleRunner extends it to do the same as before. Now SBTRunner also extends AbstractRunner (instead of AntRunner), to get support for --grep, --update-check, and all other options, for testing an explicit file, and for pretty output. This requires changes on the scala-partest-interface side too (SBTRunner's public API has changed). --- .../tools/partest/nest/AbstractRunner.scala | 180 ++++++++++++++++++ .../tools/partest/nest/ConsoleRunner.scala | 172 +---------------- .../scala/tools/partest/nest/NestUI.scala | 4 +- ...nsoleRunnerSpec.scala => RunnerSpec.scala} | 8 +- .../scala/tools/partest/nest/SBTRunner.scala | 51 ++--- 5 files changed, 217 insertions(+), 198 deletions(-) create mode 100644 src/main/scala/scala/tools/partest/nest/AbstractRunner.scala rename src/main/scala/scala/tools/partest/nest/{ConsoleRunnerSpec.scala => RunnerSpec.scala} (93%) diff --git a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala new file mode 100644 index 0000000..f93107f --- /dev/null +++ b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala @@ -0,0 +1,180 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest +package nest + +import utils.Properties._ +import scala.tools.nsc.Properties.{ versionMsg, propOrFalse, setProp } +import scala.collection.{ mutable, immutable } +import TestKinds._ +import scala.reflect.internal.util.Collections.distinctBy +import scala.tools.cmd.{ CommandLine, CommandLineParser, Instance } + +abstract class AbstractRunner(argstr: String) extends { + val parsed = RunnerSpec.creator(CommandLineParser tokenize argstr) +} with RunnerSpec with Instance { + + val suiteRunner: SuiteRunner + + import suiteRunner._ + import NestUI._ + import NestUI.color._ + + private var totalTests = 0 + private val passedTests = mutable.ListBuffer[TestState]() + private val failedTests = mutable.ListBuffer[TestState]() + + def comment(s: String) = echo(magenta("# " + s)) + def levyJudgment() = { + if (totalTests == 0) echoMixed("No tests to run.") + else if (elapsedMillis == 0) echoMixed("Test Run ABORTED") + else if (isSuccess) echoPassed("Test Run PASSED") + else echoFailed("Test Run FAILED") + } + + def passFailString(passed: Int, failed: Int, skipped: Int): String = { + val total = passed + failed + skipped + val isSuccess = failed == 0 + def p0 = s"$passed/$total" + def p = ( if (isSuccess) bold(green(p0)) else p0 ) + " passed" + def f = if (failed == 0) "" else bold(red("" + failed)) + " failed" + def s = if (skipped == 0) "" else bold(yellow("" + skipped)) + " skipped" + + oempty(p, f, s) mkString ", " + } + + protected var summarizing = false + private var elapsedMillis = 0L + private var expectedFailures = 0 + protected def isSuccess = failedTests.size == expectedFailures + + def issueSummaryReport() { + // Don't run twice + if (!summarizing) { + summarizing = true + + val passed0 = passedTests.toList + val failed0 = failedTests.toList + val passed = passed0.size + val failed = failed0.size + val skipped = totalTests - (passed + failed) + val passFail = passFailString(passed, failed, skipped) + val elapsed = if (elapsedMillis > 0) " (elapsed time: " + elapsedString(elapsedMillis) + ")" else "" + val message = passFail + elapsed + + if (failed0.nonEmpty) { + if (isPartestVerbose) { + echo(bold(cyan("##### Transcripts from failed tests #####\n"))) + failed0 foreach { state => + comment("partest " + state.testFile) + echo(state.transcriptString + "\n") + } + } + + def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") + echo("# Failed test paths (this command will update checkfiles)") + echo("test/partest --update-check \\\n " + files_s + "\n") + } + + echo(message) + levyJudgment() + } + } + + def run(): Unit = { + if (optDebug || propOrFalse("partest.debug")) NestUI.setDebug() + if (optVerbose) NestUI.setVerbose() + if (optTerse) NestUI.setTerse() + if (optShowDiff) NestUI.setDiffOnFail() + + // Early return on no args, version, or invalid args + if (optVersion) return echo(versionMsg) + if (optHelp) return NestUI.usage() + + val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath + if (invalid.nonEmpty) { + if (isPartestVerbose) + invalid foreach (p => echoWarning(s"Discarding invalid test path " + p)) + else if (!isPartestTerse) + echoWarning(s"Discarding ${invalid.size} invalid test paths") + } + + optTimeout foreach (x => setProp("partest.timeout", x)) + + if (!isPartestTerse) + NestUI echo banner + + val partestTests = ( + if (optSelfTest) TestKinds.testsForPartest + else Nil + ) + + val grepExpr = optGrep getOrElse "" + + // If --grep is given we suck in every file it matches. + // TODO: intersect results of grep with specified kinds, if any + val greppedTests = if (grepExpr == "") Nil else { + val paths = grepFor(grepExpr) + if (paths.isEmpty) + echoWarning(s"grep string '$grepExpr' matched no tests.\n") + + paths.sortBy(_.toString) + } + + val isRerun = optFailed + val rerunTests = if (isRerun) TestKinds.failedTests else Nil + def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests + + val givenKinds = standardKinds filter parsed.isSet + val kinds = ( + if (givenKinds.nonEmpty) givenKinds + else if (miscTests.isEmpty) standardKinds // If no kinds, --grep, or individual tests were given, assume --all + else Nil + ) + val kindsTests = kinds flatMap testsFor + + def testContributors = { + List( + if (partestTests.isEmpty) "" else "partest self-tests", + if (rerunTests.isEmpty) "" else "previously failed tests", + if (kindsTests.isEmpty) "" else s"${kinds.size} named test categories", + if (greppedTests.isEmpty) "" else s"${greppedTests.size} tests matching '$grepExpr'", + if (individualTests.isEmpty) "" else "specified tests" + ) filterNot (_ == "") mkString ", " + } + + val allTests: Array[Path] = distinctBy(miscTests ++ kindsTests)(_.toCanonical) sortBy (_.toString) toArray + val grouped = (allTests groupBy kindOf).toArray sortBy (x => standardKinds indexOf x._1) + + totalTests = allTests.size + expectedFailures = propOrNone("partest.errors") match { + case Some(num) => num.toInt + case _ => 0 + } + val expectedFailureMessage = if (expectedFailures == 0) "" else s" (expecting $expectedFailures to fail)" + echo(s"Selected $totalTests tests drawn from $testContributors$expectedFailureMessage\n") + + val (_, millis) = timed { + for ((kind, paths) <- grouped) { + val num = paths.size + val ss = if (num == 1) "" else "s" + comment(s"starting $num test$ss in $kind") + val results = runTestsForFiles(paths map (_.jfile.getAbsoluteFile), kind) + val (passed, failed) = results partition (_.isOk) + + passedTests ++= passed + failedTests ++= failed + if (failed.nonEmpty) { + comment(passFailString(passed.size, failed.size, 0) + " in " + kind) + } + echo("") + } + } + this.elapsedMillis = millis + issueSummaryReport() + } +} diff --git a/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala b/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala index f199832..462fc2f 100644 --- a/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala @@ -7,185 +7,23 @@ package scala.tools package partest package nest -import utils.Properties._ -import scala.tools.nsc.Properties.{ versionMsg, propOrFalse, setProp } -import scala.collection.{ mutable, immutable } -import TestKinds._ -import scala.reflect.internal.util.Collections.distinctBy -import scala.tools.cmd.{ CommandLine, CommandLineParser, Instance } +class ConsoleRunner(argstr: String) extends AbstractRunner(argstr) { -class ConsoleRunner(argstr: String) extends { - val parsed = ConsoleRunnerSpec.creator(CommandLineParser tokenize argstr) -} with ConsoleRunnerSpec with Instance { - - val suiteRunner = new SuiteRunner ( + override val suiteRunner = new SuiteRunner ( testSourcePath = optSourcePath getOrElse PartestDefaults.sourcePath, fileManager = new FileManager(ClassPath split PathResolver.Environment.javaUserClassPath map (Path(_))), // the script sets up our classpath for us via ant updateCheck = optUpdateCheck, failed = optFailed) - import suiteRunner._ - import NestUI._ - import NestUI.color._ // So we can ctrl-C a test run and still hear all // the buffered failure info. scala.sys addShutdownHook issueSummaryReport() - private var totalTests = 0 - private val passedTests = mutable.ListBuffer[TestState]() - private val failedTests = mutable.ListBuffer[TestState]() - - def comment(s: String) = echo(magenta("# " + s)) - def levyJudgment() = { - if (totalTests == 0) echoMixed("No tests to run.") - else if (elapsedMillis == 0) echoMixed("Test Run ABORTED") - else if (isSuccess) echoPassed("Test Run PASSED") - else echoFailed("Test Run FAILED") - } - - def passFailString(passed: Int, failed: Int, skipped: Int): String = { - val total = passed + failed + skipped - val isSuccess = failed == 0 - def p0 = s"$passed/$total" - def p = ( if (isSuccess) bold(green(p0)) else p0 ) + " passed" - def f = if (failed == 0) "" else bold(red("" + failed)) + " failed" - def s = if (skipped == 0) "" else bold(yellow("" + skipped)) + " skipped" - - oempty(p, f, s) mkString ", " - } - - private var summarizing = false - private var elapsedMillis = 0L - private var expectedFailures = 0 - private def isSuccess = failedTests.size == expectedFailures - - def issueSummaryReport() { - // Don't run twice - if (!summarizing) { - summarizing = true - - val passed0 = passedTests.toList - val failed0 = failedTests.toList - val passed = passed0.size - val failed = failed0.size - val skipped = totalTests - (passed + failed) - val passFail = passFailString(passed, failed, skipped) - val elapsed = if (elapsedMillis > 0) " (elapsed time: " + elapsedString(elapsedMillis) + ")" else "" - val message = passFail + elapsed - - if (failed0.nonEmpty) { - if (isPartestVerbose) { - echo(bold(cyan("##### Transcripts from failed tests #####\n"))) - failed0 foreach { state => - comment("partest " + state.testFile) - echo(state.transcriptString + "\n") - } - } - - def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") - echo("# Failed test paths (this command will update checkfiles)") - echo("test/partest --update-check \\\n " + files_s + "\n") - } - - echo(message) - levyJudgment() - } - } - - def run(): Unit = { - if (optDebug || propOrFalse("partest.debug")) NestUI.setDebug() - if (optVerbose) NestUI.setVerbose() - if (optTerse) NestUI.setTerse() - if (optShowDiff) NestUI.setDiffOnFail() - - // Early return on no args, version, or invalid args - if (optVersion) return echo(versionMsg) - if (optHelp) return NestUI.usage() - - val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath - if (invalid.nonEmpty) { - if (isPartestVerbose) - invalid foreach (p => echoWarning(s"Discarding invalid test path " + p)) - else if (!isPartestTerse) - echoWarning(s"Discarding ${invalid.size} invalid test paths") - } - - optTimeout foreach (x => setProp("partest.timeout", x)) - - if (!isPartestTerse) - NestUI echo banner - - val partestTests = ( - if (optSelfTest) TestKinds.testsForPartest - else Nil - ) - - val grepExpr = optGrep getOrElse "" - - // If --grep is given we suck in every file it matches. - // TODO: intersect results of grep with specified kinds, if any - val greppedTests = if (grepExpr == "") Nil else { - val paths = grepFor(grepExpr) - if (paths.isEmpty) - echoWarning(s"grep string '$grepExpr' matched no tests.\n") - - paths.sortBy(_.toString) - } - - val isRerun = optFailed - val rerunTests = if (isRerun) TestKinds.failedTests else Nil - def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests - - val givenKinds = standardKinds filter parsed.isSet - val kinds = ( - if (givenKinds.nonEmpty) givenKinds - else if (miscTests.isEmpty) standardKinds // If no kinds, --grep, or individual tests were given, assume --all - else Nil - ) - val kindsTests = kinds flatMap testsFor - - def testContributors = { - List( - if (partestTests.isEmpty) "" else "partest self-tests", - if (rerunTests.isEmpty) "" else "previously failed tests", - if (kindsTests.isEmpty) "" else s"${kinds.size} named test categories", - if (greppedTests.isEmpty) "" else s"${greppedTests.size} tests matching '$grepExpr'", - if (individualTests.isEmpty) "" else "specified tests" - ) filterNot (_ == "") mkString ", " - } - - val allTests: Array[Path] = distinctBy(miscTests ++ kindsTests)(_.toCanonical) sortBy (_.toString) toArray - val grouped = (allTests groupBy kindOf).toArray sortBy (x => standardKinds indexOf x._1) - - totalTests = allTests.size - expectedFailures = propOrNone("partest.errors") match { - case Some(num) => num.toInt - case _ => 0 - } - val expectedFailureMessage = if (expectedFailures == 0) "" else s" (expecting $expectedFailures to fail)" - echo(s"Selected $totalTests tests drawn from $testContributors$expectedFailureMessage\n") - - val (_, millis) = timed { - for ((kind, paths) <- grouped) { - val num = paths.size - val ss = if (num == 1) "" else "s" - comment(s"starting $num test$ss in $kind") - val results = runTestsForFiles(paths map (_.jfile.getAbsoluteFile), kind) - val (passed, failed) = results partition (_.isOk) - - passedTests ++= passed - failedTests ++= failed - if (failed.nonEmpty) { - comment(passFailString(passed.size, failed.size, 0) + " in " + kind) - } - echo("") - } - } - this.elapsedMillis = millis - issueSummaryReport() + override def run(): Unit = { + super.run() System exit ( if (isSuccess) 0 else 1 ) } - + run() } diff --git a/src/main/scala/scala/tools/partest/nest/NestUI.scala b/src/main/scala/scala/tools/partest/nest/NestUI.scala index 5148115..a22351e 100644 --- a/src/main/scala/scala/tools/partest/nest/NestUI.scala +++ b/src/main/scala/scala/tools/partest/nest/NestUI.scala @@ -144,8 +144,8 @@ object NestUI { } def usage() { - println(ConsoleRunnerSpec.programInfo.usage) - println(ConsoleRunnerSpec.helpMsg) + println(RunnerSpec.programInfo.usage) + println(RunnerSpec.helpMsg) sys.exit(1) } diff --git a/src/main/scala/scala/tools/partest/nest/ConsoleRunnerSpec.scala b/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala similarity index 93% rename from src/main/scala/scala/tools/partest/nest/ConsoleRunnerSpec.scala rename to src/main/scala/scala/tools/partest/nest/RunnerSpec.scala index 86a0ecd..46d0088 100644 --- a/src/main/scala/scala/tools/partest/nest/ConsoleRunnerSpec.scala +++ b/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala @@ -4,8 +4,8 @@ import language.postfixOps import scala.tools.cmd.{ CommandLine, Interpolation, Meta, Reference, Spec } -trait ConsoleRunnerSpec extends Spec with Meta.StdOpts with Interpolation { - def referenceSpec = ConsoleRunnerSpec +trait RunnerSpec extends Spec with Meta.StdOpts with Interpolation { + def referenceSpec = RunnerSpec def programInfo = Spec.Info( "console-runner", "Usage: ConsoleRunner [options] [test test ...]", @@ -47,7 +47,7 @@ trait ConsoleRunnerSpec extends Spec with Meta.StdOpts with Interpolation { } -object ConsoleRunnerSpec extends ConsoleRunnerSpec with Reference { +object RunnerSpec extends RunnerSpec with Reference { type ThisCommandLine = CommandLine - def creator(args: List[String]): ThisCommandLine = new CommandLine(ConsoleRunnerSpec, args) + def creator(args: List[String]): ThisCommandLine = new CommandLine(RunnerSpec, args) } diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index 3e1271f..5e041b3 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -22,30 +22,31 @@ import java.net.URLClassLoader // 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]) - extends AntRunner(srcDir, testClassLoader, javaCmd, javacCmd, scalacArgs, None) { - override def error(msg: String): Nothing = sys.error(msg) - def echo(msg: String): Unit = loggers foreach { l => l.info(msg) } - def log(msg: String): Unit = loggers foreach { l => l.debug(msg) } - def onFinishKind(kind: String, passed: Array[TestState], failed: Array[TestState]): Unit = - eventHandler.handle(new Event { - def fullyQualifiedName: String = kind - def fingerprint: Fingerprint = partestFingerprint - def selector: Selector = new SuiteSelector - def status: Status = if (failed.isEmpty) Status.Success else Status.Failure - def throwable: OptionalThrowable = new OptionalThrowable - def duration: Long = -1 - }) + srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], args: Array[String]) + extends AbstractRunner(args.mkString(" ")) { - override def onFinishTest(testFile: File, result: TestState): TestState = { - eventHandler.handle(new Event { - def fullyQualifiedName: String = testFile.testIdent - def fingerprint: Fingerprint = partestFingerprint - def selector: Selector = new TestSelector(testFile.testIdent) - def status: Status = if (result.isOk) Status.Success else Status.Failure - def throwable: OptionalThrowable = new OptionalThrowable - def duration: Long = -1 - }) - result - } + // no summary, SBT will do that for us + summarizing = true + + override val suiteRunner = new SuiteRunner( + testSourcePath = Option(srcDir) getOrElse PartestDefaults.sourcePath, + new FileManager(testClassLoader = testClassLoader), + updateCheck = optUpdateCheck, + failed = optFailed, + javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, + javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, + scalacExtraArgs = scalacArgs) { + + override def onFinishTest(testFile: File, result: TestState): TestState = { + eventHandler.handle(new Event { + def fullyQualifiedName: String = testFile.testIdent + def fingerprint: Fingerprint = partestFingerprint + def selector: Selector = new TestSelector(testFile.testIdent) + def status: Status = if (result.isOk) Status.Success else Status.Failure + def throwable: OptionalThrowable = new OptionalThrowable + def duration: Long = -1 + }) + result + } + } } From 19bfc74c7a9d7e83ecdc442f00413eee1b6fd56b Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Mon, 20 Oct 2014 16:01:43 +0200 Subject: [PATCH 2/4] improve mapping from partest's TestState to sbt's Status. --- .../scala/scala/tools/partest/nest/SBTRunner.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index 5e041b3..f11dfd9 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -19,6 +19,7 @@ import sbt.testing.OptionalThrowable import sbt.testing.SuiteSelector import sbt.testing.TestSelector import java.net.URLClassLoader +import TestState._ // called reflectively from scala-partest-test-interface class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], @@ -42,11 +43,19 @@ class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, log def fullyQualifiedName: String = testFile.testIdent def fingerprint: Fingerprint = partestFingerprint def selector: Selector = new TestSelector(testFile.testIdent) - def status: Status = if (result.isOk) Status.Success else Status.Failure - def throwable: OptionalThrowable = new OptionalThrowable + val (status, throwable) = makeStatus(result) def duration: Long = -1 }) result } } + + def makeStatus(t: TestState): (Status, OptionalThrowable) = t match { + case _: Uninitialized => (Status.Pending, new OptionalThrowable) + case _: Pass => (Status.Success, new OptionalThrowable) + case _: Updated => (Status.Success, new OptionalThrowable) + case _: Skip => (Status.Skipped, new OptionalThrowable) + case _: Fail => (Status.Failure, new OptionalThrowable) + case Crash(_,e,_) => (Status.Error, new OptionalThrowable(e)) + } } From 7034b2cc028e729bb6324466e866c2774940f30c Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Thu, 22 Oct 2015 17:38:36 +0200 Subject: [PATCH 3/4] Improved `makeStatus` to get exhaustiveness checking from scalac --- .../scala/scala/tools/partest/nest/SBTRunner.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index f11dfd9..1cb3142 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -51,11 +51,11 @@ class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, log } def makeStatus(t: TestState): (Status, OptionalThrowable) = t match { - case _: Uninitialized => (Status.Pending, new OptionalThrowable) - case _: Pass => (Status.Success, new OptionalThrowable) - case _: Updated => (Status.Success, new OptionalThrowable) - case _: Skip => (Status.Skipped, new OptionalThrowable) - case _: Fail => (Status.Failure, new OptionalThrowable) - case Crash(_,e,_) => (Status.Error, new OptionalThrowable(e)) + case Uninitialized(_) => (Status.Pending, new OptionalThrowable) + case Pass(_) => (Status.Success, new OptionalThrowable) + case Updated(_) => (Status.Success, new OptionalThrowable) + case Skip(_, _) => (Status.Skipped, new OptionalThrowable) + case Fail(_, _, _) => (Status.Failure, new OptionalThrowable) + case Crash(_, e, _) => (Status.Error, new OptionalThrowable(e)) } } From 7c4659e1f88b410109ad3c4e7f66ae7070c6e985 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Mon, 26 Oct 2015 16:54:05 +0100 Subject: [PATCH 4/4] Pass javaOpts into SuiteRunner and allow setting them from SBTRunner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously there was no way to set the Java options from sbt. While the Scala build script sets `javaOptions` this only affects the JVM which runs the partest launcher, not the actual tests. This commit improves the handling of `javaOpts` in partest and allows setting it from SBTRunner. You can pass a special ‘-Dpartest.java_opts=…’ argument to it which is handled directly in SBTRunner. partest usage from ant and from the command line does not change. --- .../scala/scala/tools/partest/nest/AntRunner.scala | 5 ++--- src/main/scala/scala/tools/partest/nest/Runner.scala | 11 ++++++----- .../scala/scala/tools/partest/nest/SBTRunner.scala | 11 +++++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/scala/scala/tools/partest/nest/AntRunner.scala b/src/main/scala/scala/tools/partest/nest/AntRunner.scala index d0ae0a7..786a7e9 100644 --- a/src/main/scala/scala/tools/partest/nest/AntRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AntRunner.scala @@ -19,9 +19,8 @@ abstract class AntRunner(srcDir: String, testClassLoader: URLClassLoader, javaCm failed = false, javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, - scalacExtraArgs = scalacArgs) { - - for (jOpts <- javaOpts) System.setProperty("partest.java_opts", jOpts mkString " ") + scalacExtraArgs = scalacArgs, + javaOpts = javaOpts.map(_.mkString(" ")).getOrElse(PartestDefaults.javaOpts)) { def error(msg: String): Nothing = sys.error(msg) def echo(msg: String): Unit diff --git a/src/main/scala/scala/tools/partest/nest/Runner.scala b/src/main/scala/scala/tools/partest/nest/Runner.scala index 151d47e..1dec027 100644 --- a/src/main/scala/scala/tools/partest/nest/Runner.scala +++ b/src/main/scala/scala/tools/partest/nest/Runner.scala @@ -146,7 +146,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) { val testFullPath = testFile.getAbsolutePath - // Note! As this currently functions, PartestDefaults.javaOpts must precede argString + // Note! As this currently functions, suiteRunner.javaOpts must precede argString // because when an option is repeated to java only the last one wins. // That means until now all the .javaopts files were being ignored because // they all attempt to change options which are also defined in @@ -174,7 +174,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) { val classpath = joinPaths(extraClasspath ++ testClassPath) javaCmdPath +: ( - (PartestDefaults.javaOpts.split(' ') ++ extraJavaOptions ++ argString.split(' ')).map(_.trim).filter(_ != "").toList ++ Seq( + (suiteRunner.javaOpts.split(' ') ++ extraJavaOptions ++ argString.split(' ')).map(_.trim).filter(_ != "").toList ++ Seq( "-classpath", join(outDir.toString, classpath) ) ++ propertyOptions ++ Seq( @@ -516,7 +516,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) { if (NestUI._verbose) List("-verbose", "-noinput") else List("-noinput") val cmd = javaCmdPath +: ( - PartestDefaults.javaOpts.split(' ').map(_.trim).filter(_ != "") ++ Seq( + suiteRunner.javaOpts.split(' ').map(_.trim).filter(_ != "") ++ Seq( "-classpath", antLauncher.path, "org.apache.tools.ant.launch.Launcher" @@ -753,7 +753,8 @@ class SuiteRunner( val failed: Boolean, val javaCmdPath: String = PartestDefaults.javaCmd, val javacCmdPath: String = PartestDefaults.javacCmd, - val scalacExtraArgs: Seq[String] = Seq.empty) { + val scalacExtraArgs: Seq[String] = Seq.empty, + val javaOpts: String = PartestDefaults.javaOpts) { import PartestDefaults.{ numThreads, waitTime } @@ -775,7 +776,7 @@ class SuiteRunner( |Compilation Path: ${relativize(joinPaths(fileManager.testClassPath))} |Java binaries in: $vmBin |Java runtime is: $vmName - |Java options are: ${PartestDefaults.javaOpts} + |Java options are: $javaOpts |baseDir: $baseDir |sourceDir: ${PathSettings.srcDir} """.stripMargin diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index 1cb3142..46006e1 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -24,11 +24,17 @@ 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.mkString(" ")) { + extends AbstractRunner(args.filter(a => !a.startsWith("-D")).mkString(" ")) { // no summary, SBT will do that for us summarizing = true + val javaOpts = { + val l = args.filter(_.startsWith("-Dpartest.java_opts=")).map(_.substring(20)) + if(l.isEmpty) PartestDefaults.javaOpts + else l.mkString(" ") + } + override val suiteRunner = new SuiteRunner( testSourcePath = Option(srcDir) getOrElse PartestDefaults.sourcePath, new FileManager(testClassLoader = testClassLoader), @@ -36,7 +42,8 @@ class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, log failed = optFailed, javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, - scalacExtraArgs = scalacArgs) { + scalacExtraArgs = scalacArgs, + javaOpts = javaOpts) { override def onFinishTest(testFile: File, result: TestState): TestState = { eventHandler.handle(new Event {