Use test-interface for test support.

This commit is contained in:
Mark Harrah 2009-10-22 19:10:54 -04:00
parent 75b8fb7208
commit e417e19b02
9 changed files with 147 additions and 632 deletions

View File

@ -52,6 +52,8 @@ class SbtProject(info: ProjectInfo) extends DefaultProject(info) //with test.Sbt
val jsch = "com.jcraft" % "jsch" % "0.1.31" intransitive()
val jetty = "org.mortbay.jetty" % "jetty" % "6.1.14" % "optional"
val testInterface = "org.scala-tools.testing" % "test-interface" % "0.1"
// xsbt components
val xsbti = "org.scala-tools.sbt" % "launcher-interface" % projectVersion.value.toString % "provided"
val compiler = "org.scala-tools.sbt" %% "compile" % projectVersion.value.toString

View File

@ -12,6 +12,7 @@ class DefaultWebProject(val info: ProjectInfo) extends BasicWebScalaProject with
import BasicScalaProject._
import ScalaProject.{optionsAsString, javaOptionsAsString}
import java.io.File
import java.net.URLClassLoader
import java.util.jar.Attributes
/** This class defines concrete instances of actions from ScalaProject using overridable paths,
@ -169,9 +170,13 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
/** The list of test frameworks to use for testing. Note that adding frameworks to this list
* for an active project currently requires an explicit 'clean' to properly update the set of tests to
* run*/
def testFrameworks: Iterable[TestFramework] = ScalaCheckFramework :: SpecsFramework :: ScalaTestFramework :: Nil
def testFrameworks: Seq[TestFramework] =
{
import TestFrameworks.{ScalaCheck, ScalaTest, Specs}
/*ScalaCheckFramework :: SpecsFramework ::*/ ScalaTest :: Nil
}
/** The list of listeners for testing. */
def testListeners: Seq[TestReportListener] = new LogTestReportListener(log) :: Nil
def testListeners: Seq[TestReportListener] = TestLogger(log) :: Nil
def mainLabel = "main"
def testLabel = "test"
@ -186,6 +191,14 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
def options = optionsAsString(baseCompileOptions.filter(!_.isInstanceOf[MaxCompileErrors]))
def maxErrors = maximumErrors(baseCompileOptions)
def compileOrder = BasicScalaProject.this.compileOrder
protected def testClassNames(frameworks: Seq[TestFramework]) =
{
val loader = new URLClassLoader(classpath.get.map(_.asURL).toSeq.toArray)
def getTestNames(framework: TestFramework): Seq[String] =
try { framework.create(loader).tests.map(_.superClassName) }
catch { case e: ClassNotFoundException => log.debug("Framework implementation '" + framework.implClassName + "' not present."); Nil }
frameworks.flatMap(getTestNames)
}
}
class MainCompileConfig extends BaseCompileConfig
{
@ -196,7 +209,7 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
def outputDirectory = mainCompilePath
def classpath = compileClasspath
def analysisPath = mainAnalysisPath
def testDefinitionClassNames: Iterable[String] = Nil
def testDefinitionClassNames: Seq[String] = Nil
def javaOptions = javaOptionsAsString(javaCompileOptions)
}
class TestCompileConfig extends BaseCompileConfig
@ -208,7 +221,7 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
def outputDirectory = testCompilePath
def classpath = testClasspath
def analysisPath = testAnalysisPath
def testDefinitionClassNames: Iterable[String] = testFrameworks.map(_.testSuperClassName)
def testDefinitionClassNames: Seq[String] = testClassNames(testFrameworks)
def javaOptions = javaOptionsAsString(testJavaCompileOptions)
}

View File

@ -15,7 +15,7 @@ trait IntegrationTesting extends NotNull
trait ScalaIntegrationTesting extends IntegrationTesting
{ self: ScalaProject =>
protected def integrationTestTask(frameworks: Iterable[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) =
protected def integrationTestTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) =
testTask(frameworks, classpath, analysis, options)
}
@ -83,7 +83,7 @@ trait BasicIntegrationTesting extends ScalaIntegrationTesting with IntegrationTe
def analysisPath = integrationTestAnalysisPath
def baseCompileOptions = integrationTestCompileOptions
def javaOptions = javaOptionsAsString(javaCompileOptions)
def testDefinitionClassNames = integrationTestFrameworks.map(_.testSuperClassName)
def testDefinitionClassNames = testClassNames(integrationTestFrameworks)
}
}

View File

