mirror of https://github.com/sbt/sbt.git
Use test-interface for test support.
This commit is contained in:
parent
75b8fb7208
commit
e417e19b02
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue