diff --git a/build.sbt b/build.sbt index 283c048..5fd012e 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,7 @@ scalaVersionsByJvm in ThisBuild := { scalaXmlVersion := "1.0.6" scalacOptions += "-Xfatal-warnings" +scalacOptions ++= "-Xelide-below" :: "0" :: "-Ywarn-unused" :: Nil enableOptimizer // dependencies diff --git a/project/build.properties b/project/build.properties index 64317fd..c091b86 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.15 +sbt.version=0.13.16 diff --git a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala index fbbc07e..04f606b 100644 --- a/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AbstractRunner.scala @@ -63,7 +63,7 @@ abstract class AbstractRunner { oempty(p, f, s) mkString ", " } - private[this] def isSuccess = failedTests.size == expectedFailures + private[this] def isSuccess = failedTests.size == expectedFailures && failedTests.size + passedTests.size > 0 def issueSummaryReport() { // Don't run twice @@ -87,12 +87,12 @@ abstract class AbstractRunner { echo(state.transcriptString + "\n") } } - - def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") - echo("# Failed test paths (this command will update checkfiles)") - echo(partestCmd + " --update-check \\\n " + files_s + "\n") + if (nestUI.verbose || failed0.size <= 50) { + def files_s = failed0.map(_.testFile).mkString(""" \""" + "\n ") + echo("# Failed test paths (this command will update checkfiles)") + echo(partestCmd + " --update-check \\\n " + files_s + "\n") + } } - if (printSummary) { echo(message) levyJudgment() @@ -118,10 +118,7 @@ abstract class AbstractRunner { if (!nestUI.terse) nestUI.echo(suiteRunner.banner) - val partestTests = ( - if (config.optSelfTest) TestKinds.testsForPartest - else Nil - ) + val partestTests = if (config.optSelfTest) TestKinds.testsForPartest else Nil val grepExpr = config.optGrep getOrElse "" @@ -140,9 +137,11 @@ abstract class AbstractRunner { def miscTests = partestTests ++ individualTests ++ greppedTests ++ rerunTests val givenKinds = standardKinds filter config.parsed.isSet + // named kinds to run, or else --all kinds, unless individual tests were specified + // (by path, --grep, --failed, even if there were invalid files specified) val kinds = ( if (givenKinds.nonEmpty) givenKinds - else if (miscTests.isEmpty && invalid.isEmpty) standardKinds // If no kinds, --grep, or individual tests were given, assume --all, unless there were invalid files specified + else if (miscTests.isEmpty && invalid.isEmpty) standardKinds else Nil ) val kindsTests = kinds flatMap testsFor @@ -168,18 +167,24 @@ abstract class AbstractRunner { val expectedFailureMessage = if (expectedFailures == 0) "" else s" (expecting $expectedFailures to fail)" echo(s"Selected $totalTests tests drawn from $testContributors$expectedFailureMessage\n") + var limping = false 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) + if (limping) comment(s"suite already in failure, skipping") else { + val results = suiteRunner.runTestsForFiles(paths.map(_.jfile.getAbsoluteFile)) match { + case Left((_, states)) => limping = true ; states + case Right(states) => states + } + val (passed, failed) = results partition (_.isOk) + + passedTests ++= passed + failedTests ++= failed + if (failed.nonEmpty) { + comment(passFailString(passed.size, failed.size, 0) + " in " + kind) + } } echo("") } diff --git a/src/main/scala/scala/tools/partest/nest/AntRunner.scala b/src/main/scala/scala/tools/partest/nest/AntRunner.scala index 12b4190..89960f2 100644 --- a/src/main/scala/scala/tools/partest/nest/AntRunner.scala +++ b/src/main/scala/scala/tools/partest/nest/AntRunner.scala @@ -33,7 +33,7 @@ abstract class AntRunner(srcDir: String, testClassLoader: URLClassLoader, javaCm else { log(s"Running ${files.length} tests in '$kind' at $now") // log(s"Tests: ${files.toList}") - val results = runTestsForFiles(files, kind) + val results = runTestsForFiles(files).fold(x => x._2, ss => ss) val (passed, failed) = results partition (_.isOk) val numPassed = passed.size val numFailed = failed.size diff --git a/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala b/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala index ac16414..66406e3 100644 --- a/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala +++ b/src/main/scala/scala/tools/partest/nest/DirectCompiler.scala @@ -7,10 +7,10 @@ package scala.tools.partest package nest import scala.collection.mutable.ListBuffer -import scala.tools.nsc.{ Global, Settings, CompilerCommand } -import scala.tools.nsc.reporters.{ Reporter, ConsoleReporter } +import scala.tools.nsc.{Global, Settings, CompilerCommand} +import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} import scala.reflect.io.AbstractFile -import java.io.{ PrintWriter, FileWriter } +import java.io.{PrintWriter, FileWriter} class ExtConsoleReporter(settings: Settings, val writer: PrintWriter) extends ConsoleReporter(settings, Console.in, writer) { shortname = true diff --git a/src/main/scala/scala/tools/partest/nest/FileManager.scala b/src/main/scala/scala/tools/partest/nest/FileManager.scala index 2be2a50..48e82f5 100644 --- a/src/main/scala/scala/tools/partest/nest/FileManager.scala +++ b/src/main/scala/scala/tools/partest/nest/FileManager.scala @@ -8,7 +8,7 @@ package scala.tools.partest package nest -import java.io.{ File, IOException } +import java.io.{File, IOException} import java.net.URLClassLoader object FileManager { diff --git a/src/main/scala/scala/tools/partest/nest/PathSettings.scala b/src/main/scala/scala/tools/partest/nest/PathSettings.scala index 67c1633..b22bc35 100644 --- a/src/main/scala/scala/tools/partest/nest/PathSettings.scala +++ b/src/main/scala/scala/tools/partest/nest/PathSettings.scala @@ -41,6 +41,6 @@ object PathSettings { // Directory /test/files or .../scaladoc def srcDir = Directory(testRoot / testSourcePath toCanonical) - def srcSpecLib = findJar("instrumented", Directory(srcDir / "speclib")) - def srcCodeLib = findJar("code", Directory(srcDir / "codelib"), Directory(testRoot / "files" / "codelib") /* work with --srcpath pending */) + def srcSpecLib = findJar("instrumented", Directory(srcDir / "speclib")) + def srcCodeLib = findJar("code", Directory(srcDir / "codelib"), Directory(testRoot / "files" / "codelib") /* work with --srcpath pending */) } diff --git a/src/main/scala/scala/tools/partest/nest/Runner.scala b/src/main/scala/scala/tools/partest/nest/Runner.scala index 4876204..da8e0a8 100644 --- a/src/main/scala/scala/tools/partest/nest/Runner.scala +++ b/src/main/scala/scala/tools/partest/nest/Runner.scala @@ -5,23 +5,23 @@ package scala.tools.partest package nest -import java.io.{ Console => _, _ } -import java.util.concurrent.Executors +import java.io.{Console => _, _} +import java.util.concurrent.{Executors, ExecutionException} import java.util.concurrent.TimeUnit.NANOSECONDS import scala.collection.mutable.ListBuffer import scala.concurrent.duration.Duration import scala.reflect.internal.FatalError import scala.reflect.internal.util.ScalaClassLoader -import scala.sys.process.{ Process, ProcessLogger } -import scala.tools.nsc.Properties.{ envOrNone, isWin, javaHome, propOrEmpty, versionMsg, javaVmName, javaVmVersion, javaVmInfo } -import scala.tools.nsc.{ Settings, CompilerCommand, Global } +import scala.sys.process.{Process, ProcessLogger} +import scala.tools.nsc.Properties.{envOrNone, isWin, javaHome, propOrEmpty, versionMsg, javaVmName, javaVmVersion, javaVmInfo} +import scala.tools.nsc.{Settings, CompilerCommand, Global} import scala.tools.nsc.reporters.ConsoleReporter import scala.tools.nsc.util.stackTraceString -import scala.util.{ Try, Success, Failure } +import scala.util.{Try, Success, Failure} import ClassPath.join -import TestState.{ Pass, Fail, Crash, Uninitialized, Updated } +import TestState.{Pass, Fail, Crash, Uninitialized, Updated} -import FileManager.{ compareContents, joinPaths, withTempFile } +import FileManager.{compareContents, joinPaths, withTempFile} trait TestInfo { /** pos/t1234 */ @@ -63,11 +63,11 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU // except for a . per passing test to show progress. def isEnumeratedTest = false - private var _lastState: TestState = null - private val _transcript = new TestTranscript + private[this] var _lastState: TestState = null + private[this] val _transcript = new TestTranscript def lastState = if (_lastState == null) Uninitialized(testFile) else _lastState - def setLastState(s: TestState) = _lastState = s + def lastState_=(s: TestState) = _lastState = s def transcript: List[String] = _transcript.fail ++ logFile.fileLines def pushTranscript(msg: String) = _transcript add msg @@ -93,7 +93,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU System.err.println(stackTraceString(t)) } protected def crashHandler: PartialFunction[Throwable, TestState] = { - case t: InterruptedException => + case _: InterruptedException => genTimeout() case t: Throwable => showCrashInfo(t) @@ -141,7 +141,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU */ def nextTestAction[T](body: => T)(failFn: PartialFunction[T, TestState]): T = { val result = body - setLastState( if (failFn isDefinedAt result) failFn(result) else genPass() ) + lastState = failFn.applyOrElse(result, (_: T) => genPass()) result } def nextTestActionExpectTrue(reason: String, body: => Boolean): Boolean = ( @@ -204,15 +204,14 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU protected def runCommand(args: Seq[String], outFile: File): Boolean = { //(Process(args) #> outFile !) == 0 or (Process(args) ! pl) == 0 val pl = ProcessLogger(outFile) - val nonzero = 17 // rounding down from 17.3 def run: Int = { val p = Process(args) run pl try p.exitValue catch { - case e: InterruptedException => + case ie: InterruptedException => nestUI.verbose(s"Interrupted waiting for command to finish (${args mkString " "})") p.destroy - nonzero + throw ie case t: Throwable => nestUI.verbose(s"Exception waiting for command to finish: $t (${args mkString " "})") p.destroy @@ -312,7 +311,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU "\n" + diff } - catch { case t: Exception => None } + catch { case _: Exception => None } } /** Normalize the log output by applying test-specific filters @@ -399,9 +398,6 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU else diff _transcript append bestDiff genFail("output differs") - // TestState.fail("output differs", "output differs", - // genFail("output differs") - // TestState.Fail("output differs", bestDiff) case None => genPass() // redundant default case } getOrElse true } @@ -636,7 +632,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU // and just looking at the diff, so I made them all do that // because this is long enough. if (!Output.withRedirected(logWriter)(try loop() finally resReader.close())) - setLastState(genPass()) + lastState = genPass() (diffIsOk, LogContext(logFile, swr, wr)) } @@ -645,16 +641,22 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU // javac runner, for one, would merely append to an existing log file, so just delete it before we start logFile.delete() - if (kind == "neg" || (kind endsWith "-neg")) runNegTest() - else kind match { - case "pos" => runTestCommon(true) - case "ant" => runAntTest() - case "res" => runResidentTest() - case "scalap" => runScalapTest() - case "script" => runScriptTest() - case _ => runTestCommon(execTest(outDir, logFile) && diffIsOk) - } + def runWhatever() = + if (kind == "neg" || (kind endsWith "-neg")) runNegTest() + else kind match { + case "pos" => runTestCommon(true) + case "ant" => runAntTest() + case "res" => runResidentTest() + case "scalap" => runScalapTest() + case "script" => runScriptTest() + case _ => runTestCommon(execTest(outDir, logFile) && diffIsOk) + } + try runWhatever() + catch { + case _: InterruptedException => lastState = genTimeout() + case t: Throwable => lastState = genCrash(t) + } lastState } @@ -731,7 +733,7 @@ class SuiteRunner( val javaOpts: String = PartestDefaults.javaOpts, val scalacOpts: String = PartestDefaults.scalacOpts) { - import PartestDefaults.{ numThreads, waitTime } + import PartestDefaults.{numThreads, waitTime} setUncaughtHandler @@ -759,7 +761,7 @@ class SuiteRunner( // |Java Classpath: ${sys.props("java.class.path")} } - def onFinishTest(testFile: File, result: TestState): TestState = result + def onFinishTest(testFile: File, result: TestState): TestState = { unused(testFile) ; result } def runTest(testFile: File): TestState = { val runner = new Runner(testFile, this, nestUI) @@ -770,11 +772,8 @@ class SuiteRunner( if (failed && !runner.logFile.canRead) runner.genPass() else { - val (state, _) = - try timed(runner.run()) - catch { - case t: Throwable => throw new RuntimeException(s"Error running $testFile", t) - } + val (state, time) = timed(runner.run()) + nestUI.verbose(s"$testFile completed in $time") nestUI.reportTest(state, runner) runner.cleanup() state @@ -782,35 +781,38 @@ class SuiteRunner( onFinishTest(testFile, state) } - def runTestsForFiles(kindFiles: Array[File], kind: String): Array[TestState] = { + def runTestsForFiles(kindFiles: Array[File]): Either[(Exception, Array[TestState]), Array[TestState]] = { nestUI.resetTestNumber(kindFiles.size) - val pool = Executors newFixedThreadPool numThreads - val futures = kindFiles map (f => pool submit callable(runTest(f.getAbsoluteFile))) + val pool = Executors.newFixedThreadPool(numThreads) + val futures = kindFiles.map(f => pool.submit(callable(runTest(f.getAbsoluteFile)))) pool.shutdown() - Try (pool.awaitTermination(waitTime) { - throw TimeoutException(waitTime) - }) match { - case Success(_) => futures map (_.get) - case Failure(e) => + Try (pool.awaitTermination(waitTime)(throw TimeoutException(waitTime))) match { + case Success(_) => Right(futures.map(_.get)) + case Failure(e: Exception) => e match { - case TimeoutException(d) => - nestUI.warning("Thread pool timeout elapsed before all tests were complete!") + case TimeoutException(_) => + nestUI.echoWarning("Thread pool timeout elapsed before all tests were complete!") case ie: InterruptedException => - nestUI.warning("Thread pool was interrupted") + nestUI.echoWarning("Thread pool was interrupted") ie.printStackTrace() } - pool.shutdownNow() // little point in continuing - // try to get as many completions as possible, in case someone cares - val results = for (f <- futures) yield { + pool.shutdownNow() + val states = (kindFiles, futures).zipped.map {(testFile, future) => try { - Some(f.get(0, NANOSECONDS)) + if (future.isDone) future.get(0, NANOSECONDS) else { + val s = Uninitialized(testFile) + onFinishTest(testFile, s) // reported as pending + s + } } catch { - case _: Throwable => None + case e: ExecutionException => new Runner(testFile, this, nestUI).genCrash(e.getCause) + case _: Throwable => new Runner(testFile, this, nestUI).genTimeout() } } - results.flatten + Left((e, states)) + case Failure(t) => throw t } } diff --git a/src/main/scala/scala/tools/partest/package.scala b/src/main/scala/scala/tools/partest/package.scala index 7aa60be..4a4629f 100644 --- a/src/main/scala/scala/tools/partest/package.scala +++ b/src/main/scala/scala/tools/partest/package.scala @@ -4,7 +4,8 @@ package scala.tools -import java.util.concurrent.{ Callable, ExecutorService } +import java.util.concurrent.{Callable, ExecutorService} +import scala.annotation.elidable, elidable.ALL import scala.concurrent.duration.Duration import scala.sys.process.javaVmArguments import scala.tools.nsc.util.Exceptional @@ -104,7 +105,7 @@ package object partest { implicit class ExecutorOps(val executor: ExecutorService) { def awaitTermination[A](wait: Duration)(failing: => A = ()): Option[A] = ( - if (executor awaitTermination (wait.length, wait.unit)) None + if (executor.awaitTermination(wait.length, wait.unit)) None else Some(failing) ) } @@ -180,4 +181,7 @@ package object partest { import scala.collection.JavaConverters._ System.getProperties.asScala.toList.sorted map { case (k, v) => "%s -> %s\n".format(k, v) } mkString "" } + + // consume a value elidably to avoid unused warning + @elidable(ALL) def unused[A](a: A): A = a }