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/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/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/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/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..46006e1 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -19,33 +19,50 @@ 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], - 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 - }) - - 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 + srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], args: Array[String]) + 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), + updateCheck = optUpdateCheck, + failed = optFailed, + javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, + javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, + scalacExtraArgs = scalacArgs, + javaOpts = javaOpts) { + + 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) + 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)) } }