Skip to content

Commit

Permalink
Fold SuiteRunner into AbstractRunner
Browse files Browse the repository at this point in the history
The distinction was rather arbitrary and only added to the confusion
between all the different things that partest calls “runner”.
  • Loading branch information
szeiger committed Jun 14, 2018
1 parent 777b11b commit 93e257a
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 155 deletions.
107 changes: 103 additions & 4 deletions src/partest/scala/tools/partest/nest/AbstractRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ package partest
package nest

import utils.Properties._
import scala.tools.nsc.Properties.{ versionMsg, propOrFalse, setProp }
import scala.tools.nsc.Properties.{propOrFalse, setProp, versionMsg}
import scala.collection.mutable
import TestKinds._
import scala.reflect.internal.util.Collections.distinctBy
import scala.util.{ Try, Success, Failure }
import scala.concurrent.duration.Duration
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.NANOSECONDS

abstract class AbstractRunner {

val config: RunnerSpec.Config
val fileManager: FileManager
protected val testSourcePath: String

lazy val nestUI: NestUI = new NestUI(
verbose = config.optVerbose,
Expand All @@ -26,7 +33,11 @@ abstract class AbstractRunner {
colorEnabled = colorEnabled
)

val suiteRunner: SuiteRunner
val javaCmdPath: String = PartestDefaults.javaCmd
val javacCmdPath: String = PartestDefaults.javacCmd
val scalacExtraArgs: Seq[String] = Seq.empty
val javaOpts: String = PartestDefaults.javaOpts
val scalacOpts: String = PartestDefaults.scalacOpts

protected val printSummary = true
protected val partestCmd = "test/partest"
Expand Down Expand Up @@ -102,6 +113,11 @@ abstract class AbstractRunner {

/** Run the tests and return the success status */
def run(): Boolean = {
setUncaughtHandler

// TODO: make this immutable
PathSettings.testSourcePath = testSourcePath

if (config.optVersion) echo(versionMsg)
else if (config.optHelp) nestUI.usage()
else {
Expand All @@ -116,7 +132,7 @@ abstract class AbstractRunner {
config.optTimeout foreach (x => setProp("partest.timeout", x))

if (!nestUI.terse)
nestUI.echo(suiteRunner.banner)
nestUI.echo(banner)

val grepExpr = config.optGrep getOrElse ""

Expand Down Expand Up @@ -168,7 +184,7 @@ abstract class AbstractRunner {
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 results = runTestsForFiles(paths map (_.jfile.getAbsoluteFile), kind)
val (passed, failed) = results partition (_.isOk)

passedTests ++= passed
Expand All @@ -184,4 +200,87 @@ abstract class AbstractRunner {
}
isSuccess
}

def banner = {
val baseDir = fileManager.compilerUnderTest.parent.toString
def relativize(path: String) = path.replace(baseDir, s"$$baseDir").replace(PathSettings.srcDir.toString, "$sourceDir")
val vmBin = javaHome + fileSeparator + "bin"
val vmName = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo)

s"""|Partest version: ${Properties.versionNumberString}
|Compiler under test: ${relativize(fileManager.compilerUnderTest.getAbsolutePath)}
|Scala version is: $versionMsg
|Scalac options are: ${(scalacExtraArgs ++ scalacOpts.split(' ')).mkString(" ")}
|Compilation Path: ${relativize(FileManager.joinPaths(fileManager.testClassPath))}
|Java binaries in: $vmBin
|Java runtime is: $vmName
|Java options are: $javaOpts
|baseDir: $baseDir
|sourceDir: ${PathSettings.srcDir}
""".stripMargin
// |Available processors: ${Runtime.getRuntime().availableProcessors()}
// |Java Classpath: ${sys.props("java.class.path")}
}

def onFinishTest(testFile: File, result: TestState, durationMs: Long): TestState = {
result
}

def runTest(testFile: File): TestState = {
val start = System.nanoTime()
val runner = new Runner(testFile, this, nestUI)
var stopwatchDuration: Option[Long] = None

// when option "--failed" is provided execute test only if log
// is present (which means it failed before)
val state =
if (config.optFailed && !runner.logFile.canRead)
runner.genPass()
else {
val (state, durationMs) =
try runner.run()
catch {
case t: Throwable => throw new RuntimeException(s"Error running $testFile", t)
}
stopwatchDuration = Some(durationMs)
nestUI.reportTest(state, runner, durationMs)
runner.cleanup()
state
}
val end = System.nanoTime()
val durationMs = stopwatchDuration.getOrElse(TimeUnit.NANOSECONDS.toMillis(end - start))
onFinishTest(testFile, state, durationMs)
}

def runTestsForFiles(kindFiles: Array[File], kind: String): Array[TestState] = {
nestUI.resetTestNumber(kindFiles.size)

val pool = Executors newFixedThreadPool PartestDefaults.numThreads
val futures = kindFiles map (f => pool submit callable(runTest(f.getAbsoluteFile)))

pool.shutdown()
Try (pool.awaitTermination(PartestDefaults.waitTime) {
throw TimeoutException(PartestDefaults.waitTime)
}) match {
case Success(_) => futures map (_.get)
case Failure(e) =>
e match {
case TimeoutException(d) =>
nestUI.warning("Thread pool timeout elapsed before all tests were complete!")
case ie: InterruptedException =>
nestUI.warning("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 {
try {
Some(f.get(0, NANOSECONDS))
} catch {
case _: Throwable => None
}
}
results.flatten
}
}
}
9 changes: 2 additions & 7 deletions src/partest/scala/tools/partest/nest/ConsoleRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@ package scala.tools.partest
package nest

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 = config.optUpdateCheck,
failed = config.optFailed,
noexec = config.optNoExec,
nestUI = nestUI)
val testSourcePath = config.optSourcePath getOrElse PartestDefaults.sourcePath
val fileManager = new FileManager(ClassPath split PathResolver.Environment.javaUserClassPath map (Path(_))) // the script sets up our classpath for us via ant
}

object ConsoleRunner {
Expand Down
140 changes: 23 additions & 117 deletions src/partest/scala/tools/partest/nest/Runner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ trait TestInfo {
}

/** Run a single test. Rubber meets road. */
class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestUI) extends TestInfo {
class Runner(val testFile: File, val suiteRunner: AbstractRunner, val nestUI: NestUI) extends TestInfo {
private val stopwatch = new Stopwatch()

import suiteRunner.{fileManager => fm, _}
Expand All @@ -71,7 +71,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
def isEnumeratedTest = false

private var _lastState: TestState = null
private val _transcript = new TestTranscript
private val _transcript = new TestTranscript(nestUI.color)

def lastState = if (_lastState == null) Uninitialized(testFile) else _lastState
def setLastState(s: TestState) = _lastState = s
Expand Down Expand Up @@ -440,7 +440,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
// if diff is not empty, is update needed?
val updating: Option[Boolean] = (
if (diff == "") None
else Some(updateCheck)
else Some(config.optUpdateCheck)
)
pushTranscript(s"diff $checkFile $logFile")
nextTestAction(updating) {
Expand Down Expand Up @@ -692,7 +692,7 @@ class Runner(val testFile: File, val suiteRunner: SuiteRunner, val nestUI: NestU
val javaopts = readOptionsFile(argsFile)
val execInProcess = PartestDefaults.execInProcess && javaopts.isEmpty && !Set("specialized", "instrumented").contains(testFile.getParentFile.getName)
def exec() = if (execInProcess) execTestInProcess(outDir, logFile) else execTest(outDir, logFile)
def noexec() = suiteRunner.noexec && { setLastState(genSkip("no-exec: tests compiled but not run")) ; true }
def noexec() = suiteRunner.config.optNoExec && { setLastState(genSkip("no-exec: tests compiled but not run")) ; true }
runTestCommon(noexec() || exec() && diffIsOk)
}

Expand Down Expand Up @@ -758,128 +758,16 @@ object Properties extends scala.util.PropertiesTrait {

/** Used by SBT- and ConsoleRunner for running a set of tests. */
class SuiteRunner(
val config: RunnerSpec.Config,
val testSourcePath: String, // relative path, like "files", or "pending"
val fileManager: FileManager,
val updateCheck: Boolean,
val failed: Boolean,
val noexec: Boolean,
val nestUI: NestUI,
val javaCmdPath: String = PartestDefaults.javaCmd,
val javacCmdPath: String = PartestDefaults.javacCmd,
val scalacExtraArgs: Seq[String] = Seq.empty,
val javaOpts: String = PartestDefaults.javaOpts,
val scalacOpts: String = PartestDefaults.scalacOpts) {

import PartestDefaults.{ numThreads, waitTime }

setUncaughtHandler

// TODO: make this immutable
PathSettings.testSourcePath = testSourcePath

val durations = collection.concurrent.TrieMap[File, Long]()

def banner = {
val baseDir = fileManager.compilerUnderTest.parent.toString
def relativize(path: String) = path.replace(baseDir, s"$$baseDir").replace(PathSettings.srcDir.toString, "$sourceDir")
val vmBin = javaHome + fileSeparator + "bin"
val vmName = "%s (build %s, %s)".format(javaVmName, javaVmVersion, javaVmInfo)

s"""|Partest version: ${Properties.versionNumberString}
|Compiler under test: ${relativize(fileManager.compilerUnderTest.getAbsolutePath)}
|Scala version is: $versionMsg
|Scalac options are: ${(scalacExtraArgs ++ scalacOpts.split(' ')).mkString(" ")}
|Compilation Path: ${relativize(joinPaths(fileManager.testClassPath))}
|Java binaries in: $vmBin
|Java runtime is: $vmName
|Java options are: $javaOpts
|baseDir: $baseDir
|sourceDir: ${PathSettings.srcDir}
""".stripMargin
// |Available processors: ${Runtime.getRuntime().availableProcessors()}
// |Java Classpath: ${sys.props("java.class.path")}
}

def onFinishTest(testFile: File, result: TestState, durationMs: Long): TestState = {
durations(testFile) = durationMs
result
}

def runTest(testFile: File): TestState = {
val start = System.nanoTime()
val runner = new Runner(testFile, this, nestUI)
var stopwatchDuration: Option[Long] = None

// when option "--failed" is provided execute test only if log
// is present (which means it failed before)
val state =
if (failed && !runner.logFile.canRead)
runner.genPass()
else {
val (state, durationMs) =
try runner.run()
catch {
case t: Throwable => throw new RuntimeException(s"Error running $testFile", t)
}
stopwatchDuration = Some(durationMs)
nestUI.reportTest(state, runner, durationMs)
runner.cleanup()
state
}
val end = System.nanoTime()
val durationMs = stopwatchDuration.getOrElse(TimeUnit.NANOSECONDS.toMillis(end - start))
onFinishTest(testFile, state, durationMs)
}

def runTestsForFiles(kindFiles: Array[File], kind: String): Array[TestState] = {
nestUI.resetTestNumber(kindFiles.size)

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) =>
e match {
case TimeoutException(d) =>
nestUI.warning("Thread pool timeout elapsed before all tests were complete!")
case ie: InterruptedException =>
nestUI.warning("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 {
try {
Some(f.get(0, NANOSECONDS))
} catch {
case _: Throwable => None
}
}
results.flatten
}
}

class TestTranscript {
private val buf = ListBuffer[String]()

def add(action: String): this.type = { buf += action ; this }
def append(text: String): Unit = { 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
Expand Down Expand Up @@ -933,3 +821,21 @@ object Output {
}
}
}

class TestTranscript(color: Colors) {
private val buf = ListBuffer[String]()

def add(action: String): this.type = { buf += action ; this }
def append(text: String): Unit = { val s = buf.last ; buf.trimEnd(1) ; buf += (s + text) }

// Colorize prompts according to pass/fail
def fail: List[String] = {
import 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)
}
}
}
Loading

0 comments on commit 93e257a

Please sign in to comment.