mirror of https://github.com/sbt/sbt.git
'test' task
This commit is contained in:
parent
9a8c62517f
commit
46a6a1af16
|
|
@ -12,6 +12,7 @@ package sbt
|
||||||
import Types._
|
import Types._
|
||||||
import xsbti.api.Definition
|
import xsbti.api.Definition
|
||||||
|
|
||||||
|
import org.scalatools.testing.Framework
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DefaultProject(val info: ProjectInfo) extends BasicProject
|
class DefaultProject(val info: ProjectInfo) extends BasicProject
|
||||||
|
|
@ -73,6 +74,43 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with
|
||||||
val classpath = cp.map(x => Path.fromFile(x.data))
|
val classpath = cp.map(x => Path.fromFile(x.data))
|
||||||
r.run(mainClass, classpath, in.splitArgs, s.log) foreach error
|
r.run(mainClass, classpath, in.splitArgs, s.log) foreach error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy val testFrameworks: Task[Seq[TestFramework]] = task {
|
||||||
|
import TestFrameworks._
|
||||||
|
Seq(ScalaCheck, Specs, ScalaTest, ScalaCheckCompat, ScalaTestCompat, SpecsCompat, JUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val testLoader: Task[ClassLoader] =
|
||||||
|
fullClasspath(TestConfig) :^: buildScalaInstance :^: KNil map { case classpath :+: instance :+: HNil =>
|
||||||
|
TestFramework.createTestLoader(data(classpath), instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val loadedTestFrameworks: Task[Map[TestFramework,Framework]] =
|
||||||
|
testFrameworks :^: streams :^: testLoader :^: KNil map { case frameworks :+: s :+: loader :+: HNil =>
|
||||||
|
frameworks.flatMap( f => f.create(loader, s.log).map( x => (f, x)).toIterable ).toMap
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val discoverTest: Task[(Seq[TestDefinition], Set[String])] =
|
||||||
|
loadedTestFrameworks :^: testCompile :^: KNil map { case frameworkMap :+: analysis :+: HNil =>
|
||||||
|
Test.discover(frameworkMap.values.toSeq, analysis)
|
||||||
|
}
|
||||||
|
lazy val definedTests: Task[Seq[TestDefinition]] = discoverTest.map(_._1)
|
||||||
|
|
||||||
|
lazy val testOptions: Task[Seq[TestOption]] = task { Nil }
|
||||||
|
|
||||||
|
lazy val test = (loadedTestFrameworks :^: testOptions :^: testLoader :^: definedTests :^: streams :^: KNil) flatMap {
|
||||||
|
case frameworkMap :+: options :+: loader :+: discovered :+: s :+: HNil =>
|
||||||
|
|
||||||
|
val toTask = (testTask: NamedTestTask) => task { testTask.run() } named(testTask.name)
|
||||||
|
def dependsOn(on: Iterable[Task[_]]): Task[Unit] = task { () } dependsOn(on.toSeq : _*)
|
||||||
|
|
||||||
|
val (begin, work, end) = Test(frameworkMap, loader, discovered, options, s.log)
|
||||||
|
val beginTasks = dependsOn( begin.map(toTask) ) // test setup tasks
|
||||||
|
val workTasks = work.map(w => toTask(w) dependsOn(beginTasks) ) // the actual tests
|
||||||
|
val endTasks = dependsOn( end.map(toTask) ) // tasks that perform test cleanup and are run regardless of success of tests
|
||||||
|
dependsOn( workTasks ) doFinally { endTasks }
|
||||||
|
}
|
||||||
|
|
||||||
lazy val clean = task {
|
lazy val clean = task {
|
||||||
IO.delete(outputDirectory)
|
IO.delete(outputDirectory)
|
||||||
}
|
}
|
||||||
|
|
@ -107,4 +145,7 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with
|
||||||
|
|
||||||
lazy val compileInputs: Task[Compile.Inputs] = compileInputsTask(Configurations.Compile, "src" / "main", buildScalaInstance) named(name + "/compile-inputs")
|
lazy val compileInputs: Task[Compile.Inputs] = compileInputsTask(Configurations.Compile, "src" / "main", buildScalaInstance) named(name + "/compile-inputs")
|
||||||
lazy val compile: Task[Analysis] = compileTask(compileInputs) named(name + "/compile")
|
lazy val compile: Task[Analysis] = compileTask(compileInputs) named(name + "/compile")
|
||||||
|
|
||||||
|
lazy val testCompileInputs: Task[Compile.Inputs] = compileInputsTask(Configurations.Test, "src" / "test", buildScalaInstance) named(name + "/test-inputs")
|
||||||
|
lazy val testCompile: Task[Analysis] = compileTask(testCompileInputs) named(name + "/test")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
/* sbt -- Simple Build Tool
|
||||||
|
* Copyright 2010 Mark Harrah
|
||||||
|
*/
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import std._
|
||||||
|
import compile.{Discovered,Discovery}
|
||||||
|
import inc.Analysis
|
||||||
|
import TaskExtra._
|
||||||
|
import Types._
|
||||||
|
import xsbti.api.Definition
|
||||||
|
|
||||||
|
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint}
|
||||||
|
|
||||||
|
import collection.mutable
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
sealed trait TestOption
|
||||||
|
object Test
|
||||||
|
{
|
||||||
|
type Output = (TestResult.Value, Map[String,TestResult.Value])
|
||||||
|
|
||||||
|
final case class Setup(setup: ClassLoader => Unit) extends TestOption
|
||||||
|
def Setup(setup: () => Unit) = new Setup(_ => setup())
|
||||||
|
|
||||||
|
final case class Cleanup(cleanup: ClassLoader => Unit) extends TestOption
|
||||||
|
def Cleanup(setup: () => Unit) = new Cleanup(_ => setup())
|
||||||
|
|
||||||
|
final case class Exclude(tests: Iterable[String]) extends TestOption
|
||||||
|
final case class Listeners(listeners: Iterable[TestReportListener]) extends TestOption
|
||||||
|
final case class Filter(filterTest: String => Boolean) extends TestOption
|
||||||
|
|
||||||
|
// args for all frameworks
|
||||||
|
def Argument(args: String*): Argument = Argument(None, args.toList)
|
||||||
|
// args for a particular test framework
|
||||||
|
def Argument(tf: TestFramework, args: String*): Argument = Argument(Some(tf), args.toList)
|
||||||
|
|
||||||
|
// None means apply to all, Some(tf) means apply to a particular framework only.
|
||||||
|
final case class Argument(framework: Option[TestFramework], args: List[String]) extends TestOption
|
||||||
|
|
||||||
|
|
||||||
|
def apply(frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, discovered: Seq[TestDefinition], options: Seq[TestOption], log: Logger) =
|
||||||
|
{
|
||||||
|
import mutable.{HashSet, ListBuffer, Map, Set}
|
||||||
|
val testFilters = new ListBuffer[String => Boolean]
|
||||||
|
val excludeTestsSet = new HashSet[String]
|
||||||
|
val setup, cleanup = new ListBuffer[ClassLoader => Unit]
|
||||||
|
val testListeners = new ListBuffer[TestReportListener]
|
||||||
|
val testArgsByFramework = Map[Framework, ListBuffer[String]]()
|
||||||
|
def frameworkArgs(framework: Framework, args: Seq[String]): Unit =
|
||||||
|
testArgsByFramework.getOrElseUpdate(framework, new ListBuffer[String]) ++= args
|
||||||
|
|
||||||
|
for(option <- options)
|
||||||
|
{
|
||||||
|
option match
|
||||||
|
{
|
||||||
|
case Filter(include) => testFilters += include
|
||||||
|
case Exclude(exclude) => excludeTestsSet ++= exclude
|
||||||
|
case Listeners(listeners) => testListeners ++= listeners
|
||||||
|
case Setup(setupFunction) => setup += setupFunction
|
||||||
|
case Cleanup(cleanupFunction) => cleanup += cleanupFunction
|
||||||
|
/**
|
||||||
|
* There are two cases here.
|
||||||
|
* The first handles TestArguments in the project file, which
|
||||||
|
* might have a TestFramework specified.
|
||||||
|
* The second handles arguments to be applied to all test frameworks.
|
||||||
|
* -- arguments from the project file that didnt have a framework specified
|
||||||
|
* -- command line arguments (ex: test-only someClass -- someArg)
|
||||||
|
* (currently, command line args must be passed to all frameworks)
|
||||||
|
*/
|
||||||
|
case Argument(Some(framework), args) => frameworkArgs(frameworks(framework), args)
|
||||||
|
case Argument(None, args) => frameworks.values.foreach { f => frameworkArgs(f, args) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(excludeTestsSet.size > 0)
|
||||||
|
log.debug(excludeTestsSet.mkString("Excluding tests: \n\t", "\n\t", ""))
|
||||||
|
|
||||||
|
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name))
|
||||||
|
val tests = discovered.filter(includeTest).toSet.toSeq
|
||||||
|
val arguments = testArgsByFramework.map { case (k,v) => (k, v.toList) } toMap;
|
||||||
|
testTask(frameworks.values.toSeq, testLoader, tests, setup.readOnly, cleanup.readOnly, log, testListeners.readOnly, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
// (overall result, individual results)
|
||||||
|
def testTask(frameworks: Seq[Framework], loader: ClassLoader, tests: Seq[TestDefinition],
|
||||||
|
userSetup: Iterable[ClassLoader => Unit], userCleanup: Iterable[ClassLoader => Unit],
|
||||||
|
log: Logger, testListeners: Seq[TestReportListener], arguments: Map[Framework, Seq[String]]): Task[Output] =
|
||||||
|
{
|
||||||
|
def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn( actions.toSeq.fork( _() ) : _*)
|
||||||
|
def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map {a => () => a(loader) }
|
||||||
|
|
||||||
|
val (frameworkSetup, runnables, frameworkCleanup) =
|
||||||
|
TestFramework.testTasks(frameworks, loader, tests, log, testListeners, arguments)
|
||||||
|
|
||||||
|
val setupTasks = fj(partApp(userSetup) :+ frameworkSetup)
|
||||||
|
val mainTasks = runnables map { case (name, test) => task { (name, test()) } dependsOn setupTasks named name }
|
||||||
|
mainTasks.toSeq.join map processResults flatMap { results =>
|
||||||
|
val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results._1))
|
||||||
|
cleanupTasks map { _ => results }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def processResults(results: Iterable[(String, TestResult.Value)]): (TestResult.Value, Map[String, TestResult.Value]) =
|
||||||
|
(overall(results.map(_._2)), results.toMap)
|
||||||
|
def overall(results: Iterable[TestResult.Value]): TestResult.Value =
|
||||||
|
(TestResult.Passed /: results) { (acc, result) => if(acc.id < result.id) result else acc }
|
||||||
|
def discover(frameworks: Seq[Framework], analysis: Analysis): (Seq[TestDefinition], Set[String]) =
|
||||||
|
discover(frameworks.flatMap(_.tests), allDefs(analysis))
|
||||||
|
|
||||||
|
def allDefs(analysis: Analysis) = analysis.apis.internal.values.flatMap(_.definitions).toSeq
|
||||||
|
def discover(fingerprints: Seq[Fingerprint], definitions: Seq[Definition]): (Seq[TestDefinition], Set[String]) =
|
||||||
|
{
|
||||||
|
val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superClassName, sub.isModule, sub) };
|
||||||
|
val annotations = fingerprints collect { case ann: AnnotatedFingerprint => (ann.annotationName, ann.isModule, ann) };
|
||||||
|
|
||||||
|
def firsts[A,B,C](s: Seq[(A,B,C)]): Set[A] = s.map(_._1).toSet
|
||||||
|
def defined(in: Seq[(String,Boolean,Fingerprint)], names: Set[String], isModule: Boolean): Seq[Fingerprint] =
|
||||||
|
in collect { case (name, mod, print) if names(name) && isModule == mod => print }
|
||||||
|
|
||||||
|
def toFingerprints(d: Discovered): Seq[Fingerprint] =
|
||||||
|
defined(subclasses, d.baseClasses, d.isModule) ++
|
||||||
|
defined(annotations, d.annotations, d.isModule)
|
||||||
|
|
||||||
|
val discovered = Discovery(firsts(subclasses), firsts(annotations))(definitions)
|
||||||
|
val tests = for( (df, di) <- discovered; fingerprint <- toFingerprints(di) ) yield new TestDefinition(df.name, fingerprint)
|
||||||
|
val mains = discovered collect { case (df, di) if di.hasMain => df.name }
|
||||||
|
(tests, mains.toSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
def showResults(log: Logger, results: (TestResult.Value, Map[String, TestResult.Value])) =
|
||||||
|
{
|
||||||
|
import TestResult.{Error, Failed, Passed}
|
||||||
|
|
||||||
|
def select(Tpe: TestResult.Value) = results._2 collect { case (name, Tpe) => name }
|
||||||
|
|
||||||
|
val failures = select(Failed)
|
||||||
|
val errors = select(Error)
|
||||||
|
val passed = select(Passed)
|
||||||
|
|
||||||
|
def show(label: String, level: Level.Value, tests: Iterable[String]): Unit =
|
||||||
|
if(!tests.isEmpty)
|
||||||
|
{
|
||||||
|
log.log(level, label)
|
||||||
|
log.log(level, tests.mkString("\t", "\n\t", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
show("Passed tests:", Level.Debug, passed )
|
||||||
|
show("Failed tests:", Level.Error, failures)
|
||||||
|
show("Error during tests:", Level.Error, errors)
|
||||||
|
|
||||||
|
if(!failures.isEmpty || !errors.isEmpty)
|
||||||
|
error("Tests unsuccessful")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -87,7 +87,8 @@ class XSbt(info: ProjectInfo) extends ParentProject(info) with NoCrossPaths
|
||||||
val stdTaskSub = testedBase(tasksPath / "standard", "Task System", taskSub, collectionSub, logSub, ioSub, processSub)
|
val stdTaskSub = testedBase(tasksPath / "standard", "Task System", taskSub, collectionSub, logSub, ioSub, processSub)
|
||||||
// The main integration project for sbt. It brings all of the subsystems together, configures them, and provides for overriding conventions.
|
// The main integration project for sbt. It brings all of the subsystems together, configures them, and provides for overriding conventions.
|
||||||
val mainSub = baseProject("main", "Main",
|
val mainSub = baseProject("main", "Main",
|
||||||
buildSub, compileIncrementalSub, compilerSub, completeSub, discoverySub, ioSub, logSub, processSub, taskSub, stdTaskSub, runSub, trackingSub)
|
buildSub, compileIncrementalSub, compilerSub, completeSub, discoverySub,
|
||||||
|
ioSub, logSub, processSub, taskSub, stdTaskSub, runSub, trackingSub, testingSub)
|
||||||
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
|
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
|
||||||
val sbtSub = project(sbtPath, "Simple Build Tool", new Sbt(_), mainSub) // technically, we need a dependency on all of mainSub's dependencies, but we don't do that since this is strictly an integration project
|
val sbtSub = project(sbtPath, "Simple Build Tool", new Sbt(_), mainSub) // technically, we need a dependency on all of mainSub's dependencies, but we don't do that since this is strictly an integration project
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ sealed trait ProcessPipe
|
||||||
|
|
||||||
trait TaskExtra
|
trait TaskExtra
|
||||||
{
|
{
|
||||||
|
final def nop: Task[Unit] = const( () )
|
||||||
|
final def const[T](t: T): Task[T] = task(t)
|
||||||
|
|
||||||
final def cross[T](key: AttributeKey[T])(values: T*): Task[T] =
|
final def cross[T](key: AttributeKey[T])(values: T*): Task[T] =
|
||||||
CrossAction( for(v <- values) yield ( AttributeMap.empty put (key, v), task(v) ) )
|
CrossAction( for(v <- values) yield ( AttributeMap.empty put (key, v), task(v) ) )
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
*/
|
*/
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint}
|
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint}
|
||||||
import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger}
|
import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger}
|
||||||
import classpath.{ClasspathUtilities, DualLoader, FilteredLoader}
|
import classpath.{ClasspathUtilities, DualLoader, FilteredLoader}
|
||||||
|
|
||||||
object Result extends Enumeration
|
object TestResult extends Enumeration
|
||||||
{
|
{
|
||||||
val Error, Passed, Failed = Value
|
val Passed, Failed, Error = Value
|
||||||
}
|
}
|
||||||
|
|
||||||
object TestFrameworks
|
object TestFrameworks
|
||||||
|
|
@ -55,7 +56,7 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq
|
||||||
case _ => error("Framework '" + framework + "' does not support test '" + testDefinition + "'")
|
case _ => error("Framework '" + framework + "' does not support test '" + testDefinition + "'")
|
||||||
}
|
}
|
||||||
|
|
||||||
final def run(testDefinition: TestDefinition, args: Seq[String]): Result.Value =
|
final def run(testDefinition: TestDefinition, args: Seq[String]): TestResult.Value =
|
||||||
{
|
{
|
||||||
log.debug("Running " + testDefinition + " with arguments " + args.mkString(", "))
|
log.debug("Running " + testDefinition + " with arguments " + args.mkString(", "))
|
||||||
val name = testDefinition.name
|
val name = testDefinition.name
|
||||||
|
|
@ -73,7 +74,7 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq
|
||||||
safeListenersCall(_.startGroup(name))
|
safeListenersCall(_.startGroup(name))
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
val result = runTest().getOrElse(Result.Passed)
|
val result = runTest().getOrElse(TestResult.Passed)
|
||||||
safeListenersCall(_.endGroup(name, result))
|
safeListenersCall(_.endGroup(name, result))
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -81,7 +82,7 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq
|
||||||
{
|
{
|
||||||
case e =>
|
case e =>
|
||||||
safeListenersCall(_.endGroup(name, e))
|
safeListenersCall(_.endGroup(name, e))
|
||||||
Result.Error
|
TestResult.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,8 +90,6 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq
|
||||||
TestFramework.safeForeach(listeners, log)(call)
|
TestFramework.safeForeach(listeners, log)(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class NamedTestTask(val name: String, action: => Unit) { def run() = action }
|
|
||||||
|
|
||||||
object TestFramework
|
object TestFramework
|
||||||
{
|
{
|
||||||
def getTests(framework: Framework): Seq[Fingerprint] =
|
def getTests(framework: Framework): Seq[Fingerprint] =
|
||||||
|
|
@ -117,39 +116,28 @@ object TestFramework
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
||||||
import scala.collection.{immutable, Map, Set}
|
def testTasks(frameworks: Seq[Framework],
|
||||||
|
testLoader: ClassLoader,
|
||||||
def testTasks(frameworks: Seq[TestFramework],
|
|
||||||
classpath: Iterable[Path],
|
|
||||||
scalaInstance: ScalaInstance,
|
|
||||||
tests: Seq[TestDefinition],
|
tests: Seq[TestDefinition],
|
||||||
log: Logger,
|
log: Logger,
|
||||||
listeners: Seq[TestReportListener],
|
listeners: Seq[TestReportListener],
|
||||||
endErrorsEnabled: Boolean,
|
testArgsByFramework: Map[Framework, Seq[String]]):
|
||||||
setup: Iterable[ClassLoader => Unit],
|
(() => Unit, Iterable[(String, () => TestResult.Value)], TestResult.Value => () => Unit) =
|
||||||
cleanup: Iterable[ClassLoader => Unit],
|
|
||||||
testArgsByFramework: Map[TestFramework, Seq[String]]):
|
|
||||||
(Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) =
|
|
||||||
{
|
{
|
||||||
val (loader, tempDir) = createTestLoader(classpath, scalaInstance)
|
val arguments = testArgsByFramework withDefaultValue Nil
|
||||||
val arguments = immutable.Map() ++
|
val mappedTests = testMap(frameworks, tests, arguments)
|
||||||
( for(framework <- frameworks; created <- framework.create(loader, log)) yield
|
|
||||||
(created, testArgsByFramework.getOrElse(framework, Nil)) )
|
|
||||||
val cleanTmp = (_: ClassLoader) => IO.delete(tempDir)
|
|
||||||
|
|
||||||
val mappedTests = testMap(arguments.keys.toList, tests, arguments)
|
|
||||||
if(mappedTests.isEmpty)
|
if(mappedTests.isEmpty)
|
||||||
(new NamedTestTask(TestStartName, None) :: Nil, Nil, new NamedTestTask(TestFinishName, { log.info("No tests to run."); cleanTmp(loader) }) :: Nil )
|
(() => (), Nil, _ => () => log.info("No tests to run.") )
|
||||||
else
|
else
|
||||||
createTestTasks(loader, mappedTests, log, listeners, endErrorsEnabled, setup, Seq(cleanTmp) ++ cleanup)
|
createTestTasks(testLoader, mappedTests, log, listeners)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
|
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
|
||||||
immutable.Map[Framework, (Set[TestDefinition], Seq[String])] =
|
Map[Framework, (Set[TestDefinition], Seq[String])] =
|
||||||
{
|
{
|
||||||
import scala.collection.mutable.{HashMap, HashSet, Set}
|
import scala.collection.mutable.{HashMap, HashSet, Set}
|
||||||
val map = new HashMap[Framework, Set[TestDefinition]]
|
val map = new HashMap[Framework, Set[TestDefinition]]
|
||||||
def assignTests(): Unit =
|
def assignTests()
|
||||||
{
|
{
|
||||||
for(test <- tests if !map.values.exists(_.contains(test)))
|
for(test <- tests if !map.values.exists(_.contains(test)))
|
||||||
{
|
{
|
||||||
|
|
@ -160,70 +148,38 @@ object TestFramework
|
||||||
}
|
}
|
||||||
if(!frameworks.isEmpty)
|
if(!frameworks.isEmpty)
|
||||||
assignTests()
|
assignTests()
|
||||||
(immutable.Map() ++ map) transform { (framework, tests) => (tests, args(framework)) }
|
map.toMap transform { (framework, tests) => (tests.toSet, args(framework)) };
|
||||||
}
|
}
|
||||||
private def createTasks[T](work: Iterable[T => Unit], baseName: String, input: T) =
|
|
||||||
work.toList.zipWithIndex.map{ case (work, index) => new NamedTestTask(baseName + " " + (index+1), work(input)) }
|
|
||||||
|
|
||||||
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], log: Logger,
|
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], log: Logger, listeners: Seq[TestReportListener]) =
|
||||||
listeners: Seq[TestReportListener], endErrorsEnabled: Boolean, setup: Iterable[ClassLoader => Unit],
|
|
||||||
cleanup: Iterable[ClassLoader => Unit]) =
|
|
||||||
{
|
{
|
||||||
val testsListeners = listeners.filter(_.isInstanceOf[TestsListener]).map(_.asInstanceOf[TestsListener])
|
val testsListeners = listeners collect { case tl: TestsListener => tl }
|
||||||
def foreachListenerSafe(f: TestsListener => Unit): Unit = safeForeach(testsListeners, log)(f)
|
def foreachListenerSafe(f: TestsListener => Unit): () => Unit = () => safeForeach(testsListeners, log)(f)
|
||||||
|
|
||||||
import Result.{Error,Passed,Failed}
|
import TestResult.{Error,Passed,Failed}
|
||||||
object result
|
|
||||||
{
|
val startTask = foreachListenerSafe(_.doInit)
|
||||||
private[this] var value: Result.Value = Passed
|
|
||||||
def apply() = synchronized { value }
|
|
||||||
def update(v: Result.Value): Unit = synchronized { if(value != Error) value = v }
|
|
||||||
}
|
|
||||||
val startTask = new NamedTestTask(TestStartName, {foreachListenerSafe(_.doInit); None}) :: createTasks(setup, "Test setup", loader)
|
|
||||||
val testTasks =
|
val testTasks =
|
||||||
tests flatMap { case (framework, (testDefinitions, testArgs)) =>
|
tests.view flatMap { case (framework, (testDefinitions, testArgs)) =>
|
||||||
|
|
||||||
val runner = new TestRunner(framework, loader, listeners, log)
|
val runner = new TestRunner(framework, loader, listeners, log)
|
||||||
for(testDefinition <- testDefinitions) yield
|
for(testDefinition <- testDefinitions) yield
|
||||||
{
|
{
|
||||||
def runTest() =
|
val runTest = () => withContextLoader(loader) { runner.run(testDefinition, testArgs) }
|
||||||
|
(testDefinition.name, runTest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
val oldLoader = Thread.currentThread.getContextClassLoader
|
||||||
Thread.currentThread.setContextClassLoader(loader)
|
Thread.currentThread.setContextClassLoader(loader)
|
||||||
try {
|
try { eval } finally { Thread.currentThread.setContextClassLoader(oldLoader) }
|
||||||
runner.run(testDefinition, testArgs) match
|
|
||||||
{
|
|
||||||
case Error => result() = Error; Some("ERROR occurred during testing.")
|
|
||||||
case Failed => result() = Failed; Some("Test FAILED")
|
|
||||||
case _ => None
|
|
||||||
}
|
}
|
||||||
}
|
def createTestLoader(classpath: Seq[File], scalaInstance: ScalaInstance): ClassLoader =
|
||||||
finally {
|
|
||||||
Thread.currentThread.setContextClassLoader(oldLoader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new NamedTestTask(testDefinition.name, runTest())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def end() =
|
|
||||||
{
|
|
||||||
foreachListenerSafe(_.doComplete(result()))
|
|
||||||
result() match
|
|
||||||
{
|
|
||||||
case Error => if(endErrorsEnabled) Some("ERROR occurred during testing.") else None
|
|
||||||
case Failed => if(endErrorsEnabled) Some("One or more tests FAILED.") else None
|
|
||||||
case Passed =>
|
|
||||||
{
|
|
||||||
log.info(" ")
|
|
||||||
log.info("All tests PASSED.")
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val endTask = new NamedTestTask(TestFinishName, end() ) :: createTasks(cleanup, "Test cleanup", loader)
|
|
||||||
(startTask, testTasks, endTask)
|
|
||||||
}
|
|
||||||
def createTestLoader(classpath: Iterable[Path], scalaInstance: ScalaInstance): (ClassLoader, Path) =
|
|
||||||
{
|
{
|
||||||
val filterCompilerLoader = new FilteredLoader(scalaInstance.loader, ScalaCompilerJarPackages)
|
val filterCompilerLoader = new FilteredLoader(scalaInstance.loader, ScalaCompilerJarPackages)
|
||||||
val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.")
|
val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ trait TestReportListener
|
||||||
/** called if there was an error during test */
|
/** called if there was an error during test */
|
||||||
def endGroup(name: String, t: Throwable)
|
def endGroup(name: String, t: Throwable)
|
||||||
/** called if test completed */
|
/** called if test completed */
|
||||||
def endGroup(name: String, result: Result.Value)
|
def endGroup(name: String, result: TestResult.Value)
|
||||||
/** Used by the test framework for logging test results*/
|
/** Used by the test framework for logging test results*/
|
||||||
def contentLogger: Option[TLogger] = None
|
def contentLogger: Option[TLogger] = None
|
||||||
}
|
}
|
||||||
|
|
@ -25,23 +25,23 @@ trait TestsListener extends TestReportListener
|
||||||
/** called once, at beginning. */
|
/** called once, at beginning. */
|
||||||
def doInit
|
def doInit
|
||||||
/** called once, at end. */
|
/** called once, at end. */
|
||||||
def doComplete(finalResult: Result.Value)
|
def doComplete(finalResult: TestResult.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class TestEvent extends NotNull
|
abstract class TestEvent extends NotNull
|
||||||
{
|
{
|
||||||
def result: Option[Result.Value]
|
def result: Option[TestResult.Value]
|
||||||
def detail: Seq[TEvent] = Nil
|
def detail: Seq[TEvent] = Nil
|
||||||
}
|
}
|
||||||
object TestEvent
|
object TestEvent
|
||||||
{
|
{
|
||||||
def apply(events: Seq[TEvent]): TestEvent =
|
def apply(events: Seq[TEvent]): TestEvent =
|
||||||
{
|
{
|
||||||
val overallResult = (Result.Passed /: events) { (sum, event) =>
|
val overallResult = (TestResult.Passed /: events) { (sum, event) =>
|
||||||
val result = event.result
|
val result = event.result
|
||||||
if(sum == Result.Error || result == TResult.Error) Result.Error
|
if(sum == TestResult.Error || result == TResult.Error) TestResult.Error
|
||||||
else if(sum == Result.Failed || result == TResult.Failure) Result.Failed
|
else if(sum == TestResult.Failed || result == TResult.Failure) TestResult.Failed
|
||||||
else Result.Passed
|
else TestResult.Passed
|
||||||
}
|
}
|
||||||
new TestEvent {
|
new TestEvent {
|
||||||
val result = Some(overallResult)
|
val result = Some(overallResult)
|
||||||
|
|
@ -76,7 +76,7 @@ class TestLogger(val log: TLogger) extends TestsListener
|
||||||
log.trace(t)
|
log.trace(t)
|
||||||
log.error("Could not run test " + name + ": " + t.toString)
|
log.error("Could not run test " + name + ": " + t.toString)
|
||||||
}
|
}
|
||||||
def endGroup(name: String, result: Result.Value) {}
|
def endGroup(name: String, result: TestResult.Value) {}
|
||||||
protected def count(event: TEvent): Unit =
|
protected def count(event: TEvent): Unit =
|
||||||
{
|
{
|
||||||
event.result match
|
event.result match
|
||||||
|
|
@ -95,15 +95,15 @@ class TestLogger(val log: TLogger) extends TestsListener
|
||||||
skipped = 0
|
skipped = 0
|
||||||
}
|
}
|
||||||
/** called once, at end. */
|
/** called once, at end. */
|
||||||
def doComplete(finalResult: Result.Value): Unit =
|
def doComplete(finalResult: TestResult.Value): Unit =
|
||||||
{
|
{
|
||||||
val totalCount = failures + errors + skipped + passed
|
val totalCount = failures + errors + skipped + passed
|
||||||
val postfix = ": Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped
|
val postfix = ": Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped
|
||||||
finalResult match
|
finalResult match
|
||||||
{
|
{
|
||||||
case Result.Error => log.error("Error" + postfix)
|
case TestResult.Error => log.error("Error" + postfix)
|
||||||
case Result.Passed => log.info("Passed: " + postfix)
|
case TestResult.Passed => log.info("Passed: " + postfix)
|
||||||
case Result.Failed => log.error("Failed: " + postfix)
|
case TestResult.Failed => log.error("Failed: " + postfix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override def contentLogger: Option[TLogger] = Some(log)
|
override def contentLogger: Option[TLogger] = Some(log)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue