From bd2d2c6c12d5a49aa9ec7eeb06f9e9c9d7928487 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Thu, 18 Feb 2016 19:58:08 +0100 Subject: [PATCH 1/3] Clean up the outer layers of partest - Refactored for less mutability - Narrowed scopes - Removed obsolete code --- .../scala/tools/partest/PartestTask.scala | 6 +- .../tools/partest/nest/AbstractRunner.scala | 206 +++++++++--------- .../scala/tools/partest/nest/AntRunner.scala | 3 +- .../tools/partest/nest/ConsoleRunner.scala | 33 +-- .../tools/partest/nest/DirectCompiler.scala | 2 +- .../scala/tools/partest/nest/NestUI.scala | 82 +++---- .../scala/tools/partest/nest/Runner.scala | 81 +++---- .../scala/tools/partest/nest/RunnerSpec.scala | 6 +- .../scala/tools/partest/nest/SBTRunner.scala | 20 +- .../scala/scala/tools/partest/package.scala | 12 - .../scala/tools/partest/sbt/Framework.scala | 5 +- 11 files changed, 213 insertions(+), 243 deletions(-) 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..c5f4082 100644 --- a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala @@ -12,31 +12,46 @@ 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 + + val nestUI: NestUI = new NestUI( + verbose = config.optVerbose, + debug = config.optDebug || propOrFalse("partest.debug"), + terse = config.optTerse, + diffOnFail = config.optShowDiff, + logOnFail = config.optShowLog, + colorEnabled = sys.props contains "partest.colors" + ) val suiteRunner: SuiteRunner - import suiteRunner._ - import NestUI._ - import NestUI.color._ + protected val printSummary = true + protected val partestCmd = "test/partest" + + 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 +62,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 +79,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 +99,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..57909f8 100644 --- a/src/main/scala/scala/tools/partest/nest/NestUI.scala +++ b/src/main/scala/scala/tools/partest/nest/NestUI.scala @@ -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,18 +23,18 @@ 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._ @@ -44,14 +42,14 @@ object NestUI { val SOME = 1 val MANY = 2 - private var _outline = "" - private var _success = "" - private var _failure = "" - private var _warning = "" - private var _default = "" + private[this] var _outline = "" + private[this] var _success = "" + private[this] var _failure = "" + private[this] var _warning = "" + private[this] 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 +71,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 +87,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))) @@ -158,39 +157,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/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index a79dfcc..1d17dec 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -13,13 +13,14 @@ 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(" ")) { +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 @@ -38,11 +39,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, 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..a89dbb3 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.{SBTRunner, 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"""")) From e790f9717b4af8cc5e19a97b15c7e66e75fdbe50 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Fri, 19 Feb 2016 13:53:35 +0100 Subject: [PATCH 2/3] Enable colors for terse output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was a choice for “many”, “some” or “none” but no way to set it. The color choices were impractical anyway (bold black foreground on standard background). Now the colors for terse output provide proper visibility and are enabled together with colored output. --- .../scala/tools/partest/nest/NestUI.scala | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/main/scala/scala/tools/partest/nest/NestUI.scala b/src/main/scala/scala/tools/partest/nest/NestUI.scala index 57909f8..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) @@ -38,15 +38,9 @@ class NestUI(val verbose: Boolean = false, val debug: Boolean = false, val terse val color = new Colors(colorEnabled) import color._ - val NONE = 0 - val SOME = 1 - val MANY = 2 - - private[this] var _outline = "" - private[this] var _success = "" - private[this] var _failure = "" - private[this] var _warning = "" - private[this] var _default = "" + 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[this] var dotCount = 0 private[this] val DotWidth = 72 @@ -113,22 +107,6 @@ class NestUI(val verbose: Boolean = false, val debug: Boolean = false, val terse 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) From c0ec7fa37ae56c47e00585ff604625c3b01d7b0e Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Fri, 19 Feb 2016 14:45:20 +0100 Subject: [PATCH 3/3] Enable colors from sbt --- .../tools/partest/nest/AbstractRunner.scala | 5 ++-- .../scala/tools/partest/sbt/Framework.scala | 2 +- .../partest/{nest => sbt}/SBTRunner.scala | 23 +++++++++++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) rename src/main/scala/scala/tools/partest/{nest => sbt}/SBTRunner.scala (80%) diff --git a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala index c5f4082..8981868 100644 --- a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala @@ -17,19 +17,20 @@ abstract class AbstractRunner { val config: RunnerSpec.Config - val nestUI: NestUI = new NestUI( + 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 = sys.props contains "partest.colors" + colorEnabled = colorEnabled ) val suiteRunner: SuiteRunner 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]() diff --git a/src/main/scala/scala/tools/partest/sbt/Framework.scala b/src/main/scala/scala/tools/partest/sbt/Framework.scala index a89dbb3..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, RunnerSpec} +import scala.tools.partest.nest.RunnerSpec import _root_.sbt.testing._ import java.net.URLClassLoader import java.io.File diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala similarity index 80% rename from src/main/scala/scala/tools/partest/nest/SBTRunner.scala rename to src/main/scala/scala/tools/partest/sbt/SBTRunner.scala index 1d17dec..d42d082 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/sbt/SBTRunner.scala @@ -6,12 +6,15 @@ ** |/ ** \* */ -package scala.tools.partest -package nest +package scala.tools.partest.sbt -import _root_.sbt.testing._ import java.net.URLClassLoader -import TestState._ + +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], @@ -27,6 +30,18 @@ class SBTRunner(val config: RunnerSpec.Config, 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