2009-06-26 03:26:06 +02:00
|
|
|
/* sbt -- Simple Build Tool
|
2009-12-23 06:10:53 +01:00
|
|
|
* Copyright 2008, 2009 Steven Blundy, Mark Harrah, Josh Cough
|
2009-06-26 03:26:06 +02:00
|
|
|
*/
|
|
|
|
|
package sbt
|
|
|
|
|
|
2010-11-24 20:03:26 +01:00
|
|
|
import java.io.File
|
2009-10-23 01:10:54 +02:00
|
|
|
import java.net.URLClassLoader
|
2010-03-28 06:05:40 +02:00
|
|
|
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint}
|
|
|
|
|
import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger}
|
2010-07-05 18:53:37 +02:00
|
|
|
import classpath.{ClasspathUtilities, DualLoader, FilteredLoader}
|
2009-10-23 01:10:54 +02:00
|
|
|
|
2010-11-24 20:03:26 +01:00
|
|
|
object TestResult extends Enumeration
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-11-24 20:03:26 +01:00
|
|
|
val Passed, Failed, Error = Value
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
|
2009-10-23 01:10:54 +02:00
|
|
|
object TestFrameworks
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-11-27 02:53:28 +01:00
|
|
|
val ScalaCheck = new TestFramework("org.scalacheck.ScalaCheckFramework")
|
2009-10-23 14:10:52 +02:00
|
|
|
val ScalaTest = new TestFramework("org.scalatest.tools.ScalaTestFramework")
|
2009-11-27 19:12:58 +01:00
|
|
|
val Specs = new TestFramework("org.specs.runner.SpecsFramework")
|
2009-12-22 03:32:10 +01:00
|
|
|
val JUnit = new TestFramework("com.novocode.junit.JUnitFramework")
|
2010-01-26 04:52:50 +01:00
|
|
|
// These are compatibility frameworks included in the 'test-compat' library
|
|
|
|
|
val ScalaCheckCompat = new TestFramework("sbt.impl.ScalaCheckFramework")
|
|
|
|
|
val ScalaTestCompat = new TestFramework("sbt.impl.ScalaTestFramework")
|
|
|
|
|
val SpecsCompat = new TestFramework("sbt.impl.SpecsFramework")
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2009-11-27 02:53:28 +01:00
|
|
|
|
2009-10-23 01:10:54 +02:00
|
|
|
class TestFramework(val implClassName: String) extends NotNull
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-10-23 15:18:00 +02:00
|
|
|
def create(loader: ClassLoader, log: Logger): Option[Framework] =
|
|
|
|
|
{
|
|
|
|
|
try { Some(Class.forName(implClassName, true, loader).newInstance.asInstanceOf[Framework]) }
|
|
|
|
|
catch { case e: ClassNotFoundException => log.debug("Framework implementation '" + implClassName + "' not present."); None }
|
|
|
|
|
}
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2010-03-28 06:05:40 +02:00
|
|
|
final class TestDefinition(val name: String, val fingerprint: Fingerprint) extends NotNull
|
|
|
|
|
{
|
|
|
|
|
override def toString = "Test " + name + " : " + fingerprint
|
|
|
|
|
override def equals(t: Any) =
|
|
|
|
|
t match
|
|
|
|
|
{
|
|
|
|
|
case r: TestDefinition => name == r.name && TestFramework.matches(fingerprint, r.fingerprint)
|
|
|
|
|
case _ => false
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-11-27 02:53:28 +01:00
|
|
|
|
2010-07-05 18:53:37 +02:00
|
|
|
final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq[TestReportListener], log: Logger)
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-10-23 01:10:54 +02:00
|
|
|
private[this] val delegate = framework.testRunner(loader, listeners.flatMap(_.contentLogger).toArray)
|
2010-03-28 06:05:40 +02:00
|
|
|
private[this] def run(testDefinition: TestDefinition, handler: EventHandler, args: Array[String]): Unit =
|
|
|
|
|
(testDefinition.fingerprint, delegate) match
|
|
|
|
|
{
|
|
|
|
|
case (simple: TestFingerprint, _) => delegate.run(testDefinition.name, simple, handler, args)
|
|
|
|
|
case (basic, runner2: Runner2) => runner2.run(testDefinition.name, basic, handler, args)
|
|
|
|
|
case _ => error("Framework '" + framework + "' does not support test '" + testDefinition + "'")
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-24 20:03:26 +01:00
|
|
|
final def run(testDefinition: TestDefinition, args: Seq[String]): TestResult.Value =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-01-21 05:06:10 +01:00
|
|
|
log.debug("Running " + testDefinition + " with arguments " + args.mkString(", "))
|
2010-03-28 06:05:40 +02:00
|
|
|
val name = testDefinition.name
|
2009-10-23 01:10:54 +02:00
|
|
|
def runTest() =
|
|
|
|
|
{
|
2009-11-27 02:53:28 +01:00
|
|
|
// here we get the results! here is where we'd pass in the event listener
|
|
|
|
|
val results = new scala.collection.mutable.ListBuffer[Event]
|
|
|
|
|
val handler = new EventHandler { def handle(e:Event){ results += e } }
|
2010-03-28 06:05:40 +02:00
|
|
|
run(testDefinition, handler, args.toArray)
|
2009-10-23 01:10:54 +02:00
|
|
|
val event = TestEvent(results)
|
|
|
|
|
safeListenersCall(_.testEvent( event ))
|
|
|
|
|
event.result
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-28 06:05:40 +02:00
|
|
|
safeListenersCall(_.startGroup(name))
|
2009-06-26 03:26:06 +02:00
|
|
|
try
|
|
|
|
|
{
|
2010-11-24 20:03:26 +01:00
|
|
|
val result = runTest().getOrElse(TestResult.Passed)
|
2010-03-28 06:05:40 +02:00
|
|
|
safeListenersCall(_.endGroup(name, result))
|
2009-06-26 03:26:06 +02:00
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
case e =>
|
2010-03-28 06:05:40 +02:00
|
|
|
safeListenersCall(_.endGroup(name, e))
|
2010-11-24 20:03:26 +01:00
|
|
|
TestResult.Error
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-23 01:10:54 +02:00
|
|
|
protected def safeListenersCall(call: (TestReportListener) => Unit): Unit =
|
|
|
|
|
TestFramework.safeForeach(listeners, log)(call)
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object TestFramework
|
|
|
|
|
{
|
2010-03-28 06:05:40 +02:00
|
|
|
def getTests(framework: Framework): Seq[Fingerprint] =
|
|
|
|
|
framework.getClass.getMethod("tests").invoke(framework) match
|
|
|
|
|
{
|
|
|
|
|
case newStyle: Array[Fingerprint] => newStyle.toList
|
|
|
|
|
case oldStyle: Array[TestFingerprint] => oldStyle.toList
|
|
|
|
|
case _ => error("Could not call 'tests' on framework " + framework)
|
|
|
|
|
}
|
|
|
|
|
|
2010-04-25 04:44:14 +02:00
|
|
|
private val ScalaCompilerJarPackages = "scala.tools." :: "jline." :: "ch.epfl.lamp." :: Nil
|
2009-06-26 03:26:06 +02:00
|
|
|
|
|
|
|
|
private val TestStartName = "test-start"
|
|
|
|
|
private val TestFinishName = "test-finish"
|
|
|
|
|
|
2009-10-23 01:10:54 +02:00
|
|
|
private[sbt] def safeForeach[T](it: Iterable[T], log: Logger)(f: T => Unit): Unit =
|
2010-07-05 18:53:37 +02:00
|
|
|
it.foreach(i => try f(i) catch { case e: Exception => log.trace(e); log.error(e.toString) })
|
2009-10-23 01:10:54 +02:00
|
|
|
|
2010-03-28 06:05:40 +02:00
|
|
|
def matches(a: Fingerprint, b: Fingerprint) =
|
|
|
|
|
(a, b) match
|
|
|
|
|
{
|
|
|
|
|
case (a: SubclassFingerprint, b: SubclassFingerprint) => a.isModule == b.isModule && a.superClassName == b.superClassName
|
|
|
|
|
case (a: AnnotatedFingerprint, b: AnnotatedFingerprint) => a.isModule == b.isModule && a.annotationName == b.annotationName
|
|
|
|
|
case _ => false
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-24 20:03:26 +01:00
|
|
|
def testTasks(frameworks: Seq[Framework],
|
|
|
|
|
testLoader: ClassLoader,
|
2010-01-21 05:06:10 +01:00
|
|
|
tests: Seq[TestDefinition],
|
|
|
|
|
log: Logger,
|
|
|
|
|
listeners: Seq[TestReportListener],
|
2010-11-24 20:03:26 +01:00
|
|
|
testArgsByFramework: Map[Framework, Seq[String]]):
|
|
|
|
|
(() => Unit, Iterable[(String, () => TestResult.Value)], TestResult.Value => () => Unit) =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-11-24 20:03:26 +01:00
|
|
|
val arguments = testArgsByFramework withDefaultValue Nil
|
|
|
|
|
val mappedTests = testMap(frameworks, tests, arguments)
|
2009-06-26 03:26:06 +02:00
|
|
|
if(mappedTests.isEmpty)
|
2010-11-24 20:03:26 +01:00
|
|
|
(() => (), Nil, _ => () => log.info("No tests to run.") )
|
2009-06-26 03:26:06 +02:00
|
|
|
else
|
2010-11-24 20:03:26 +01:00
|
|
|
createTestTasks(testLoader, mappedTests, log, listeners)
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2010-01-21 05:06:10 +01:00
|
|
|
|
|
|
|
|
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
|
2010-11-24 20:03:26 +01:00
|
|
|
Map[Framework, (Set[TestDefinition], Seq[String])] =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
|
|
|
|
import scala.collection.mutable.{HashMap, HashSet, Set}
|
2009-10-23 01:10:54 +02:00
|
|
|
val map = new HashMap[Framework, Set[TestDefinition]]
|
2010-11-24 20:03:26 +01:00
|
|
|
def assignTests()
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2009-10-23 01:10:54 +02:00
|
|
|
for(test <- tests if !map.values.exists(_.contains(test)))
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-03-28 06:05:40 +02:00
|
|
|
def isTestForFramework(framework: Framework) = getTests(framework).exists {t => matches(t, test.fingerprint) }
|
2009-06-26 03:26:06 +02:00
|
|
|
for(framework <- frameworks.find(isTestForFramework))
|
2009-10-23 01:10:54 +02:00
|
|
|
map.getOrElseUpdate(framework, new HashSet[TestDefinition]) += test
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
2009-10-23 01:10:54 +02:00
|
|
|
if(!frameworks.isEmpty)
|
|
|
|
|
assignTests()
|
2010-11-24 20:03:26 +01:00
|
|
|
map.toMap transform { (framework, tests) => (tests.toSet, args(framework)) };
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
|
2010-11-24 20:03:26 +01:00
|
|
|
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], log: Logger, listeners: Seq[TestReportListener]) =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-11-24 20:03:26 +01:00
|
|
|
val testsListeners = listeners collect { case tl: TestsListener => tl }
|
|
|
|
|
def foreachListenerSafe(f: TestsListener => Unit): () => Unit = () => safeForeach(testsListeners, log)(f)
|
2009-06-26 03:26:06 +02:00
|
|
|
|
2010-11-24 20:03:26 +01:00
|
|
|
import TestResult.{Error,Passed,Failed}
|
|
|
|
|
|
|
|
|
|
val startTask = foreachListenerSafe(_.doInit)
|
2009-06-26 03:26:06 +02:00
|
|
|
val testTasks =
|
2010-11-24 20:03:26 +01:00
|
|
|
tests.view flatMap { case (framework, (testDefinitions, testArgs)) =>
|
2009-06-26 03:26:06 +02:00
|
|
|
|
2009-10-23 01:10:54 +02:00
|
|
|
val runner = new TestRunner(framework, loader, listeners, log)
|
|
|
|
|
for(testDefinition <- testDefinitions) yield
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-11-24 20:03:26 +01:00
|
|
|
val runTest = () => withContextLoader(loader) { runner.run(testDefinition, testArgs) }
|
|
|
|
|
(testDefinition.name, runTest)
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
2010-11-24 20:03:26 +01:00
|
|
|
|
|
|
|
|
val endTask = (result: TestResult.Value) => foreachListenerSafe(_.doComplete(result))
|
|
|
|
|
(startTask, testTasks.toList, endTask)
|
|
|
|
|
}
|
|
|
|
|
private[this] def withContextLoader[T](loader: ClassLoader)(eval: => T): T =
|
|
|
|
|
{
|
|
|
|
|
val oldLoader = Thread.currentThread.getContextClassLoader
|
|
|
|
|
Thread.currentThread.setContextClassLoader(loader)
|
|
|
|
|
try { eval } finally { Thread.currentThread.setContextClassLoader(oldLoader) }
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
2010-11-24 20:03:26 +01:00
|
|
|
def createTestLoader(classpath: Seq[File], scalaInstance: ScalaInstance): ClassLoader =
|
2009-06-26 03:26:06 +02:00
|
|
|
{
|
2010-05-21 15:32:14 +02:00
|
|
|
val filterCompilerLoader = new FilteredLoader(scalaInstance.loader, ScalaCompilerJarPackages)
|
2009-11-10 05:29:19 +01:00
|
|
|
val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.")
|
|
|
|
|
val notInterfaceFilter = (name: String) => !interfaceFilter(name)
|
2010-07-05 18:53:37 +02:00
|
|
|
val dual = new DualLoader(filterCompilerLoader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false)
|
2010-05-21 15:32:14 +02:00
|
|
|
ClasspathUtilities.makeLoader(classpath, dual, scalaInstance)
|
2009-06-26 03:26:06 +02:00
|
|
|
}
|
|
|
|
|
}
|