diff --git a/src/main/scala/scala/tools/partest/PartestTask.scala b/src/main/scala/scala/tools/partest/PartestTask.scala index 269fa11..dd255b8 100644 --- a/src/main/scala/scala/tools/partest/PartestTask.scala +++ b/src/main/scala/scala/tools/partest/PartestTask.scala @@ -118,9 +118,7 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { } override def execute() { - if (debug || propOrFalse("partest.debug")) { - NestUI.setDebug() - } + val nestUI: NestUI = new NestUI(debug = debug || propOrFalse("partest.debug")) if (compilationPath.isEmpty) sys.error("Mandatory attribute 'compilationPath' is not set.") @@ -130,7 +128,7 @@ class PartestTask extends Task with CompilationPathProperty with ScalaTask { }) var failureCount = 0 - val summary = new scala.tools.partest.nest.AntRunner(srcDir.getOrElse(null), new URLClassLoader(compilationPath.get.list.map(Path(_).toURL)), javacmd.getOrElse(null), javaccmd.getOrElse(null), scalacArgsFlat, javaOpts) { + val summary = new scala.tools.partest.nest.AntRunner(srcDir.getOrElse(null), new URLClassLoader(compilationPath.get.list.map(Path(_).toURL)), javacmd.getOrElse(null), javaccmd.getOrElse(null), scalacArgsFlat, javaOpts, nestUI) { def echo(msg: String): Unit = PartestTask.this.log(msg) def log(msg: String): Unit = PartestTask.this.log(msg) diff --git a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala index d26413c..8981868 100644 --- a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala @@ -12,31 +12,47 @@ 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 { +abstract class AbstractRunner { + + val config: RunnerSpec.Config + + lazy val nestUI: NestUI = new NestUI( + verbose = config.optVerbose, + debug = config.optDebug || propOrFalse("partest.debug"), + terse = config.optTerse, + diffOnFail = config.optShowDiff, + logOnFail = config.optShowLog, + colorEnabled = colorEnabled + ) val suiteRunner: SuiteRunner - import suiteRunner._ - import NestUI._ - import NestUI.color._ + protected val printSummary = true + protected val partestCmd = "test/partest" + protected val colorEnabled = sys.props contains "partest.colors" + + private[this] var totalTests = 0 + private[this] val passedTests = mutable.ListBuffer[TestState]() + private[this] val failedTests = mutable.ListBuffer[TestState]() + + private[this] var summarizing = false + private[this] var elapsedMillis = 0L + private[this] var expectedFailures = 0 - private var totalTests = 0 - private val passedTests = mutable.ListBuffer[TestState]() - private val failedTests = mutable.ListBuffer[TestState]() + import nestUI._ + import nestUI.color._ - def comment(s: String) = echo(magenta("# " + s)) - def levyJudgment() = { + private[this] def comment(s: String) = echo(magenta("# " + s)) + + private[this] 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 = { + private[this] def passFailString(passed: Int, failed: Int, skipped: Int): String = { val total = passed + failed + skipped val isSuccess = failed == 0 def p0 = s"$passed/$total" @@ -47,12 +63,7 @@ abstract class AbstractRunner(argstr: String) extends { oempty(p, f, s) mkString ", " } - protected var partestCmd = "test/partest" - protected var summarizing = false - protected var printSummary = true - private var elapsedMillis = 0L - private var expectedFailures = 0 - protected def isSuccess = failedTests.size == expectedFailures + private[this] def isSuccess = failedTests.size == expectedFailures def issueSummaryReport() { // Don't run twice @@ -69,7 +80,7 @@ abstract class AbstractRunner(argstr: String) extends { val message = passFail + elapsed if (failed0.nonEmpty) { - if (isPartestVerbose) { + if (nestUI.verbose) { echo(bold(cyan("##### Transcripts from failed tests #####\n"))) failed0 foreach { state => comment(partestCmd + " " + state.testFile) @@ -89,97 +100,93 @@ abstract class AbstractRunner(argstr: String) extends { } } - def run(): Unit = { - if (optDebug || propOrFalse("partest.debug")) NestUI.setDebug() - if (optVerbose) NestUI.setVerbose() - if (optTerse) NestUI.setTerse() - if (optShowDiff) NestUI.setDiffOnFail() - if (optShowLog) NestUI.setLogOnFail() - - // 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") - } + /** Run the tests and return the success status */ + def run(): Boolean = { + if (config.optVersion) echo(versionMsg) + else if (config.optHelp) nestUI.usage() + else { + val (individualTests, invalid) = config.parsed.residualArgs map (p => Path(p)) partition denotesTestPath + if (invalid.nonEmpty) { + if (nestUI.verbose) + invalid foreach (p => echoWarning(s"Discarding invalid test path " + p)) + else if (!nestUI.terse) + echoWarning(s"Discarding ${invalid.size} invalid test paths") + } - optTimeout foreach (x => setProp("partest.timeout", x)) + config.optTimeout foreach (x => setProp("partest.timeout", x)) - if (!isPartestTerse) - NestUI echo banner + if (!nestUI.terse) + nestUI.echo(suiteRunner.banner) - val partestTests = ( - if (optSelfTest) TestKinds.testsForPartest - else Nil - ) + val partestTests = ( + if (config.optSelfTest) TestKinds.testsForPartest + else Nil + ) - val grepExpr = optGrep getOrElse "" + val grepExpr = config.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") + // 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) - } + 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 isRerun = config.optFailed + val rerunTests = if (isRerun) TestKinds.failedTests else Nil + def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests + + val givenKinds = standardKinds filter config.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) + 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) + 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 = suiteRunner.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("") } - echo("") } + this.elapsedMillis = millis + issueSummaryReport() } - this.elapsedMillis = millis - issueSummaryReport() + isSuccess } } diff --git a/src/main/scala/scala/tools/partest/nest/AntRunner.scala b/src/main/scala/scala/tools/partest/nest/AntRunner.scala index 786a7e9..12b4190 100644 --- a/src/main/scala/scala/tools/partest/nest/AntRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AntRunner.scala @@ -12,11 +12,12 @@ package nest import java.net.URLClassLoader // not using any Scala types to ease calling across different scala versions -abstract class AntRunner(srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], javaOpts: Option[Seq[String]]) extends SuiteRunner( +abstract class AntRunner(srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], javaOpts: Option[Seq[String]], nestUI: NestUI) extends SuiteRunner( testSourcePath = Option(srcDir) getOrElse PartestDefaults.sourcePath, new FileManager(testClassLoader = testClassLoader), updateCheck = false, failed = false, + nestUI = nestUI, javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, scalacExtraArgs = scalacArgs, diff --git a/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala b/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala index 462fc2f..5f515eb 100644 --- a/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala @@ -3,33 +3,24 @@ * @author Philipp Haller */ -package scala.tools -package partest +package scala.tools.partest package nest -class ConsoleRunner(argstr: String) extends AbstractRunner(argstr) { - - override val suiteRunner = new SuiteRunner ( - testSourcePath = optSourcePath getOrElse PartestDefaults.sourcePath, +class ConsoleRunner(val config: RunnerSpec.Config) extends AbstractRunner { + val suiteRunner = new SuiteRunner ( + testSourcePath = config.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) - - // So we can ctrl-C a test run and still hear all - // the buffered failure info. - scala.sys addShutdownHook issueSummaryReport() - - override def run(): Unit = { - super.run() - System exit ( if (isSuccess) 0 else 1 ) - } - - run() + updateCheck = config.optUpdateCheck, + failed = config.optFailed, + nestUI = nestUI) } object ConsoleRunner { def main(args: Array[String]): Unit = { - new ConsoleRunner(args mkString " ") + val r = new ConsoleRunner(RunnerSpec.forArgs(args)) + // So we can ctrl-C a test run and still hear all + // the buffered failure info. + scala.sys addShutdownHook r.issueSummaryReport() + System.exit( if (r.run) 0 else 1 ) } } - diff --git a/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala b/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala index 9c52624..2a5db56 100644 --- a/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala +++ b/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala @@ -107,7 +107,7 @@ class DirectCompiler(val runner: Runner) { } def ids = sources.map(_.testIdent) mkString space - vlog(s"% scalac $ids") + nestUI.vlog(s"% scalac $ids") def execCompile() = if (command.shouldStopWithInfo) { diff --git a/src/main/scala/scala/tools/partest/nest/NestUI.scala b/src/main/scala/scala/tools/partest/nest/NestUI.scala index e70f4e9..0913782 100644 --- a/src/main/scala/scala/tools/partest/nest/NestUI.scala +++ b/src/main/scala/scala/tools/partest/nest/NestUI.scala @@ -8,7 +8,7 @@ package nest import java.io.PrintWriter -class Colors(enabled: => Boolean) { +class Colors(enabled: Boolean) { import Console._ val bold = colored(BOLD) @@ -16,8 +16,6 @@ class Colors(enabled: => Boolean) { val green = colored(GREEN) val blue = colored(BLUE) val red = colored(RED) - val red_b = colored(RED_B) - val green_b = colored(GREEN_B) val cyan = colored(CYAN) val magenta = colored(MAGENTA) @@ -25,33 +23,27 @@ class Colors(enabled: => Boolean) { s => if (enabled) code + s + RESET else s } -object NestUI { - private val testNum = new java.util.concurrent.atomic.AtomicInteger(1) - @volatile private var testNumberFmt = "%3d" - // @volatile private var testNumber = 1 - private def testNumber = testNumberFmt format testNum.getAndIncrement() +class NestUI(val verbose: Boolean = false, val debug: Boolean = false, val terse: Boolean = false, + val diffOnFail: Boolean = false, val logOnFail: Boolean = false, + val colorEnabled: Boolean = sys.props contains "partest.colors") { + private[this] val testNum = new java.util.concurrent.atomic.AtomicInteger(1) + @volatile private[this] var testNumberFmt = "%3d" + private[this] def testNumber = testNumberFmt format testNum.getAndIncrement() def resetTestNumber(max: Int = -1) { testNum set 1 val width = if (max > 0) max.toString.length else 3 testNumberFmt = s"%${width}d" } - var colorEnabled = sys.props contains "partest.colors" val color = new Colors(colorEnabled) import color._ - val NONE = 0 - val SOME = 1 - val MANY = 2 + private[this] val (_outline, _success, _failure, _warning, _default) = + if (colorEnabled) (Console.BOLD, Console.BOLD + Console.GREEN, Console.BOLD + Console.RED, Console.BOLD + Console.YELLOW, Console.RESET) + else ("", "", "", "", "") - private var _outline = "" - private var _success = "" - private var _failure = "" - private var _warning = "" - private var _default = "" - - private var dotCount = 0 - private val DotWidth = 72 + private[this] var dotCount = 0 + private[this] val DotWidth = 72 def leftFlush() { if (dotCount != 0) { @@ -73,8 +65,8 @@ object NestUI { f"$word $testNumber - $testIdent%-40s$reasonString" } - def reportTest(state: TestState, info: TestInfo) = - if (isTerse && state.isOk) { + def reportTest(state: TestState, info: TestInfo): Unit = { + if (terse && state.isOk) { if (dotCount >= DotWidth) { outline("\n.") dotCount = 1 @@ -89,24 +81,25 @@ object NestUI { echo(bold(cyan(s"##### Log file '${info.logFile}' from failed test #####\n"))) echo(info.logFile.fileContents) } - if (isDiffy) { + if (diffOnFail) { val differ = bold(red("% ")) + "diff " val diffed = state.transcript find (_ startsWith differ) diffed match { case Some(diff) => echo(diff) - case None if !isLogging && !isPartestVerbose => showLog() + case None if !logOnFail && !verbose => showLog() case _ => () } } - if (isLogging) showLog() + if (logOnFail) showLog() } } + } def echo(message: String): Unit = synchronized { leftFlush() print(message + "\n") } - def chatty(msg: String) = if (isVerbose) echo(msg) + def chatty(msg: String): Unit = if (verbose) echo(msg) def echoSkipped(msg: String) = echo(yellow(msg)) def echoPassed(msg: String) = echo(bold(green(msg))) @@ -114,22 +107,6 @@ object NestUI { def echoMixed(msg: String) = echo(bold(yellow(msg))) def echoWarning(msg: String) = echo(bold(red(msg))) - def initialize(number: Int) = number match { - case MANY => - _outline = Console.BOLD + Console.BLACK - _success = Console.BOLD + Console.GREEN - _failure = Console.BOLD + Console.RED - _warning = Console.BOLD + Console.YELLOW - _default = Console.RESET - case SOME => - _outline = Console.BOLD + Console.BLACK - _success = Console.RESET - _failure = Console.BOLD + Console.BLACK - _warning = Console.BOLD + Console.BLACK - _default = Console.RESET - case _ => - } - def outline(msg: String) = print(_outline + msg + _default) def outline(msg: String, wr: PrintWriter) = synchronized { wr.print(_outline + msg + _default) @@ -158,39 +135,16 @@ object NestUI { sys.exit(1) } - var _verbose = false - var _debug = false - var _terse = false - var _diff = false - var _logging = false + def verbose(msg: String): Unit = + if (verbose) System.err.println(msg) - def isVerbose = _verbose - def isDebug = _debug - def isTerse = _terse - def isDiffy = _diff - def isLogging = _logging + def debug(msg: String): Unit = + if (debug) System.err.println(msg) - def setVerbose() { - _verbose = true - } - def setDebug() { - _debug = true - } - def setTerse() { - _terse = true - } - def setDiffOnFail() { - _diff = true - } - def setLogOnFail() { - _logging = true - } - def verbose(msg: String) { - if (isVerbose) - System.err.println(msg) - } - def debug(msg: String) { - if (isDebug) - System.err.println(msg) + def showAllJVMInfo(): Unit = { + vlog(vmArgString) + vlog(allPropertiesString) } + + def vlog(msg: => String) = if (verbose) System.err.println(msg) } diff --git a/src/main/scala/scala/tools/partest/nest/Runner.scala b/src/main/scala/scala/tools/partest/nest/Runner.scala index 0b9106c..0478a7d 100644 --- a/src/main/scala/scala/tools/partest/nest/Runner.scala +++ b/src/main/scala/scala/tools/partest/nest/Runner.scala @@ -26,22 +26,6 @@ import TestState.{ Pass, Fail, Crash, Uninitialized, Updated } import FileManager.{ compareFiles, compareContents, joinPaths, withTempFile } -class TestTranscript { - import NestUI.color._ - private val buf = ListBuffer[String]() - private def pass(s: String) = bold(green("% ")) + s - private def fail(s: String) = bold(red("% ")) + s - - def add(action: String): this.type = { buf += action ; this } - def append(text: String) { val s = buf.last ; buf.trimEnd(1) ; buf += (s + text) } - - // Colorize prompts according to pass/fail - def fail: List[String] = buf.toList match { - case Nil => Nil - case xs => (xs.init map pass) :+ fail(xs.last) - } -} - trait TestInfo { /** pos/t1234 */ def testIdent: String @@ -70,7 +54,7 @@ trait TestInfo { } /** Run a single test. Rubber meets road. */ -class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo { +class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestUI) extends TestInfo { import suiteRunner.{fileManager => fm, _} val fileManager = fm @@ -108,7 +92,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo def showCrashInfo(t: Throwable) { System.err.println(s"Crashed running test $testIdent: " + t) - if (!isPartestTerse) + if (!nestUI.terse) System.err.println(stackTraceString(t)) } protected def crashHandler: PartialFunction[Throwable, TestState] = { @@ -173,7 +157,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo val argsFile = testFile changeExtension "javaopts" val argString = file2String(argsFile) if (argString != "") - NestUI.verbose("Found javaopts file '%s', using options: '%s'".format(argsFile, argString)) + nestUI.verbose("Found javaopts file '%s', using options: '%s'".format(argsFile, argString)) val testFullPath = testFile.getAbsolutePath @@ -185,7 +169,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo // // debug: Found javaopts file 'files/shootout/message.scala-2.javaopts', using options: '-Xss32k' // debug: java -Xss32k -Xss2m -Xms256M -Xmx1024M -classpath [...] - val extras = if (isPartestDebug) List("-Dpartest.debug=true") else Nil + val extras = if (nestUI.debug) List("-Dpartest.debug=true") else Nil val propertyOptions = List( "-Dfile.encoding=UTF-8", "-Djava.library.path="+logFile.getParentFile.getAbsolutePath, @@ -229,11 +213,11 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo try p.exitValue catch { case e: InterruptedException => - NestUI verbose s"Interrupted waiting for command to finish (${args mkString " "})" + nestUI.verbose(s"Interrupted waiting for command to finish (${args mkString " "})") p.destroy nonzero case t: Throwable => - NestUI verbose s"Exception waiting for command to finish: $t (${args mkString " "})" + nestUI.verbose(s"Exception waiting for command to finish: $t (${args mkString " "})") p.destroy throw t } @@ -265,7 +249,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo } def fail(what: Any) = { - NestUI.verbose("scalac: compilation of "+what+" failed\n") + nestUI.verbose("scalac: compilation of "+what+" failed\n") false } @@ -381,8 +365,8 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo ) logFile.mapInPlace(canonicalize)(lineFilter) - if (isPartestVerbose && elisions.nonEmpty) { - import NestUI.color._ + if (nestUI.verbose && elisions.nonEmpty) { + import nestUI.color._ val emdash = bold(yellow("--")) pushTranscript(s"filtering ${logFile.getName}$EOL${elisions mkString (emdash, EOL + emdash, EOL)}") } @@ -400,7 +384,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo pushTranscript(s"diff $logFile $checkFile") nextTestAction(updating) { case Some(true) => - NestUI.verbose("Updating checkfile " + checkFile) + nestUI.verbose("Updating checkfile " + checkFile) checkFile writeAll file2String(logFile) genUpdated() case Some(false) => @@ -549,7 +533,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo // Apache Ant 1.6 or newer def ant(args: Seq[String], output: File): Boolean = { val antOptions = - if (NestUI._verbose) List("-verbose", "-noinput") + if (nestUI.verbose) List("-verbose", "-noinput") else List("-noinput") val cmd = javaCmdPath +: ( suiteRunner.javaOpts.split(' ').map(_.trim).filter(_ != "") ++ Seq( @@ -568,14 +552,14 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo val succeeded = try { val binary = "-Dbinary="+ fileManager.distKind val args = Array(binary, "-logfile", logFile.getPath, "-file", testFile.getPath) - NestUI.verbose("ant "+args.mkString(" ")) + nestUI.verbose("ant "+args.mkString(" ")) pushTranscript(s"ant ${args.mkString(" ")}") nextTestActionExpectTrue("ant failed", ant(args, logFile)) && diffIsOk } catch { // *catch-all* case e: Exception => - NestUI.warning("caught "+e) + nestUI.warning("caught "+e) false } @@ -592,7 +576,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo } def runScalacheckTest() = runTestCommon { - NestUI verbose f"compilation of $testFile succeeded%n" + nestUI.verbose(f"compilation of $testFile succeeded%n") val logWriter = new PrintStream(new FileOutputStream(logFile), true) @@ -624,7 +608,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo val claas = "Test" val fingerprint = f.tests collectFirst { case x: SubclassFingerprint if x.isModule => x } val args = toolArgs("scalacheck") - vlog(s"Run $testFile with args $args") + nestUI.vlog(s"Run $testFile with args $args") // set the context class loader for scaladoc/scalacheck tests (FIX ME) ScalaClassLoader(fileManager.testClassLoader).asContext { r.run(claas, fingerprint.get, handler, args.toArray) // synchronous? @@ -641,7 +625,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo val prompt = "\nnsc> " val (swr, wr) = newTestWriters() - NestUI.verbose(s"$this running test $fileBase") + nestUI.verbose(s"$this running test $fileBase") val dir = parentFile val resFile = new File(dir, fileBase + ".res") @@ -649,7 +633,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo // $SCALAC -d "$os_dstbase".obj -Xresident -sourcepath . "$@" val sourcedir = logFile.getParentFile.getAbsoluteFile val sourcepath = sourcedir.getAbsolutePath+File.separator - NestUI.verbose("sourcepath: "+sourcepath) + nestUI.verbose("sourcepath: "+sourcepath) val argList = List( "-d", outDir.getAbsoluteFile.getPath, @@ -769,7 +753,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner) extends TestInfo def cleanup() { if (lastState.isOk) logFile.delete() - if (!isPartestDebug) + if (!nestUI.debug) Directory(outDir).deleteRecursively() } @@ -787,6 +771,7 @@ class SuiteRunner( val fileManager: FileManager, val updateCheck: Boolean, val failed: Boolean, + val nestUI: NestUI, val javaCmdPath: String = PartestDefaults.javaCmd, val javacCmdPath: String = PartestDefaults.javacCmd, val scalacExtraArgs: Seq[String] = Seq.empty, @@ -824,7 +809,7 @@ class SuiteRunner( def onFinishTest(testFile: File, result: TestState): TestState = result def runTest(testFile: File): TestState = { - val runner = new Runner(testFile, this) + val runner = new Runner(testFile, this, nestUI) // when option "--failed" is provided execute test only if log // is present (which means it failed before) @@ -837,7 +822,7 @@ class SuiteRunner( catch { case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) } - NestUI.reportTest(state, runner) + nestUI.reportTest(state, runner) runner.cleanup() state } @@ -845,7 +830,7 @@ class SuiteRunner( } def runTestsForFiles(kindFiles: Array[File], kind: String): Array[TestState] = { - NestUI.resetTestNumber(kindFiles.size) + nestUI.resetTestNumber(kindFiles.size) val pool = Executors newFixedThreadPool numThreads val futures = kindFiles map (f => pool submit callable(runTest(f.getAbsoluteFile))) @@ -858,9 +843,9 @@ class SuiteRunner( case Failure(e) => e match { case TimeoutException(d) => - NestUI warning "Thread pool timeout elapsed before all tests were complete!" + nestUI.warning("Thread pool timeout elapsed before all tests were complete!") case ie: InterruptedException => - NestUI warning "Thread pool was interrupted" + nestUI.warning("Thread pool was interrupted") ie.printStackTrace() } pool.shutdownNow() // little point in continuing @@ -875,6 +860,24 @@ class SuiteRunner( results.flatten } } + + class TestTranscript { + private val buf = ListBuffer[String]() + + def add(action: String): this.type = { buf += action ; this } + def append(text: String) { val s = buf.last ; buf.trimEnd(1) ; buf += (s + text) } + + // Colorize prompts according to pass/fail + def fail: List[String] = { + import nestUI.color._ + def pass(s: String) = bold(green("% ")) + s + def fail(s: String) = bold(red("% ")) + s + buf.toList match { + case Nil => Nil + case xs => (xs.init map pass) :+ fail(xs.last) + } + } + } } case class TimeoutException(duration: Duration) extends RuntimeException diff --git a/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala b/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala index 974a5e9..229ac59 100644 --- a/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala +++ b/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala @@ -2,7 +2,7 @@ package scala.tools.partest.nest import language.postfixOps -import scala.tools.cmd.{ CommandLine, Interpolation, Meta, Reference, Spec } +import scala.tools.cmd.{ CommandLine, Interpolation, Meta, Reference, Spec, Instance } trait RunnerSpec extends Spec with Meta.StdOpts with Interpolation { def referenceSpec = RunnerSpec @@ -49,6 +49,10 @@ trait RunnerSpec extends Spec with Meta.StdOpts with Interpolation { } object RunnerSpec extends RunnerSpec with Reference { + trait Config extends RunnerSpec with Instance + type ThisCommandLine = CommandLine def creator(args: List[String]): ThisCommandLine = new CommandLine(RunnerSpec, args) + + def forArgs(args: Array[String]): Config = new { val parsed = creator(args.toList) } with Config } diff --git a/src/main/scala/scala/tools/partest/package.scala b/src/main/scala/scala/tools/partest/package.scala index 43fb611..0d56ac3 100644 --- a/src/main/scala/scala/tools/partest/package.scala +++ b/src/main/scala/scala/tools/partest/package.scala @@ -7,7 +7,6 @@ package scala.tools import java.util.concurrent.{ Callable, ExecutorService } import scala.concurrent.duration.Duration import scala.sys.process.javaVmArguments -import scala.tools.partest.nest.NestUI import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } package object partest { @@ -181,15 +180,4 @@ package object partest { import scala.collection.JavaConversions._ System.getProperties.toList.sorted map { case (k, v) => "%s -> %s\n".format(k, v) } mkString "" } - - def showAllJVMInfo() { - vlog(vmArgString) - vlog(allPropertiesString) - } - - def isPartestTerse = NestUI.isTerse - def isPartestDebug = NestUI.isDebug - def isPartestVerbose = NestUI.isVerbose - - def vlog(msg: => String) = if (isPartestVerbose) System.err.println(msg) } diff --git a/src/main/scala/scala/tools/partest/sbt/Framework.scala b/src/main/scala/scala/tools/partest/sbt/Framework.scala index 489dae6..068910a 100644 --- a/src/main/scala/scala/tools/partest/sbt/Framework.scala +++ b/src/main/scala/scala/tools/partest/sbt/Framework.scala @@ -1,6 +1,6 @@ package scala.tools.partest.sbt -import scala.tools.partest.nest.SBTRunner +import scala.tools.partest.nest.RunnerSpec import _root_.sbt.testing._ import java.net.URLClassLoader import java.io.File @@ -34,7 +34,8 @@ case class PartestTask(taskDef: TaskDef, args: Array[String]) extends Task { 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 = new SBTRunner(taskDef.fingerprint(), eventHandler, loggers, "files", classLoader, null, null, Array.empty[String], args) + val config = RunnerSpec.forArgs(args.filter(a => !a.startsWith("-D"))) + val runner = new SBTRunner(config, 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"""")) diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala similarity index 63% rename from src/main/scala/scala/tools/partest/nest/SBTRunner.scala rename to src/main/scala/scala/tools/partest/sbt/SBTRunner.scala index a79dfcc..d42d082 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala @@ -6,26 +6,42 @@ ** |/ ** \* */ -package scala.tools.partest -package nest +package scala.tools.partest.sbt -import _root_.sbt.testing._ import java.net.URLClassLoader -import TestState._ -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(" ")) { +import _root_.sbt.testing._ + +import scala.tools.partest.TestState._ +import scala.tools.partest._ +import scala.tools.partest.nest.{AbstractRunner, FileManager, RunnerSpec, SuiteRunner} + +class SBTRunner(val config: RunnerSpec.Config, + partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], + srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, + scalacArgs: Array[String], args: Array[String]) extends AbstractRunner { // no summary, SBT will do that for us - printSummary = false - partestCmd = "partest" + override protected val printSummary = false + override protected val partestCmd = "partest" val defs = { val Def = "-D([^=]*)=(.*)".r args.collect { case Def(k, v) => (k, v) } } + // Enable colors if there's an explicit override or all loggers support them + override protected val colorEnabled = { + val ptOverride = defs.collect { case ("partest.colors", v) => v.toBoolean }.lastOption + ptOverride.getOrElse { + val sbtOverride1 = sys.props.get("sbt.log.format").map(_.toBoolean) + val sbtOverride2 = sys.props.get("sbt.log.noformat").map(s => !s.toBoolean) + sbtOverride1.orElse(sbtOverride2).getOrElse { + loggers.forall(_.ansiCodesSupported()) + } + } + } + val javaOpts = { val l = defs.collect { case ("partest.java_opts", v) => v } if(l.isEmpty) PartestDefaults.javaOpts @@ -38,11 +54,12 @@ class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, log else l.mkString(" ") } - override val suiteRunner = new SuiteRunner( - testSourcePath = optSourcePath orElse Option(srcDir) getOrElse PartestDefaults.sourcePath, + val suiteRunner = new SuiteRunner( + testSourcePath = config.optSourcePath orElse Option(srcDir) getOrElse PartestDefaults.sourcePath, new FileManager(testClassLoader = testClassLoader), - updateCheck = optUpdateCheck, - failed = optFailed, + updateCheck = config.optUpdateCheck, + failed = config.optFailed, + nestUI = nestUI, javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, scalacExtraArgs = scalacArgs,