@ -24,6 +24,7 @@ abstract class Logger extends xsbt.CompileLogger with xsbt.IvyLogger
def setLevel(newLevel: Level.Value)
def enableTrace(flag: Boolean)
def traceEnabled: Boolean
def ansiCodesSupported = false
def atLevel(level: Level.Value) = level.id >= getLevel.id
def trace(t: => Throwable): Unit
@ -73,6 +74,7 @@ abstract class BasicLogger extends Logger
final class SynchronizedLogger(delegate: Logger) extends Logger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
def getLevel = { synchronized { delegate.getLevel } }
def setLevel(newLevel: Level.Value) { synchronized { delegate.setLevel(newLevel) } }
def enableTrace(enabled: Boolean) { synchronized { delegate.enableTrace(enabled) } }
@ -87,6 +89,7 @@ final class SynchronizedLogger(delegate: Logger) extends Logger
final class MultiLogger(delegates: List[Logger]) extends BasicLogger
{
override lazy val ansiCodesSupported = delegates.forall(_.ansiCodesSupported)
override def setLevel(newLevel: Level.Value)
{
super.setLevel(newLevel)
@ -110,6 +113,7 @@ final class MultiLogger(delegates: List[Logger]) extends BasicLogger
* */
final class FilterLogger(delegate: Logger) extends BasicLogger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
def trace(t: => Throwable)
{
if(traceEnabled)
@ -146,6 +150,7 @@ final class FilterLogger(delegate: Logger) extends BasicLogger
* */
final class BufferedLogger(delegate: Logger) extends Logger
{
override lazy val ansiCodesSupported = delegate.ansiCodesSupported
private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]]
private[this] var recordingAll = false
@ -271,7 +276,7 @@ object ConsoleLogger
* This logger is not thread-safe.*/
class ConsoleLogger extends BasicLogger
{
import ConsoleLogger.formatEnabled
override def ansiCodesSupported = ConsoleLogger.formatEnabled
def messageColor(level: Level.Value) = Console.RESET
def labelColor(level: Level.Value) =
level match
@ -300,7 +305,7 @@ class ConsoleLogger extends BasicLogger
}
private def setColor(color: String)
{
if(formatEnabled)
if(ansiCodesSupported)
System.out.synchronized { System.out.print(color) }
}
private def log(labelColor: String, label: String, messageColor: String, message: String): Unit =

View File

@ -149,9 +149,9 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
def copyTask(sources: PathFinder, destinationDirectory: Path): Task =
task { FileUtilities.copy(sources.get, destinationDirectory, log).left.toOption }
def testTask(frameworks: Iterable[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: TestOption*): Task =
def testTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: TestOption*): Task =
testTask(frameworks, classpath, analysis, options)
def testTask(frameworks: Iterable[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]): Task =
def testTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]): Task =
{
def work =
{
@ -249,7 +249,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
}
}
protected def incrementImpl(v: BasicVersion): Version = v.incrementMicro
protected def testTasks(frameworks: Iterable[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) = {
protected def testTasks(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) = {
import scala.collection.mutable.HashSet
val testFilters = new ListBuffer[String => Boolean]
@ -267,7 +267,6 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
case TestSetup(setupFunction) => setup += setupFunction
case TestCleanup(cleanupFunction) => cleanup += cleanupFunction
}
() // 2.8.0-SNAPSHOT bug in type inference
}
if(excludeTestsSet.size > 0 && log.atLevel(Level.Debug))
@ -277,7 +276,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
}
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.testClassName) && testFilters.forall(filter => filter(test.testClassName))
val tests = HashSet.empty[TestDefinition] ++ analysis.allTests.filter(includeTest)
TestFramework.testTasks(frameworks, classpath.get, tests, log, testListeners.readOnly, false, setup.readOnly, cleanup.readOnly)
TestFramework.testTasks(frameworks, classpath.get, tests.toSeq, log, testListeners.readOnly, false, setup.readOnly, cleanup.readOnly)
}
private def flatten[T](i: Iterable[Iterable[T]]) = i.flatMap(x => x)

View File

