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 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..f11dfd9 100644 --- a/src/main/scala/scala/tools/partest/nest/SBTRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/SBTRunner.scala @@ -19,33 +19,43 @@ 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) { - 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) + 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)) } }