From 16ba946e94eba3096e0bdd0ea19c33f43f81b91e Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Mon, 20 Oct 2014 12:29:26 +0200 Subject: [PATCH 1/3] update sbt and dependency versions --- build.sbt | 4 ++-- project/build.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 0cc5778..f51fbb1 100644 --- a/build.sbt +++ b/build.sbt @@ -6,11 +6,11 @@ name := "scala-partest" version := "1.0.2-SNAPSHOT" -scalaVersion := "2.11.0" +scalaVersion := "2.11.2" scalaXmlVersion := "1.0.1" -scalaCheckVersion := "1.11.3" +scalaCheckVersion := "1.11.4" // used as binary version when compiling against 2.12.0-SNAPSHOT snapshotScalaBinaryVersion := "2.11" diff --git a/project/build.properties b/project/build.properties index 37b489c..64abd37 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.1 +sbt.version=0.13.6 From ac86eb77f10782174524464bb52009983971f024 Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Mon, 20 Oct 2014 15:17:44 +0200 Subject: [PATCH 2/3] extract internal partest behavior from ConsoleRunner This splits ConsoleRunner in two: AbstractRunner contains most of the behavior but doesn't assume (or at least less that before...) that we are running from the command line. ConsoleRunner extends it to do the same as before. Now SBTRunner also extends AbstractRunner (instead of AntRunner), to get support for --grep, --update-check, and all other options, for testing an explicit file, and for pretty output. This requires changes on the scala-partest-interface side too (SBTRunner's public API has changed). --- .../tools/partest/nest/AbstractRunner.scala | 180 ++++++++++++++++++ .../tools/partest/nest/ConsoleRunner.scala | 172 +---------------- .../scala/tools/partest/nest/NestUI.scala | 4 +- ...nsoleRunnerSpec.scala => RunnerSpec.scala} | 8 +- .../scala/tools/partest/nest/SBTRunner.scala | 51 ++--- 5 files changed, 217 insertions(+), 198 deletions(-) create mode 100644 src/main/scala/scala/tools/partest/nest/AbstractRunner.scala rename src/main/scala/scala/tools/partest/nest/{ConsoleRunnerSpec.scala => RunnerSpec.scala} (93%) diff --git a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala new file mode 100644 index 0000000..f93107f --- /dev/null +++ b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala @@ -0,0 +1,180 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Philipp Haller + */ + +package scala.tools +package partest +package nest + +import utils.Properties._ +import scala.tools.nsc.Properties.{ versionMsg, propOrFalse, setProp } +import scala.collection.{ mutable, immutable } +import TestKinds._ +import scala.reflect.internal.util.Collections.distinctBy +import scala.tools.cmd.{ CommandLine, CommandLineParser, Instance } + +abstract class AbstractRunner(argstr: String) extends { + val parsed = RunnerSpec.creator(CommandLineParser tokenize argstr) +} with RunnerSpec with Instance { + + val suiteRunner: SuiteRunner + + import suiteRunner._ + import NestUI._ + import NestUI.color._ + + private var totalTests = 0 + private val passedTests = mutable.ListBuffer[TestState]() + private val failedTests = mutable.ListBuffer[TestState]() + + def comment(s: String) = echo(magenta("# " + s)) + def levyJudgment() = { + if (totalTests == 0) echoMixed("No tests to run.") + else if (elapsedMillis == 0) echoMixed("Test Run ABORTED") + else if (isSuccess) echoPassed("Test Run PASSED") + else echoFailed("Test Run FAILED") + } + + def passFailString(passed: Int, failed: Int, skipped: Int): String = { + val total = passed + failed + skipped + val isSuccess = failed == 0 + def p0 = s"$passed/$total" + def p = ( if (isSuccess) bold(green(p0)) else p0 ) + " passed" + def f = if (failed == 0) "" else bold(red("" + failed)) + " failed" + def s = if (skipped == 0) "" else bold(yellow("" + skipped)) + " skipped" + + oempty(p, f, s) mkString ", " + } + + protected var summarizing = false + private var elapsedMillis = 0L + private var expectedFailures = 0 + protected def isSuccess = failedTests.size == expectedFailures + + def issueSummaryReport() { + // Don't run twice + if (!summarizing) { + summarizing = true + + val passed0 = passedTests.toList + val failed0 = failedTests.toList + val passed = passed0.size + val failed = failed0.size + val skipped = totalTests - (passed + failed) + val passFail = passFailString(passed, failed, skipped) + val elapsed = if (elapsedMillis > 0) " (elapsed time: " + elapsedString(elapsedMillis) + ")" else "" + val message = passFail + elapsed + + if (failed0.nonEmpty) { + if (isPartestVerbose) { + echo(bold(cyan("##### Transcripts from failed tests #####\n"))) + failed0 foreach { state => + comment("partest " + state.testFile) + echo(state.transcriptString + "\n") + } + } + + def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") + echo("# Failed test paths (this command will update checkfiles)") + echo("test/partest --update-check \\\n " + files_s + "\n") + } + + echo(message) + levyJudgment() + } + } + + def run(): Unit = { + if (optDebug || propOrFalse("partest.debug")) NestUI.setDebug() + if (optVerbose) NestUI.setVerbose() + if (optTerse) NestUI.setTerse() + if (optShowDiff) NestUI.setDiffOnFail() + + // Early return on no args, version, or invalid args + if (optVersion) return echo(versionMsg) + if (optHelp) return NestUI.usage() + + val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath + if (invalid.nonEmpty) { + if (isPartestVerbose) + invalid foreach (p => echoWarning(s"Discarding invalid test path " + p)) + else if (!isPartestTerse) + echoWarning(s"Discarding ${invalid.size} invalid test paths") + } + + optTimeout foreach (x => setProp("partest.timeout", x)) + + if (!isPartestTerse) + NestUI echo banner + + val partestTests = ( + if (optSelfTest) TestKinds.testsForPartest + else Nil + ) + + val grepExpr = optGrep getOrElse "" + + // If --grep is given we suck in every file it matches. + // TODO: intersect results of grep with specified kinds, if any + val greppedTests = if (grepExpr == "") Nil else { + val paths = grepFor(grepExpr) + if (paths.isEmpty) + echoWarning(s"grep string '$grepExpr' matched no tests.\n") + + paths.sortBy(_.toString) + } + + val isRerun = optFailed + val rerunTests = if (isRerun) TestKinds.failedTests else Nil + def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests + + val givenKinds = standardKinds filter parsed.isSet + val kinds = ( + if (givenKinds.nonEmpty) givenKinds + else if (miscTests.isEmpty) standardKinds // If no kinds, --grep, or individual tests were given, assume --all + else Nil + ) + val kindsTests = kinds flatMap testsFor + + def testContributors = { + List( + if (partestTests.isEmpty) "" else "partest self-tests", + if (rerunTests.isEmpty) "" else "previously failed tests", + if (kindsTests.isEmpty) "" else s"${kinds.size} named test categories", + if (greppedTests.isEmpty) "" else s"${greppedTests.size} tests matching '$grepExpr'", + if (individualTests.isEmpty) "" else "specified tests" + ) filterNot (_ == "") mkString ", " + } + + val allTests: Array[Path] = distinctBy(miscTests ++ kindsTests)(_.toCanonical) sortBy (_.toString) toArray + val grouped = (allTests groupBy kindOf).toArray sortBy (x => standardKinds indexOf x._1) + + totalTests = allTests.size + expectedFailures = propOrNone("partest.errors") match { + case Some(num) => num.toInt + case _ => 0 + } + val expectedFailureMessage = if (expectedFailures == 0) "" else s" (expecting $expectedFailures to fail)" + echo(s"Selected $totalTests tests drawn from $testContributors$expectedFailureMessage\n") + + val (_, millis) = timed { + for ((kind, paths) <- grouped) { + val num = paths.size + val ss = if (num == 1) "" else "s" + comment(s"starting $num test$ss in $kind") + val results = runTestsForFiles(paths map (_.jfile.getAbsoluteFile), kind) + val (passed, failed) = results partition (_.isOk) + + passedTests ++= passed + failedTests ++= failed + if (failed.nonEmpty) { + comment(passFailString(passed.size, failed.size, 0) + " in " + kind) + } + echo("") + } + } + this.elapsedMillis = millis + issueSummaryReport() + } +} diff --git a/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala b/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala index f199832..462fc2f 100644 --- a/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/ConsoleRunner.scala @@ -7,185 +7,23 @@ package scala.tools package partest package nest -import utils.Properties._ -import scala.tools.nsc.Properties.{ versionMsg, propOrFalse, setProp } -import scala.collection.{ mutable, immutable } -import TestKinds._ -import scala.reflect.internal.util.Collections.distinctBy -import scala.tools.cmd.{ CommandLine, CommandLineParser, Instance } +class ConsoleRunner(argstr: String) extends AbstractRunner(argstr) { -class ConsoleRunner(argstr: String) extends { - val parsed = ConsoleRunnerSpec.creator(CommandLineParser tokenize argstr) -} with ConsoleRunnerSpec with Instance { - - val suiteRunner = new SuiteRunner ( + override val suiteRunner = new SuiteRunner ( testSourcePath = optSourcePath getOrElse PartestDefaults.sourcePath, fileManager = new FileManager(ClassPath split PathResolver.Environment.javaUserClassPath map (Path(_))), // the script sets up our classpath for us via ant updateCheck = optUpdateCheck, failed = optFailed) - import suiteRunner._ - import NestUI._ - import NestUI.color._ // So we can ctrl-C a test run and still hear all // the buffered failure info. scala.sys addShutdownHook issueSummaryReport() - private var totalTests = 0 - private val passedTests = mutable.ListBuffer[TestState]() - private val failedTests = mutable.ListBuffer[TestState]() - - def comment(s: String) = echo(magenta("# " + s)) - def levyJudgment() = { - if (totalTests == 0) echoMixed("No tests to run.") - else if (elapsedMillis == 0) echoMixed("Test Run ABORTED") - else if (isSuccess) echoPassed("Test Run PASSED") - else echoFailed("Test Run FAILED") - } - - def passFailString(passed: Int, failed: Int, skipped: Int): String = { - val total = passed + failed + skipped - val isSuccess = failed == 0 - def p0 = s"$passed/$total" - def p = ( if (isSuccess) bold(green(p0)) else p0 ) + " passed" - def f = if (failed == 0) "" else bold(red("" + failed)) + " failed" - def s = if (skipped == 0) "" else bold(yellow("" + skipped)) + " skipped" - - oempty(p, f, s) mkString ", " - } - - private var summarizing = false - private var elapsedMillis = 0L - private var expectedFailures = 0 - private def isSuccess = failedTests.size == expectedFailures - - def issueSummaryReport() { - // Don't run twice - if (!summarizing) { - summarizing = true - - val passed0 = passedTests.toList - val failed0 = failedTests.toList - val passed = passed0.size - val failed = failed0.size - val skipped = totalTests - (passed + failed) - val passFail = passFailString(passed, failed, skipped) - val elapsed = if (elapsedMillis > 0) " (elapsed time: " + elapsedString(elapsedMillis) + ")" else "" - val message = passFail + elapsed - - if (failed0.nonEmpty) { - if (isPartestVerbose) { - echo(bold(cyan("##### Transcripts from failed tests #####\n"))) - failed0 foreach { state => - comment("partest " + state.testFile) - echo(state.transcriptString + "\n") - } - } - - def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") - echo("# Failed test paths (this command will update checkfiles)") - echo("test/partest --update-check \\\n " + files_s + "\n") - } - - echo(message) - levyJudgment() - } - } - - def run(): Unit = { - if (optDebug || propOrFalse("partest.debug")) NestUI.setDebug() - if (optVerbose) NestUI.setVerbose() - if (optTerse) NestUI.setTerse() - if (optShowDiff) NestUI.setDiffOnFail() - - // Early return on no args, version, or invalid args - if (optVersion) return echo(versionMsg) - if (optHelp) return NestUI.usage() - - val (individualTests, invalid) = parsed.residualArgs map (p => Path(p)) partition denotesTestPath - if (invalid.nonEmpty) { - if (isPartestVerbose) - invalid foreach (p => echoWarning(s"Discarding invalid test path " + p)) - else if (!isPartestTerse) - echoWarning(s"Discarding ${invalid.size} invalid test paths") - } - - optTimeout foreach (x => setProp("partest.timeout", x)) - - if (!isPartestTerse) - NestUI echo banner - - val partestTests = ( - if (optSelfTest) TestKinds.testsForPartest - else Nil - ) - - val grepExpr = optGrep getOrElse "" - - // If --grep is given we suck in every file it matches. - // TODO: intersect results of grep with specified kinds, if any - val greppedTests = if (grepExpr == "") Nil else { - val paths = grepFor(grepExpr) - if (paths.isEmpty) - echoWarning(s"grep string '$grepExpr' matched no tests.\n") - - paths.sortBy(_.toString) - } - - val isRerun = optFailed - val rerunTests = if (isRerun) TestKinds.failedTests else Nil - def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests - - val givenKinds = standardKinds filter parsed.isSet - val kinds = ( - if (givenKinds.nonEmpty) givenKinds - else if (miscTests.isEmpty) standardKinds // If no kinds, --grep, or individual tests were given, assume --all - else Nil - ) - val kindsTests = kinds flatMap testsFor - - def testContributors = { - List( - if (partestTests.isEmpty) "" else "partest self-tests", - if (rerunTests.isEmpty) "" else "previously failed tests", - if (kindsTests.isEmpty) "" else s"${kinds.size} named test categories", - if (greppedTests.isEmpty) "" else s"${greppedTests.size} tests matching '$grepExpr'", - if (individualTests.isEmpty) "" else "specified tests" - ) filterNot (_ == "") mkString ", " - } - - val allTests: Array[Path] = distinctBy(miscTests ++ kindsTests)(_.toCanonical) sortBy (_.toString) toArray - val grouped = (allTests groupBy kindOf).toArray sortBy (x => standardKinds indexOf x._1) - - totalTests = allTests.size - expectedFailures = propOrNone("partest.errors") match { - case Some(num) => num.toInt - case _ => 0 - } - val expectedFailureMessage = if (expectedFailures == 0) "" else s" (expecting $expectedFailures to fail)" - echo(s"Selected $totalTests tests drawn from $testContributors$expectedFailureMessage\n") - - val (_, millis) = timed { - for ((kind, paths) <- grouped) { - val num = paths.size - val ss = if (num == 1) "" else "s" - comment(s"starting $num test$ss in $kind") - val results = runTestsForFiles(paths map (_.jfile.getAbsoluteFile), kind) - val (passed, failed) = results partition (_.isOk) - - passedTests ++= passed - failedTests ++= failed - if (failed.nonEmpty) { - comment(passFailString(passed.size, failed.size, 0) + " in " + kind) - } - echo("") - } - } - this.elapsedMillis = millis - issueSummaryReport() + override def run(): Unit = { + super.run() System exit ( if (isSuccess) 0 else 1 ) } - + run() } diff --git a/src/main/scala/scala/tools/partest/nest/NestUI.scala b/src/main/scala/scala/tools/partest/nest/NestUI.scala index 5148115..a22351e 100644 --- a/src/main/scala/scala/tools/partest/nest/NestUI.scala +++ b/src/main/scala/scala/tools/partest/nest/NestUI.scala @@ -144,8 +144,8 @@ object NestUI { } def usage() { - println(ConsoleRunnerSpec.programInfo.usage) - println(ConsoleRunnerSpec.helpMsg) + println(RunnerSpec.programInfo.usage) + println(RunnerSpec.helpMsg) sys.exit(1) } diff --git a/src/main/scala/scala/tools/partest/nest/ConsoleRunnerSpec.scala b/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala similarity index 93% rename from src/main/scala/scala/tools/partest/nest/ConsoleRunnerSpec.scala rename to src/main/scala/scala/tools/partest/nest/RunnerSpec.scala index 86a0ecd..46d0088 100644 --- a/src/main/scala/scala/tools/partest/nest/ConsoleRunnerSpec.scala +++ b/src/main/scala/scala/tools/partest/nest/RunnerSpec.scala @@ -4,8 +4,8 @@ import language.postfixOps import scala.tools.cmd.{ CommandLine, Interpolation, Meta, Reference, Spec } -trait ConsoleRunnerSpec extends Spec with Meta.StdOpts with Interpolation { - def referenceSpec = ConsoleRunnerSpec +trait RunnerSpec extends Spec with Meta.StdOpts with Interpolation { + def referenceSpec = RunnerSpec def programInfo = Spec.Info( "console-runner", "Usage: ConsoleRunner [options] [test test ...]", @@ -47,7 +47,7 @@ trait ConsoleRunnerSpec extends Spec with Meta.StdOpts with Interpolation { } -object ConsoleRunnerSpec extends ConsoleRunnerSpec with Reference { +object RunnerSpec extends RunnerSpec with Reference { type ThisCommandLine = CommandLine - def creator(args: List[String]): ThisCommandLine = new CommandLine(ConsoleRunnerSpec, args) + def creator(args: List[String]): ThisCommandLine = new CommandLine(RunnerSpec, args) } diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index ec204e8..5e041b3 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -22,30 +22,31 @@ import java.net.URLClassLoader // called reflectively from scala-partest-test-interface class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], - srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String]) - extends AntRunner(srcDir, testClassLoader, javaCmd, javacCmd, scalacArgs) { - override def error(msg: String): Nothing = sys.error(msg) - def echo(msg: String): Unit = loggers foreach { l => l.info(msg) } - def log(msg: String): Unit = loggers foreach { l => l.debug(msg) } - def onFinishKind(kind: String, passed: Array[TestState], failed: Array[TestState]): Unit = - eventHandler.handle(new Event { - def fullyQualifiedName: String = kind - def fingerprint: Fingerprint = partestFingerprint - def selector: Selector = new SuiteSelector - def status: Status = if (failed.isEmpty) Status.Success else Status.Failure - def throwable: OptionalThrowable = new OptionalThrowable - def duration: Long = -1 - }) + srcDir: String, testClassLoader: URLClassLoader, javaCmd: File, javacCmd: File, scalacArgs: Array[String], args: Array[String]) + extends AbstractRunner(args.mkString(" ")) { - override def onFinishTest(testFile: File, result: TestState): TestState = { - eventHandler.handle(new Event { - def fullyQualifiedName: String = testFile.testIdent - def fingerprint: Fingerprint = partestFingerprint - def selector: Selector = new TestSelector(testFile.testIdent) - def status: Status = if (result.isOk) Status.Success else Status.Failure - def throwable: OptionalThrowable = new OptionalThrowable - def duration: Long = -1 - }) - result - } + // no summary, SBT will do that for us + summarizing = true + + override val suiteRunner = new SuiteRunner( + testSourcePath = Option(srcDir) getOrElse PartestDefaults.sourcePath, + new FileManager(testClassLoader = testClassLoader), + updateCheck = optUpdateCheck, + failed = optFailed, + javaCmdPath = Option(javaCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javaCmd, + javacCmdPath = Option(javacCmd).map(_.getAbsolutePath) getOrElse PartestDefaults.javacCmd, + scalacExtraArgs = scalacArgs) { + + override def onFinishTest(testFile: File, result: TestState): TestState = { + eventHandler.handle(new Event { + def fullyQualifiedName: String = testFile.testIdent + def fingerprint: Fingerprint = partestFingerprint + def selector: Selector = new TestSelector(testFile.testIdent) + def status: Status = if (result.isOk) Status.Success else Status.Failure + def throwable: OptionalThrowable = new OptionalThrowable + def duration: Long = -1 + }) + result + } + } } From 91c005a5e9abaad1d84557af704f7c1ce9d442a0 Mon Sep 17 00:00:00 2001 From: Antoine Gourlay Date: Mon, 20 Oct 2014 16:01:43 +0200 Subject: [PATCH 3/3] improve mapping from partest's TestState to sbt's Status. --- .../scala/scala/tools/partest/nest/SBTRunner.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala index 5e041b3..f11dfd9 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -19,6 +19,7 @@ import sbt.testing.OptionalThrowable import sbt.testing.SuiteSelector import sbt.testing.TestSelector import java.net.URLClassLoader +import TestState._ // called reflectively from scala-partest-test-interface class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, loggers: Array[Logger], @@ -42,11 +43,19 @@ class SBTRunner(partestFingerprint: Fingerprint, eventHandler: EventHandler, log def fullyQualifiedName: String = testFile.testIdent def fingerprint: Fingerprint = partestFingerprint def selector: Selector = new TestSelector(testFile.testIdent) - def status: Status = if (result.isOk) Status.Success else Status.Failure - def throwable: OptionalThrowable = new OptionalThrowable + val (status, throwable) = makeStatus(result) def duration: Long = -1 }) result } } + + def makeStatus(t: TestState): (Status, OptionalThrowable) = t match { + case _: Uninitialized => (Status.Pending, new OptionalThrowable) + case _: Pass => (Status.Success, new OptionalThrowable) + case _: Updated => (Status.Success, new OptionalThrowable) + case _: Skip => (Status.Skipped, new OptionalThrowable) + case _: Fail => (Status.Failure, new OptionalThrowable) + case Crash(_,e,_) => (Status.Error, new OptionalThrowable(e)) + } }