@ -3,62 +3,63 @@
*/
package sbt
import java.net.URLClassLoader
import org.scalatools.testing.{TestFingerprint => Fingerprint, Framework, Runner, Logger=>TLogger}
object Result extends Enumeration
{
val Error, Passed, Failed = Value
}
object ClassType extends Enumeration
{
val Module, Class = Value
}
trait TestFramework extends NotNull
object TestFrameworks
{
def name: String
def testSuperClassName: String
def testSubClassType: ClassType.Value
def testRunner(classLoader: ClassLoader, listeners: Iterable[TestReportListener], log: Logger): TestRunner
val ScalaCheck = new TestFramework("org.scalacheck.FrameworkImpl")
val ScalaTest = new TestFramework("org.scalatest.FrameworkImpl")
val Specs = new TestFramework("org.specs.FrameworkImpl")
}
trait TestRunner extends NotNull
class TestFramework(val implClassName: String) extends NotNull
{
def run(testClassName: String): Result.Value
def create(loader: ClassLoader): Framework =
Class.forName(implClassName, true, loader).newInstance.asInstanceOf[Framework]
}
abstract class BasicTestRunner extends TestRunner
final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq[TestReportListener], log: Logger) extends NotNull
{
protected def log: Logger
protected def listeners: Seq[TestReportListener]
final def run(testClass: String): Result.Value =
private[this] val delegate = framework.testRunner(loader, listeners.flatMap(_.contentLogger).toArray)
final def run(testDefinition: TestDefinition, args: Seq[String]): Result.Value =
{
val testClass = testDefinition.testClassName
def runTest() =
{
val results = delegate.run(testClass, testDefinition, args.toArray)
val event = TestEvent(results)
safeListenersCall(_.testEvent( event ))
event.result
}
safeListenersCall(_.startGroup(testClass))
try
{
val result = runTest(testClass)
val result = runTest().getOrElse(Result.Passed)
safeListenersCall(_.endGroup(testClass, result))
result
}
catch
{
case e =>
{
safeListenersCall(_.endGroup(testClass, e))
Result.Error
}
}
}
def runTest(testClass: String): Result.Value
protected def fire(event: TestEvent) = safeListenersCall(_.testEvent(event))
protected def safeListenersCall(call: (TestReportListener) => Unit) = TestFramework.safeForeach(listeners, log)(call)
protected def safeListenersCall(call: (TestReportListener) => Unit): Unit =
TestFramework.safeForeach(listeners, log)(call)
}
final class NamedTestTask(val name: String, action: => Option[String]) extends NotNull { def run() = action }
object TestFramework
{
def runTests(frameworks: Iterable[TestFramework], classpath: Iterable[Path], tests: Iterable[TestDefinition], log: Logger,
listeners: Iterable[TestReportListener]) =
def runTests(frameworks: Seq[TestFramework], classpath: Iterable[Path], tests: Seq[TestDefinition], log: Logger,
listeners: Seq[TestReportListener]) =
{
val (start, runTests, end) = testTasks(frameworks, classpath, tests, log, listeners, true, Nil, Nil)
def run(tasks: Iterable[NamedTestTask]) = tasks.foreach(_.run())
@ -72,49 +73,55 @@ object TestFramework
private val TestStartName = "test-start"
private val TestFinishName = "test-finish"
private[sbt] def safeForeach[T](it: Iterable[T], log: Logger)(f: T => Unit): Unit = it.foreach(i => Control.trapAndLog(log){ f(i) } )
import scala.collection.{Map, Set}
def testTasks(frameworks: Iterable[TestFramework], classpath: Iterable[Path], tests: Iterable[TestDefinition], log: Logger,
listeners: Iterable[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Option[String]],
private[sbt] def safeForeach[T](it: Iterable[T], log: Logger)(f: T => Unit): Unit =
it.foreach(i => Control.trapAndLog(log){ f(i) } )
import scala.collection.{Map, Set}
def testTasks(frameworks: Seq[TestFramework], classpath: Iterable[Path], tests: Seq[TestDefinition], log: Logger,
listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Option[String]],
cleanup: Iterable[() => Option[String]]): (Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) =
{
val mappedTests = testMap(frameworks, tests)
val loader = createTestLoader(classpath)
val rawFrameworks = frameworks.map(_.create(loader))
val mappedTests = testMap(rawFrameworks, tests)
if(mappedTests.isEmpty)
(new NamedTestTask(TestStartName, None) :: Nil, Nil, new NamedTestTask(TestFinishName, { log.info("No tests to run."); None }) :: Nil )
else
createTestTasks(classpath, mappedTests, log, listeners, endErrorsEnabled, setup, cleanup)
createTestTasks(loader, mappedTests, log, listeners, endErrorsEnabled, setup, cleanup)
}
private def testMap(frameworks: Iterable[TestFramework], tests: Iterable[TestDefinition]): Map[TestFramework, Set[String]] =
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition]): Map[Framework, Set[TestDefinition]] =
{
import scala.collection.mutable.{HashMap, HashSet, Set}
val map = new HashMap[TestFramework, Set[String]]
if(!frameworks.isEmpty)
val map = new HashMap[Framework, Set[TestDefinition]]
def assignTests(): Unit =
{
for(test <- tests)
for(test <- tests if !map.values.exists(_.contains(test)))
{
def isTestForFramework(framework: TestFramework) =
(framework.testSubClassType == ClassType.Module) == test.isModule &&
framework.testSuperClassName == test.superClassName
def isTestForFramework(framework: Framework) = framework.tests.exists(matches)
def matches(fingerprint: Fingerprint) =
(fingerprint.isModule == test.isModule) &&
fingerprint.superClassName == test.superClassName
for(framework <- frameworks.find(isTestForFramework))
map.getOrElseUpdate(framework, new HashSet[String]) += test.testClassName
map.getOrElseUpdate(framework, new HashSet[TestDefinition]) += test
}
}
if(!frameworks.isEmpty)
assignTests()
wrap.Wrappers.readOnly(map)
}
private def createTasks(work: Iterable[() => Option[String]], baseName: String) =
work.toList.zipWithIndex.map{ case (work, index) => new NamedTestTask(baseName + " " + (index+1), work()) }
private def createTestTasks(classpath: Iterable[Path], tests: Map[TestFramework, Set[String]], log: Logger,
listeners: Iterable[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Option[String]],
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, Set[TestDefinition]], log: Logger,
listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[() => Option[String]],
cleanup: Iterable[() => Option[String]]) =
{
val filterCompilerLoader = new FilteredLoader(getClass.getClassLoader, ScalaCompilerJarPackages)
val loader: ClassLoader = new IntermediateLoader(classpath.map(_.asURL).toSeq.toArray, filterCompilerLoader)
val testsListeners = listeners.filter(_.isInstanceOf[TestsListener]).map(_.asInstanceOf[TestsListener])
def foreachListenerSafe(f: TestsListener => Unit): Unit = safeForeach(testsListeners, log)(f)
import Result.{Error,Passed,Failed}
import Result.{Error,Passed,Failed}
object result
{
private[this] var value: Result.Value = Passed
@ -123,17 +130,17 @@ object TestFramework
}
val startTask = new NamedTestTask(TestStartName, {foreachListenerSafe(_.doInit); None}) :: createTasks(setup, "Test setup")
val testTasks =
tests flatMap { case (framework, testClassNames) =>
tests flatMap { case (framework, testDefinitions) =>
val runner = framework.testRunner(loader, listeners, log)
for(testClassName <- testClassNames) yield
val runner = new TestRunner(framework, loader, listeners, log)
for(testDefinition <- testDefinitions) yield
{
def runTest() =
{
val oldLoader = Thread.currentThread.getContextClassLoader
Thread.currentThread.setContextClassLoader(loader)
try {
runner.run(testClassName) match
runner.run(testDefinition, Nil) match
{
case Error => result() = Error; Some("ERROR occurred during testing.")
case Failed => result() = Failed; Some("Test FAILED")
@ -144,7 +151,7 @@ object TestFramework
Thread.currentThread.setContextClassLoader(oldLoader)
}
}
new NamedTestTask(testClassName, runTest())
new NamedTestTask(testDefinition.testClassName, runTest())
}
}
def end() =
@ -165,67 +172,9 @@ object TestFramework
val endTask = new NamedTestTask(TestFinishName, end() ) :: createTasks(cleanup, "Test cleanup")
(startTask, testTasks, endTask)
}
}
abstract class LazyTestFramework extends TestFramework
{
/** The class name of the the test runner that executes
* tests for this framework.*/
protected def testRunnerClassName: String
protected def bundledTestRunnerClassName: Option[String] = None
/** Creates an instance of the runner given by 'testRunnerClassName'.*/
final def testRunner(projectLoader: ClassLoader, listeners: Iterable[TestReportListener], log: Logger): TestRunner =
private def createTestLoader(classpath: Iterable[Path]): ClassLoader =
{
val frameworkClasspath = FileUtilities.classLocation(getClass)
val sbtURL = FileUtilities.sbtJar.toURI.toURL
val projectClasspath = projectLoader.asInstanceOf[java.net.URLClassLoader].getURLs
val loaderURLs = Array(frameworkClasspath, sbtURL) ++ projectClasspath
def load(name: String): Option[Class[_]] =
{
val lazyLoader = new LazyFrameworkLoader(name, loaderURLs, projectLoader, getClass.getClassLoader)
try { Some(Class.forName(name, true, lazyLoader)) }
catch { case e: ClassNotFoundException => None }
}
val c = bundledTestRunnerClassName.flatMap(load).orElse(load(testRunnerClassName))
val runnerClass = c.getOrElse(error("Could not find test runner")).asSubclass(classOf[TestRunner])
runnerClass.getConstructor(classOf[Logger], classOf[Seq[TestReportListener]], classOf[ClassLoader]).newInstance(log, listeners, projectLoader)
val filterCompilerLoader = new FilteredLoader(getClass.getClassLoader, ScalaCompilerJarPackages)
new URLClassLoader(classpath.map(_.asURL).toSeq.toArray, filterCompilerLoader)
}
}
/** The test framework definition for ScalaTest.*/
object ScalaTestFramework extends LazyTestFramework
{
val name = "ScalaTest"
val SuiteClassName = "org.scalatest.Suite"
def testSuperClassName = SuiteClassName
def testSubClassType = ClassType.Class
def testRunnerClassName = "sbt.impl.ScalaTestRunner"
override def bundledTestRunnerClassName = Some("org.scalatest.sbt.ScalaTestRunner")
}
/** The test framework definition for ScalaCheck.*/
object ScalaCheckFramework extends LazyTestFramework
{
val name = "ScalaCheck"
val PropertiesClassName = "org.scalacheck.Properties"
def testSuperClassName = PropertiesClassName
def testSubClassType = ClassType.Module
def testRunnerClassName = "sbt.impl.ScalaCheckRunner"
}
/** The test framework definition for specs.*/
object SpecsFramework extends LazyTestFramework
{
val name = "specs"
val SpecificationClassName = "org.specs.Specification"
def testSuperClassName = SpecificationClassName
def testSubClassType = ClassType.Module
def testRunnerClassName = "sbt.impl.SpecsRunner"
}

View File

@ -4,6 +4,8 @@
package sbt
import org.scalatools.testing.{Logger => TLogger, Event => TEvent, Result => TResult}
trait TestReportListener
{
/** called for each class or equivalent grouping */
@ -14,6 +16,8 @@ trait TestReportListener
def endGroup(name: String, t: Throwable)
/** called if test completed */
def endGroup(name: String, result: Result.Value)
/** Used by the test framework for logging test results*/
def contentLogger: Option[TLogger] = None
}
trait TestsListener extends TestReportListener
@ -24,317 +28,82 @@ trait TestsListener extends TestReportListener
def doComplete(finalResult: Result.Value)
}
abstract class WriterReportListener(val log: Logger) extends TestsListener
{
import java.io.{IOException, PrintWriter, Writer}
import scala.collection.mutable.{Buffer, ListBuffer}
protected case class Summary(count: Int, failures: Int, errors: Int, skipped: Int, message: Option[String], cause: Option[Throwable]) extends NotNull
private var out: Option[PrintWriter] = None
private var groupCount: Int = 0
private var groupFailures: Int = 0
private var groupErrors: Int = 0
private var groupSkipped: Int = 0
private var groupMessages: Seq[String] = Nil
protected val passedEventHandler: TestEvent => Summary = (event: TestEvent) => event match
{
case SpecificationReportEvent(successes, failures, errors, skipped, desc, systems, subSpecs) => Summary(successes, failures, errors, skipped, None, None)
case IgnoredEvent(name, Some(message)) => Summary(1, 0, 0, 1, Some(message), None)
case IgnoredEvent(name, None) => Summary(1, 0, 0, 1, None, None)
case _ => Summary(1, 0, 0, 0, None, None)
}
protected val failedEventHandler: TestEvent => Summary = (event: TestEvent) => event match
{
case FailedEvent(name, msg) => Summary(1, 1, 0, 0, Some("! " + name + ": " + msg), None)
case TypedErrorEvent(name, event, Some(msg), cause) => Summary(1, 1, 0, 0, Some(event + " - " + name + ": " + msg), cause)
case TypedErrorEvent(name, event, None, cause) => Summary(1, 1, 0, 0, Some(event + " - " + name), cause)
case ErrorEvent(msg) => Summary(1, 1, 0, 0, Some(msg), None)
case SpecificationReportEvent(successes, failures, errors, skipped, desc, systems, subSpecs) => Summary(successes + failures + errors + skipped, failures, errors, skipped, Some(desc), None)
case _ => {log.warn("Unrecognized failure: " + event); Summary(1, 1, 0, 0, None, None)}
}
protected val errorEventHandler: TestEvent => Summary = (event: TestEvent) => event match
{
case FailedEvent(name, msg) => Summary(1, 0, 1, 0, Some("! " + name + ": " + msg), None)
case TypedErrorEvent(name, event, Some(msg), cause) => Summary(1, 0, 1, 0, Some(event + " - " + name + ": " + msg), cause)
case TypedErrorEvent(name, event, None, cause) => Summary(1, 0, 1, 0, Some(event + " - " + name), cause)
case ErrorEvent(msg) => Summary(1, 0, 1, 0, Some(msg), None)
case SpecificationReportEvent(successes, failures, errors, skipped, desc, systems, subSpecs) => Summary(successes + failures + errors + skipped, failures, errors, skipped, Some(desc), None)
case _ => {log.warn("Unrecognized error: " + event); Summary(1, 0, 1, 0, None, None)}
}
protected def open: Writer
protected def close =
{
onOut(_.close())
out = None
}
def doInit = Control.trapAndLog(log){ out = Some(new PrintWriter(open)) }
def doComplete(finalResult: Result.Value) =
{
finalResult match
{
case Result.Error => println("Error during Tests")
case Result.Passed => println("All Tests Passed")
case Result.Failed => println("Tests Failed")
}
close
}
def doComplete(t: Throwable) =
{
println("Exception in Test Framework")
onOut(t.printStackTrace(_))
close
}
def startGroup(name: String) =
{
groupCount = 0
groupFailures = 0
groupErrors = 0
groupSkipped = 0
groupMessages = Nil
}
def testEvent(event: TestEvent) = event.result match
{
case Some(result) =>
{
val Summary(count, failures, errors, skipped, msg, cause) = result match
{
case Result.Passed => passedEventHandler(event)
case Result.Failed => failedEventHandler(event)
case Result.Error => errorEventHandler(event)
}
groupCount += count
groupFailures += failures
groupErrors += errors
groupSkipped += skipped
groupMessages ++= msg.toList
}
case None => {}
}
def endGroup(name: String, t: Throwable) =
{
groupMessages = Nil
println("Exception in " + name)
onOut(t.printStackTrace(_))
}
def endGroup(name: String, result: Result.Value) =
{
result match
{
case Result.Error => println("Error: " + name + " - Count " + groupCount + ", Failed " + groupFailures + ", Errors " + groupErrors)
case Result.Passed => println("Passed: " + name + " - Count " + groupCount + ", Failed " + groupFailures + ", Errors " + groupErrors)
case Result.Failed => println("Failed: " + name + " - Count " + groupCount + ", Failed " + groupFailures + ", Errors " + groupErrors)
}
if(!groupMessages.isEmpty)
{
groupMessages.foreach(println(_))
groupMessages = Nil
println("")
}
}
protected def onOut(f: PrintWriter => Unit) = Control.trapAndLog(log){
out match
{
case Some(pw) => f(pw)
case None => log.warn("Method called when output was not open")
}
}
protected def println(s: String) = onOut(_.println(s))
}
class FileReportListener(val file: Path, log: Logger) extends WriterReportListener(log)
{
def open = new java.io.FileWriter(file.asFile)
}
abstract class TestEvent extends NotNull
{
def result: Option[Result.Value]
def detail: Seq[TEvent] = Nil
}
sealed abstract class ScalaCheckEvent extends TestEvent
final case class PassedEvent(name: String, msg: String) extends ScalaCheckEvent { def result = Some(Result.Passed) }
final case class FailedEvent(name: String, msg: String) extends ScalaCheckEvent { def result = Some(Result.Failed) }
sealed abstract class ScalaTestEvent(val result: Option[Result.Value]) extends TestEvent
final case class TypedEvent(name: String, `type`: String, msg: Option[String])(result: Option[Result.Value]) extends ScalaTestEvent(result)
final case class TypedErrorEvent(name: String, `type`: String, msg: Option[String], cause: Option[Throwable])(result: Option[Result.Value]) extends ScalaTestEvent(result)
final case class MessageEvent(msg: String) extends ScalaTestEvent(None)
final case class ErrorEvent(msg: String) extends ScalaTestEvent(None)
final case class IgnoredEvent(name: String, msg: Option[String]) extends ScalaTestEvent(Some(Result.Passed))
sealed abstract class SpecsEvent extends TestEvent
final case class SpecificationReportEvent(successes: Int, failures: Int, errors: Int, skipped: Int, pretty: String, systems: Seq[SystemReportEvent], subSpecs: Seq[SpecificationReportEvent]) extends SpecsEvent
object TestEvent
{
def result = if(errors > 0) Some(Result.Error) else if(failures > 0) Some(Result.Failed) else Some(Result.Passed)
}
final case class SystemReportEvent(description: String, verb: String, skipped: Seq[Throwable], literateDescription: Option[Seq[String]], examples: Seq[ExampleReportEvent]) extends SpecsEvent { def result = None }
final case class ExampleReportEvent(description: String, errors: Seq[Throwable], failures: Seq[RuntimeException], skipped: Seq[RuntimeException], subExamples: Seq[ExampleReportEvent]) extends SpecsEvent { def result = None }
trait EventOutput[E <: TestEvent]
{
def output(e: E): Unit
}
sealed abstract class LazyEventOutput[E <: TestEvent](val log: Logger) extends EventOutput[E]
class ScalaCheckOutput(log: Logger) extends LazyEventOutput[ScalaCheckEvent](log)
{
def output(event: ScalaCheckEvent) = event match
{
case PassedEvent(name, msg) => log.info("+ " + name + ": " + msg)
case FailedEvent(name, msg) => log.error("! " + name + ": " + msg)
def apply(events: Seq[TEvent]): TestEvent =
{
val overallResult = (Result.Passed /: events) { (sum, event) =>
val result = event.result
if(sum == Result.Error || result == TResult.Error) Result.Error
else if(sum == Result.Failed || result == TResult.Failure) Result.Failed
else Result.Passed
}
}
class ScalaTestOutput(log: Logger) extends LazyEventOutput[ScalaTestEvent](log)
{
def output(event: ScalaTestEvent) = event match
{
case TypedEvent(name, event, Some(msg)) => log.info(event + " - " + name + ": " + msg)
case TypedEvent(name, event, None) => log.info(event + " - " + name)
case TypedErrorEvent(name, event, Some(msg), cause) => logError(event + " - " + name + ": " + msg, cause)
case TypedErrorEvent(name, event, None, cause) => logError(event + " - " + name, cause)
case MessageEvent(msg) => log.info(msg)
case ErrorEvent(msg) => logError(msg, None)
case IgnoredEvent(name, Some(msg)) => log.info("Test ignored - " + name + ": " + msg)
case IgnoredEvent(name, None) => log.info("Test ignored - " + name)
}
private def logError(message: String, cause: Option[Throwable])
{
cause.foreach(x => log.trace(x))
log.error(message)
}
}
class SpecsOutput(val log: Logger) extends EventOutput[SpecsEvent]
{
private val Indent = " "
def output(event: SpecsEvent) = event match
{
case sre: SpecificationReportEvent => reportSpecification(sre, "")
case sre: SystemReportEvent => reportSystem(sre, "")
case ere: ExampleReportEvent => reportExample(ere, "")
}
/* The following is closely based on org.specs.runner.OutputReporter,
* part of specs, which is Copyright 2007-2008 Eric Torreborre.
* */
private def reportSpecification(specification: SpecificationReportEvent, padding: String)
{
val newIndent = padding + Indent
reportSpecifications(specification.subSpecs, newIndent)
reportSystems(specification.systems, newIndent)
}
private def reportSpecifications(specifications: Iterable[SpecificationReportEvent], padding: String)
{
for(specification <- specifications)
reportSpecification(specification, padding)
}
private def reportSystems(systems: Iterable[SystemReportEvent], padding: String)
{
for(system <- systems)
reportSystem(system, padding)
}
private def reportSystem(sus: SystemReportEvent, padding: String)
{
val skipped = if(sus.skipped.isEmpty) "" else sus.skipped.map(_.getMessage).mkString(" (skipped: ", ", ", ")")
log.info(padding + sus.description + " " + sus.verb + skipped)
for(description <- sus.literateDescription)
log.info(padding + description.mkString)
reportExamples(sus.examples, padding)
log.info(" ")
}
private def reportExamples(examples: Iterable[ExampleReportEvent], padding: String)
{
for(example <- examples)
{
reportExample(example, padding)
reportExamples(example.subExamples, padding + Indent)
}
}
private def status(example: ExampleReportEvent) =
{
if (example.errors.size + example.failures.size > 0)
"x "
else if (example.skipped.size > 0)
"o "
else
"+ "
}
private def reportExample(example: ExampleReportEvent, padding: String)
{
log.info(padding + status(example) + example.description)
for(skip <- example.skipped)
{
log.trace(skip)
log.warn(padding + skip.toString)
}
for(e <- example.failures ++ example.errors)
{
log.trace(e)
log.error(padding + e.toString)
new TestEvent {
val result = Some(overallResult)
override val detail = events
}
}
}
class LogTestReportListener(val log: Logger) extends TestsListener
object TestLogger
{
lazy val scalaCheckOutput: EventOutput[ScalaCheckEvent] = createScalaCheckOutput
lazy val scalaTestOutput: EventOutput[ScalaTestEvent] = createScalaTestOutput
lazy val specsOutput: EventOutput[SpecsEvent] = createSpecsOutput
protected def createScalaCheckOutput = new ScalaCheckOutput(log)
protected def createScalaTestOutput = new ScalaTestOutput(log)
protected def createSpecsOutput = new SpecsOutput(log)
def apply(logger: sbt.Logger): TestLogger = new TestLogger(wrap(logger))
def wrap(logger: sbt.Logger): TLogger =
new TLogger
{
def error(s: String) = log(Level.Error, s)
def warn(s: String) = log(Level.Warn, s)
def info(s: String) = log(Level.Info, s)
def debug(s: String) = log(Level.Debug, s)
private def log(level: Level.Value, s: String) = logger.log(level, s)
def ansiCodesSupported() = logger.ansiCodesSupported
}
}
class TestLogger(val log: TLogger) extends TestsListener
{
protected var skipped, errors, passed, failures = 0
def startGroup(name: String) {}
def testEvent(event: TestEvent)
{
log.debug("in testEvent:" + event)
count(event)
event match
{
case sce: ScalaCheckEvent => scalaCheckOutput.output(sce)
case ste: ScalaTestEvent => scalaTestOutput.output(ste)
case se: SpecsEvent => specsOutput.output(se)
case e => handleOtherTestEvent(e)
}
}
protected def handleOtherTestEvent(event: TestEvent) {}
def testEvent(event: TestEvent): Unit = event.detail.foreach(count)
def endGroup(name: String, t: Throwable)
{
log.error("Could not run test " + name + ": " + t.toString)
log.trace(t)
//log.trace(t)
}
def endGroup(name: String, result: Result.Value)
def endGroup(name: String, result: Result.Value) {}
protected def count(event: TEvent): Unit =
{
log.debug("in endGroup:" + result)
}
protected def count(event: TestEvent): Unit =
{
for(result <- event.result)
event.result match
{
totalTests += 1
result match
{
case Result.Error => errors += 1
case Result.Failed => failed += 1
case Result.Passed => passed += 1
}
case TResult.Error => errors +=1
case TResult.Success => passed +=1
case TResult.Failure => failures +=1
case TResult.Skipped => skipped += 1
}
}
protected var totalTests, errors, passed, failed = 0
def doInit
{
totalTests = 0
failures = 0
errors = 0
passed = 0
failed = 0
skipped = 0
}
/** called once, at end. */
def doComplete(finalResult: Result.Value): Unit =
log.info(<x>Run: {totalTests.toString}, Passed: {passed.toString}, Errors: {errors.toString}, Failed: {failed.toString}</x>.text)
{
val totalCount = failures + errors + skipped + passed
val postfix = ": Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped
finalResult match
{
case Result.Error => log.error("Error" + postfix)
case Result.Passed => log.info("Passed: " + postfix)
case Result.Failed => log.error("Failed: " + postfix)
}
}
override def contentLogger: Option[TLogger] = Some(log)
}

View File

@ -1,222 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Steven Blundy, Mark Harrah
*/
package sbt.impl
import scala.xml.{Elem, Group}
/* The following classes run tests for their associated test framework.
* NOTE #1: DO NOT actively use these classes. Only specify their names to LazyTestFramework
* for reflective loading. This allows using the test libraries provided on the
* project classpath instead of requiring global versions.
* NOTE #2: Keep all active uses of these test frameworks inside these classes so that sbt
* runs without error when a framework is not available at runtime and no tests for that
* framework are defined.*/
/** The test runner for ScalaCheck tests. */
private[sbt] class ScalaCheckRunner(val log: Logger, val listeners: Seq[TestReportListener], val testLoader: ClassLoader) extends BasicTestRunner
{
import org.scalacheck.{Pretty, Properties, Test}
def runTest(testClassName: String): Result.Value =
{
val test = ModuleUtilities.getObject(testClassName, testLoader).asInstanceOf[Properties]
if(Test.checkProperties(test, Test.defaultParams, propReport, testReport).find(!_._2.passed).isEmpty)
Result.Passed
else
Result.Failed
}
private def propReport(pName: String, s: Int, d: Int) {}
private def testReport(pName: String, res: Test.Result) =
{
if(res.passed)
fire(PassedEvent(pName, pretty(res)))
else
fire(FailedEvent(pName, pretty(res)))
}
private def pretty(res: Test.Result): String =
{
try { pretty1_5(res) }
catch { case e: NoSuchMethodError => pretty1_6(res) }
}
private def pretty1_5(res: Test.Result): String = Pretty.pretty(res)
private def pretty1_6(res: Test.Result): String =
{
// the following is equivalent to: (Pretty.prettyTestRes(res))(Pretty.defaultParams)
// and is necessary because of binary incompatibility in Pretty between ScalaCheck 1.5 and 1.6
val loader = getClass.getClassLoader
val prettyObj = ModuleUtilities.getObject("org.scalacheck.Pretty", loader)
val prettyInst = prettyObj.getClass.getMethod("prettyTestRes", classOf[Test.Result]).invoke(prettyObj, res)
val defaultParams = prettyObj.getClass.getMethod("defaultParams").invoke(prettyObj)
prettyInst.getClass.getMethod("apply", Class.forName("org.scalacheck.Pretty$Params", true, loader)).invoke(prettyInst, defaultParams).toString
}
}
/** The test runner for ScalaTest suites. */
private[sbt] class ScalaTestRunner(val log: Logger, val listeners: Seq[TestReportListener], val testLoader: ClassLoader) extends BasicTestRunner
{
def runTest(testClassName: String): Result.Value =
{
import org.scalatest.{Stopper, Suite}
val testClass = Class.forName(testClassName, true, testLoader).asSubclass(classOf[Suite])
val test = testClass.newInstance
val reporter = new ScalaTestReporter
val stopper = new Stopper { override def stopRequested = false }
test.execute(None, reporter, stopper, Set.empty, Set("org.scalatest.Ignore"), Map.empty, None)
if(reporter.succeeded)
Result.Passed
else
Result.Failed
}
/** An implementation of Reporter for ScalaTest. */
private class ScalaTestReporter extends org.scalatest.Reporter with NotNull
{
import org.scalatest.Report
override def testIgnored(report: Report) =
{
if(report.message.trim.isEmpty) fire(IgnoredEvent(report.name, None))
else fire(IgnoredEvent(report.name, Some(report.message.trim)))
}
override def testStarting(report: Report) { info(report, "Test starting", None) }
override def testSucceeded(report: Report) { info(report, "Test succeeded", Some(Result.Passed)) }
override def testFailed(report: Report)
{
succeeded = false
error(report, "Test failed", Some(Result.Failed))
}
override def infoProvided(report : Report) { info(report, "", None) }
override def suiteStarting(report: Report) { info(report, "Suite starting", None) }
override def suiteCompleted(report: Report) { info(report, "Suite completed", None) }
override def suiteAborted(report: Report) { error(report, "Suite aborted", None) }
override def runStarting(testCount: Int) { fire(MessageEvent("Run starting")) }
override def runStopped()
{
succeeded = false
fire(ErrorEvent("Run stopped"))
}
override def runAborted(report: Report)
{
succeeded = false
error(report, "Run aborted", None)
}
override def runCompleted() { log.info("Run completed.") }
private def error(report: Report, event: String, result: Option[Result.Value]) { logReport(report, event, result, Level.Error) }
private def info(report: Report, event: String, result: Option[Result.Value]) { logReport(report, event, result, Level.Info) }
private def logReport(report: Report, event: String, result: Option[Result.Value], level: Level.Value)
{
level match
{
case Level.Error =>
if(report.message.trim.isEmpty)
fire(TypedErrorEvent(report.name, event, None, report.throwable)(result))
else
fire(TypedErrorEvent(report.name, event, Some(report.message.trim), report.throwable)(result))
case Level.Info =>
if(report.message.trim.isEmpty)
fire(TypedEvent(report.name, event, None)(result))
else
fire(TypedEvent(report.name, event, Some(report.message.trim))(result))
case l => log.warn("Level not expected:" + l)
}
}
var succeeded = true
}
}
/** The test runner for specs tests. */
private[sbt] class SpecsRunner(val log: Logger, val listeners: Seq[TestReportListener], val testLoader: ClassLoader) extends BasicTestRunner
{
import org.specs.Specification
import org.specs.specification.{Example, Sus}
def runTest(testClassName: String): Result.Value =
{
val test = ModuleUtilities.getObject(testClassName, testLoader).asInstanceOf[Specification]
val event = reportSpecification(test)
fire(event)
if(test.isFailing)
Result.Failed
else
Result.Passed
}
/* The following is closely based on org.specs.runner.OutputReporter,
* part of specs, which is Copyright 2007-2008 Eric Torreborre.
* */
private def reportSpecification(spec: Specification): SpecificationReportEvent =
{
// this is for binary compatibility between specs 1.4.x and 1.5.0: the ancestor of Specification containing these two methods changed
def reflectSeq[T](name: String) = classOf[Specification].getMethod(name).invoke(spec).asInstanceOf[Seq[T]]
val systems = reflectSeq[Sus]("systems")
val subSpecifications = reflectSeq[Specification]("subSpecifications")
return SpecificationReportEvent(spec.successes.size, spec.failures.size, spec.errors.size, spec.skipped.size, spec.pretty,
reportSystems(systems), reportSpecifications(subSpecifications))
}
private def reportSpecifications(specifications: Seq[Specification]): Seq[SpecificationReportEvent] =
{
for(specification <- specifications) yield
reportSpecification(specification)
}
private def reportSystems(systems: Seq[Sus]): Seq[SystemReportEvent] =
{
for(system <- systems) yield
reportSystem(system)
}
private def reportSystem(sus: Sus): SystemReportEvent =
{
// for source compatibility between specs 1.4.x and 1.5.0:
// in specs 1.5.0, description is LiterateDescription
// in specs < 1.5.0, description is Elem
// LiterateDescription.desc is a Node
// Elem.child is a Seq[Node]
// each has a map[T](f: Node => T): Seq[T] defined so we use reflection to call the right method
//description.child.map(_.text) // Elem equivalent
//description.desc.map(_.text) // LiterateDescription
def formatDescription(a: AnyRef) =
{
val toMap =
try { call(a, "desc") }
catch { case e: Exception => call(a, "child") }
val mapText = (a: AnyRef) => a.getClass.getMethod("text").invoke(a)
toMap.getClass.getMethod("map", Class.forName("scala.Function1")).invoke(toMap, mapText).asInstanceOf[Seq[String]]
}
def call(a: AnyRef, m: String) = a.getClass.getMethod(m).invoke(a)
def format: Option[Seq[String]] =
{
val litD = sus.literateDescription
if(litD.isEmpty) None else Some(formatDescription(litD.get))
}
// these are for 1.6 compatibility, which removed skippedSus (skipped still exists) and examples (moved to specification)
def skipped(sus: Sus) = classOf[Sus].getMethod("skipped").invoke(sus).asInstanceOf[Seq[Throwable]]
def examples(sus: Sus) =
{
try { sus.examples } // we compile against specs 1.4.x, which has examples directly on Sus so this compiles
catch { case _: NoSuchMethodError => // It fails at runtime for specs 1.6 because examples is now on BaseSpecification
val spec = classOf[Sus].getMethod("specification").invoke(sus)
spec.getClass.getMethod("examples").invoke(spec).asInstanceOf[List[Example]]
}
}
SystemReportEvent(sus.description, sus.verb, skipped(sus), format, reportExamples(examples(sus)))
}
private def reportExamples(examples: Seq[Example]): Seq[ExampleReportEvent] =
{
for(example <- examples) yield
reportExample(example)
}
private def reportExample(example: Example): ExampleReportEvent =
{
def examples(example: Example) =
try { example.subExamples } // we compile against specs 1.4.x, which has subExamples defined on Example, so this compiles
catch { case _ : NoSuchMethodError => // It fails at runtime for specs 1.6 because examples is the new method
classOf[Example].getMethod("examples").invoke(example).asInstanceOf[Seq[Example]]
}
ExampleReportEvent(example.description, example.errors, example.failures, example.skipped, reportExamples(examples(example)))
}
}

View File

@ -15,7 +15,7 @@ import scala.util.parsing.combinator._
import TestParser._
/** Represents a test implemented by 'testClassName' of type 'superClassName'.*/
final case class TestDefinition(isModule: Boolean, testClassName: String, superClassName: String) extends NotNull
final case class TestDefinition(isModule: Boolean, testClassName: String, superClassName: String) extends org.scalatools.testing.TestFingerprint with NotNull
{
override def toString =
(if(isModule) IsModuleLiteral else "") + testClassName + SubSuperSeparator + superClassName